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



//<file>
//
// File:        textb.C - browser for some lines of text
//
// Created:     10 Oct 94   Michael Pichler
//
// $Id: textb.C,v 1.14 1996/10/16 07:33:12 jschipf Exp $
//
//</file>
//
// $Log: textb.C,v $
// Revision 1.14  1996/10/16 07:33:12  jschipf
// adjust copy for wcharacter strings
//
// Revision 1.13  1996/09/03 14:42:24  bmarsch
// Bugfix: didn't delete adjustable in destructor
//
// Revision 1.12  1996/07/26 12:43:17  bmarsch
// Bugfix in destructor
//
// Revision 1.11  1996/05/31 16:48:38  bmarsch
// Bugfix in appendText()
//
// Revision 1.10  1996/05/21 07:26:43  bmarsch
// Bugfix in copy()
//
// Revision 1.9  1996/02/15 17:45:10  bmarsch
// list.h moved from textb.h to textb.C
//
// Revision 1.8  1996/02/01 13:23:21  bmarsch
// Bugfix in clearText()
//
// Revision 1.7  1996/01/18 16:26:44  bmarsch
// Removed warnings
//
// Revision 1.6  1996/01/09 15:55:29  bmarsch
// Range check in scrollToCursor()
//


#include <hyperg/utils/str.h>
#include <hyperg/OS/list.h>

#include "textb.h"

#include "editlabel.h"
#include "glyphutil.h"

#ifdef SMOOTHSCROLL
#include "sscrbox.h"
#else
#include <InterViews/scrbox.h>
#endif

#include <IV-look/kit.h>
#include <InterViews/color.h>
#include <InterViews/display.h>
#include <InterViews/event.h>
#include <InterViews/font.h>
#include <InterViews/fontset.h>
#include <InterViews/hit.h>
#include <InterViews/layout.h>
#include <InterViews/selection.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <InterViews/target.h>

#include <X11/keysym.h>

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



/*** TxtBrAdjustable ***/
// horizontal adjustable of a TextBrowser


class TxtBrAdjustable: public Adjustable
{
  public:
    TxtBrAdjustable (TextBrowser* tbrowser);

    void scrollRange (Coord allocationwidth, Coord maxglyphwidth);

    // Adjustable
    virtual Coord lower (DimensionName) const;
    virtual Coord upper (DimensionName) const;
    virtual Coord length (DimensionName) const;
    virtual Coord cur_lower (DimensionName) const;
    virtual Coord cur_upper (DimensionName) const;
    virtual Coord cur_length (DimensionName) const;
    virtual void scroll_to (DimensionName, Coord);

  private:
    TextBrowser* tbrowser_;
    Coord maxrange_, currange_, curlower_;
};


TxtBrAdjustable::TxtBrAdjustable (TextBrowser* tbrowser)
{
  tbrowser_ = tbrowser;
  maxrange_ = currange_ = 1.0;
  curlower_ = 0.0;
}


void TxtBrAdjustable::scrollRange (Coord allocationwidth, Coord maxglyphwidth)
{
  if (maxglyphwidth > allocationwidth)  // have something to scroll
  { maxrange_ = maxglyphwidth;
    currange_ = allocationwidth;
  }
  else
  { maxrange_ = currange_ = allocationwidth;
  }

  Coord space = maxrange_ - currange_;  // space to scroll
  small_scroll (Dimension_X, space / 16);  // might use average or maximum char width here
  large_scroll (Dimension_X, space / 4);

  notify(Dimension_X);
  if (curlower_ > space)  // (curlower_ + currange_ > maxrange_)
    scroll_to (Dimension_X, space);  // scroll to right border

} // scrollRange


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

Coord TxtBrAdjustable::upper (DimensionName) const
{ return maxrange_;
}

Coord TxtBrAdjustable::length (DimensionName) const
{ return maxrange_;
}

Coord TxtBrAdjustable::cur_lower (DimensionName) const
{ return curlower_;
}

Coord TxtBrAdjustable::cur_upper (DimensionName) const
{ return (curlower_ + currange_);
}

Coord TxtBrAdjustable::cur_length (DimensionName) const
{ return currange_;
}


void TxtBrAdjustable::scroll_to (DimensionName, Coord newlower)
{
  if (newlower < 0)
    newlower = 0;
  Coord maxnewlower = maxrange_ - currange_;
  if (newlower > maxnewlower)  // newlower + currange_ !<= maxrange_
    newlower = maxnewlower;

  curlower_ = newlower;
  tbrowser_->scrollTo (newlower);

} // scroll_to



/*** TextEditLabel ***/
// EditLabel that behaves slightly different in allocate

class TextEditLabel: public EditLabel
{
  public:
    TextEditLabel (
      const char* string, const FontSet* fontset, const Color* color,
      const Color* csrcol, const Color* selcol,
      const Color* invcol, const Color* chgcol,
      int hide, char hidechar
    )
    : EditLabel (string, fontset, color, csrcol, selcol, invcol, chgcol, hide, hidechar)
    { fillSelection (1);
    }

    virtual void allocate (Canvas* c, const Allocation& a, Extension& ext)
    { do_allocate (c, a, ext);
    }
};


/*** TextBrowser ***/

declarePtrList(TextBrowserLines,EditLabel)
implementPtrList(TextBrowserLines,EditLabel)

declareSelectionCallback(TextBrowser)
implementSelectionCallback(TextBrowser)

// bmarsch 19950810: a small utility function
inline boolean is_inside(Hit& hit)
{
  return hit.any() && hit.depth() > 2 &&
         hit.index(0) == 0 && hit.index(1) == 0;
}


TextBrowser::TextBrowser (WidgetKit& kit, int size,
                          const char* stylename, const char* aliasname)
: InputHandler (nil, kit.style ())
{
  kit_ = &kit;
  lines_ = new TextBrowserLines (size);
  maxwidth_ = 0;
  horoffset_ = 0;
  dragged_ = false;

  cursorline_ = markerline_ = 0;
  cursorcol_ = markercol_ = 0;

  focus_ = false;
  copy_ = lose_ = 0;

  // will have to use specific attribute names
  // like in TextBrowser instead of foreground/background
  kit.begin_style (stylename ? stylename : "TextBrowser",
                   aliasname ? aliasname : "FieldBrowser");
  Style* style = kit.style ();
  style->alias ("EditLabel");
  Display* dis = Session::instance ()->default_display ();

  fontset_ = kit.fontset ();
  Resource::ref (fontset_);
  fgcolor_ = kit.foreground ();
  Resource::ref (fgcolor_);

  if (!(csrcolor_ = lookupColor (style, dis, "cursorColour", "cursorColor", "cursorcolour", "cursorcolor")))
    csrcolor_ = kit.foreground ();
  Resource::ref (csrcolor_);

  if (!(selcolor_ = lookupColor (style, dis, "selectionColour", "selectionColor", "selectioncolour", "selectioncolor")))
    selcolor_ = kit.foreground ();
  Resource::ref (selcolor_);

  if (!(invcolor_ = lookupColor (style, dis, "inverseColour", "inverseColor", "inversecolour", "inversecolor")))
    invcolor_ = kit.background ();
  Resource::ref (invcolor_);

  hadj_ = new TxtBrAdjustable (this);
  allwidth_ = 0;

  box_ = new TBSCROLLBOX (size);  // ref'ed as body

  const LayoutKit& layout = *LayoutKit::instance();

  Glyph* target = new Target (
    kit.inset_frame (box_), TargetTransparentHit
  );

  kit.end_style ();  // "TextBrowser"/"FieldBrowser"/"EditLabel"

  hscrollbar_ = kit.hscroll_bar (hadj_);
  vscrollbar_ = kit.vscroll_bar (box_);

  Requisition req;
  vscrollbar_->request (req);
  const Requirement& xreq = req.x_requirement ();
  float scrollbarwidth = xreq.natural ();

  Glyph* buttonfiller = layout.hfixed (
    kit.outset_frame (
      layout.flexible (nil)
    ),
    scrollbarwidth
  );

  hbox1_ = layout.hbox (
    layout.vcenter (
      target,
      1.0
    ),
    vscrollbar_
  );
  hbox2_ = layout.hbox (
    hscrollbar_,
    buttonfiller
  );
  vbox_ = layout.vbox (
    hbox1_,
    hbox2_
  );
  body (vbox_);

  insertmode_ = true;

  // insert dummy line
  empty_ = false;
  appendLine("");
  empty_ = true;
}


TextBrowser::~TextBrowser ()
{ 
  // clear list of all lines
  long n = lines_->count();
  while (n--)
    box_->remove(0);
  lines_->remove_all();
  delete lines_;

  Resource::unref (fontset_);
  Resource::unref (fgcolor_);
  Resource::unref (csrcolor_);
  Resource::unref (selcolor_);
  Resource::unref (invcolor_);

  // inhibit selection callbacks on this text browser
  if (copy_)
  { copy_->detach ();
    copy_->unref ();
  }
  if (lose_)
  { lose_->detach ();
    lose_->unref ();
  }

  delete hadj_;
}


Adjustable* TextBrowser::adjustableY ()
{
  return box_;
}

Adjustable* TextBrowser::adjustableX ()
{
  return hadj_;
}


void TextBrowser::appendLine (const char* text)
{
  removeDummy();
  empty_ = false;

  EditLabel* line = newLine (text);
  box_->append (line);  // ref's line
  lines_->append (line);

  reallocate();
  redraw();
}


void TextBrowser::prependLine (const char* text)
{
  removeDummy();
  empty_ = false;

  EditLabel* line = newLine (text);
  box_->prepend (line);  // ref's line
  lines_->prepend (line);

  reallocate();
  redraw();
}


void TextBrowser::appendText (const char* text)
{
  if (!text || !*text)
    return;

  removeDummy();
  empty_ = false;

  char* str = (char*) text;  // don't worry: the string will remain the same
  EditLabel* line;
  int len;
  char* nlpos;

  int curlen = 256;  // extends if necessary
  char* linebuf = new char [curlen];

  while (*str)
  {
    nlpos = strchr (str, '\n');

    if (nlpos)
    {
      len = nlpos - str;
      if (len+1 > curlen)
      { delete[] linebuf;
        linebuf = new char [curlen = len+1];  // incl. '\0'
      }
      strncpy (linebuf, str, len);
      linebuf [len] = '\0';

      line = newLine (linebuf);
      box_->append (line);
      lines_->append (line);

      str = nlpos + 1;  // behind '\n'
    }
    else
    {
      line = newLine (str);
      box_->append (line);
      lines_->append (line);
      break;  // "incomplete last line"
    }

  } // for each line

  delete[] linebuf;

  reallocate();
  redraw();
} // appendText

void TextBrowser::removeDummy()
{
  // remove dummy empty line
  if (empty_) {
    lines_->remove(0);
    box_->remove(0);
  }
}

EditLabel* TextBrowser::newLine (const char* text)
{
  // create a new line
  EditLabel* line = new TextEditLabel (
    text, fontset_,
    fgcolor_, csrcolor_, selcolor_, invcolor_,
    nil, 0, 0  // no modified background, no text hiding
  );
  line->insertMode(insertmode_);

  // update maximum line width (see also listbox.C)
  Requisition req;
  line->request (req);
  const Requirement& xreq = req.x_requirement ();
  float nat = xreq.natural ();
  if (nat > maxwidth_)
    maxwidth_ = nat;

  return line;
} // newLine


void TextBrowser::breakLine()
{
  EditLabel* curlabel = lines_->item(cursorline_);
  EditLabel* newlabel = newLine(curlabel->string(cursorcol_,curlabel->strlen()));
  curlabel->deleteToEndOfLine();

  box_->insert(cursorline_+1, newlabel);
  lines_->insert(cursorline_+1, newlabel);
  cursorPosition(cursorline_+1, 0, 0);
  
  // update maxwidth_ and reallocate scrollbar
  maxwidth_ = 0;
  for (int i=0; i<numLines(); i++) {
    Coord width = lines_->item(i)->width();
    if (width > maxwidth_)
      maxwidth_ = width;
  }
  hadj_->scrollRange(allwidth_, maxwidth_ + 4.0);
  // magic 4.0 makes up for border of inset frame, sorry

  reallocate();
}


void TextBrowser::reallocate()
{
  Extension ext;
  if (canvas()) {
    // modify (change) all boxes; otherwise they do no allocate on
    // their children!!!
    hscrollbar_->change(0);
    vscrollbar_->change(0);
    vbox_->modified(0);
    hbox1_->modified(0);
    hbox2_->modified(0);

    allocate(canvas(), allocation(), ext);
  }
}


void TextBrowser::notifyX ()
{
  hadj_->scrollRange(allwidth_, maxwidth_ + 4.0);
  // magic 4.0 makes up for border of inset frame, sorry

  // update scrollbar
  float offset = lines_->item(cursorline_)->getOffset();
  hadj_->scroll_to(Dimension_X, offset);
}


void TextBrowser::clearText ()
{
  long n = lines_->count ();

  while (n--)
    box_->remove (n);  // unref's line

  lines_->remove_all ();
  maxwidth_ = 0;
  horoffset_ = 0;

  cursorline_ = markerline_ = 0;
  cursorcol_ = markercol_ = 0;

  // append dummy line
  empty_ = false;
  appendLine("");
  empty_ = true;

  if (focus_) {
    showCursor(0);
    redraw();
  }
}


GlyphIndex TextBrowser::numLines () const
{
  if (empty_)
    return 0;
  else
    return lines_->count ();
}


void TextBrowser::getText(RString& text) const
{
  if (empty_) return;

  RString newline = "\n";
  text = RString::lambda();
  for (long i = 0; i < numLines(); i++) {
    text += RString(lines_->item(i)->string());
    text += newline;
  }
}


const char* TextBrowser::getLine(long i) const
{
  if (empty_) return nil;

  // constrain index
  if (i < 0) i = 0;
  else if (i >= numLines()) i = numLines()-1;

  return lines_->item(i)->string();
}


void TextBrowser::scrollTo (Coord horoffset)
{
  if (horoffset == horoffset_)  // nothing to do
    return;

  horoffset_ = horoffset;

  TextBrowserLines* lines = lines_;
  long n = lines->count ();
  while (n--)
    lines->item (n)->setOffset (horoffset);

  redraw ();
  hadj_->notify (Dimension_X);  // update scroll bar
}


void TextBrowser::cursorPosition (long line, int col, int sel)
{
  if (empty_) return;

  // constrain
  if (line < 0) line = 0;
  if (line >= numLines()) line = numLines() - 1;

  long min, max;  // touched lines
  if (cursorline_ < markerline_)
    min = cursorline_,  max = markerline_;
  else
    min = markerline_,  max = cursorline_;
  if (line < min)
    min = line;
  if (line > max)
    max = line;

  // set range of col to [0;len)
  int len = lines_->item(line)->strlen();
  if (col > len) col = len;

  hideCursor (cursorline_);
  lines_->item (line)->cursorPosition (col, sel);
  if (!sel) {
    markerline_ = line;
    markercol_ = col;
  }
  cursorline_ = line;
  cursorcol_ = col;
  showCursor (cursorline_);
  // assert: cursorline_ and markerline_ in valid range

  long from, to;  // new selection range
  if (cursorline_ < markerline_)
    from = cursorline_,  to = markerline_;
  else
    from = markerline_,  to = cursorline_;

  for (long i = min;  i <= max;  i++)
  { // enusure not to call functions of Editlabel
    // that change the scrolling offset here
    EditLabel* label = lines_->item (i);
    if (i == cursorline_ && markerline_ <= cursorline_)
      label->fillSelection(0);
    else
      label->fillSelection(1);
    if (i < from || i > to) {  // outside range
      label->stopSelecting ();
    }
    else if (i != from && i != to) { // inside range
      label->selectAll ();
    }
    else if (sel)
    {
      boolean down = (cursorline_ > markerline_);
      if (i == from && i != to) { // first line
        if (down)
          label->cursorPosition(label->strlen(), 1);
        else
          label->markPosition(label->strlen());
      }
      else if (i == to && i != from) { // last line
        if (down)
          label->markPosition(0);
        else
          label->cursorPosition(0, 1);
      }
    }
  }

  scrollToCursor();
  notifyX();

  // caller responsible for redraw
} // cursorPosition


void TextBrowser::scrollToCursor()
{
  // scroll line
  if (cursorline_ <= box_->first_shown()) {  // scroll up
    box_->scrollOnTop(cursorline_);
  }
  else if (cursorline_ >= box_->last_shown()) { // scroll down
    box_->scrollOnBottom(cursorline_);
  }

  // scroll column
  if (numLines() == 0) return;

  EditLabel* curlabel = lines_->item(cursorline_);
  curlabel->scrollToCursor();
  float offset = curlabel->getOffset();
  horoffset_ = offset;
  for (int i = 0; i < numLines(); i++)
    lines_->item(i)->setOffset(offset);
}


static long labs (long l)  { return (l < 0) ? -l : l; }

void TextBrowser::attractMark (long line, int col)
{
  // make markline_ be nearer line than cursorline_

  if (labs (line - cursorline_) > labs (line - markerline_))
  {
    line = cursorline_;
    cursorline_ = markerline_;
    markerline_ = line;
    int temp = cursorcol_;
    cursorcol_ = markercol_;
    markercol_ = temp;
  }
  else if (line == cursorline_ && line == markerline_)
    lines_->item (line)->attractMark (col);
}


void TextBrowser::paste(SelectionManager* s)
{
  String* type;
  void* data;
  int nbyte, format;

  s->get_value(type, data, nbyte, format);

  if (nbyte <= 0) return;

  hideCursor(cursorline_);

  char* text = (char*) data;
  while (*text) {
    empty_ = false;
    char* nlpos = strchr(text, '\n');
    if (nlpos) {
      int len = nlpos - text;
      text[len]= '\0';
      breakLine();
      lines_->item(cursorline_-1)->insertString(text, len);
      text[len] = '\n';
      text = nlpos + 1;
    }
    else {
      EditLabel* curlabel = lines_->item(cursorline_);
      curlabel->insertString(text, strlen(text));

      // update maxwidth_ and reallocate
      float width = curlabel->width();
      if (width > maxwidth_)
        maxwidth_ = width;

      cursorcol_ = curlabel->strlen();
      scrollToCursor();
      notifyX();
      break;
    }
  }

  reallocate();
  redraw();
  showCursor(cursorline_);
}


void TextBrowser::copySelection (const Event& e)
{
  SelectionManager* s = e.display ()->primary_selection ();
  Resource::unref (copy_);
  if (lose_)
  { lose_->detach ();  // otherwise I'd lose my new selection
    lose_->unref ();
  }
  // offer a copy
  copy_ = new SelectionCallback(TextBrowser) (this, &TextBrowser::copy);
  Resource::ref (copy_);
  lose_ = new SelectionCallback(TextBrowser) (this, &TextBrowser::lose);
  Resource::ref (lose_);
  s->own (copy_, lose_);
}


void TextBrowser::copy (SelectionManager* s)  // do copy operation
{
  long from, to;  // line no.
  if (cursorline_ < markerline_)
    from = cursorline_,  to = markerline_;
  else
    from = markerline_,  to = cursorline_;

  if (from == to)  // part of single line
  {
    EditLabel* line = lines_->item (from);


    int sindex1;int sindex2;
    int mark = sindex1 = line->markPosition ();
    int cursor = sindex2 = line->cursorPosition ();
    if (cursor < mark)
    {
      sindex1=cursor;
      sindex2=mark;
    }
    const char* text = line->string (sindex1,sindex2);
    int textlengthtail=text?strlen(text):0;

    s->put_value (text,textlengthtail);
  }
  else  // several lines
  {
    EditLabel* line = lines_->item (from);  // tail of first line

    int sindex1;int sindex2;
    int mark = sindex1 = line->markPosition ();
    int cursor = sindex2 = line->cursorPosition ();
    if (cursor < mark)
    {
      sindex1=cursor;
      sindex2=mark;
    }
    const char* text = line->string (sindex1,sindex2);
    int textlengthtail=text?strlen(text):0;


    int len = textlengthtail + 1;  // with '\n'

    long i;
    for (i = from + 1;  i < to;  i++)  // lines in between
    { line = lines_->item (i);
      len += ::strlen(line->string()) + 1; // with '\n'
    }

    line = lines_->item (to);  // head of last line
    int eindex1;int eindex2;
    mark = eindex1 = line->markPosition ();
    cursor = eindex2 = line->cursorPosition ();
    if (cursor < mark)
    {
      eindex1=cursor;
      eindex2=mark;
    }
    text = line->string (eindex1,eindex2);
    int textlengthhead=text?strlen(text):0;


    int completelastline = (line->cursorPosition () == eindex2);
    len += textlengthhead + completelastline;


    char* data = new char [len + 1];  // strcpy adds '\0'...

    char* dptr = data;
    line = lines_->item (from);  // tail of first line
    strcpy (dptr, lines_->item (from)->string (sindex1,sindex2));
    dptr += textlengthtail;
    *dptr++ = '\n';

    for (i = from + 1;  i < to;  i++)  // lines in between
    { line = lines_->item (i);
      strcpy (dptr, line->string ());
      dptr += ::strlen(line->string());
      *dptr++ = '\n';
    }

    line = lines_->item (to);  // head of last line
    strcpy (dptr, line->string (eindex1,eindex2));
    if (completelastline)
      dptr [textlengthhead] = '\n';

    s->put_value (data, len);

    delete data;
  }

} // copy


void TextBrowser::lose (SelectionManager*)  // losing selection
{
  stopSelecting ();
  redraw ();
}


void TextBrowser::stopSelecting ()
{
  long min, max;
  if (cursorline_ < markerline_)
    min = cursorline_, max = markerline_;
  else
    min = markerline_, max = cursorline_;

  for (long i = min; i <= max; i++)
    lines_->item(i)->stopSelecting();

  markerline_ = cursorline_;
  markercol_ = cursorcol_;
}


void TextBrowser::allocate (Canvas* c, const Allocation& a, Extension& e)
{
  InputHandler::allocate (c, a, e);

  allwidth_ = a.right () - a.left ();

  // subtract space for vscrollbar
  Requisition vreq;
  vscrollbar_->request(vreq);
  allwidth_ -= vreq.x_requirement().natural();

  hadj_->scrollRange (allwidth_, maxwidth_ + 4.0);
  // magic 4.0 makes up for border of inset frame, sorry
}


void TextBrowser::press (const Event& e)
{
  if (numLines() == 0) return;

  dragged_ = false;
  press_x_ = e.pointer_root_x();
  press_y_ = e.pointer_root_y();

  Hit hit (&e);
  repick (0, hit);

  button_ = -1;

  if (is_inside(hit))
  {
    button_ = e.pointer_button ();
    long li = hit.index (2);
    int index = (int) hit.index (3);

    switch (button_)
    {
      case Event::left:  // set text cursor
      case Event::middle:
        cursorPosition (li, index, 0);  // move cursor (and mark)
      break;

      case Event::right:  // expand selection (at nearer endpoint)
        attractMark (li, index);
        cursorPosition (li, index, 1);  // move mark
      break;
    }

    redraw ();
  }
} // press

#define THRESH 3

void TextBrowser::drag (const Event& e)
{
  if (empty_) return;

  if (!dragged_) {
    // check threshold
    Coord x = e.pointer_root_x();
    Coord y = e.pointer_root_y();
    Coord diffx = x - press_x_;
    if (diffx < 0) diffx = -diffx;
    Coord diffy = y - press_y_;
    if (diffy < 0) diffy = -diffy;
    if (diffx < THRESH && diffy < THRESH)
      return;
  }

  Hit hit (&e);
  repick (0, hit);

  long li;
  int index;

  if (is_inside(hit)) {
    li = hit.index (2);
    index = (int) hit.index (3);
  }
  else {   // pointer is outside text field
    const Allocation& a = allocation();
    Coord y = e.pointer_y();
    // all magic's 4.0 come from the inset frame, sorry!
    if (y < a.bottom()+4.0) {
      li = numLines() - 1;
    }
    else if (y > a.top()-4.0) {
      li = 0;
    }
    else {
      Hit h(a.left()+4.0, y);
      repick(0, h);
      if (h.any() && h.depth() == 3 && h.index(0) == 0)
        li = h.index(2);
      else
        li = numLines() - 1;
    }
    EditLabel* el = lines_->item(li);
    index = el->hitIndex(hit.left() - a.x());
  }

  long oldcursorline = cursorline_;
  int oldcursorcol = cursorcol_;

  switch (button_) {
    case Event::left:  // expand selection
    case Event::right:
      cursorPosition (li, index, 1);  // move mark
      // TODO: equivalent for scrollToMark
      if (cursorline_ != oldcursorline || cursorcol_ != oldcursorcol)
        redraw ();
      break;
      // TODO: move text around with middle mouse button
  }

  dragged_ = true;
} // drag

#undef THRESH

void TextBrowser::release (const Event& e)
{
  if (e.pointer_button () == Event::middle) {
    if (!dragged_) {
      pasteSelection(e);
      redraw();
    }
  }
  else {
    if (dragged_)
      copySelection (e);
  }
}


void TextBrowser::double_click (const Event& e)
{
  Hit hit (&e);
  repick (0, hit);
  static Coord oldx = -1;
  static Coord oldy = -1;
  Coord newx = e.pointer_x ();
  Coord newy = e.pointer_y ();

  if (is_inside(hit) && button_ == Event::left)
  {
    int li = (int) hit.index (2);
    int index = (int) hit.index (3);

    EditLabel* line = lines_->item (li);
    if (newx == oldx && newy == oldy)
      line->selectAll ();
    else
      line->selectWord (index);
    redraw ();
  }
  oldx = newx;
  oldy = newy;
} // double_click


void TextBrowser::keystroke (const Event& e)
{
  int shift = e.shift_is_down ();
  int ctrl = e.control_is_down ();

// TODO: home/end/ctrl-home/ctrl-end

  switch (e.keysym ())
  {
    case XK_Tab:
      if (shift)
        prev_focus ();
      else
        next_focus ();
    return;

    // vertical scrolling
    // the scrollbox has its minimum at the lower and the maximum at the upper end
    case XK_Up:
      box_->scroll_forward (Dimension_Y);
    break;
    case XK_Down:
      box_->scroll_backward (Dimension_Y);
    break;
    case XK_Prior:
      box_->page_forward (Dimension_Y);
    break;
    case XK_Next:
      box_->page_backward (Dimension_Y);
    break;

    // horicontal scrolling
    case XK_Left:
      if (ctrl)
        hadj_->page_backward (Dimension_X);
      else
        hadj_->scroll_backward (Dimension_X);
    break;
    case XK_Right:
      if (ctrl)
        hadj_->page_forward (Dimension_X);
      else
        hadj_->scroll_forward (Dimension_X);
    break;
  }

  redraw ();

} // keystroke

InputHandler* TextBrowser::focus_in()
{
  focus_ = true;
  return InputHandler::focus_in();
}

void TextBrowser::focus_out()
{
  focus_ = false;
  InputHandler::focus_out();
}

// should have visual cue for focus!
