/**************************************************************************

    This file is part of Xclass95, a Win95-looking GUI toolkit.
    Copyright (C) 1996, 1997 David Barth, Hector Peraza.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

**************************************************************************/

#include <xclass/utils.h>
#include <xclass/OXWidgets.h>
#include <xclass/OXClient.h>
#include <xclass/OString.h>

#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>


//-----------------------------------------------------------------

OXTextEntry::OXTextEntry(const OXWindow *p, OTextBuffer *text, int ID,
                         GC norm, XFontStruct *font, unsigned int options,
                         unsigned long back)
  : OXFrame(p, 1, 1, options | OWN_BKGND, back) {
    int tw;

    _widgetID = ID;
    _widgetType = "OXTextEntry";
    _widgetFlags = WIDGET_WANT_FOCUS;
    _msgWindow = p;

    _normGC = norm;
    _selGC  = _defaultSelectedGC;
    _selbackGC = _defaultSelectedBackgroundGC;
    _fontStruct = font;
    _text = text;
    _state = ENABLED;
    
    tw = XTextWidth(_fontStruct, _text->GetString(), _text->GetTextLength());

    Resize(tw + 8, _fontStruct->max_bounds.ascent +
	           _fontStruct->max_bounds.descent + 7);

    _cursor_x  = 4;
    _cursor_ix = _start_ix = _end_ix = 0;
    _selection_on = _cursor_on = False;
    _curblink = NULL;

    XDefineCursor(GetDisplay(), _id, _defaultCursor);

    XGrabButton(GetDisplay(), AnyButton, AnyModifier, _id, True,
		ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
		GrabModeAsync, GrabModeAsync, None, None);

    XSelectInput(GetDisplay(), _id, KeyPressMask | ExposureMask | FocusChangeMask);
}

OXTextEntry::~OXTextEntry() {
  delete _text;
  if (_curblink) delete _curblink;
}


void OXTextEntry::SetState(int state) {
  _state = state;
  switch(_state) {
    case ENABLED:
      SetBackgroundColor(_client->GetColorByName("white"));
      break;

    case DISABLED:
      SetBackgroundColor(_defaultFrameBackground);
      break;
  }
  _client->NeedRedraw(this);
}

void OXTextEntry::_DoRedraw() {
  int x, y;
  
  OXFrame::_DoRedraw();
  x = 4; // do not center text
  y = 4;
//  _text->Draw(GetDisplay(), _id, _normGC, x, y + _fontStruct->max_bounds.ascent);
  XDrawString(GetDisplay(), _id, _normGC, x, y + _fontStruct->max_bounds.ascent, 
              _text->GetString(), _text->GetTextLength());

  if (_cursor_on) {
    XDrawLine(GetDisplay(), _id, _blackGC, _cursor_x, 3,
                  _cursor_x, _fontStruct->max_bounds.ascent +
                             _fontStruct->max_bounds.descent + 3);
//  } else {
//    XDrawLine(GetDisplay(), _id, _whiteGC, _cursor_x, 4,
//                  _cursor_x, _fontStruct->max_bounds.ascent +
//                             _fontStruct->max_bounds.descent + 1);
  }

  if (_selection_on) {
    int xs, ws, ixs, iws;

    xs = min(_start_x, _end_x);
    ws = abs(_end_x - _start_x);
    ixs = min(_start_ix, _end_ix);
    iws = abs(_end_ix - _start_ix);

    XFillRectangle(GetDisplay(), _id, _selbackGC, x+xs, y-1,
                   ws, _fontStruct->max_bounds.ascent +
                       _fontStruct->max_bounds.descent +1);

  //  _text->Draw(GetDisplay(), _id, _normGC, x, y + _fontStruct->max_bounds.ascent);

    XDrawString(GetDisplay(), _id, _selGC, x+xs, y + _fontStruct->max_bounds.ascent, 
                (_text->GetString())+ixs, iws);
  }

  // hmmm...
  DrawBorder();
}

void OXTextEntry::DrawBorder() {
  switch (_options & (SUNKEN_FRAME | RAISED_FRAME | DOUBLE_BORDER)) {
  case SUNKEN_FRAME | DOUBLE_BORDER:
    XDrawLine (GetDisplay(), _id, _shadowGC, 0, 0, _w-2, 0);
    XDrawLine (GetDisplay(), _id, _shadowGC, 0, 0, 0, _h-2);
    XDrawLine (GetDisplay(), _id, _blackGC, 1, 1, _w-3, 1);
    XDrawLine (GetDisplay(), _id, _blackGC, 1, 1, 1, _h-3);

    XDrawLine (GetDisplay(), _id, _hilightGC, 0, _h-1, _w-1, _h-1);
    XDrawLine (GetDisplay(), _id, _hilightGC, _w-1, _h-1, _w-1, 0);
    XDrawLine (GetDisplay(), _id, _bckgndGC,  1, _h-2, _w-2, _h-2);
    XDrawLine (GetDisplay(), _id, _bckgndGC,  _w-2, 1, _w-2, _h-2);
    break;

  default:
    OXFrame::DrawBorder();
    break;
  }
}

// Maybe this should be in OString or OTextBuffer?

int OXTextEntry::_GetCharacterIndex(int xcoord) {
  int tw, ix, up, down, len;

  // check for out of boundaries first...
  len = _text->GetTextLength();
  tw = XTextWidth(_fontStruct, _text->GetString(), len);
  if (xcoord < 0) return 0;
  if (xcoord > tw) return len; // len-1

  // do a binary approximation
  up = len; //-1
  down = 0;
  while (up-down > 1) {
    ix = (up+down) >> 1;
    tw = XTextWidth(_fontStruct, _text->GetString(), ix);
    if (tw > xcoord)
      up = ix;
    else
      down = ix;
    if (tw == xcoord) break;
  }
  ix = down;

  // safety check...
  ix = max(ix, 0);
  ix = min(ix, len); // len-1
  return ix;
}

int OXTextEntry::HandleButton(XButtonEvent *event) {
  int x;

  if (_state == DISABLED) return True;
  
  if (event->type == ButtonPress) {

    XSetInputFocus(GetDisplay(), _id, RevertToParent, CurrentTime);

    if (event->button == Button1) {

      x = 4;
      _start_ix = _GetCharacterIndex(event->x - x);
      _start_x  = XTextWidth(_fontStruct, _text->GetString(), _start_ix);
      _cursor_ix = _start_ix;
      _cursor_x = _start_x + x;
      _selection_on = False;
      _DoRedraw();

    } else if (event->button == Button2) {

      // request selection
      Atom sel_property;
  
      if (XGetSelectionOwner(GetDisplay(), XA_PRIMARY) == None) {
        // No primary selection, so use the cut buffer
        _PastePrimary(_client->GetRoot()->GetId(), XA_CUT_BUFFER0, False);
      } else {
        sel_property = XInternAtom(GetDisplay(), "VT_SELECTION", False);
        XConvertSelection(GetDisplay(), XA_PRIMARY, XA_STRING,
                          sel_property, _id, event->time);
      }

    }
  }
  return True;
}

int OXTextEntry::HandleDoubleClick(XButtonEvent *event) {
  int x;

  if (_state == DISABLED) return True;

  XSetInputFocus(GetDisplay(), _id, RevertToParent, CurrentTime);

  x = 4;
  _start_ix = 0;
  _start_x  = XTextWidth(_fontStruct, _text->GetString(), _start_ix);
  _end_ix = _text->GetTextLength();
  _end_x  = XTextWidth(_fontStruct, _text->GetString(), _end_ix);
  _cursor_ix = _end_ix;
  _cursor_x = _end_x + x;
  _selection_on = True;

  _DoRedraw();

  return True;
}

int OXTextEntry::HandleMotion(XMotionEvent *event) {
  int x, y;

  if (_state == DISABLED) return True;

  x = 4;
  y = 4;

  _end_ix = _GetCharacterIndex(event->x - x); // + 1;
  _end_x  = XTextWidth(_fontStruct, _text->GetString(), _end_ix);
  _cursor_ix = _end_ix;
  _cursor_x = _end_x + x;
  _selection_on = True;

  _DoRedraw();
  return True;
}

int OXTextEntry::HandleKey(XKeyEvent *event) {
  int  modifier, n, len, text_changed = False;
  char tmp[10];
  KeySym keysym;
  XComposeStatus compose = { NULL, 0 };
  if (_state == DISABLED) return True;

  len = _text->GetTextLength();
  n = XLookupString(event, tmp, sizeof(tmp)-1, &keysym, &compose);
  tmp[n] = 0;

  switch (keysym) {
    case XK_Right:
      if (_selection_on) _selection_on = False;
      if (_cursor_ix < len) ++_cursor_ix;
      break;

    case XK_Left:
      if (_selection_on) _selection_on = False;
      if (_cursor_ix > 0) --_cursor_ix;
      break;

    case XK_Home:
      if (_selection_on) _selection_on = False;
      _cursor_ix = 0;
      break;

    case XK_End:
      if (_selection_on) _selection_on = False;
      _cursor_ix = len;
      break;

    case XK_Delete:
      if (_selection_on) {
        int start, ns;

        start = min(_start_ix, _end_ix);
        ns = abs(_end_ix - _start_ix);
        _text->RemoveText(start, ns);
        _selection_on = False;
        _cursor_ix = start;
      } else {
        _text->RemoveText(_cursor_ix, 1);
      }
      text_changed = True;
      break;

    case XK_BackSpace:
      if (_selection_on) {
        int start, ns;

        start = min(_start_ix, _end_ix);
        ns = abs(_end_ix - _start_ix);
        _text->RemoveText(start, ns);
        _selection_on = False;
        _cursor_ix = start;
      } else {
        if (_cursor_ix > 0) {
          --_cursor_ix;
          _text->RemoveText(_cursor_ix, 1);
        }
      }
      text_changed = True;
      break;

    case XK_Up:
    case XK_Down:
    case XK_Escape:
    case XK_Execute:
    case XK_Return:
      break;    

    default:
      if (_selection_on) {
        int start, ns;

        start = min(_start_ix, _end_ix);
        ns = abs(_end_ix - _start_ix);
        _text->RemoveText(start, ns);
        _selection_on = False;
        _cursor_ix = start;
      }
      _text->AddText(_cursor_ix, tmp);
      len = _text->GetTextLength();
      _cursor_ix += n;
      if (_cursor_ix > len) _cursor_ix = len;
      text_changed = True;
      break;
  }
  if (text_changed)
    SendMessage(_msgWindow, MK_MSG(XC_TEXTENTRY, TE_TEXTCHANGED),
                _widgetID, 0L);

  _cursor_x = 4 + XTextWidth(_fontStruct, _text->GetString(), _cursor_ix);
  _client->NeedRedraw(this);
  return True;
}

int OXTextEntry::HandleFocusChange(XFocusChangeEvent *event) {

  if (_state == DISABLED) return True;

  if ((event->mode == NotifyNormal) &&
      (event->detail != NotifyPointer)) {
    if (event->type == FocusIn) {
      _cursor_on = True;
      if (!_curblink) _curblink = new OTimer(this, 500);
    } else {
      _cursor_on = False;
      _selection_on = False;
      if (_curblink) { delete _curblink; _curblink = NULL; }
    }
    _client->NeedRedraw(this);
  }
  return True;
}

int OXTextEntry::HandleSelection(XSelectionEvent *event) {
  _PastePrimary(event->requestor, event->property, True);
  return True;
}

void OXTextEntry::_PastePrimary(Window wid, Atom property, int Delete) {
  Atom actual_type;
  int  actual_format, len;
  unsigned long nitems, bytes_after, nread;
  unsigned char *data, *data2;

  if (_state == DISABLED) return;

  if (property == None) return;

  // paste primary
  nread = 0;
  do {
    if (XGetWindowProperty(GetDisplay(), wid, property,
                           nread/4, 1024, Delete,
                           AnyPropertyType,
                           &actual_type, &actual_format,
                           &nitems, &bytes_after,
                           (unsigned char **) &data)
        != Success)
      break;

    if (actual_type != XA_STRING) break;

    data2 = data;

    // remove any previous selected text
    if (_selection_on) {
      int start, ns;

      start = min(_start_ix, _end_ix);
      ns = abs(_end_ix - _start_ix);
      _text->RemoveText(start, ns);
      _selection_on = False;
      _cursor_ix = start;
    }

    _text->AddText(_cursor_ix, (char *) data2, (int) nitems);
    len = _text->GetTextLength();
    _cursor_ix += nitems;
    if (_cursor_ix > len) _cursor_ix = len;

    nread += nitems;
    XFree(data2);

  } while (bytes_after > 0);

  SendMessage(_msgWindow, MK_MSG(XC_TEXTENTRY, TE_TEXTCHANGED),
              _widgetID, 0L);
  _cursor_x = 4 + XTextWidth(_fontStruct, _text->GetString(), _cursor_ix);
  _client->NeedRedraw(this);
  return;
}

int OXTextEntry::HandleTimer(OTimer *t) {
  if (t != _curblink) return False;

  _cursor_on = !_cursor_on; _DoRedraw();

  delete _curblink;
  _curblink = new OTimer(this, 500);
}
