//<copyright>
// 
// Copyright (c) 1994
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// original version (label parts):
// Copyright (c) 1987, 1988, 1989, 1990, 1991 Stanford University
// Copyright (c) 1991 Silicon Graphics, Inc.
// 
//</copyright>
/*
 * 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.
 */

/*
 * Label - ASCII text glyph with font
 */


//<file>
//
// File:        editlabel.C - implementation of class EditLabel
//
// Created:     10 Mar 94   Michael Pichler
//
// Changed:     12 Oct 94   Michael Pichler
//
//
//</file>


#include "editlabel.h"

#include <InterViews/brush.h>
#include <InterViews/canvas.h>
#include <InterViews/color.h>
#include <InterViews/font.h>
#include <InterViews/hit.h>
#include <OS/string.h>

#include <iostream.h>



/*** Label ***/

EditLabel::EditLabel (
  const char* s, const Font* f,
  const Color* color, const Color* csrcol,
  const Color* selcol, const Color* invcol, const Color* chgcol,
  int hide, char hidechar
)
: text_ (s)
{
  // font
  font_ = f;
  Resource::ref (font_);
  // colors
  color_ = color;
  csrcolor_ = csrcol;
  selcolor_ = selcol;
  invcolor_ = invcol;
  chgcolor_ = chgcol;
  Resource::ref (color);
  Resource::ref (csrcol);
  Resource::ref (selcol);
  Resource::ref (invcol);
  Resource::ref (chgcol);
  // cursor brush
  brush_ = new Brush (2.0);  // Cursor brush, will become Xattribute!
  Resource::ref (brush_);

  hide_ = hide;
  hidechar_ = hidechar;
  modified_ = 0;
  chrwidthsum_ = 0;
  horoffset_ = 0;  // <= 0
  hmargin_ = 5.0;  // will become Xattribute!
  insertmode_ = 1;
  showcursor_ = 0;  // must be given focus explicitly
  cursorposition_ = 0;
  selecting_ = 0;
  fillselection_ = 0;
  markposition_ = 0;
  compute_metrics();
  allwidth_ = width_;  // excluding margin
  naturalwidth_ = width_ + 2 * hmargin_;  // including margin
}

EditLabel::~EditLabel ()
{
  Resource::unref (font_);
  Resource::unref (color_);
  Resource::unref (csrcolor_);
  Resource::unref (selcolor_);
  Resource::unref (invcolor_);
  Resource::unref (chgcolor_);
  Resource::unref (brush_);
  delete chrwidthsum_;
}

void EditLabel::compute_metrics ()
{
  const Font* f = font_;
  const char* str = text_.string();
  int len = text_.length();
  FontBoundingBox b;
  f->string_bbox(str, len, b);
  ascent_ = b.font_ascent();
  descent_ = b.font_descent();
  delete chrwidthsum_;
  // chrwidthsum_ [0..len]; chrwidthsum_ [cursorposition_]
  chrwidthsum_ = new Coord [len+1];

  if (hide_)  // hide string
    if (hidechar_)  // substitute input chars
    {
      width_ = f->width ((u_char) hidechar_);
      for (int i = 0; i <= len; i++)
        chrwidthsum_ [i] = i * width_;
      width_ *= len;
    }
    else  // invisible input
    { width_ = 0;
      for (int i = 0; i <= len; i++)
        chrwidthsum_ [i] = 0;
    }
  else
  {
    width_ = b.width ();

    chrwidthsum_ [0] = 0;
    for (int i = 0; i < len; i++)
      chrwidthsum_ [i+1] = chrwidthsum_ [i] + f->width (((u_char*)str)[i]);
  }
} // compute_metrics


void EditLabel::request (Requisition& requisition) const
{
  Coord height = ascent_ + descent_;
  float alignment = (height == 0) ? 0 : descent_ / height;
//cerr << "EditLabel::request " << endl;

  // cannot use current text width as natural size -
  // problems when field editor is part of a box on resizing an outer box
  // Requirement rx (width_ + 2 * hmargin_, fil, width_, 0);
  Requirement rx (naturalwidth_, fil, naturalwidth_ - 2 * hmargin_, 0);  // be flexible

  Requirement ry (height, 0, 0, alignment);
  requisition.require (Dimension_X, rx);
  requisition.require (Dimension_Y, ry);
}


void EditLabel::do_allocate (Canvas* c, const Allocation& a, Extension& ext)
{
  // use allocation got, be sure that extension comprises whole drawing area (for redraw)
  ext.set_xy (c, a.left (), a.bottom () - 0.75, a.right () + 0.5, a.top ());

  allwidth_ = a.right () - a.left () - 2 * hmargin_;  // allocation width excl. margin
  if (allwidth_ < 0)
    allwidth_ = 0;
}


void EditLabel::allocate (Canvas* c, const Allocation& a, Extension& ext)
{
//cerr << "EL::allocate ";
  do_allocate (c, a, ext);  // this must be done

  // really, both functions below are necessary for 'natural' behaviour
  // if derived classes find this not desireable they should only call do_allocate
  scrollToCursor ();  // ensure cursor remains in window
  scroll (0);         // adjust horoffset, such that string is not too much left/right
}


void EditLabel::draw (Canvas* c, const Allocation& a) const
{
  // it is assumed that the EditLabel is the body of a patch, which draws
  // its body (this class) only when the extension and canvas damage intersect

  Coord x = a.x () + horoffset_ + hmargin_;
  Coord y = a.y ();
  const Font* f = font_;
  const Color* color = color_;
  const char* p = text_.string ();
  int len = text_.length ();
  Coord* cws = chrwidthsum_;
  Coord left = a.left ();
  Coord bottom = a.bottom () - 0.75;  // empirical constants that should
  Coord right = a.right () + 0.5;     // eliminate rounding errors
  Coord top = a.top ();

//cerr << "drawing; cursor: " << cursorposition_ << ", mark: " << markposition_ << ", selecting: " << selecting_ << endl;

  if (modified_ && chgcolor_)
    c->fill_rect (left, bottom, right, top, chgcolor_);

  if (hide_ && !hidechar_ && showcursor_)  // hide input
  { drawCursor (c, x, y, x+5.0);
    return;
  }

  c->push_clipping ();  // clip at allocation
  c->clip_rect (left, bottom, right, top);

  if (selecting_ && (cursorposition_ != markposition_ || fillselection_))
    drawSelection (c,
      x + chrwidthsum_ [cursorposition_], bottom,
      (fillselection_ && markposition_ == len) ? right : x + chrwidthsum_ [markposition_],
      top
    );

  int hiliton, hilitoff;
  if (selecting_)
  { if (cursorposition_ < markposition_)
    { hiliton = cursorposition_;
      hilitoff = markposition_;
    }
    else
    { hiliton = markposition_;
      hilitoff = cursorposition_;
    }
  }
  else  // no selection
    hiliton = hilitoff = -1;

  for (int i = 0;  i < len;  i++, p++, cws++)
  {
    if (i == hiliton)
      color = invcolor_;
    if (i == hilitoff)
      color = color_;

    Coord width = cws [1] - *cws;
    if (i == cursorposition_ && showcursor_ && !selecting_)
      drawCursor (c, x, y, x+width);

    if (x+width >= left && x <= right)  // potentially visible
      c->character (f, hide_ ? hidechar_ : *p, width, color, x, y);
    // ought to write a Canvas::string routine
    x += width;
  } // for all char's

//cerr << "finished Editlabel::draw at x = " << x << endl;

  if (cursorposition_ == len && showcursor_ && !selecting_)  // to insert after last char
    drawCursor (c, x, y, x+5.0);

  c->pop_clipping ();

} // draw


void EditLabel::drawCursor (Canvas* c, float left, float base, float right) const
{
  if (insertmode_)
    c->line (left, base-descent_, left, base+ascent_, csrcolor_, brush_);
  else
  { base -= descent_/2;
    c->line (left, base, right, base, csrcolor_, brush_);
  }
}


void EditLabel::drawSelection (Canvas* c, float left, float bottom, float right, float top) const
{
  if (right < left)
  { float temp = right;
    right = left;
    left = temp;
  }
  // left <= right

  c->fill_rect (left, bottom, right, top, selcolor_); 
}


void EditLabel::pick (Canvas*, const Allocation& a, int depth, Hit& h)
{
  Coord x = h.left();
  if (h.right() >= a.left() && x < a.right() &&
      h.top() >= a.bottom() && h.bottom() < a.top())
  {
    int index;
    int len = text_.length ();

    if (hide_)
      if (hidechar_ && len)
      { float hcw = chrwidthsum_ [1];   // hide char width
        x -= a.x () + horoffset_ + hmargin_ - hcw/2;
        index = (hcw > 0 && x > 0) ? (int) (x / hcw) : 0;
        if (index > len)
          index = len;        
      }
      else
        index = 0;
    else
      index = font_->index (
        text_.string(), text_.length(), x - a.x() - horoffset_ - hmargin_, true
      );

    h.target(depth, this, index);
  }
}


int EditLabel::hitIndex (float x)
{
  // x ... hit.left () - a.x ()
  int index;
  int len = text_.length ();

  if (hide_)
    if (hidechar_ && len)
    { float hcw = chrwidthsum_ [1];  // hide char width
      x -= horoffset_ + hmargin_ - hcw/2;
      index = (hcw > 0 && x > 0) ? (int) (x / hcw) : 0;
      if (index > len)
        index = len;        
    }
    else
      index = 0;
  else
    index = font_->index (
      text_.string(), text_.length(), x - horoffset_ - hmargin_, true
    );

  return index;
}



/*** Editing ***/


void EditLabel::setString (const char* s)
{
  text_.setString (s);
  cursorposition_ = 0;
  selecting_ = 0;
  compute_metrics ();
  horoffset_ = 0;
}


void EditLabel::cursorPosition (int pos, int sel)
{
  if (!sel)
    cursorposition_ = pos;
  markposition_ = pos;
  selecting_ = sel;
}


int EditLabel::inSelection (int pos) const
{
  if (cursorposition_ < markposition_)
    return (cursorposition_ <= pos && pos <= markposition_);
  else if (markposition_ < cursorposition_)
    return (markposition_ <= pos && pos <= cursorposition_);
  else
    return 0;
}


// scroll by fixed amount
// positive x means left to right

void EditLabel::scroll (float x)
{
  horoffset_ += x;
  // keep in boundaries
  float offs = allwidth_ - chrwidthsum_ [text_.length ()];  // < 0 when line too long
  if (horoffset_ < offs)  // too much left
    horoffset_ = offs;
  if (horoffset_ > 0)  // too much right (also offs may have been > 0)
    horoffset_ = 0;
}


// scroll the label such that the cursor position is within window

void EditLabel::scrollToCursor ()
{
  float cws_cp = chrwidthsum_ [cursorposition_];

  if (horoffset_ + cws_cp < 0)
    horoffset_ = -cws_cp;
  else if (horoffset_ + cws_cp > allwidth_)
    horoffset_ = allwidth_ - cws_cp;
}


/*
// scroll the label such that the characters left and right of the new
// mark position is not at margin (this allows mouse selection)
// will become an anachronism when mouse can be dragged out of window!

void EditLabel::scrollToMark ()
{
  int mp = markposition_;

  if (mp && horoffset_ + chrwidthsum_ [mp-1] < 0)
    horoffset_ = -chrwidthsum_ [mp-1];
  else if (mp < text_.length () && horoffset_ + chrwidthsum_ [mp+1] > allwidth_)
    horoffset_ = allwidth_ - chrwidthsum_ [mp+1];
}
*/


// scroll the label such that the mark position is within winow

void EditLabel::scrollToMark ()
{
  float cws_mp = chrwidthsum_ [markposition_];

  if (horoffset_ + cws_mp < 0)
    horoffset_ = -cws_mp;
  else if (horoffset_ + cws_mp > allwidth_)
    horoffset_ = allwidth_ - cws_mp;
}


static int iabs (int i)  { return (i < 0) ? -i : i; }

void EditLabel::attractMark (int pos)
{
  if (iabs (pos - cursorposition_) < iabs (pos - markposition_))
  {
    int temp = cursorposition_;
    cursorposition_ = markposition_;
    markposition_ = temp;
  }
}


// cursorLeft
// move mark and cursor (only when not selecting) a position left

void EditLabel::cursorLeft (int sel)
{
  int& pos = sel ? markposition_ : cursorposition_;
  if (pos)
    if (horoffset_ + chrwidthsum_ [--pos] < 0)
      horoffset_ = -chrwidthsum_ [pos];
  selecting_ = sel;
  if (!sel)
    markposition_ = cursorposition_;
}


void EditLabel::cursorRight (int sel)
{
  int& pos = sel ? markposition_ : cursorposition_;
  if (pos < text_.length ())
    if (horoffset_ + chrwidthsum_ [++pos] > allwidth_)
      horoffset_ = allwidth_ - chrwidthsum_ [pos];
  selecting_ = sel;
  if (!sel)
    markposition_ = cursorposition_;
}


void EditLabel::cursorWordLeft (int sel)
{
  int& pos = sel ? markposition_ : cursorposition_;
  pos = text_.wordLeft (pos);
  if (horoffset_ + chrwidthsum_ [pos] < 0)
    horoffset_ = -chrwidthsum_ [pos];
  selecting_ = sel;
  if (!sel)
    markposition_ = cursorposition_;
}


void EditLabel::cursorWordRight (int sel)
{
  int& pos = sel ? markposition_ : cursorposition_;
  pos = text_.wordRight (pos);
  if (horoffset_ + chrwidthsum_ [pos] > allwidth_)
    horoffset_ = allwidth_ - chrwidthsum_ [pos];
  selecting_ = sel;
  if (!sel)
    markposition_ = cursorposition_;
}


void EditLabel::cursorHome (int sel)
{
  cursorPosition(0, sel);
  horoffset_ = 0;
}


void EditLabel::cursorEnd (int sel)
{
  cursorPosition (text_.length (), sel);
  int& pos = sel ? markposition_ : cursorposition_;
  if (horoffset_ + chrwidthsum_ [pos] > allwidth_)
    horoffset_ = allwidth_ - chrwidthsum_ [pos];
//if (!sel)
//  markposition_ = cursorposition_;
//selecting_ = sel;
}


void EditLabel::selectAll ()
{
  cursorposition_ = 0;
  markposition_ = text_.length ();
  selecting_ = 1;
}


void EditLabel::selectWord (int pos)
{
  text_.findWord (pos, cursorposition_, markposition_);
  selecting_ = 1;
}


void EditLabel::insertChar (char c)
{
  if (insertmode_ || cursorposition_ == text_.length () || selecting_)
  {
    if (selecting_)  // overwrite selection
      deleteSelection ();
    text_.insertChar (c, cursorposition_++);
  }
  else
    text_.overwrite (c, cursorposition_++);
  compute_metrics ();
  if (horoffset_ + chrwidthsum_ [cursorposition_] > allwidth_)
    horoffset_ = allwidth_ - chrwidthsum_ [cursorposition_];
  selecting_ = 0;
  markposition_ = cursorposition_;
}


void EditLabel::insertString (const char* str, int len)
{
  text_.insertSubstring (str, len, cursorposition_);
  cursorposition_ += len;
  compute_metrics ();
  if (horoffset_ + chrwidthsum_ [cursorposition_] > allwidth_)
    horoffset_ = allwidth_ - chrwidthsum_ [cursorposition_];
}


void EditLabel::deleteSelection ()
{
  text_.deleteSubstring (cursorposition_, markposition_);
  if (markposition_ < cursorposition_)
    cursorposition_ = markposition_;
  else
    markposition_ = cursorposition_;
  // caller responsible for compute_metrics
}


void EditLabel::deleteChar ()
{
  if (selecting_)
    deleteSelection ();
  else
    text_.deleteChar (cursorposition_);
  compute_metrics ();
  selecting_ = 0;
  markposition_ = cursorposition_;
}


void EditLabel::deleteBackward ()
{
  if (selecting_)
  { deleteSelection ();
    compute_metrics ();
    selecting_ = 0;
    markposition_ = cursorposition_;
  }
  else if (cursorposition_)
  { text_.deleteChar (cursorposition_-1);
    compute_metrics ();
    cursorLeft (0);
  }
}


void EditLabel::deleteToEndOfLine ()
{
  text_.deleteSubstring (cursorposition_, text_.length ());
  compute_metrics ();
  selecting_ = 0;
  markposition_ = cursorposition_;
}
