//<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/fontset.h>
#include <NLS/charset.h>
#include <NLS/wstring.h>
#include <NLS/locale.h>
#include <InterViews/hit.h>
#include <hyperg/OS/string.h>

#include <ctype.h>
#include <iostream.h>

#define EDBUFFERSIZE 10

static boolean is_space (const WChar& ch) {
   return ((ch.value() & 0xff) == ch.value()) && isspace(ch.value());
}

/*** Label ***/

EditLabel::EditLabel (
  const char* s, const FontSet* f,
  const Color* color, const Color* csrcol,
  const Color* selcol, const Color* invcol, const Color* chgcol,
  int hide, char hidechar
)
: text_ (s?s:"",s?::strlen(s):0,EDBUFFERSIZE)
{
  // font
  fontset_ = f;
  Resource::ref (fontset_);
  // 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
  tmpCharBuffer_=0;
}

EditLabel::~EditLabel ()
{
  Resource::unref (fontset_);
  Resource::unref (color_);
  Resource::unref (csrcolor_);
  Resource::unref (selcolor_);
  Resource::unref (invcolor_);
  Resource::unref (chgcolor_);
  Resource::unref (brush_);
  delete chrwidthsum_;
  delete [] tmpCharBuffer_;
}

void EditLabel::compute_metrics ()
{
  const FontSetRep* rep;
  CharSet_T charset = CharSet::ascii();
  const Font *fn = fontset_->Find(charset, rep);
  int len = text_.Length();

  FontBoundingBox b;
  fontset_->string_bbox(text_.Text().string(), text_.Length(), 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_ = fn->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
  {
    chrwidthsum_ [0] = 0;
    for (int i = 0; i < len ; i++) {
      if (text_.Text()[i].charset() != charset) {
        charset = text_.Text()[i].charset();
        fn = fontset_->Find(charset, rep);
      }
      chrwidthsum_ [i+1] = fn ? 
      chrwidthsum_ [i] + fn->width(rep->convert(text_.Text()[i].charcode())) : 
      chrwidthsum_ [i];
    }
    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;

  // 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)
{
  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

  EditLabel* ThisEditLabel=(EditLabel*)this;

  const FontSetRep* rep;
  CharSet_T charset = CharSet::ascii();
  const Font *fn = fontset_->Find(charset, rep);
  const Font *fna = fontset_->Find(charset, rep);

  Coord x = a.x () + horoffset_ + hmargin_;
  Coord y = a.y ();
  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 ();

  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_)) {
    boolean ml = markposition_ == len;
    boolean cl = cursorposition_ == len;
    drawSelection (c,
      (fillselection_ && ml) ? right : x + chrwidthsum_ [markposition_],
      bottom,
      (fillselection_ && cl && !ml) ? right : x + chrwidthsum_ [cursorposition_],
      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++, 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);
    if (!hide_)
    {
      if (ThisEditLabel->text_.Text()[i].charset() != charset) {
        charset = ThisEditLabel->text_.Text()[i].charset();
        fn = fontset_->Find(charset, rep);
      }
      if (fn) {
        c->character(fn, rep->convert(ThisEditLabel->text_.Text()[i].charcode()),
          width, color, x, y);
      }
    }
    else
    {
      if (fna)
        c->character(fna, hidechar_,width, color, x, y);
    }


    // ought to write a Canvas::string routine
    x += width;
  } // for all char's

  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
//       );
      index = fontset_->index (
        text_.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
    index = fontset_->index (
      text_.Text().string(), text_.Length(), x - horoffset_ - hmargin_, true
    );

  return index;
}

const char* EditLabel::string()
{
  delete [] tmpCharBuffer_;
  tmpCharBuffer_=text_.Text().string(Locale::setlocale());
  return tmpCharBuffer_;
}

const char* EditLabel::string(int index1, int index2)
{
  if (tmpCharBuffer_)
    delete [] tmpCharBuffer_;
  tmpCharBuffer_=text_.Text(index1, index2-index1).string(Locale::setlocale());
  return tmpCharBuffer_;
}

// int EditLabel::strlen()
// {
//   const char* str=string();
//   return str?::strlen(str):0;
// }


/*** Editing ***/


void EditLabel::setString (const char* s)
{
//   text_.setString (s);
  text_.Delete(text_.BeginningOfText(), text_.Length());
  WString ws (s);
  text_.Insert(0, ws.string(), ws.length());
  cursorposition_ = 0;
  selecting_ = 0;
  compute_metrics ();
  horoffset_ = 0;
}


void EditLabel::cursorPosition (int pos, int sel)
{
  if (!sel)
    markposition_ = pos;
  cursorposition_ = 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 mark position is within winow

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

  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)
{
  if (cursorposition_)
    if (horoffset_ + chrwidthsum_ [--cursorposition_] < 0)
      horoffset_ = -chrwidthsum_ [cursorposition_];
  selecting_ = sel;
  if (!sel)
    markposition_ = cursorposition_;
}


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


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


void EditLabel::cursorWordRight (int sel)
{
  //  cursorposition_ = text_.wordRight (cursorposition_);
  while (cursorposition_<text_.Length() && is_space(text_.Text().item(cursorposition_)))
    cursorposition_++;
  cursorposition_ = text_.EndOfWord(cursorposition_);
  if (horoffset_ + chrwidthsum_ [cursorposition_] > allwidth_)
    horoffset_ = allwidth_ - chrwidthsum_ [cursorposition_];
  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);
  if (horoffset_ + chrwidthsum_ [cursorposition_] > allwidth_)
    horoffset_ = allwidth_ - chrwidthsum_ [cursorposition_];
}


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


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


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


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


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


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


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


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


void EditLabel::modifyColor(const Color* col)
{
  Resource::unref(chgcolor_);
  chgcolor_ = col;
  Resource::ref(chgcolor_);
  // caller responsible for redraw
}

void EditLabel::hidden(int onoff)
{
  hide_ = onoff;
}
