/* stringgadget.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2001-2023 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "stringgadget.h"
#include "awindow.h"
#include "guielement.h"
#include "utf8.hh"
#include "drawablecont.hh"
#include "util.h"

const char *StringGadget::type="StringGadget";

StringGadget::~StringGadget()
{
    _aguix->cancelCutPaste( this, false );
    _aguix->cancelCutPaste( this, true );
    destroy();
}

StringGadget::StringGadget(AGUIX *taguix,int tx,int ty,int width,int height,const char *ttext,int tdata):GUIElement(taguix)
{
  this->data=tdata;
  _x=tx;
  _y=ty;
  if ( width > 8 )
    _w = width;
  else
    _w = 8;
  if ( height > 8 )
    _h = height;
  else
    _h = 8;
  active=false;
  content.setText( ttext );

  font=NULL;
  forbidPosChange=false;
  strongkeycapture=true;
  wantpaste=false;
  pasterequest=0;
  paste_from_clipboard = false;
  setCanHandleFocus();
  setAcceptFocus( true );
  passwordMode = false;
  m_read_only = false;
}

StringGadget::StringGadget(AGUIX *taguix,int tx,int ty,int width,const char *ttext,int tdata):GUIElement(taguix)
{
  int bw;

  bw = getBorderWidth();
  this->data=tdata;
  _x=tx;
  _y=ty;
  if ( width > 8 )
    _w = width;
  else
    _w = 8;

  _h = taguix->getCharHeight() + 2 * bw;
  active=false;
  content.setText( ttext );

  font=NULL;
  forbidPosChange=false;
  strongkeycapture=true;
  wantpaste=false;
  pasterequest=0;
  paste_from_clipboard = false;
  setCanHandleFocus();
  setAcceptFocus( true );
  passwordMode = false;
  m_read_only = false;
}

void StringGadget::resize(int tw,int th)
{
  if ( tw < 8 ) tw = 8;
  if ( th < 8 ) th = 8;
  _w=tw;
  _h=th;
  if ( isCreated() == true ) {
    _parent->resizeSubWin(win,tw,th);
  }
  updateWin();
  redraw();
}

int StringGadget::getData() const
{
  return data;
}

void StringGadget::setData(int tdata)
{
  this->data=tdata;
}

void StringGadget::redraw()
{
  if ( isCreated() == true ) {
    prepareBG();

    _aguix->ClearWin(win);
    
    _aguix->setFG( _aguix->getFaceCol_3d_bright() );
    _aguix->DrawLine(win,0,_h-1,0,0);
    _aguix->DrawLine(win,0,0,_w-1,0);
    _aguix->DrawLine(win,2,_h-1-2,_w-1-2,_h-1-2);
    _aguix->DrawLine(win,_w-1-2,_h-1-2,_w-1-2,2);
    _aguix->setFG( _aguix->getFaceCol_3d_dark() );
    _aguix->DrawLine(win,0,_h-1,_w-1,_h-1);
    _aguix->DrawLine(win,_w-1,_h-1,_w-1,0);
    _aguix->DrawLine(win,2,_h-1-2,2,2);
    _aguix->DrawLine(win,2,2,_w-1-2,2);
    textRedraw();
  }
}

void StringGadget::textRedraw()
{
  int minx, maxx;
  int bw = getBorderWidth();
  int inner_x, inner_y;

  if ( isCreated() == false ) return;

  clearTextBG();

  inner_x = inner_y = getBorderWidth();

  int ch;
  if(font==NULL) {
    ch=_aguix->getCharHeight();
  } else {
    ch=font->getCharHeight();
  }

  char *use_text = NULL;
  int use_xoffset = 0, use_selstart = 0, use_selend = 0;

  use_xoffset = content.getXOffset();
  use_selstart = content.getSelStart();
  use_selend = content.getSelEnd();
  use_text = dupstring( content.getText() );

  int use_text_len = strlen( use_text );

  int dx;
  dx=1;
  double f1 = ( ( _h - 2 * bw ) / 2 ) - ( ch / 2 );
  int dy=(int)f1;
  int x1,x2;
  int temp_ch_pos;
  char temp_ch;
  int text_color;

  x1 = ( use_selstart <= use_selend ? use_selstart : use_selend );
  x2 = ( use_selstart > use_selend ? use_selstart : use_selend );
  if ( x2 == use_text_len && use_selstart != use_selend ) {
    UTF8::movePosToPrevChar( use_text, x2 );
  }

  if ( x1 < 0 || x1 > use_text_len ) x1 = 0;
  if ( x2 < 0 || x2 > use_text_len ) x2 = 0;
  if ( x2 < x1 ) x2 = x1;
  
  int restwidth = getInnerWidth() - 2;
  int strwidth, vislen;
  bool cont = true;

  DrawableCont dc( _aguix, win );

  if( use_xoffset < x1 ) {
    // links vom x1 ist noch Text
    temp_ch = use_text[x1];
    temp_ch_pos = x1;
    use_text[x1] = '\0';
    vislen = _aguix->getStrlen4Width( use_text + use_xoffset, restwidth, &strwidth, font );

    // use_xoffset + vislen is equal or less x1 in any case
    if ( use_text[use_xoffset + vislen] != '\0' ) {
      use_text[temp_ch_pos] = temp_ch;
      temp_ch = use_text[use_xoffset + vislen];
      temp_ch_pos = use_xoffset + vislen;
      use_text[temp_ch_pos] = '\0';

      // doesn't fit completely so don't draw anything else
      cont = false;
    }

    if ( vislen > 0 ) {
      if ( active ) {
          text_color = _aguix->getFaces().getColor( "stringgadget-active-fg" );
      } else {
          text_color = _aguix->getFaces().getColor( "stringgadget-normal-fg" );
      }

      _aguix->DrawText( dc, font, use_text + use_xoffset, inner_x + dx, inner_y + dy, text_color );
    }

    use_text[temp_ch_pos] = temp_ch;
    
    dx += strwidth;
    restwidth -= strwidth;
  }

  if ( ( cont == true ) && ( x2 >= use_xoffset ) ) {
    // some part of the selection is visible

    minx = ( x1 < use_xoffset ) ? use_xoffset : x1;
    maxx = x2;
    if ( use_text[maxx] != '\0' ) {
      UTF8::movePosToNextChar( use_text, maxx );
    }

    // draw selection from minx to maxx which can be outside screen

    temp_ch = use_text[maxx];
    temp_ch_pos = maxx;
    use_text[maxx] = '\0';

    vislen = _aguix->getStrlen4Width( use_text + minx, restwidth, &strwidth, font );

    if ( use_text[minx + vislen] != '\0' ) {
      use_text[temp_ch_pos] = temp_ch;
      temp_ch = use_text[minx + vislen];
      temp_ch_pos = minx + vislen;
      use_text[temp_ch_pos] = '\0';

      // doesn't fit completely so don't draw anything else
      cont = false;
    }

    if ( ( vislen > 0 ) || ( ( minx + vislen ) == use_text_len ) ) {
        if(active==true) {
            // draw selection area
            _aguix->setFG( _aguix->getFaces().getColor( "stringgadget-selection-bg" ) );
            _aguix->FillRectangle( win, inner_x + dx, inner_y + dy, strwidth, ch );
        }
        if(active==true) {
            text_color = _aguix->getFaces().getColor( "stringgadget-selection-fg" );
        } else {
            text_color = _aguix->getFaces().getColor( "stringgadget-normal-fg" );
        }
        _aguix->DrawText( dc, font, use_text + minx, inner_x + dx, inner_y + dy, text_color );
    }

    use_text[temp_ch_pos] = temp_ch;
    
    dx += strwidth;
    restwidth -= strwidth;
  }

  if ( ( cont == true ) && ( use_text[x2] != '\0' ) ) {
    // the rest after the selection is possibly visible
    int first_vis_char;
    
    // end of selection can be less than use_xoffset
    first_vis_char = x2;
    UTF8::movePosToNextChar( use_text, first_vis_char );
    first_vis_char = a_max( first_vis_char, use_xoffset );
    
    vislen = _aguix->getStrlen4Width( use_text + first_vis_char, restwidth, &strwidth, font );
    
    if ( vislen > 0 ) {
      use_text[first_vis_char + vislen] = '\0';

      if ( active ) {
          text_color = _aguix->getFaces().getColor( "stringgadget-active-fg" );
      } else {
          text_color = _aguix->getFaces().getColor( "stringgadget-normal-fg" );
      }

      _aguix->DrawText( dc, font, use_text + first_vis_char, inner_x + dx, inner_y + dy, text_color );
    }
  }
  _freesafe(use_text);
  _aguix->Flush();
}

void StringGadget::flush()
{
}

bool StringGadget::isInside(int px,int py) const
{
/*  if((px>x)&&(px<=(x+width))) {
    if((py>y)&&(py<=(y+height))) return true;
  }*/
  return false;
}

bool StringGadget::handleMessage(XEvent *E,Message *msg)
{
    bool returnvalue;
    returnvalue=false;
    int tx,ty;
    AGMessage *agmsg;
    int oldselstart;
  
    if ( isCreated() == false ) return false;

    returnvalue = GUIElement::handleMessage( E, msg );

    if(active==true) {
        if(msg->type==ButtonPress) {
            if ( msg->window != win ) {
                active=false;
                redraw();
                //        returnvalue=true;
                agmsg = AGUIX_allocAGMessage();
                agmsg->type=AG_STRINGGADGET_DEACTIVATE;
                agmsg->stringgadget.sg=this;
                agmsg->stringgadget.ok = false;
                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
            } else {
                if(forbidPosChange==false) {
                    _aguix->queryPointer(msg->window,&tx,&ty);

                    if ( m_click_mode == AGUIX::NO_CLICK ) {
                        m_last_click_time = msg->time;
                        m_click_mode = AGUIX::NORMAL_CLICK;

                        updateSelection( tx, ty, true );
                    } else if ( m_click_mode == AGUIX::NORMAL_CLICK ) {
                        if ( _aguix->isDoubleClick( msg->time, m_last_click_time ) &&
                             isNearFirstClick( tx, ty ) ) {
                            m_last_click_time = msg->time;
                            m_click_mode = AGUIX::DOUBLE_CLICK;

                            updateSelection( tx, ty, false );
                        } else {
                            m_last_click_time = msg->time;
                            m_click_mode = AGUIX::NORMAL_CLICK;

                            updateSelection( tx, ty, true );
                        }
                    } else if ( m_click_mode == AGUIX::DOUBLE_CLICK ) {
                        if ( _aguix->isDoubleClick( msg->time, m_last_click_time ) &&
                             isNearFirstClick( tx, ty ) ) {
                            m_last_click_time = msg->time;
                            m_click_mode = AGUIX::TRIPLE_CLICK;

                            updateSelection( tx, ty, false );
                        } else {
                            m_last_click_time = msg->time;
                            m_click_mode = AGUIX::NORMAL_CLICK;

                            updateSelection( tx, ty, true );
                        }
                    } else {
                        m_last_click_time = msg->time;
                        m_click_mode = AGUIX::NORMAL_CLICK;

                        updateSelection( tx, ty, true );
                    }
                }
            }
        } else {
            if(msg->type==KeyPress) {
                if ( isVisible() == true ) {
                    if ( _parent->isTopParent( msg->window ) == true ) {
                        ignoreRelease=false;
                        int keystate=KEYSTATEMASK(msg->keystate);
                        if((msg->key==XK_Right)||
                           (msg->key==XK_End)||
                           ((msg->key==XK_e)&&(keystate==ControlMask))||
                           ((msg->key==XK_f)&&(keystate==ControlMask))) {
                            if(strongkeycapture==true) returnvalue=true;
                            if(forbidPosChange==false) {
                                if((((keystate==0)||(keystate==ShiftMask))&&
                                    (msg->key==XK_Right))||
                                   ((msg->key==XK_f)&&(keystate==ControlMask))) {
                                    if(content.getCursorPos()<(int)strlen( content.getText() )) {
                                        oldselstart = content.getSelStart();
                                        int new_cur = content.getCursorPos();
                                        UTF8::movePosToNextChar( content.getText(), new_cur );

                                        //TODO if next character is invalid setCursor will go back to prev
                                        //  so effectively don't change the cursor at all
                                        setCursor( new_cur );
                    
                                        content.setSelStart( oldselstart );
                                        if(keystate!=ShiftMask) {
                                            content.setSelStart( content.getCursorPos() );
                                            content.setSelEnd( content.getCursorPos() );
                                        } else {
                                            content.setSelEnd( content.getCursorPos() );
                                            applySelection();
                                        }
                                        textRedraw();
                                        returnvalue=true;
                                    } else if ( ( content.getSelStart() != content.getSelEnd() ) && ( keystate != ShiftMask ) ) {
                                        // selection active and cursor is right
                                        // => deactivate selection
                                        content.setSelStart( content.getCursorPos() );
                                        content.setSelEnd( content.getCursorPos() );
                                        textRedraw();
                                        returnvalue = true;
                                    }
                                } else if(((keystate&Mod1Mask)==Mod1Mask)||
                                          (msg->key==XK_End)||
                                          ((msg->key==XK_e)&&(keystate==ControlMask))) {
                                    oldselstart = content.getSelStart();
                                    setCursor( (int)strlen( content.getText() ) ); 
                                    content.setSelStart( oldselstart );
                                    if((keystate&ShiftMask)==0) {
                                        content.setSelStart( content.getCursorPos() );
                                        content.setSelEnd( content.getCursorPos() );
                                    } else {
                                        content.setSelEnd( content.getCursorPos() );
                                        applySelection();
                                    }
                                    textRedraw();
                                    returnvalue=true;
                                }
                                agmsg = AGUIX_allocAGMessage();
                                agmsg->type=AG_STRINGGADGET_CURSORCHANGE;
                                agmsg->stringgadget.sg=this;
                                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                            }
                        } else if((msg->key==XK_Left)||
                                  (msg->key==XK_Home)||
                                  ((msg->key==XK_a)&&(keystate==ControlMask))||
                                  ((msg->key==XK_b)&&(keystate==ControlMask))) {
                            if(strongkeycapture==true) returnvalue=true;
                            if(forbidPosChange==false) {
                                if((((keystate==0)||(keystate==ShiftMask))&&
                                    (msg->key==XK_Left))||
                                   ((msg->key==XK_b)&&(keystate==ControlMask))) {
                                    if(content.getCursorPos()>0) {
                                        oldselstart = content.getSelStart();
                                        int new_cur = content.getCursorPos();

                                        UTF8::movePosToPrevChar( content.getText(), new_cur );
                                        setCursor( new_cur );

                                        content.setSelStart( oldselstart );
                                        if(keystate!=ShiftMask) {
                                            content.setSelStart( content.getCursorPos() );
                                            content.setSelEnd( content.getCursorPos() );
                                        } else {
                                            content.setSelEnd( content.getCursorPos() );
                                            applySelection();
                                        }
                                        textRedraw();
                                        returnvalue=true;
                                    } else if ( ( content.getSelStart() != content.getSelEnd() ) && ( keystate != ShiftMask ) ) {
                                        // selection active and normal cursor left
                                        // => deactivate selection
                                        content.setSelStart( content.getCursorPos() );
                                        content.setSelEnd( content.getCursorPos() );
                                        textRedraw();
                                        returnvalue = true;
                                    }
                                } else if(((keystate&Mod1Mask)==Mod1Mask)||
                                          (msg->key==XK_Home)||
                                          ((msg->key==XK_a)&&(keystate==ControlMask))) {
                                    content.setXOffset( 0 );
                                    content.setCursorPos( 0 );
                                    if((keystate&ShiftMask)==0) {
                                        content.setSelStart( content.getCursorPos() );
                                        content.setSelEnd( content.getCursorPos() );
                                    } else {
                                        content.setSelEnd( content.getCursorPos() );
                                        applySelection();
                                    }
                                    textRedraw();
                                    returnvalue=true;
                                }
                                agmsg = AGUIX_allocAGMessage();
                                agmsg->type=AG_STRINGGADGET_CURSORCHANGE;
                                agmsg->stringgadget.sg=this;
                                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                            }
                        } else if ( msg->key == XK_Delete ||
                                    ( msg->key == XK_d && keystate == ControlMask ) ) {
                            int len=strlen( content.getText() );
                            if(strongkeycapture==true) returnvalue=true;

                            if ( ! m_read_only ) {
                                if(content.getSelStart()!=content.getSelEnd()) {
                                    removeSelection();

                                    returnvalue = true;
                                    agmsg = AGUIX_allocAGMessage();
                                    agmsg->type = AG_STRINGGADGET_CONTENTCHANGE;
                                    agmsg->stringgadget.sg = this;
                                    msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                                } else if((content.getCursorPos()<len)&&(len>0)) {
                                    content.removeRange( content.getCursorPos(), content.getCursorPos() );
                                    returnvalue=true;
                                    agmsg = AGUIX_allocAGMessage();
                                    agmsg->type=AG_STRINGGADGET_CONTENTCHANGE;
                                    agmsg->stringgadget.sg=this;
                                    msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                                }
                            }
                            textRedraw();
                        } else if ( msg->key == XK_BackSpace || 
                                    ( msg->key == XK_h && keystate == ControlMask ) ) {
                            if(strongkeycapture==true) returnvalue=true;

                            if ( ! m_read_only ) {
                                if(content.getSelEnd()!=content.getSelStart()) {
                                    removeSelection();

                                    returnvalue = true;
                                    agmsg = AGUIX_allocAGMessage();
                                    agmsg->type = AG_STRINGGADGET_CONTENTCHANGE;
                                    agmsg->stringgadget.sg = this;
                                    msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                                } else if(content.getCursorPos()>0) {
                                    int prev_char = content.getCursorPos();
                                    UTF8::movePosToPrevChar( content.getText(), prev_char );

                                    content.removeRange( prev_char, prev_char );

                                    setCursor( content.getCursorPos() );

                                    content.setSelStart( content.getCursorPos() );
                                    content.setSelEnd( content.getCursorPos() );
                                    returnvalue=true;
                                    agmsg = AGUIX_allocAGMessage();
                                    agmsg->type=AG_STRINGGADGET_CONTENTCHANGE;
                                    agmsg->stringgadget.sg=this;
                                    msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                                }
                            }
                            textRedraw();
                        } else if ( msg->key == XK_Return ) {
                            if ( strongkeycapture == true ) returnvalue = true;
                        } else if ( msg->key == XK_x && keystate == ControlMask ) {
                            if ( strongkeycapture == true ) returnvalue = true;

                            if ( ! m_read_only ) {
                                applySelection( true );
                                removeSelection();
                            }

                            textRedraw();
                        } else if ( msg->key == XK_c && keystate == ControlMask ) {
                            if ( strongkeycapture == true ) returnvalue = true;

                            applySelection( true );
                        } else if ( msg->key == XK_v && keystate == ControlMask ) {
                            if ( strongkeycapture == true ) returnvalue = true;

                            if ( ! m_read_only ) {
                                if ( content.getSelStart() != content.getSelEnd() ) {
                                    removeSelection();
                                }
                                insertSelection( true );
                            }

                            textRedraw();
                        } else if ( ( msg->key == XK_f ||
                                      msg->key == XK_F ) && ( keystate & Mod1Mask ) == Mod1Mask ) {
                            if ( strongkeycapture == true ) returnvalue = true;

                            if ( ! forbidPosChange ) {
                                int oldselstart = content.getSelStart();

                                jumpToNextWord();

                                content.setSelStart( oldselstart );
                                if ( ! ( keystate & ShiftMask ) ) {
                                    content.setSelStart( content.getCursorPos() );
                                    content.setSelEnd( content.getCursorPos() );
                                } else {
                                    content.setSelEnd( content.getCursorPos() );
                                    applySelection();
                                }
                                textRedraw();
                            }
                        } else if ( ( msg->key == XK_b ||
                                      msg->key == XK_B ) && ( keystate & Mod1Mask ) == Mod1Mask ) {
                            if ( strongkeycapture == true ) returnvalue = true;

                            if ( ! forbidPosChange ) {
                                int oldselstart = content.getSelStart();

                                jumpToPrevWord();

                                content.setSelStart( oldselstart );
                                if ( ! ( keystate & ShiftMask ) ) {
                                    content.setSelStart( content.getCursorPos() );
                                    content.setSelEnd( content.getCursorPos() );
                                } else {
                                    content.setSelEnd( content.getCursorPos() );
                                    applySelection();
                                }
                                textRedraw();
                            }
                        } else if ( IsModifierKey( msg->key ) ||
                                    IsCursorKey( msg->key ) ||
                                    IsPFKey( msg->key ) ||
                                    IsFunctionKey( msg->key ) ||
                                    IsMiscFunctionKey( msg->key ) ||
                                    ( ( msg->key >= XK_BackSpace ) && ( msg->key <= XK_Escape ) ) ) {
                            // catch this special keys even they are not handled
                            // so they will not be added to the text
                        } else if ( strlen( msg->keybuf ) > 0 && ( keystate & ControlMask ) == 0 ) {
                            if ( ! m_read_only ) {
                                if ( content.getSelStart() != content.getSelEnd() ) {
                                    removeSelection();
                                }
                                if ( UTF8::isValidCharacterString( msg->keybuf ) == true ) {
                                    content.insertAtCursor( msg->keybuf );
                                    int nr_of_char = UTF8::getNumberOfCharacters( msg->keybuf );
                                    int new_cur = content.getCursorPos();
                                    while ( nr_of_char > 0 ) {
                                        UTF8::movePosToNextChar( content.getText(), new_cur );
                                        nr_of_char--;
                                    }
                                    setCursor( new_cur );
                                    content.setSelStart( content.getCursorPos() );
                                    content.setSelEnd( content.getCursorPos() );
                                }
                                textRedraw();
                                agmsg = AGUIX_allocAGMessage();
                                agmsg->type = AG_STRINGGADGET_CONTENTCHANGE;
                                agmsg->stringgadget.sg = this;
                                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                            }
                            returnvalue = true;
                        }
                    }
                }
            } else if((msg->type==KeyRelease)&&(ignoreRelease==false)) {
                if ( isVisible() == true ) {
                    if ( _parent->isTopParent( msg->window ) == true ) {
                        int keystate=KEYSTATEMASK(msg->keystate);
                        if((msg->key==XK_Return)||(msg->key==XK_Escape)) {
                            active=false;
                            content.setSelStart( content.getCursorPos() );
                            content.setSelEnd( content.getCursorPos() );
                            redraw();
                            returnvalue=true;
                            agmsg = AGUIX_allocAGMessage();
                            agmsg->type=AG_STRINGGADGET_DEACTIVATE;
                            agmsg->stringgadget.sg=this;
                            agmsg->stringgadget.ok = ( msg->key == XK_Return ) ? true : false;
                            msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                            if(msg->key==XK_Return) {
                                agmsg = AGUIX_allocAGMessage();
                                agmsg->type=AG_STRINGGADGET_OK;
                                agmsg->stringgadget.sg=this;
                                agmsg->stringgadget.keystate = keystate;
                                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                            } else {
                                agmsg = AGUIX_allocAGMessage();
                                agmsg->type=AG_STRINGGADGET_CANCEL;
                                agmsg->stringgadget.sg=this;
                                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                            }
                        } else if((msg->key==XK_Right)||
                                  (msg->key==XK_End)||
                                  ((msg->key==XK_e)&&(keystate==ControlMask))||
                                  ((msg->key==XK_f)&&(keystate==ControlMask))) {
                            if(strongkeycapture==true) returnvalue=true;
                        } else if((msg->key==XK_Left)||
                                  (msg->key==XK_Home)||
                                  ((msg->key==XK_a)&&(keystate==ControlMask))||
                                  ((msg->key==XK_b)&&(keystate==ControlMask))) {
                            if(strongkeycapture==true) returnvalue=true;
                        } else if((msg->key==XK_Delete)||((msg->key==XK_d)&&(keystate==ControlMask))) {
                            if(strongkeycapture==true) returnvalue=true;
                        } else if((msg->key==XK_BackSpace)||((msg->key==XK_h)&&(keystate==ControlMask))) {
                            if(strongkeycapture==true) returnvalue=true;
                        } else if ( IsModifierKey( msg->key ) ||
                                    IsCursorKey( msg->key ) ||
                                    IsPFKey( msg->key ) ||
                                    IsFunctionKey( msg->key ) ||
                                    IsMiscFunctionKey( msg->key ) ||
                                    ( ( msg->key >= XK_BackSpace ) && ( msg->key <= XK_Escape ) ) ) {
                            // Some of these keys could create a char in keybuf but because we don't
                            // handle them here do nothing
                        } else if ( ( strlen( msg->keybuf ) > 0 ) && ( keystate != ControlMask ) ) {
                            if(strongkeycapture==true) returnvalue=true;
                        }
                    }
                }
            } else if(msg->type==ButtonRelease) {
                if ( ! m_read_only && msg->button == Button2 ) {
                    insertSelection();
                    redraw();
                }
            } else if ( msg->type == MotionNotify &&
                        msg->window == win &&
                        ( msg->keystate & Button1Mask ) == Button1Mask ) {
                _aguix->queryPointer(msg->window,&tx,&ty);

                updateSelection( tx, ty, false );
            }
        }
    } else if(msg->type==ButtonPress) {
        if ( msg->window == win ) {
            takeFocus();
            active=true;
            //      returnvalue=true;
            agmsg = AGUIX_allocAGMessage();
            agmsg->type=AG_STRINGGADGET_ACTIVATE;
            agmsg->stringgadget.sg=this;
            msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
            if(forbidPosChange==false) {
                _aguix->queryPointer(msg->window,&tx,&ty);

                m_last_click_time = msg->time;
                m_click_mode = AGUIX::NORMAL_CLICK;

                updateSelection( tx, ty, true );
            }
            redraw();
        }
    }
    if(msg->type==Expose) {
        if ( msg->window == win ) {
            redraw();
        }
    }
    return returnvalue;
}

void StringGadget::setText(const char *new_text)
{
  content.setText( new_text );

  content.setSelStart( content.getCursorPos() );
  content.setSelEnd( content.getCursorPos() );
  setXOffset( content.getXOffset() );

  textRedraw();
}

const char *StringGadget::getText() const
{
  return content.getRealText();
}

bool StringGadget::isActive() const
{
  return active;
}

void StringGadget::updateWin()
{
  int i1;

  if ( isCreated() == true ) {
    i1 = a_min( content.getSelStart(), content.getSelEnd() );
    setXOffset( i1 );
    textRedraw();
  }
}

void StringGadget::activate()
{
  active=true;
  ignoreRelease=true;
  redraw();
}

void StringGadget::deactivate()
{
  active=false;
  redraw();
}

void StringGadget::applySelection( bool both_buffers )
{
  int x1,x2;
  char *buffer;

  if ( passwordMode == false ) {
    x1=(content.getSelStart()<=content.getSelEnd()?content.getSelStart():content.getSelEnd());
    x2=(content.getSelStart()>content.getSelEnd()?content.getSelStart():content.getSelEnd());

    if ( x1 < x2 ) {
      // x2 is the end character inside the selection so
      // move to next character and use this position as
      // limit
      if ( x2 < (int)strlen( content.getText() ) ) {
        UTF8::movePosToNextChar( content.getText(), x2 );
      }
      
      buffer = dupstring( content.getText() + x1 );
      buffer[x2 - x1] = 0;
      _aguix->startCut( this, buffer, false );
      if ( both_buffers ) {
          _aguix->startCut( this, buffer, true );
      }
      _freesafe( buffer );
    }
  }
}

void StringGadget::removeSelection()
{
  int x1,x2;
  x1=(content.getSelStart()<=content.getSelEnd()?content.getSelStart():content.getSelEnd());
  x2=(content.getSelStart()>content.getSelEnd()?content.getSelStart():content.getSelEnd());

  content.removeRange( x1, x2 );

  content.setCursorPos( x1 );
  content.setSelStart( x1 );
  content.setSelEnd( x1 );
  if ( content.getCursorPos() < content.getXOffset() ) content.setXOffset( content.getCursorPos() );
}

void StringGadget::insertSelection( bool from_clipboard )
{
    if ( isCreated() == false ) return;

    if ( _aguix->amiOwner( from_clipboard ) == true ) {
        std::string s2 = AGUIXUtils::remove_char( _aguix->getCutBuffer( from_clipboard ), '\n' );
        content.insertAtCursor( s2.c_str() );

        if ( from_clipboard ) {
            setCursor( content.getCursorPos() + s2.length() );
        }
    } else {
        _aguix->requestCut( getWindow(),
                            false,
                            from_clipboard,
                            this, NULL,
                            AGUIX::CLIPBOARD_REQUEST_STRING );
        wantpaste = true;
        pasterequest = time( NULL );
        paste_from_clipboard = from_clipboard;
    }
}

int StringGadget::setFont( const char *fontname )
{
  font=_aguix->getFont(fontname);
  updateWin();
  if(font==NULL) return -1;
  return 0;
}

const char *StringGadget::getType() const
{
  return type;
}

bool StringGadget::isType(const char *qtype) const
{
  if(strcmp(type,qtype)==0) return true;
  return false;
}

bool StringGadget::isParent(Window child) const
{
  if ( isCreated() == false ) return false;
  if(child==win) return true;
  return false;
}

int StringGadget::getXOffset() const
{
  return content.getXOffset();
}

void StringGadget::setXOffset( int new_pos )
{
  int l, strwidth, newx;

  content.setXOffset( new_pos );

  newx = content.getXOffset();
  while ( newx >= 0 ) {
    l = _aguix->getStrlen4Width( content.getText() + newx, getInnerWidth() - 2, &strwidth, font );
    
    if ( ( l + newx ) == (int)strlen( content.getText() ) ) {
      // everything visible reduce xoffset
      // lower means not everything is visible, higher is not possible
      content.setXOffset( newx );
      UTF8::movePosToPrevChar( content.getText(), newx );
      if ( content.getXOffset() == newx ) break; // no prev char
    } else break;
  }

  if ( content.getXOffset() < 0 ) content.setXOffset( 0 );
  textRedraw();
}

int StringGadget::getCursor() const
{
  return content.getCursorPos();
}

void StringGadget::setCursor( int new_pos )
{
  int newx, l, strwidth;

  content.setCursorPos( new_pos );

  content.setSelStart( content.getCursorPos() );
  content.setSelEnd( content.getCursorPos() );

  if ( ( content.getCursorPos() - content.getXOffset() ) < 0 )
    content.setXOffset( content.getCursorPos() );

  newx = content.getXOffset();
  for (;;) {
    l = _aguix->getStrlen4Width( content.getText() + newx, getInnerWidth() - 2, &strwidth, font );
    if ( ( l + newx ) == (int)strlen( content.getText() ) ) break;
    else if ( content.getCursorPos() >= ( l + newx ) ) {
      UTF8::movePosToNextChar( content.getText(), newx );
    } else break;
  }

  setXOffset( newx );
}

bool StringGadget::isPosChangeForbidden() const
{
  return forbidPosChange;
}

void StringGadget::setForbidPosChange(bool nv)
{
  forbidPosChange=nv;
}

void StringGadget::setStrongKeyCapture(bool nv)
{
  strongkeycapture=nv;
}

void StringGadget::paste( const unsigned char *buf )
{
  if((wantpaste==true)&&(difftime(time(NULL),pasterequest)<10.0)) {
    if(forbidPosChange==false) {
      content.setSelStart( content.getCursorPos() );
      content.setSelEnd( content.getCursorPos() );

      std::string s2 = AGUIXUtils::remove_char( (const char *)buf, '\n' );
      content.insertAtCursor( s2.c_str() );
      
      if ( paste_from_clipboard ) {
          setCursor( content.getCursorPos() + s2.length() );
      }

      redraw();
    }
  }
}

void StringGadget::cancelpaste()
{
  wantpaste=false;
  pasterequest=0;
}

void StringGadget::cancelcut()
{
  content.setSelStart( content.getCursorPos() );
  content.setSelEnd( content.getCursorPos() );
  redraw();
}

void StringGadget::doCreateStuff()
{
  GUIElement::doCreateStuff();
}

void StringGadget::doDestroyStuff()
{
  GUIElement::doDestroyStuff();
}

void StringGadget::insertAtCursor( const char *str )
{
  if ( str == NULL ) return;

  content.insertAtCursor( str );
  
  textRedraw();
}

void StringGadget::selectAll( bool copy_selection )
{
  content.setCursorPos( (int)strlen(content.getText()) );
  setXOffset( content.getCursorPos() );
  content.setSelStart( 0 );
  content.setSelEnd( content.getCursorPos() );

  if ( copy_selection == true ) {
      applySelection();
  }
  redraw();
}

void StringGadget::setPasswordMode( bool nv )
{
  passwordMode = nv;
  content.setPasswordMode( nv );
  redraw();
}

void StringGadget::lostFocus()
{
  if ( active == true ) {
    active = false;
    redraw();
    AGMessage *agmsg = AGUIX_allocAGMessage();
    agmsg->type = AG_STRINGGADGET_DEACTIVATE;
    agmsg->stringgadget.sg = this;
    agmsg->stringgadget.ok = false;
    msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
  }
}

void StringGadget::gotFocus()
{
  if ( active == false ) {
    activate();
    AGMessage *agmsg = AGUIX_allocAGMessage();
    agmsg->type = AG_STRINGGADGET_ACTIVATE;
    agmsg->stringgadget.sg = this;
    msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
  }
}

int StringGadget::getInnerWidth() const
{
  int bw = getBorderWidth();
  return _w - 2 * bw;
}

void StringGadget::prepareBG( bool force )
{
  if ( isCreated() == false ) return;
  if ( win == 0 ) return;

  _aguix->SetWindowBG( win, _parent->getBG() );
}

int StringGadget::getBorderWidth() const
{
  int bw;

  bw = 4;
  
  return bw;
}

int StringGadget::clearTextBG()
{
  if ( isCreated() == false ) return 1;

  int bw = getBorderWidth();
  int newbg;
  
  if ( active == true ) {
      newbg = _aguix->getFaces().getColor( "stringgadget-active-bg" );
  } else {
      newbg = _aguix->getFaces().getColor( "stringgadget-normal-bg" );
  }
  
  _aguix->setFG( newbg );
  _aguix->FillRectangle( win, bw, bw, getInnerWidth(), _h - 2 * bw );
  return 0;
}

StringGadget::SGContent::SGContent()
{
  real_text = "";
  UTF8::buildCharacterLookupList( real_text.c_str(), real_char2byte_lookup );

  pw_text = "";
  UTF8::buildCharacterLookupList( pw_text.c_str(), pw_char2byte_lookup );
  
  xoffset = selstart = selend = cursorpos = 0;
  password_mode = false;
}

void StringGadget::SGContent::setXOffset( int np )
{
  if ( np != xoffset && np >= 0 ) {
    fixPosition( np );
    xoffset = np;
  }
}

void StringGadget::SGContent::setSelStart( int np )
{
  if ( np != selstart && np >= 0 ) {
    fixPosition( np );
    selstart = np;
  }
}

void StringGadget::SGContent::setSelEnd( int np )
{
  if ( np != selend && np >= 0 ) {
    fixPosition( np );
    selend = np;
  }
}

void StringGadget::SGContent::setCursorPos( int np )
{
  if ( np != cursorpos && np >= 0 ) {
    fixPosition( np );
    cursorpos = np;
  }
}

void StringGadget::SGContent::setPasswordMode( bool mode )
{
  if ( mode == password_mode ) return;
  
  if ( mode == true ) {
    xoffset = real2pwPos( xoffset );
    cursorpos = real2pwPos( cursorpos );
    selstart = real2pwPos( selstart );
    selend = real2pwPos( selend );
  } else {
    xoffset = pw2realPos( xoffset );
    cursorpos = pw2realPos( cursorpos );
    selstart = pw2realPos( selstart );
    selend = pw2realPos( selend );
  }
  password_mode = mode;
}

const char *StringGadget::SGContent::getText() const
{
  if ( password_mode == true ) return pw_text.c_str();
  return real_text.c_str();
}

int StringGadget::SGContent::getXOffset() const
{
  return xoffset;
}

int StringGadget::SGContent::getSelStart() const
{
  return selstart;
}

int StringGadget::SGContent::getSelEnd() const
{
  return selend;
}

int StringGadget::SGContent::getCursorPos() const
{
  return cursorpos;
}

void StringGadget::SGContent::insertAtCursor( const char *text )
{
  if ( text == NULL ) return;
  
  if ( UTF8::isValidCharacterString( text ) == false ) return;

  int pos = getCursorPos();
  std::string new_text = real_text;
  
  if ( password_mode == true ) {
    pos = pw2realPos( pos );
  }
  
  new_text.insert( pos, text );
  setText( new_text.c_str() );
}

void StringGadget::SGContent::setText( const char *text )
{
  if ( text == NULL ) return;

  if ( UTF8::isValidCharacterString( text ) == false ) return;

  real_text = text;

  real_char2byte_lookup.clear();
  UTF8::buildCharacterLookupList( real_text.c_str(), real_char2byte_lookup );

  pw_text = "";
  pw_char2byte_lookup.clear();

  int l = real_char2byte_lookup.size() - 1;
  pw_text = std::string( l, '*' );
  UTF8::buildCharacterLookupList( pw_text.c_str(), pw_char2byte_lookup );

  fixPositions();
}

const char *StringGadget::SGContent::getRealText() const
{
  return real_text.c_str();
}

int StringGadget::SGContent::pw2realPos( int pos )
{
  int p = UTF8::findCharacterPosition( pos, pw_char2byte_lookup );
  return real_char2byte_lookup[p];
}

int StringGadget::SGContent::real2pwPos( int pos )
{
  int p = UTF8::findCharacterPosition( pos, real_char2byte_lookup );
  return pw_char2byte_lookup[p];
}

void StringGadget::SGContent::removeRange( int start, int end )
{
  if ( start > end ) return;
  if ( start < 0 ) return;

  std::string s;

  if ( password_mode == true ) {
    s = pw_text;
  } else {
    s = real_text;
  }
  
  if ( end > (int)s.length() ) return;
  
  if ( end < (int)s.length() ) {
    UTF8::movePosToNextChar( s.c_str(), end );
  }

  // start and end are now valid position in either real or
  // password text depending on current mode
  // however, we want to remove the bytes from the real
  // text and update the password text accordingly so
  // convert the position to real_text positions

  int real_start = start;
  int real_end = end;

  if ( password_mode == true ) {
    real_start = pw2realPos( start );
    real_end = pw2realPos( end );
  }
  
  int real_bytes = real_end - real_start;

  s = real_text;
  s.erase( real_start, real_bytes );

  // okay, text is changed but before actually setting it
  // we will update the position so setText doesn't change them

  int bytes = end - start;

  // start, end and bytes are positions relative to current text (pw or real)
  // just as xoffset/cursorpos/selstart and selend are so use the former to
  // fix the latter
  if ( xoffset > start ) {
    if ( xoffset < end )
      xoffset = start;
    else
      xoffset -= bytes;
  }
  if ( cursorpos > start ) {
    if ( cursorpos < end )
      cursorpos = start;
    else
      cursorpos -= bytes;
  }
  if ( selstart > start ) {
    if ( selstart < end )
      selstart = start;
    else
      selstart -= bytes;
  }
  if ( selend > start ) {
    if ( selend < end )
      selend = start;
    else
      selend -= bytes;
  }

  setText( s.c_str() );
}

void StringGadget::SGContent::fixPositions()
{
  fixPosition( cursorpos );
  fixPosition( selstart );
  fixPosition( selend );
  fixPosition( xoffset );
}

void StringGadget::SGContent::fixPosition( int &pos )
{
  std::string s;

  if ( password_mode == true ) {
    s = pw_text;
  } else {
    s = real_text;
  }

  if ( pos > (int)s.length() )
    pos = (int)s.length();

  if ( pos < 0 )
    pos = 0;
  
  if ( UTF8::isValidCharacter( s.c_str() + pos ) == false ) {
    UTF8::movePosToPrevChar( s.c_str(), pos );
  }
}

void StringGadget::changeHeightForCurFont()
{
  int ch;
  if ( font == NULL ) {
    ch = _aguix->getCharHeight();
  } else {
    ch = font->getCharHeight();
  }
  resize( getWidth(), ch + 2 * getBorderWidth() );
}

void StringGadget::setSelection( int start, int end, bool copy_selection )
{
    content.setSelStart( start );
    content.setSelEnd( end );

    if ( copy_selection == true ) {
        applySelection();
    }
    redraw();
}

void StringGadget::jumpToNextWord()
{
    int new_cur = content.getCursorPos();

    //TODO this is not utf8 safe

    while ( new_cur < (int)strlen( content.getText() ) &&
            ! std::isalnum( *(content.getText() + new_cur) ) ) {
        UTF8::movePosToNextChar( content.getText(), new_cur );
    }

    while ( new_cur < (int)strlen( content.getText() ) &&
            std::isalnum( *(content.getText() + new_cur) ) ) {
        UTF8::movePosToNextChar( content.getText(), new_cur );
    }

    setCursor( new_cur );
}

void StringGadget::jumpToPrevWord()
{
    int new_cur = content.getCursorPos();

    if ( new_cur > 0 ) {
        UTF8::movePosToPrevChar( content.getText(), new_cur );
    }

    //TODO this is not utf8 safe

    while ( new_cur > 0 &&
            ! std::isalnum( *(content.getText() + new_cur) ) ) {
        UTF8::movePosToPrevChar( content.getText(), new_cur );
    }

    int last_valid_pos = new_cur;

    while ( new_cur > 0 &&
            std::isalnum( *(content.getText() + new_cur) ) ) {

        last_valid_pos = new_cur;

        UTF8::movePosToPrevChar( content.getText(), new_cur );
    }

    setCursor( last_valid_pos );
}

void StringGadget::setReadOnly( bool nv )
{
    m_read_only = nv;
}

bool StringGadget::isNearFirstClick( int mx, int my ) const
{
    if ( labs( mx - m_first_click_pos.mx ) < 5 ) {
        return true;
    }
    return false;
}

void StringGadget::updateSelection( int mx, int my, bool initial )
{
    int bw = getBorderWidth();
    mx -= bw;
    my -= bw;

    if ( initial ) {
        m_first_click_pos = { mx, content.getXOffset() };
        auto first = getSelectionElement( m_first_click_pos.mx, m_first_click_pos.xoffset );
        content.setCursorPos( first.second );
        content.setSelStart( first.first );
        content.setSelEnd( first.second );
        redraw();
        return;
    }

    if ( m_click_mode == AGUIX::NO_CLICK ) return;

    auto first = getSelectionElement( m_first_click_pos.mx, m_first_click_pos.xoffset );
    auto second = getSelectionElement( mx, content.getXOffset() );

    if ( first.first < second.first ||
         ( first.first == second.first &&
           first.second < second.second ) ) {
        content.setCursorPos( second.second );
        content.setSelStart( first.first );
        content.setSelEnd( second.second );
    } else {
        content.setCursorPos( first.second );
        content.setSelStart( second.first );
        content.setSelEnd( first.second );
    }

    applySelection();

    if ( mx < 0 && content.getXOffset() > 0 ) {
        int val = content.getXOffset();
        UTF8::movePosToPrevChar( content.getText(), val );
        content.setXOffset( val );
    } else if ( mx >= getInnerWidth() ) {
        int l, strwidth;
        l = _aguix->getStrlen4Width( content.getText() + content.getXOffset(), getInnerWidth() - 2, &strwidth, font );

        if ( l + content.getXOffset() < content.getCursorPos() ) {
            int val = content.getXOffset();
            UTF8::movePosToNextChar( content.getText(), val );
            content.setXOffset( val );
        }
    }

    redraw();
}

std::pair< int, int > StringGadget::getSelectionElement( const int mx,
                                                         const int xoffset ) const
{
    int l, strwidth;
    const char *str = content.getText();
    int str_len = (int)strlen( str );
    
    l = _aguix->getStrlen4Width( str + xoffset, a_max( mx - 2, 0 ), &strwidth, font );
	  
    int cursorpos = l + xoffset;
    if ( cursorpos > str_len ) cursorpos = str_len;
    if ( cursorpos < 0 ) cursorpos = 0;

    if ( m_click_mode == AGUIX::NORMAL_CLICK ) {
        return { cursorpos, cursorpos };
    }

    if ( m_click_mode == AGUIX::TRIPLE_CLICK ) {
        return { 0, strlen( str ) };
    }

    int start_pos = cursorpos;
    int end_pos = cursorpos;
    int word_start = start_pos;
    int word_end = end_pos;

    for (;;) {
        if ( std::isspace( str[ start_pos ] ) ||
             str[ start_pos ] == '/' ) {
            break;
        }

        word_start = start_pos;

        UTF8::movePosToPrevChar( str, start_pos );

        if ( word_start == start_pos ) {
            break;
        }
    }

    for (;;) {
        if ( std::isspace( str[ end_pos ] ) ||
             str[ end_pos ] == '/' ) {
            break;
        }

        word_end = end_pos;

        if ( end_pos >= str_len) {
            break;
        }

        UTF8::movePosToNextChar( str, end_pos );

        if ( word_end == end_pos ) {
            break;
        }
    }

    return { word_start, word_end };
}
