
/******************************************************************************
* MODULE     : scrollbar_widget.gen.cc
* DESCRIPTION: Horizontal and vertical scrollbars
* COPYRIGHT  : (C) 1999  Joris van der Hoeven
*******************************************************************************
* This software falls under the GNU general public license and comes WITHOUT
* ANY WARRANTY WHATSOEVER. See the file $TEXMACS_PATH/LICENSE for more details.
* If you don't have this file, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
******************************************************************************/

#include <Widget/attribute_widget.gen.h>

#module code_scrollbar_widget
#import attribute_widget

typedef long long int DI;

/******************************************************************************
* Abstract scrollbars
******************************************************************************/

class scrollbar_rep: public scroll_widget_rep {
protected:
  widget_rep* ref;
  SI          sc_min, sc_max, sc_pos, before, after;
  double      factor;
  bool        gripped;
  bool        scrolling;
  int         increment;

public:
  scrollbar_rep (widget ref);

  void handle_set_coord1 (set_coord1_event ev);
  void handle_set_coord2 (set_coord2_event ev);
};

/******************************************************************************
* Routines for abstract scrollbars
******************************************************************************/

scrollbar_rep::scrollbar_rep (widget ref2):
  scroll_widget_rep (ref2->dis, 0, south_west), ref (ref2.rep),
  factor (0.5), gripped (FALSE), scrolling (FALSE), increment (0) {}

void
scrollbar_rep::handle_set_coord1 (set_coord1_event ev) {
  if (ev->which == "scroll position") {
    sc_pos = ev->c1;
    sc_pos = min (sc_pos, sc_max);
    sc_pos = max (sc_pos, sc_min);
    abs_round (sc_pos);  
    this << emit_scroll (sc_pos, before, after);
    
    if (before+ after > sc_max- sc_min) {
      SI total= before+after;
      DI ext  = sc_max- sc_min; if (total==0) total=1;
      before  = (SI) ((before*ext)/total);
      after   = ((SI) ext)- before;
    }
    
    if (sc_pos- before < sc_min) before = sc_pos - sc_min;
    if (sc_pos+ after  > sc_max) after  = sc_max - sc_pos;
    
    if (attached ()) this << emit_invalidate_all ();
    return;
  }
  attribute_widget_rep::handle_set_coord1 (ev);
}

void
scrollbar_rep::handle_set_coord2 (set_coord2_event ev) {
  /*
  cout << "Scrollbar extents  " << (ev->c1/PIXEL) << ", "
       << (ev->c2/PIXEL) << "\n";
  cout << "Scrollbar position " << (sc_pos/PIXEL) << "\n";
  */
  if (ev->which == "extents") {
    sc_min= ev->c1;
    sc_max= ev->c2;
    this << emit_bar_scroll_to (sc_pos);
    return;
  }
  attribute_widget_rep::handle_set_coord2 (ev);
}

/******************************************************************************
* Horizontal scrollbars
******************************************************************************/

class hor_scrollbar_widget_rep: public scrollbar_rep {
  void decode_position (SI& x1, SI& x2);
  SI   encode_position (SI x);

public:
  hor_scrollbar_widget_rep (widget ref);
  operator tree ();

  void handle_get_size (get_size_event ev);
  void handle_repaint (repaint_event ev);
  void handle_mouse (mouse_event ev);
  void handle_scroll (scroll_event ev);
  void handle_alarm (alarm_event ev);
};

/******************************************************************************
* Routines for horizontal scrollbars
******************************************************************************/

hor_scrollbar_widget_rep::hor_scrollbar_widget_rep (widget ref):
  scrollbar_rep (ref) {}

hor_scrollbar_widget_rep::operator tree () {
  return "horizontal scrollbar";
}

void
hor_scrollbar_widget_rep::handle_get_size (get_size_event ev) {
  if (ev->mode== 0) ev->h= 16*PIXEL;
  if (ev->mode==-1) {
    ev->w= 48*PIXEL;
    ev->h= 16*PIXEL;
  }
  if (ev->mode== 1) {
    dis->get_extents (ev->w, ev->h);
    ev->h= 16*PIXEL;
  }
}

void
hor_scrollbar_widget_rep::decode_position (SI& x1, SI& x2) {
  SI total = sc_max- sc_min; if (total==0) total=1;
  SI extra = (((h/PIXEL)*3)/4)*PIXEL + 3*PIXEL;
  DI real_w= w- 2*extra;
  SI bef   = (SI) ((before*real_w)/total);
  SI aft   = (SI) ((after*real_w)/total);
  SI p;

  if (bef+aft==0) aft=1;
  while (bef+aft< 4*PIXEL) {
    bef= aft= 2*PIXEL;
    p = (SI) (((sc_pos- sc_min)*real_w)/total);
    if (p<2*PIXEL) { bef=p; aft= 4*PIXEL-bef; }
    if (p>(real_w- 2*PIXEL)) { aft=real_w-p; bef= 4*PIXEL-aft; }
  }
  
  p = (SI) (((sc_pos- sc_min)*real_w)/total);
  x1= max (0, p-bef)+ extra;
  x2= min ((SI) real_w, p+aft)+ extra;
}

SI
hor_scrollbar_widget_rep::encode_position (SI x) {
  DI total  = sc_max- sc_min; if (total==0) total=1;
  SI extra  = (((h/PIXEL)*3)/4)*PIXEL + 3*PIXEL;
  SI real_w = w- 2*extra;
  SI dec_x  = (SI) (((x- extra)*total)/real_w);
  return dec_x+ sc_min;
}

void
hor_scrollbar_widget_rep::handle_repaint (repaint_event ev) { (void) ev;
  SI X1, X2;
  decode_position (X1, X2);
  // ps_device dev= win->window_to_shadow (ev->x1, ev->y1, ev->x2, ev->y2);
  layout_dark (win, 0, 0, w, h);
  layout_lower (win, 0, 0, w, h); 
  layout_default (win, X1, PIXEL, X2, h-PIXEL);
  layout_higher  (win, X1, PIXEL, X2, h-PIXEL);

  SI aw= (((h/PIXEL)*3)/4)*PIXEL;
  SI ah= h-4*PIXEL;
  layout_left_arrow (win, 2*PIXEL, 2*PIXEL, aw, ah);
  layout_right_arrow (win, w- 2*PIXEL- aw, 2*PIXEL, aw, ah);
  // win->shadow_to_window (ev->x1, ev->y1, ev->x2, ev->y2);
}

void
hor_scrollbar_widget_rep::handle_mouse (mouse_event ev) {
  string type= ev->type;
  SI     X   = ev->x;
  SI     aw  = (((h/PIXEL)*3)/4)*PIXEL + 3*PIXEL;
  SI     X1, X2;
  decode_position (X1, X2);

  if (type == "press-left") {
    if (X < aw) {
      scrolling= TRUE;
      increment= -5*PIXEL;
      this << emit_bar_scroll_to (sc_pos + increment);
      this << emit_mouse_grab (TRUE);
      dis->delayed_message (this, "scroll", 100);
    }
    else if (X >= (w-aw)) {
      scrolling= TRUE;
      increment= 5*PIXEL;
      this << emit_bar_scroll_to (sc_pos + increment);
      this << emit_mouse_grab (TRUE);
      dis->delayed_message (this, "scroll", 100);
    }
    else if (X<X1) this << emit_bar_scroll_to (sc_pos- 25*PIXEL);
    else if (X>X2) this << emit_bar_scroll_to (sc_pos+ 25*PIXEL);
    else {
      gripped= TRUE;
      this << emit_mouse_grab (TRUE);
      factor= ((double) (X-X1))/((double) (X2-X1));
    }
  }

  if (type == "press-middle") {
    SI x= encode_position (X);
    this << emit_bar_scroll_to (x- ((after-before)>>1));
    this << emit_mouse_grab (TRUE);
    factor= 0.5;
  }

  if (type == "press-right") {
    SI ww; ref << get_width (ww);
    if (X<X1) this << emit_bar_scroll_to (sc_pos- ww);
    if (X>X2) this << emit_bar_scroll_to (sc_pos+ ww);
  }

  if ((type == "move") &&
      ((gripped && ev->pressed ("left")) || ev->pressed ("middle"))) {
    if (win->check_event (DRAG_EVENT)) return;
    SI x = encode_position (X);
    SI dx= (SI) ((after+before)*factor);
    this << emit_bar_scroll_to (x+ before- dx);
  }

  if (((type == "release-left") || (type == "release-middle")) &&
      (!ev->pressed ("left")) && (!ev->pressed ("middle"))) {
    gripped= scrolling= FALSE;
    this << emit_mouse_grab (FALSE);
  }
}

void
hor_scrollbar_widget_rep::handle_alarm (alarm_event ev) {
  if (scrolling && (ev->message == "scroll")) {
    this << emit_bar_scroll_to (sc_pos + increment);
    dis->delayed_message (this, "scroll", 10);
  }
}

void
hor_scrollbar_widget_rep::handle_scroll (scroll_event ev) {
  if (ev->which != "this")
    fatal_error ("Invalid scroll", "hor_scrollbar_widget_rep::handle_scroll");
  ref << emit_hor_scroll (ev->c1, ev->c2, ev->c3);
}

/******************************************************************************
* Vertical scrollbars
******************************************************************************/

class ver_scrollbar_widget_rep: public scrollbar_rep {
  void decode_position (SI& y1, SI& y2);
  SI   encode_position (SI y);

public:
  ver_scrollbar_widget_rep (widget ref);
  operator tree ();

  void handle_get_size (get_size_event ev);
  void handle_repaint (repaint_event ev);
  void handle_mouse (mouse_event ev);
  void handle_scroll (scroll_event ev);
  void handle_alarm (alarm_event ev);
};

/******************************************************************************
* Routines for vertical scrollbars
******************************************************************************/

ver_scrollbar_widget_rep::ver_scrollbar_widget_rep (widget ref):
  scrollbar_rep (ref) {}

ver_scrollbar_widget_rep::operator tree () {
  return "vertical scrollbar";
}

void
ver_scrollbar_widget_rep::handle_get_size (get_size_event ev) {
  if (ev->mode== 0) ev->w= 16*PIXEL;
  if (ev->mode==-1) {
    ev->w= 16*PIXEL;
    ev->h= 8*PIXEL;
  }
  if (ev->mode== 1) {
    dis->get_extents (ev->w, ev->h);
    ev->w= 16*PIXEL;
  }
}

void
ver_scrollbar_widget_rep::decode_position (SI& y1, SI& y2) {
  SI total = sc_max- sc_min; if (total==0) total=1;
  SI extra = (((w/PIXEL)*3)/4)*PIXEL + 3*PIXEL;
  DI real_h= h- 2*extra;
  SI bef   = (SI) ((before*real_h)/total);
  SI aft   = (SI) ((after*real_h)/total);
  SI p;

  if (bef+aft==0) aft=1;
  while (bef+aft< 4*PIXEL) {
    bef= aft= 2*PIXEL;
    p = (SI) (((sc_pos- sc_min)*real_h)/total);
    if (p<2*PIXEL) { bef=p; aft= 4*PIXEL-bef; }
    if (p>(real_h- 2*PIXEL)) { aft=real_h-p; bef= 4*PIXEL-aft; }
  }
  
  p = (SI) (((sc_pos- sc_min)*real_h)/total);
  y1= max (0, p-bef)+ extra;
  y2= min ((SI) real_h, p+aft)+ extra;
}

SI
ver_scrollbar_widget_rep::encode_position (SI y) {
  DI total  = sc_max- sc_min; if (total==0) total=1;
  SI extra = (((w/PIXEL)*3)/4)*PIXEL + 3*PIXEL;
  SI real_h = h- 2*extra;
  SI dec_y  = (SI) (((y- extra)*total)/real_h);
  return dec_y+ sc_min;
}

void
ver_scrollbar_widget_rep::handle_repaint (repaint_event ev) { (void) ev;
  SI Y1, Y2;
  decode_position (Y1, Y2);
  // ps_device dev= win->window_to_shadow (ev->x1, ev->y1, ev->x2, ev->y2);
  layout_dark (win, 0, 0, w, h);
  layout_lower (win, 0, 0, w, h); 
  layout_default (win, PIXEL, Y1, w- PIXEL, Y2);
  layout_higher (win, PIXEL, Y1, w- PIXEL, Y2);

  SI aw= w-4*PIXEL;
  SI ah= (((w/PIXEL)*3)/4)*PIXEL;
  layout_up_arrow (win, 2*PIXEL, h- 2*PIXEL- ah, aw, ah);
  layout_down_arrow (win, 2*PIXEL, 2*PIXEL, aw, ah);
  // win->shadow_to_window (ev->x1, ev->y1, ev->x2, ev->y2);
}

void
ver_scrollbar_widget_rep::handle_mouse (mouse_event ev) {
  string type= ev->type;
  SI     Y   = ev->y;
  SI     ah  = (((w/PIXEL)*3)/4)*PIXEL + 3*PIXEL;
  SI     Y1, Y2;
  decode_position (Y1, Y2);

  if (type == "press-left") {
    if (Y < ah) {
      scrolling= TRUE;
      increment= -5*PIXEL;
      this << emit_bar_scroll_to (sc_pos + increment);
      this << emit_mouse_grab (TRUE);
      dis->delayed_message (this, "scroll", 100);
    }
    else if (Y >= (h-ah)) {
      scrolling= TRUE;
      increment= 5*PIXEL;
      this << emit_bar_scroll_to (sc_pos + increment);
      this << emit_mouse_grab (TRUE);
      dis->delayed_message (this, "scroll", 100);
    }
    else if (Y < Y1) this << emit_bar_scroll_to (sc_pos- 25*PIXEL);
    else if (Y > Y2) this << emit_bar_scroll_to (sc_pos+ 25*PIXEL);
    else {
      gripped= TRUE;
      this << emit_mouse_grab (TRUE);
      factor= ((double) (Y-Y1))/((double) (Y2-Y1));
    }
  }

  if (type == "press-middle") {
    SI y= encode_position (Y);
    this << emit_bar_scroll_to (y- ((after-before)>>1));
    this << emit_mouse_grab (TRUE);
    factor= 0.5;
  }

  if (type == "press-right") {
    SI hh; ref << get_height (hh);
    if (Y<Y1) this << emit_bar_scroll_to (sc_pos- hh);
    if (Y>Y2) this << emit_bar_scroll_to (sc_pos+ hh);
  }

  if ((type == "move") &&
      ((gripped && ev->pressed ("left")) || ev->pressed ("middle"))) {
    if (win->check_event (DRAG_EVENT)) return;
    SI y = encode_position (Y);
    SI dy= (SI) ((after+before)*factor);
    this << emit_bar_scroll_to (y+ before- dy);
  }

  if (((type == "release-left") || (type == "release-middle")) &&
      (!ev->pressed ("left")) && (!ev->pressed ("middle"))) {
    gripped= scrolling= FALSE;
    this << emit_mouse_grab (FALSE);
  }
}

void
ver_scrollbar_widget_rep::handle_alarm (alarm_event ev) {
  if (scrolling && (ev->message == "scroll")) {
    this << emit_bar_scroll_to (sc_pos + increment);
    dis->delayed_message (this, "scroll", 10);
  }
}

void
ver_scrollbar_widget_rep::handle_scroll (scroll_event ev) {
  if (ev->which != "this")
    fatal_error ("Invalid scroll", "ver_scrollbar_widget_rep::handle_scroll");
  ref << emit_ver_scroll (ev->c1, ev->c2, ev->c3);
}

#endmodule // code_scrollbar_widget
