/* ==================================================== ======== ======= *
 *
 *  uuctrl.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	"@(#)uuctrl.cc	ubit:b1.11.6"
#include <ctype.h>
#include <udefs.hh>
#include <ubrick.hh>
#include <uprop.hh>
#include <ustr.hh>
#include <uctrl.hh>
#include <ubox.hh>
#include <uwin.hh>
#include <uview.hh>
#include <ucontext.hh>
#include <uevent.hh>
#include <uappli.hh>
#include <ucontext.hh>
#include <ugraph.hh>
#include <ucursor.hh>
#include <ustyle.hh>
#include <ucall.hh>
#include <uflow.hh>
#include <umenuImpl.hh>
#include <uobs.hh>
#include <ucolor.hh>
#include <ufont.hh>
#include <unat.hh>
#include <X11/keysym.h>

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

const UClass UMode::uclass("UMode");

UMode UMode::selected(UCONST,   SELECTED, true);
UMode UMode::unselected(UCONST, SELECTED, false);
//UMode UMode::enabled(UCONST,    ENABLED, true);
//UMode UMode::disabled(UCONST,   ENABLED, false);

UMode UMode::canArm(UCONST,     CAN_ARM, true);
//UMode UMode::canBrowseChildren(UCONST, CAN_BROWSE_CHILDREN, true);

//NOTE: canSelect automatically implies canArm
UMode UMode::canSelect(UCONST, CAN_SELECT|CAN_ARM, true);
//UMode UMode::canSelectChildren(UCONST, CAN_SELECT_CHILDREN, true);

UMode UMode::enterHighlight(UCONST,  ENTER_HIGHLIGHT, true);
UMode UMode::enterHighborder(UCONST, ENTER_HIGHBORDER, true);
UMode UMode::actionHighlight(UCONST, ACTION_HIGHLIGHT, true);
UMode UMode::actionHighborder(UCONST,ACTION_HIGHBORDER, true);
//UMode UMode::actionWatch(UCONST,     ACTION_WATCH, true);

UMode UMode::canDrag(UCONST, CAN_DRAG, true);
UMode UMode::canDrop(UCONST, CAN_DROP, true);

//EX: UMode UMode::ignoreEvents(UCONST, IGNORE_EVENTS, true);
UMode UMode::ignoreEvents(UCONST|IGNORE_EVENTS, 0, true);


/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
//bmodes, cmodes, istate;

UOn UOn::message(ANY,   UMode::MESSAGE_CB, 0);
UOn UOn::anyEvent(ANY,  UMode::ANY_EVENT_CB,0);

UOn UOn::add(ANY,       UMode::ADD_CB,0);
UOn UOn::remove(ANY,    UMode::REMOVE_CB,0);

//repaint is an obsolete: equivalent to viewPaint
UOn UOn::repaint(ANY,   UMode::VIEW_PAINT_CB,0);
UOn UOn::viewPaint(ANY, UMode::VIEW_PAINT_CB,0);
UOn UOn::viewResize(ANY,UMode::VIEW_CHANGE_CB,0);
UOn UOn::viewMove(ANY,  UMode::VIEW_CHANGE_CB,0);
//UOn UOn::viewShow(ANY,   UMode::VIEW_CHANGE_CB,0);
//UOn UOn::viewHide(ANY,   UMode::VIEW_CHANGE_CB,0);

//ATT: le mode SELECTED pose probleme car ce n'est pas un mode
//qu'il faut affecter au parent mais juste un referent pour le UOn
//(pour l'instant c'est regle en ne rajoutant pas ces modes et 
// en testant directement l'egalite des UOn dans fire() et match()
// 
UOn UOn::select(ANY,   0, UMode::CAN_SELECT | UMode::CAN_ARM); //!!PAS de SELECTED!!
UOn UOn::unselect(ANY, 0, UMode::CAN_SELECT | UMode::CAN_ARM);

UOn UOn::change(ANY,    UMode::CHANGE_CB, 0);
UOn UOn::propChange(ANY,UMode::CHILD_CHANGE_CB, 0);
UOn UOn::itemChange(ANY,UMode::CHILD_CHANGE_CB, 0);
UOn UOn::strChange(ANY, UMode::CHILD_CHANGE_CB, 0);

UOn UOn::ktype(ANY,    UMode::KEY_CB, 0);
UOn UOn::kpress(ANY,   UMode::KEY_CB, 0);
UOn UOn::krelease(ANY, UMode::KEY_CB, 0);

UOn UOn::mpress(ANY,   UMode::MOUSE_CB, 0);
UOn UOn::mrelease(ANY, UMode::MOUSE_CB, 0);
UOn UOn::mrelax(ANY,   UMode::MOUSE_CB, 0);

// mouse click, double click, etc.
UOn UOn::mclick(ANY,   UMode::MOUSE_CB | UMode::MOUSE_CLICK_CB, 0);
UOn UOn::m1click(ANY,  UMode::MOUSE_CB | UMode::MOUSE_CLICK_CB, 0);
UOn UOn::m2click(ANY,  UMode::MOUSE_CB | UMode::MOUSE_CLICK_CB, 0);
UOn UOn::m3click(ANY,  UMode::MOUSE_CB | UMode::MOUSE_CLICK_CB, 0);
UOn UOn::m4click(ANY,  UMode::MOUSE_CB | UMode::MOUSE_CLICK_CB, 0);


UOn UOn::mdrag(ANY,    UMode::MOUSE_DRAG_CB, 0);
UOn UOn::mmove(ANY,    UMode::MOUSE_MOVE_CB, 0);

UOn UOn::enter(ENTERED,UMode::MOUSE_CROSS_CB, 0);
UOn UOn::leave(ANY,    UMode::MOUSE_CROSS_CB, 0);

// NB: action n'entraine pas necessairement CAN_ARM :
// ce n'est vrai que si on n'est pas en CAN_EDIT_TEXT
UOn UOn::action(IDLE,  0, UMode::CAN_ACTION);
UOn UOn::arm(ARMED,    0, UMode::CAN_ARM);
UOn UOn::disarm(ANY,   0, UMode::CAN_ARM);
//NB: idle is mainly used for conditional flags
UOn UOn::idle(IDLE,    0, 0);

UOn UOn::dragStart(ANY, 0, UMode::CAN_DRAG);
UOn UOn::dragDone(ANY,  0, UMode::CAN_DRAG);
UOn UOn::dropEnter(ANY, 0, UMode::CAN_DROP);
UOn UOn::dropLeave(ANY, 0, UMode::CAN_DROP);
UOn UOn::dropDone(ANY,  0, UMode::CAN_DROP);

//EX: UOn UOn::traverse(ANY,0, UMode::FLAG_EVENTS);
UOn UOn::traverse(ANY,  UMode::FLAG_EVENTS,0);

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

UMode::UMode(u_modes b_modes) {
  bmask  = b_modes; //##mod20avr01##!!!
  bmodes = b_modes;
  cmask  = 0;
  cmodes = 0;
}

UMode::UMode(u_modes b_modes, u_modes c_modes, u_bool onoff) {
  bmask = b_modes;
  cmask = c_modes;

  if (onoff) {
    bmodes = b_modes;
    cmodes = c_modes;
  }
  else {
    // ATTENTION: certains modes (UMode::CANT_DEL etc...) sont propres 
    // a l'objet 'm' et ne doivent surtout PAS ETRE PROPAGES aux parents
    bmodes = b_modes & UMode::CANT_AUTODEL;
    cmodes = 0;
  }
}

void UMode::addingTo(ULink *selflink, UGroup *parent) {
  UBrick::addingTo(selflink, parent);
  setParentModes(parent);
}

void UMode::removingFrom(ULink *selflink, UGroup *parent) {
  //.... enelver le mode!!!
  UBrick::removingFrom(selflink, parent);
}

/* ==================================================== ======== ======= */
//NOTE: this function does not copy modes that are specific to its
//UMode argument (such as UCONST, UREF, etc.)
// !!!ATTENTION: PROBLEME
// setModes() pose probleme avec select/unselect car ce n'est pas un mode
// qu'il faut propager au parent mais juste un referent pour le UOn
// (pour l'instant c'est regle en ne rajoutant pas ces modes et 
// en testant directement l'egalite des UOn dans fire() et match()

void UMode::setParentModes(UCtrl *p/*parent*/) const {
  // B modes
  // ATTENTION: certains modes (UMode::CANT_DEL etc...) sont propres 
  // a l'objet 'm' et ne doivent surtout PAS ETRE PROPAGES aux parents
  u_modes mod = bmodes & ~UMode::CANT_AUTODEL;
  u_modes msk = bmask  & ~UMode::CANT_AUTODEL;

  p->bmodes = p->bmodes |  (mod & msk);    //on_mask;
  p->bmodes = p->bmodes & ~(mod ^ msk);    //off_mask;
 
  // C modes
  p->cmodes = p->cmodes |  (cmodes & cmask);    //on_mask;
  p->cmodes = p->cmodes & ~(cmodes ^ cmask);    //off_mask;
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// NB: on_modes are added in order to make the UGroup & UBox parents
// sensitive to the action that is specified as the first argument

UOn::UOn(u_state state, u_modes b_modes, u_modes c_modes) : UCond() {
  istate = state;
  bmodes = b_modes;
  cmodes = c_modes;
}

// NB: programme' pour faire un OU entre modes et action
//
u_bool UOn::verifies(const UContext*, const class UCtrl *ctrl) const {
  /*
    TOUT CE QUI CONCEERNE LES MODES EST VIRE A PART SELECTION !
  if (mask & SELECTED) {
    u_bool b = ( (ctrl->modes & mask) == modes );
    b = b && ( (ctrl->mask & mask) == mask );
    // on est en mode OU => return true si une des conditions est validee
    if (b) return true;
  }
  */
  // A REVOIR!!
  if (this == &UOn::select) {
    return ctrl->isSelected();
  }
  else  if (this == &UOn::unselect) {
    return ! ctrl->isSelected();
  }

  //!!!PBM: pas le meme test que pour les callbacks!!
  if (istate != UOn::ANY)
    return (ctrl->istate == istate);
  else return false;
}

/* ==================================================== ======== ======= */
//NOTE: this function does not copy modes that are specific to its
//UMode argument (such as UCONST, UREF, etc.)
// !!!ATTENTION: PROBLEME
// setModes() pose probleme avec select/unselect car ce n'est pas un mode
// qu'il faut propager au parent mais juste un referent pour le UOn
// (pour l'instant c'est regle en ne rajoutant pas ces modes et 
// en testant directement l'egalite des UOn dans fire() et match()

void UOn::setParentModes(UCtrl *p/*parent*/) const {
  // B modes
  // ATTENTION: certains modes (UMode::CANT_DEL etc...) sont propres 
  // a l'objet 'm' et ne doivent surtout PAS ETRE PROPAGES au Uctrl !!!
  p->bmodes = p->bmodes | (bmodes & ~UMode::CANT_AUTODEL);
  // C modes
  p->cmodes = p->cmodes | cmodes;
}

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

const UClass UCtrl::uclass("UCtrl");
const int UCtrl::STANDARD = 0;
const int UCtrl::BROWSING = 1;
const int UCtrl::TYPING = 2;

UCtrl::UCtrl()  {
  cmodes = 0;  // bmodes mis a 0 par UBrick
  istate = UOn::IDLE;
}

/* ==================================================== ======== ======= */
// NB: preserves existing modes (except those being redefined)

void UCtrl::setCmodes(u_modes m, u_bool onoff) {
  if (onoff) cmodes = cmodes | m;
  else cmodes = cmodes & ~m;
}
//inherited: void UCtrl::setBmodes(u_modes m, u_bool onoff) {

void UCtrl::setState(u_state state) {
  istate = state;
}

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

u_bool UCtrl::isShown() const {
  if (!isShowable()) return false;

  for (ULinkLink *l = parents.first(); l!=null; l = l->next()) {

    // NB: par == null if no other parent (UFrame or similar)
    UGroup *par = l->link()->parent();
    if (par) {
      /* obs: completement faux: 
	 remplace par methode virtuelle UWin::isShown
	 UWin *win = par->winCast();
	 if (win) {     // si c'est une UWin: tester si elle est initialisee
	 if (win->getWinGraph()) return true;
	 else return false;
      }
      */
      // continuer recursivement (on retourne direct si OK
      // on continue en sequence grace au for() sinon
      if (par->isShown()) return true;
    }
  }

  // aucune racine valide parmi tous les chemins des parents
  return false;
}

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

void UCtrl::select(u_bool state) {
  setCmodes(UMode::SELECTED, state);
  {
    UEvent e(UEvent::select, null, NULL);  //NB: win_view = null!
    if (groupCast()) e.setGroupSource(groupCast());
    if (state) fire(e, UOn::select);
    else fire(e, UOn::unselect);
  }
  update(UUpdate::paint); //!!
}

void UCtrl::enable(u_bool state) {
  //setCmodes(UMode::ENABLED, state);
  if (state) {
    setState(UOn::IDLE);
  }
  else {
    setState(UOn::DISABLED);
  }

  update(UUpdate::paint); //!!
}

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

void UCtrl::changeCursorBehavior(UAppli *a, UEvent& e) {
  UGroup *grp = groupCast();   // doit etre un group 
  if (!grp) return;
  const UCursor *curs = null;
  u_bool change_curs = false;
  //printf("\n-- last_cursor %d\n", a->lastCursor);
  //printf("sourcecursor %d\n", e->getContext()->cursor);
  
  // l'objet a un Cursor associe => modifier cursor courant
  //
  if (grp && isDef(0, UMode::HAS_CURSOR)) {
    curs = grp->getStyle(null)->cursor;
    //printf("Change Curs = %d\n", curs);
    if (curs) change_curs = true;

    // chercher le Cursor !!A OPTIMISER!!
    UCursor *curs2 = grp->getTypedChild<UCursor>();
    if (curs2) {curs = curs2; change_curs = true;} 
  }

  // l'objet n'a pas de Curseur associe => mettre celui qui est herite des 
  // parents via le contexte si c'est necessaire (ie. s'il avait ete change)
  //
  else if (e.getParentContext()->cursor != a->lastCursor) {
    curs = e.getParentContext()->cursor;    // recupere le Cursor du parent
    change_curs = true;
  }
    
  // le test n'est PAS sur 'curs' qui peut etre null pour remise a zero !
  if (change_curs) { 
    UGraph g(e.sourceView);
    g.setCursor(curs);
    a->lastCursor = curs;
  }
}

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

void UCtrl::enterBehavior(UAppli *a, UEvent& e, int bstyle) {
  // comportement standard: le redraw et le change mouse ne sont faits 
  // que si l'on rentre dans l'objet avec la souris non enfoncee

                          // !! MAIS ATT: DnD: le cursor change si btn>0 !!!
  if (bstyle == STANDARD) {
    // changer le cursor si necessaire
    // ATT: ca ne depend uniquement pas de OWN_CURSOR: la view precedente
    // peut avoir affecte son propre cursor qu'il faut donc enlever
  
    //if (e && e->getContext()) 
    changeCursorBehavior(a, e);

    // ne changer l'ACTION et ne reafficher QUE si necessaire
    // ATT: ne pas changer l'action si ces modes sont off car 
    // on aurait alors un changement de couleur des reafficahges partiel 
    // (par exple dans le Text)

    // jamais execute si disabled
    if (istate != UOn::DISABLED
	&& isDef(0, UMode::ENTER_HIGHLIGHT | UMode::ENTER_HIGHBORDER)) {
                                // !!NB: il faudra distinguer les 2 cas !!
      setState(UOn::ENTERED);
      updateView(e, e.sourceView, UUpdate::paint);
    }
  }

  // appeler qu'un button soit pressed ou non  (et meme si DND ???)
  /*if (isDef(UMode::ENTER_CB, 0))*/
  fire(e, UOn::enter); // ordre???

  // cas du Browse ou la souris a ete enfoncee ailleurs
  if (istate != UOn::DISABLED && bstyle == BROWSING) {

    if (!a->dragSource)		// just browsing
      armBehavior(a, e, BROWSING);
    else			// DND being performed
      if (isDef(0, UMode::CAN_DROP)) {
	// check OK  .... + cursor
	fire(e, UOn::dropEnter);

	a->dropTarget = this;
	setState(UOn::DROPPED);
	updateView(e, e.sourceView, UUpdate::paint);
      }
  }
}

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

void UCtrl::leaveBehavior(UAppli *a, UEvent& e) {

  // jamais execute SAUF CALLBACK si disabled
  if (istate != UOn::DISABLED) {

    if (a->dragSource /*&& bstyle == BROWSING*/) {  // DND being performed
      
      if (isDef(0, UMode::CAN_DROP) && istate == UOn::DROPPED) {
	// check OK  .... + cursor
	fire(e, UOn::dropLeave);

	a->dropTarget = null;
	setState(UOn::IDLE);
	updateView(e, e.sourceView, UUpdate::paint);
      }
    }

    else {  // NOT dnd
      
      if (!disarmBehavior(a, e, true))    // true -> means browsing
	return;     // si la vue a ete detruite, sortir au plus vite !
      
      // si istate vaut IDLE c'est que l'on etait en mode browse et 
      // que disarmB() a fait le travail. sinon disarm() a remit en mode
      // ENTER et il faut donc reafficher a la sortie de soursi
    
      if (istate != UOn::IDLE) {
	setState(UOn::IDLE);

	// ne reafficher QUE si necessaire
	if (isDef(0, UMode::ENTER_HIGHLIGHT | UMode::ENTER_HIGHBORDER))
                                // !!NB: il faudra distinguer les 2 cas !!
	  updateView(e, e.sourceView, UUpdate::paint);
      }
    }
  }

  // appeler que btn soit pressed ou non (et meme DND)
  /*if (isDef(UMode::ENTER_CB, 0))*/
  fire(e, UOn::leave);  // cf ordre!!
}

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

void UCtrl::armBehavior(UAppli *a, UEvent& e, int bstyle) {

  // jamais execute si disabled
  if (istate == UOn::DISABLED) return;

  if (isDef(0, UMode::CAN_DRAG)) {	// Drag and Drop

    if (bstyle == STANDARD && e.getButtonNumber() == 2) {// RENDRE PARAMETRABLE!!!
      // !ATT: ne commence JAMAIS en mode BROWSE
      // (sinon, entrerait en mode DND des qu'on survolerait un objet 
      // CAN_DRAG meme si on n'est pas parti de celui-ci)

      a->dragSource = this;
      a->dropTarget = null;

      fire(e, UOn::dragStart);

      // changer curseur!     //NB: pourrait avoir cursor propre 
      UGraph g(e.sourceView);
      g.setCursor(&UCursor::gumby); // a changer...
      a->lastCursor = e.getParentContext()->cursor;
    }
  }

  u_bool armable =
    isDef(0, UMode::CAN_ARM)
    || (isDef(0, UMode::CAN_ACTION) && !isDef(0, UMode::CAN_EDIT_TEXT));


  // ATT: pas de else: un objet peut etre a la fois canArm ET canDrag !
  if (armable) {

    if (bstyle == STANDARD) {
      if (e.getButtonNumber() == 1) { // RENDRE PARAMETRABLE!!!
	a->lastArmed = this;
	setState(UOn::ARMED);
	updateView(e, e.sourceView, UUpdate::paint);
	fire(e, UOn::arm);
      }
    } 

    else if (bstyle == BROWSING) {
      //printf("BROWSING a->browseGroup = %ld  -- e.browseGroup = %ld\n",
      //       a->browseGroup, e.getContext()->browseGroup);
      
      if (      // coming back to the button that was initially pressed
	  (a->lastPressed  == this && armable)
	  ||    // OR: this object is in the current browse group
	  (a->browseGroup && a->browseGroup == e.getParentContext()->browseGroup)
	  ) {
	a->lastArmed = this;
	setState(UOn::ARMED);
	updateView(e, e.sourceView, UUpdate::paint);
	fire(e, UOn::arm);
      } 
    }

    else if (bstyle == TYPING) { // this action was provoked by a keyPress
      a->lastArmed = this;
      setState(UOn::ARMED);
      updateView(e, e.sourceView, UUpdate::paint);
      fire(e, UOn::arm);
    }
  }
}

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

u_bool UCtrl::disarmBehavior(UAppli *a, UEvent& e, int bstyle) {
  // jamais execute si disabled
  if (istate == UOn::DISABLED) return true;

  if (a->dragSource) {   // DND being performed
    //fprintf(stderr, ">drop on: %d\n", this);

    if (a->dropTarget) // si on est toujours au dessus de dropTarget
      a->dropTarget->fire(e, UOn::dropDone);

    a->dragSource->fire(e, UOn::dragDone);

    a->dragSource->setState(UOn::IDLE);
    a->dragSource->updateView(e, e.sourceView, UUpdate::paint);

    // retablir curseur
    UGraph g(e.sourceView);
    g.setCursor(a->lastCursor);
    a->lastCursor = null;

    a->dragSource = null;
    a->dropTarget = null;
  }

  else {

    // dans le cas normal du Disarm provoqué par un mrelease
    // ==> mettre le browseGroup  a null  (cas typique de la List
    //     ou on relache le bouton de la souris)
    // Exception: les menus ou le fait de relacher le menu opener
    //     ne doit pas desactiver le browsing puisque celui-ci
    //     doit continuer a se faire dans le menu affiche

    // si c'est bien l'objet sur lequel on avait appuye
    
    if (a->lastArmed && a->lastArmed == this) {
      //printf("disarm a->lastArmedOrBrowsed %d\n",a->lastArmedOrBrowsed );
       //ex: a->lastArmed = null; mal place

      if (bstyle != BROWSING) {
	//!SI la vue a ete detruite => sortir au plus vite !
	if (!a->lastArmed) return false;

	if (isDef(UMode::MOUSE_CLICK_CB,0)) {

	  // cas multi-click :
	  // verifier delai et position entre clics successifs
	  if (a->mclickCount > 0 
	      && e.time < a->mclickTime + a->mclickDelay
	      && e.xmouse > a->mclickX - a->mclickRadius
	      && e.xmouse < a->mclickX + a->mclickRadius
	      && e.ymouse > a->mclickY - a->mclickRadius
	      && e.ymouse < a->mclickY + a->mclickRadius) {

	    // initialise le champ detail du UEvent recupere par 
	    // methode getClickCount()
	    // (!att: detail sert a plusieurs choses)
	    e.detail = ++a->mclickCount;

	    // mclick appele dans tous les cas qq soit nbre de clicks
	    fire(e, UOn::mclick);

	    //printf("mclickCount %d \n",  a->mclickCount);
	    switch (a->mclickCount) {
	    case 2: fire(e, UOn::m2click); break;
	    case 3: fire(e, UOn::m3click); break;
	    case 4: fire(e, UOn::m4click); break;
	    }
	  }

	  // sinon cas mono-clic:
	  // penser a reinitialiser pour check multi-clics eventuels
	  else {
	    // initialise le champ detail recupere par getClickCount
	    e.detail = a->mclickCount = 1;

	    // mclick appele dans tous les cas qq soit nbre de clicks
	    fire(e, UOn::mclick);

	    // appelee uniquement au 1er click
	    fire(e, UOn::m1click);
	  }

	  //!SI la vue a ete detruite => sortir au plus vite !
	  if (!a->lastArmed) return false;

	  // reset 1st click time and pos
	  a->mclickTime = e.time;
	  a->mclickX = e.xmouse;
	  a->mclickY = e.ymouse;
	}
	else a->mclickCount = 0;

	actionBehavior(a, e);

	//!SI la vue a ete detruite => sortir au plus vite !
	if (!a->lastArmed) return false;
      }

      // reaffichage final en mode IDLE si on est en browsing
      // et en mode ENTER sinon (car ds ce cas le press et le release
      // n'ont pu se faire qu'a l'interieur d'un objet!
      // --noter egalement que ds le cas general non browse actionB() 
      // ne reaffiche pas l'objet et que c'est donc disarm qui le fait

      a->lastArmed = null; 
      if (bstyle == BROWSING)  setState(UOn::IDLE);
      else setState(UOn::ENTERED);
  
      updateView(e, e.sourceView, UUpdate::paint);
      fire(e, UOn::disarm);
    }
  }

  return true;  //pas detruit, continuer normalement
}

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

u_bool UCtrl::actionBehavior(UAppli *a, UEvent& e) {
  // mis en premier sinon fire(action) est appele avec une valeur 
  // de isSelected() qui est obsolete

  if (isSelectable()) {
    // il faut selectionner au DISARM et non au ARM pour avoir un
    // comportement conforme a l'usage
 
    setCmodes(UMode::SELECTED, !isSelected());
    if (isSelected())  fire(e, UOn::select);
    else fire(e, UOn::unselect);

    // change est appele apres select/unselect et avant action
    fire(e, UOn::change);
   }

  setState(UOn::ACTIONED);

  //if (isDef(0, UMode::ACTION_WATCH)     !!! A COMPLETER !!!
  //  changeCursor(...)

  if (isDef(0, UMode::ACTION_HIGHLIGHT | UMode::ACTION_HIGHBORDER))
    updateView(e, e.sourceView, UUpdate::paint);

  fire(e, UOn::action);

  //if (isDef(0, UMode::ACTION_WATCH)     !!! A COMPLETER !!!
  //  changeCursor(...)

  //NB: normalement le repaint est effectue dans le disarm
  // (sauf si appel depuis typeB() auquel cas il y aura un pbm
  // si on met UMode::ACTION_HIGHLIGHT

  //!Attention: la vue a ete detruite => sortir au plus vite !
  if (!a->lastArmed) return false;

  // IDLE doit etre la car actionB() n'est pas seulement appele de armB() 
  // mais aussi de typeB() et aussi pour le update des selectables

  setState(UOn::IDLE);

  if (isSelectable()) {
    //remet a jour toutes les "views" en mode IDLE
    //NB: le redraw simple ne suffit pas car objet modal 
    update(UUpdate::paint);
 }

  return true;  //pas detruit, continuer normalement
}

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

void UCtrl::keyPressBehavior(UAppli *a, UEvent& e) {
  // jamais execute si disabled
  if (istate == UOn::DISABLED) return;

  XKeyEvent *keyevent = &(e.getXEvent()->xkey);
  KeySym keysym = 0; 
  XComposeStatus compose;
  char buf[1] = {'\0'};
  int count = XLookupString(keyevent, buf, sizeof(buf), &keysym, &compose);
  // initialise le champ detail recupere par getTypedChar
  // (!att: detail sert a plusieurs choses)
  e.detail = buf[0];

  if (isDef(UMode::KEY_CB, 0)) fire(e, UOn::kpress);

  if (count > 0) {
    // n'est appele que pour les touches "imprimables"
    //!! NB: faudrait composer les touches !!!
    if (isDef(UMode::KEY_CB, 0))fire(e, UOn::ktype);

    if (isDef(0, UMode::CAN_EDIT_TEXT)) {
      if (keysym == XK_Return)           // rendre configurable!!
	actionBehavior(a, e);
    }
    else {
      if (keysym == XK_space)            // rendre configurable!!
	armBehavior(a, e, TYPING);
    }
  }
}

/* ==================================================== ======== ======= */
// NB: certaines implementation pourraient etre nettement plus compliquees,
// le UOn::type pouvant eventuellement etre appele apres le keyReleaseBehavior

void UCtrl::keyReleaseBehavior(UAppli *a, UEvent& e) {
  if (istate == UOn::DISABLED) return;

  if (isDef(UMode::KEY_CB, 0)){
    XKeyEvent *keyevent = &(e.getXEvent()->xkey);
    KeySym keysym = 0; 
    XComposeStatus compose;
    char buf[1] = {'\0'};
    XLookupString(keyevent, buf, sizeof(buf), &keysym, &compose);
    e.detail = buf[0];

    fire(e, UOn::krelease);
  }
  disarmBehavior(a, e, TYPING);
}


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

const UClass UEdit::uclass("UEdit");
UEdit& uedit() {return *(new UEdit());}

//!!!ATTENTION: PROBLEME si caret_str est detruite !!! REFS INVERSES !!!

// de plus il faudrait simplifier les UCalls!

UEdit::UEdit() : UProp(0) {
  caret_str = null;
  caret_pos  = 0;
  enable_Return = false;
  call1 = call2 = call3 = null;
}

void UEdit::addingTo(ULink *selflink, UGroup *parent) {
  UProp::addingTo(selflink, parent);
  parent->setCmodes(UMode::CAN_EDIT_TEXT
		    | UMode::CAN_SELECT_TEXT,   //!NEW:15m
		    true);
  //parent->setCmodes(UMode::CAN_CLOSE_MENU, false);
  parent->setCmodes(UMode::HAS_AUTOCLOSE_BHV, true);
  parent->setCmodes(UMode::AUTOCLOSE_BHV, false);

  // ajouter handlers au box parent
  // Notes: 
  // - press et non arm car arm provoque un reaffichage 
  //   (comme pressCB fait un reaffichage on reafficherait donc 2 fois)
  // - insert et non add pour mettre le callback en debut de liste
  //   (sinon pressCB, qui fait donc un reaffichage, va d'effacer
  //   les appels graphiques evntuellement faits dans les callbacks
  //   ajoutes par l'utilisateur)
  //
  // NE pas ajouter au debut: fait deconner l'ajout des UProp globales
  // !!! METTRE DANS CACHE !!!

  if (!call1) { // nb: les ucalls sont partages
    call1 = &ucall(this, &UEdit::kpressed,  UOn::kpress);
    call2 = &ucall(this, &UEdit::mpressed,  UOn::mpress);
    call3 = &ucall(this, &UEdit::mreleased, UOn::mrelease);
  }
  parent->add(call1);
  parent->add(call2);
  parent->add(call3);
}

//NB: removingFrom() requires a destructor to be defined
//
void UEdit::removingFrom(ULink *selflink, UGroup *parent) {
  parent->setCmodes(UMode::CAN_EDIT_TEXT
		    | UMode::CAN_SELECT_TEXT,   //!NEW:15m
		    false);
  //parent->setCmodes(UMode::CAN_CLOSE_MENU, true);
  parent->setCmodes(UMode::HAS_AUTOCLOSE_BHV, false);

  // enlever les callbacks (note on pourrait faire + efficace sans vars!)
  if (parent) {
    // destruction des ucalls si pas d'autres parents
    parent->remove(call1, true);
    parent->remove(call2, true);
    parent->remove(call3, true);
  }
  UProp::removingFrom(selflink, parent);
}

UEdit& UEdit::enableReturn(u_bool state) {
  enable_Return = state;
  return *this;
}

void UEdit::putProp(UContext *props, UCtrl *state) {
  props->local.edit = this;
}

// met a jour la string concernee laquelle mettra ainsi a jour ses parents
//  => ceci permet de synchroniser l'affichage de la UStr dans tous les parents
// Remarque:
// par contre les moveCaret sont locaux a chaque uedit (le caret peut avoir
// une position differente dans chaque parent)

void UEdit::update() {
  ///!faux: parents.updateParents(UUpdate::paint);
  if (caret_str) caret_str->update();
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
// remarque: la Str peut eventuellement etre partagee a la fois 
// par des Box editables et non editables
// NOTE: strpos = pos in newstr / -1 means end of string

void UEdit::setCaretPosInCS(UStr *newstr, int newpos, u_bool update_views) {  

  //!!!PLANTAGE si Str detruite entre temps !!!

  //printf("\n$ set caret: str:%d pos%d -  newstr:%d Pos%d\n",
  //	 str, charpos, newstr, newpos);

  if (update_views && caret_str //&& newstr != str pas correct dans le cas
      // des flow car l'affichage via upd.paintSre(newstr, charpos)
      // se fait cellule par cellule
      ) {
    // faut d'abord effacer l'ancien caret
    UUpdate upd(UUpdate::PAINT);
    //printf("$ clear caret --> update->paintStr()\n");
    upd.paintStr(caret_str, caret_pos, caret_pos);
    caret_str = null;
    // update all parents (et pas seulement la Box cliquee)
    parents.updateParents(upd); 
  }

  caret_str = newstr;
  if (newpos < 0 || newpos > newstr->length()) 
    newpos = caret_str->length();
  caret_pos = newpos;

  if (update_views && newstr) {
    // afficher caret a la nvlle position
    UUpdate upd(UUpdate::PAINT);
    //printf("$ new caret --> update->paintStr()\n");
    upd.paintStr(newstr, caret_pos, caret_pos);
    // update all parents (et pas seulement la Box cliquee)
    parents.updateParents(upd); 
  }
}

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

static
u_bool findPrevNextStr(UGroup *box, UStr *str, UStr **s_prev, UStr **s_next) {
  *s_prev = null;
  *s_next = null;
  u_bool str_was_found = false;
  UStr *s = null;

  // il faut parcourir la liste depuis le debut car les listes sont 
  // simplement chainees
  //
  for (ULink *ch = box->getChildLinks(); ch != null; ch = ch->next())
    if ((s = ch->brick()->strCast())) {

      if (s == str) str_was_found = true;   // str is found

      // [elc@10mar00] : skip the empty strings !!!
      //
      else if (s->chars() != null && s->chars()[0] != '\0') {
	if (str_was_found) {
	  *s_next = s;		// this is the 'next' string
	  break; 		// nothing else to do => break the loop
	}
	else *s_prev = s;	// this is the 'previous' string
      }
    }

  return str_was_found;
}

/* ==================================================== ======== ======= */
// - pos_in_string is the local position IN that UStr
// - if pos_in_string < 0 the caret goes to the previous UStr in the Box
// - if pos_in_string > UStr.length(),  the caret goes to the next UStr

void UEdit::moveCaretPosInCS(int newpos, u_bool update_views) {
  if (!caret_str) return;
  // le curseur change de Str
  if (newpos < 0 || newpos > caret_str->length()) {
 
    // mettre a jour toutes les Box contenant UEdit (si ca a un sens ...)
    for (ULinkLink *llk = parents.first(); llk != null; llk = llk->next()) {

      UGroup *box = llk->parent();
      UStr *s_prev, *s_next;

      if (box && findPrevNextStr(box, caret_str, &s_prev, &s_next)) {

	if (newpos < 0 && s_prev) 
	  setCaretPosInCS(s_prev, -1, update_views);   // dernier char

	else if (newpos > caret_str->length() && s_next)
	  setCaretPosInCS(s_next, 0, update_views);	   // premier char
      }
    }
  }

  else {			// le curseur reste dans la meme Str
    setCaretPosInCS(caret_str, newpos, update_views);
  }
}

/* ==================================================== ======== ======= */
// ATT: disp_curpos != UEdit.charpos :
// - charpos = position du cursor dans la UStr
// - disp_curpos = position d'affichage en tenant compte du fait
//   que les UStr peuvent etre cassees en plusieurs lignes

void UEdit::drawCaret(const UStr *st, UContext *props, const URegion &r, 
		      UFlowCell *cell, UWinGraph &g) const {
  if (!st) return;
  int xpos = r.x;

  //!att: meme si !(str->s) penser a afficher le Caret
  if (st->chars()) {
    if (cell)
      xpos += g.getXPos(&props->fontdesc, st->chars() + cell->offset,
			cell->len, caret_pos - cell->offset);
    else 
      xpos += g.getXPos(&props->fontdesc, st->chars(), st->length(), caret_pos);
  }

  // sinon on est en dehors de la zone de clipping
  if (xpos <= r.x + r.width) {
    g.setColor(props->color);
    g.drawLine(xpos, r.y, 
	       // -1 sinon ca deborde de la zone de background!
	       xpos, r.y + g.getTextHeight(&props->fontdesc) -1);
  }
}

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

void UEdit::mpressed(UEvent *e) {
  if (!e) return;
  UItemData itd;
  UStr *newstr = null;
  int btn = e->getButtonNumber();

  if ((btn == 1 || btn == 2) && (newstr = e->getStr(itd))) { // PARAMETRER
    setCaretPosInCS(newstr, itd.strpos, true);
    if (btn == 2) {		// PARAMETRER
      // APRES setCaretPosition !
      UView *winview = null;
      UAppli *appli = null;

      if ((winview = e->getWinView())
	  && (appli = winview->getAppli())
	  && caret_str != null) {
	// !ATT: pbm  si la USTr est detruite entre temps!
	appli->insertServerSelection(*e, caret_str, caret_pos);
      }
    }
  }
}

void UEdit::mreleased(UEvent *e) {
  if (!e) return;
  UItemData itd;
  UStr *newstr = null;

  // mrelease1 necessaire pour que le Drag selection fonctionne
  if (e->getButtonNumber() == 1 	                  // PARAMETRER
      && (newstr = e->getStr(itd))) {
    setCaretPosInCS(newstr, itd.strpos, true);
  }
}

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

void UEdit::kpressed(UEvent *e) {
  if (!caret_str) return;
  KeySym keysym = e->getKeySym(); 
  int keychar   = e->getKeyChar();
  u_bool done  = false;

  switch (keysym) {
  case XK_BackSpace:
    if (caret_pos <= 0) {
      // passe au component precedent
      moveCaretPosInCS(-1, false);
    }
    else if (caret_pos > caret_str->length()) 
      caret_pos = caret_str->length();
    
    // nb: str et caret_pos event modifies par ce qui precede 
    if (caret_str && caret_pos > 0) {
      done = caret_str->replaceImpl(caret_pos-1, 1, null, false);
      if (done) moveCaretPosInCS(caret_pos-1, false);
      update();
    }
    break;

  case XK_Delete:
  case XK_KP_Delete:
    if (caret_pos >= 0 && caret_pos < caret_str->length()) {
      done = caret_str->replaceImpl(caret_pos, 1, null, false);
      //Caret ne doit pas bouger dans ce cas
      //if (done) moveCaretPosInCS(caret_pos-1, false);
      update();
    }
    break;

  case XK_Left:
  case XK_KP_Left:
    moveCaretPosInCS(caret_pos - 1, true);
    break;
	
  case XK_Right:
  case XK_KP_Right:
    moveCaretPosInCS(caret_pos + 1, true);
    break;
    
  case XK_Home:
  case XK_KP_Home:
  case XK_Begin:
  case XK_KP_Begin:
    moveCaretPosInCS(0, true);
    break;

  case XK_End:
  case XK_KP_End:
    moveCaretPosInCS(caret_str->length(), true);
    break;

  case XK_Return:
    if (enable_Return) {
      keychar = '\n';

      if (caret_pos == caret_str->length()-1)
	// -1 means: end of string
	done = caret_str->insertImpl(-1, keychar, false);
      else 
	done = caret_str->insertImpl(caret_pos, keychar, false);
      
      if (done) moveCaretPosInCS(caret_pos+1, false);
      update();
    }
    break;
    
  default:
    //EX: if (isprint(kk))... for some reason isprint elime les chars 
    //accentues sauf avec certaines locales quand elles existent => pas sur!
    // si le char est affichable
    if (keychar != '\0' && !iscntrl(keychar)) {

      if (caret_pos == caret_str->length() - 1)
	//-1 means: end of string
	done = caret_str->insertImpl(-1, keychar, false);
      else 
	done = caret_str->insertImpl(caret_pos, keychar, false);
	
      if (done) moveCaretPosInCS(caret_pos+1, false);
      //NB: doubebuf abandonne car delai trop long
      update();
    }
    break;
  }
}
/* ==================================================== ======== ======= */
/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */

UTextsel::UTextsel() {
  color = &UColor::red;
  bgcolor = null;
  fontdesc = null;
  inObj = null;
  reset(false);
}

void UTextsel::reset(u_bool upd) {
  pressLink = oldLink = null;
  pressPos = oldPos = 0;
  fromLink = toLink = null;
  fromPos  = toPos = 0;

  if (upd) update(null, 0);
  inObj = null;
}

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

void UTextsel::update(ULink *newLink, int newPos) {
  if (!inObj) return;
  int refreshFromPos = 0, refreshToPos = 0;

  if (pressLink && newLink) {

    if (newLink == pressLink) {
      fromLink = toLink = pressLink; 
      if (newPos > pressPos) {fromPos = pressPos; toPos = newPos;}
      else {fromPos = newPos; toPos = pressPos;}
    }

    else {
      u_bool found = false;

      for (ULink *l = pressLink; l!=null; l = l->next())
	if (l == newLink) {
	  found = true;
	  fromLink = pressLink;
	  fromPos  = pressPos;
	  toLink   = newLink;
	  toPos    = newPos;
	  break;
	}
  
      if (!found) {
	for (ULink *l = newLink; l!=null; l = l->next())
	  if (l == pressLink) {
	    fromLink = newLink;
	    fromPos  = newPos;
	    toLink   = pressLink;
	    toPos    = pressPos;
	    break;
	  }
      }
      //NB: dans certains cas newLink appartient a un autre objet
      //et on n'arrive donc pas a trouver fromLink et toLink
      // c'est pas grave on garde les anciens
    }

    refreshFromPos = fromPos, refreshToPos = toPos;

    if (oldLink == fromLink && newPos == fromPos) {
      if (oldPos < fromPos) {
	refreshFromPos = oldPos;
	refreshToPos   = fromPos;
      }
      else {
	refreshFromPos = fromPos;
	refreshToPos   = oldPos;
      }
    }

    else if (oldLink == toLink && newPos == toPos) {
      if (oldPos < toPos) {
	refreshFromPos = oldPos;
	refreshToPos = toPos;
      }
      else {
	refreshFromPos = toPos;
	refreshToPos   = oldPos;
      }
    }

    oldLink = newLink;
    oldPos  = newPos;
  }

  paint(refreshFromPos, refreshToPos);
}

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

void UTextsel::paint(int refreshFromPos, int refreshToPos) {

  u_bool state = false;

  for (ULink *l = inObj->getChildLinks(); l!=null; l = l->next()) {

    if (l == fromLink) state = true;

    UItem *item = l->brick()->itemCast();
    if (item) {
      u_modes bmodes = item->getBmodes();

      if (state || (!state && (bmodes & UMode::IN_TEXTSEL) != 0)) {
	item->setBmodes(UMode::IN_TEXTSEL, state);
	UUpdate upd(UUpdate::PAINT);

	UStr *str = item->strCast();
	if (!str) upd.paintItem(item);
	else {
	  /* manque de bol l'optimisation [commentee]
	     est completement fausse car il faut egalement reafficher
	     les zones qui redeviennent non selectionneees et repassent en noir
	     --> il faudrait donc prendre le MAX des anciennes et 
	     des nouvelles positions
	  */
	  if (l == fromLink && l == toLink)
	    upd.paintStr(str, refreshFromPos, refreshToPos);	 
	  else if (l == fromLink)
	    upd.paintStr(str, refreshFromPos, str->length());
	  else if (l == toLink)
	    upd.paintStr(str, 0, refreshToPos);
	  else 
	    upd.paintStr(str, 0, str->length());
	  
	}
	// a defaut de faire un traitement plus optimise
	// on reaffiche en double buffering pour eviter le flicking
	upd.doubleBuffering(true);

	// update all parents (not only the inBox being selected)
	inObj->update(upd); 
      }
    }
    
    //!att: a mettre a la fin du corps de boucle!
    if (l == toLink) state = false;
  }
}

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

void UTextsel::copyTo(UStr &selection) const {

  int refreshFromPos = fromPos, refreshToPos = toPos;
  u_bool state = false;
  char *ps = null;
  selection.set((UStr*)null);

  for (ULink *l = inObj->getChildLinks(); l!=null; l = l->next()) {

    if (l == fromLink) state = true;

    UItem *item = l->brick()->itemCast();
    if (item) {
      u_modes bmodes = item->getBmodes();

      if (state || (!state && (bmodes & UMode::IN_TEXTSEL) != 0)) {
	
	UStr *str = item->strCast();
	if (str) {
	  if (l == fromLink && l == toLink) {
	    if ((ps = str->getN(refreshFromPos, refreshToPos-refreshFromPos))){
	      selection.append(ps);
	      free(ps);
	    }
	  }
	  else if (l == fromLink) {
	    if ((ps = str->getN(refreshFromPos, str->length()))){
	      selection.append(ps);
	      free(ps);
	    }
	  }
	  else if (l == toLink) {
	    if ((ps = str->getN(0, refreshToPos))){
	      selection.append(ps);
	      free(ps);
	    }
	  }
	  else selection.append(str->chars());
	}
      }
    }
    
    //!att: a mettre a la fin du corps de boucle!
    if (l == toLink) state = false;
  }
}

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

void UTextsel::start(UAppli *a, UEvent& e) {

  if (e.getButtonNumber() == 1    // RENDRE PARAMETRABLE!!!
      && e.getSource()) {
    
    //effacer selection courante (ce qui necessite ancien obj)
    reset(true);

    // nouvelle selection
    inObj = e.getSource();
    if (!inObj) return;
    beingSelected = true;

    UItemData itd;
    //printf("\n$ textsel:start --> getStr()\n");
    if (!e.getStr(itd)) inObj = null;
    else {
      pressLink = itd.itemLink;
      pressPos  = itd.strpos;
    }
  }
}

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

void UTextsel::extend(UAppli *a, UEvent& e) {
  if (!inObj || e.getSource() != inObj) return;

  // si on a localise une string, changer la selection
  // (sinon on ne fait rien pour ne pas abimer inutilement 
  // la selection precedente)

  UItemData itd;
  //printf("\n$ textsel:extend --> getStr()\n");
  if (e.getStr(itd)) {
    update(itd.itemLink, itd.strpos);
  }
  /*
  UView *view = e.getView();
  if (view) {
    // on ne reaffiche que la vue courante
    UUpdate upd;
    upd.paintRegion(&itd.region);
    e.getSource()->updateView(e, view, upd); 
  }
  */
}

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

void UTextsel::complete(UAppli *a, UEvent& e) {
  beingSelected = false;

  // si on a *effectivement* selectionne qq chose 
  // ==> obtenir la XSelection (pour desactiver la section dans les
  //     autres applis et leur permettre ensuite de savoir que qq chose
  //     a ete selectione)

  if (fromLink && toLink && (fromLink != toLink || fromPos != toPos))
    a->ownServerSelection(e);
}

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

UFlag::UFlag(const char *name) {
  flagname = (name ? strdup(name) : null);
}

UFlag& uflag(const char *name) {
  return *new UFlag(name);
}

u_bool UFlag::verifies(const UContext *ctx, const class UCtrl*) const {
  if (!ctx->pflags || !*ctx->pflags) return false;
  else {
    for (int k = 0; k < ctx->flagCount; k++) 
      if ((*ctx->pflags)[k] == this) return true;
  }
  return false;  // not found
}

void UFlag::setParentModes(UCtrl *parent) const {}

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

UScaleRange::UScaleRange(int smin, int smax) {
  scale_min = smin, scale_max = smax;
}

UScaleRange& uscaleRange(int scale_min, int scale_max) {
  return *new UScaleRange(scale_min, scale_max);
}

u_bool UScaleRange::verifies(const UContext *ctx, const class UCtrl*) const {
  //NB: lscale = LOGICAL scale
  return (ctx->lscale >= scale_min && ctx->lscale <= scale_max);
};

void UScaleRange::setParentModes(UCtrl *parent) const {}

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

const UClass UFlagFilter::uclass("UFlagFilter");

UFlagFilter::UFlagFilter(const UFlag &fl, u_modes m) : UProp(m) {
  flag = &fl;
}
UFlagFilter::UFlagFilter(const UFlag *fl, u_modes m) : UProp(m) {
  flag = fl;
}

UFlagFilter& uflagFilter(const UFlag &fl) {return *(new UFlagFilter(fl));}
UFlagFilter& uflagFilter(const UFlag *fl) {return *(new UFlagFilter(fl));}

/*
void UFlagFilter::set(const UFlag &fl) {
  flag = &fl;
  //if (upd) update();
  update();
}
*/
void UFlagFilter::set(const UFlag *fl) {
  flag = fl;
  //if (upd) update();
  update();
}

void UFlagFilter::update() {
  //parents.updateParents(UUpdate::win);
  parents.updateParents(UUpdate::layout);
}

void UFlagFilter::putProp(UContext *ctx, UCtrl *state) {
  if (!ctx->pflags) {
    uerror("UFlagFilter::putProp", "Null flag vector");
    return;
  }

  if (*ctx->pflags)
    *ctx->pflags = (const UFlag**)realloc(*ctx->pflags, 
				    (ctx->flagCount+1) * sizeof(UFlag*));
  else *ctx->pflags = (const UFlag**)malloc(sizeof(UFlag**));

  (*ctx->pflags)[ctx->flagCount] = flag;
  (ctx->flagCount)++;
}

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

