//<copyright>
// 
// Copyright (c) 1994,95
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// original version:
// 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.
 */

/*
 * FieldBrowser -- scrolled display of text fields
 */


//<file>
//
// File:        field.C - portation of field editor to InterViews 3.1
//
// Created:     10 Mar 94   Michael Pichler
//
// $Id: fieldb.C,v 1.12 1996/10/16 07:23:42 jschipf Exp $
//
// $Log: fieldb.C,v $
// Revision 1.12  1996/10/16 07:23:42  jschipf
// paste now works with multibyte strings
//
// Revision 1.11  1996/05/31 12:42:36  bmarsch
// Added scrollToBegin()
//
// Revision 1.10  1996/05/13 08:08:29  bmarsch
// New member functions hidden()
//
// Revision 1.9  1996/04/29 09:44:23  bmarsch
// Overloaded undraw to reset cursor
//
// Revision 1.8  1996/02/16 16:26:22  bmarsch
// Paste with middle mouse button even if pointer has moved
// below a certain threshold
//
// Revision 1.7  1996/02/15 17:39:52  bmarsch
// Includes from OS/ were moved to hyperg/OS/
//
// Revision 1.6  1996/02/15 17:17:33  bmarsch
// Includes from Dispatch/ were moved to hyperg/Dispatch/
//
// Revision 1.5  1996/01/18 16:28:32  bmarsch
// Removed warnings
//
//</file>


#include "fieldb.h"

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

#include <hyperg/Dispatch/dispatcher.h>
#include <hyperg/Dispatch/iocallback.h>
#include <IV-look/kit.h>
#include <InterViews/background.h>
#include <InterViews/canvas.h>
#include <InterViews/color.h>
#include <InterViews/display.h>
#include <InterViews/font.h>
#include <InterViews/event.h>
#include <InterViews/hit.h>
#include <InterViews/layout.h>
#include <InterViews/patch.h>
#include <InterViews/selection.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <InterViews/window.h>

#include <hyperg/OS/math.h>
#include <hyperg/OS/string.h>

#include <X11/keysym.h>

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



/*** FieldBrowser ***/

declareSelectionCallback(FieldBrowser)
implementSelectionCallback(FieldBrowser)


FieldBrowser::FieldBrowser (
  WidgetKit* kit, Style* s,
  const char* sample,
  int hide, char hidechar,
  const char* stylename
)
: InputHandler (nil, s)
{
  dblclickindex_ = -1;

  copy_ = lose_ = nil;

  kit->begin_style (stylename ? stylename : "FieldBrowser", "EditLabel");
  Style* style = kit->style ();

  // threshold for paste
  thresh_ = 3.0;
  style->find_attribute("dragDistance", thresh_);

  Display* dis = Session::instance ()->default_display ();

  const Color* csrcol;  // "cursorColor"
  if (!(csrcol = lookupColor(style, dis,
                             "cursorColour", "cursorColor",
                             "cursorcolour", "cursorcolor")))
    csrcol = kit->foreground ();

  const Color* selcol;  // "selectionColor"
  if (!(selcol = lookupColor(style, dis,
                             "selectionColour", "selectionColor",
                             "selectioncolour", "selectioncolor")))
    selcol = kit->foreground ();

  const Color* invcol;  // "inverseColor"
  if (!(invcol = lookupColor(style, dis,
                             "inverseColour", "inverseColor",
                             "inversecolour", "inversecolor")))
    invcol = kit->background ();

  if (!(modcolor_ = lookupColor(style, dis,
                                "modifiedColour", "modifiedColor",
                                "modifiedcolour", "modifiedcolor")))
    modcolor_ = nil;
  Resource::ref(modcolor_);

  edlabel_ = new EditLabel (
    sample, kit->fontset (),
    kit->foreground (),
    csrcol,
    selcol,
    invcol,
    modcolor_,
    hide, hidechar
  );
  edpatch_ = new Patch (edlabel_);

  body (kit->inset_frame (edpatch_));

  kit->end_style ();  // "FieldEditor"/"EditLabel"
  cursor_changed_ = false;
}


FieldBrowser::~FieldBrowser ()
{
  // inhibit selection callbacks on this field editor
  if (copy_)
  { copy_->detach ();
    copy_->unref ();
  }
  if (lose_)
  { lose_->detach ();
    lose_->unref ();
  }

  Resource::unref(modcolor_);
}

void FieldBrowser::undraw()
{
  // reset cursor
  if (cursor_changed_) {
    Canvas* c = edpatch_->canvas();
    if (c) {
      Window* w = c->window();
      w->pop_cursor();
    }
  }
  cursor_changed_ = false;
  dragged_ = 0;

  InputHandler::undraw();
}

// FieldBrowser uses middle mouse button for paste (click) and scrolling (drag)

void FieldBrowser::press (const Event& e)
{
  Hit hit (&e);
  repick (0, hit);  // pick (canvas (), allocation (), 0, hit);

  button_ = e.pointer_button ();
  dragx_ = e.pointer_x ();
  dragged_ = 0;

  if (button_ != Event::middle && hit.any())  // initiate scrolling
  {
    int index = (int) hit.index (0);
    switch (button_)
    {
      case Event::left:  // set text cursor
        edlabel_->cursorPosition (index, 0);
        if (index != dblclickindex_)
          dblclickindex_ = -1;
      break;

      case Event::right:  // expand selection (at any endpoint)
        // when index is nearer to cursor than mark then swap mark and cursor
        edlabel_->attractMark (index);  // change at side nearer to index position
        edlabel_->cursorPosition (index, 1);  // move mark
        dragged_ = 1;  // to copy the selection on release
      break;
    }

    edpatch_->redraw ();
  }

} // press


void FieldBrowser::drag (const Event& e)
{
  if (button_ == Event::middle) {
    if (!dragged_) {
      if (Math::abs(dragx_ - e.pointer_x()) > thresh_) {
        // does not affect text cursor position
        // change mouse cursor to grabbing hand
        Window* win = edpatch_->canvas ()->window();
        win->push_cursor();
        win->cursor (WidgetKit::instance()->hand_cursor());
        cursor_changed_ = true;

        dragged_ = 1;
      }
    }

    else {
      edlabel_->scroll (e.pointer_x () - dragx_);
      edpatch_->redraw ();
      dragx_ = e.pointer_x ();
    }
  }

  else {
    Hit hit (&e);
    int index = edlabel_->hitIndex (hit.left () - allocation ().x ());
    int oldcursor = edlabel_->cursorPosition ();

    edlabel_->cursorPosition (index, 1);
    edlabel_->scrollToCursor ();
    if (edlabel_->cursorPosition () != oldcursor)
      edpatch_->redraw ();

    if (index != dblclickindex_)
      dblclickindex_ = -1;

    dragged_ = 1;
  }
} // drag


void FieldBrowser::release (const Event& e)
{
  if (button_ == Event::middle) {
    if (dragged_) {
      edpatch_->canvas ()->window ()->pop_cursor ();  // stop scrolling
      cursor_changed_ = false;
    }
    else { // paste at mouse position
      Hit hit (&e);
      repick (0, hit);
      if (hit.any ()) {
        edlabel_->cursorPosition ((int) hit.index (0), 0);
        // tiny bug: does not allow paste within one FieldEditor
        pasteSelection (e);
        edpatch_->redraw ();  // show new cursor when no paste was possible
      }
    } // paste
  }
  else {  // copy selected text
    if (dragged_)
      copySelection (e);
  }
}


void FieldBrowser::double_click (const Event& e)
{
  Hit hit (&e);
  repick (0, hit);

  if (hit.any () && button_ == Event::left)
  {
    int index = (int) hit.index (0);
  
    if (index == dblclickindex_)  // triple click (press removed selection)
      edlabel_->selectAll ();
    else  // double click
      edlabel_->selectWord (index);
    edpatch_->redraw ();
  
    dblclickindex_ = index;
    copySelection (e);  // no longer done in press
  }
} // double_click


// FieldBrowser not sensitive for keystrokes


void FieldBrowser::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(FieldBrowser) (this, &FieldBrowser::copy);
  Resource::ref (copy_);
  lose_ = new SelectionCallback(FieldBrowser) (this, &FieldBrowser::lose);
  Resource::ref (lose_);
  s->own (copy_, lose_);
}


void FieldBrowser::copy (SelectionManager* s)  // do copy operation
{
  if (edlabel_->hidden ())  // do not copy hidden fields
    return;

  int index1;int index2;
  int mark = index1 = edlabel_->markPosition ();
  int cursor = index2 = edlabel_->cursorPosition ();
  if (cursor < mark)
  {
    index1=cursor;
    index2=mark;
  }
  const char* text = edlabel_->string (index1,index2);
  int textlength=text?::strlen(text):0;
  s->put_value (text,textlength);
}



void FieldBrowser::paste (SelectionManager* s)  // do paste operation
{
  String* type;
  void* data;
  int nbyte, format;

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

  if (nbyte <= 0)
    return;

  // care for invalid characters
  char* str = (char*) data;
  for (int n = nbyte;  n--;  str++)
  { if (!*str || *str == '\n')
      *str = ' ';
  }

  edlabel_->insertString ((const char*) data, nbyte);
  edpatch_->redraw ();
}


void FieldBrowser::lose (SelectionManager*)  // losing selection
{
  edlabel_->stopSelecting ();
  edpatch_->redraw ();
}


// FieldBrowser has no focus management


void FieldBrowser::field (const char* str)
{
  edlabel_->setString (str);
  edpatch_->redraw ();
}

void FieldBrowser::scrollToBegin ()
{
  edlabel_->cursorHome (0);
  edpatch_->redraw ();
}

void FieldBrowser::scrollToEnd ()
{
  edlabel_->cursorEnd (0);
  edpatch_->redraw ();
}


const char* FieldBrowser::field () const
{
  return edlabel_->string ();
}

void FieldBrowser::hidden(int onoff)
{
  edlabel_->setString("");
  edlabel_->hidden(onoff);
  edpatch_->redraw();
}

int FieldBrowser::hidden() const
{
  return edlabel_->hidden();
}
