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



//<file>
//
// File:        text.C - editor for some lines of text
//
// Created:     25 Apr 95   Bernhard Marschall
//
// Changed:     13 Apr 95   
//
//
//</file>

#include <iostream.h>

#include <hyperg/utils/str.h>

#include "text.h"
#include "editlabel.h"
#include "sscrbox.h"

#include <InterViews/display.h>
#include <InterViews/event.h>
#include <InterViews/selection.h>
#include <InterViews/style.h>
#include <IV-look/kit.h>
#include <OS/string.h>

#include <X11/keysym.h>


declarePtrList(TextBrowserLines,EditLabel)
declareSelectionCallback(TextBrowser)


TextEditor::TextEditor(WidgetKit& kit, int size)
: TextBrowser(kit, size, "TextEditor", "FieldEditor")
{
  kit.begin_style("TextEditor");
  kit.alias("FieldEditor");
  kit.alias("EditLabel");
  delbackward_ = kit.style()->value_is_on("deleteBackward");
  kit.end_style();

//  appendLine("");
}


TextEditor::~TextEditor()
{}


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


const char* TextEditor::getLine(long i) const
{
  // constrain index
  if (i < 0) i = 0;
  else if (i >= numLines()) i = numLines()-1;

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


void TextEditor::setCursorLine(long line, int sel)
{
  if (line < 0) line = 0;
  else if (line >= numLines()) line = numLines()-1;
  cursorPosition(line, cursorcol_, sel);
}


void TextEditor::setCursorCol(int col, int sel)
{
  int len = lines_->item(cursorline_)->strlen();
  long line = cursorline_;
  if (col < 0) {
    if (line == 0)
      col = 0;
    else
      col = lines_->item(--line)->strlen();
  }
  else if (col > len) {
    if (line < numLines()-1)
      line++, col = 0;
    else
      col = len;
  }

  cursorPosition(line, col, sel);
}


void TextEditor::insertChar(char c)
{
  // delete selection, if there is one
  if (markerline_ != cursorline_) deleteChar();

  EditLabel* curlabel = lines_->item(cursorline_);
  curlabel->insertChar(c);

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

  cursorcol_++;
  scrollToCursor();
  notifyX();
}


void TextEditor::deleteChar()
{
  EditLabel* curlabel = lines_->item(cursorline_);
  int pos = curlabel->cursorPosition();

  // if text is selected delete it
  if (cursorline_ != markerline_) {
    long min, max;
    if (cursorline_ < markerline_)
      min = cursorline_, max = markerline_;
    else
      min = markerline_, max = cursorline_;

    lines_->item(min)->deleteChar();
    lines_->item(max)->deleteChar();
    long d = min+1;
    for (long i=d; i<max; i++) {
      lines_->remove(d);
      box_->remove(d);
    }
    markerline_ = cursorline_;
    markercol_ = cursorcol_;
    cursorPosition(min, lines_->item(min)->strlen(), 0);
    removeNL();
  }  // if

  // otherwise delete character following cursor
  else if (pos == curlabel->strlen())
    removeNL();

  else
    curlabel->deleteChar();

  // 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;
  }
  notifyX();
}


void TextEditor::showCursor(long line)
{
  // assert: line is in valid range
  lines_->item(line)->showCursor(1);
}


void TextEditor::hideCursor(long line)
{
  // assert: line is in valid range
  lines_->item(line)->showCursor(0);
}


void TextEditor::keystroke(const Event& event)
{
  unsigned long keysym = event.keysym();
  boolean shift = event.shift_is_down();
  boolean ctrl = event.control_is_down();

  char key = '\0';
  event.mapkey(&key, 1);

  switch (keysym) {
    // cursor movement (scrolling)
    case XK_Up: 
      setCursorLine(cursorline_-1, shift);
      break;
    case XK_Down:
      if (cursorline_ == numLines()-1) {  // insert a new line at bottom of text
        EditLabel* edlabel = newLine("");
        box_->append(edlabel);
        lines_->append(edlabel);
      }
      setCursorLine(cursorline_+1, shift);
      break;
    case XK_Left:
      setCursorCol(cursorcol_-1, shift);
      break;
    case XK_Right:
      setCursorCol(cursorcol_+1, shift);
      break;
    case XK_Prior:
      if (ctrl)
        cursorPosition(0, 0, shift);
      else {
        box_->page_forward(Dimension_Y);
        setCursorLine(box_->first_shown(), shift);
      }
      break;
    case XK_Next:
      if (ctrl)
        cursorPosition(numLines()-1, 0, shift);
      else
        box_->page_backward(Dimension_Y);
        setCursorLine(box_->last_shown(), shift);
      break;
    case XK_Home:
      cursorPosition(cursorline_, 0, shift);
      break;
    case XK_End:
      cursorPosition(cursorline_, lines_->item(cursorline_)->strlen(), shift);
      break;
    case XK_Return: {
      breakLine();
      break;
    }

    // deleting
    case XK_Delete:
      if (ctrl)
        lines_->item(cursorline_)->deleteToEndOfLine();
      else {
        if (delbackward_)
          deleteBackward();
        else
          deleteChar();
      }
      break;
    case XK_BackSpace:
      deleteBackward();
      break;
    case XK_Insert:
      if (shift)
        pasteSelection(event);
      else if (ctrl)
        copySelection(event);
      else {
        insertmode_ = !insertmode_;
        for (int i=0; i<numLines(); i++)
          lines_->item(i)->toggleInsert();
      }
      break;

    case XK_Tab:
      next_focus();
//       int n = 8;
//       while (n--) insertChar(' ');
      break;

    default:
      if (ctrl) {  // handle control keys
        switch (keysym) {
          case XK_a:
            cursorPosition(cursorline_, 0, shift);
            break;
          case XK_d:
            if (delbackward_)
              deleteBackward();
            else
              deleteChar();
            break;
          case XK_e:
            cursorPosition(cursorline_, lines_->item(cursorline_)->strlen(), shift);
            break;
          case XK_k: {
            EditLabel* el = lines_->item(cursorline_);
            if (cursorcol_ < el->strlen())
              el->deleteToEndOfLine();
            else
              removeNL();
            break;
          }
        }
      }
      else if (key)
        insertChar(key);
      break;
  }

  redraw();
}


InputHandler* TextEditor::focus_in()
{
  // append an empty line if the editor is empty yet
  if (numLines() == 0) {
    appendLine("");
    reallocate();
  }

  lines_->item(cursorline_)->showCursor(1);
  redraw();
  return InputHandler::focus_in();
}


void TextEditor::focus_out()
{
  lines_->item(cursorline_)->showCursor(0);
  redraw();
  InputHandler::focus_out();
}


void TextEditor::pasteSelection(const Event& e)
{
  SelectionManager* s = e.display()->primary_selection();
  // request the selection
  s->retrieve("string", new SelectionCallback(TextBrowser) (this, &TextBrowser::paste));
}


long TextEditor::visibleLines() const
{
  return box_->last_shown() - box_->first_shown() - 1;
}


void TextEditor::removeNL()
{
  if (cursorline_ == numLines()-1) return;

  EditLabel* curlabel = lines_->item(cursorline_);
  EditLabel* dellabel = lines_->item(cursorline_+1);

  curlabel->insertString(dellabel->string(), dellabel->strlen());
  box_->remove(cursorline_+1);
  lines_->remove(cursorline_+1);
  cursorPosition(cursorline_, cursorcol_, 0);
  reallocate();
}
