//<copyright>
//
// Copyright (c) 1994-96
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
//
//</copyright>


//<file>
//
//
// Name :       mslider.C
//
// Purpose :    Implementation of classes MSlider, MXSlider and MYSlider
//
// Created :    13 May 94    Bernhard Marschall
//
// $Id: mslider.C,v 1.2 1996/10/16 10:26:43 bmarsch Exp $
//
// Description:
// It's just InterViews' slider.c with one feature added: dragging while the
// right mouse button is pressed marks a region
//
//</file>
//
// $Log: mslider.C,v $
// Revision 1.2  1996/10/16 10:26:43  bmarsch
// Bugfixes: removed uninitialized memory reads
//
// Revision 1.1  1996/03/12 16:54:59  bmarsch
// Initial revision
//


/*
 * Copyright (c) 1991 Stanford University
 * Copyright (c) 1991 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Stanford and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Stanford and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL STANFORD OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */

#include <IV-look/stepper.h>
#include <IV-look/telltale.h>
#include <InterViews/canvas.h>
#include <InterViews/display.h>
#include <InterViews/event.h>
#include <InterViews/geometry.h>
#include <InterViews/patch.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <hyperg/OS/math.h>

#include "madjust.h"
#include "mslider.h"

#define default_minimum_thumb_size 1.0


//<class>
//
// Name:       MSliderImpl
//
// Purpose:    Implementation-Class of MSlider
//
// Public Interface:
//
//
// Description:
// It's really just a InterViews' slider.h with some small changes
//
//</class>

class MSliderImpl {
public:
  Glyph* normal_thumb_;
  Glyph* visible_thumb_;
  Patch* thumb_patch_;
  Patch* old_thumb_;
  Coord min_thumb_size_;
  boolean dragging_ : 1;
  boolean aborted_ : 1;
  boolean showing_old_thumb_ : 1;
  Stepper* forward_;
  Stepper* backward_;
  Stepper* stepper_;
  Coord xoffset_;
  Coord yoffset_;

  DimensionName dimension_;
  boolean marking_;
  Canvas* canvas_;
  Coord left_, right_, bottom_, top_;
  Extension extension_;
  Coord markMin_;
  Coord markMax_;
  Coord thumbWidth_;

  int hit_thumb(const Event&);
};

// *********************************************************************

MSlider::MSlider(Style* style, MAdjustable* madj) : 
ActiveHandler(nil, style) {
  madj_ = madj;

  MSliderImpl* s = new MSliderImpl;
  impl_ = s;
  s->normal_thumb_ = nil;
  s->visible_thumb_ = nil;
  s->thumb_patch_ = nil;
  s->old_thumb_ = nil;
  s->min_thumb_size_ = default_minimum_thumb_size;
  s->dragging_ = false;
  s->aborted_ = false;
  s->showing_old_thumb_ = false;
  s->forward_ = nil;
  s->backward_ = nil;
  s->stepper_ = nil;
  s->xoffset_ = 0.0;
  s->yoffset_ = 0.0;

  s->marking_ = false;
  s->canvas_ = nil;
  s->left_ = 0.0;
  s->right_ = 0.0;
  s->bottom_ = 0.0;
  s->top_ = 0.0;
  s->markMin_ = madj->markLower();
  s->markMax_ = madj->markUpper();
  s->thumbWidth_ = 2.0; 
}

MSlider::~MSlider() {
  MSliderImpl* s = impl_;
  Resource::unref(s->normal_thumb_);
  Resource::unref(s->visible_thumb_);
  Resource::unref(s->old_thumb_);
  Resource::unref(s->thumb_patch_);
  Resource::unref(s->forward_);
  Resource::unref(s->backward_);
  delete s;
}

void MSlider::normal_thumb(Glyph* g) {
  MSliderImpl& s = *impl_;
  Resource::ref(g);
  Resource::unref(s.normal_thumb_);
  s.normal_thumb_ = g;
  Resource::unref(s.thumb_patch_);
  s.thumb_patch_ = new Patch(g);
  Resource::ref(s.thumb_patch_);
}

void MSlider::visible_thumb(Glyph* g) {
  MSliderImpl& s = *impl_;
  Resource::ref(g);
  Resource::unref(s.visible_thumb_);
  s.visible_thumb_ = g;
}

void MSlider::old_thumb(Glyph* g) {
  MSliderImpl& s = *impl_;
  Patch* patch = new Patch(g);
  Resource::ref(patch);
  Resource::unref(s.old_thumb_);
  s.old_thumb_ = patch;
}

Coord MSlider::minimum_thumb_size() const {
  return impl_->min_thumb_size_;
}

void MSlider::request(Requisition& req) const {
  Requirement default_size(22.0, fil, 22.0, 0.0);
  req.require(Dimension_X, default_size);
  req.require(Dimension_Y, default_size);
}

void MSlider::allocate(Canvas* c, const Allocation& a, Extension& ext) {
  ext.merge(c, a);
  ActiveHandler::allocate(c, a, ext);
  impl_->left_ = a.left();
  impl_->right_ = a.right();
  impl_->bottom_ = a.bottom();
  impl_->top_ = a.top();
  impl_->extension_ = ext;
}

void MSlider::draw(Canvas* c, const Allocation& a) const {
  MSliderImpl& s = *impl_;
  s.canvas_ = c;
  c->push_clipping();
  c->clip_rect(a.left(), a.bottom(), a.right(), a.top());
  if (s.showing_old_thumb_) {
    s.old_thumb_->draw(c, s.old_thumb_->allocation());
  }
  s.thumb_patch_->draw(c, s.thumb_patch_->allocation());
  c->pop_clipping();
}

void MSlider::undraw() {
  MSliderImpl& s = *impl_;
  if (s.thumb_patch_ != nil) {
    s.thumb_patch_->undraw();
  }
  if (s.old_thumb_ != nil) {
    s.old_thumb_->undraw();
  }
  ActiveHandler::undraw();
}

void MSlider::move(const Event& e) {
  MSliderImpl& s = *impl_;
  if (s.visible_thumb_ != nil) {
    Glyph* g = (s.hit_thumb(e) == 0) ? s.visible_thumb_ : s.normal_thumb_;
    Patch& thumb = *s.thumb_patch_;
    if (thumb.body() != g) {
      thumb.body(g);
      thumb.reallocate();
      thumb.redraw();
    }
  }
  ActiveHandler::move(e);
}

void MSlider::allocation_changed(Canvas*, const Allocation& a) {
  allocate_thumb(a);
}

void MSlider::update(Observable*) {
  if (canvas() != nil) {
    allocate_thumb(allocation());
  }
}

void MSlider::forward_stepper(Stepper* stepper) {
  MSliderImpl& s = *impl_;
  Resource::ref(stepper);
  Resource::unref(s.forward_);
  s.forward_ = stepper;
}

void MSlider::backward_stepper(Stepper* stepper) {
  MSliderImpl& s = *impl_;
  Resource::ref(stepper);
  Resource::unref(s.backward_);
  s.backward_ = stepper;
}

void MSlider::move_to(Coord, Coord) { }
void MSlider::allocate_thumb(const Allocation&) { }
void MSlider::apply_adjustment(SliderAdjustment) { }

void MSlider::do_adjustment(
  Adjustable* a, SliderAdjustment s, DimensionName d
) {
  if (a != nil) {
    (a->*s)(d);
  }
}

void MSlider::allot_thumb_major_axis(
  const Allocation& slider, DimensionName d, Adjustable* adj,
Coord min_thumb_size, float& scale, Allotment& new_a
) {
  const Allotment& a = slider.allotment(d);
  Coord length = adj->length(d);
  Coord cur_length = adj->cur_length(d);
  Coord slider_size = a.span();
  Coord thumb_size;
  Coord thumb_start;

  if (Math::equal(length, float(0.0), float(1e-3))) {
    thumb_size = 4.0;
    thumb_start = 0.0;
    scale = 1.0;
  } else {
    thumb_size = 4.0;
    if (thumb_size > slider_size) {
      thumb_size = slider_size;
      thumb_start = 0.0;
      scale = 1.0;
    } else {
      if (thumb_size < min_thumb_size) {
        thumb_size = min_thumb_size;
      }
      scale = (slider_size - thumb_size) / (length - cur_length);
      thumb_start = scale * (adj->cur_lower(d) - adj->lower(d));
    }
  }
  new_a.origin(a.begin() + thumb_start);
  new_a.span(thumb_size);
  new_a.alignment(0.0);
}

void MSlider::allot_thumb_minor_axis(const Allotment& a, Allotment& new_a) {
  new_a.origin(a.begin());
  new_a.span(a.span());
  new_a.alignment(0.0);
}

void MSlider::redraw_thumb() {
  impl_->thumb_patch_->redraw();
}

void MSlider::reallocate_thumb(const Allocation& a) {
  if (canvas()) {
    Patch& thumb = *impl_->thumb_patch_;
    Extension ext;
    ext.clear();
    thumb.allocate(canvas(), a, ext);
    thumb.redraw();
  }
}

void MSlider::redraw_slider() {
  Canvas* canvas = impl_->canvas_;
  if (canvas) {
    Extension& ext = impl_->extension_;
    // check for extension (0,0,0,0); it may crash!
    if (ext.left() != 0 || ext.right() != 0 || ext.bottom() != 0 || ext.top() != 0)
      canvas->damage(impl_->extension_);
  }
}

void MSlider::adj_to_coord(Coord al, Coord ar, Coord& w0, Coord& w1) const
{
  Coord alen = ar-al;
  Coord low = madj_->lower(0);
  Coord len = madj_->length(0);
  Coord ml = madj_->markLower();
  Coord mu;
  if (impl_->marking_) {
    mu = impl_->markMax_;
  }
  else {
    mu = madj_->markUpper();
  }

  w0 = al + (ml-low) * alen/len;
  w1 = al + (mu-low) * alen/len;
}

// *********************************************************************

/* class MSliderImpl */

int MSliderImpl::hit_thumb(const Event& event) {
  Coord x = event.pointer_x();
  Coord y = event.pointer_y();
  const Extension& e = thumb_patch_->extension();
  Coord l = e.left();
  Coord r = e.right();
  Coord b = e.bottom();
  Coord t = e.top();
      
  if (x >= l && x < r && y >= b && y < t) {
    return 0;
  }
  if (x < e.left() || y < e.bottom()) {
    return -1;
  }
  return 1;
}

// *********************************************************************
/* class MXSlider */

MXSlider::MXSlider(Style* s, MAdjustable* a) : MSlider(s, a) {
  impl_->dimension_ = Dimension_X;
  a->attach(Dimension_X, this);
  TelltaleState* t = new TelltaleState;
  forward_stepper(new ForwardPager(nil, s, t, a, Dimension_X));
  backward_stepper(new BackwardPager(nil, s, t, a, Dimension_X));
}

MXSlider::~MXSlider() {
  if (madj_ != nil) {
    madj_->detach(Dimension_X, this);
  }
}

void MXSlider::draw(Canvas* can, const Allocation& all) const {
  // mark active region
  Coord x0, x1;
  adj_to_coord(all.left(), all.right(), x0, x1);
  Coord y0 = all.bottom();
  Coord y1 = all.top();
  can->fill_rect(x0, y0, x1, y1, madj_->drawColor());

  MSlider::draw(can, all);
}

void MXSlider::move_to(Coord x, Coord) {
  MAdjustable* a = madj_;
  a->scroll_to(Dimension_X, a->lower(Dimension_X) + x / xscale_);
}

void MXSlider::allocate_thumb(const Allocation& a) {
  redraw_thumb();
  Allocation thumb_a;
  allot_thumb_major_axis(
    a, Dimension_X, madj_, minimum_thumb_size(),
  xscale_, thumb_a.x_allotment()
  );
  allot_thumb_minor_axis(a.y_allotment(), thumb_a.y_allotment());
  reallocate_thumb(thumb_a);
}

void MXSlider::disconnect(Observable*) {
  madj_ = nil;
}

void MXSlider::apply_adjustment(SliderAdjustment s) {
  do_adjustment(madj_, s, Dimension_X);
}

void MXSlider::press(const Event& e) {
  EventButton b = e.pointer_button();
  MSliderImpl& s = *impl_;
  Coord x = e.pointer_x();
  Coord y = e.pointer_y();
  const Allocation& slider = allocation();
  const Allocation& a = s.thumb_patch_->allocation();

  boolean shift = e.shift_is_down() || e.control_is_down();
  int rel = s.hit_thumb(e);

  if (b == Event::right) {
    // jump
    apply_adjustment(&Adjustable::begin_adjustment);
    s.dragging_ = true;
    s.xoffset_ = slider.left() + (a.right() - a.left()) / 2.0;
    s.yoffset_ = slider.bottom() + (a.top() - a.bottom()) / 2.0;
    Coord xp = x - s.xoffset_;
    Coord yp = y - s.yoffset_;
    move_to(xp, yp);
    move(e);

    // mark region
    s.marking_ = true;
    Coord xa = madj_->lower(Dimension_X) + xp / xscale_;
    if (shift) {  // extent existing mark if shift or control is pressed
      if (xa-madj_->markLower() < madj_->markUpper()-xa) {
        s.markMin_ = madj_->markUpper();
        s.markMax_ = xa;
      }
      else {
        s.markMin_ = madj_->markLower();
        s.markMax_ = xa;
      }
    }
    else {
      s.markMax_ = s.markMin_ = xa;
    }
    madj_->startMarking(s.markMin_);

    s.dragging_ = true;
    redraw_slider();
    apply_adjustment(&Adjustable::begin_adjustment);
    s.xoffset_ = slider.left() + (a.right() - a.left()) / 2.0;
    s.yoffset_ = slider.bottom() + (a.top() - a.bottom()) / 2.0;
    move_to(xp, yp);
    move(e);
  } else if (rel == 0) {
    /* drag */
    apply_adjustment(&Adjustable::begin_adjustment);
    s.xoffset_ = slider.left() + x - a.left();
    s.yoffset_ = slider.bottom() + y - a.bottom();
    s.dragging_ = true;
  } else if (b == Event::middle) {
    /* jump */
    apply_adjustment(&Adjustable::begin_adjustment);
    s.dragging_ = true;
    s.xoffset_ = slider.left() + (a.right() - a.left()) / 2.0;
    s.yoffset_ = slider.bottom() + (a.top() - a.bottom()) / 2.0;
    move_to(x - s.xoffset_, y - s.yoffset_);
    move(e);
  } else {
    /* step */
    s.stepper_ = (rel == 1) ? s.forward_ : s.backward_;
    if (s.stepper_ != nil) {
      s.stepper_->start_stepping();
    }
  }
}

void MXSlider::drag(const Event& e) {
  MSliderImpl& s = *impl_;
  if (!s.aborted_ && s.dragging_) {
    Coord x = e.pointer_x() - s.xoffset_;
    Coord y = e.pointer_y() - s.yoffset_;
    if (!s.showing_old_thumb_ && s.old_thumb_ != nil) {
      s.showing_old_thumb_ = true;
      Extension ext;
      ext.clear();
      s.old_thumb_->allocate(
        canvas(), s.thumb_patch_->allocation(), ext
      );
    }
    move_to(x, y);
    if (s.marking_) {
      s.markMax_ = madj_->lower(Dimension_X) + x / xscale_;
      redraw_slider();
    }
  }
}

void MXSlider::release(const Event& e) {
  MSliderImpl& s = *impl_;
  Coord x = e.pointer_x() - s.xoffset_;
  Coord y = e.pointer_y() - s.yoffset_;
  if (s.dragging_) {
    if (s.showing_old_thumb_) {
      s.showing_old_thumb_ = false;
      s.old_thumb_->redraw();
    }
    s.dragging_ = false;
    if (s.aborted_) {
      s.aborted_ = false;
      return;
    }
    move_to(x, y);
    redraw_thumb();
    move(e);
    apply_adjustment(&Adjustable::commit_adjustment);
  } else if (s.stepper_ != nil) {
    s.stepper_->stop_stepping();
    s.stepper_ = nil;
    move(e);
  }
  if (s.marking_) {
    s.markMax_ = madj_->lower(Dimension_X) + x / xscale_;
    madj_->stopMarking(s.markMax_);
    redraw_slider();
    s.marking_ = false;
  }
}

// *********************************************************************
/* class YSlider */

MYSlider::MYSlider(Style* s, MAdjustable* a) : MSlider(s, a) {
  impl_->dimension_ = Dimension_Y;
  a->attach(Dimension_Y, this);
  TelltaleState* t = new TelltaleState;
  forward_stepper(new ForwardPager(nil, s, t, a, Dimension_Y));
  backward_stepper(new BackwardPager(nil, s, t, a, Dimension_Y));
}

MYSlider::~MYSlider() {
  if (madj_ != nil) {
    madj_->detach(Dimension_Y, this);
  }
}

void MYSlider::draw(Canvas* can, const Allocation& all) const {
  Coord y0, y1;
  adj_to_coord(all.bottom(), all.top(), y0, y1);
  Coord x0 = all.left();
  Coord x1 = all.right();
  can->fill_rect(x0, y0, x1, y1, madj_->drawColor());

  MSlider::draw(can, all);
}

void MYSlider::move_to(Coord, Coord y) {
  MAdjustable* a = madj_;
  a->scroll_to(Dimension_Y, a->lower(Dimension_Y) + y / yscale_);
}

void MYSlider::allocate_thumb(const Allocation& a) {
  redraw_thumb();
  Allocation thumb_a;
  allot_thumb_major_axis(
    a, Dimension_Y, madj_, minimum_thumb_size(),
  yscale_, thumb_a.y_allotment()
  );
  allot_thumb_minor_axis(a.x_allotment(), thumb_a.x_allotment());
  reallocate_thumb(thumb_a);
}

void MYSlider::disconnect(Observable*) {
  madj_ = nil;
}

void MYSlider::apply_adjustment(SliderAdjustment s) {
  do_adjustment(madj_, s, Dimension_Y);
}

void MYSlider::press(const Event& e) {
  EventButton b = e.pointer_button();
  MSliderImpl& s = *impl_;
  Coord x = e.pointer_x();
  Coord y = e.pointer_y();
  const Allocation& slider = allocation();
  const Allocation& a = s.thumb_patch_->allocation();

  boolean shift = e.shift_is_down();
  int rel = s.hit_thumb(e);

  if (b == Event::right) {
    // jump
    apply_adjustment(&Adjustable::begin_adjustment);
    s.dragging_ = true;
    s.xoffset_ = slider.left() + (a.right() - a.left()) / 2.0;
    s.yoffset_ = slider.bottom() + (a.top() - a.bottom()) / 2.0;
    Coord xp = x - s.xoffset_;
    Coord yp = y - s.yoffset_;
    move_to(xp, yp);
    move(e);

    // mark region
    s.marking_ = true;
    Coord ya = madj_->lower(Dimension_Y) + yp / yscale_;
    if (shift) {  // extent existing mark
      if (ya-madj_->markLower() < madj_->markUpper()-ya) {
        s.markMin_ = madj_->markUpper();
        s.markMax_ = ya;
      }
      else {
        s.markMin_ = madj_->markLower();
        s.markMax_ = ya;
      }
    }
    else {
      s.markMax_ = s.markMin_ = ya;
    }
    madj_->startMarking(s.markMin_);

  } else if (rel == 0) {
    /* drag */
    apply_adjustment(&Adjustable::begin_adjustment);
    s.xoffset_ = slider.left() + x - a.left();
    s.yoffset_ = slider.bottom() + y - a.bottom();
    s.dragging_ = true;
  } else if (b == Event::middle) {
    /* jump */
    apply_adjustment(&Adjustable::begin_adjustment);
    s.dragging_ = true;
    s.xoffset_ = slider.left() + (a.right() - a.left()) / 2.0;
    s.yoffset_ = slider.bottom() + (a.top() - a.bottom()) / 2.0;
    move_to(x - s.xoffset_, y - s.yoffset_);
    move(e);
  } else {
    /* step */
    s.stepper_ = (rel == 1) ? s.forward_ : s.backward_;
    if (s.stepper_ != nil) {
      s.stepper_->start_stepping();
    }
  }
}

void MYSlider::drag(const Event& e) {
  MSliderImpl& s = *impl_;
  if (!s.aborted_ && s.dragging_) {
    Coord x = e.pointer_x() - s.xoffset_;
    Coord y = e.pointer_y() - s.yoffset_;
    if (s.marking_) {
      s.markMax_ = madj_->lower(Dimension_Y) + y / yscale_;
      redraw_slider();
    }
    else if (!s.showing_old_thumb_ && s.old_thumb_ != nil) {
      s.showing_old_thumb_ = true;
      Extension ext;
      ext.clear();
      s.old_thumb_->allocate(
        canvas(), s.thumb_patch_->allocation(), ext
      );
    }
    move_to(x, y);
  }
}

void MYSlider::release(const Event& e) {
  MSliderImpl& s = *impl_;
  Coord x = e.pointer_x() - s.xoffset_;
  Coord y = e.pointer_y() - s.yoffset_;
  if (s.dragging_) {
    if (s.showing_old_thumb_) {
      s.showing_old_thumb_ = false;
      s.old_thumb_->redraw();
    }
    s.dragging_ = false;
    if (s.aborted_) {
      s.aborted_ = false;
      return;
    }
    move_to(x, y);
    redraw_thumb();
    move(e);
    apply_adjustment(&Adjustable::commit_adjustment);
  } else if (s.stepper_ != nil) {
    s.stepper_->stop_stepping();
    s.stepper_ = nil;
    move(e);
  }
  if (s.marking_) {
    s.markMax_ = madj_->lower(Dimension_X) + y / yscale_;
    madj_->stopMarking(s.markMax_);
    redraw_slider();
    s.marking_ = false;
  }
}

