/* 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2006  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "swidget/STextEdit.h"
#include "stoolkit/SEncoder.h"

#ifdef  USE_OSX
# define SS_NO_STATUS_UNDER true
#else
# define SS_NO_STATUS_UNDER false
#endif

SDClick::SDClick (void)
{
//fprintf (stderr, "DClick create\n");
  clickCount = 0;
  timeoutCount = 0;
  timer = 0;
}

SDClick::~SDClick ()
{
  if (timer) delete timer;
}

void
SDClick::start (unsigned int millisec)
{
  if (timer == 0)
  {
    timer = STimer::newTimer(300, this);
    // 1 click is zero.
    clickCount = 0;
  }
  else
  {
    clickCount++;
  }
}

/**
 * The timer event
 */
bool
SDClick::timeout (const SEventSource* s)
{
  timeoutCount++;
  if (timer == 0)
  {
    fprintf (stderr, "strange. timeout..\n");
    return false;
  }
  // triple clicks have twice the timeout.
  if (timeoutCount < 2 && clickCount == 1) return true; /* again */
  clickCount = 0;
  timeoutCount = 0;
  delete timer;
  timer = 0;
  return false; /* don't call me again */
}

#define SS_LEFT_MARGIN 2
/**
 * A text area is an editable area of text.
 * This one creates one with empty text.
 */
STextEdit::STextEdit (void) : textView ("")
{
  autoClipboard = true;
  preEditSize = 0;
  currentHistorySize = 0;
  historySize = 0;
  sliderListener = 0;
  yuditInput = 0;
  editable = true;
  textView.setEditable(true);
  focused = false;
  selecting = false;
  editor.setInterface (this);
  column = 0;
  resized = false;
  statusHeight = 0;

  textView.move (SLocation((int)border.getBorderSize().width + SS_LEFT_MARGIN, 
        (int)border.getBorderSize().height));
  clip (true);
  textView.setWindowInterface (this);
  caret.resize (SDimension(textView.lineHeight, textView.lineHeight));
  updateCaretPosition ();
  updateCaretLocation ();

  caret.setWindowInterface (this);
}

bool
STextEdit::isFocused () const
{
  return focused;
}

/**
 * Deletes a text area.
 */
STextEdit::~STextEdit ()
{
  if (yuditInput)
  {
    //fprintf (stderr, "deleting yuditinput\n");
    delete yuditInput;
  }
}

/**
 * Create a text area with pre-set utf8 text.
 * @param utf8 is the initial text.
 */
STextEdit::STextEdit (const SString& utf8) : textView (utf8)
{
  autoClipboard = true;
  preEditSize = 0;
  currentHistorySize = 0;
  historySize = 0;
  sliderListener = 0;
  yuditInput = 0;
  statusHeight = 0;
  editable = true;
  textView.setEditable(true);
  focused = false;
  resized = false;

  editor.setInterface (this);
  textView.textData.move (STextIndex(0,0));
  updateCaretPosition ();
  selecting = false;
  column = 0;
  textView.move (SLocation((int)border.getBorderSize().width + SS_LEFT_MARGIN, 
        (int)border.getBorderSize().height));
  clip (true);
  textView.textData.fireEvent ();
  textView.setWindowInterface (this);
  caret.resize (SDimension(textView.lineHeight, textView.lineHeight));
  updateCaretLocation ();
  caret.setWindowInterface (this);
}


/**
 * Set the editor that works on this widget.
 */
void
STextEdit::setEditor (const SEditor& _editor)
{
  editor = _editor;
  if (yuditInput) yuditInput->clear();
  endSelect();
  deselectText();
  editor.setInterface (this);
}

/**
 * Add a text edit listener to the list.
 * @param ls is the listener to be added.
 */
void
STextEdit::addTextEditLS (STextEditLS* ls)
{
  listeners.append (ls);
}

/**
 * Remove the text edit listener from the list.
 * @param ls is the listener to be removed.
 */
void
STextEdit::removeTextEditLS (STextEditLS* ls)
{
  for (unsigned int i=0; i<listeners.size(); i++)
  {
    if (listeners[i] == ls)
    {
      listeners.remove (i);
      return;
    }
  }
  return;
}

/**
 * Set the overall background. This will effectively be the border's 
 * background.
 * @param bg is the backgroud.
 */
void
STextEdit::setBackground (const SColor& bg)
{
  border.setBackground (bg);
}

/**
 * Set the background of the text itself.
 * @param bg is the backgroud.
 */
void
STextEdit::setTextBackground (const SColor& bg)
{
  SPanel::setBackground (bg);
  textView.setBackground (bg);
  caret.setBackground (bg);
}

/**
 * Set the foreground of the text itself.
 * @param bg is the foreground.
 */
void
STextEdit::setForeground (const SColor& lrfg, const SColor& rlfg)
{
  textView.setForeground (lrfg, rlfg);
  caret.setForeground (lrfg, rlfg);
}

/**
 * Set the foreground of the text itself.
 * @param bg is the foreground.
 */
void
STextEdit::setCaretForeground (const SColor& lrfg, const SColor& rlfg)
{
  caret.setForeground (lrfg, rlfg);
}

/**
 * Set the text alignment align is true if it is right aligned.
 */
void
STextEdit::setAlignment (bool align)
{
  textView.setAlignment (align);
}

/**
 */
void
STextEdit::setMultiline (bool _multiline)
{
  textView.setMultiline (_multiline);
  /* extents may change */
  notifySlider();
}

/**
 */
bool
STextEdit::isMultiline () const
{
  return textView.isMultiline ();
}


/**
 * Redraw the Component on a canvas 
 * @param canvas is where we redraw this.
 */
void
STextEdit::redraw (SCanvas *w, int x, int y, unsigned int width, unsigned int height)
{
  clip (false);
  border.redraw (w, x, y, width, height);
  clip (true);
 
  unsigned int h0 = textView.getDocumentHeight(); 
  textView.redraw (w, x, y, width, height);
  unsigned int h1 = textView.getDocumentHeight(); 
  if (h1 != h0)
  {
    notifySlider();
    updateCaretLocation ();
    redrawInternal();
  }
  /*
   * We need this hack because when resizing, we may not 
   * have the proper info about where to put the caret.
   * We know it after we redrew the stuff.
   */
  if (resized)
  {
    resized = false;
    updateCaretLocation ();
  }
  caret.redraw (w);
  if (imWaiting.size()!=0 && window->isVisible())
  {
     setInputMethod(imWaiting);
  }
}

/**
 * Turn clipping at the border on and off.
 * @param on is true if we turn on clipping.
 */
void
STextEdit::clip (bool on)
{
  if (!on)
  {
    window->removeClippingArea ();
    textView.setClippingArea (0,0,0,0);
    return;
  }
  window->setClippingArea (
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height,
       size.width - 2 * border.getBorderSize().width,
       size.height - 2 * border.getBorderSize().height);
  textView.setClippingArea (
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height,
       size.width - 2 * border.getBorderSize().width,
       size.height - 2 * border.getBorderSize().height);
}

/**
 * Pass this to the editor.
 */
void
STextEdit::keyPressed (SWindow * w, SKey key, const SString& s,
          bool ctrl, bool shift, bool meta)
{
/*
fprintf (stderr, "try: %*.*s %*.*s %*.*s\n", 
   SSARGS(window->getInputMethod()),
   SSARGS(inputMethod), SSARGS(s));
*/

  if (yuditInput)
  {
    yuditInput->keyPressed (key, s, ctrl, shift, meta);
  }
  else  if (
      (window->getInputMethod() == "x-ascii" || window->getInputMethod() == "")
        && inputMethod == SS_KINPUT2_IM
        && shift == true && s == " ") 
  {
    //setInputMethod (SS_KINPUT2_IM);
    if (!window->startInputMethod ("_JAPANESE_CONVERSION",getProperties(),this))
    {
      fprintf (stderr, "Can not start kinput2 (_JAPANESE_CONVERSION) input method.\n");
    }
  } 
  else
  {
    editor.keyPressed (key, s, ctrl, shift, meta);
  }
}


/**
 * Pass this to the editor.
 */
void
STextEdit::keyReleased (SWindow * w, SKey key, const SString& s,
          bool ctrl, bool shift, bool meta)
{
  //if (!yuditInput) editor.keyReleased (key, s, ctrl, shift, meta);
  /* end selection needs this. */
  editor.keyReleased (key, s, ctrl, shift, meta);
}

/**
 * Pass this to the editor. Start a timer to measure
 * the double click. Get the focus.
 */
void
STextEdit::buttonPressed (SWindow * w, int button, int x, int y)
{
  if (yuditInput) yuditInput->clear();
  window->commitInputMethod();
  window->getKeyboardFocus();
  SCursorIndex textIndex = textView.getCursorIndex (SLocation (x, y));
  setFocus ();
  // not scroll
  if (button != 4 && button != 3) {
    /* double click check */
    clicks.start (990);    
  }
  editor.buttonPressed (button, textIndex);
}

/**
 * A button was released.
 */
void
STextEdit::buttonReleased (SWindow * w, int button, int x, int y)
{
  if (yuditInput) yuditInput->clear();
  window->commitInputMethod();
  editor.buttonReleased (button, textView.getCursorIndex (SLocation (x, y)));
  if (clicks.clickCount == 1)
  {
    editor.multiClicked (button, textView.getCursorIndex (SLocation (x, y)), 2);
  }
  else if (clicks.clickCount == 2)
  {
    editor.multiClicked (button, textView.getCursorIndex (SLocation (x, y)), 3);
  }
}

void
STextEdit::buttonDragged (SWindow * w, int button, int x, int y)
{
  if (yuditInput) yuditInput->clear();
  window->commitInputMethod();
  editor.buttonDragged (button, textView.getCursorIndex (SLocation (x, y)));
}

/**
 * Keyboard focus lost.
 * In some environment this is called *after* the 
 * mouse clicked is called. This results in some serious problem:
 * you can not select text with the mouse if 
 * if (isSelecting())
 * {
 *    endSelect ();
 * }
 * is present.
 */
void 
STextEdit::lostKeyboardFocus (SWindow* w)
{
  focused = false;
  caret.animate (false);
  for (unsigned int i=0; i<listeners.size(); i++)
  {
    listeners[i]->focusChanged (this, false);
  }
}

/**
 * Text has gone from clipboard. 
 */ 
void
STextEdit::lostClipSelection (SWindow* w)
{
  deselectText();
}

/**
 * keyboard focus lost.
 */
bool 
STextEdit::gainedKeyboardFocus (SWindow* w)
{
  focused = true;
/*
  fprintf (stderr, "STextEdit::gainedKeyboardFocus %lx editable %d\n",
        (unsigned long) this, editable);
*/
  if (editable)
  {
    caret.animate (true);
  }
  for (unsigned int i=0; i<listeners.size(); i++)
  {
    listeners[i]->focusChanged (this, true);
  }
  return true;
}


/**
 * Resize the component
 * @param d is the new size
 */
void 
STextEdit::resize(const SDimension& d)
{
  if (getSize() == d) return;
  SPanel::resize (d);
  border.resize (d);
  resizeTextView(false);
}

void
STextEdit::resizeTextView(bool scroll)
{
  SDimension td = size;

  if (size.width + 2 * SS_LEFT_MARGIN> border.getBorderSize().width * 2)
  {
    td.width  = size.width - border.getBorderSize().width * 2 - 2 * SS_LEFT_MARGIN;
  }
  if (size.height > border.getBorderSize().height * 2)
  {
    td.height  = size.height - border.getBorderSize().height * 2;
    if (td.height > statusHeight)
    {
      td.height  = td.height - statusHeight;
    }
    if (scroll) 
    {
      window->redraw (true, 0, 0, size.width, size.height);
    }
  }
  clip (true);
  textView.resize (td);
  resized = true;
}

/**
 * Move the component
 * @param l is the new location
 */
void 
STextEdit::move(const SLocation& l)
{
  SPanel::move (l);
}

/**
 * getPreferredSize.
 * This is calculated form the preferred size of the textView and
 * adding the border size to it.
 */
const SDimension&
STextEdit::getPreferredSize()
{
  preferredSize = textView.getPreferredSize();
  SDimension d = border.getBorderSize();
  preferredSize.width += d.width * 2;
  preferredSize.height += d.height * 2;
  return preferredSize;
}

/**
 * Gain keyboard focus
 * @return true if focus could be gained.
 */
void
STextEdit::setFocus ()
{
  window->getKeyboardFocus();
}

/**
 * Scroll the area up or down.
 * Move text up -
 * Move text down +
 * @param value is positive if up.
 * @param value is negative is down.
 */
void
STextEdit::scrollVertical (int value, bool notify)
{
  if (value ==0) return;

  int eheight = (int)size.height - (int)2 * border.getBorderSize().height;
  if (eheight <= 0) return;
  int ewidth = (int)size.width - (int)2 * border.getBorderSize().width;
  if (ewidth <= 0) return;
  SLocation v = textView.getViewPort();
  v.y = v.y + value;
  textView.setViewPort (v);
  /* redraw whole thing */
  if (window->isDoubleBufferEnabled ())
  {
    window->redraw (true,
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height,
       ewidth, eheight);
  }
  /* outof range. */
  else if ((value < 0 && -value >= eheight) || (value > 0 && value >= eheight))
  {
    window->redraw (true,
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height,
       ewidth, eheight);
  }
  /* copy the area and redraw the rest */
  else if (value < 0)
  {
    value = -value;
    /* copy up */
    window->copy ((int) border.getBorderSize().width, 
        value + (int) border.getBorderSize().height,
        ewidth, eheight-value, 
        border.getBorderSize().width,
        border.getBorderSize().height);
    /* redraw down 5 + 4 oeverdraw */
    window->redraw (true,
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height + eheight - value - 5,
       ewidth,
       value + 9);
       
  }
  else
  {
    /* copy down */
    window->copy ((int) border.getBorderSize().width, 
        (int) border.getBorderSize().height,
        ewidth, eheight-value, 
        border.getBorderSize().width,
        (int) border.getBorderSize().height + value);
    /* redraw up */
    window->redraw (true,
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height,
       ewidth,
       value + 4);
  }
  if (notify)
  {
    notifySlider();
  }
}

void
STextEdit::scrollHorizontal (int value, bool notify)
{
  if (value ==0) return;
  SLocation v = textView.getViewPort();
  v.x = v.x + value;
  textView.setViewPort (v);

  int eheight = (int)size.height - (int)2 * border.getBorderSize().height;
  if (eheight <= 0) return;
  int ewidth = (int)size.width - (int)2 * border.getBorderSize().width 
       + 2 * SS_LEFT_MARGIN;
  if (ewidth <= 0) return;
  /* redraw whole thing */
  if (window->isDoubleBufferEnabled ())
  {
    window->redraw (true,
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height,
       ewidth, eheight);
  }
  /* outof range. */
  else if ((value < 0 && -value >= ewidth) || (value > 0 && value >= ewidth))
  {
    window->redraw (true,
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height,
       ewidth, eheight);
  }
  /* copy the area and redraw the rest */
  else if (value < 0)
  {
    value = -value;
    /* copy up */
    window->copy (value + (int) border.getBorderSize().width, 
        (int) border.getBorderSize().height,
        ewidth-value, eheight, 
        border.getBorderSize().width,
        border.getBorderSize().height);
    /* redraw down 2+2 overdraw */
    window->redraw (true,
       (int) ewidth-value - 2,
       (int) border.getBorderSize().height,
       value + 4,
       eheight);
  }
  else
  {
    /* copy down */
    window->copy ((int) border.getBorderSize().width, 
        (int) border.getBorderSize().height,
        ewidth-value, eheight, 
        border.getBorderSize().width + value,
        (int) border.getBorderSize().height);
    /* redraw up + 4 overdraw */
    window->redraw (true,
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height,
       value + 4,
       ewidth);
  }
  if (notify)
  {
    notifySlider();
  }
}

/**
 * Shift the text, and caret so that caret is always visible.
 */
void
STextEdit::setCaretLocationVisible ()
{
  if (isMultiline())
  {
    setCaretVisibleVertical ();
  }
  else
  {
    setCaretVisibleHorizontal ();
  }
}

/**
 * Do the vertical part of caret visible trickies.
 */
void
STextEdit::setCaretVisibleVertical()
{
/*
 *  I dont like this type of scrolling... It also
 *  has a bug ath the end of the page.
 */
  SDimension ts = textView.getSize();
  /* It happens before resize. */
  if (ts.height==0) return;

  SLocation tb = textView.getLocation();

  /* t=text-widget c=caret tf=text-full*/
  SCursorIndex ci = caret.getCursorIndex();
  SLocation cl = textView.getCursorLocation(ci);


  /* this is negative for scrolls to left */
  SLocation viewp = textView.getViewPort();
  /* do we need to move ? */
  int movey = 0;
  if (cl.y + (int)textView.lineHeight >  (int)ts.height + tb.y)
  {
    /* negative - left */
    movey = (int) ts.height + tb.y - cl.y - (int) textView.lineHeight;
  }
  else if (cl.y < tb.y)
  {
    /* positive - right */
    movey =  tb.y - cl.y;
  }
  if (viewp.y + movey > 0) movey = -viewp.y;
  /* fits one page */
  if (viewp.y + movey < -((int)textView.getDocumentHeight() - (int)ts.height))
  {
    movey = -(int)textView.getDocumentHeight()-viewp.y+(int)ts.height;
  }
  /* check overscroll */
  if (textView.getDocumentHeight() <= ts.height)
  {
    movey = -viewp.y;
  }

  if (movey!=0)
  {
    scrollVertical (movey, true);
  }

}

/**
 * Do the vertical part of caret visible trickies.
 */
void
STextEdit::centerCaretVertical()
{
  /* t=text-widget c=caret tf=text-full*/
  SLocation cb = caret.getLocation();

  int delta = ((int)(textView.getSize().height/2))-cb.y;
  /* it would make the first line appear down below */
  if (textView.getViewPort().y + delta >= 0)
  {
    delta = - textView.getViewPort().y;
  }
  /* it would make the last line appear up below */
  else if (textView.getViewPort().y + delta
       + (int)textView.getDocumentHeight() < (int)textView.getSize().height)
  {
    delta = ((int)textView.getSize().height) - textView.getViewPort().y 
       - (int)textView.getDocumentHeight();
    /* one page text may over-request. */
    if (textView.getViewPort().y + delta >= 0)
    {
      delta = 0;
    }
  }
  if (delta != 0)
  {
    scrollVertical (delta, true);
    /* move the caret silently */
    updateCaretPosition (); 
    updateCaretLocation (false); 
    window->setInputMethodProperties (getProperties());
  }
  return;
}

/**
 * Do the horizontal part of caret visible trickies.
 */
void
STextEdit::setCaretVisibleHorizontal()
{
  SDimension ts = textView.getSize();
  /* It happens before resize. */
  if (ts.width == 0) return;
  SLocation tb = textView.getLocation();

  /* t=text-widget c=caret tf=text-full*/
  SCursorIndex ci = caret.getCursorIndex();
  SLocation cl = textView.getCursorLocation(ci);
  bool islr = textView.textData.isLR(ci.textIndex.line);

  /* this is negative for scrolls to left */
  SLocation viewp = textView.getViewPort();
  /* do we need to move ? */
  int movex = 0;
  if (islr)
  {
    if (cl.x >=  8 * (int)ts.width/ 10 + tb.x)
    {
      /* negative - left */
      movex = (6 * (int) ts.width / 10  + tb.x) - cl.x;
    }
    else if (cl.x <=  2 * ((int)ts.width) / 10 + tb.x)
    {
      /* positive - right */
      movex =  (4 * ((int) ts.width) / 10  + tb.x) - cl.x;
    }
    if (viewp.x + movex > 0) movex = -viewp.x;

    if (movex!=0)
    {
      scrollHorizontal (movex, true);
    }
  }
  else /* no support for scrolling yet */
  {
#if 0
/*
    if (cl.x <=  6 * (int)ts.width/ 10 + tb.x)
    {
      movex = (8 * (int) ts.width / 10  + tb.x) - cl.x;
    }
    else if (cl.x >=  4 * ((int)ts.width) / 10 + tb.x)
    {
      movex =  (2 * ((int) ts.width) / 10  + tb.x) - cl.x;
    }
    if (viewp.x + movex > 0) movex = -viewp.x;

    if (movex!=0)
    {
      scrollHorizontal (movex, true);
    }
*/
#endif
    if (viewp.x != 0)
    {
      scrollHorizontal (-viewp.x, true);
    }
  }

  return;
}


/**
 * If show <- newline characters
 * @param lineend is true if lineend is shown.
 */
void
STextEdit::setLineEndMark (bool lineend)
{
  textView.setLineEndMark (lineend);
  resized = true;
}

/**
 * Is new line shown?
 * @return true if newline characters are shown.
 */
bool
STextEdit::getLineEndMark () const
{
  return textView.getLineEndMark ();
}


/**
 * Chose between fonts.
 * @param fnt is the font to chose.
 * @param size is the size of the font or zero if old size is used.
 */
void
STextEdit::setFont (const SString& fnt, double _size)
{
  textView.setFont (fnt, _size);
  /* extents may change */
  notifySlider();
  caret.resize (SDimension(textView.lineHeight, textView.lineHeight));
  resized = true;
  updateCaretLocation ();
  redrawInternal ();
}

/**
 * Set the size of the font.
 */
void
STextEdit::setFontSize (double _size)
{
  textView.setFontSize (_size);
  /* extents may change */
  notifySlider();
  caret.resize (SDimension(textView.lineHeight, textView.lineHeight));
  resized = true;
  updateCaretLocation ();
  redrawInternal ();
}

/**
 * redraw the whole thing 
 */
void
STextEdit::redrawInternal ()
{
  int eheight = (int)size.height - (int)2 * border.getBorderSize().height;
  if (eheight <= 0) return;
  int ewidth = (int)size.width - (int)2 * border.getBorderSize().width 
       + 2 * SS_LEFT_MARGIN;

  resized = true;

  window->redraw (true, (int) border.getBorderSize().width ,
    (int) border.getBorderSize().height, ewidth + 2 * SS_LEFT_MARGIN,
     eheight);
}


/*----------- This is the implementation of SEditorIF ---------------*/

/**
 * Chose between fonts.
 * @param fnt is the font to chose.
 */
void
STextEdit::setFont (const SString& fnt)
{
  setFont (fnt, 0.0);
}

/**
 * Chose between input methods.
 * @patam im is the input to chose.
 */
void
STextEdit::setInputMethod (const SString& _im)
{
  //fprintf (stderr, "Starting -----------%*.*s\n", SSARGS(im));
  /* Stop current xinput method. */
  inputMethod = _im;
  if (!window->isVisible())
  {
     imWaiting = inputMethod;
     return;
  }

  imWaiting.clear(); 
  unsigned int oldH = statusHeight;
  statusHeight = 0;
  SDimension d = getSize();

  window->commitInputMethod();

  if (yuditInput)
  {
    yuditInput->clear();
    delete yuditInput;
    yuditInput=0;;
  }
  else
  {
    window->commitInputMethod();
  }
  if (inputMethod == SS_DEFAULT_IM)
  {
    if (oldH != statusHeight) resizeTextView (true);
    window->startInputMethod ("x-ascii", SProperties(),this);
  }
  else if (inputMethod == SS_KINPUT2_IM)
  {
    if (!SS_NO_STATUS_UNDER) {
        statusHeight = (textView.lineHeight < 24) ? 24: textView.lineHeight;
        resizeTextView (true);
    }
    if (!window->startInputMethod ("_JAPANESE_CONVERSION",getProperties(),this))
    {
      fprintf (stderr, "Can not start kinput2 (_JAPANESE_CONVERSION) input method.\n");
    }
    if (SS_NO_STATUS_UNDER) {
        return;
    }
    if (window->getInputMethod()=="x-utf-8" 
     || window->getInputMethod() == "x-ascii" 
     || window->getInputMethod() == ""
     || ximProperties.get ("InputStyle")  == 0
     || (ximProperties["InputStyle"] != "preedit-under-status-under"
         && ximProperties["InputStyle"] != "preedit-over-status-under")) 
    {
       statusHeight = 0;
       resizeTextView (true);
    }
  }
  else if (inputMethod == SS_WINDOWS_IM)
  {
    if (!SS_NO_STATUS_UNDER) {
        statusHeight = (textView.lineHeight < 24) ? 24: textView.lineHeight;
        resizeTextView (true);
    }
    if (!window->startInputMethod ("_JAPANESE_CONVERSION",getProperties(),this))
    {
      fprintf (stderr, "Can not start Window input method.\n");
    }
    if (SS_NO_STATUS_UNDER) {
        return;
    }
    if (window->getInputMethod()=="x-utf-8" 
     || window->getInputMethod() == "x-ascii" 
     || window->getInputMethod() == ""
     || ximProperties.get ("InputStyle")  == 0
     || (ximProperties["InputStyle"] != "preedit-under-status-under"
         && ximProperties["InputStyle"] != "preedit-over-status-under")) 
    {
       statusHeight = 0;
       resizeTextView (true);
    }
  }
  /* X Input method. */
  else  if (inputMethod.size() > 2 
      && (inputMethod[0] == 'x' || inputMethod[0] == 'X') 
      && (inputMethod[1] == '-' || inputMethod[1] == '-') )
  {
    if (!SS_NO_STATUS_UNDER) {
        statusHeight = (textView.lineHeight < 24) ? 24: textView.lineHeight;
        resizeTextView (true);
    }
    if (!window->startInputMethod (inputMethod, getProperties(),this))
    {
      fprintf (stderr, "Can not start %*.*s input method.\n", 
          SSARGS(inputMethod));
    }
    
    if (SS_NO_STATUS_UNDER) {
        return;
    }
    if (window->getInputMethod()=="x-utf-8" 
     || window->getInputMethod() == "x-ascii" 
     || window->getInputMethod() == ""
     || window->hasStatusArea() == false
     || ximProperties.get ("InputStyle")  == 0
     || (ximProperties["InputStyle"] != "preedit-under-status-under"
         && ximProperties["InputStyle"] != "preedit-over-status-under")) 
    {
       statusHeight = 0;
       resizeTextView (true);
    }
  }
  else
  {
    if (oldH != statusHeight) resizeTextView (true);
    window->startInputMethod ("x-ascii", SProperties(),this);
    yuditInput = new SYuditInput (inputMethod, this, &editor);
  }
}

/**
 * Get text from clipboard and put it here.
 */
void
STextEdit::insertClipboardText ()
{
  SString str = window->getClipUTF8();
  if (str.size()==0)
  {
    return;
  }
  if (isMultiline())
  {
    textView.textData.insert (str);
    textView.textData.fireEvent();

    updateCaretPosition ();
    updateCaretLocation ();

    fireTextChanged ();
    return;
  }
  /* multiline */
  SEncoder oc;
  SV_UCS4 s4 = oc.decode (str);
  SV_UCS4 vle;
  for (unsigned int i=0; i<s4.size(); i++)
  {
    SS_UCS4 u = s4[i];
    if (u==SD_CD_CR || u==SD_CD_LF || u==SD_CD_LS 
       || u==SD_CD_PS || u==SD_CD_FF) break;
    vle.append ((SS_UCS4) u);
  }
  textView.textData.insert (oc.encode(vle));
  textView.textData.fireEvent();
  /* move to the logical end of previous character */

  updateCaretPosition ();
  updateCaretLocation ();

  fireTextChanged ();
  return;
}


/**
 * Put selection on clipboard.
 * Strictly speaking this is not and EditorIM
 */
void
STextEdit::autoClipSelection ()
{
  if (autoClipboard) clipSelect();
  
}

void
STextEdit::clipSelect ()
{
  if (textView.isHideText()) return;

  if (startSelection == endSelection)
  {
    return;
  }
  SString str = textView.textData.getText (startSelection, endSelection);
  window->putClipUTF8 (str);
}

// Return an empty string if there is no selected text, or
// return the text currently selected.
SString
STextEdit::getSelectedText ()
{
  if (startSelection == endSelection)
  {
    return SString("");
  }
  SString str = textView.textData.getText (startSelection, endSelection);
  return SString (str);
}

/**
 * Dirty text may conta8ing newline characters and the line. 
 * This routine is calling insertText for multiline widget.
 * For a signle line widget it is chopping the text at newline
 * and calls the textEntered callback.
 *
 * @param str is an utf8 string that may contain line-breaks as well.
 * @return the index to the previous insertion point.
 */
void
STextEdit::insertDirtyText (const SString& str, bool embed)
{
  if (!editable)
  {
    return ;
  }
  if (selecting)
  {
    deselectText();
  }
  if (isMultiline())
  {
    insertText (str, embed);
    return;
  }
 
  int ind0 = str.find (SS_LB_DOS); 
  int ind1 = str.find (SS_LB_MAC); 
  int ind2 = str.find (SS_LB_UNIX); 
  int ind3 = str.find (SS_LB_PS); 
  int ind4 = str.find (SS_LB_LS); 
  int ind5 = str.find (SS_LB_FF); 

  if (ind0 < 0) ind0 = str.size();
  if (ind1 < 0) ind1 = str.size();
  if (ind2 < 0) ind2 = str.size();
  if (ind3 < 0) ind3 = str.size();
  if (ind4 < 0) ind4 = str.size();
  if (ind5 < 0) ind5 = str.size();

  int smallest = ind0;
  if (ind1 < smallest) smallest = ind1;
  if (ind2 < smallest) smallest = ind2;
  if (ind3 < smallest) smallest = ind3;
  if (ind4 < smallest) smallest = ind4;
  if (ind5 < smallest) smallest = ind5;

  if (smallest < (int)str.size())
  {
    SString  s = str;
    s.truncate ((int)smallest);
    insertText (s, embed);
    for (unsigned i=0; i<listeners.size(); i++)
    {
      listeners[i]->textEntered (this);
    }
    return;
  }
  insertText (str, embed);
  return;
}

/**
 * Insert the text in str. The text won't be affected by keys.
 * @param str is an utf8 string that may contain line-breaks as well.
 * @param embed is true if embed marks need to be added.
 * undo, paste does not need them.
 * @return the index to the previous insertion point.
 */
void
STextEdit::insertText (const SString& _str, bool embed)
{
  /* find out what text it is */
  SEmbedState s = caret.getEmbedState();

  SString str = _str;
  if (embed && s.getExplicitLevel()!=0)
  {
    SV_UCS4 ucs = s.getEmbeddingMarks(0); 
    unsigned int i = ucs.size();;
    /* we have to go the opposite order to get the right order... */
    while (i>0)
    {
      i--;
      switch (ucs[i])
      {
      case SD_CD_LRO:
        str.insert(0, SS_LB_LRO);
        str.append (SS_LB_PDF);
        break;
      case SD_CD_RLO:
        str.insert(0, SS_LB_RLO);
        str.append (SS_LB_PDF);
        break;
      case SD_CD_LRE:
        str.insert(0, SS_LB_LRE);
        str.append (SS_LB_PDF);
        break;
      case SD_CD_RLE:
        str.insert(0, SS_LB_RLE);
        str.append (SS_LB_PDF);
        break;
      }
    }
  }
  textView.textData.insert (str);
  textView.textData.fireEvent ();

  /* move to the logical end of previous character */
  updateCaretPosition ();
  updateCaretLocation ();

  fireTextChanged ();
  return;
}

/**
 * Scroll  it up.
 */
void
STextEdit::pageUp ()
{
  if ((textView.lineHeight == 0 && !isMultiline()) 
     || textView.textData.size ()==0)
  {
    return;
  }
  unsigned int h0 = textView.getDocumentHeight(); 
  SCursorIndex indexNow = caret.getCursorIndex();
  SLocation l0 = textView.getCursorLocation (indexNow);
  /* half pages */
  int decrement = ((int)textView.getSize().height/2)/textView.lineHeight + 1;
  decrement = decrement * textView.lineHeight;
  SLocation l1 = SLocation (0, l0.y - decrement);
  if (l0.y-l1.y > -textView.getViewPort().y)
  {
    l1.y = l0.y + textView.getViewPort().y;
  }

  int scrollVle = l0.y - l1.y;
  if (scrollVle == 0)
  {
    /* size changed - redraw whole window */
    unsigned int h1 = textView.getDocumentHeight(); 
    if (h1 != h0)
    {
      redrawInternal();
    }
    return;
  }

  caret.redraw();
  scrollVertical (scrollVle, true);
  SCursorIndex indexNext = textView.getCursorIndex (l0);

  indexNext.textIndex.index = column;
  if (scrollVle <= (int)textView.lineHeight && indexNext.textIndex.line > 1)
  {
    indexNext.textIndex.line -= 1;
  }

  if (selecting)
  {
    selectText (indexNext);
  }
  else
  {
    deselectText();
    textView.textData.move (indexNext.textIndex);
    textView.textData.fireEvent();
    updateCaretPosition (false);
    updateCaretLocation ();
  }
  /* size changed - redraw whole window */
  unsigned int h1 = textView.getDocumentHeight(); 
  if (h1 != h0)
  {
    redrawInternal();
  }
  return;
}

/**
 * Scroll  it down.
 */
void
STextEdit::pageDown ()
{
  if ((textView.lineHeight == 0 && !isMultiline()) 
     || textView.textData.size ()==0)
  {
    return;
  }
  SCursorIndex indexNow = caret.getCursorIndex();
  SLocation l0 = textView.getCursorLocation (indexNow);
  /* half pages */
  int increment = ((int)textView.getSize().height/2)/textView.lineHeight + 1;
  increment = increment * textView.lineHeight;
  SLocation l1 = SLocation (0, l0.y + increment);
  int docHeight = (int) textView.getDocumentHeight();

  /* so what if we scroll */
  int scrollVle = l1.y - l0.y;
  if (scrollVle - textView.getViewPort().y  > docHeight 
       - (int) textView.getSize().height)
  {
    scrollVle = docHeight + textView.getViewPort().y 
          - (int) textView.getSize().height;
  }
  if (scrollVle ==0) return;
  if (textView.getViewPort().y -scrollVle > 0) return;

  caret.redraw();
  scrollVertical (-scrollVle, true);
  SCursorIndex indexNext = textView.getCursorIndex (l0);
  indexNext.textIndex.index = column;
  if (scrollVle <= (int)textView.lineHeight 
         && textView.textData.size() > indexNext.textIndex.line+1)
  {
    indexNext.textIndex.line += 1;
  }

  if (selecting)
  {
    selectText (indexNext);
  }
  else
  {
    deselectText();
    textView.textData.move (indexNext.textIndex);
    textView.textData.fireEvent();
    updateCaretPosition (false);
    updateCaretLocation ();
  }
  return;
}

/**
 * Move caret up. if possible.
 */
void
STextEdit::caretUp ()
{
  unsigned int h0 = textView.getDocumentHeight(); 
  SCursorIndex indexNow = caret.getCursorIndex ();
  if (indexNow.textIndex.line==0)
  {
    historyUp ();
    return;
  }
  SCursorIndex ti = SCursorIndex (indexNow.textIndex.line-1, column);
  unsigned int sz = textView.textData.size (ti.textIndex.line);
  if (sz < ti.textIndex.index) ti.textIndex.index = sz;
  if (sz > 0 && ti.textIndex.index == sz && textView.textData.isProperLine (ti.textIndex.line))
  {
    ti.textIndex.index--;
  }
  if (selecting)
  {
    selectText (ti);
  }
  else
  {
    deselectText();
    textView.textData.move (ti.textIndex);
    textView.textData.fireEvent();
    updateCaretPosition (false);
    updateCaretLocation ();
  }
  /* size changed - redraw whole window */
  unsigned int h1 = textView.getDocumentHeight(); 
  if (h1 != h0)
  {
    redrawInternal();
  }
  return;
}

/**
 * Move caret downwards. if possible.
 */
void
STextEdit::caretDown ()
{
  SCursorIndex indexNow = caret.getCursorIndex ();
  if (!textView.textData.isProperLine (indexNow.textIndex.line) 
       || indexNow.textIndex.line + 1 > textView.textData.size())
  {
    historyDown ();
    return;
  }
  SCursorIndex ti = SCursorIndex (indexNow.textIndex.line+1, column);
  unsigned int sz = textView.textData.size (ti.textIndex.line);
  if (sz < ti.textIndex.index) ti.textIndex.index = sz;
  if (sz > 0 && ti.textIndex.index == sz && textView.textData.isProperLine (ti.textIndex.line))
  {
    ti.textIndex.index--;
  }
  if (selecting)
  {
    selectText (ti);
  }
  else
  {
    deselectText();
    textView.textData.move (ti.textIndex);
    textView.textData.fireEvent();
//fprintf (stderr, "Y1 column=%u\n", column);
    updateCaretPosition (false);
    updateCaretLocation ();
  }
  historyDown ();
  return;
}

/**
 * Move caret to the left. if possible.
 */
void
STextEdit::caretLeft ()
{
  SCursorIndex indexNow = caret.getCursorIndex();
  SCursorIndex indexPrev = textView.leftOf (indexNow);

  if (indexPrev == indexNow)
  {
    return;
  }

  if (selecting)
  {
     selectText (indexPrev);
  }
  else
  {
    deselectText();
    textView.textData.move (indexPrev.getTextIndex());
    textView.textData.fireEvent();
    setCursorIndex (indexPrev);
  }
  STextIndex ndx = textView.textData.getTextIndex();
  column = ndx.index;
  return;
}


/**
 * Move caret to the right. if possible.
 */
void
STextEdit::caretRight ()
{
  SCursorIndex indexNow = caret.getCursorIndex();
  SCursorIndex indexNext = textView.rightOf (indexNow);

  if (indexNext == indexNow) 
  {
    return;
  }
  if (selecting)
  {
    selectText (indexNext);
  }
  else 
  {
    deselectText();
    textView.textData.move (indexNext.getTextIndex());
    textView.textData.fireEvent();
    setCursorIndex (indexNext);
  } 
  STextIndex ndx = textView.textData.getTextIndex();
  column = ndx.index;
  return;
}

/**
 * Put state machine into a 'selecting' state.
 */
void
STextEdit::startSelect ()
{
  deselectText();
  startSelection = textView.textData.getTextIndex();
  endSelection = startSelection;
  selecting = true;
}

/**
 * End the selection.
 */
void
STextEdit::endSelect ()
{
  selecting = false;
  if (startSelection != endSelection)
  {
     autoClipSelection();
  }
}

/**
 * Drag select the text.
 * @param till indicates new endselction.
 */
void
STextEdit::selectText (const SCursorIndex& _till)
{
  if (!selecting)
  {
    return;
  }
  STextIndex now = _till.getTextIndex();

  if (now == endSelection)
  {
    setCursorIndex(_till);
    return;
  }


  /* make sure selection doesn't exceed boundaries 
   (for example, because of pressing the END key). 
   Addition by Maarten van Gompel <proycon@anaproy.homeip.net> */
  unsigned int maxsize = textView.textData.size(now.line);
  if (textView.textData.isProperLine(now.line)) maxsize--;
  if (now.index > maxsize) 
  {
    STextIndex maxindex(now.line, maxsize);
    now = maxindex;  
  }

  //textIndex = endSelection;
  if (now > endSelection)
  {
    /* crossed startSelection - delete */
    textView.textData.move (endSelection);
    if (endSelection < startSelection)
    {
      if (now < startSelection)
      {
        textView.textData.select (now, false);
      }
      else
      {
        textView.textData.select (startSelection, false);
      }
    }
    textView.textData.select (now, true);
  }
  else /* moving down */
  {
    /* crossed startSelection - delete */
    textView.textData.move (endSelection);
    if (endSelection > startSelection)
    {
       if (now > startSelection)
       {
         textView.textData.select (now, false);
       }
       else
       {
         textView.textData.select (startSelection, false);
       }
    }
    textView.textData.select (now, true);
  }
  endSelection = now;
  textView.textData.fireEvent ();
  textView.textData.move (now);
  setCursorIndex(_till);
} 

/**
 * Wherever the current index is, select a word there.
 */
void
STextEdit::selectWord ()
{
  if (selecting) return;

  deselectText();

  STextIndex index = textView.textData.getTextIndex();
  if (index.line == textView.textData.size())
  {
     return;
  }
  unsigned int start;
  for (start=index.index; start>0; start--)
  {
    //the new function isDelimiter is now used instead of the
    //isWhitespace function, so words are now carefully being
    //selected based on hundreds of possible unicode delimiters.
    //addition by Maarten van Gompel <proycon@anaproy.homeip.net>
    if (textView.textData.isDelimiter (STextIndex (index.line, start-1)))
    {
      break;
    }
  }
  unsigned int end;
  for (end=index.index; end<textView.textData.size(index.line); end++)
  {
    if (textView.textData.isDelimiter (STextIndex (index.line, end)))
    {
      break;
    }
  }
  if (start != end)
  {
    startSelection = (STextIndex (index.line, start));
    endSelection = (STextIndex (index.line, end));
    textView.textData.move (startSelection);
    textView.textData.select (endSelection);
    textView.textData.fireEvent ();
    textView.textData.move (index);
    /* caret did not move */
    autoClipSelection();
  }
}

/**
 * Wherever the current index is, select that line.
 */
void
STextEdit::selectLine ()
{
  if (selecting) return;

  deselectText();
  STextIndex index = textView.textData.getTextIndex();
  //STextIndex nextIndex = getIndexAfterLineBreak();
  STextIndex nextIndex = STextIndex (index.line, 
     textView.textData.size(index.line));
  if (textView.textData.isProperLine (index.line) && nextIndex.index > 0)
  {
     nextIndex.index--;
  }
  if (index==nextIndex) return;
  startSelection = STextIndex(index.line, 0);
  endSelection = nextIndex;
  textView.textData.move (startSelection);
  textView.textData.select (endSelection);
  textView.textData.fireEvent ();
  /* caret did not move */
  textView.textData.move (index);
  autoClipSelection();
}

/**
 * get the index after new line mark. that is already on
 * the next line, or if there is no new line, this is the same line.
 */
STextIndex
STextEdit::getIndexAfterLineBreak ()
{
  STextIndex index = textView.textData.getTextIndex();
  if (index.line == textView.textData.size())
  {
    return STextIndex(index);
  }
  if (textView.textData.isProperLine (index.line))
  {
    index.line = index.line+1;
    index.index = 0;
  }
  else
  {
    index.index = textView.textData.size(index.line);
  }
  return STextIndex(index);
}

/**
 * The text hoghlighted should be unhighlighted.
 */
void
STextEdit::deselectText ()
{
  selecting = false;
  if (startSelection == endSelection)
  {
    startSelection = textView.textData.getTextIndex();
    endSelection = textView.textData.getTextIndex();
    return;
  }
  STextIndex now = textView.textData.getTextIndex();

  textView.textData.move (startSelection);
  textView.textData.select (endSelection, false);
  textView.textData.fireEvent();
  /* caret did not move */
  textView.textData.move (now);

  startSelection = now;
  endSelection = now;
}

/**
 * getSelectedIndex 
 * @param hi is true if we want the upper index.
 */
STextIndex
STextEdit::getSelectedIndex (bool hi)
{
  if (startSelection < endSelection)
  {
    return hi ? endSelection : startSelection;
  }
  else
  {
    return hi ? startSelection : startSelection;
  }
}

/**
 * Remove the selected text.
 * Return the text that was remove - if any.
 */
SString
STextEdit::eraseSelectedText ()
{
  if (!editable) return (SString(""));

  selecting = false;
  if (startSelection == endSelection)
  {
    startSelection = textView.textData.getTextIndex();
    endSelection = textView.textData.getTextIndex();
    return SString("");
  }
  textView.textData.move (startSelection);
  SString ret = textView.textData.getText (endSelection);
  textView.textData.remove (endSelection);
  textView.textData.fireEvent();

  startSelection = textView.textData.getTextIndex();
  endSelection = textView.textData.getTextIndex();
  fireTextChanged ();
  updateCaretPosition ();
  updateCaretLocation ();
  return SString(ret);
}


/**
 * erase a glyph - that is one glyph back.
 * text deleted are always <- direction.
 * in an LR context this should be backspace.
 * @return the text erased (if any)
 */
SString
STextEdit::backspace ()
{
  if (selecting || !editable) return SString("");

  STextIndex indexNow = textView.textData.getTextIndex ();
  STextIndex indexPrev = textView.textData.getTextIndex (-1, true);
  if (indexPrev == indexNow) return SString("");
  SString ret = textView.textData.getText (indexPrev);
  textView.textData.remove (indexPrev);
  // fireTextDataEvent is used now. textView.textData.fireEvent();
  textView.textData.fireEvent ();
  fireTextChanged ();
  updateCaretPosition ();
  updateCaretLocation ();
  return SString(ret);
}

/**
 * Delete text  - that is one position forward.
 * text deleted are always -> direction.
 * in an LR context this should be delete.
 * @return the text deleted (if any)
 */
SString
STextEdit::erase ()
{
  if (selecting || !editable) return (SString(""));
  
  STextIndex indexNow = textView.textData.getTextIndex ();
  STextIndex indexPrev = textView.textData.getTextIndex (-1, true);
  STextIndex indexNext = textView.textData.getTextIndex (1, true);
  if (indexNext == indexNow) return SString("");
  SString ret = textView.textData.getText (indexNext);
  textView.textData.remove (indexNext);
  textView.textData.fireEvent();
  fireTextChanged ();
  updateCaretPosition ();
  updateCaretLocation ();
  return SString(ret);
}

/**
 * Remove composing letter to the point ponterd away from with cursor 
 */
SS_UCS4
STextEdit::removeComposing()
{
  STextIndex afrom = getCaretArrowFrom();
  /* saving it */
  STextIndex before = textView.textData.getTextIndex();
  STextIndex ci(afrom.line, afrom.index);
  textView.textData.move (ci);

  SS_UCS4 ret = textView.textData.removeComposing (true);

  textView.textData.move (before);
  if (ret)
  {
    textView.textData.fireEvent();
    updateCaretLocation();
    fireTextChanged ();
  }
  return ret;
}

/**
 * Add composing letter to the point ponterd away from with cursor 
 */
bool
STextEdit::addComposing(SS_UCS4 c)
{
  STextIndex afrom = getCaretArrowFrom();

  /* saving it */
  STextIndex before = textView.textData.getTextIndex();
  STextIndex ci(afrom.line, afrom.index);
  textView.textData.move (ci);

  bool ret = textView.textData.addComposing (c, true);

  textView.textData.move (before);
  if (ret)
  {
    textView.textData.fireEvent();
    updateCaretLocation();
    fireTextChanged ();
  }
  return ret;
}

const SCursorIndex&
STextEdit::getCursorIndex () const
{
  return caret.getCursorIndex();
}

/**
 * We should put away the focus to somewhere else.
 */
void
STextEdit::focusOut ()
{
  for (unsigned int i=0; i<listeners.size(); i++)
  {
    listeners[i]->focusOutRequest (this);
  }
}

void 
STextEdit::fireTextChanged ()
{
  for (unsigned int i=0; i<listeners.size(); i++)
  {
    listeners[i]->textChanged (this);
  }
}

void 
STextEdit::fireCaretMoved ()
{
  SCursorIndex cin = caret.getCursorIndex();
  for (unsigned int i=0; i<listeners.size(); i++)
  {
    listeners[i]->caretMoved (this, cin.textIndex.line, 
        cin.textIndex.index, cin.before);
  }
}

void
STextEdit::setEditable (bool _editable)
{
  if (editable == _editable) return;
  editable = _editable;
  caret.animate (editable && focused);
  //textView.setEditable(_editable);
}

bool
STextEdit::isEditable () const
{
  return editable;
}
bool
STextEdit::isSelecting () const
{
  return selecting;
}

/**
 * Insert text from pre-edit, bypassing undo
 */
void
STextEdit::insertPreEditText (const SString& str, SPreEditor::SStyle style)
{
  STextIndex before = textView.textData.getTextIndex ();
  /* this will move caret */
  insertDirtyText (str);
  STextIndex now = textView.textData.getTextIndex ();
  switch (style)
  {
  case SPreEditor::Style_Selected:
    textView.textData.select (before);
    break;
  case SPreEditor::Style_Default:
  default:
    textView.textData.underline (before);
  }
  textView.textData.move (now);
  textView.textData.fireEvent ();
  return;
}

/**
 * erase text between this and 'till'.
 * @return the erased text.
 */
SString
STextEdit::eraseText (const STextIndex& till)
{
  if (selecting || !editable)
  { 
    //fprintf (stderr, "selecting=%d editable=%d\n", 
    //     (int) selecting, (int) editable);
    return SString("");
  }
  STextIndex index = textView.textData.getTextIndex();
  if (index == till)
  {
    //fprintf (stderr, "index=%u %u till=%u %u\n", 
    //     index.line, index.index, till.line, till.index);
    return SString("");
  }

  SString ret = textView.textData.getText (till);
  textView.textData.remove (till);
  textView.textData.fireEvent();
  fireTextChanged ();
  updateCaretPosition ();
  updateCaretLocation ();
  return SString(ret);
}

SString
STextEdit::getText (const STextIndex& from, const STextIndex& till)
{
  return textView.textData.getText (from, till);
}

SString
STextEdit::getText ()
{
  return textView.textData.getText ();
}
void
STextEdit::setUnderlineColor (const SColor& c)
{
  textView.setUnderlineColor(c);
}

/**
 * This is called by a slibadle and it shows that vales have been changed.
 */
void
STextEdit::valueChanged (SSlidable* _slidable, SSlideType _type)
{
  SLocation diff = textView.getViewPort() + _slidable->value;
  /* it can not change anything by the viewport. */

  if (isMultiline()) /* only vertical */
  {
    if (diff.y == 0) return;
    SCursorIndex ci = caret.getCursorIndex();
    caret.redraw();

    /* set see what we get */
    scrollVertical (-diff.y, false);

    SLocation l = textView.getCursorLocation(ci);

    SDimension tsize = textView.getSize();
    SLocation  tloc = textView.getLocation();
    if (l.y > (int) tsize.height - tloc.y )
    {
      l.y = (int) tsize.height - textView.lineHeight 
          - tloc.y;
    }
    if (l.y < tloc.y + (int) textView.lineHeight +1)
    {
      l.y = tloc.y + (int) textView.lineHeight +1;
    }
    SCursorIndex index = textView.getCursorIndex(l);
    if (index != ci)
    {
      l.x = 0;
      index = textView.getCursorIndex(l);

      STextIndex wanted = index.getTextIndex();
      /* get our limitation */
      textView.textData.move (wanted);
      STextIndex real = textView.textData.getTextIndex();
      SCursorIndex realIndex (index);
      /* wanted is more  */
      if (wanted != real)
      {
        if (real.index == 0)
        {
          realIndex = SCursorIndex(real.line, real.index, true);
        }
    	else
    	{
          realIndex = SCursorIndex(real.line, real.index-1, false);
    	}
      }
      else
      {
        realIndex = index;
      }
      internalMoveCaret(realIndex);
      /* not necessarily visible */
      fireCaretMoved();
      //column = ci.textIndex.index ;
    }
    updateCaretLocation (false);
  }
  else /* only horizontal */
  {
    if (diff.x == 0) return;
    scrollHorizontal (-diff.x, false);
    updateCaretLocation(false);
  } 
}

/**
 * calculate value, step, page and max and send it to 
 * the listener
 */
void 
STextEdit::notifySlider ()
{
  if (!sliderListener) return;
  /* FIXME: add horizontal one */
  SDimension d = SDimension (0, textView.getDocumentHeight());
  SSlidable s;
  s.max = d - textView.getSize();
  s.step = SDimension (0, textView.lineHeight);
  s.value = SLocation (0, -textView.getViewPort().y);
  s.page = textView.getSize();
  if (slidable==s) return;
  slidable = s;

  if (isMultiline()) /* only vertical */
  {
    sliderListener->valueChanged (&slidable, SSliderListener::SS_VERTICAL);
  }
}

/**
 * @param l  is the listener.
 */
SSlidable*
STextEdit::setSliderListener (SSliderListener* l)
{
  sliderListener = l;
  return &slidable;
}

bool
STextEdit::undo ()
{
 return editor.undo();
}

/**
 * Clear all states 
 * return treu if it had a state.
 */
bool
STextEdit::clearState()
{
  bool had=false;
  selecting = false;
  if (window->commitInputMethod()) {
    had = true;
  }
  if (yuditInput && yuditInput->clear(false))
  {
    had = true;
  }
  if (startSelection != endSelection)
  {
    had=true;
    autoClipSelection();
  }
  deselectText();
  return had;
}

bool
STextEdit::redo ()
{
 return editor.redo();
}

/**
 * Set the ParagraphSeparator
 */
void
STextEdit::setParagraphSeparator (const SString& lbr, bool change)
{
  editor.setParagraphSeparator(lbr);
  if (!change) return;

  if (textView.textData.setParagraphSeparator(lbr))
  {
    for (unsigned i=0; i<listeners.size(); i++)
    {
      listeners[i]->textChanged (this);
    }
    window->redraw (true, 0, 0, size.width, size.height);
  }
}

void
STextEdit::clear()
{
  if (yuditInput) yuditInput->clear();
  window->commitInputMethod();
  startSelection = STextIndex(0,0);
  endSelection = STextIndex(0,0);
  selecting = false;
  editor.clear();
  textView.textData.clear();
  textView.textData.fireEvent();
  updateCaretPosition();
  updateCaretLocation();
  fireCaretMoved();
  fireTextChanged();
}

void
STextEdit::setText (const SString& text)
{
  clear();
  textView.textData.setText (text);
  textView.textData.fireEvent();
  updateCaretPosition();
  updateCaretLocation();
  fireCaretMoved();
  fireTextChanged();
  editor.clear();
}

/**
 * Insert text through editor.
 */
void
STextEdit::insertEditorText (const SString& text)
{
    editor.keyPressed (SWindowListener::Key_Send, text, 
           false, false, false); 
}

const SStringVector&
STextEdit::getHistory()
{
  return history;
}

/**
 * history is for singles
 */
void
STextEdit::putHistory (const SString& str)
{
  if (historySize==0) return;
  if (isMultiline())  return;
  unsigned int i;
  for (i=0; i<history.size(); i++)
  {
    if (history[i] == str) break;
  } 
  if (i<history.size())
  {
    history.remove(i);
  }
  history.append (str);
  /* actually - next */
  currentHistorySize = history.size();
  if (currentHistorySize > historySize)
  {
    history.remove (0);
  }
  currentHistorySize = history.size();
}

void
STextEdit::setHistorySize (unsigned int siz)
{
   historySize = siz;
   if (history.size() > siz) history.truncate (siz);
}

/**
 * set text from history
 */
void
STextEdit::historyDown ()
{
  if (history.size() == 0 || historySize==0) return;
  if (currentHistorySize+1 >= history.size()) return;
  if (isMultiline())  return;
  editor.clear();
  clear();
  currentHistorySize++;
  setText (history[currentHistorySize]);
  setCursorIndex(SCursorIndex(0,10000));
}

/**
 * set text from history
 */
void
STextEdit::historyUp ()
{
  if (history.size() == 0 || historySize==0) return;
  if (currentHistorySize == 0) return;
  if (isMultiline())  return;
  editor.clear();
  clear();
  currentHistorySize--;
//fprintf (stderr, "history up %u \n", currentHistorySize);
  setText (history[currentHistorySize]);
  setCursorIndex(SCursorIndex(0,10000));
}

void
STextEdit::historyEnd()
{
  currentHistorySize = history.size();
}

/**
 * If there is selected text unselect it increment the index.
 * and try find again.
 */
bool
STextEdit::find(const SString& str)
{
  if (isSelecting ())
  {
    endSelect ();
  }
  /* just find */
  STextIndex fs;
  if (startSelection == endSelection)
  {
    fs = textView.textData.find(str);
    if (fs == STextIndex(0,0))
    {
      setCursorIndex(SCursorIndex(0,0));
      fs = textView.textData.find(str);
      if (fs == STextIndex(0,0))
      {
        return false;
      }
    }
  }
  /* select the text between current index and fs */
  else
  {
    STextIndex indexNext = textView.textData.getTextIndex (1, true);
    textView.textData.move (indexNext);
    updateCaretPosition();

    fs = textView.textData.find(str);
    if (fs == STextIndex(0,0))
    {
      setCursorIndex(SCursorIndex(0,0));
      fs = textView.textData.find(str);
      if (fs == STextIndex(0,0))
      {
        deselectText();
        return false;
      }
    }
  }

  STextIndex currentIndex = textView.textData.getTextIndex();

  if (startSelection != endSelection)
  {
    textView.textData.move (startSelection);
    textView.textData.select (endSelection, false);
    textView.textData.fireEvent();
    textView.textData.move (currentIndex);
  }

  startSelection = fs;
  endSelection = fs;

  /* select text moves stuff around so we might want to deselect previous now */

  selecting = true;
  selectText (SCursorIndex (currentIndex.line, currentIndex.index, true));
  selecting = false;


  /* this scrolles and leaves old selected text behind */
  centerCaretVertical();

  return true;
}

/**
 * replace text.
 */
bool
STextEdit::replace (const SString& orig, const SString& repl)
{
  if (isSelecting())
  {
    endSelect ();
  }
  if (orig.size()==0)
  {
     return false;
  }
  SString str;
  if (startSelection != endSelection)
  {
    str = cleanEmbed(textView.textData.getText (startSelection, endSelection));
  }
  /* do if it is the same as search string */
  if (startSelection == endSelection || str != orig)
  {
     return find (orig);
  }
  else
  {
    /* re-embed */
    SEmbedState es = textView.textData.getEmbedState(
     (startSelection > endSelection) ? endSelection : startSelection);

    editor.keyPressed (SWindowListener::Key_Delete, SString(""), 
           false, false, false); 
    if (repl.size())
    {
      /* replace with the current embed state */
      SV_UCS4 emark = es.getEmbeddingMarks(0);
      SEncoder utf8enc;
      SString str = utf8enc.encode (emark);
      str.append (repl);
      for (unsigned int i=0; i<emark.size(); i++)
      {
        str.append (SS_LB_PDF);
      }
      editor.insertText (str); 
    }
  }
  return true;
}

const SGlyph*
STextEdit::glyphAt(const STextIndex & ti) const
{
  if (ti.line >= textView.textData.size()) return 0;
  if (ti.index >= textView.textData.size(ti.line)) return 0;
  return &(textView.textData.glyphAt (ti));
}

/**
 * Return the visual index of where to caret is pointing from:
 * returned index is index + 1 
 */
STextIndex
STextEdit::getCaretArrowFrom ()
{
  SCursorIndex ci = caret.getCursorIndex();
  /* before or after, this, visually */ 
  bool clr = caret.isLR();
  SCursorIndex cr = (clr) ? textView.leftOf (ci) : textView.rightOf (ci);
  if (cr == ci)
  {
    bool plr = textView.textData.isLR(ci.textIndex.line);
    if ((plr && !clr) || (!plr && clr))
    {
      if (textView.textData.isProperLine (cr.textIndex.line))
      {
        return STextIndex(cr.textIndex.line, 
           textView.textData.size(cr.textIndex.line));
      }
      return STextIndex(cr.textIndex.line, 
         textView.textData.size(cr.textIndex.line)+1);
    }
    return STextIndex(cr.textIndex.line, 0);
  }
  return STextIndex (cr.textIndex.line, cr.textIndex.index+1);
}

void
STextEdit::setSyntax (const SString& hlMode)
{
  textView.setSyntax(hlMode);
  redrawInternal ();
}

const SString&
STextEdit::getSyntax () const
{
  return textView.getSyntax();
}

void
STextEdit:: setSyntaxColors (const SSyntaxColors& attr)
{
  textView.setSyntaxColors(attr);
}

const SSyntaxColors&
STextEdit::getSyntaxColors () const
{
  return textView.getSyntaxColors();
}

void
STextEdit::setWordWrap (bool lbm)
{
  SCursorIndex xy = getCursorIndex();
  textView.setWordWrap(lbm);
  /* extents may change */
  notifySlider();
  setCursorIndex (xy);
  setCaretLocationVisible ();
  redrawInternal ();
}

bool
STextEdit::getWordWrap () const
{
  return textView.getWordWrap();
}

void
STextEdit::setDocumentEmbedding (SS_Embedding e)
{
  if (textView.textData.getDocumentEmbedding()==e) return;
  textView.textData.setDocumentEmbedding (e);
  textView.textData.fireEvent ();
  updateCaretPosition();
  updateCaretLocation();
  fireCaretMoved();
  window->redraw (true, 0, 0, size.width, size.height);
}
SS_Embedding
STextEdit::getDocumentEmbedding () const
{
  return textView.textData.getDocumentEmbedding();
}

/**
 * Set the direction of the caret. If the direction of 
 * The case is not simpliy SS_DR_L or SS_DR_R, add
 * necessary extra embedding levels to caret if necessary
 * direction is different.
 * @return true if the caret direction was different.
 */
void
STextEdit::setDirection (SS_DR_Dir dir)
{
  STextIndex ti = textView.textData.getTextIndex();

  SCursorIndex ci = caret.getCursorIndex();
  SEmbedState embed = textView.textData.getEmbedState(ci.textIndex);
  caret.setEmbedState (embed);

  if (dir==SS_DR_L || dir==SS_DR_R)
  {
    caret.setDirection (dir);
    caret.setEmbedState (embed);
    return;
  }
  SV_UCS4 v = embed.getEmbeddingMarks(0);
  SS_UCS4 current = (v.size()) ? v[v.size()-1] : 0;
  SS_UCS4 needed = 0;
  switch (dir)
  {
  case SS_DR_LE:
    if (current != SD_CD_LRE) needed = SD_CD_LRE;
    break;
  case SS_DR_RE:
    if (current != SD_CD_RLE) needed = SD_CD_RLE;
    break;
  case SS_DR_LO:
    if (current != SD_CD_LRO) needed = SD_CD_LRO;
    break;
  case SS_DR_RO:
    if (current != SD_CD_RLO) needed = SD_CD_RLO;
    break;
  default: break;
  }
  if (needed != 0)
  {
    v.append (needed);
    embed.setEmbeddingMarks(v);
    caret.setEmbedState(embed);
  }
  caret.setDirection (dir);
  return;
}

/**
 * get the current direction of the caret.
 */
SS_DR_Dir
STextEdit::getDirection () const
{
  return caret.getDirection();
}

/**
 * return the explicit embed level at cursor 
 */
bool
STextEdit::isEmbedStateLR() const
{
  SEmbedState ec = caret.getEmbedState();
  SCursorIndex ci = caret.getCursorIndex();
  bool isEL = ((ec.getExplicitLevel() % 2)==0);
  if (ec.getExplicitLevel() == 0)
  {
    isEL = textView.textData.isLR(ci.textIndex.line);
  }
  return isEL;
}

/**
 * Change the direction of the selected text.
 * If no text is selected insert new direction.
 * @return true if the direction of cursor succeded, false
 *  if only text embedding has been changed.
 */
bool
STextEdit::changeDirection (SS_DR_Dir dir)
{
  if (isSelecting())
  {
    endSelect ();
  }
  if (yuditInput) yuditInput->clear();
  window->commitInputMethod();
  /* if we want to change the same emvedding dont do it */
  SEmbedState ec = caret.getEmbedState();
  SCursorIndex ci = caret.getCursorIndex();

  bool skip = false;
  bool isEL = ((ec.getExplicitLevel() % 2)==0);
  if (ec.getExplicitLevel() == 0)
  {
    isEL = textView.textData.isLR(ci.textIndex.line);
  }
  bool isOve = ec.isOverride();
  SS_DR_Dir cdir = caret.getDirection();
  switch (dir)
  {
  case SS_DR_LE:
    if (isEL&& !isOve) skip = true;
    if (cdir==dir) skip = true;
    if (cdir==SS_DR_RE) skip = true;
    break;
  case SS_DR_RE:
    if (!isEL && !isOve) skip = true;
    if (cdir==dir) skip = true;
    if (cdir==SS_DR_LE) skip = true;
    break;
  case SS_DR_LO:
    if (isEL && isOve) skip = true;
    if (cdir==dir) skip = true;
    if (cdir==SS_DR_RO) skip = true;
    break;
  case SS_DR_RO:
    if (cdir==dir) skip = true;
    if (!isEL && isOve) skip = true;
    if (cdir==SS_DR_LO) skip = true;
    break;
  default: break;   
  }
  if (skip && startSelection!=endSelection)
  {
    endSelect();
    deselectText();
  }
  /* just reset */
  if (skip || ((dir == SS_DR_L || dir == SS_DR_R) 
     && startSelection == endSelection))
  {
    /* pop embedding direction and return 1 if 
     * next direction is different. Also lower the 
     * embedding if there is selected text.
     */
    SEmbedState et = textView.textData.getEmbedState(ci.textIndex);
    if (ec == et)
    {
      /* do a loop while we have this state or bigger */
      unsigned int i=ci.textIndex.index;
      SV_UCS4 m0 = ec.getEmbeddingMarks(0);
      if (m0.size()==0)
      {
        setCursorIndex (caret.getCursorIndex());
        return true;
      }
      /* find the end  of this embeding */
      while (i<textView.textData.size(ci.textIndex.line))
      {
        SEmbedState en = textView.textData.getEmbedState(
          STextIndex (ci.textIndex.line, i));
        SV_UCS4 m1 = en.getEmbeddingMarks(0);
        if (m1.size() < m0.size()) break;
        i++;
      }
      /* move it to the beginning of that place */
      SCursorIndex index(ci.textIndex.line, i, true); 
      /* move caret left or right */
      setCursorIndex (index);
    }
    else /* we have different states. just need to reset */
    {
      setCursorIndex (caret.getCursorIndex());
    }
    return true;
  }
  /* Nothing sleected. change direction of cursor */
  if (startSelection == endSelection) 
  {
    setDirection (dir);
    return true;
  }
  SString str = textView.textData.getText (startSelection, endSelection);
  /* remove all embedding marks. */
  if (dir != SS_DR_L && dir != SS_DR_R)
  {
    /* add embedding marks */
    SEncoder utf8enc;
    SV_UCS4 u4 = utf8enc.decode (str);
    SV_UCS4 res;
    bool nl = true;
    SS_UCS4 em = SD_CD_PDF;
    switch (dir)
    {
    case SS_DR_LE: em = SD_CD_LRE; break;
    case SS_DR_RE: em = SD_CD_RLE; break;
    case SS_DR_LO: em = SD_CD_LRO; break;
    case SS_DR_RO: em = SD_CD_RLO; break;
    default: break;
    }
    for (unsigned int i=0; i<u4.size(); i++)
    {
      if (nl) res.append (em);
      nl = false;
      bool cont = false;
      switch (u4[i])
      {
      case SD_CD_CR:
        nl = true;
        res.append (SD_CD_PDF);
        if (i+1<u4.size() && u4[i+1] == SD_CD_LF)
        {
          res.append (u4[i]);
          i++;
        }
        break;
      case SD_CD_LF:
        nl = true;
        res.append (SD_CD_PDF);
        if (i+1<u4.size() && u4[i+1] == SD_CD_CR)
        {
          res.append (u4[i]);
          i++;
        }
        break;
      case SD_CD_PS:
        nl = true;
        res.append (SD_CD_PDF);
        break;
      /* unembed */
      case SD_CD_LRO:
      case SD_CD_RLO:
      case SD_CD_LRE:
      case SD_CD_RLE:
      case SD_CD_PDF:
        cont = true;
        break;
      default:
        break;
      }
      if (cont) continue;
      res.append (u4[i]);
    }
    if (!nl) res.append (SD_CD_PDF);
    str = utf8enc.encode(res);
  }
  else /* unembed */
  {
    str = cleanEmbed (str);
  }
  editor.keyPressed (SWindowListener::Key_Delete, SString(""), 
        false, false, false); 
  /* do we need this? */
  caret.setDirection (dir);
  /* reset embed state */
  caret.setEmbedState (SEmbedState());
  editor.insertText (str); 
  updateCaretPosition();
  return false;
}


/**
 * Move the caret to preceeding getTextIndex position.
 * @param updateColumn is true if column needs updating (dafult)
 */
void
STextEdit::updateCaretPosition(bool updateColumn)
{
  unsigned int ocolumn = column;
  STextIndex ti = textView.textData.getTextIndex();
  SCursorIndex ci;
  if (ti.index == 0)
  {
    ci = SCursorIndex(ti.line, ti.index, true);
  }
  else
  {
    ci = SCursorIndex(ti.line, ti.index-1, false);
  }
//fprintf (stderr, "updateCaretPosition %u caret: %u %d\n", 
//   ti.index, ci.textIndex.index, ci.before);
  internalMoveCaret(ci);
  if (updateColumn) column = ti.index;
  fireCaretMoved();
  notifySlider();
  if (!updateColumn) column = ocolumn;
}

/**
 * Update the location of the caret 
 */
void
STextEdit::updateCaretLocation(bool setvisible)
{
  SLocation lc = textView.getCursorLocation (caret.getCursorIndex());
  caret.move (lc);
  if (setvisible)
  {
    setCaretLocationVisible ();
    /* might be off because of above */
    lc = textView.getCursorLocation (caret.getCursorIndex());
    caret.move (lc);
    window->setInputMethodProperties (getProperties());
  }
}

/**
 * Move caret to index. if possible.
 * @param index is the index to go to.
 */
void
STextEdit::setCursorIndex (const SCursorIndex& index)
{
  STextIndex wanted = index.getTextIndex();
  /* get our limitation */
  textView.textData.move (wanted);
  STextIndex real = textView.textData.getTextIndex();
  textView.textData.fireEvent();
  SCursorIndex realIndex (index);

  /* wanted is more  */
  if (wanted != real)
  {
    if (real.index == 0)
    {
      realIndex = SCursorIndex(real.line, real.index, true);
    }
    else
    {
      realIndex = SCursorIndex(real.line, real.index-1, false);
    }
  }
  else
  {
    realIndex = index;
  }
  internalMoveCaret(realIndex);
  updateCaretLocation ();
  fireCaretMoved();
  notifySlider();
}

/**
 * Internal move caret with all fancy shape settings
 */
void
STextEdit::internalMoveCaret (const SCursorIndex& realIndex)  
{
  /* This will make sure it wont blink for a while now */
  caret.on (true);
  column = realIndex.textIndex.index;
  caret.move (realIndex);
  bool lr = textView.textData.isLR(realIndex.textIndex);

  SEmbedState ec = textView.textData.getEmbedState(realIndex.textIndex);
  caret.setEmbedState (ec);
  caret.setDirection (lr ? SS_DR_L : SS_DR_R);

  /* check if previous embed state is different */

  /* if this embedding state is different from sorrounding embedding
     state change cursor shape tu current embedding state. */
  unsigned int line = realIndex.textIndex.line;
  unsigned int max = textView.textData.size(line);
  STextIndex bbox (line, max);

  /* if the thind does not move get the paragraph embedding */
  SCursorIndex right = textView.rightOf (realIndex);
  SEmbedState rec = (right == realIndex) 
   ? textView.textData.getEmbedState (bbox)
   : textView.textData.getEmbedState (right.textIndex);

  SCursorIndex left = textView.leftOf (realIndex);
  SEmbedState lec = (left == realIndex)
   ? textView.textData.getEmbedState (bbox)
   : textView.textData.getEmbedState (left.textIndex);

  SV_UCS4 marks = ec.getEmbeddingMarks(0);
  if ((ec != lec || ec != rec) && marks.size()!=0)
  {
    switch(marks[marks.size()-1])
    {
    case SD_CD_LRO:
      caret.setDirection (SS_DR_LO);
      break;
    case SD_CD_RLO:
      caret.setDirection (SS_DR_RO);
      break;
    case SD_CD_LRE:
      caret.setDirection (SS_DR_LE);
      break;
    case SD_CD_RLE:
      caret.setDirection (SS_DR_RE);
      break;
    }
  }
}

/**
 * Encode the glyph with current keymap 
 */
SString
STextEdit::encode (const SV_UCS4& v) const
{
  SString ret;
  if (yuditInput!=0)
  {
    ret = yuditInput->encode (v);
  }
  return SString(ret);
}

/**
 * get rid of embedding marks
 */
SString
STextEdit::cleanEmbed(const SString str) const
{
  SEncoder utf8enc;
  SV_UCS4 u4 = utf8enc.decode (str);
  SV_UCS4 res;
  /* remove direction marks */
  for (unsigned int i=0; i<u4.size(); i++)
  {
    switch (u4[i])
    {
    case SD_CD_LRO:
    case SD_CD_RLO:
    case SD_CD_LRE:
    case SD_CD_RLE:
    case SD_CD_PDF:
      break;
    default:
      res.append (u4[i]);
    }
  }
  SString s = utf8enc.encode(res);
  return SString (s);
}
   
/**
 * Add a property for XInput
 * @param key is the property.
 * @param p is the property to be modified.
 */
SProperties
STextEdit::getProperties ()
{
  SProperties p;
  char arr[64];

  if (ximProperties.get ("InputStyle"))
  {
    p.put ("InputStyle", ximProperties["InputStyle"]);
  }
  else
  {
    p.put ("InputStyle", "preedit-over-status-over");
  }

  SColor bg = textView.getBackground();
  SColor fg = textView.getForeground (true);
  sprintf (arr, "%lu,%lu", 
      (unsigned long)bg.getValue(), (unsigned long)fg.getValue());
  if (ximProperties.get ("InputClientColor"))
  {
    p.put ("InputClientColor", ximProperties["InputClientColor"]);
  }
  else
  {
    p.put ("InputClientColor", arr);
  }
  /* status area */
  if (ximProperties.get ("InputStatusColor"))
  {
     p.put ("InputStatusColor", ximProperties["InputStatusColor"]);
  }
  else
  {
     p.put ("InputStatusColor", p["InputClientColor"]);
  }

  SLocation caretL = caret.getLocation();
  sprintf (arr, "%d,%d", caretL.x, caretL.y + textView.lineAscent);
  p.put ("InputSpot", arr);

  /* add required and optional parameters if any */
  if (!SS_NO_STATUS_UNDER && p["InputStyle"] == SString("preedit-under-status-under"))
  {
    SWindow* w = getComponentWindow();
    unsigned long wid = w->getWindowID();
    sprintf (arr, "%lu", wid);
    p.put ("InputWindow", arr);

    SLocation vl = textView.getLocation();
    vl.x = vl.x - (int) SS_LEFT_MARGIN;
    SDimension vd = textView.getSize();
    vd.width = vd.width + (unsigned int) 2 * SS_LEFT_MARGIN;
    vl.y = vl.y + (int) vd.height;
    sprintf (arr, "%d,%d", vl.x, vl.y); 
    p.put ("InputStatusLocation", arr);
    unsigned int statusw = 2 * statusHeight;
    if (statusw < 48) statusw = 48;

    sprintf (arr, "%u,%u", statusw, statusHeight);
    p.put ("InputStatusSize", arr);

    vl.x = (int) statusw;
    sprintf (arr, "%d,%d", vl.x, vl.y); 
    p.put ("InputClientLocation", arr);

    vd.width = vd.width + (unsigned int) 2 * SS_LEFT_MARGIN;
    if (vd.width > statusw) vd.width = vd.width - statusw;
    sprintf (arr, "%u,%u", vd.width, statusHeight);
    p.put ("InputClientSize", arr);
   
  } 
  /* add required and optional parameters if any */
  if (!SS_NO_STATUS_UNDER && p["InputStyle"] == SString("preedit-over-status-under"))
  {
    SLocation vl = textView.getLocation();
    vl.x = vl.x - (int) SS_LEFT_MARGIN;
    SDimension vd = textView.getSize();
    vd.width = vd.width + (unsigned int) 2 * SS_LEFT_MARGIN;
    vl.y = vl.y + (int) vd.height;
    sprintf (arr, "%d,%d", vl.x, vl.y); 
    p.put ("InputStatusLocation", arr);

    sprintf (arr, "%u,%u", vd.width, statusHeight);
    p.put ("InputStatusSize", arr);
  } 

  return SProperties (p);
}

/**
 * Set X Input Method properties.
 * This method is called if the default
 *   InputStyle="preedit-over-status-over"
 *  needs to be overridden.
 * Accepted InputStyles:
 *  preedit-root-status-root
 *  preedit-over-status-over
 *  preedit-over-status-under
 *  preedit-under-status-under
 *      Optional:
 *       InputWindow=id - an integer window id
 *       InputStatusLocation="x,y" - the status position
 *       InputStatusSize="width,height" - the status size
 *       InputClientColor=colorbg,colorfg - input status background,foreground
 *       InputClientLocation="x,y" - the client position
 *       InputClientSize="width,height" - the client size
 * @param props are the properties. 
 */
void
STextEdit::setXIMProperties (const SProperties& props)
{
   ximProperties = props;
}
void
STextEdit::preEditClearMarkedText () {
    if (preEditSize == 0) {
        preEditStartDirection = getDirection();
        preEditStartIndex = getCursorIndex();
        return;
    }
    SCursorIndex s = preEditStartIndex;
    SCursorIndex e = preEditEndIndex;

    endSelect ();
    setCursorIndex (s);
    eraseText (e.getTextIndex());
    setCursorIndex (preEditStartIndex);
    setDirection (preEditStartDirection);

    preEditSize = 0;
}

void
STextEdit::preEditInsertMarkedText(const SString& utf8, SStyle style=Style_Default) {
    if (preEditSize == 0) {
        preEditStartDirection = getDirection();
        preEditStartIndex = getCursorIndex();
    }
    if(utf8.size() == 0) {
        return;
    }
    insertPreEditText (utf8, style);
    // FIXME figure out where the first caracter is.
    preEditEndIndex = getCursorIndex();
    preEditSize++;
    return;
}
// Count characters as if they were UTF16
// FIXME: SMP surrogates.
static unsigned int countUTF16(const SV_UCS4& ucs4v) 
{
    unsigned int count =0;
    for(unsigned int i=0; i<ucs4v.size(); i++) {
        SS_UCS4 ucs4 = ucs4v[i]; 
        if (ucs4 < 0x10000) {
            count++;
        } else {
            count++;
            count++;
        }
    }
    return count;
}

SRectangle
STextEdit::getGlyphRectangle (const STextIndex& ti) {
  SCursorIndex ci;
  if (ti.index == 0)
  {
    ci = SCursorIndex(ti.line, ti.index, true);
  }
  else
  {
    // both before.
    ci = SCursorIndex(ti.line, ti.index, true);
    //ci = SCursorIndex(ti.line, ti.index-1, false);
  }
//fprintf (stderr, "STextEdit::getGlypRectangle index %u\n", ti.index);
  //bool lr = textView.textData.isLR(textIndex);
  SLocation lc = textView.getCursorLocation (ci);
  // screen top is zero.
  //fprintf (stderr, "lc.x=%d lc.y=%d\n", lc.x, lc.y);
  return (SRectangle(lc.x, lc.y, textView.lineAscent, textView.lineAscent));
  //return (SRectangle(lc.x, lc.y, textView.lineHeight, textView.lineHeight));
  //caret.move (lc);
}

// FIXME: SMP surrogates.
// FIXME: Currently only works on LR text.
SRectangle
STextEdit::preEditGlyphRectangleUTF16(unsigned int charPos) {
    SRectangle ret;
    if (preEditSize == 0) {
        return SRectangle(ret);
    }
    // Find the characterIndex.
    SCursorIndex s = preEditStartIndex;
    SCursorIndex e = getCursorIndex();
    if (s.getTextIndex().line != e.getTextIndex().line) {
        return SRectangle(ret);
    }
    if (s.getTextIndex().index > e.getTextIndex().index) {
        return getGlyphRectangle (e.getTextIndex());
        // LR is beginning.
    }
    STextIndex ti = s.getTextIndex();
    if (ti.line >= textView.textData.size()) {
        return getGlyphRectangle(ti);
    }
    unsigned int currentSize = 0;
    ret = getGlyphRectangle (ti);
    while (currentSize <= charPos) {
        if (ti.index >= textView.textData.size(ti.line)) break;
        if (ti.index >= e.getTextIndex().index) break; 
        const SGlyph* glyph = textView.textData.peekGlyphAt (ti);
        currentSize += countUTF16(glyph->getChars());
        //currentSize += 1;
        ret = getGlyphRectangle (ti);
        ti.index = ti.index+1;
    }
    return SRectangle(ret);
}

void
STextEdit::setAutoClipboard(bool is) 
{
   autoClipboard = is; 
}

void
STextEdit::setHideText(bool is)
{
   textView.setHideText(is);
}
