/* ==================================================== ======== ======= *
 *
 *  uubox.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	"@(#)uubox.cc	ubit:b1.11.5"
#include <udefs.hh>
#include <ubrick.hh>
#include <ucall.hh>
#include <ustr.hh>
#include <uctrl.hh>
#include <ubox.hh>
#include <uwin.hh>
#include <uview.hh>
#include <uviewImpl.hh>
#include <uevent.hh>
#include <ustyle.hh>
#include <uborder.hh>
#include <ugraph.hh>
#include <uobs.hh>
#include <umenu.hh>

const UClass UGroup::uclass("UGroup");
const UClass UBox::uclass("UBox");
const UClass UVbox::uclass("UVbox");
const UClass UHbox::uclass("UHbox");
const UClass UBar::uclass("UBar");

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

UStyle* UGroup::style = null;

const UStyle* UGroup::getStyle(const UBox*) {
  if (!style) {
    style = new UStyle(null);
  }
  return style;
}

UGroup::UGroup(UArgs a) : UCtrl() {
  setCmodes(UMode::CAN_SHOW | UMode::GROUP, true);
  children = a.children;

  // call add-initialization procedure of child
  for (ULink *l = children.first(); l!=null; l = l->next())
    l->brick()->addingTo(l, this);   //VOIR addlist !!!
}

UGroup& ugroup(UArgs l) {return *(new UGroup(l));}

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

UStyle* UBox::style = null;

const UStyle* UBox::getStyle(const UBox*) {
  if (!style) {
    style = new UStyle(null);
    style->local.orient   = UOrient::horizontal.get();
    style->local.halign   = UHalign::left.get();
    style->local.valign   = UValign::flex.get();
    style->local.hspacing = 1;
    style->local.vspacing = 1;
    //style->local.vmargin  = 0;  style->local.hmargin  = 0;
    style->local.padding.set(0,0);
  }
  return style;
}

UBox::UBox(UArgs a) : UGroup(a) {
  setCmodes(UMode::BOX, true);
  setCmodes(UMode::GROUP, false);
  // number of horizontal and vertical flexible children
  flexCount = 0;
}

UBox& ubox(UArgs l) {return *(new UBox(l));}

//UWhen& UBox::when(const UOn& cd) {
//  UWhen *wh = new UWhen(this, cd);
//  add(wh);
//  return *wh;
//}

/* ==================================================== ======== ======= */
// UHbox = synonym of UBox

UHbox& uhbox(UArgs l)  {return *(new UHbox(l));}

/* ==================================================== [Elc:00] ======= */
/* ==================================================== ======== ======= */

UStyle *UVbox::style = null;

const UStyle* UVbox::getStyle(const UBox*) {
  if (!style) {
    style = new UStyle(null);
    style->local.orient   = UOrient::vertical.get();
    style->local.halign   = UHalign::flex.get();
    style->local.valign   = UValign::top.get();
    style->local.hspacing = 1;
    style->local.vspacing = 1;
    //style->local.vmargin  = 0;  style->local.hmargin  = 0;
    style->local.padding.set(0,0);
  }
  return style;
}

UVbox& uvbox(UArgs l)  {return *(new UVbox(l));}

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

UStyle *UBar::style = null;

const UStyle* UBar::getStyle(const UBox*) {
  if (!style) {
    style = new UStyle(null);
    style->local.orient   = UOrient::horizontal.get();
    style->local.halign   = UHalign::left.get();
    style->local.valign   = UValign::flex.get();
    //style->local.vmargin  = 1;  style->local.hmargin  = 1;
    style->local.padding.set(1, 1);
    style->local.hspacing = 7;
    style->local.vspacing = 7;
    style->local.border   = &UBorder::shadowOut;
  }
  return style;
}

UBar::UBar(UArgs a): UBox(a) {}

UBar& ubar(UArgs l)  {return *(new UBar(l));}

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

UBoxLink::UBoxLink(UBox *box) : ULink(box) {
  views = null;
  viewCount = 0; 
  //deletedCount = 0;
  //viewStyle = null; ##
}

void UBoxLink::deleteRelatedViews(UView *parview) {
  if (!b->boxCast()) {
    uerror("UBoxLink::~UBoxLink() : not a Box (%s) !\n", b->cname());
    return;
  }
  if (!views) return;

  for (int k = 0; k < viewCount; k++) 
    // !!toujours verifier views[k] != null 
    // (certaines vues peuvent deja avoir ete supprimees)
    
    if (views[k] && (!parview || views[k]->getParentView() == parview)) {
	
      //! IMPORTANT: the SHARED VIEWS of the UWINdows are NOT destroyed
      //! by this function (but by ~UWin)
      if (!views[k]->isDef(UView::WIN_SHARED)) {

	// supprimer les descendants de cette vue dans les enfants (et 
	// petits-enfants, etc...) de la box
	for (ULink *ch = b->groupCast()->getChildLinks(); 
	     ch != null; 
	     ch = ch->next()) {
	  UBoxLink *chb = dynamic_cast<UBoxLink*>(ch);
	  if (chb) chb->deleteRelatedViews(views[k]);
	}
	delete views[k];
      }

      // NB: dans le cas des vues partages des UWin le vecteur 'views' est 
      // modifie mais la view partagee n'est pas detruite 
      views[k] = null;
      //deletedCount++;
    }

  // if(deletedCount > ??) purger...  A_COMPLETER!!                !!!!!!!
  //revoir makelInk...
}

// NOTE importante: ~UBoxLink ne detache PAS les liens: c'est deja fait ailleurs
// Par contre ~UBoxLink() detruit les UView de ce lien.
// !Attention: ~UWinLink() ne detruit pas de vues contrairement a ~UBoxLink
// car la Window View est unique et peut etre partagee par plusieurs
// UWinLinks (c'est ~UWin qui est en charge de cette destruction)

// nb: plantera si il y a des views[k] identiques (ce qui n'est normalement
// jamais le cas)

UBoxLink::~UBoxLink() {
  // supprimer les descendants de cette vue dans les enfants (et 
  // petits-enfants, etc...) de la box

  if (views) {
    deleteRelatedViews(null);
    free(views);
  }
  // securite si appel d'un autre destructeur
  views = null; viewCount = 0; 
  //deletedCount = 0;
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// getView() returns the View corresponding to the parent view

UView* UBoxLink::getView(UView* parview) {
  for (int k = 0; k < viewCount; k++) 
    if (views[k]) {
      if (views[k]->getParentView() == parview || views[k]->isDef(UView::WIN_SHARED))
	return views[k];
    }
  return null;   //not found
}

UView* UBoxLink::getViewSize(UView* parview, u_dim *w, u_dim *h) {
  UView *v = getView(parview);
  if (v) {*w = v->width; *h = v->height;}
  else {*w = 0; *h = 0;}
  return v;
}

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

UViewIndex UBox::getViewIndex() {
  UViewIndex ix(parents.first());
  return ix;
}

UViewIndex& UViewIndex::operator++() {
  if (ll) {
    UBoxLink *lb = dynamic_cast<UBoxLink*>(ll->link());
    if (vpos < lb->getViewCount()) vpos++;
    else {
      ll = ll->next(); 
      vpos = 0;
    }
  }
  return *this;
}

UViewIndex  UViewIndex::operator++(int) {
  if (ll) {
    UBoxLink *lb = dynamic_cast<UBoxLink*>(ll->link());
    if (vpos < lb->getViewCount()) vpos++;
    else {
      ll = ll->next(); 
      vpos = 0;
    }
  }
  return *this;
}


UView* UBox::getView(UViewIndex& ix) {
  if (!ix.ll) return null;
  do {
    ULink *lk = ix.ll->link();
    UBoxLink *lb = dynamic_cast<UBoxLink*>(lk);

    if (ix.vpos < lb->getViewCount()) {
      // !!toujours verifier views[k] != null 
      // (certaines vues peuvent deja avoir ete supprimees)
      UView* v = lb->getViews()[ix.vpos];

      if (!v) (ix.vpos)++;	// continuer
      else return v;	// trouve
    }
    else {
      ix.ll = ix.ll->next();
      ix.vpos = 0;	// continuer
    }

  } while (ix.ll);    // liste nulle ou fin de liste

  return null;  // cas fin de liste
}

/* ==================================================== ======== ======= */
// returns the Box's View that contains the Event location
// (if any and applicable, and null otherwise)

UView* UBox::getViewContaining(UEvent *e) {
  UView *v =null;
  if (e && (v = e->getView()))
    return getViewContaining(v);
  else 
    return null;
}

// returns the Box's View that contains this child (or descendant) view 
// (if any, and null otherwise)

UView* UBox::getViewContaining(UView *childview) { 
  for (UView *v = childview; v != null; v = v->getParentView()) {
    if (v->getBox() == this) return v;
  }
  return null; // not found
}

/* ==================================================== ======== ======= */
// Note: the box may be included several times in 'parview'.
// Argument 'num' specifies the number of the appropriate box instance (from 0 
// to card-1). 'num' must be 0 (the default) in most case (i.e. when the box 
// is only included once in its parent)

UView* UBox::getViewContainedInParent(UView* parview, u_card num) {
  if (!parview) return null;
  u_card found_num = -1;

  // retrouver tous les parents
  for (ULinkLink *ll = parents.first(); ll != null; ll = ll->next()) {

    //NB: au moins UBoxLink* par construction
    //UBoxLink *boxlink = dynamic_cast<UBoxLink*>(ll->link());
    UBoxLink *boxlink = static_cast<UBoxLink*>(ll->link());

    for (int vk = 0; vk < boxlink->viewCount; vk++) {
      // !!NB: toujours verifier views[k] != null 
      // (certaines vues peuvent avoir ete supprimees)
      if (boxlink->views[vk] && boxlink->views[vk]->getParentView() == parview)
	found_num++;
      if (found_num == num) return boxlink->views[vk];
    }
  }

  return null;			// not found
}

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

void UGroup::closeWin(const UClass *cl) {
  for (ULinkLink *l = getParentList().first(); l!=null; l = l->next()){
    UGroup *par = l->link()->parent();
    // par == null if no other parent (UFrame or similar)
    if (par) {
      UWin *win = null;
      if (cl == null) win = dynamic_cast<UWin*>(par);
      else if (cl == &UDialog::uclass) win = dynamic_cast<UDialog*>(par);
      else if (cl == &UMenu::uclass) win = dynamic_cast<UMenu*>(par);
      if (win) win->close();
      else par->closeWin(cl);
    }
  } 
}

// NB: ne ferme que la VIEW du dialog qui contient le bouton clique

void UGroup::closeWin(UEvent *e, const UClass *cl) {
  for (UView *v = e->getView(); v != null; v = v->getParentView()) {
    UBox *box = v->getBox();
    if (box) {
      UWin *win = null;
      if (cl == null) win = dynamic_cast<UWin*>(box);
      else if (cl == &UDialog::uclass) win = dynamic_cast<UDialog*>(box);
      else if (cl == &UMenu::uclass) win = dynamic_cast<UMenu*>(box);
      if (win) {
	win->close();
	break;
      }
    }
  }
}

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

void UGroup::init(ULink *selflink, ULink *parlink, UView *parview) {
  // Propager aux children
  UBrick *child;
  for (ULink *ch = getChildLinks(); ch != null; ch = ch->next())
    if ((child = ch->brick())) {     //!!CHANGE 23sep01: call init for all objs
      child->init(ch, selflink, parview);

      // if ((child = ch->brick()->groupCast())) {
      // parview est la vue de l'objet lui-meme si c'est un UBox
      // (et que la fct est appelee depuis UBox::init)
      // et la vue du premier ascendent de type UBox sinon
      // child->initViews(ch, selflink, parview);
    }
}

//att: parview n'est pas la view du parent mais du 1er box!
//
void UBox::init(ULink *selflink, ULink *parlink, UView *parview) {
  if (!parview) {
    error("init", "No parent view!");
    return;
  }
  // par construction
  //UBoxLink *bl = dynamic_cast<UBoxLink*>(selflink);
  UBoxLink *bl = static_cast<UBoxLink*>(selflink);

  // creer et ajouter la vue
  if (bl->viewCount == 0) bl->views = (UView**)malloc(sizeof(UView*));
  bl->views = (UView**)realloc(bl->views, (bl->viewCount+1) * sizeof(UView*));
  if (!bl->views) {
    bl->viewCount = 0;
    uerror("UBox::init", "Not enough memory");
    return;
  }

  const UViewStyle *render;
  if (isDef(0,UMode::HAS_RENDERER)) {
    // si renderer defini dynamiquement dans la childlist
    render = (UViewStyle*) getChild(&UViewStyle::uclass);
  }
  else {  // default: rendre le renderer defini par le style de la Win
    //ex: calcul assez farfelu:
    //ex  render = link->brick()->boxCast()->getStyle(closest_parent)->viewStyle;

    //NB: closest_parent: ne sert en fait pas a grand chose (sauf dans des
    //cas de styles contextuels ou le renderer changerait suivant parent)
    UBox *closest_parent = parview->getBox();
    if (!closest_parent) warning("init", "No closest parent");
    render = getStyle(closest_parent)->viewStyle;
  }

  UView *view = null;
  if (render)
    view = (render->makeView)(bl, parview, &(parview->wg()));
  else {
    warning("init", "missing ViewStyle Spec.");
    view = new UView(bl, parview, &(parview->wg()));
  }
  bl->views[bl->viewCount] = view;
  (bl->viewCount)++;

  // Propager aux children
  UGroup::init(selflink, parlink, view); //view, pas parview !
}

/* ==================================================== [Elc:00] ======= */
/* ==================================================== ======== ======= */
// ATTENTION: les appels aux fcts vituelles ne marchent pas normalement dans
// les destructeurs (l'appel est fait avec la classe du destructeur, pas la
// classe effective)
// ==> appeler deleteChildren() DANS LE DESTRUCTEUR de la SOUS-CLASSE adequate 
// (et PAS dans UBrick!!!) pour qu'il soit 
// applique avec un type d'objet adequat (par exple UBox ou UWin)
//
// ===> CECI IMPOSE DE DEFINIR UN DESTRUCTEUR POUR CHAQUE CLASSE

// !! IMPORTANT NOTE 2:
// !!   The children (and descendants) of the deleted box are also 
// !!   destroyed recursively *IF* they do not have other parents.

void UGroup::clean() {
  // - enleve les enfants puis les parents (c'est plus economique au niveau
  //   de la gestion des Views)
  // - deleteChildren() = deletes children that DO NOT have another
  //   parent and just removes other children

  removeAll(true);
  if (parents.first()) removeFromParents();

  // check if the object is firing callbacks
  // in such a case link.brick() is reset and will point to null
  // in order to stop the execution of callbacks
  // NB: reset ALL links that points to this as several callback lists
  // may be fired with the same 'this' object

  if (firingList.first()) {
    for (ULink *l = firingList.first(); l!=null; l = l->next()) 
      if (l->brick() == this)  l->setBrick(null);
  }
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// UPropagateFlag derive de UMode pour pouvoir etre ajoute a un parent 
// (ie. l'objet sur lequel on applique la methode onChildrenEvents)
// de telle sorte que la destruction du parent entraine aussi celle 
// du UPropagateFlag
// (NB: l'avantage de UMode c'est qu'il n'est pas pris en compte 
// a l'affichage (et qu'on peut voir propagateFlags comme un mode)
// Par exemple, si on derivait directement de UGroup on pourrait
// eventuellement avoir des pbms imprevus)

class UPropagateFlag : public UFlag, public UMode {
public:
  static const char* FLAGNAME;
  UGroup propagateGroup;

  UPropagateFlag(UGroup *obj, UArgs args): UFlag(null), propagateGroup(args) {
    flagname = UPropagateFlag::FLAGNAME; // !!ATT: pas de strdup!
    // contrairement au constructeur UFlag afin de pouvoir faire
    // ensuite une comparaison sur les adresses des strings
  }
};
const char* UPropagateFlag::FLAGNAME = u_strdup("UPropagateFlag");

static void flagEventsInPropagateMode(UEvent *e, UPropagateFlag *propflag) {
  e->propagate = true;
  e->addFlag(propflag);
}

// NOTE IMPORTANTE:
// event flagging is meaningless in 'searchedView' mode:
// mouse coords must be defined in order to determine which objects
// are traversed by events
// ==> ceci implique que seules les UBox peuvent flagger les events
//     (car les UGroup n'ont pas de coords)
//
// ET DONC: onChildrenEvents n'existe pas pour les UGroup !

UBox& UBox::onChildrenEvents(UArgs args) {
  //NB: UFrom cree par cette methode et partage ensuite
  UPropagateFlag *propflag = new UPropagateFlag(this, args);

  // will flag events so that they will fire the propagateGroup
  // after being processed by the object's children
  add(ucall(flagEventsInPropagateMode, propflag, UOn::traverse));
  return *this;
}

/* ==================================================== ======== ======= */
// list of objects whose callbacks are currently being fired
UChain UGroup::firingList;

void UGroup::fire(UEvent &e, const UOn &cond) {
  /*!!ATT: les ucall devraient etre dans le cache pour 3 raisons:
   * 1- pour optimser les appels
   * 2- pour eviter de polluer la children list avec des callbacks
   *    indirects (qunad on ajoute un dialog, un menu, un uedit)
   * 3- pour eviter des plantages si on detruit (ou eleve) un ucall
   *    pendant un fire() de l'objet qui contient cet ucall
   */

  // optimisation pour eviter de scanner la liste quand c'est inutile
  // principe: 
  // les bmodes de l'objet doivent (au moins) verifier un des bmodes du cond 
  // (c'est un un OU, on ne demande pas que toutes les conditions soient 
  // verifiees, une suffit), sinon on est sur qu'aucun callback correspondant
  // ne sera dans la liste
  // note: que l'on exclut les CANT_DEL du cond qui n'ont rien a voir)

  u_modes cond_modes = cond.bmodes & ~UMode::CANT_AUTODEL;
  if (!cond_modes || (bmodes & cond_modes) != 0) {

    // IMPORTANT NOTE:
    // 'link' (see below) is inserted in the global 'firingList' and
    // is checked and changed by the UGroup destructor in order to stop 
    // the execution of callbacks when the object is destroyed:
    // -- in such a case link.brick() will be reset and point to null
    // Note:
    // -- links can be added and removed in any order (depending on
    // which order callback lists are being fired)

    ULink link(this);      // !!!CHECK MULTITHREADS !!
    firingList.insertAfter(&link, null);  //null=>beginning of list

    // tester d'abord les callbacks dans le cache PUIS dans les children
    ULink *l; UCall *c;

    if (cache) {
      for (l = cache->first(); 
	   link.brick() != null && l != null;  //!!!see note before
	   l = l->next()) {
	
	if (l->match(cond) && (c = dynamic_cast<UCall*>(l->brick()))) {
	  e.cond = &cond; c->call(&e);
	}
      }
    }

    for (l = getChildLinks(); 
	 link.brick() != null && l != null;  //!!!see note before
	 l = l->next()) {

      if (l->match(cond) && (c = dynamic_cast<UCall*>(l->brick()))) {
	e.cond = &cond; c->call(&e);
      }
    }
    
    // remove the link from the 'firingList' once over
    firingList.remove(&link);   // !!!CHECK MULTITHREADS !!
  }

  // propager l'event a d'autres objets (sauf dans cas traverse
  // pour eviter des absurdites et des bouclages)
  if (e.propagate && &cond != &UOn::traverse) {
    UFlag **flags = e.getFlags();

    // temporairement mis a false pour eviter recursion infinie
    e.propagate = false;

    for (int k = 0; k < e.flagCount; k++)
      // cette condition assure que c'est bien un UPropagateFlag
      if (flags[k]->getName() == UPropagateFlag::FLAGNAME) {
	UPropagateFlag *propflag = (UPropagateFlag*) flags[k];
	propflag->propagateGroup.fire(e, cond);
      }

    e.propagate = true;
  }
}

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

ULink* UGroup::getChildLinks() const {return children.first();}

int UGroup::getChildCount() {
  int kk = 0;
  for (ULink *l = children.first(); l!=null; l = l->next()) 
    kk++;   //k++ mis ici pour eviter les bugs... des compilateurs!
  return kk;
}

// Returns a copy of the object's child list
// -- returns a null terminated table that must be destroyed after use
//    by using the delete[] primitive

UBrick** UGroup::getChildren() {
  int count;
  return getChildren(count);
}

UBrick** UGroup::getChildren(int &count) {
  count = getChildCount();

  if (count <= 0) return null;	// ajout 12fev01
  else {
    UBrick **tab = new UBrick*[count+1];
    int kk = 0;
    for (ULink *l = children.first(); l!=null; l = l->next(), kk++) {
      tab[kk] = l->brick();
    }
    tab[kk] = null;  // null terminated
    return tab;
  }
}

// Returns the nth element in the object's child list
// -- 'pos' = 0 means "first child" and 'pos' = -1 means "last child"
// -- returns null if 'pos' is out of range
// -- prints a warning and returns null if 'pos' < -1

UBrick* UGroup::getChild(int pos) {
  if (pos == -1) {  // find the last one
    return (children.last() ? children.last()->brick() : null);
  }
  else if (pos < 0) warning("getChild", "invalid position: pos=%d", pos);
  else {
    int kk = 0;
    for (ULink *l = children.first(); l!=null; l = l->next(), kk++)
      if (kk == pos) return l->brick();
  }
  return null; // out of range
}

int UGroup::getChildPos(const UBrick *child) {
  int kk = 0;
  for (ULink *l = children.first(); l!=null; l = l->next(), kk++)
    if (l->brick() == child) return kk;
  return -1; // not found
}

UBrick* UGroup::getChild(const UClass *cl) {
  for (ULink *l = children.first(); l!=null; l = l->next()) 
    if (l->brick()->getClass() == cl) return l->brick();
  return null; // not found
}

int UGroup::getChildPos(const UClass *cl) {
  int kk = 0;
  for (ULink *l = children.first(); l!=null; l = l->next(), kk++)
    if (l->brick()->getClass() == cl) return kk;
  return -1; // not found 
}

/* ==================================================== [Elc:00] ======= */
/* ==================================================== ======== ======= */
// cherche le link du child qui PRECEDE le child que l'on cherche
// -- renvoie true si trouve, false sinon
// -- initialise prevlink si trouve (indefini sinon)
// -- initialise prevlink a null si 'first child'

u_bool UGroup::getChildImpl(int pos, ULink **prevlink) {
  if (pos == 0) {         // returns link BEFORE the first child
    *prevlink = null;     // (prevlink=null means 'first child')
    return true;
  }
  else if (pos == -1) {   // returns link BEFORE the last child
    ULink *prevl = null;
    for (ULink *l = children.first(); 
	 l!=null && l->next()!=null; 
	 prevl = l, l = l->next());
    *prevlink = prevl;
    return true;
  }
  else {
    int kk = 0;
    pos--; //!!
    for (ULink *l = children.first(); l!=null; l = l->next(), kk++)
      if (kk == pos) {
	*prevlink = l;
	return true;
      }
  }
  return false; // pos < -1 or pos out of range
}


u_bool UGroup::getChildImpl(const UBrick *child, ULink **prevlink) {
  ULink *prevl = null;
  for (ULink *l = children.first(); l!=null; prevl = l, l = l->next()) 
    if (l->brick() == child) {
      *prevlink = prevl;   // NB: prevlink=null means 'first child' 
      return true;
    }

  return false; // not found
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// NB: addlist() appelle addImpl comme add() ce qui permet d'eviter de 
// serieuses erreurs d'initialisation!

//!PBM : la liste a ne peut pas etre partagee et utilisee 2 fois !!
// dans des ugroup differents
 
UGroup& UGroup::addlist(UArgs a) {
  ULink *l = a.children.first(); 
  ULink *lnext = null;
  // NB: on pourrait en profiter pour faire une copie !!!
  // des Links et permettre ainsi des UArgs partages  !!!
  while (l != null) {
    lnext = l->next();
    addImpl(l->brick(), l, -1);
    l = lnext;
  }

  // remet children a null pour eviter problemes eventuels (au cas ou 
  // on aurait eu la mauvaise idee d'utiliser 2 fois la list 'a')
  a.children.reset();
  return *this;
}

/* ==================================================== ======== ======= */
// Adds 'child' in the object's child list
// -- 'pos' specifies the position in the list
//     0 means "beginning of list" (= before the first child)
//    -1 (the default) means "end of list" (= after the last child)
// -- prints a warning and do nothing if 'pos' is out of range or < -1

void UGroup::add(const char *s, int pos) {
  if (s) {
    UBrick *b = &ustr(s);
    addImpl(b, b->makeLink(), pos);
  }
}

void UGroup::add(UBrick *b, int pos) {
  if (!b) error("add", "Null child");
  else addImpl(b, b->makeLink(), pos);
}

void UGroup::add(UBrick &b, int pos) {
  addImpl(&b, b.makeLink(), pos);
}

void UGroup::add(ULink *childlink, int pos) {
  UBrick *b;
  if (!childlink || !(b = childlink->brick())) error("add", "Null child");
  else addImpl(b, childlink, pos);
}

void UGroup::add(ULink &childlink, int pos) {
  UBrick *b;
  if (!(b = childlink.brick())) error("add", "Link pointing to null");
  else addImpl(b, &childlink, pos);
}


void UGroup::addImpl(UBrick *child, ULink *childlink, int pos) {
  // add(.,-1) a un sens particulier: end-of-list et non last-child !
  if (pos == -1)
    // add at the end of the list
    children.add(childlink);
  else {
    ULink *prevlink;
    if (getChildImpl(pos, &prevlink))
      // OK: add after prevlink (NB: if prevlink=null then beginning-of-list)
      children.insertAfter(childlink, prevlink);
    else {
      error("add", "position (%d) is out of range", pos);
      delete childlink;  //??
      return;
    }
  }

  // call add-initialization procedure of child
  child->addingTo(childlink, this);

  // il manque la mise a jour des vues des enfants pour les UGroup parents!!!!!

  if (boxCast()) {
    // si 'b' de type Group ou derive il faut mettre a jour les vues
    UGroup *gchild = child->groupCast();
    if (gchild) {

      // retrouver toutes les vues pour tous les parents de box
      for (ULinkLink *ll = parents.first(); ll != null; ll = ll->next()) {

	//NB: parent au moins UBoxLink* par construction
	//UBoxLink *boxlink = dynamic_cast<UBoxLink*>(ll->link());
	UBoxLink *boxlink = static_cast<UBoxLink*>(ll->link());
    
	for (int kv = 0; kv < boxlink->viewCount; kv++) {
	  UView *view = boxlink->views[kv];
	  // initialiser b et ses descendants
	  // -- !!NB: toujours verifier views[k] != null 
	  // -- (certaines vues peuvent avoir ete supprimees)
	  if (view && view->isRealized()) 
	    gchild->init(childlink, boxlink, view);
	}
      }
    }
  }

  // a la fin
  if (isDef(UMode::ADD_CB,0)) {
    UEvent e(UEvent::add, null, NULL);
    e.setGroupSource(this);
    fire(e, UOn::add);
  }
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// Removes or deletes 'child' from the object's child list
// Notes:
// -- this fct. only removes the FIRST occurence of 'child' in the list
//
// -- if arg. 'delete_singles' is false:
//    'child' is NEVER deleted and is returned by the function
//
// -- if arg. 'delete_singles' is true:
//    'child' and its descendants are recursively deleted EXCEPT if they
//    they have other parents (or if there are multiple occurences of
//    'child' in the object's child list)
//
//    The fct. returns null if 'child' was deleted (no other parent links)
//    and 'child' if it was not deleted (mutiple parent links)

UBrick* UGroup::remove(UBrick *child, u_bool delete_singles) {
  ULink *prevlink;
  if (!child) {
    error("remove", "null child");
    return null;
  }
  else if (!getChildImpl(child, &prevlink)) {
    error("remove", "child not found (child class: %s)", child->cname());
    return null;
  }
  // NB: prevlink=null means remove first child 
  else return removeImpl(child, prevlink, delete_singles);
}

UBrick* UGroup::remove(UBrick &child, u_bool delete_singles) {
  ULink *prevlink;
  if (!getChildImpl(&child, &prevlink)) {
    error("remove", "child not found (child class: %s)", child.cname());
    return null;
  }
  // NB: prevlink=null means remove first child 
  else return removeImpl(&child, prevlink, delete_singles);
}


UBrick* UGroup::remove(int pos, u_bool delete_singles) {
  ULink *prevlink;
  if (!getChildImpl(pos, &prevlink)) {
    error("remove", "position (%d) is out of range", pos);
    return null;
  }

  UBrick *child;
  if (prevlink) {
    // si prevlink n'a pas de successeur c'est que 'pos' vaut exactement 
    // childCount (ce qui est OK pour add mais pas pour remove!)
    child = (prevlink->next() ? prevlink->next()->brick() : null);
  }
  else {
    // cas 1st child (prevlink=null). OK si la liste n'est pas vide
    child = (children.first() ? children.first()->brick() : null);
  }

  if (!child) {
    error("remove", "position (%d) is out of range", pos);
    return null;
  }
  else return removeImpl(child, prevlink, delete_singles);
}

void UGroup::removeAll(u_bool delete_singles) {
  // on detruit le premier jusqu'a ce qu'il n'y en ait plus
  // cette version est compatible avec les multi-occurences :
  // il n'y aura pas de probleme si 'child' est detruit alors qu'il est 
  // present plusieurs fois dans la liste
  ULink *l;
  while ((l = children.first()))
    // prevlink=null means remove first child 
    removeImpl(l->brick(), null, delete_singles);
}

/* ==================================================== ======== ======= */
//NB: et si child pas dans l'obj ? faudrait pas le detruire et renvoyer null!

// ATT: prevlink : pas le lien qui pointe sur brick mais le precedent!
// si prevlink=null on enleve le 1er child

UBrick* UGroup::removeImpl(UBrick *child, ULink *prevlink, 
			   u_bool delete_singles) {

  if (isDef(UMode::REMOVE_CB, 0)) {  //deplace avant
    UEvent e(UEvent::remove, null, NULL);
    e.setGroupSource(this);
    fire(e, UOn::remove);
  }

  //*** if NOT delete_singles OR child has other parents
  if (!delete_singles || child->hasMultipleTrueParents()
      // || (child->parents.first() && child->parents.first()->next())
      ) {

    // just remove, don't delete /(!ATT: PREVlink not selflink!)
    ULink *selflink = children.removeAfter(prevlink);
    child->removingFrom(selflink, this);
    delete selflink;
    return child;
  }

  //*** if delete_singles AND child does NOT have other parents
  else {
    // si pas en mode AUTODEL on ne detruit pas l'objet mais on essaie
    // tout de meme de detruire ses enfants
    // NB: il va y a voir des cas ou des enfants ne seront pas detruits
    // car ils auront encore 1 ou N parents pas AUTODEL

    child->clean();  //detacher du graphe d'instanciation
    /*
    // remove or destroy children
    if (child->groupCast())  child->groupCast()->removeAll(true);
    // remove from parent
    ULink *selflink = children.removeAfter(prevlink);
    child->removingFrom(selflink, this);
    delete selflink;
   */

    // pas AUTODEL : l'objet lui-meme n'est pas detruit
    if (child->bmodes & UMode::CANT_AUTODEL) return child;

    // sinon on appelle udelete pour detruire l'objet
    else {
      udelete(child);  //child set to null if actually deleted
      return child;
    }
  }
}

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

int UGroup::getTextLength(u_bool recursive) {
  int len = 0;
  UStr *str = null;
  UGroup *grp = null;

  for (ULink *ch = children.first(); ch != null; ch = ch->next()) {
    UBrick *b = ch->brick();

    if ((str = b->strCast()))  
      len += str->length();
    else if (recursive && (grp = b->groupCast()))
      len += grp->getTextLength(recursive);
    
  }
  return len;
}

char *UGroup::getTextData(char* ptrpos, u_bool recursive) {
  UStr *str = null;
  UGroup *grp = null;

  for (ULink *ch = children.first(); ch != null; ch = ch->next()) {
    UBrick *b = ch->brick();

    if ((str = b->strCast()) && str->chars()) {
      strcpy(ptrpos, str->chars());
      ptrpos += str->length();
    }
    else if (recursive && (grp = b->groupCast()))
      ptrpos = grp->getTextData(ptrpos, recursive);
  }

  return ptrpos;
}

//obsolete name of copyText() :
int UGroup::sprintText(UStr &str, u_bool recursive) {
  return copyText(str, recursive);
}

int UGroup::copyText(UStr &str, u_bool recursive) {
  int len = getTextLength(recursive);
  if (len <= 0) return 0;
 
  char *data = (char*)malloc((len+1)*sizeof(char));
  if (!data) {
    error("retrieveAsciiText", "No more memory available");
    return 0;
  }

  char *ptrpos = getTextData(data, recursive);

  if (ptrpos != data + len) {
    error("retrieveAsciiText", "internal error: erroneous length");
    return null;
  }

  *ptrpos = '\0';  //null terminated!
  str.set(data);
  return len;
}

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

const UUpdate UUpdate::paint(PAINT); 
const UUpdate UUpdate::layout(LAYOUT); 

UUpdate::UUpdate(UUpdate::Mode m) : ix(m) {
  always  = doublebuf = false;
  delta_x = delta_y = 0;
  item    = null;
  region  = null;
}

void UUpdate::paintTitle(const UStr *_item) {
  ix = TITLE;
  item = _item;
}

void UUpdate::paintItem(const UItem *_item) {
  ix = PAINT;
  item = _item;
  delta_x = 0; //nb: this is only useful for strings in uflows
  delta_y = 0;
}

// will only paint an enclosing subpart of this string
//
void UUpdate::paintStr(const UStr *_item, int strpos1, int strpos2) {
  ix = PAINT;
  item = _item;
  delta_x = strpos1; //nb: this is only useful for strings in uflows
  delta_y = strpos2;
}

void UUpdate::paintRegion(const URegion *_region) {
  ix = PAINT;
  region = _region;
}

void UUpdate::doubleBuffering(u_bool state) {
  doublebuf = state;
}

void UUpdate::evenIfHidden(u_bool state) {
  always = state;
}

void UUpdate::setOffset(u_pos d_x, u_pos d_y) {
  delta_x = d_x;
  delta_y = d_y;
}

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

void UGroup::updateView(UEvent&, UView*, const UUpdate&) {
  // comme group n'a pas de views il se contente de faire un update des parents
  parents.updateParents(UUpdate::paint);
}

void UBox::updateView(UEvent& e, UView *view, const UUpdate &upmode) {
  if (!view) {
    error("updateView", "Internal error: null event or null view!");
    return;
  } 
  // !!ATT: verifier que la Window a ete cree
  // il ne s'agit pas d'une erreur ou d'une incoherence mais du simple fait
  // que les champs "visible" ne sont pas modifies recursivement:
  // ainsi un element peut etre "visible" alors qu'un parent ne l'est pas
  // et n'a pas ete realize.

  UWinGraph &g = view->wg();
  if (!g.isWinRealized()) return;

  // pas visible du tout ==> rien a faire (sauf si show/hide)
  if (e.redrawStatus <= 0 
      && upmode.ix != UUpdate::SHOW && upmode.ix != UUpdate::HIDE) {
    view->updateWinData(e.redrawClip);
    return;    // !att: return necessaire pour sauter default_case
  }

  //*************SCROLL****************
  //*************SCROLL****************

  else if (upmode.ix == UUpdate::SCROLL) {
    // Just update data, do not paint
    // !att: c'est necessaire pour remettre entierement a jour les coordonnees
    // des objets scrolles et EN PARTICULIER la zone qui a ete directement
    // recopiee sans regeneration du graphique
    //
    // DE PLUS: ce mode permet de savoir s'il n'ya pas un overlapping
    // avec un ou des objets superposes (voir ci-dessous)

    // !att: ne pas utiliser 2 fois de suite le meme winctx !
    int overlapp = view->updateWinData(e.redrawClip);

    // s'il y a un objet (TRANSP ou non) qui OVERLAPPE (= qui est au dessus
    // le la zone SCROLLEE ==> on ne peut pas recopier directement cette zone
    // (sinon le machin par dessus va aussi etre recopie)
    // ==> le plus simple dans ce cas est de tout regenerer et reafficher
    //    (tant pis pour les perfs, au moins l'affichage sera correct)

    if (overlapp > 1) {
      view->updateWinPaint(e.redrawClip, upmode.doublebuf, 
			   e.firstTranspView, false);
      return;    // !att: return necessaire pour sauter default_case
    }
    else {
      // ON RECOPIE directement ce qu'on peut en changeant l'offset vertical
      // ou horizontal (selon le cas, un seul ne devant changer qu' ala fois)
      // PUIS on regenere et on affiche la zone manquante
      // ==> avantage : BEAUCOUP plus rapide, presque plus de flicking

      UView *winview = g.getHardwin()->getWinView();  //!! HARDWIN!!
      if (!winview) return;
      g.begin(winview); // ne pas oublier!
     
      URegion clip(e.redrawClip);
      URegion manquant;
      int src_x, src_y, dst_x, dst_y;

      // ATTENTION:
      // - SEULE une des 2 directions doit changer a la fois
      // - ne RIEN faire qunad delta_* == 0, de maniere a ce que la dimansion
      //   invariante (par exple: width ) ne soit pas mise a 0 stupidement

      if (upmode.delta_x == 0) {

	// rien a faire dans ce cas: sans ce test les micro-deplacements 
	// entraineraient un reaffichage complet de toute la page !
	if (upmode.delta_y == 0) return;

	//clip.width inchange
	src_x = clip.x;
	dst_x = clip.x;
	manquant.x = clip.x;
	manquant.width = clip.width;
      }
      else if (upmode.delta_x > 0) {
	clip.width -= upmode.delta_x;
	src_x = clip.x + upmode.delta_x;
	dst_x = clip.x;
	manquant.x = clip.x + clip.width;
	manquant.width = upmode.delta_x;
      }
      else {
	clip.width += upmode.delta_x; // en fait une soustraction
	src_x = clip.x;
	dst_x = clip.x - upmode.delta_x;
	manquant.x = clip.x;
	manquant.width = -upmode.delta_x;
      }

      if (upmode.delta_y == 0) {
	//clip.height inchange
	src_y = clip.y;
	dst_y = clip.y;
	manquant.y = clip.y;
	manquant.height = clip.height;
      }
      else if (upmode.delta_y > 0) {
	clip.height -= upmode.delta_y;
	src_y = clip.y + upmode.delta_y;
	dst_y = clip.y;
	manquant.y = clip.y + clip.height -1;
	manquant.height = upmode.delta_y;
      }
      else {
	clip.height += upmode.delta_y; // en fait une soustraction
	src_y = clip.y;
	dst_y = clip.y - upmode.delta_y;
	manquant.y = clip.y;
	manquant.height = -upmode.delta_y;
      }
      
      // eviter pbms (en particulier si le delta_* depasse la largeur ou
      // la hauteur de la zone a afficher, ce qui peut arriver
      clip.setInter(winview); 

      if (clip.width <= 0 || clip.height <= 0) {
	// dans ce cas il faut tout reafficher: le slider est alle trop vite
	// et il n'y a aucune partie commune avec ce qui precedait
	// (donc rien a recopier et tout a reafficher)
	goto default_case;
      }

      // copier ce qui n'a pas change
      // note: CopyArea can't copy obscured parts of the component
      // but will generate refresh events form repainting the missing parts
      // as its last arg. is true

      g.copyArea(src_x, src_y, clip.width, clip.height,
		 -upmode.delta_x, -upmode.delta_y, true);

      // puis generer et afficher la partie manquante

      // permet de corriger un regrettable bug: le copyArea precedent
      // recopie la dernier ligne qui est malheureusement blanche
      // car ecrasee qq part par une fonction indelicate. en affichant
      // une partie manquante un peu plus grande on resoud donc le pbm
      manquant.x--;
      manquant.width++;
      manquant.y--;
      manquant.height++;

      view->updateWinPaint(manquant, upmode.doublebuf, 
			   e.firstTranspView, false);
      g.end();
    }
    return;    // !att: return necessaire pour sauter default_case
  }


  //************* MOVE ****************
  //************* MOVE ****************

  else if (upmode.ix == UUpdate::MOVE) {
    // faire en sorte que les coords. de CLIP soient TOUJOURS les nouvelles
    // coords en testant si celles de VIEW ont deja ete mises a jour
    // (OBSOLETE_COORDS => view = anciennes valeurs (*avant* le move), 
    // sinon view = nouvelles valeurs (*apres* le move)
    // NOTES:
    // -- voir notes dans UPos::set()  (floating boxes)
    // -- ce mecanisme est necessaire car l'ordre de remise a jour des coords
    // des floating views est indetermine du fait d'eventuelles dependances 
    // entre les views (dans les cas ou le floating est partage)
    // -- la maj. est faite par UView::doUpdate() qui est appelee
    // recursivement en fonction du view tree et des occlusions
    URegion clip(view);
    if (view->isDef(UView::OBSOLETE_COORDS)) {
      clip.x += upmode.delta_x;
      clip.y += upmode.delta_y;
    }

    //ce decoupage en 3 parties evite de reafficher SOUS le Floating
    //quand il est OPAQUE. Noter que 'xstrip' inclue aussi le 'coin'
    //(l'intersection entre la bande verticale et la bande horizontale)
    //et que ce coin est donc exclu de 'ystrip'.

    //  zzyy     dx>0          yyzz     dx<0    Note: zz et xx sont
    //  zzyy     dy>0          yyzz     dy>0    regroupes dans 'xstrip'
    //  xx****               ****xx             yy = 'ystrip'
    //  xx****               ****xx
    //    ****               ****
    //    ****               ****

    //    ****               ****
    //    ****               ****
    //  xx****               ****xx
    //  xx****               ****xx
    //  zzyy     dx>0          yyzz     dx<0
    //  zzyy     dy<0          yyzz     dy<0

    URegion xstrip, ystrip;
    xstrip.y = clip.y - upmode.delta_y;
    xstrip.height = clip.height;

    if (upmode.delta_x > 0) {
      xstrip.x = clip.x - upmode.delta_x;
      xstrip.width = upmode.delta_x;
      ystrip.x = clip.x;
      ystrip.width = clip.width - upmode.delta_x;
    } else {
      xstrip.x = clip.x + clip.width; 
      xstrip.width  = -upmode.delta_x;   //nb: deltax <= 0 !
      ystrip.x = clip.x - upmode.delta_x;
      ystrip.width  = clip.width + upmode.delta_x;
    }

    if (upmode.delta_y > 0) {
      ystrip.y = clip.y - upmode.delta_y;
      ystrip.height  = upmode.delta_y;
    } else {
      ystrip.y = clip.y + clip.height;
      ystrip.height = -upmode.delta_y;
    }

    //intersection du clip avec parent pour eviter de reafficher (inutilement)
    //ce qui est en dehors du parent
    UView *parview = view->getParentView();
    if (parview) clip.setInter(parview);

    //NOTE: on pourrait aussi faire un hard copy comme pour SCROLL si pas d'overlapp
    //et pas transparent ni dynamique

    //contrairement a ce que l'ion avait autrefois, on n'utilise le double buffering
    // que si expressement demande ou si l'objet est TRANSPARENT
    u_bool doublebuf = upmode.doublebuf || e.firstTranspView != null;

    view->updateWinPaint(clip, doublebuf, e.firstTranspView, false);
    if (parview) {
      parview->updateWinPaint(xstrip, upmode.doublebuf, e.firstTranspView, false);
      parview->updateWinPaint(ystrip, upmode.doublebuf, e.firstTranspView, false);
    }

    /*old:
    if (!view->isDef(UView::OBSOLETE_COORDS)) {
      clip.x -= upmode.delta_x;
      clip.y -= upmode.delta_y;
    }
    if (upmode.delta_x > 0) {
      // clip.x unchanged
      //reunion de l'ancienne et de la nouvelle zone 
      clip.width += upmode.delta_x;
    } else {
      clip.x += upmode.delta_x; // en fait c'est une soustraction!
      clip.width += -upmode.delta_x; // addition
    }
    if (upmode.delta_y > 0) {
      // clip.y unchanged
      //reunion de l'ancienne et de la nouvelle zone 
      clip.height += upmode.delta_y;
    } else {
      clip.y += upmode.delta_y; // en fait c'est une soustraction!
      clip.height += -upmode.delta_y; // addition
    }
    // on veut reafficher la zone minimale du PARENT de view
    // (= la reunion de l'ancienne et de la nouvelle position de view)
    UView *parview = view->getParentView();
    if (parview) {
      //clip.setInter(e.redrawClip);
      clip.setInter(parview);
      // vv surtout ne me demandez pas pourquoi ...
      clip.width += 1;
      clip.height += 1;
      //par_view et non view!
      parview->updateWinPaint(clip, upmode.doublebuf, e.firstTranspView);
    }
    */

    return;    // !att: return necessaire pour sauter default_case
  }  //endif (upmode.ix == UUpdate::MOVE)


  //*************CAS STANDARD****************
  //*************CAS STANDARD****************

 default_case:   //PAINT, LAYOUT...
 // Notes:
 // 1. normalement redrawClip est defini SAUF si show/hide
 //    c'est en particulier necessaire pour le scrolling (sinon l'objet 
 //    serait affiche en entier et deborderait sur ses voisins)
 // 2. le mode PAINT_DAMAGED est est incompatible avec le DoubleBuffering
 //    (lequel entraine donc tjrs un PAINT_ALL dans updateWinPaint())

  if (upmode.region)
    //ne reafficher que la region demandee 
    view->updateWinPaint(upmode.region, upmode.doublebuf, 
			 e.firstTranspView, false);
  else if (e.redrawStatus > 0)
    //reafficher clipZone (si definie)
    view->updateWinPaint(e.redrawClip, upmode.doublebuf, 
			 e.firstTranspView, false);
  else
    // sinon reafficher zone de l'objet en entier
    view->updateWinPaint(view, upmode.doublebuf, 
			 e.firstTranspView, false);
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// Note: isShow() ne veut pas dire grand chose (sauf pour les Window)
// a cause des scrollpanes et autres clipping: une box peut-etre "SHOWN"
// sans apparaitre reellement a l'ecran (car hors zone de clipping
// du viewport)

void UGroup::show(u_bool state) {
  setCmodes(UMode::CAN_SHOW, state);
  if (state) {
    //fire(&e, UOn::show); // pas recursif! => faire autrement
    UUpdate upd(UUpdate::SHOW);
    update(upd);
  }
  else {
    //fire(&e, UOn::hide);
    UUpdate upd(UUpdate::HIDE);
    update(upd);
  }
}

void UGroup::update(UUpdate upd) {
  parents.updateParents(upd);
}
void UGroup::update() {
  parents.updateParents(UUpdate::layout);
}

/* ==================================================== ======== ======= */
//Notes: 
// 1. cette fct est virtuelle et c'est la redefinition UWin::update(...)
//    qui doit etre appelee pour les wins
// 2. peut faire apparaitre ou disparaitre this (show appelle cette fct)

void UBox::update() {
  update(UUpdate::layout);
}

void UBox::update(UUpdate upmode) {
  // REMARQUE IMPORTANTE: les hide/show ne sont pas traites de la meme facon
  // si l'objet est floating ou non:
  // - dans le 1er cas, pas besoin de recalculer le layout, un repaint suffit
  // - dans le 2nd cas, il faut recalculer le Layout - et actuellement -
  //   c'est meme celui DE LA FENETRE qu'on recalcule ce qui est A REVOIR!!!

  //UWin *updated_win = null;
  for (ULinkLink *llk = parents.first(); llk != null; llk = llk->next()){

    //NB: au moins (UBoxLink*) par construction
    //UBoxLink *link = dynamic_cast<UBoxLink*>(llk->link());
    UBoxLink *link = static_cast<UBoxLink*>(llk->link());

    for (int kv = 0; kv < link->viewCount; kv++) {
      UView *view = link->views[kv];
      UWin *win = null;
      UView *winview = null;

      // !!NB toujours verifier views[k] != null 
      // (certaines vues peuvent avoir ete supprimees)
      if (view && view->isRealized() 
	  && (win = view->getHardwin())
	  && (winview = win->getWinView())
	  // ne rien faire si la window n'est pas visible (ce qui implique
	  // de ne pas appliquer directement cette version de 'update()'
	  // sur une window                     !!!POSE PBM AVEC SOFTWINS!!
	  && (win->isShowable() || upmode.always)      //!att isShowable
	  ){

	UEvent e(UEvent::viewPaint, winview, null);

	switch (upmode.ix) {
	case UUpdate::MOVE: 
	  // on veut reafficher la zone minimale du parent de view
	  // (= la reunion de l'ancienne et de la nouvelle position de view)
	  e.locateSource(view);
	  updateView(e, view, upmode); 

	  // ce cas est particulier pour les UUpdate::move des floating objects:
	  // comme la position absolue des floating est mise a jour dans
	  // getSizeHints et doUpdate, il est possible, dans le cas de vues
	  // multiples crees au fur et a mesure, que les coords de certaines
	  // vues n'aient jamais ete mises a jour si elles sont sorties de
	  // la zone visible du parent a un moment donne.
	  // ==> un updateView() direct sur la View de ce floating ne ferait rien
	  // ==> il faut faire une updateView() sur la vue du parent pour
	  //     remettre a jour les corrds de la vue du floating (et le
	  //     redessiner par la meme occasion)
	  //
	  //  if (!e.sourceView) {
	  //    UView* parview = view->getParentView();
	  //    parview->getBox()->updateView(&e, parview);
	  //  } 
	  //  else updateView(&e, view, &upmode);
	  break;

	case UUpdate::PAINT: {
	  if (!isShowable() && !upmode.always) break;
	  e.locateSource(view);
	  // le cas !e.sourceView signifie que cette view n'est pas visible
	  // a l'ecran actuellement car cachee par un ScrollPane, etc.
	  if (!e.sourceView) return;

	  // si item est specifie, essayer de ne reafficher QUE la region
	  // contenant item (au lieu de la box entiere)
	  // NB: noter que item n'est pas necessairement l'item situe
	  // sous la souris (ie. ce n'est pas toujours e->getItem())

	  UItemData itd; //!!ATTENTION: declarer itd ICI pour que itd.region
	  // reste correctement alloue dans l'appel a updateView()
	  // (itd.region etant pointe par upmode.region)

	  if (upmode.item) {
	    //NOTE: delta_x, delta_y used for strpos1, strpos2
	    UItem *item = e.searchItem(itd, upmode.item, 
				       upmode.delta_x, upmode.delta_y);
	    if (item && item == upmode.item)
	      upmode.region = &itd.region; //!!ATTENTION: voir note ci-dessus
	  }

	  // NB: optimisation (dans updateView) qui utilise e.redrawClip
	  // et redrawStatus (initialises par locateSource) de maniere a ne
	  // reafficher que le clip concerne (ou juste upmode.region si definie)
	  updateView(e, view, upmode);
	  }break;

	case UUpdate::SHOW:
	  setCmodes(UMode::CAN_SHOW, true);
	  win->update(UUpdate::layout);  // UUpdate::layout pas upmode!
	  break;

	case UUpdate::HIDE:
	  setCmodes(UMode::CAN_SHOW, false);
	  win->update(UUpdate::layout);  // UUpdate::layout pas upmode!
	  break;

	case UUpdate::TITLE:                     //!!!A_REVOIR POUR SOFTWINS!!!
	  // commande de differente nature => a ne pas compter dans
	  // le "updated_win" sinon il y aura des cas de double modif
	  // ou soit le titre soit le contenu n'auront pas ete mis a jour
	  //OBS: win->update(upmode);    //!att: upmode PAS UUpdate::title
	                          //car upmode contient des data!
	  break;

	default:         //arrive ds quel cas au juste???jamais?
	  printf("Update default case %d\n", upmode.ix);
	case UUpdate::LAYOUT:
	  e.locateSource(view);
	  if (!e.getParentContext()) return; //cas d'erreur

	  if (isShowable()) {
	    if (!e.getView()) return;  //vue pas visible (ou cachee)
	    // fromview = from which view we must perform the lay out
	    // (de telle sorte que les parents se reajustent correctement
	    // qunad c'est necessaire)
	    UView *fromview = e.getParentContext()->firstLayoutView;
	    if (!fromview) fromview = winview;
	    u_dim w=0, h=0;
	    fromview->getSize(w, h);  	    //keep the same size
	    updateLayout(winview, fromview, true, w, h);
	  }
	  else if (upmode.always) {
	    UViewLayout vl;
	    setCmodes(UMode::CAN_SHOW, true);
	    view->doLayout(*(e.getParentContext()), vl);
	    //remise a jour des datas
	    view->updateWinData(view);
	    setCmodes(UMode::CAN_SHOW, false);
	  }
	  break;
	}
      }
    }
  }
}

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

void UBox::updateLayout(UView* winview, UView* fromview, 
			u_bool impose_size, u_dim w, u_dim h) {
  UViewLayout vl;
  UWinContext winctx1(winview);
  u_bool mustLayoutAgain = fromview->doLayout(winctx1, vl);
  
 AGAIN:
  // forcer valeurs de w et h, indep de ce que veut layout()
  if (impose_size) fromview->resize(w, h);
  
  if (!mustLayoutAgain) {
    winview->updateWinPaint(fromview, false/*doublebuf*/, null, true);
    //optimisations possibles pour pas reafficher tous les layers
    //fromview->updateWinPaint(fromview, false/*doublebuf*/, 
    //        e.firstTranspView *de fromview!*, false);
  }
  else {
    fromview->updateWinData(fromview);
    if (impose_size) fromview->resize(w, h);
    
    UWinContext winctx2(winview);
    fromview->doLayout(winctx2, vl);
    mustLayoutAgain = false;
    goto AGAIN;
  }
}

/*** ????? A TESTER AVEC RESIZED FLOATING              !!!!!!
     
      //si c'est un floating (ou une softwin) il faut reafficher
      //l'eventuelle zone manquante du parent si la taille a diminue
      //!OPTIMISATION: ne reafficher que la partie manquante !!!
      UView *parview = null;
      if (!fromview->getBox()->isDef(0,UMode::FLOATING|UMode::SOFTWIN)
	  || !(parview = fromview->getParentView())) {
	// modifier le e->redrawClip qui a change
	e.redrawClip = fromview;
	fromview->getBox()->updateView(&e, fromview, upmode);
      }
      else {
	e.redrawClip = parview;               //NB: pas bon pour SOFTWIN!!
	parview->getBox()->updateView(&e, parview, upmode);
      }
***/


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

/***ex: merge avec version UBox
static void updateLayout(UEvent& e, UView* winview, u_bool reize){
  if (!e.getView()) return;  //vue pas visible (ou cachee)
  
  // fromview = from which view we must perform the lay out
  // (de telle sorte que les parents se reajustent correctement
  // qunad c'est necessaire)
  UView *fromview = e.getParentContext()->firstLayoutView;
  if (!fromview) fromview = winview;
  u_dim w=0, h=0;
  if (keepSize) fromview->getSize(w, h);
  
  UViewLayout vl;
  UWinContext winctx1(winview);
  u_bool mustLayoutAgain = fromview->doLayout(winctx1, vl);
  
 AGAIN:
  // forcer valeurs de w et h, indep de ce que veut layout()
  if (keepSize) fromview->resize(w, h);
  
  if (!mustLayoutAgain) {
    winview->updateWinPaint(fromview, false, null, true);
    //optimisations possibles pour pas reafficher tous les layers
    //fromview->updateWinPaint(fromview, false, 
    //        e.firstTranspView *de fromview!*, false);
  }
  else {
    fromview->updateWinData(fromview);
    if (keepSize) fromview->resize(w, h);
    UWinContext winctx2(winview);
    fromview->doLayout(winctx2, vl);
    mustLayoutAgain = false;
    goto AGAIN;
  }
}
****/
/*old:
     case UUpdate::LAYOUT:
	  if (isShowable()) {
	    if (win != updated_win) {
	      win->update(UUpdate::layout);  // UUpdate::layout pas upmode!
	      updated_win = win;
	    }

	  }
	  //remettre a jour les datas meme si obj not shown en mode always
	  else if (upmode.always) {
	    e.locateSource(view);
	    if (!e.getParentContext()) break; //cas d'erreur
	    setCmodes(UMode::CAN_SHOW, true);
	    view->doLayout(*(e.getParentContext()), vl);
	    //remise a jour des datas
	    view->updateWinData(view);
	    setCmodes(UMode::CAN_SHOW, false);
	  }
     default:
	  UViewLayout vl;
	  e.locateSource(view);
	  if (!e.sourceView) break; //vue pas visible (ou cachee)
	  if (!e.getParentContext()) break; //cas d'erreur

	  // just lay out and redraw this view
	  view->doLayout(*(e.getParentContext()), vl);
	  //voir remarque ci-dessus:
	  upmode.ix = UUpdate::PAINT; 
	  updateView(e, view, upmode);

      ==================================================== ======== ======= 
      versions precedentes:
	  
	case UUpdate::SHOW:
	  setCmodes(UMode::CAN_SHOW, true);

	  if (isDef(0,UMode::FLOATING|UMode::SOFTWIN) || link->floatingCast()){
	    e.locateSource(view);
	    if (!e.getParentContext()) break; //cas d'erreur

	    //calcul du layout de cette vue
	    view->doLayout(*(e.getParentContext()), vl);
	  
	    //remise a jour des datas (updateView pas toujours suffisant 
	    //si appele une seule fois: des elements peuvent manquer)
	    view->updateWinData(view);
	    updateView(&e, view, upmode);
	  }
	  else {  //not floating                         // A REVOIR!!!
	    if (win != updated_win) {
	      win->update(UUpdate::win);  // UUpdate::win pas upmode  !
	      updated_win = win;
	    }
	  }
	  break;

	case UUpdate::HIDE:
	  setCmodes(UMode::CAN_SHOW, false);

	  if (isDef(0,UMode::FLOATING|UMode::SOFTWIN) || link->floatingCast()){
	    e.locateSource(view);
	    UView *parview = view->getParentView();
	                   // PAS SUFFISANT si softwin (pas le bon parent) !!!!
	    if (!parview) updateView(&e, view, upmode);
	    else {
	      //faut pas faire parent->doLayout car ca remet le parent 
	      //a sa taille initiale : il faut juste remettre a jour
	      //les donnees sans changer sa taille:
	      //EX: par_view->doLayout(*(e.getParentContext()), vl);
	      
	      //l'argument est parview pour reafficher le parent
	      //a la place de la vue qui vient de disparaitre
	      parview->getBox()->updateView(&e, parview, upmode);
	    }
	  }
	  else {  //not floating                       // A REVOIR!!!
	    if (win != updated_win) {
	      win->update(UUpdate::win);  // UUpdate::win pas upmode  !
	      updated_win = win;
	    }
	  }
	break;

	case UUpdate::WIN:
	  // eviter de refaire win->update() 36 fois !
	  if (win != updated_win) {
	    win->update(UUpdate::win);  // UUpdate::win pas upmode  !
	    updated_win = win;
	  }
	  break;

	case UUpdate::LAYOUT:{
	  e.locateSource(view);
	  if (!e.sourceView) break;  //vue pas visible (ou cachee)
	  if (!e.getParentContext()) break; //cas d'erreur

	  // fromview = from which view we must perform the lay out
	  // (de telle sorte que les parents se reajustent correctement
	  // qunad c'est necessaire)
	  UView *fromview = e.getParentContext()->firstLayoutView;

	  if (!fromview) fromview = winview; //securite
	  UWinContext winctx(winview);
	  fromview->doLayout(winctx, vl);
	    
	  // conserver tous les attributs de upmode (et en part. le
	  // doublebuf), ne changer que ix :
	  upmode.ix = UUpdate::PAINT; 

	  //si c'est un floating (ou une softwin) il faut reafficher
	  //l'eventuelle zone manquante du parent si la taille a diminue
	  //!OPTIMISATION: ne reafficher que la partie manquante !!!
	  UView *parview = null;
	  if (!fromview->getBox()->isDef(0,UMode::FLOATING|UMode::SOFTWIN)
	      || !(parview = fromview->getParentView())) {
	    // modifier le e->redrawClip qui a change
	    e.redrawClip = fromview;
	    fromview->getBox()->updateView(&e, fromview, upmode);
	  }
	  else {
	    e.redrawClip = parview;               //NB: pas bon pour SOFTWIN!!
	    parview->getBox()->updateView(&e, parview, upmode);
	  }
	}break;

*/
/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
void UGroup::highlight(u_bool st) {
}
/*
  // dans tous les cas pour faire un raise eventuel
  if (st) show(true);
  // mettre a jour cette Box virtuelle pour toutes ses realisations 
  // a l'ecran (ie. pour tous ses parents)
  ULinkLink *llk;

  for (llk = parents.first(); llk != null; llk = llk->next()) {
    //NB: au moins un UBoxLink * par construction
    UBoxLink *link = llk->boxLinkCast(); !!!FAUX: c'est un UGroup maintenant!
    for (int kv = 0; kv < link->viewCount; kv++) {

      UView *view = link->views[kv];
      // !!NB toujours verifier views[k] != null 
      // (certaines vues peuvent avoir ete supprimees)

      if (view && view->isRealized()) {
	// BUG: fait le changeAction N+1 fois !!!
	if (st > 0)
	  link->b->boxCast()->setState(UOn::HIGHLIGHTED);
	else 
	  link->b->boxCast()->setState(UOn::IDLE);

	UEvent e(UEvent::highlight, view->getWinView(), null);
	e.locateSource(view);
	updateView(&e, view, UUpdate::paint);
      }
    }
  }
}
  */
/* ==================================================== [TheEnd] ======= */
/* ==================================================== [Elc:01] ======= */

