//<copyright>
//
// Copyright (c) 1993
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// original version:
// Copyright (c) 1991 Stanford University
// Copyright (c) 1991 Silicon Graphics, Inc.
//
//</copyright>

//<file>
//
// Name: sscrbox.C
//
// Purpose: implementation of class SmoothTBScrollBox
//
// Created: 1.9.94   Gaisbauer Mansuet Juergen
//
// Modified:
//
// Description:
//
//</file>



/*
 * ScrollBox - scrollable list of glyphs
 */

#include "sscrbox.h"

#include <IV-X11/xcanvas.h>
#include <IV-X11/xdisplay.h>
#include <InterViews/canvas.h>
#include <InterViews/hit.h>
#include <OS/math.h>

#include <iostream.h>

//#define VERBOSE
//#include <hyperg/hyperg/verbose.h>
#define DEBUGNL(x)  /**/

implementList(GlyphInfoList, GlyphInfo)
implementList(ComponentInfoList, ComponentInfo)

SmoothTBScrollBox::SmoothTBScrollBox(GlyphIndex size)
     : ScrollBox(size),
       first_shown_(0),
       last_shown_(0),
       cur_upper_(0.0),
       cur_lower_(0.0),
       length_(0.0),
       changed_(true),
       requested_(false),
       canvas_(nil),
       component_infos_(size)
{
}

SmoothTBScrollBox::~SmoothTBScrollBox()
{
}

void SmoothTBScrollBox::request(Requisition& req) const
{
     DEBUGNL("SSB::request");
     SmoothTBScrollBox* This = (SmoothTBScrollBox*) this;
     GlyphIndex n = count();
     if (changed_) {
	  Requisition r;
	  const Requirement& rx = r.x_requirement();
	  const Requirement& ry = r.y_requirement();

	  Coord natural_width = 0.0;
	  Coord natural_height = 0.0;

	  ComponentInfo ci;

	  This->component_infos_.remove_all();
	  for (GlyphIndex i = 0; i < n; i++) {
	       DEBUGNL("\ti:" << i);
	       Glyph* g = component(i);
	       if (g) {
		    g->request(r);
		    Coord r_width = rx.natural();
		    if (r_width > natural_width) {
			 natural_width = r_width;
		    }
		    natural_height += ry.natural();
		    ci.y_req_ = ry;
		    ci.offs_ = natural_height;
		    This->component_infos_.append(ci);
	       }
	       else {
		    cerr << "NIIIILLLLLL" << endl;
	       }
	  }

          if (!requested_) {
               This->requested_ = true;

               Requirement& box_x = This->requisition_.x_requirement();
               box_x.natural(natural_width);
               box_x.stretch(fil);
               box_x.shrink(natural_width);
               box_x.alignment(0.0);
	  
               Requirement& box_y = This->requisition_.y_requirement();
               box_y.natural(natural_height);
               box_y.stretch(fil);
               box_y.shrink(natural_height);
               box_y.alignment(1.0);
          }
          This->changed_ = false;
	  This->length_ = natural_height;
     }
     req = requisition_;
     DEBUGNL("SSB::request ENDE");
}

void SmoothTBScrollBox::allocate(Canvas* c, const Allocation& a, Extension& ext) {
     DEBUGNL("SSB::allocate:");
     if (changed_) {
	  Requisition req;
	  request(req);
     }
     ext.set(c, a);
     canvas_ = c;
     if (c != nil) {
	  transformer_ = c->transformer();
     }
     extension_ = ext;
     if (changed_ || !allocation_.equals(a, 1e-4) || canvas_->damaged(extension_)) {
	  allocation_ = a;
	  do_scroll(Dimension_Y, cur_lower_, cur_lower_ + a.y_allotment().span());
	  notify(Dimension_X);
	  notify(Dimension_Y);
     }
     else {
	  allocation_ = a;
     }
}

void SmoothTBScrollBox::draw(Canvas* c, const Allocation& a) const {
     // SmoothTBScrollBox* This = (SmoothTBScrollBox*)this;  // never used
     check(c, a);
     if (canvas_->damaged(extension_)) {
	  c->push_clipping();
	  c->clip_rect(a.left(), a.bottom(), a.right(), a.top());
	  for (ListItr(GlyphInfoList) i(shown_list_); i.more(); i.next()) {
	       const GlyphInfo& info = i.cur_ref();
	       Glyph* g = info.glyph_;
	       if (g) {
		    g->draw(c, info.allocation_);
	       }
	       else {
		    cerr << "SmoothTBScrollBox::draw. internal error: nil glyph" << endl;
	       }
	  }
	  c->pop_clipping();
     }
}

void SmoothTBScrollBox::pick(Canvas* c, const Allocation& a, int depth, Hit& h) {
     check(c, a);
     if (h.left() < a.right() && h.right() >= a.left() &&
	 h.bottom() < a.top() && h.top() >= a.bottom()
	  ) {
	  if (changed_) {
	       refresh();
	  }
	  GlyphIndex n = first_shown_;
	  for (ListItr(GlyphInfoList) i(shown_list_); i.more(); i.next()) {
	       const GlyphInfo& info = i.cur_ref();
	       Glyph* g = info.glyph_;
	       h.begin(depth, this, n);
	       g->pick(c, info.allocation_, depth + 1, h);
	       h.end();
	       ++n;
	  }
     }
}

void SmoothTBScrollBox::undraw() {
     canvas_ = nil;
     ScrollBox::undraw();
}

void SmoothTBScrollBox::modified(GlyphIndex) {
     changed_ = true;
}

boolean SmoothTBScrollBox::shown(GlyphIndex i) const {
     return i >= first_shown_ && i <= last_shown_;
}

GlyphIndex SmoothTBScrollBox::first_shown() const {
     return first_shown_;
}

GlyphIndex SmoothTBScrollBox::last_shown() const {
     return last_shown_;
}

void SmoothTBScrollBox::allotment(
     GlyphIndex i,
     DimensionName d,
     Allotment& a
     ) const 
{
     if (i >= first_shown_ && i <= last_shown_) {
	  a = shown_list_.item_ref(i - first_shown_).allocation_.allotment(d);
     }
}

Coord SmoothTBScrollBox::lower(DimensionName) const {
     return 0.0;
}

Coord SmoothTBScrollBox::upper(DimensionName) const {
     return length_;
}

Coord SmoothTBScrollBox::length(DimensionName) const {
     return length_;
}

Coord SmoothTBScrollBox::cur_lower(DimensionName) const {
     return length_ - cur_upper_;
}

Coord SmoothTBScrollBox::cur_upper(DimensionName) const {
     return length_ - cur_lower_;
}

Coord SmoothTBScrollBox::cur_length(DimensionName) const {
     return cur_upper_ - cur_lower_;
}

void SmoothTBScrollBox::scroll_forward(DimensionName) {
     DEBUGNL("s_f");
     scrollOnTop(first_shown_ - 1);
}

void SmoothTBScrollBox::scroll_backward(DimensionName) {
     DEBUGNL("s_b");
     scrollOnBottom(last_shown_ + 1);
}

void SmoothTBScrollBox::page_forward(DimensionName d) {
     scroll_by(d, -large_scroll(d));
}

void SmoothTBScrollBox::page_backward(DimensionName d) {
     scroll_by(d, large_scroll(d));
}

void SmoothTBScrollBox::scroll_to(DimensionName d, Coord lower) {
     Coord new_upper = length_ - lower;
     Coord new_lower = new_upper - allocation_.y_allotment().span();
     do_scroll(d, new_lower, new_upper);
}

void SmoothTBScrollBox::scrollOnTop(GlyphIndex i) {
     DEBUGNL("\ti:" << i);
     if (changed_) {
	  Requisition req;
	  request(req);
     }
     i = i < 0 ? 0 : i <  count() ? i : count();
     Coord new_lower = i <= 0 ? 0.0 : component_infos_.item_ref(i - 1).offs_ + 0.1 ;
     do_scroll(0, new_lower, new_lower + allocation_.y_allotment().span());
}

void SmoothTBScrollBox::scrollOnBottom(GlyphIndex i) {
     DEBUGNL("\ti:" << i);
     if (changed_) {
	  Requisition req;
	  request(req);
     }
     i = i < 0 ? 0 : i <  count() ? i : count() - 1;
     Coord new_upper = i < 0 ? 0.0 : component_infos_.item_ref(i).offs_ - 0.1 ;
     do_scroll(0, new_upper - allocation_.y_allotment().span(), new_upper);
}

void SmoothTBScrollBox::scroll_by(DimensionName d, Coord offset) {
     do_scroll(d, cur_lower_ + offset, cur_upper_ + offset);
}

void SmoothTBScrollBox::do_scroll(
     DimensionName, Coord new_lower, Coord new_upper
     )
{
     DEBUGNL("SSB::do_scroll");
     if (changed_) {
	  Requisition req;
	  request(req);
     }
     if (new_lower < 0) {
	  new_upper -= new_lower; // new_lower is negative -> adding
	  new_lower = 0.0;
	  if (new_upper > length_ ) {
	       new_upper = length_;
	  }
     }
     if (new_upper > length_) {
	  new_lower -= (new_upper - length_);
	  new_upper = length_;
	  if (new_lower < 0.0) {
	       new_lower = 0.0;
	  }
     }

     GlyphIndex new_start = getComponentRel(new_lower, first_shown_);
     GlyphIndex new_end = getComponentRel(new_upper, last_shown_);
     DEBUGNL("\tn_star:" << new_start << "\tn_end:" << new_end);

     cur_lower_ = new_lower;
     cur_upper_ = new_upper;
     
     if (new_start != first_shown_ || new_end != last_shown_ + 1) {
	  undraw_range(first_shown_, new_start - 1);
     }
     GlyphIndex old_end = last_shown_;

     first_shown_ = new_start;
     last_shown_ = new_end;
     
     reallocate();
     if (new_start != first_shown_ || new_end != last_shown_ + 1) {
	  undraw_range(last_shown_ + 1, old_end - 1);
     }
     redraw();
     notify(Dimension_Y);
}

void SmoothTBScrollBox::check(Canvas* c, const Allocation& a) const {
     SmoothTBScrollBox* This = (SmoothTBScrollBox*) this;
     if (canvas_ == nil || canvas_ != c || changed_ ||
	 transformer_ != c->transformer() || !allocation_.equals(a, 1e-4)
	  ) {
	  Extension ext;
	  This->allocate(c, a, ext);
     }
}

void SmoothTBScrollBox::refresh() const {
    SmoothTBScrollBox* This = (SmoothTBScrollBox*) this;
    Requisition req;
    request(req);
//    This->cur_lower_ = 0.0;
    This->scroll_to(Dimension_Y, length_);
    This->redraw();
}

void SmoothTBScrollBox::reallocate() {
    if (canvas_ == nil) {
	return;
    }

    DEBUGNL("SmoothTBScrollBox::reallocate(): f:" << first_shown_ << " l:" <<  last_shown_ << " c:" << count());

    GlyphIndex n = count();
    shown_list_.remove_all();

    Requisition req;
    GlyphInfo info;
    Extension e_i;
    Requirement& r = req.y_requirement();

    Coord off = 0.0;
    if (first_shown_ > 0) {
	 off = component_infos_.item_ref(first_shown_ - 1).offs_;
    }

    Coord base = allocation_.top() + (cur_lower_ - off);
    Coord bottom = allocation_.bottom();
    for (GlyphIndex i = first_shown_; i >= 0 && i <= last_shown_; i++) {
	 r = component_infos_.item_ref(i).y_req_;
	 Coord span = r.natural();
	 Glyph* g = component(i);
	 if (g) {
	      Coord alignment = r.alignment();
	      base -= span;
	      info.glyph_ = g;
	      Allotment& ax = info.allocation_.x_allotment();
	      ax = allocation_.x_allotment();
	      Allotment& ay = info.allocation_.y_allotment();
	      ay.span(span);
	      ay.origin(base + Coord(alignment * span));
	      ay.alignment(alignment);
	      shown_list_.append(info);
	      info.glyph_->allocate(canvas_, info.allocation_, e_i);
	 }
    }
    DEBUGNL("SSB::reallocate: END");
}

void SmoothTBScrollBox::redraw() {
    if (canvas_ != nil) {
	canvas_->damage(extension_);
    }
}

void SmoothTBScrollBox::undraw_range(GlyphIndex begin, GlyphIndex end) {
     DEBUGNL("SSB::undraw_range: b: " << begin << "  e: " << end);
     begin = begin < 0 ? 0 : begin;
     end = end >= count() ? count()-1 : end;
     for (GlyphIndex i = begin; i <= end; i++) {
	  Glyph* g = component(i);
	  if (g != nil) {
	       g->undraw();
	  }
     }
}

GlyphIndex SmoothTBScrollBox::getComponentRel(Coord y, GlyphIndex i)
{
     GlyphIndex num = count ();
     if (!num)  // bmarsch: handle empty box correct
       return -1; 

     if (i >= num)
       i = num - 1;
     else if (i < 0)
       i = 0;

     if (i >= 0 && y > component_infos_.item_ref(i).offs_) {
	  DEBUGNL(i << " higher "<< y << " " << component_infos_.item_ref(i).offs_);
	  while (i < num-1 && y > component_infos_.item_ref(i).offs_) {
	       DEBUGNL(i);
	       ++i;
	  }
     }
     else {
	  DEBUGNL(i <<" lower " << y << " " << (i>0? component_infos_.item_ref(i-1).offs_ : 0.0 ));
	  while (i > 0 && y < component_infos_.item_ref(i-1).offs_) {
	       DEBUGNL(i);
	       --i;
	  }
     }
     return i;
}
