/* ==================================================== ======== ======= *
 *
 *  uuborder.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	"@(#)uuborder.cc		ubit:001.11.6"
#include <ubrick.hh>
#include <uctrl.hh>
#include <uprop.hh>
#include <ucolor.hh>
#include <uborder.hh>
#include <ugraph.hh>
#include <uview.hh>
#include <uviewImpl.hh>
#include <ucontext.hh>
#include <ubox.hh>


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

const int UBorder::NONE = 0, 
  UBorder::ACTIVE_SHADOW_IN  = 1, 
  UBorder::ACTIVE_SHADOW_OUT = 2, 
  UBorder::SHADOW_IN  = 3, 
  UBorder::SHADOW_OUT = 4, 
  UBorder::ETCHED_IN  = 5, 
  UBorder::ETCHED_OUT = 6;

static UMargin margin0(0,0), margin1(1,1), margin2(2,2);

UBorder UBorder::none(margin0, NONE, UMode::UCONST);
UBorder UBorder::activeShadowIn(margin1, ACTIVE_SHADOW_IN, UMode::UCONST);
UBorder UBorder::activeShadowOut(margin1, ACTIVE_SHADOW_OUT, UMode::UCONST);
UBorder UBorder::shadowIn(margin1, SHADOW_IN, UMode::UCONST);
UBorder UBorder::shadowOut(margin1, SHADOW_OUT, UMode::UCONST);
UBorder UBorder::etchedIn(margin2, ETCHED_IN, UMode::UCONST);
UBorder UBorder::etchedOut(margin2, ETCHED_OUT, UMode::UCONST);
//UBorder UBorder::dialog("dialog", 7, UMode::UCONST);
//UBorder UBorder::menu("menu", 8, UMode::UCONST);

/* ==================================================== [Elc:001] ======= */
/* ==================================================== ========= ======= */
 
UBorder& uborder(const UBorder &b) {
  return *(new UBorder(b));
}
UBorder& uborder(const UMargin &m, int d) {
  return *(new UBorder(m,d));
}
UBorder& uborder(UArgs a) {
  return *(new UBorder(a));
}

/* ==================================================== [Elc:001] ======= */
/* ==================================================== ========= ======= */ 

UBorder::UBorder(const UMargin &marg, int decor, u_modes m) : UProp(m) { 
  decoration = decor;
  margin     = marg;
  color      = &UColor::white;
  bgcolor    = &UColor::black;
  elems      = null;
}
UBorder::UBorder(const UBorder &b, u_modes m) : UProp(m) { 
  decoration = b.decoration;
  margin     = b.margin;
  color      = b.color;
  bgcolor    = b.bgcolor;
  elems      = null;
}
UBorder::UBorder(UArgs a) : UProp(0) { 
  decoration = 0;
  margin.set(0,0);
  color      = &UColor::white;
  bgcolor    = &UColor::black;
  elems      = new UGroup(uhcenter() + utop() + UFont::medium);
  elems->addlist(a);
 }

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

void UBorder::init(ULink *selflink, ULink *parlink, UView *parview) {
  if (elems)
    elems->init(elems->makeLink(), selflink, parview);
}

void UBorder::update() {
  parents.updateParents(UUpdate::layout);
}
void UBorder::putProp(UContext *props, UCtrl *state) {
  props->local.border = this;
}

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

void UBorder::set(const UBorder &b, u_bool upd) {
  decoration = b.decoration;
  margin     = b.margin;
  color      = b.color;
  bgcolor    = b.bgcolor;
  if (upd) update();
  changed(true);
}

void UBorder::setDecoration(int decor) {
  decoration = decor;
}
void UBorder::setMargin(const UMargin& ma)  {
  margin = ma;
}
void UBorder::setColor(const UColor& c)  {
  color = &c;
}
void UBorder::setBgcolor(const UColor& c)  {
  bgcolor = &c;
}

/* ==================================================== [Elc:001] ======= */
/* ==================================================== ========= ======= */

u_bool UBorder::doLayout(UView *view, UContext &parp, UMargin &m) {
  //!att: pour l'instant c'est EXCLUSIF!
  if (!elems) {
    m = margin;
    return false;
  }
  else return doElemLayout(view, parp, m);
}

void UBorder::doUpdate(UUpdateImpl& vd, UWinGraph &g, 
		       UContext &parp, const URegion &r){
  //!att: pour l'instant c'est EXCLUSIF!
  if (!elems) doDecorUpdate(vd, g, parp, r);
  else doElemUpdate(vd, g, parp, r);
}

/* ==================================================== [Elc:001] ======= */
/* ==================================================== ========= ======= */

void UBorder::doDecorUpdate(UUpdateImpl& vd, UWinGraph &g, 
			    UContext &parp, const URegion &r){
  vd.margin = margin;
  //si rien a peindre on s'arrete la
  if (!vd.can_paint) return;

  int parx1 = vd.view->getXwin();
  int pary1 = vd.view->getYwin();
  int parx2 = vd.view->getXwin() + vd.view->getWidth() - 1;
  int pary2 = vd.view->getYwin() + vd.view->getHeight() -1;
 
  //!ATT: il peut y avoir des scrollbars meme si border == none
  if (decoration != NONE) {

    // INVERSER les activeXXX qunad on presse le button
    u_bool active;
    if (parp.obj->isSelected())
      active = (parp.obj->getState() != UOn::ARMED);
    else 
      active = (parp.obj->getState() == UOn::ARMED);

    const UColor *back, *fore;
    if (decoration == SHADOW_IN
	|| (decoration == ACTIVE_SHADOW_IN && !active)
	|| (decoration == ACTIVE_SHADOW_OUT && active)
	||  decoration == ETCHED_IN
	) {
      fore = bgcolor;
      back = color;
    }
    else {
      fore = color;
      back = bgcolor;
    }

    if (decoration == ETCHED_IN || decoration == ETCHED_OUT) {
      g.setColor(back);
      g.drawRect(parx1+1, pary1+1, vd.view->getWidth()-1, vd.view->getHeight()-1);
      g.setColor(fore);
      g.drawRect(parx1, pary1, vd.view->getWidth()-1,  vd.view->getHeight()-1);
    }
    /**
    else if (decoration == UBorder::dialog.ix) {
      // !!! devarit etre CONTEXTUEL !!!
    g.setColor(back);
      g.drawRect(parx1+1, pary1+1, vd.view->getWidth()-1, vd.view->getHeight()-1);
      g.setColor(fore);
      g.drawRect(parx1, pary1, vd.view->getWidth()-1,  vd.view->getHeight()-1);
    }
    ***/
    else {
      // backShadow = bottom+right
      g.setColor(back);
      g.drawLine(parx1,pary2, parx2,pary2); // bottom
      g.drawLine(parx2,pary1, parx2,pary2); // right
      
      // frontShadow = top+left
      g.setColor(fore);
      g.drawLine(parx1,pary1, parx2,pary1); // top
      g.drawLine(parx1,pary1, parx1,pary2); // left
    }
  }
}

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

u_bool UBorder::doElemLayout(UView *view, UContext &parp, UMargin &m) {
  UGroup *grp = elems;
  UContext curp(grp, view, parp);
  u_bool mustLayoutAgain = false;

  //ULink *ch = view->doPrelude(grp, curp);
  UMultiList mlist(curp, grp);

  margin.set(0, 0, 0, 0);
  //int children_w = 0, children_h = 0;

  //for ( ; ch != null; ch = ch->next())
  for (ULink *ch = mlist.first(); ch != null; ch = mlist.next(ch))
    if (ch->verifies(&curp, grp)) {

      UBrick *b = ch->brick();
      UView *chboxview = null;
      UItem *it = null;
      UGroup *chgrp = null; //!att reinit!
      UViewLayout chvl; //!att: reinit by constr.

      if (b->propCast())  
	b->propCast()->putProp(&curp, grp);

      /* a completer...
      else if ((chgrp = b->groupNotBoxCast())) {
	if (chgrp->isShowable())
	  doUpdateG(vd, b->groupCast(), curp, g, r, clip, vup); //meme vd !
      }
      */

      // UItems + UBoxes, UWins
      else if ((it = b->itemCast())
	       ||
	       ((chgrp = b->boxCast()) 
		&& chgrp->isShowable()
		&& chgrp->isDef(0, UMode::BOX)
		&& (chboxview = ((UBoxLink*)ch)->getView(view)))
	       ) {

	if (it) it->getSize(&curp, &(chvl.cmax_w), &(chvl.cmax_h));
	else {
	  mustLayoutAgain |= chboxview->doLayout(curp, chvl);
	  if (chgrp->isDef(0,UMode::FLOATING) || ch->floatingCast())
	    continue;  //rien d'autre a faire
	}

	//NB: top et bottom s'adaptent a la zone centrale en largeur
	//ils ne controlent donc que la hauteur de leurs zones respectives
	switch (curp.local.valign) {
	case UView::TOP:
	  if (chvl.cmax_h > margin.top)  margin.top = chvl.cmax_h;
	  break;
	  
	case UView::BOTTOM:
	  if (chvl.cmax_h > margin.bottom)  margin.bottom = chvl.cmax_h;
	  break;

	  //pas pris en compte: adapte sur zone centrale
	  //case UView::VCENTER:
	  //case UView::VFLEX:
	  //if (chvl.cmax_h > children_h)  children_h = chvl.cmax_h;
	  //break;
	}

	//NB: left et right s'adaptent a la zone centrale en hauteur
	//ils ne controlent donc que la largeur de leurs zones respectives
	switch (curp.local.halign) {
	case UView::LEFT:
	  if (chvl.cmax_w > margin.left)  margin.left = chvl.cmax_w;
	  break;

	case UView::RIGHT:
	  if (chvl.cmax_w > margin.right)  margin.right = chvl.cmax_w;
	  break;
	  
	  //pas pris en compte: adapte sur zone centrale
	  //case UView::HCENTER:
	  //case UView::HFLEX:
	  //if (chvl.cmax_w > children_w)  children_w = chvl.cmax_w;
	  //break;
	}
      }

    } //endfor
  
  m = margin;
  return mustLayoutAgain;
}

/* ==================================================== [Elc:01] ======= */
/* ==================================================== ======== ======= */
//NB: mode SearchItem: juste recuperer l'item et sa position sans redessiner
//!ATT il faut IMPERATIVEMENT itemData != null dans le mode SearchItem !
//NB: clip est passe en valeur, pas r

void UBorder::doElemUpdate(UUpdateImpl &vd, UWinGraph &g,
			   UContext &parp, const URegion &r) {
  UGroup *grp = elems;
  UContext curp(grp, vd.view, parp);

  //ULink *ch = vd.view->doPrelude(grp, curp);
  UMultiList mlist(curp, grp);

  vd.margin = margin;  //!!!

  //for ( ; ch != null; ch = ch->next())
  for (ULink *ch = mlist.first(); ch != null; ch = mlist.next(ch))
   if (ch->verifies(&curp, grp)) {

    UBrick *b = ch->brick();
    UView *chboxview = null;
    UItem *it = null; 
    UGroup *chgrp = null; //!att reinit!

    if (b->propCast())  
      b->propCast()->putProp(&curp, grp);

    /* a completer
       else if ((chgrp = b->groupNotBoxCast())) {
       if (chgrp->isShowable())
	  doUpdateG(vd, b->groupCast(), curp, g, r, clip, vup); //meme vd !
       }
    */
    
    // UItems + UBoxes, UWins
    else if ( 
	     (it = b->itemCast())
	     ||
	     ( (chgrp = b->boxCast()) 
	       && chgrp->isShowable()
	       && chgrp->isDef(0, UMode::BOX)
	       && (chboxview = ((UBoxLink*)ch)->getView(vd.view))
	       )
	     ) {
      
      // 1::cas des Floating
      if ((chgrp && chgrp->isDef(0, UMode::FLOATING)) || ch->floatingCast()) {

	URegion fl_chr; //chr for floatings
	chboxview->getSize(fl_chr.width, fl_chr.height);
	/** obs
	if (ch->floatingCast()) {
	    // transformer les coords relatives en coords absolues
	    // puis afficher l'objet a cet endroit precis
	    fl_chr.x = r.x + ch->floatingCast()->getX();
	    fl_chr.y = r.y + ch->floatingCast()->getY();
	}
	else **/
	{
	  //!att: on rajoute la MARGE: les coords sont locales 
	  // % a l'INTERIEUR du CADRE et non % a l'origine de la Box.
	  fl_chr.x = r.x;
	  fl_chr.y = r.y;
	  // coords relatives rajoutees ensuite dans doUpdate
	}
	
	chboxview->doUpdate(curp, fl_chr, vd.chclip, vd.upmode);
      }
      
      // 2- Cas particulier de la zone centrale:
      // celle-ci est la seule a pouvoir etre scrollee

      else if ((curp.local.valign==UView::VFLEX || curp.local.valign==UView::VCENTER)
	       &&
	       (curp.local.halign==UView::HFLEX || curp.local.halign==UView::HCENTER)
	       ){
      }

      // 3- cas du frame entourant la zone centrale
      else {

	switch (curp.local.valign) {
	case UView::TOP:
	  vd.chr.y = r.y;
	  vd.chr.height = margin.top;
	  break;

	case UView::BOTTOM:
	  vd.chr.y = r.y + r.height - margin.bottom;
	  vd.chr.height = margin.bottom;
	  break;

	case UView::VCENTER: // ne s'adapte PAS a la taille du Pane
	case UView::VFLEX: // Middle adaptatif: s'adapte a la taille du Pane
	  vd.chr.y = r.y + margin.top;
#if EXX
	  // !NOTE: pour les Items, VFLEX est identique a MIDDLE
	  if (!it && curp.local.valign == VFLEX)
	    vd.chr.height = r.height - margin.top - margin.bottom;
	  else vd.chr.height = chview->h;
#endif
	  //dans TOUS les cas (flex ou center/middle, Item ou Box) :
	  // => TOUJOURS justifier
	  vd.chr.height = r.height - margin.top - margin.bottom; 
	  break;
	}

	switch(curp.local.halign) {
	case UView::LEFT:
	  vd.chr.x = r.x;
	  vd.chr.width = margin.left;
	  break;

	case UView::RIGHT:
	  vd.chr.x = r.x + r.width - margin.right;
	  vd.chr.width = margin.right;
	  break;

	case UView::HCENTER: // ne s'adapte PAS a la taille du Pane
	case UView::HFLEX: // Center adaptatif: s'adapte a la taille du Pane
	  vd.chr.x = r.x + margin.left;
#if EXX
	  // !NOTE: pour les Items, FLEX est identique a CENTER
	  if (!it && curp.local.halign == HFLEX)
	    chr.width = r.width - margin.left - margin.right;	 
	  else chr.width = chview->w;
#endif
	  //dans TOUS les cas (flex ou center/middle, Item ou Box) :
	  // => TOUJOURS justifie
	  vd.chr.width = r.width - margin.left - margin.right;
	  break;
	}//endswitch()


	if (vd.chr.width > 0 && vd.chr.height > 0) {

	  //pas un item => Box
	  if (!it) chboxview->doUpdate(curp, vd.chr, vd.chclip, vd.upmode);
	  else {

	    //NB: ITEM_OPS => can_paint == false mais pas l'inverse !
	    if (vd.can_paint) {
	      g.setClip(vd.chclip);
	      it->paint(g, &curp, vd.chr);
	    }

	    else if (vd.upmode.mode >= UViewUpdate::LOCATE_ITEM_POS) {
	      // do not draw, just find
	      // !att: il faut que e->itemData soit =!null
	      // s'arrete de cherche qunad true est renvoye'

	      if (vd.upmode.mode == UViewUpdate::LOCATE_ITEM_POS) {
		if (vd.view->locateItemVPos(curp, ch, g, vd.chr, vd.upmode))
		  return;
	      }
	      else if (vd.upmode.mode == UViewUpdate::LOCATE_ITEM_PTR) {
		if (vd.view->locateItemPtr(curp, ch, g, vd.chr, vd.upmode))
		  return;
	      }
	    } //endif(ITEM_OPS)
	  }

	}// endif (chr.width > 0 && chr.height > 0)
      }//endelse (in frame)
    }
  }

  // call callbacks (desormais appele en fin de traitement)
  //vd.view->paintFire(grp, curp.winview, vd.chclip);
  if (vd.can_paint 
      && grp->isDef(UMode::VIEW_PAINT_CB|UMode::VIEW_CHANGE_CB,0)) 
    vd.callbacks(grp, curp.winview);
  /*
FIN:
  if (vup.mode == UViewUpdate::UPDATE_DATA && isDef(DAMAGED)) {
    //damaged = false;
    setVmodes(DAMAGED, false);
    // ecrase les autres overlapp, y compris ceux mis par enfants
    // et ceux mis par objets qui sont avant dans les listes d'affichage
    vup.overlapp = 1;
  }
  */
}

/* ==================================================== [The End] ======= */
/* ==================================================== [Elc:001] ======= */

