/*
 * pawstextbox.cpp - Author: Andrew Craig
 *
 * Copyright (C) 2003 Atomic Blue (info@planeshift.it, http://www.atomicblue.org)
 *
 *
 * 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 (version 2 of the License)
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
// pawstextbox.cpp: implementation of the pawsTextBox class.
//
//////////////////////////////////////////////////////////////////////
#include <psconfig.h>
#include <ctype.h>
#include <ivideo/fontserv.h>
#include <iutil/virtclk.h>
#include <iutil/evdefs.h>
#include <iutil/event.h>

#include "pawstextbox.h"
#include "pawsmanager.h"
#include "pawsprefmanager.h"
#include "pawscrollbar.h"
#include "util/log.h"
#include "util/strutil.h"
#include "util/psstring.h"

#define BORDER_SIZE            2 // For pawsFadingTextBox

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

pawsTextBox::pawsTextBox()
{
    horizAdjust = horizLEFT;
    vertAdjust  = vertTOP;
    
    vertical = false;
    grayed = false;
    letterSizes = NULL;
}

pawsTextBox::~pawsTextBox()
{
    if (letterSizes != NULL)
        delete [] letterSizes;
}

bool pawsTextBox::Setup( iDocumentNode* node )
{    
    csRef<iDocumentAttribute> vertAttribute = node->GetAttribute("vertical");
    if ( vertAttribute )
        SetVertical(strcmp(vertAttribute->GetValue(), "yes") == 0);

    csRef<iDocumentNode> textNode = node->GetNode( "text" );        
    
    if ( textNode )
    {
        csRef<iDocumentAttribute> fontAdjustAttribute;
        fontAdjustAttribute = textNode->GetAttribute("horizAdjust");
        if ( fontAdjustAttribute )
        {
            csString fontAdjust = fontAdjustAttribute->GetValue();            
            if (fontAdjust == "CENTRE")
                HorizAdjust(horizCENTRE);
            else if (fontAdjust == "RIGHT")
                HorizAdjust(horizRIGHT);
            else
                HorizAdjust(horizLEFT);
        }            

        fontAdjustAttribute = textNode->GetAttribute("vertAdjust");
        if ( fontAdjustAttribute )
        {
            csString fontAdjust = fontAdjustAttribute->GetValue();            
            if (fontAdjust == "CENTRE")
                VertAdjust(vertCENTRE);
            else if (fontAdjust == "BOTTOM")
                VertAdjust(vertBOTTOM);
            else
                VertAdjust(vertTOP);
        }            
        
        csRef<iDocumentAttribute> textAttribute = textNode->GetAttribute("string");
        if ( textAttribute )
        {
            SetText(PawsManager::GetSingleton().Translate(textAttribute->GetValue()));
        }
    }
        
    return true;
}

void pawsTextBox::CalcTextPos()
{
    int width, height;

    if (horizAdjust==horizRIGHT  ||  horizAdjust==horizCENTRE  ||  vertAdjust==vertBOTTOM  ||  vertAdjust==vertCENTRE)
        CalcTextSize(width, height);

    switch (horizAdjust)
    {
        case horizLEFT:
            textX = 0;
            break;
        case horizRIGHT:
            textX = screenFrame.Width() - width;
            break;
        case horizCENTRE:
            textX = (screenFrame.Width() - width)  / 2;
            break;
    }

    switch (vertAdjust)
    {
        case vertTOP:
            textY = 0;
            break;
        case vertBOTTOM:
            textY = screenFrame.Height() - height;
            break;
        case vertCENTRE:
            textY = (screenFrame.Height() - height) / 2;
            break;
    }
}

void pawsTextBox::CalcLetterSizes()
{
    char letterStr[2];
    unsigned int i;
    
    assert(vertical);
    
    if (letterSizes != NULL)
        delete [] letterSizes;
    
    letterSizes = new psPoint [text.Length()];
    textWidth = 0;
    letterStr[1] = '\0'; // Make sure the string is terminated
                         // pos 0 will be filled in later.
    
    for (i=0; i < text.Length(); i++)
    {
        letterStr[0] = text.GetAt(i);

        GetFont()->GetDimensions(letterStr, letterSizes[i].x, letterSizes[i].y);

        textWidth = MAX(textWidth, letterSizes[i].x);
    }
}

void pawsTextBox::SetVertical(bool vertical)
{
    this->vertical = vertical;
    
    if (vertical)
        CalcLetterSizes();
    else if (letterSizes != NULL)
    {
        delete [] letterSizes;
        letterSizes = NULL;
    }
    CalcTextPos();
}

void pawsTextBox::Adjust(pawsHorizAdjust horiz, pawsVertAdjust vert)
{
    horizAdjust  = horiz;
    vertAdjust   = vert;
    CalcTextPos();
}

void pawsTextBox::HorizAdjust(pawsHorizAdjust horiz)
{
    horizAdjust  = horiz;
    CalcTextPos();
}

void pawsTextBox::VertAdjust(pawsVertAdjust vert)
{
    vertAdjust   = vert;
    CalcTextPos();
}

void pawsTextBox::CalcTextSize(int & width, int & height)
{
    if (vertical)
    {
        unsigned int i;
        
        width = 0;
        height = 0;

        for (i=0; i < text.Length(); i++)
        {
            width   = MAX(width, letterSizes[i].x);
            height  += letterSizes[i].y;
        }
    }
    else if (text.GetData() != NULL)
    {
        GetFont()->GetDimensions( (const char*)text, width, height );
        width+=5;
    }        
    else
    {
        width  = 0;
        height = 0;
    }
}

bool pawsTextBox::SelfPopulate( iDocumentNode *node)
{
    if (node->GetAttributeValue("text"))
    {
        SetText (node->GetAttributeValue("text"));
        return true;
    }
    else
        return false;
}

void pawsTextBox::FormatText( const char *fmt, ... )
{
    char text[1024];
    va_list args;

    va_start(args, fmt);
    cs_vsnprintf(text,sizeof(text),fmt,args);
    va_end(args);
    
    SetText( (const char*)text );
}    


void pawsTextBox::SetText( const char* newText )
{
    // Make sure any \r characters are replaced by printable \n ones.
    psString str(newText);
    str.ReplaceAllSubString("\r", "\n");

    text.Replace(str.GetData());

    if (vertical)
        CalcLetterSizes();
    CalcTextPos();
}

void pawsTextBox::SetSizeByText()
{
    int width, height;
    
    CalcTextSize(width, height);
    SetRelativeFrameSize(width, height);
    textX = 0;
    textY = 0;
}

int pawsTextBox::GetFontColour()
{
    if (grayed)
        return graphics2D->FindRGB( 150,150,100 );
    return pawsWidget::GetFontColour();
}

void pawsTextBox::Draw()
{
    pawsWidget::Draw();

    if (vertical)
    {
        char letterStr[2];
        int letterY;
        
        letterStr[1] = 0;
        letterY = textY;
        for (unsigned int i = 0; i < text.Length(); i++)
        {
            letterStr[0] = text.GetAt(i);
            DrawWidgetText( letterStr , 
                            screenFrame.xmin + textX + (textWidth-letterSizes[i].x)/2,
                            screenFrame.ymin + letterY );

            letterY += letterSizes[i].y;
        }
    }
    else
    {
        DrawWidgetText( (const char*)text, 
                        screenFrame.xmin + textX,
                        screenFrame.ymin + textY );   
    }
}

void pawsTextBox::OnUpdateData(const char *dataname,PAWSData& value)
{
    // This is called automatically whenever subscribed data is published.
    if (subscription_format == "percent")
    {
        float f = value.GetFloat();
        char buff[10];
        sprintf(buff,"%.01f%%",f*100);
        SetText(buff);
    }
    else
        SetText( value.GetStr());
}



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

pawsMessageTextBox::pawsMessageTextBox()
{
    topLine = 0;
    maxLines = 0;
    CalcLineHeight();
    scrollBar = NULL;
}

pawsMessageTextBox::~pawsMessageTextBox()
{
    Clear();
}

void pawsMessageTextBox::Clear()
{
    messages.Empty();
    adjusted.Empty();
    topLine = 0;
    if (scrollBar)
        scrollBar->Hide();
}

void pawsMessageTextBox::CalcLineHeight()
{
    int dummy;
    GetFont()->GetMaxSize( dummy, lineHeight );
    lineHeight -=2;
}

bool pawsMessageTextBox::Setup( iDocumentNode* node )
{
    CalcLineHeight();

    // Create the optional scroll bar here as well but hidden.
    scrollBar = (pawsScrollBar*) PawsManager::GetSingleton().CreateWidget ( "pawsScrollBar" );
    scrollBar->SetParent( this );
    scrollBar->SetRelativeFrame( defaultFrame.Width() - 24, 6, 24, defaultFrame.Height() - 12 ); 
    int attach = ATTACH_TOP | ATTACH_BOTTOM | ATTACH_RIGHT;
    scrollBar->SetAttachFlags( attach );
    scrollBar->PostSetup();
    scrollBar->SetTickValue( 1.0 );
    AddChild( scrollBar );
   
    OnResize();

    return true;
}
bool pawsMessageTextBox::Setup( void )
{
    CalcLineHeight();

    // Create the optional scroll bar here as well but hidden.
    scrollBar = (pawsScrollBar*) PawsManager::GetSingleton().CreateWidget ( "pawsScrollBar" );
    scrollBar->SetParent( this );
    scrollBar->SetRelativeFrame( defaultFrame.Width() - 24, 6, 24, defaultFrame.Height() - 12 ); 
    int attach = ATTACH_TOP | ATTACH_BOTTOM | ATTACH_RIGHT;
    scrollBar->SetAttachFlags( attach );
    scrollBar->PostSetup();
    scrollBar->SetTickValue( 1.0 );
    scrollBar->SetMaxValue(0.0);
    AddChild( scrollBar );

    OnResize();

    return true;
}

void pawsMessageTextBox::Resize()
{
    pawsWidget::Resize();

    CalcLineHeight();

    adjusted.Empty();    
    for ( size_t x = 0; x < messages.Length(); x++ )    
    {        
       SplitMessage( messages[x]->text, messages[x]->colour ); 
    } 
}

void pawsMessageTextBox::OnResize()
{
    int borderOffSize = 4;
    if ( border )
        borderOffSize = 8;

    int rest = (screenFrame.Height()-borderOffSize) % lineHeight;
    if (screenFrame.Height() >= borderOffSize+rest)
        maxLines = (screenFrame.Height()-borderOffSize-rest) / lineHeight;
    else
        maxLines = 0;

    // Re adjust the top line value 
    topLine = (int)adjusted.Length() - (int)maxLines;
    if ( topLine < 0 ) 
        topLine = 0;

    if (scrollBar)
    {
        scrollBar->SetMaxValue( (float)(adjusted.Length()-maxLines) );
        scrollBar->SetCurrentValue( (float)topLine );

        if ( maxLines < adjusted.Length() )
        {
            scrollBar->Show();
            scrollBar->RecalcScreenPositions();
        }
        else
            scrollBar->Hide();
    }
}


void pawsMessageTextBox::Draw()
{
    pawsWidget::Draw();

    ClipToParent();

    int yPos = 0;
    
    if ( topLine < 0 )
        topLine = 0;

    for ( size_t x = topLine; x < (topLine+maxLines); x++ )
    {
        if ( x < adjusted.Length() )
        {
            graphics2D->Write(  GetFont(),
                                screenFrame.xmin,
                                screenFrame.ymin + yPos*lineHeight,
                                adjusted[x]->colour,
                                -1,
                                (const char*)adjusted[x]->text );              
            yPos++;                                                                            
        }
    }
}


bool pawsMessageTextBox::SelfPopulate( iDocumentNode *node)
{
    if (node->GetAttributeValue("text"))
    {
        Clear();
        AddMessage(node->GetAttributeValue("text"));
        return true;
    }
    return false;
}


void pawsMessageTextBox::AddMessage( const char* data, int msgColour )
{   
    bool onBottom = false;
    int oldTopLine = topLine;
    
    if ( topLine == (int)scrollBar->GetMaxValue() )
        onBottom = true;                        
    if ( (size_t)scrollBar->GetMaxValue() < maxLines )
        onBottom = true;    
    
    if ( topLine < 0 )
        topLine = 0;                 
     
    // Add it to the main message buffer.
    MessageLine * msg = new MessageLine;
    int colour;
    
    msg->text = data;
    if ( msgColour != -1 ) 
        colour = msgColour;
    else 
        colour = GetFontColour();
    msg->colour = colour;
            
    messages.Push(msg);    
        

    SplitMessage( data, colour );            
                      
    if (scrollBar)
    {
        if ( adjusted.Length() > maxLines )
            scrollBar->ShowBehind();
        else
            scrollBar->Hide();
    }

    
    scrollBar->SetMaxValue( (float)(adjusted.Length()-maxLines) );
        
    topLine = (int)adjusted.Length() - (int)maxLines;      
    if ( topLine < 0 )
        topLine = 0;                 

                        
    if ( !onBottom )            
    {
        topLine = oldTopLine;
        scrollBar->SetCurrentValue( (float)topLine );
    }
    else
    {
        scrollBar->SetCurrentValue( float(topLine) );
    }            
}

void pawsMessageTextBox::OnUpdateData(const char *dataname,PAWSData& value)
{
    // This is called automatically whenever subscribed data is published.
    if (overwrite_subscription)
        Clear();

    AddMessage( value.GetStr() );
}

void pawsMessageTextBox::ResetScroll()
{
    if (scrollBar)
        scrollBar->SetCurrentValue(0);
}

void pawsMessageTextBox::FullScroll()
{
    if(scrollBar)
        scrollBar->SetCurrentValue(scrollBar->GetMaxValue());

}

void pawsMessageTextBox::SplitMessage( const char* newText, int colour )
{
    int maxWidth;
    int maxHeight;
    
    GetFont()->GetMaxSize( maxWidth, maxHeight );
        
    char* dummy = new char[strlen( newText ) + 1];
    char* head = dummy;
    
    strcpy( dummy, newText );
    
    int offSet = 40;
            
    while ( dummy )
    {
        /// See how many characters can be drawn on a single line.                
        int canDrawLength =  GetFont()->GetLength( dummy, screenFrame.Width()-offSet );
        
        /// If it can fit the entire string then return.
        if ( canDrawLength == (int)strlen( dummy ) )
        {
            csString temp( dummy );
            MessageLine* msgLine = new MessageLine;
            msgLine->text   = temp;
            msgLine->colour = colour;
            adjusted.Push( msgLine );
            break;
        }
        // We have to push in a new line to the lines bit.
        else
        {
            // Find out the nearest white space to break the line. 
            int index = canDrawLength;
            
            while ( index > 0 && dummy[index] != ' ' )
            {
                index--;
            }
            if (index == 0)
                index = canDrawLength;
            
            // Index now points to the whitespace line so we can break it here.
            
            csString test;
            test.Append( dummy, index );
            dummy+=index;        
            MessageLine* msgLine = new MessageLine;
            msgLine->text   = test;
            msgLine->colour = colour;
            adjusted.Push( msgLine );            
        }
    }
                
    delete [] head;        
}

bool pawsMessageTextBox::OnScroll( int direction, pawsScrollBar* widget )
{   
    topLine = (int)widget->GetCurrentValue();       
    return true;
}

bool pawsMessageTextBox::OnMouseDown( int button, int modifiers, int x, int y )
{
    if (button == csmbWheelUp)
    {
        if (scrollBar)
            scrollBar->SetCurrentValue(scrollBar->GetCurrentValue() - MESSAGE_TEXTBOX_MOUSE_SCROLL_AMOUNT);
        return true;
    }
    else if (button == csmbWheelDown)
    {
        if (scrollBar)
            scrollBar->SetCurrentValue(scrollBar->GetCurrentValue() + MESSAGE_TEXTBOX_MOUSE_SCROLL_AMOUNT);
        return true;
    }

    return pawsWidget::OnMouseDown( button, modifiers, x ,y);
}
    


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


pawsEditTextBox::pawsEditTextBox()
{

    
    start = 0;
    blink = true;
    cursorPosition = 0;
    cursorLine = 0;
    topLine=0;
    usedLines=0;
    multiLine = false;
    vScrollBar = NULL;
    topLine = 0;
    password = 0;

    clock = CS_QUERY_REGISTRY( PawsManager::GetSingleton().GetObjectRegistry(), iVirtualClock );

    blinkTicks = clock->GetCurrentTicks();

    // Find the line height;
    int dummy;
    GetFont()->GetMaxSize( dummy, lineHeight );
    lineHeight -=2;
}

pawsEditTextBox::~pawsEditTextBox()
{
}

void pawsEditTextBox::SetSizeByText()
{
    int width, height;
    
    if (GetFont()!=NULL && text.GetData()!=NULL )
    {       
        GetFont()->GetDimensions( text.GetData(), width, height );
        SetSize(width, height);
    }
}

void pawsEditTextBox::SetPassword( bool pass )
{
    password = pass;
}

bool pawsEditTextBox::Setup( iDocumentNode* node )
{
    csRef<iDocumentAttribute> multi = node->GetAttribute( "multiline" );
    if ( multi )
    {
        csString choice = csString( multi->GetValue() );
        if ( choice == "yes" ) SetMultiline(true);
    }

    csRef<iDocumentNode> textNode = node->GetNode( "text" );        
    if (textNode)
    {
        csRef<iDocumentAttribute> stringAttribute = textNode->GetAttribute("string");
        if ( stringAttribute )
        {
            SetText( PawsManager::GetSingleton().Translate(stringAttribute->GetValue()) );
        }
    }
    // Find the line height
    int dummy;
    GetFont()->GetMaxSize( dummy, lineHeight );
    lineHeight -=2;
            
    return true;
}

bool pawsEditTextBox::SelfPopulate( iDocumentNode *node)
{
    if (node->GetAttributeValue("text"))
    {
        SetText(node->GetAttributeValue("text"));
        return true;
    }
    else
        return false;
}

void pawsEditTextBox::SetText( const char* newText, bool publish )
{
    if (publish && subscribedVar)
        PawsManager::GetSingleton().Publish(subscribedVar, newText);

    text.Replace( newText );
    
    if (multiLine)
    {
        start = 0;
        cursorPosition = 0;
        cursorLine = 0;
        usedLines = 0;
        topLine = 0;
    }
    else
    {
        if (newText)
            cursorPosition = strlen( newText );
        else
            cursorPosition = 0;
    }
}

void pawsEditTextBox::OnUpdateData(const char *dataname,PAWSData& value)
{
    // This is called automatically whenever subscribed data is published.
    SetText( value.GetStr(), false );
}

void pawsEditTextBox::Draw()
{
    if ( clock->GetCurrentTicks() - blinkTicks > BLINK_TICKS )
    {
        blink = !blink;
        blinkTicks = clock->GetCurrentTicks();
    }
    
    pawsWidget::Draw();

    ClipToParent();

    if (!multiLine)
    {
        if (cursorPosition>text.Length())
            cursorPosition=text.Length();
      
        if(start>(int)text.Length())
            start=0;               
            
        if ( text.Length() > 0 )
        {
            // Get the number of characters to draw
            int maxChars;
            maxChars = GetFont()->GetLength( text.GetData() + start, screenFrame.Width());

            if ( (int)cursorPosition > start + maxChars )
                start = (int)cursorPosition - maxChars;
            if ( start < 0 )
                start = 0;

            /// Make the text the correct length
            csString tmp ( text.GetData() + start );
            tmp.Truncate( maxChars );
            if (password) //show astrices instead of text
                 for (unsigned int i=0;i<tmp.Length();i++)
                     tmp.SetAt(i,'*');

            //Get size of text
            int textWidth;
            int textHeight;

            GetFont()->GetDimensions( tmp.GetData(), textWidth, textHeight );

            // Get the center
            int textCenterX = 4;
            int textCenterY = screenFrame.Height() / 2  - textHeight / 2;


            DrawWidgetText( tmp.GetData(),
                            screenFrame.xmin + textCenterX,
                            screenFrame.ymin + textCenterY );

            if ( blink && hasFocus )
            {
                int cursor = (int)cursorPosition - start;
                tmp.Truncate( cursor );

                //Figure out where to put the cursor.
                int width, height;
                GetFont()->GetDimensions( tmp, width, height );

                graphics2D->DrawLine( (float)(screenFrame.xmin + textCenterX + width + 1),
                    (float)(screenFrame.ymin + textCenterY),
                    (float)(screenFrame.xmin + textCenterX + width + 1),
                    (float)(screenFrame.ymin + textCenterY+height),
                    GetFontColour() );
            }
        } // End if stored.Length() > 0
        else if ( blink && hasFocus )
        {
            graphics2D->DrawLine( (float)screenFrame.xmin + 5,
                (float)screenFrame.ymin + 4,
                (float)screenFrame.xmin + 5,
                (float)screenFrame.ymax - 4,
                GetFontColour() );
        }
    }
    else if ( text.Length() > 0 )
    {
        int max = 0;
        if ( border ) 
            max = 8;
        if ( vScrollBar->IsVisible() )
            max += 24;

        int maxChars;

        unsigned int line=0;
        const char* nextLine=text.GetData();
        const char* pos=nextLine;
        csString thisLine;
        for (line=0;pos && *nextLine!='\0';line++)
        {
            pos =strchr(nextLine,'\n');
            thisLine = nextLine;
            if (pos)
            {
                thisLine.Truncate(pos-nextLine);
                nextLine=pos+1;
            }
            if (line<topLine || (unsigned int)start > thisLine.Length())
                continue;

            // No need to break here because we need to find the maximum number of lines too
            if (line>maxLines+topLine)
                continue;

            // Get the number of characters to draw
            maxChars = GetFont()->GetLength( thisLine.GetData() + start, screenFrame.Width() - max);

            // This is the line of text that contains the cursor
            if (line==cursorLine)
            {
                // Calculate number of starting characters that should NOT be drawn
                if (cursorPosition<=thisLine.Length())
                {
                    if ((int)cursorPosition > start + maxChars)
                        start = (int)cursorPosition - maxChars;
                }
                else if ((int)thisLine.Length() > start + maxChars)
                    start = (int)thisLine.Length() - maxChars;
            }
            if ( start < 0 )
                start = 0;
            csString tmp(thisLine.GetData()+start);
            tmp.Truncate(maxChars);

            DrawWidgetText( tmp , 
                            screenFrame.xmin + 4,
                            screenFrame.ymin + (line-topLine)*lineHeight );

            if (blink && hasFocus && line==cursorLine) // Draw the cursor
            {
                if ((int)cursorPosition-start<(int)tmp.Length())
                    tmp.Truncate(cursorPosition-start);

                //Figure out where to put the cursor.
                int width, height;
                GetFont()->GetDimensions( tmp.GetData(), width, height );
                height=lineHeight*(cursorLine-topLine);

                graphics2D->DrawLine( (float)(screenFrame.xmin + 4 + width + 1),
                    (float)(screenFrame.ymin + height),
                    (float)(screenFrame.xmin + 4 + width + 1),
                    (float)(screenFrame.ymin + height + lineHeight), 
                    GetFontColour() );
            }
        }

        if (*nextLine=='\0') // Single line on its own
            if (blink && hasFocus && line==cursorLine) // Draw the cursor
            {
                //Figure out where to put the cursor.
                int height=lineHeight*(cursorLine-topLine);

                graphics2D->DrawLine( (float)(screenFrame.xmin + 4 + 1),
                    (float)(screenFrame.ymin + height),
                    (float)(screenFrame.xmin + 4 + 1),
                    (float)(screenFrame.ymin + height + lineHeight), 
                    GetFontColour() );
            }

        // If line doesn't exist, the maximum line counter needs to be decremented
        if (!pos)
            line--;
        vScrollBar->SetCurrentValue( topLine );
        if (usedLines != line)
        {
            usedLines = line;
            if ( usedLines > maxLines )
            {
                vScrollBar->Show();
                vScrollBar->SetMaxValue( usedLines );
            }
            else
                vScrollBar->Hide();
        }

    }
    else if ( blink && hasFocus ) // Multiline, no text and blinking
    {
        graphics2D->DrawLine( (float)screenFrame.xmin + 5,
            (float)screenFrame.ymin,
            (float)screenFrame.xmin + 5,
            (float)screenFrame.ymin + lineHeight,
            GetFontColour() );
    }
}


bool pawsEditTextBox::OnKeyDown( int code, int key, int modifiers )
{   
    bool changed = false;
    blink = true;
    csString curLine;
    int startLoc=0;
    size_t oldLength=text.Length();

    // Find the line the cursor is on
    if (multiLine && text.Length()>0)
    {
        const char* next=NULL;
        const char* pos=text.GetData();
        for (unsigned int line=0;line<cursorLine;line++)
            pos=strchr(pos, '\n')+1;
        startLoc=pos-text.GetData();
        if (*pos)
            next=strchr(pos,'\n');

        if (next)
        {
            oldLength=next-pos;
            text.SubString(curLine,startLoc,oldLength);
        }
        else
        {
            oldLength=text.Length()-(size_t)startLoc;
            text.SubString(curLine,startLoc,oldLength);
        }
    }
    else curLine=text.GetData();

    switch ( key )
    {
    case CSKEY_DEL:
    {
        if (cursorPosition>=curLine.Length())
            break;
        if ( cursorPosition != (size_t)-1 )
        {
            if ( cursorPosition - start < 5 ) 
                start = (int)cursorPosition - 5;

            if ( start < 0 ) 
                start = 0;

            if ( curLine.Length() > 1 )
            {
                if ( cursorPosition == curLine.Length() )
                {
                    curLine.Truncate( curLine.Length() - 1 );
                }
                else
                {
                    csString tmp( curLine.GetData() );
                    tmp.Truncate( cursorPosition );
                    tmp.Append( curLine.GetData() + cursorPosition + 1 );
                    curLine.Replace( tmp );
                }
            } 
            else
            {
                curLine.Clear();
            }
        } //endif cursor position > 0
        else if (cursorLine != 0)
        {
            startLoc--;
            text.DeleteAt(startLoc);           // Delete newline character
            cursorLine--;
            if (cursorLine<topLine)
                topLine--;
            cursorPosition=1024;
        }
        changed = true; 
        break;        
    }
    
    
    case CSKEY_BACKSPACE:
        if (cursorPosition>curLine.Length())
            cursorPosition=curLine.Length();
        if ( cursorPosition > 0 )
        {

            cursorPosition--;
            if ( cursorPosition - start < 5 ) 
                start = (int)cursorPosition - 5;

            if ( start < 0 ) 
                start = 0;

            if ( curLine.Length() > 1 )
            {
                if ( cursorPosition == curLine.Length() )
                {
                    curLine.Truncate( curLine.Length() - 1 );
                }
                else
                {
                    csString tmp( curLine.GetData() );
                    tmp.Truncate( cursorPosition );
                    tmp.Append( curLine.GetData() + cursorPosition + 1 );
                    curLine.Replace( tmp );
                }
            } 
            else
            {
                curLine.Clear();
            }
        } //endif cursor position > 0
        else if (cursorLine != 0)
        {
            startLoc--;
            text.DeleteAt(startLoc);           // Delete newline character
            cursorLine--;
            if (cursorLine<topLine)
                topLine--;
            cursorPosition=1024;
        }
        changed = true;
        break;
        // End case CSKEY_BACKSPACE

    case CSKEY_LEFT:
        if (cursorPosition>curLine.Length())
            cursorPosition=curLine.Length();
        if ( cursorPosition > 0 )
            cursorPosition--;
        else if (cursorLine != 0)
        {
            cursorLine--;
            if (cursorLine<topLine)
                topLine--;
            cursorPosition=1024;
            break;
        }
        if ( cursorPosition - start < 5 )
            start = (int)cursorPosition - 5;
        if ( start < 0 )
            start = 0;

        break;
    case CSKEY_RIGHT:
        if (cursorPosition>curLine.Length())
            cursorPosition=curLine.Length();
        if ( cursorPosition < curLine.Length() )
        {
            cursorPosition++;
        }
        else if (cursorLine<usedLines)
        {
            cursorLine++;
            cursorPosition=0;
            if (cursorLine>topLine+maxLines)
                topLine++;
        }
        break;
    case CSKEY_UP:
        if (multiLine && cursorLine>0)
            cursorLine--;
        if (cursorLine<topLine)
            topLine--;
        break;
    case CSKEY_DOWN:
        if (multiLine && cursorLine < usedLines)
            cursorLine++;
        if (cursorLine>topLine+maxLines)
            topLine++;
        break;
    case CSKEY_END:
        cursorPosition=1024;
        break;

    case CSKEY_HOME:
        cursorPosition=0;
        start=0;
        break;
    default:
        if ( !isprint((unsigned char)key) && !(multiLine && key==CSKEY_ENTER))
        {
            break;
        }            

        if ( key == CSKEY_ALT || key == CSKEY_CTRL )
        {
            break;
        }
         
        if ( cursorPosition >= curLine.Length() )
        {
            curLine.Append( (char)key );
        }
        else
        {
            curLine.Insert( cursorPosition, (char)key );
        }
        if (key == CSKEY_ENTER)
        {
            cursorLine++;
            cursorPosition=0;
            start=0;
            if (cursorLine>topLine+maxLines)
                topLine++;
        }
        else
            cursorPosition++;

        changed = true;
    }
    
    if (oldLength)
        text.DeleteAt(startLoc,oldLength);
    text.Insert(startLoc,curLine);

    if (changed)
    {
        if (subscribedVar)
            PawsManager::GetSingleton().Publish(subscribedVar, text);
        parent->OnChange(this);
    }

    if ( isprint((unsigned char)key)  )       
        return true;
    
    if (!(multiLine && code== CSKEY_ENTER))
    {        
        pawsWidget::OnKeyDown( code, key, modifiers );
    }

    return true;
}

void pawsEditTextBox::Clear()
{
    start = 0;
    cursorPosition = 0;
    cursorLine = 0;
    usedLines = 0;
    topLine = 0;

    text.Clear();
}

bool pawsEditTextBox::OnMouseDown( int button, int modifiers, int x, int y )
{
    if (button == csmbWheelUp)
    {
        if (vScrollBar)
            vScrollBar->SetCurrentValue(vScrollBar->GetCurrentValue() - EDIT_TEXTBOX_MOUSE_SCROLL_AMOUNT);
        return true;
    }
    else if (button == csmbWheelDown)
    {
        if (vScrollBar)
            vScrollBar->SetCurrentValue(vScrollBar->GetCurrentValue() + EDIT_TEXTBOX_MOUSE_SCROLL_AMOUNT);
        return true;
    }
    
    x -= (screenFrame.xmin+4); // Adjust x to be relative to the text box
    int textWidth;
    int dummy;

    //Force the cursor to blink
    blink = true;

    // Find the line the mouse is on and move the cursor there
    if (multiLine)
        cursorLine=(y-screenFrame.ymin)/lineHeight+topLine;
    if (cursorLine>usedLines)
        cursorLine=(unsigned int)usedLines;

    // Basic comparisons to see if it was clicked after all the text or before all the text or neither
    if (x<=0)
    {
        start=0;
        cursorPosition=0;
        return true;
    }

    csString curLine;
    // Find the line the cursor is on
    if (multiLine && text.Length()>0)
    {
        const char* next=NULL;
        const char* pos=text.GetData();
        for (unsigned int line=0;line<cursorLine;line++)
            pos=strchr(pos, '\n')+1;
        int startLoc=pos-text.GetData();
        if (*(pos+1))
            next=strchr(pos+1,'\n');

        if (next)
        {
            text.SubString(curLine,startLoc,next-pos);
        }
        else
        {
            text.SubString(curLine,startLoc,text.Length()-startLoc);
        }
    }
    else curLine=text.GetData();

    if ( curLine.Length() > 0 )
    {
        GetFont()->GetDimensions(curLine.GetData(),textWidth,dummy);
        if (x>=textWidth)
        {
            cursorPosition=text.Length();
            return true;
        }
    }

    //Find exact letter mouse was clicked on
    int xlast;
    int xlast2=0;
    const char *c;
    char temp[2];
    temp[1]='\0';
    c=temp;

    for (unsigned int i=0;i<curLine.Length();i++)
    {
        xlast=xlast2;
        temp[0]=curLine.GetAt(i);
        GetFont()->GetDimensions(c, textWidth, dummy);
        xlast2+=textWidth;
        if (x >= xlast && x < xlast2)
        {
            cursorPosition=i;
            break;
        }
    }
    return pawsWidget::OnMouseDown( button, modifiers, x ,y);
}

bool pawsEditTextBox::OnScroll( int direction, pawsScrollBar* widget )
{    
    topLine = (int)widget->GetCurrentValue();
    return true;
}

void pawsEditTextBox::SetMultiline(bool multi)
{
    if (multiLine && multi == false)
    {
        multiLine=multi;
        DeleteChild(vScrollBar);
        cursorLine=0;
        return;
    }
    else if ((multiLine && multi == true) || (!multiLine && multi == false))
        return;

    multiLine=true;

    // Define the total number of total allowable drawing lines. 
    int borderOffSize = 0;
    if ( border )
        borderOffSize = 8;

    cursorLine=0;

    maxLines = (screenFrame.Height()-borderOffSize) / (lineHeight) - 1;

    // Create the optional scroll bar here as well but hidden.
    vScrollBar = (pawsScrollBar*) PawsManager::GetSingleton().CreateWidget ( "pawsScrollBar" );
    AddChild( vScrollBar );
    vScrollBar->SetRelativeFrame( screenFrame.Width() - 24, 0, 24, screenFrame.Height());
    int attach = ATTACH_TOP | ATTACH_BOTTOM | ATTACH_RIGHT;
    vScrollBar->SetAttachFlags( attach );
    vScrollBar->UseBorder();
    vScrollBar->PostSetup();
    vScrollBar->SetTickValue( 1.0 );
    vScrollBar->Hide();
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
pawsMultiLineTextBox::pawsMultiLineTextBox()
{
    scrollBar = 0;
    startLine = 0;
    canDrawLines = 0;
    maxHeight = 0;
    maxWidth = 0;
}

pawsMultiLineTextBox::~pawsMultiLineTextBox()
{

}

bool pawsMultiLineTextBox::Setup( iDocumentNode* node )
{    
    csRef<iDocumentNode> textNode = node->GetNode( "text" );        
    
    if ( textNode )
    {        
        csRef<iDocumentAttribute> textAttribute = textNode->GetAttribute("string");
        if ( textAttribute )
        {            
            SetText( textAttribute->GetValue() );
        }
    }        

    return true;
}

         

void pawsMultiLineTextBox::OrganizeText( const char* newText )
{       
    csString text(newText);

    // Check if we end with \n
    if (text.Length() > 0)
    {
        if (text.GetAt(text.Length()-1) == '\n')
        {
            text = text.Slice(0,text.Length()-1);
        }

    }
    startLine = 0;

    GetFont()->GetMaxSize( maxWidth, maxHeight );

    canDrawLines = screenFrame.Height() / maxHeight;

    char* dummy = new char[text.Length() + 1];
    char* head = dummy;

    if (text.Length () > 0)
        strcpy( dummy, text );
    else
        dummy[0] = 0;

    int offSet = 0;
    if ( usingScrollBar ) 
        offSet = 36;


    while ( dummy )
    {
        /// See how many characters can be drawn on a single line.                
        int canDrawLength =  GetFont()->GetLength( dummy, screenFrame.Width()-offSet );

        // Check for linebreaks
        const char* pos=strchr(dummy,'\n');
        csString loopStr(dummy);
        if (pos)
        {
            while(pos)
            {
                csString temp(loopStr.GetData());
                csString temp2(loopStr.GetData());

                size_t pos1=temp.FindFirst('\n');

                temp= temp.Slice(0,pos1);
                temp2= temp2.Slice(pos1+1,(temp2.Length() - pos1-1));

                OrganizeText(temp.GetData());

                if (temp2.Length())
                {
                    loopStr = temp2;
                    pos=strchr(loopStr.GetData(),'\n');
                    if (!pos)
                    {
                        OrganizeText(temp2.GetData());
                    }
                }  
                else
                    break;
            }
            break;
        }

        /// If it can fit the entire string then return.
        if ( canDrawLength == (int)strlen( dummy ) )
        {
            csString temp( dummy );
            lines.Push( temp );
            break;
        }
        // We have to push in a new line to the lines bit.
        else
        {
            // Find out the nearest white space to break the line. 
            int index = canDrawLength;

            while ( index > 0 && dummy[index] != ' ' )
            {
                index--;
            }

            if (index == 0)
                index = canDrawLength;

            // Index now points to the whitespace line so we can break it here.

            csString test;
            test.Append( dummy, index+1 );
            dummy+=index+1;        
            lines.Push( test );            
        }
    }

    delete [] head;    
}


void pawsMultiLineTextBox::SetText( const char* newText )
{
    lines.Empty();
    
    psString str(newText);      
    size_t pos = str.FindSubString("\r");
    while(pos != (size_t)-1)
    {            
        str = str.Slice(0,pos) + "\n" + str.Slice(pos+2,str.Length()-pos-2);
        pos = str.FindSubString("\r");            
    }
            
    
    usingScrollBar = false;
    if ( scrollBar ) scrollBar->Hide();
    
    
    OrganizeText( str.GetData() );
                
    if ( canDrawLines > lines.Length() )
        canDrawLines = lines.Length();
    else
    {
        usingScrollBar = true;
        lines.Empty();    
        OrganizeText( str.GetData() );        
        if ( !scrollBar )
        {
            // Create the optional scroll bar here as well but hidden.
            scrollBar = (pawsScrollBar*) PawsManager::GetSingleton().CreateWidget ( "pawsScrollBar" );
            scrollBar->SetParent( this );
            scrollBar->SetRelativeFrame( defaultFrame.Width() - 40, 6, 24, defaultFrame.Height() - 12 ); 
            scrollBar->PostSetup();
            scrollBar->SetTickValue( 1.0 );
            AddChild( scrollBar );            
        }
        
        scrollBar->Show();
        scrollBar->SetMaxValue(lines.Length() - canDrawLines );
        scrollBar->SetCurrentValue(0);
    }    
    startLine = 0;    
    text.Replace( str.GetData() );    
}

void pawsMultiLineTextBox::OnUpdateData(const char *dataname,PAWSData& value)
{
    // This is called automatically whenever subscribed data is published.
    SetText( value.GetStr() );
}


void pawsMultiLineTextBox::Draw()
{
    pawsWidget::Draw();
    pawsWidget::ClipToParent();
    
    int drawX = screenFrame.xmin;
    int drawY = screenFrame.ymin;
    
    if (!maxHeight && GetFont())
        GetFont()->GetMaxSize( maxWidth, maxHeight );

    if (!canDrawLines && maxHeight)
        canDrawLines = screenFrame.Height() / maxHeight;

    for (size_t x = startLine; x < (startLine+canDrawLines); x++ )
    {
        if ( x >= lines.Length() )
            return;

        DrawWidgetText( (const char*)lines[x], drawX, drawY);

        drawY+=maxHeight;
    }

}

bool pawsMultiLineTextBox::OnScroll( int direction, pawsScrollBar* widget )
{
    startLine = (int)widget->GetCurrentValue();       
    return true;
}

bool pawsMultiLineTextBox::OnMouseDown( int button, int modifiers, int x, int y )
{
    if (button == csmbWheelUp)
    {
        if (scrollBar)
            scrollBar->SetCurrentValue(scrollBar->GetCurrentValue() - MULTILINE_TEXTBOX_MOUSE_SCROLL_AMOUNT);
        return true;
    }
    else if (button == csmbWheelDown)
    {
        if (scrollBar)
            scrollBar->SetCurrentValue(scrollBar->GetCurrentValue() + MULTILINE_TEXTBOX_MOUSE_SCROLL_AMOUNT);
        return true;
    }
    return pawsWidget::OnMouseDown( button, modifiers, x ,y);
}

pawsFadingTextBox::pawsFadingTextBox()
{
    start = 0;
    color = scolor = 0;
    ymod = 0;
    firstFont = NULL;
    font = NULL;
}

void pawsFadingTextBox::SetText(const char* newtext, iFont* font1,iFont* font2, int color,int time,int fadetime)
{
    // Seperate the first and the rest
    text = newtext;
    first = "XXX";

    first.SetAt(0,text.GetAt(0));
    first.SetAt(1,0);
    text = text.Slice(1,text.Length() -1);
    
    // Add the other stuff
    firstFont = font1;
    font = font2;
    start = csGetTicks(); //Start the effect

    this->org_color = color;
    this->time = time;
    this->fadetime = fadetime;

}

void pawsFadingTextBox::GetSize(int &w, int &h)
{
    int iw=0,ih=0;
    int dummy=0;
    // Get the sizes
    firstFont->GetDimensions(first,iw,ih,dummy);
    font->GetDimensions(text,w,h,dummy);

    if(iw > w) // Stretch the size to have space for the first letter
        w = iw;
    if(ih > h)
        h = ih;

    w += BORDER_SIZE*2; // Add the border pixels
    h += BORDER_SIZE*2; // Add the border pixels
}

void pawsFadingTextBox::Draw()
{
    if(!text || text.Length() == 0)
        return;

    // Calculate fading
    int alpha = 0;
    if(csGetTicks() > start + time)
    {
        int elapsed = csGetTicks() - (start + time);

        float multi = float(elapsed) / float(fadetime);
        alpha = int(multi * float(255));
    }

    int r=0,g=0,b=0,a=0;
    graphics2D->GetRGB(org_color,r,g,b,a);
    a -= alpha; // Apply alpha

    if(a < 1)
    {
        // Destroy the widget
        DeleteYourself();
        return;
    }

    int newymod = alpha / 2;

    MoveDelta(0,newymod - ymod); // Move downwards
    ymod = newymod;

    // Prepare to paint the text
    color  = graphics2D->FindRGB(r,g,b,a); // Color on text
    scolor = graphics2D->FindRGB(0,0,0,a); // Color on "shadow"

    int iw=0; // Width on the first letter
    int dummy=-1; // Used as return value for 'desc' and the height

    firstFont->GetDimensions(first,iw,dummy,dummy);

    // Draw
    //ClipToParent();

    DrawBorderText(first,firstFont,0);
    DrawBorderText(text,font,iw);
}

void pawsFadingTextBox::Fade()
{
    if(start < csGetTicks() - time) // Already fading?
        return;

    start = csGetTicks() - time;
}

void pawsFadingTextBox::DrawBorderText(const char* text, iFont* font,int x)
{
    int y = 0;
    x += screenFrame.xmin;
    y += screenFrame.ymin;

    graphics2D->Write(font,x+BORDER_SIZE,y+BORDER_SIZE,scolor,-1,text); // Right down
    graphics2D->Write(font,x-BORDER_SIZE,y+BORDER_SIZE,scolor,-1,text); // Left down
    graphics2D->Write(font,x+BORDER_SIZE,y-BORDER_SIZE,scolor,-1,text); // Right up
    graphics2D->Write(font,x-BORDER_SIZE,y-BORDER_SIZE,scolor,-1,text); // Left up

    graphics2D->Write(font,x,y+BORDER_SIZE,scolor,-1,text); // Down
    graphics2D->Write(font,x,y-BORDER_SIZE,scolor,-1,text); // Up
    graphics2D->Write(font,x+BORDER_SIZE,y,scolor,-1,text); // Right
    graphics2D->Write(font,x-BORDER_SIZE,y,scolor,-1,text); // Left

    // Draw the letter
    graphics2D->Write(font,x,y,color,-1,text);
}
