/*
     PLIB - A Suite of Portable Game Libraries
     Copyright (C) 1998,2002  Steve Baker

     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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

     For further information visit http://plib.sourceforge.net

     $Id: puAuxLargeInput.cxx 2161 2010-02-27 04:06:53Z fayjf $
*/


#include "puAuxLocal.h"

UL_RTTI_DEF2(puaLargeInput,puInputBase,puGroup)


// Callbacks from the internal widgets

static void puaLargeInputHandleRightSlider ( puObject *slider )
{
  puaLargeInput* text = (puaLargeInput*) slider->getUserData () ;
  text->setSliderPosition ( 1 - slider->getFloatValue () ) ;
}

// Private function from the widget itself

void puaLargeInput::normalizeCursors ( void )
{
  puInputBase::normalizeCursors () ;

  // Set the top line in the window so that the last line is at the bottom of the window
  if ( top_line_in_window > num_lines - lines_in_window + 2 )
    top_line_in_window = num_lines - lines_in_window + 2 ;

  if ( top_line_in_window < 0 ) top_line_in_window = 0 ;
}

void puaLargeInput::removeSelectRegion ( void )
{
  puInputBase::removeSelectRegion () ;
  updateGeometry () ;
}


// Public functions from the widget itself

puaLargeInput::puaLargeInput ( int x, int y, int w, int h, int arrows, int sl_width, int wrap_text ) :
    puInputBase (), puGroup ( x, y )
{
  setColour ( PUCOL_MISC, 0.1f, 0.1f, 1.0f ) ; // Colour of the 'I' bar cursor

  // Set the variables

  type |= PUCLASS_LARGEINPUT ;
  num_lines = 1 ;
  slider_width = sl_width ;
  top_line_in_window = 0 ;
  lines_in_window = 1 ;
  max_width = 0 ;
  widget = this ;

  // Set up the widgets

  frame = new puFrame ( 0, 0, w, h ) ;

  // filler for the little square in the lower right corner when both sliders are visible
  plug = new puFrame ( 0, 0, slider_width, slider_width ) ;
  plug->setStyle ( -PUSTYLE_SMALL_BEVELLED ) ;
  plug->setBorderThickness ( 2 ) ;

  if (wrap_text )
    bottom_slider = NULL ;
  else
  {
    bottom_slider = new puaScrollBar ( 0, 0, slider_width, (arrows / 10) & 3, FALSE, slider_width ) ;
    bottom_slider->setValue ( 0.0f ) ;   // All the way to the left
  }

  right_slider = new puaScrollBar ( 0, 0, slider_width, (arrows % 10) & 3, TRUE, slider_width ) ;
  right_slider->setValue ( 1.0f ) ;    // All the way to the top
  right_slider->setUserData ( this ) ;
  right_slider->setCallback ( puaLargeInputHandleRightSlider ) ;

  setValue ( "" ) ;
  setSize ( w, h ) ;

  close  () ;
  reveal () ;
}

void puaLargeInput::setSize ( int w, int h )
{
  puGroup::setSize ( w, h ) ;
  if ( w < 2 * slider_width ) w = 2 * slider_width ;
  if ( h < 2 * line_height ) h = 2 * line_height ;
  frame->setSize ( w, h ) ;
  abox.max[0] = abox.min[0] + w ;
  abox.max[1] = abox.min[1] + h ;
  updateGeometry () ;
}

void puaLargeInput::setSliderPosition ( float fraction )
{
  if ( num_lines > lines_in_window )
    top_line_in_window = int ( ( num_lines - lines_in_window ) * fraction + 0.5f ) ;
  else
    top_line_in_window = 0 ;
  updateGeometry () ;
}

void puaLargeInput::updateGeometry ( void )
{
  enum { HORIZONTAL = 1, VERTICAL = 2, BOTH = 3 } ;
  int w = abox.max[0] - abox.min[0] ;
  int h = abox.max[1] - abox.min[1] ;

  int frame_width = getStyle() == PUSTYLE_BOXED ? getBorderThickness() : 0 ;
  hgap = int(legendFont.getFloatStringWidth(" ")) + frame_width ;
  vgap = int(legendFont.getStringHeight()) / 2 + frame_width ;
  line_height = legendFont.getStringHeight() + legendFont.getStringDescender() + 1 ;
  input_width = w - hgap - hgap - frame_width ;
  input_height = h - vgap - vgap - frame_width ;

  // determine which sliders are necessary
  int slider = 0 ;
  if ( bottom_slider ) {  // no line wrapping
    if ( max_width > input_width ) {
      slider |= HORIZONTAL ;
      input_height -= slider_width ;
    }
    if ( num_lines > input_height / line_height ) {
      slider |= VERTICAL ;
      input_width -= slider_width ;
    }
    if ( max_width > input_width && !(slider & HORIZONTAL) ) {
      slider |= HORIZONTAL ;
      input_height -= slider_width ;
    }
    if ( num_lines > input_height / line_height && !(slider & VERTICAL) ) {
      slider |= VERTICAL ;
      input_height -= slider_width ;
    }

  } else {              // with line wrapping
    delete [] displayed_text ;
    displayed_text = wrapText ( input_width, &num_lines, &max_width ) ;

    if ( num_lines > input_height / line_height ) {
      slider |= VERTICAL ;
      input_width -= slider_width ;

      delete [] displayed_text ;
      displayed_text = wrapText( input_width, &num_lines, &max_width ) ;
    }
  }

  // horizontal slider
  if ( bottom_slider ) {
    bottom_slider->setSize ( w - ( slider & VERTICAL ? slider_width : 0 ), slider_width ) ;
    float page_step = 1.0f, line_step = 1.0f ;
    if ( max_width > input_width ) {
      page_step = float(input_width) / ( max_width - input_width ) ;
      line_step = legendFont.getFloatStringWidth( "M" ) / ( max_width - input_width ) ;
      bottom_slider->setSliderFraction ( float(input_width) / max_width ) ;
    } else {
      bottom_slider->setSliderFraction ( 1.0f ) ;
      bottom_slider->setValue ( 0.0f );
    }

    bottom_slider->setPageStepSize ( page_step ) ;
    bottom_slider->setLineStepSize ( line_step ) ;

    if ( slider & HORIZONTAL )
      bottom_slider->reveal () ;
    else
      bottom_slider->hide () ;
  }

  // vertical slider
  lines_in_window = input_height / line_height ;

  // if the slider was in bottom position, make sure the last line is shown after lines were added
  if ( right_slider->getFloatValue() <= 0.0f && right_slider->getSliderFraction() < 1.0f )
    top_line_in_window = num_lines ;

  if ( top_line_in_window + lines_in_window > num_lines )
    top_line_in_window = num_lines - lines_in_window ;

  if ( top_line_in_window < 0 )
      top_line_in_window = 0 ;

  right_slider->setPosition ( w - slider_width, slider & HORIZONTAL ? slider_width : 0 ) ;
  right_slider->setSize ( slider_width, h - ( slider & HORIZONTAL ? slider_width : 0 ) ) ;
  right_slider->setSliderFraction ( float(lines_in_window) / num_lines ) ;
  float page_step = 1.0f ;
  if ( num_lines > lines_in_window )
    page_step = float(lines_in_window) / ( num_lines - lines_in_window ) ;

  right_slider->setPageStepSize ( page_step ) ;
  right_slider->setLineStepSize ( page_step / lines_in_window ) ;

  float val = 0.0f ;
  if ( num_lines > lines_in_window )
      val = 1.0f - float(top_line_in_window) / (num_lines - lines_in_window ) ;
  right_slider->setValue (val) ;

  if ( slider & VERTICAL )
    right_slider->reveal () ;
  else
    right_slider->hide () ;

  // corner filler
  plug->setPosition ( w - slider_width, 0 ) ;
  if ( slider == BOTH )
    plug->reveal () ;
  else
    plug->hide () ;
}

void puaLargeInput::setSelectRegion ( int s, int e )
{
  select_start_position = s ;
  select_end_position   = e ;
  char *lin_ptr = getText() ;
  char *text_start = lin_ptr ;

  if ( num_lines > lines_in_window )
  {
    int select_start_line = 0 ;
    while ( lin_ptr && ( lin_ptr <= text_start + select_start_position ) )  // Count the lines
    {
      select_start_line++ ;
      lin_ptr = strchr ( lin_ptr+1, '\n' ) ;
    }

    int select_end_line = select_start_line ;
    while ( lin_ptr && ( lin_ptr <= text_start + select_end_position ) )  // Count the lines
    {
      select_end_line++ ;
      lin_ptr = strchr ( lin_ptr+1, '\n' ) ;
    }

    if ( select_end_line > top_line_in_window + lines_in_window )
      top_line_in_window = select_end_line - lines_in_window - 1 ;

    if ( select_start_line < top_line_in_window )
      top_line_in_window = select_start_line - 1 ;

    if ( top_line_in_window < 0 ) top_line_in_window = 0 ;

    right_slider->setValue ( num_lines - lines_in_window - top_line_in_window ) ;
  }
}

void  puaLargeInput::selectEntireLine ( void )
{
  char *temp_text = getText() ;

  if ( select_start_position < 0 )
      select_start_position = 0 ;

  if ( ( *(temp_text + select_start_position + 1) != '\n' ) &&
       ( *(temp_text + select_start_position) != '\0' ) )
    select_start_position ++ ;

  while ( ( select_start_position > 0 ) && ( *(temp_text + select_start_position) != '\n' ) )
    select_start_position -- ;

  if ( select_start_position > 0 )
    select_start_position++ ;

  select_end_position = int ( strchr ( temp_text + select_end_position, '\n' ) + 1 - temp_text) ;
  if ( select_end_position <= 1 ) select_end_position = strlen ( temp_text ) ;
  //else select_end_position = int ( select_end_position - temp_text ) ;/** Needs real fixing **/

  puPostRefresh () ;
}

void  puaLargeInput::addNewLine ( const char *l )
{
  char *text = getStringValue () ;
  if ( cursor_position > 0 )  // If not at start of line, go to start of next line
    cursor_position = int (strchr ( text + cursor_position - 1, '\n' ) - text + 1) ;

  select_end_position = select_start_position = cursor_position ;
  addText ( l ) ;
}

void  puaLargeInput::addText ( const char *l )
{
  char *text = getStringValue () ;

  if ( l == NULL ) return ;

  int l_len = strlen ( l ) ;
  int text_len = strlen ( text ) ;
  int length = l_len + text_len  /* Length of the final string */
               + select_start_position - select_end_position + 2 ;
  if ( *(l+l_len-1) == '\n' ) length -- ;           // Decrement "length" for each final
  if ( text[select_end_position] == '\n' ) length -- ;  // carriage return already there

  char *temp_text = new char [ length ] ;

  memcpy ( temp_text, text, select_start_position ) ;
  memcpy ( temp_text + select_start_position, l, l_len + 1 ) ;  /* Plus one to get the final '\0' */
  int temp_text_len = select_start_position + l_len ;

  if ( ( *(l+l_len-1) == '\n' ) && ( text[select_end_position] == '\n' ) )
  {
    temp_text[temp_text_len-1] = '\0' ;  /* Erase the duplicate carriage return */
    temp_text_len -- ;
  }
  else if ( ( *(l+l_len-1) != '\n' ) && ( text[select_end_position] != '\n' ) )
  {
    strcpy ( temp_text + temp_text_len, "\n" ) ;  /* Add a carriage return */
    temp_text_len ++ ;
  }

  memcpy ( temp_text + temp_text_len, text + select_end_position, text_len - select_end_position + 1 ) ;
  int temp_select_start = select_start_position ;
  setValue ( temp_text ) ;
  delete [] temp_text ;
  setSelectRegion ( temp_select_start,
                    temp_select_start + strlen(l) ) ;
  setCursor ( select_end_position ) ;
}

void  puaLargeInput::appendText ( const char *l )
{
  if ( !l ) return ;

  int str_val_len = strlen ( getStringValue () ) ;
  int l_len = strlen ( l ) ;
  if ( str_val_len == 1 ) str_val_len = 0 ;  /* Don't want null line at the beginning */
  int length = str_val_len + l_len + 2 ;
  if ( *(l+l_len-1) == '\n' ) length -- ;  /* Already have a trailing carriage return, decrement the length */

  char *temp_text = new char [ length ] ;
  int temp_text_len ;

  if ( str_val_len > 0 )  /* More than just the empty carriage return */
  {
    memcpy ( temp_text, getStringValue (), str_val_len ) ;
    temp_text_len = str_val_len ;
  }
  else
    temp_text_len = 0 ;

  memcpy ( temp_text + temp_text_len, l, l_len + 1 ) ;  /* Plus one to get the final '\0' */
  temp_text_len += l_len ;
  if ( *(l+l_len-1) != '\n' )
  {
    strcpy ( temp_text + temp_text_len, "\n" ) ;
    temp_text_len ++ ;
  }

  setValue ( temp_text ) ;
  setSelectRegion ( str_val_len, temp_text_len ) ;
  setCursor ( str_val_len ) ;
  delete [] temp_text ;
}

void  puaLargeInput::removeText ( int start, int end )
{
  int str_val_len = strlen ( getStringValue () ) ;
  char *temp_text = new char [ str_val_len + start - end + 1 ] ;
  memcpy ( temp_text, getStringValue (), start ) ;
  memcpy ( temp_text + start, getStringValue ()+end, str_val_len - end + 1 ) ;  /* Plus one to get the final '\0' */
  setValue ( temp_text ) ;
  setCursor ( start ) ;
  setSelectRegion ( start, start ) ;
  delete [] temp_text ;
}

void puaLargeInput::setValue ( const char *s )
{
  if (!s || !*s) {
    puValue::setValue ( "\n" ) ;
    cursor_position = select_start_position = select_end_position = 0 ;

  } else {
    int len = strlen(s);
    if (s[len - 1] == '\n') {
      puValue::setValue ( s ) ;

    } else {
      char *buf = new char [ len + 2 ] ;
      strcpy ( buf, s ) ;
      strcat ( buf, "\n" ) ;
      puValue::setValue ( buf ) ;
      delete [] buf ;
    }
  }

  if ( bottom_slider )
    getTextProperties ( &num_lines, &max_width ) ;

  updateGeometry () ;
  normalizeCursors () ;
}


void puaLargeInput::draw ( int dx, int dy )
{
  if ( !visible || ( window != puGetWindow () ) ) return ;
  normalizeCursors () ;

  // 3D Input boxes look nicest if they are always in inverse style.
  abox.draw ( dx, dy, ( (style==PUSTYLE_SMALL_BEVELLED ||
                         style==PUSTYLE_SMALL_SHADED) ) ? -style :
                        (accepting ? -style : style ), colour, FALSE, border_thickness ) ;
  plug->draw ( abox.min[0] + dx, abox.min[1] + dy ) ;

  if ( r_cb )
    r_cb ( this, dx, dy, render_data ) ;
  else
  {
    // Calculate window parameters:

    int xwidget = abox.min[0] + dx ;
    int ywidget = abox.min[1] + dy ;

    int xx = hgap;
    int yy = abox.max[1] - abox.min[1] - legendFont.getStringHeight () - vgap ;

    float bottom_value = bottom_slider ? bottom_slider->getFloatValue () : 0.0f ;

    int beg_pos      // Position in window of start of line, in pixels
                = int(( input_width - max_width ) * bottom_value ) ;
////  int end_pos      // Position in window of end of line, in pixels
////              = beg_pos + max_width - 1 ;
    if ( top_line_in_window < 0 ) top_line_in_window = 0 ;
    int end_lin      // Position on line count of bottom of window, in lines
                = top_line_in_window + lines_in_window ;

    char *val = getText () ;

    // Highlight the select area

    if ( select_end_position > 0 && select_end_position != select_start_position )
    {
       // First:  find the positions on the window of the selection start and end

       char temp_char = val[ select_start_position ] ;
       val [ select_start_position ] = '\0' ;

       xx = dx + abox.min[0] + hgap ;
       yy = abox.max[1] - abox.min[1] - legendFont.getStringHeight () - vgap
            + top_line_in_window * line_height ;   // Offset y-coord for unprinted lines

       char *end_of_line = strchr ( val, '\n' ) ;
       char *start_of_line = val ;

       // Step down the lines until you reach the line with the selection start

       int select_start_line = 0 ;

       while ( end_of_line )
       {
         select_start_line++ ;
         start_of_line = end_of_line + 1 ;
         yy -= line_height ;
         end_of_line = strchr ( start_of_line, '\n' ) ;
       }

       int start_pos = int(legendFont.getFloatStringWidth ( start_of_line )) + xx +
                       beg_pos ;   // Start of selection

       val [ select_start_position ] = temp_char ;

       // Now repeat the process for the end of the selection.

       temp_char = val[ select_end_position ] ;
       val [ select_end_position ] = '\0' ;

       end_of_line = strchr ( start_of_line, '\n' ) ;

       // Step down the lines until you reach the line with the selection end

       int select_end_line = select_start_line ;

       while ( end_of_line )
       {
         select_end_line++ ;
         start_of_line = end_of_line + 1 ;
         end_of_line = strchr ( start_of_line, '\n' ) ;
       }

       int end_pos = int(legendFont.getFloatStringWidth ( start_of_line )) + xx +
                     beg_pos ;   // End of selection

       val [ select_end_position ] = temp_char ;

       // Now draw the selection area.

       for ( int line_count = select_start_line ; ( ( line_count <= select_end_line ) && ( line_count < end_lin ) ) ;
                 line_count++ )
       {
         if ( line_count >= top_line_in_window )
         {
           int x_start, x_end ;

           if ( line_count == select_start_line )
             x_start = ( start_pos > xx ) ? start_pos : xx ;
           else
             x_start = xx ;

           x_start = ( x_start < abox.max[0] + dx ) ? x_start : abox.max[0] + dx ;

           if ( line_count == select_end_line )
             x_end = ( end_pos < abox.max[0] + dx ) ? end_pos : abox.max[0] + dx ;
           else
             x_end = abox.max[0] + dx ;

           x_end = ( x_end > xx ) ? x_end : xx ;

           int top = dy + abox.min[1] + yy + legendFont.getStringHeight () ;
           int bot = dy + abox.min[1] + yy - legendFont.getStringDescender() ;

           glColor3f ( 1.0f, 1.0f, 0.7f ) ;
           glRecti ( x_start, bot, x_end, top ) ;
         }

         yy -= line_height ;
         if ( line_count == end_lin ) break ;
       }
     }


    // Draw the text

    {
      // If greyed out then halve the opacity when drawing the text

      if ( active )
        glColor4fv ( colour [ PUCOL_LEGEND ] ) ;
      else
        glColor4f ( colour [ PUCOL_LEGEND ][0],
                    colour [ PUCOL_LEGEND ][1],
                    colour [ PUCOL_LEGEND ][2],
                    colour [ PUCOL_LEGEND ][3] / 2.0f ) ; // 50% more transparent

      char *val = getText () ;                     // Pointer to the actual text in the box

      if ( val )
      {
        char *end_of_line = strchr (val, '\n') ;
        int line_count = 0;

        xx = hgap;
        yy = abox.max[1] - abox.min[1] - legendFont.getStringHeight () - vgap + 1 ;

        while ( end_of_line )  // While there is a carriage return in the string
        {
          if ( line_count < top_line_in_window )
          {                                        // Before the start of the window
            val = end_of_line + 1 ;
            end_of_line = strchr (val, '\n') ;     // Just go to the next line
          }
          else if ( line_count < end_lin )         // Within the window, draw it
          {
            char temp_char = *end_of_line ;   // Temporary holder for last char on line

            *end_of_line = '\0' ;     // Make end-of-line be an end-of-string

            int start_pos = beg_pos ;
            int end_pos      // Position in window of end of line, in pixels
                    = start_pos + int(legendFont.getFloatStringWidth ( val )) ;

            if ( end_pos > start_pos )  // If we actually have text in the line
            {
                char * lastonleft = val ;
                char * firstonright = end_of_line ;
                int leftpos = start_pos ;
                int rightpos = end_pos ;
                while ( lastonleft < firstonright - 1 )
                {
                    int chpos = -leftpos * ( firstonright - lastonleft ) / ( rightpos - leftpos ) + 1;
                    if ( chpos >= firstonright - lastonleft ) chpos = firstonright - lastonleft - 1 ;
                    char placeholder = *(lastonleft + chpos) ;
                    *(lastonleft + chpos) = '\0' ;
                    int strwidth = legendFont.getStringWidth ( val ) ;
                    *(lastonleft + chpos) = placeholder ;
                    if ( strwidth + start_pos < 0 ) /* Still to the left of the window */
                    {
                        lastonleft = lastonleft + chpos ;
                        placeholder = *lastonleft ;
                        *lastonleft = '\0' ;
                        leftpos = start_pos + legendFont.getStringWidth ( val ) ;
                        *lastonleft = placeholder ;
                    }
                    else
                    {
                        firstonright = lastonleft + chpos ;
                        placeholder = *firstonright ;
                        *firstonright = '\0' ;
                        rightpos = start_pos + legendFont.getStringWidth ( val ) ;
                        *firstonright = placeholder ;
                    }
                }
                if ( leftpos >= 0 )
                {
                  val = lastonleft ;
                  start_pos = leftpos ;
                }
                else
                {
                  val = firstonright ;
                  start_pos = rightpos ;
                }
            }

            while ( end_pos > input_width )  // Step up the line until it is in the window
            {
              *end_of_line = temp_char ;
              end_of_line-- ;
              temp_char = *end_of_line ;
              *end_of_line = '\0' ;
              end_pos = start_pos + legendFont.getStringWidth ( val ) ;
            }

            if ( val < end_of_line )                 // If any text shows in the window,
              legendFont.drawString ( val,           // draw it.
                                      dx + abox.min[0] + xx + start_pos,
                                      dy + abox.min[1] + yy ) ;

            *end_of_line = temp_char ;     // Restore the end-of-line character

            if ( temp_char != '\n' )               // If we had to step up from the end of
              end_of_line = strchr (val, '\n') ;   // the line, go back to the actual end

            yy -= line_height ;
            val = end_of_line + 1 ;
            end_of_line = strchr (val, '\n') ;     // On to the next line
          }
          else if ( line_count >= end_lin )        // Have gone beyond window, end process
            end_of_line = NULL ;

          line_count++ ;

        }     // while ( end_of_line )
      }     // if ( val )
    }

    if ( accepting )
    {
      char *val = getText () ;                     // Pointer to the actual text in the box

      // Draw the 'I' bar cursor.

      if ( val && ( cursor_position >= 0 ) )
      {
        char temp_char = val[ cursor_position ] ;
        val [ cursor_position ] = '\0' ;

        xx = hgap ;
        yy = abox.max[1] - abox.min[1] - legendFont.getStringHeight () - vgap
             + top_line_in_window * line_height ;   // Offset y-coord for unprinted lines

        char *end_of_line = strchr ( val, '\n' ) ;
        char *start_of_line = val ;

        // Step down the lines until you reach the line with the cursor

        int line_count = 1 ;

        while ( end_of_line )
        {
          line_count++ ;
          start_of_line = end_of_line + 1 ;
          yy -= line_height ;
          end_of_line = strchr ( start_of_line, '\n' ) ;
        }

        if ( ( line_count > top_line_in_window ) && ( line_count <= end_lin ) )
        {
          int begpos      // Position in window of start of line, in pixels
                    = int( ( input_width - max_width ) * bottom_value ) ;
          int cpos = int( legendFont.getFloatStringWidth ( start_of_line ) + xx +
                     abox.min[0] + begpos ) ;
          int top = int( abox.min[1] + yy + legendFont.getStringHeight () ) ;
          int bot = int( abox.min[1] + yy - legendFont.getStringDescender () ) ;
          if ( ( cpos > abox.min[0] ) && ( cpos < abox.max[0] ) )
          {
            glColor4fv ( colour [ PUCOL_MISC ] ) ;
            glBegin   ( GL_LINES ) ;
            glVertex2i ( dx + cpos    , dy + bot ) ;
            glVertex2i ( dx + cpos    , dy + top ) ;
            glVertex2i ( dx + cpos - 1, dy + bot ) ;
            glVertex2i ( dx + cpos - 1, dy + top ) ;
            glVertex2i ( dx + cpos - 4, dy + bot ) ;
            glVertex2i ( dx + cpos + 3, dy + bot ) ;
            glVertex2i ( dx + cpos - 4, dy + top ) ;
            glVertex2i ( dx + cpos + 3, dy + top ) ;
            glEnd      () ;
          }
        }

        val[ cursor_position ] = temp_char ;
      }
    }

    // Draw the other widgets in the large input box

    if ( bottom_slider ) bottom_slider->draw ( xwidget, ywidget ) ;
    right_slider->draw ( xwidget, ywidget ) ;
  }

  draw_label ( dx, dy ) ;
}


int puaLargeInput::checkHit ( int button, int updown, int x, int y )
{
  if ( ( button == PU_SCROLL_UP_BUTTON || button == PU_SCROLL_DOWN_BUTTON ) && isHit( x, y ) )
  {
    int sx, sy;
    right_slider->getPosition ( &sx, &sy );
    return right_slider->checkHit ( button, updown, ++sx, ++sy ) ;
  }

  int xwidget = x - abox.min[0] ;
  int ywidget = y - abox.min[1] ;

  if ( bottom_slider )
  {
    if ( bottom_slider->checkHit ( button, updown, xwidget, ywidget ) ) return TRUE ;
  }

  if ( right_slider->checkHit ( button, updown, xwidget, ywidget ) ) return TRUE ;

  // If the user has clicked within the bottom slider or to its right, don't activate.

  if ( y < slider_width ) return FALSE ;

  if ( puObject::checkHit ( button, updown, x, y ) )
    return TRUE ;

  return FALSE ;
}


void puaLargeInput::doHit ( int button, int updown, int x, int y )
{
  if ( puActiveWidget() && ( this != puActiveWidget() ) )
  {
    // Active widget exists and is not this one; call its down callback if it exists

    puActiveWidget()->invokeDownCallback () ;
    puDeactivateWidget () ;
  }

  if ( updown != PU_DRAG )
    puMoveToLast ( this ) ;

  if ( button == active_mouse_button )
  {
    // Most GUI's activate a button on button-UP not button-DOWN.

    // Text and window parameters:

    float bottom_value = bottom_slider ? bottom_slider->getFloatValue () : 0.0f ;

    int beg_pos      // Position in window of start of line, in pixels
                = int( ( input_width - max_width ) * bottom_value ) ;
//  int end_pos      // Position in window of end of line, in pixels
//              = (int)( beg_pos + max_width - 1 ) ;
    if ( top_line_in_window < 0 ) top_line_in_window = 0 ;
//  int end_lin      // Position on line count of bottom of window, in lines
//              = top_line_in_window + lines_in_window - 1 ;

//  int xx = legendFont.getStringWidth ( " " ) ;
    int yy = int( abox.max[1] - legendFont.getStringHeight () - vgap
            + top_line_in_window * line_height ) ;   // Offset y-coord for unprinted lines

    // Get the line number and position on the line of the mouse
    char *tmpval = ulStrDup ( getText() ) ;

    int i = strlen ( tmpval ) ;

    char *end_of_line = strchr ( tmpval, '\n' ) ;
    char *start_of_line = tmpval ;

    // Step down the lines until the y-coordinate is less than the mouse

    int line_count = 0 ;

    while ( ( yy > y ) && end_of_line )
    {
      line_count++ ;
      start_of_line = end_of_line + 1 ;
      yy -= line_height ;
      end_of_line = strchr ( start_of_line, '\n' ) ;
    }

    if ( end_of_line )
    {
      *end_of_line = '\0' ;

      i = strlen ( tmpval ) ;

      int length, prev_length = 0 ;
      while ( x <= (length = legendFont.getStringWidth ( start_of_line )
                    + abox.min[0] + beg_pos) &&
              i > 0 )
      {
        prev_length = length ;
        tmpval[--i] = '\0' ;
      }

      if ( ( x - length ) < ( prev_length - x ) )
        i-- ;   // Mouse is closer to previous character than next character
    }

    // Now process the mouse click itself.

    if ( updown == active_mouse_edge || active_mouse_edge == PU_UP_AND_DOWN )
    {
      lowlight () ;

      accepting = TRUE ;
      cursor_position = i ;
      normalizeCursors () ;
      puSetActiveWidget ( this, x, y ) ;
      invokeCallback () ;
    }
    else if ( updown == PU_DOWN )
    {
      // We get here if the active edge is not down but the mouse button has
      // been pressed.  Start a selection.

      select_start_position = i ;
      select_end_position = i ;
    }
    else if ( updown == PU_DRAG )
    {
      // Drag -- extend the selection.

      if ( (select_end_position - i) > (i - select_start_position) )
        select_start_position = i ;   // Cursor closer to start than to end
      else
        select_end_position = i ;     // Cursor closer to end than to start

      if (select_start_position > select_end_position)
      {
        i = select_end_position ;
        select_end_position = select_start_position ;
        select_start_position = i ;
      }
    }
    else
      highlight () ;

    delete [] tmpval ;
  }
  else
    lowlight () ;
}

int puaLargeInput::checkKey ( int key, int updown )
{
  if ( updown == PU_UP || !isAcceptingInput () || !isActive () ||
       !isVisible () || ( window != puGetWindow () ) )
    return FALSE ;

  if ( puActiveWidget() && ( this != puActiveWidget() ) )
  {
    // Active widget exists and is not this one; call its down callback if it exists

    puActiveWidget()->invokeDownCallback () ;
    puDeactivateWidget () ;
  }

  normalizeCursors () ;

  //char *old_text = getStringValue () ;
  char *old_text = getText () ;
  int lines_in_window = getLinesInWindow () ;  /* Added lines_in_window and num_lines to allow for "END" */
  int num_lines = getNumLines () ;             /* and PGUP/DOWN to work properly        - JCJ 20 Jun 2002 */
  int line_width = 0 ;                         /* Width of current line (for bottomslider) */
  int line_width_to_cursor = 0 ;               /* Width of current line up to the cursor position */
  int prev_line_width = 0 ;                    /* Width of previous line (for left mouse arrow) */
  int i = 1;                                   /* Happy useful variable */
  int line_counter = 0 ;                       /* Happy useful variable #2 */
  int tmp_cursor_position = cursor_position ;  /* Temporary cursor position for counting without moving the cursor */
  int bottom_line_in_window = 0;                  /* # of bottom line in window */
  int current_line_in_window = 0;                 /* Current line # in window */
  char *line_end = 0 ;
  char* p = new char[ strlen(old_text)+1 ] ;
  float bottom_value = bottom_slider ? bottom_slider->getFloatValue () : 0.0f ; /* Value of the bottom slider */
  if ( old_text[1] != '\0' ) /* Ensure that we don't delete something that doesn't exist! - JCJ 22 July 2002 */
  {
    /*Count how many characters from the beginning of the line the cursor is*/
    while ( ( old_text [ cursor_position - i ] != '\n' ) &&
             ( i < cursor_position ) )
     i++ ;            /* Step back to the beginning of the line */
    if ( i < cursor_position ) i-- ;

    /*Find the length of the current line*/
    while ( ( old_text [ tmp_cursor_position ] != '\n' ) &&
             ( tmp_cursor_position > 0) )
      tmp_cursor_position-- ;
  
    strcpy ( p, ( old_text + tmp_cursor_position + 1 ) ) ;
    if ( bottom_slider )
    {
      line_end = strchr ( p, '\n' ) ;
      if ( line_end )  // Found an end-of-line
      {
        *line_end = '\0' ;  // Temporary break in line
        line_width = int(legendFont.getFloatStringWidth ( p )) ;
        *line_end = '\n' ;  // Reset the carriage return
      }
      /*Now delete all characters in the string beyond i, and re-find its width*/
      p [i+1] = '\0' ;
      line_width_to_cursor = int(legendFont.getFloatStringWidth ( p )) ;
    }

    /* Now find the length of the previous line */
    tmp_cursor_position-- ;
    while ( ( old_text [ tmp_cursor_position ] != '\n' ) &&
            ( tmp_cursor_position > 0) )
      tmp_cursor_position-- ;

    strcpy ( p, ( old_text + tmp_cursor_position + 1 ) ) ;
    if ( bottom_slider )
    {
      line_end = strchr ( p, '\n' ) ;
      if ( line_end )  /*Actually, we KNOW there's a line after this one, but hey, let's be safe, eh? - JCJ 21 Jun 2002 */
      {
        *line_end = '\0' ;  // Temporary break in line
        prev_line_width = legendFont.getStringWidth ( p ) ;
        *line_end = '\n' ;  // Reset the carriage return
      }
    }

    delete [] p ;
    p = NULL ;
  }

  bool done = true ;

  switch ( key )
  {
  case PU_KEY_PAGE_UP   :
    while ( old_text [ cursor_position ] != '\0' ) /* Move the cursor to the top of the data */
      cursor_position-- ;
    select_start_position = select_end_position = cursor_position ;
    setTopLineInWindow ( top_line_in_window - lines_in_window + 2 ) ;
    right_slider->setValue (1.0f - float(top_line_in_window) / num_lines ) ;
    break;
  case PU_KEY_PAGE_DOWN :
    while ( old_text [ cursor_position ] != '\0' ) /* Move the cursor to the end of the data */
      cursor_position++ ;
    select_start_position = select_end_position = cursor_position ;
    setTopLineInWindow ( top_line_in_window + lines_in_window - 2 ) ; /* Plus two for consistency - JCJ 20 Jun 2002 */
    right_slider->setValue (1.0f - float(top_line_in_window) / num_lines ) ;
    break;
  case PU_KEY_INSERT    : return FALSE ;

  case PU_KEY_UP   :
  case PU_KEY_DOWN :
    /* Determine the current line, the line at the top of the window, and the line at the bottom.*/
    bottom_line_in_window = top_line_in_window + getLinesInWindow() ;
    while ( line_counter < cursor_position) {
      if ( old_text [ line_counter ] == '\n' ) current_line_in_window++ ;
      line_counter++ ;
    }
    if ( key == PU_KEY_UP )
    {
      // Step backwards to the beginning of the previous line
      cursor_position -= (i + 2) ;
      while ( ( old_text [ cursor_position ] != '\n' ) &&
              ( cursor_position > 0 ) )
        cursor_position-- ;
      if ( cursor_position > 0 ) cursor_position++ ;

      // Step down the line "i" spaces or to the end of the line
      while ( ( old_text [ cursor_position ] != '\n' ) &&
              ( i > 0 ) )
      {
        cursor_position++ ;
        i-- ;
      }
      if (current_line_in_window <= top_line_in_window) {
          setTopLineInWindow ( top_line_in_window - 1 ); /* Go up - JCJ 21 Jun 2002 */
          right_slider->setValue (1.0f - float(top_line_in_window) / num_lines ) ;
      }
    }
    else   // Down-arrow key
    {
      // Skip to beginning of next line
      while ( old_text [ cursor_position ] != '\n' && old_text [ cursor_position ] != '\0' )
        cursor_position++ ;
      cursor_position++ ;

      // Step down the line "i" spaces or to the end of the line
      // or to the end of the text

      while ( ( old_text [ cursor_position ] != '\n' ) &&
              ( cursor_position < (int)strlen ( old_text ) ) &&
              ( i > 0 ) )
      {
        cursor_position++ ;
        i-- ;
      }
      if ((current_line_in_window+1) >= bottom_line_in_window) {
          setTopLineInWindow ( top_line_in_window + 1 ); /* Go down - JCJ 21 Jun 2002 */
          right_slider->setValue (1.0f - float(top_line_in_window) / num_lines ) ;
      }
    }

    select_start_position = select_end_position = cursor_position ;
    break ;

  case 0x1B :  // ESC
  case '\t' :  // TAB  -- End of input
    rejectInput () ;
    normalizeCursors () ;
    invokeCallback () ;
    puDeactivateWidget () ;
    break ;

  case PU_KEY_HOME   :
    while ( ( old_text [ cursor_position-1 ] != '\n' )  &&
              ( cursor_position > 0 ) )               /* Move the cursor to the start of the line, but minus one so */
      cursor_position-- ;                             /* it does not find a \n immediately if located at the end.   */
    select_start_position = select_end_position = cursor_position ; /* Also corrects overshoots.  - JCJ 20 Jun 2002 */
    if ( bottom_slider ) bottom_slider->setValue ( 0.0f ) ;
    break ;
  case PU_KEY_END    :
    if (bottom_slider)
    {
      bottom_slider->setValue ( ( (line_width - input_width)<0 ) ? 0.0f : float(line_width+10) / max_width);
    }
    while ( old_text [ cursor_position ] != '\n' ) /* Move the cursor to the end of the line - JCJ 20 Jun 2002 */
      cursor_position++ ;
    select_start_position = select_end_position = cursor_position ;
    break ;

  case PU_KEY_LEFT   :
  case PU_KEY_RIGHT  :
    if ( key == PU_KEY_LEFT )
      {
        /* This stops it crashing out sometimes when wrappable text is used.
         * Since there is no bottom scrollbar when wrapped text is turned on, the bottom
         * slider code could make the application die when the user pressed the left
         * cursor key before they began the first line.
         * Fix by Simon <sickz6sickz AT hotmail DOT com> */
        cursor_position-- ; /* Left key pressed */
        if ( bottom_slider )
          {
            if (old_text [ cursor_position ] == '\n')
              bottom_slider->setValue( ( (prev_line_width - input_width)<0 ) ? 0.0f : float(prev_line_width+10) /max_width);
            /* If the cursor is going off the left edge of the box, scroll left. */
            else if ((bottom_value*max_width) > line_width_to_cursor+5) {
              bottom_slider->setValue( ((bottom_value*max_width)-(input_width/2)-5)<0 ? 0.0f :
                                       ((bottom_value*max_width)-(input_width/2)-5)/max_width ) ;
            }
          }
      } else {
        cursor_position++ ; /* Right key pressed */
        if ( bottom_slider ) {
          if (old_text [ cursor_position-1 ] == '\n')
            bottom_slider->setValue(0.0f) ;
          else if ((bottom_value*max_width)+(input_width) < line_width_to_cursor+5) {
            bottom_slider->setValue( ((bottom_value*max_width)+(input_width/2)+5)/max_width ) ;
          }
        }
      }
    select_start_position = select_end_position = cursor_position ;
    break ;

  default :
    done = false ;
    break ;
  }

  if ( ! done && ! input_disabled )
  {
    char *p = NULL ;
    int temp_cursor = cursor_position ;

    switch ( key )
    {
    case '\b' :  // Backspace
      if ( select_start_position != select_end_position )
        removeSelectRegion () ;
      else if ( cursor_position > 0 )
      {
        p = new char [ strlen(old_text) ] ;
        memcpy ( p, old_text, cursor_position ) ;
        --cursor_position ;
        strcpy ( p + cursor_position, old_text + cursor_position + 1 ) ;
        setValue ( p ) ;
        setCursor ( temp_cursor - 1 ) ;
        delete [] p ;
      }

      break ;

    case 0x7F :  // DEL
      if ( select_start_position != select_end_position )
        removeSelectRegion () ;
      else if (cursor_position != (int)strlen ( old_text ) )
      {
        p = new char [ strlen(old_text) ] ;
        memcpy ( p, old_text, cursor_position ) ;
        strcpy ( p + cursor_position, old_text + cursor_position + 1 ) ;
        setValue ( p ) ;
        setCursor ( temp_cursor ) ;
        delete [] p ;
      }

      break ;

    case 0x15 /* ^U */ : getStringValue () [ 0 ] = '\0' ; break ;
    case 0x03 /* ^C */ :
    case 0x18 /* ^X */ :  /* Cut or copy selected text */
      if ( select_start_position != select_end_position )
      {
        extern void puSetPasteBuffer ( const char *ch ) ;
        p = getStringValue () ;
        char ch = p[select_end_position] ;
        p[select_end_position] = '\0' ;
        puSetPasteBuffer ( p + select_start_position ) ;
        p[select_end_position] = ch ;

        if ( key == 0x18 )  /* Cut, remove text from string */
          removeSelectRegion () ;
      }

      break ;

    case 0x16 /* ^V */ : /* Paste buffer into text */
      {
        extern char *puGetPasteBuffer () ;
        if ( select_start_position != select_end_position )
          removeSelectRegion () ;

        if ( puGetPasteBuffer () )  // Make sure something has been cut previously!
        {
          int str_val_len = strlen ( getStringValue () ) ;
          int paste_len = strlen ( puGetPasteBuffer () ) ;
          p = new char [ str_val_len + paste_len + 1 ] ;
          memcpy ( p, getStringValue (), cursor_position ) ;
          memcpy ( p + cursor_position, puGetPasteBuffer (), paste_len ) ;
          memcpy ( p + cursor_position + paste_len, getStringValue() + cursor_position, str_val_len - cursor_position + 1 ) ;
          temp_cursor += paste_len ;
          setValue ( p ) ;
          setCursor ( temp_cursor ) ;
          delete [] p ;
        }
      }

      break ;

    default:
      if ( ( key < ' ' || key > 127 ) && ( key != '\n' )
                                      && ( key != '\r' ) ) return FALSE ;

      if ( key == '\r' ) key = '\n' ;

      if ( valid_data )
      {
        if ( !strchr ( valid_data, key ) ) return TRUE ;
      }

      if ( select_start_position != select_end_position ) // remove selected text
      {
        temp_cursor -= ( select_end_position - select_start_position ) ;
        removeSelectRegion () ;
      }

      p = new char [ strlen(old_text) + 2 ] ;

      memcpy ( p, old_text, cursor_position ) ;

      p [ cursor_position ] = key ;

      strcpy ( p + cursor_position + 1, ( old_text + cursor_position ) ) ;
      bottom_line_in_window = top_line_in_window + getLinesInWindow() ;

      /* If running off the screen, scroll right. - JCJ 28 Jun 2002 */
      if ((bottom_value*max_width)+(input_width) < line_width_to_cursor+5) {
          bottom_slider->setValue( ((bottom_value*max_width)+(input_width/2)+5)/max_width ) ;
      }

      if (key == '\n') {
      /* If pressing enter, figure out which line this is. */
          while ( line_counter < cursor_position) {
              if ( old_text [ line_counter ] == '\n' ) current_line_in_window++ ;
              line_counter++ ;
          }
      /* If hitting enter at the bottom of the screen, scroll down. - JCJ 28 Jun 2002 */
          if ( (current_line_in_window+1) >= bottom_line_in_window ) {
              setTopLineInWindow ( top_line_in_window + 1 );
              right_slider->setValue (1.0f - float(top_line_in_window) / num_lines ) ;
          }
      }

      setValue ( p ) ;
      setCursor ( temp_cursor + 1 ) ;
      delete [] p ;

      break ;
    }
  }

  normalizeCursors () ;
  return TRUE ;
}

void puaLargeInput::getTextProperties(int *numlines, int *maxwidth)
{
  int num = 0, max = 0 ;
  char *buf = ulStrDup ( getStringValue () ), *s = buf ;
  while ( *s ) {
    char *cr = strchr ( s, '\n' ) ;
    if ( cr )
      *cr = '\0' ;
    int w = legendFont.getStringWidth (s) ;
    if ( w > max )
      max = w ;
    num++ ;
    if ( cr )
      s = ++cr ;
  }
  *numlines = num ;
  *maxwidth = max ;
  delete [] buf ;
}

char *puaLargeInput::wrapText ( int target_width, int *numlines, int *maxwidth )
{
  char *buf = ulStrDup ( getStringValue () ) ;
  int numl = 0 ;
  int maxw = 0 ;

  char *displayed_text_wp = buf,
       *space_ptr,
       *old_space_ptr ;

  /* Somewhat inspired by tuxracer */
  while ( *displayed_text_wp != '\0' )
  {
    old_space_ptr = NULL ;
    space_ptr = strchr ( displayed_text_wp, ' ' ) ;

    while (1)
    {
      if ( space_ptr != NULL )
        *space_ptr = '\0' ;

      int w = legendFont.getStringWidth ( displayed_text_wp ) ;
      if ( w > target_width )
        break ;

      if ( w > maxw ) maxw = w ;
      old_space_ptr = space_ptr ;

      if ( space_ptr == NULL )
        /* Entire string fits in widget */
        break ;

      // Check for carriage return in the original string
      for (char *s = 0; (s = strchr ( displayed_text_wp, '\n' )); displayed_text_wp = s + 1)
        numl++;

      *space_ptr = ' ' ;

      space_ptr = strchr ( space_ptr+1, ' ' ) ;
    }

    numl++ ;
    if ( old_space_ptr == NULL )
    /* Either string is too wide for area, or the entire remaining portion
       of string fits in area (space_ptr == NULL). */
    {
      displayed_text_wp += strlen (displayed_text_wp) ;

      if ( space_ptr != NULL )
      /* Advance past the NULL since there's more string left */
        displayed_text_wp += 1 ;
    }
    else
    {
      if ( space_ptr != NULL )
        *space_ptr = ' ' ;
      *old_space_ptr = '\n' ;

      displayed_text_wp = old_space_ptr + 1 ;
    }
  }
  if (numlines) *numlines = numl ;
  if (maxwidth) *maxwidth = maxw ;
  return buf ;
}


