/*************************************************************************
 *
 *  $RCSfile: textedit.cxx,v $
 *
 *  $Revision: 1.14 $
 *
 *  last change: $Author: obo $ $Date: 2005-04-13 09:08:20 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser 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
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (the "License"); You may not use this file
 *  except in compliance with the License. You may obtain a copy of the
 *  License at http://www.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#ifndef _STREAM_HXX //autogen
#include <tools/stream.hxx>
#endif

#ifndef _TEXTENG_HXX //autogen
#include <svtools/texteng.hxx>
#endif
#ifndef _TEXTVIEW_HXX //autogen
#include <svtools/textview.hxx>
#endif
#ifndef _TXTATTR_HXX //autogen
#include <svtools/txtattr.hxx>
#endif
#ifndef __SBX_SBXMETHOD_HXX //autogen
#include <sbxmeth.hxx>
#endif
#ifndef _BASIC_TTRESHLP_HXX
#include "ttstrhlp.hxx"
#endif

#include "basic.hrc"
#include "textedit.hxx"
#include "appedit.hxx"
#include "brkpnts.hxx"
#include "testtool.hxx"     // defines für das Syntaxhighlighting

TextEditImp::TextEditImp( AppEdit* pParent, const WinBits& aBits )
: Window( pParent, aBits )
, pAppEdit( pParent )
, bHighlightning( FALSE )
, bDoSyntaxHighlight( FALSE )
, bDelayHighlight( TRUE )
, nTipId( 0 )
, bViewMoved( FALSE )
{
    pTextEngine = new TextEngine();
    pTextEngine->SetMaxTextLen( STRING_MAXLEN );
    pTextEngine->EnableUndo( TRUE );

    pTextView = new TextView( pTextEngine, this );
    pTextEngine->InsertView( pTextView );
    pTextEngine->SetModified( FALSE );

    aSyntaxIdleTimer.SetTimeout( 200 );
    aSyntaxIdleTimer.SetTimeoutHdl( LINK( this, TextEditImp, SyntaxTimerHdl ) );

    aImplSyntaxIdleTimer.SetTimeout( 1 );
    aImplSyntaxIdleTimer.SetTimeoutHdl( LINK( this, TextEditImp, SyntaxTimerHdl ) );

    StartListening( *pTextEngine );

    HideTipTimer.SetTimeout( 5000 );    // 5 Sekunden
    ShowTipTimer.SetTimeout( 500 );     // 1/2 Sekunde
    HideTipTimer.SetTimeoutHdl( LINK( this, TextEditImp, HideVarContents ) );
    ShowTipTimer.SetTimeoutHdl( LINK( this, TextEditImp, ShowVarContents ) );
}

TextEditImp::~TextEditImp()
{
    pTextEngine->RemoveView( pTextView );
    delete pTextView;
    delete pTextEngine;
}

BOOL TextEditImp::ViewMoved()
{
    BOOL bOld = bViewMoved;
    bViewMoved = FALSE;
    return bOld;
}

void TextEditImp::Notify( SfxBroadcaster& rBC, const SfxHint& rHint )
{
    if ( rHint.ISA( TextHint ) )
    {
        const TextHint& rTextHint = (const TextHint&)rHint;
        if( rTextHint.GetId() == TEXT_HINT_VIEWSCROLLED )
        {
            pAppEdit->pHScroll->SetThumbPos( pTextView->GetStartDocPos().X() );
            pAppEdit->pVScroll->SetThumbPos( pTextView->GetStartDocPos().Y() );
            if ( ((TextEdit*)(pAppEdit->pDataEdit))->GetBreakpointWindow() )
                ((TextEdit*)(pAppEdit->pDataEdit))->GetBreakpointWindow()->Scroll( 0, ((TextEdit*)(pAppEdit->pDataEdit))->GetBreakpointWindow()->GetCurYOffset() - pTextView->GetStartDocPos().Y() );
            bViewMoved = TRUE;
        }
        else if( rTextHint.GetId() == TEXT_HINT_TEXTHEIGHTCHANGED )
        {
            if ( pTextView->GetStartDocPos().Y() )
            {
                long nOutHeight = GetOutputSizePixel().Height();
                long nTextHeight = pTextEngine->GetTextHeight();
                if ( nTextHeight < nOutHeight )
                    pTextView->Scroll( 0, pTextView->GetStartDocPos().Y() );
            }

            pAppEdit->SetScrollBarRanges();
        }
        else if( rTextHint.GetId() == TEXT_HINT_TEXTFORMATTED )
        {
            ULONG nWidth = pTextEngine->CalcTextWidth();
            if ( (ULONG)nWidth != pAppEdit->nCurTextWidth )
            {
                pAppEdit->nCurTextWidth = nWidth;
                if ( pAppEdit->pHScroll )
                {   // Initialisierung abgeschlossen?
                    pAppEdit->pHScroll->SetRange( Range( 0, (long)nWidth) );
                    pAppEdit->pHScroll->SetThumbPos( pTextView->GetStartDocPos().X() );
                }
            }
        }
        else if( rTextHint.GetId() == TEXT_HINT_PARAINSERTED )
        {
            if ( ((TextEdit*)(pAppEdit->pDataEdit))->GetBreakpointWindow() )
                ((TextEdit*)(pAppEdit->pDataEdit))->GetBreakpointWindow()->AdjustBreakpoints( rTextHint.GetValue()+1, TRUE );

            // Lästiges anpassen für 2 Zeichen am Zeilenende statt einem(Hartverdrateter Default)
            pTextEngine->SetMaxTextLen( STRING_MAXLEN - pTextEngine->GetParagraphCount() );
        }
        else if( rTextHint.GetId() == TEXT_HINT_PARAREMOVED )
        {
            if ( ((TextEdit*)(pAppEdit->pDataEdit))->GetBreakpointWindow() )
                ((TextEdit*)(pAppEdit->pDataEdit))->GetBreakpointWindow()->AdjustBreakpoints( rTextHint.GetValue()+1, FALSE );

            // Lästiges anpassen für 2 Zeichen am Zeilenende statt einem(Hartverdrateter Default)
            pTextEngine->SetMaxTextLen( STRING_MAXLEN - pTextEngine->GetParagraphCount() );
        }
        else if( rTextHint.GetId() == TEXT_HINT_FORMATPARA )
        {
            DoDelayedSyntaxHighlight( rTextHint.GetValue() );
            if ( pTextView->GetTextEngine()->IsModified() )
                ModifyHdl.Call( NULL );
        }
    }
}

#define TEXTATTR_SPECHIAL   55
class TextAttribSpechial : public TextAttrib
{
private:
    FontWeight  maFontWeight;

public:
                            TextAttribSpechial( const FontWeight& rFontWeight );
                            TextAttribSpechial( const TextAttribSpechial& rAttr );
                            ~TextAttribSpechial() {;}

    virtual void            SetFont( Font& rFont ) const;
    virtual TextAttrib*     Clone() const;
    virtual int             operator==( const TextAttrib& rAttr ) const;
};

TextAttribSpechial::TextAttribSpechial( const FontWeight& rFontWeight )
    : TextAttrib( TEXTATTR_SPECHIAL ), maFontWeight( rFontWeight )
{}

TextAttribSpechial::TextAttribSpechial( const TextAttribSpechial& rAttr )
    : TextAttrib( rAttr ), maFontWeight( rAttr.maFontWeight )
{}

void TextAttribSpechial::SetFont( Font& rFont ) const
{
    rFont.SetWeight( maFontWeight );
}

TextAttrib* TextAttribSpechial::Clone() const
{
    return new TextAttribSpechial( *this );
}

int TextAttribSpechial::operator==( const TextAttrib& rAttr ) const
{
    return ( ( TextAttrib::operator==(rAttr ) ) &&
                ( maFontWeight == ((const TextAttribSpechial&)rAttr).maFontWeight ) );
}

void TextEditImp::ImpDoHighlight( const String& rSource, ULONG nLineOff )
{
    SbTextPortions aPortionList;
    pAppEdit->GetBasicFrame()->Basic().Highlight( rSource, aPortionList );

    USHORT nCount = aPortionList.Count();
    if ( !nCount )
        return;

    SbTextPortion& rLast = aPortionList[nCount-1];
    if ( rLast.nStart > rLast.nEnd )    // Nur bis Bug von MD behoben
    {
#if OSL_DEBUG_LEVEL > 1
        DBG_ERROR( "MD-Bug nicht beseitigt!" );
#endif
        nCount--;
        aPortionList.Remove( nCount);
        if ( !nCount )
            return;
    }

    /// hier werden die Type für das Testtool nachbearbeitet
    USHORT i;
    BOOL bWasTTControl = FALSE;
    for ( i = 0; i < aPortionList.Count(); i++ )
    {
        SbTextPortion& r = aPortionList[i];
//      DBG_ASSERT( r.nStart <= r.nEnd, "Highlight: Start > End?" );
        if ( r.nStart > r.nEnd )    // Nur bis Bug von MD behoben
            continue;

        SbTextType eType = r.eType;
        Color aColor;
        switch ( eType )
        {
            case SB_SYMBOL:
                {
                     String aSymbol = rSource.Copy( r.nStart, r.nEnd - r.nStart +1 );
                    r.eType = pAppEdit->GetBasicFrame()->Basic().GetSymbolType( aSymbol, bWasTTControl );

                    if ( r.eType == TT_CONTROL )
                        bWasTTControl = TRUE;
                    else
                        bWasTTControl = FALSE;
                }
                break;
            case SB_PUNCTUATION:
                {
                     String aPunctuation = rSource.Copy( r.nStart, r.nEnd - r.nStart +1 );
                    if ( aPunctuation.CompareToAscii( "." ) != COMPARE_EQUAL )
                        bWasTTControl = FALSE;
                }
                break;
            default:
                bWasTTControl = FALSE;
        }
    }

    // Evtl. Optimieren:
    // Wenn haufig gleiche Farbe, dazwischen Blank ohne Farbe,
    // ggf. zusammenfassen, oder zumindest das Blank,
    // damit weniger Attribute
    if ( TRUE /*bOptimizeHighlight*/ )
    {
        // Es muessen nur die Blanks und Tabs mit attributiert werden.
        // Wenn zwei gleiche Attribute hintereinander eingestellt werden,
        // optimiert das die EditEngine.
        xub_StrLen nLastEnd = 0;
        xub_StrLen nLine = aPortionList[0].nLine;
        for ( USHORT i = 0; i < nCount; i++ )
        {
            SbTextPortion& r = aPortionList[i];
            DBG_ASSERT( r.nLine == nLine, "doch mehrere Zeilen ?" );
            DBG_ASSERT( r.nStart <= r.nEnd, "Highlight: Start > End?" );
            if ( r.nStart > r.nEnd )    // Nur bis Bug von MD behoben
                continue;

            if ( r.nStart > nLastEnd )
            {
                // Kann ich mich drauf verlassen, dass alle ausser
                // Blank und Tab gehighlightet wird ?!
                r.nStart = nLastEnd;
            }
            nLastEnd = r.nEnd+1;
            if ( ( i == (nCount-1) ) && ( r.nEnd < rSource.Len() ) )
                r.nEnd = rSource.Len()-1;
        }
    }

    BOOL bWasModified = pTextEngine->IsModified();
    for ( i = 0; i < aPortionList.Count(); i++ )
    {
        SbTextPortion& r = aPortionList[i];
//      DBG_ASSERT( r.nStart <= r.nEnd, "Highlight: Start > End?" );
        if ( r.nStart > r.nEnd )    // Nur bis Bug von MD behoben
            continue;

        SbTextType eCol = r.eType;
        Color aColor;
        xub_StrLen nLine = nLineOff+r.nLine-1; // -1, weil Basic bei 1 beginnt
        switch ( eCol )
        {
            case SB_KEYWORD:
                aColor = Color( COL_BLUE );
                break;
            case SB_SYMBOL:
                aColor = Color( RGB_COLORDATA( 0x00, 0x60, 0x00 ) );
                break;
            case SB_STRING:
                aColor = Color( COL_RED );
                break;
            case SB_NUMBER:
                aColor = Color( COL_MAGENTA );
                break;
            case SB_PUNCTUATION:
                aColor = Color( COL_BLACK );
                break;
            case SB_COMMENT:
                aColor = Color( COL_GRAY );
                break;
            case TT_KEYWORD:
            case TT_LOCALCMD:
                aColor = Color( COL_LIGHTBLUE );
                break;
            case TT_REMOTECMD:
                aColor = Color( RGB_COLORDATA( 0x00, 0xB0, 0xB0 ) );
                break;
            case TT_CONTROL:
            case TT_SLOT:
                aColor = Color( RGB_COLORDATA( 0x00, 0xB0, 0x00 ) );
                break;
            case TT_METHOD:
                aColor = Color( RGB_COLORDATA( 0x00, 0xB0, 0x00 ) );
                break;
            case TT_NOMETHOD:
                {
                    aColor = Color( COL_RED );
                    pTextEngine->SetAttrib( TextAttribSpechial( WEIGHT_BOLD ), nLine, r.nStart, r.nEnd+1 );
                }
                break;
            default:
                {
                    aColor = Color( RGB_COLORDATA( 0xff, 0x80, 0x80 ) );
                    DBG_ERROR( "Unbekannte Syntax-Farbe?" );
                }
        }
        pTextEngine->SetAttrib( TextAttribFontColor( aColor ), nLine, r.nStart, r.nEnd+1 );
    }
    // Das Highlighten soll kein Modify setzen
    pTextEngine->SetModified( bWasModified );
}

void TextEditImp::DoSyntaxHighlight( ULONG nPara )
{
    // Durch das DelayedSyntaxHighlight kann es passieren,
    // dass die Zeile nicht mehr existiert!
    if ( nPara < pTextEngine->GetParagraphCount() )
    {
        // leider weis ich nicht, ob genau diese Zeile Modified() ...
//      if ( pProgress )
//          pProgress->StepProgress();
        pTextEngine->RemoveAttribs( nPara );
        String aSource( pTextEngine->GetText( nPara ) );
        ImpDoHighlight( aSource, nPara );
    }
}

void TextEditImp::DoDelayedSyntaxHighlight( xub_StrLen nPara )
{
    // Zeile wird nur in 'Liste' aufgenommen, im TimerHdl abgearbeitet.
    // => Nicht Absaetze manipulieren, waehrend EditEngine formatiert.
//  if ( pProgress )
//      pProgress->StepProgress();

    if ( !bHighlightning && bDoSyntaxHighlight )
    {
        if ( bDelayHighlight )
        {
            aSyntaxLineTable.Insert( nPara, (void*)(ULONG)1 );
            aSyntaxIdleTimer.Start();
        }
        else
            DoSyntaxHighlight( nPara );
    }
}

IMPL_LINK( TextEditImp, SyntaxTimerHdl, Timer *, EMPTYARG )
{
    DBG_ASSERT( pTextView, "Noch keine View, aber Syntax-Highlight ?!" );
    pTextEngine->SetUpdateMode( FALSE );

    bHighlightning = TRUE;
    USHORT nLine;
    while ( aSyntaxLineTable.First() && !Application::AnyInput( INPUT_MOUSEANDKEYBOARD ) )
    {
        nLine = (USHORT)aSyntaxLineTable.GetCurKey();
        DoSyntaxHighlight( nLine );
        aSyntaxLineTable.Remove( nLine );
/*      if ( Application::AnyInput() )
        {
            aSyntaxIdleTimer.Start();       // Starten, falls wir in einem Dialog landen
            pTextView->ShowCursor( TRUE, TRUE );
            pTextEngine->SetUpdateMode( TRUE );
            bHighlightning = FALSE;
            GetpApp()->Reschedule();
            bHighlightning = TRUE;
            pTextEngine->SetUpdateMode( FALSE );
        }*/
    }

    BOOL bWasModified = pTextEngine->IsModified();
    if ( aSyntaxLineTable.Count() > 3 )                 // Ohne VDev
    {
        pTextEngine->SetUpdateMode( TRUE );
        pTextView->ShowCursor( TRUE, TRUE );
    }
    else
        pTextEngine->SetUpdateMode( TRUE );             // ! Mit VDev
//  pTextView->ForceUpdate();

    // SetUpdateMode( TRUE ) soll kein Modify setzen
    pTextEngine->SetModified( bWasModified );

    // SyntaxTimerHdl wird gerufen, wenn Text-Aenderung
    // => gute Gelegenheit, Textbreite zu ermitteln!
//  long nPrevTextWidth = nCurTextWidth;
//  nCurTextWidth = pTextEngine->CalcTextWidth();
//  if ( nCurTextWidth != nPrevTextWidth )
//      SetScrollBarRanges();
    bHighlightning = FALSE;

    if ( aSyntaxLineTable.First() )
        aImplSyntaxIdleTimer.Start();

//  while ( Application::AnyInput() )
//      Application::Reschedule();  // Reschedule, da der UserEvent keine Paints etc. durchlässt

    return 0;
}

void TextEditImp::InvalidateSyntaxHighlight()
{
    for ( xub_StrLen i = 0; i < pTextEngine->GetParagraphCount(); i++ )
        DoDelayedSyntaxHighlight( i );
}

void TextEditImp::SyntaxHighlight( BOOL bNew )
{
    if ( ( bNew && bDoSyntaxHighlight ) || ( !bNew && !bDoSyntaxHighlight ) )
        return;

    bDoSyntaxHighlight = bNew;
    if ( !pTextEngine )
        return;


    if ( bDoSyntaxHighlight )
    {
        InvalidateSyntaxHighlight();
    }
    else
    {
        aSyntaxIdleTimer.Stop();
        pTextEngine->SetUpdateMode( FALSE );
        for ( ULONG i = 0; i < pTextEngine->GetParagraphCount(); i++ )
            pTextEngine->RemoveAttribs( i );

//      pTextEngine->QuickFormatDoc();
        pTextEngine->SetUpdateMode( TRUE );
        pTextView->ShowCursor(TRUE, TRUE );
    }
}


void TextEditImp::SetFont( const Font& rNewFont )
{
    pTextEngine->SetFont(rNewFont);
}

BOOL TextEditImp::IsModified()
{
    return pTextEngine->IsModified();
}

void TextEditImp::KeyInput( const KeyEvent& rKeyEvent )
{
    BOOL bWasModified = pTextView->GetTextEngine()->IsModified();
    pTextView->GetTextEngine()->SetModified( FALSE );

    if ( !pTextView->KeyInput( rKeyEvent ) )
        Window::KeyInput( rKeyEvent );

    if ( pTextView->GetTextEngine()->IsModified() )
        ModifyHdl.Call( NULL );
    else
        pTextView->GetTextEngine()->SetModified( bWasModified );    // Eventuell wieder setzen
}

void TextEditImp::Paint( const Rectangle& rRect ){ pTextView->Paint( rRect );}
void TextEditImp::MouseButtonUp( const MouseEvent& rMouseEvent ){ pTextView->MouseButtonUp( rMouseEvent );}
//void TextEditImp::MouseButtonDown( const MouseEvent& rMouseEvent ){ pTextView->MouseButtonDown( rMouseEvent );}
//void TextEditImp::MouseMove( const MouseEvent& rMouseEvent ){ pTextView->MouseMove( rMouseEvent );}
//void TextEditImp::Command( const CommandEvent& rCEvt ){ pTextView->Command( rCEvt );}
//BOOL TextEditImp::Drop( const DropEvent& rEvt ){ return FALSE /*pTextView->Drop( rEvt )*/;}
//BOOL TextEditImp::QueryDrop( DropEvent& rEvt ){ return FALSE /*pTextView->QueryDrop( rEvt )*/;}


void TextEditImp::Command( const CommandEvent& rCEvt )
{
    switch( rCEvt.GetCommand() ) {
        case COMMAND_CONTEXTMENU:
        case COMMAND_WHEEL:
            GetParent()->Command( rCEvt );
            break;
        default:
            pTextView->Command( rCEvt );
    }
}


void TextEditImp::MouseButtonDown( const MouseEvent& rMouseEvent )
{
    pTextView->MouseButtonDown( rMouseEvent );
    HideVarContents( NULL );
    ShowTipTimer.Stop();
}


void TextEditImp::MouseMove( const MouseEvent& rMEvt )
{
    pTextView->MouseMove( rMEvt );
    HideVarContents( NULL );
    if ( rMEvt.GetButtons() == 0 )
        ShowTipTimer.Start();
    if ( rMEvt.IsLeaveWindow() )
        ShowTipTimer.Stop();
}


IMPL_LINK( TextEditImp, HideVarContents, void*, EMPTYARG )
{
    if ( nTipId )
    {
        Help::HideTip( nTipId );
        nTipId = 0;
        aTipWord = String();
    }
    return 0;
}

static const char cSuffixes[] = "%&!#@$";


SbxBase* TextEditImp::GetSbxAtMousePos( String &aWord )
{
    Point aPos = GetPointerPosPixel();
    Point aDocPos = pTextView->GetDocPos( aPos );
    aWord = pTextEngine->GetWord( pTextEngine->GetPaM( aDocPos ) );

    if ( aWord.Len() /*&& !Application::GetAppInternational().IsNumeric( aWord )*/ )
    {
        xub_StrLen nLastChar = aWord.Len()-1;
        String aSuffixes = CUniString( cSuffixes );
        if ( aSuffixes.Search( aWord.GetChar(nLastChar) ) != STRING_NOTFOUND )
            aWord.Erase( nLastChar, 1 );

        BOOL bWasError = SbxBase::IsError();    // Da eventuell im Testtool ein Fehler geschmissen wird.
        pAppEdit->GetBasicFrame()->Basic().DebugFindNoErrors( TRUE );
        SbxBase* pSBX = StarBASIC::FindSBXInCurrentScope( aWord );
        pAppEdit->GetBasicFrame()->Basic().DebugFindNoErrors( FALSE );
        DBG_ASSERT( !( !bWasError && SbxBase::IsError()), "Error generated while retrieving Variable data for viewing" )
        if ( !bWasError && SbxBase::IsError() )
            SbxBase::ResetError();

        return pSBX;
    }
    return NULL;
}


IMPL_LINK( TextEditImp, ShowVarContents, void*, EMPTYARG )
{
    String aWord;
    SbxBase* pSBX = GetSbxAtMousePos( aWord );
    String aHelpText;
    Point aPos = GetPointerPosPixel();

    if ( pSBX && pSBX->ISA( SbxVariable ) && !pSBX->ISA( SbxMethod ) )
    {
        SbxVariable* pVar = (SbxVariable*)pSBX;
        SbxDataType eType = pVar->GetType();
        if ( eType == SbxOBJECT )
        {
            // Kann zu Absturz, z.B. bei Selections-Objekt fuehren
            // Type == Object heisst nicht, dass pVar == Object!
            if ( pVar->GetObject() && pVar->GetObject()->ISA( SbxObject ) )
                aHelpText = ((SbxObject*)(pVar->GetObject()))->GetClassName();
            else
                aHelpText = CUniString("Object");
        }
        else if ( eType & SbxARRAY )
            aHelpText = CUniString("{...}");
        else if ( eType != SbxEMPTY )
        {
            aHelpText = pVar->GetName();
            if ( !aHelpText.Len() )     // Bei Uebergabeparametern wird der Name nicht kopiert
                aHelpText = aWord;
            aHelpText += '=';
            aHelpText += pVar->GetString();
        }
    }


    if ( aHelpText.Len() && aTipPos != aPos && aTipWord != aWord )
    {
        if ( nTipId )
            Help::HideTip( nTipId );
        nTipId = Help::ShowTip( this, Rectangle(), aHelpText );

        HideTipTimer.Start();
        aTipWord = aWord;
        aTipPos = aPos;
    }
    if ( nTipId && aTipPos != aPos )
    {
        Help::HideTip( nTipId );
        nTipId = 0;
        aTipWord = String();
    }

    return 0;
}


void TextEditImp::BuildKontextMenu( PopupMenu *&pMenu )
{
    String aWord;
    SbxBase* pSBX = GetSbxAtMousePos( aWord );
    if ( pSBX && pSBX->ISA( SbxVariable ) && !pSBX->ISA( SbxMethod ) )
    {
        SbxVariable* pVar = (SbxVariable*)pSBX;
        SbxDataType eType = pVar->GetType();

        if ( ( eType & ( SbxVECTOR | SbxARRAY | SbxBYREF )) == 0 )
        {

/*
Boolean
Currency
Date
Double
Integer
Long
Object
Single
String
Variant(Empty)
*/
            switch ( eType )
            {
                case SbxBOOL:
//              case SbxCURRENCY:
//              case SbxDATE:
                case SbxDOUBLE:
                case SbxINTEGER:
                case SbxLONG:
//              case SbxOBJECT:     // kann nicht editiert werden
                case SbxSINGLE:
                case SbxSTRING:

                case SbxVARIANT:    // Taucht wohl auch nicht auf. stattdessen SbxEMPTY
                case SbxEMPTY:
                    {
                        pAppEdit->GetBasicFrame()->SetEditVar( pVar );
                        if ( !pMenu )
                            pMenu = new PopupMenu();
                        else
                            pMenu->InsertSeparator();

                        pMenu->InsertItem( RID_POPUPEDITVAR, ((BasicFrame*)GetpApp()->GetAppWindow())->GenRealString( GEN_RES_STR1( IDS_EDIT_VAR, aWord ) ) );
                    }
                    break;
            }
        }
    }
}




DBG_NAME(TextEdit)

TextEdit::TextEdit( AppEdit* pParent, const WinBits& aBits )
: aEdit( pParent, aBits | WB_NOHIDESELECTION )
, pBreakpointWindow( NULL )
, bFileWasUTF8( FALSE )
, bSaveAsUTF8( FALSE )
{
DBG_CTOR(TextEdit,0);
}

TextEdit::~TextEdit()
{DBG_DTOR(TextEdit,0);}

void TextEdit::Highlight( ULONG nLine, xub_StrLen nCol1, xub_StrLen nCol2 )
{
    if ( nLine )    // Kann eigentlich nicht vorkommen, außer bei Fehler 'Sub erwartet' in erster Zeile
        nLine--;

    String s = aEdit.pTextEngine->GetText( nLine );

    if( nCol1 == STRING_NOTFOUND )
        // Keine Spalte angegeben
        nCol1 = 0, nCol2 = STRING_NOTFOUND;
    if( nCol2 == STRING_NOTFOUND )
    {
        nCol2 = s.Len();
    }
    // Anpassung an den Precompiler | EditText != Compilierter Text
    if ( nCol2 > s.Len() )
        nCol2 = s.Len();
    if ( nCol1 >= nCol2 )
        nCol1 = 0;

    // Da nCol2 u.U. hinter das aktuelle Statement zeigt
    // (weil dort schon das naechste beginnt), muessen
    // wird am Ende evtl. Whitespace, einen Doppelpunkt
    // und mehr Whitespace entfernen
    BOOL bColon = FALSE;

    while ( s.GetChar( nCol2 ) == ' ' && nCol2 > nCol1 && !bColon )
    {
        nCol2--;
        if ( s.GetChar( nCol2 ) == ':' )
        {
            nCol2--;
            bColon = TRUE;
        }
    }

    aEdit.ViewMoved();
    aEdit.pTextView->SetSelection( TextSelection(TextPaM(nLine,nCol2+1), TextPaM(nLine,nCol1)) );
    if ( aEdit.ViewMoved() )
    {
        aEdit.pTextView->SetSelection( TextSelection(TextPaM(TEXT_PARA_ALL,1)) );   // fix #105169#
        aEdit.pTextView->SetSelection( TextSelection(TextPaM((nLine>=2?nLine-2:0),nCol2+1)) );
        aEdit.pTextView->SetSelection( TextSelection(TextPaM(nLine,nCol2+1), TextPaM(nLine,nCol1)) );
    }
}


void TextEdit::Delete(){ aEdit.pTextView->KeyInput( KeyEvent( 0, KeyCode( KEYFUNC_DELETE ) )); }
void TextEdit::Cut(){ aEdit.pTextView->Cut(); }
void TextEdit::Copy(){ aEdit.pTextView->Copy(); }
void TextEdit::Paste(){ aEdit.pTextView->Paste(); }
void TextEdit::Undo(){ aEdit.pTextView->Undo(); }
void TextEdit::Redo(){ aEdit.pTextView->Redo(); }
String TextEdit::GetSelected(){ return aEdit.pTextView->GetSelected(); }
TextSelection TextEdit::GetSelection() const{ return aEdit.pTextView->GetSelection(); }
void TextEdit::SetSelection( const TextSelection& rSelection ){ aEdit.pTextView->SetSelection( rSelection ); }

USHORT TextEdit::GetLineNr() const
{
    return aEdit.pTextView->GetSelection().GetEnd().GetPara()+1;
}

void TextEdit::ReplaceSelected( const String& rStr ){ aEdit.pTextView->InsertText(rStr); }
BOOL TextEdit::IsModified(){ return aEdit.IsModified(); }

String TextEdit::GetText() const
{
    return aEdit.pTextEngine->GetText( GetSystemLineEnd() );
}

void TextEdit::SetText( const String& rStr ){ aEdit.pTextEngine->SetText(rStr); aEdit.pTextEngine->SetModified( FALSE ); }
void TextEdit::SetModifyHdl( Link l ){ aEdit.SetModifyHdl(l); }
BOOL TextEdit::HasText() const { return aEdit.pTextEngine->GetTextLen() > 0; }

// Es wird entweder ab Beginn oder ab Markierungsbegin + 1 gesucht.

BOOL TextEdit::Find( const String& s )
{
    DBG_CHKTHIS(TextEdit,0);

    TextSelection aSelection = aEdit.pTextView->GetSelection();
    USHORT nPara = aSelection.GetStart().GetPara();
    xub_StrLen nIndex = aSelection.GetStart().GetIndex();

    if ( aSelection.HasRange() )
        nIndex ++;

    while ( nPara <= aEdit.pTextEngine->GetParagraphCount() )
    {
        String aText = aEdit.pTextEngine->GetText( nPara );

        nIndex = aText.Search( s, nIndex );
        if( nIndex != STRING_NOTFOUND )
        {
            aEdit.pTextView->SetSelection( TextSelection( TextPaM( nPara, nIndex ), TextPaM( nPara, nIndex + s.Len() ) ) );
            return TRUE;
        }
        nIndex = 0;
        nPara++;
    }
    return FALSE;
}

BOOL TextEdit::Load( const String& aName )
{
DBG_CHKTHIS(TextEdit,0);
    BOOL bOk = TRUE;
    SvFileStream aStrm( aName, STREAM_STD_READ );
    if( aStrm.IsOpen() )
    {
        String aText, aLine, aLineBreak;
        BOOL bIsFirstLine = TRUE;
        aLineBreak += '\n';
        aLineBreak.ConvertLineEnd();
        rtl_TextEncoding aFileEncoding = RTL_TEXTENCODING_IBM_850;
        while( !aStrm.IsEof() && bOk )
        {
            aStrm.ReadByteStringLine( aLine, aFileEncoding );
            if ( bIsFirstLine && IsTTSignatureForUnicodeTextfile( aLine ) )
            {
                aFileEncoding = RTL_TEXTENCODING_UTF8;
                bFileWasUTF8 = TRUE;
            }
            else
            {
                if ( !bIsFirstLine )
                    aText += aLineBreak;
                aText += aLine;
                bIsFirstLine = FALSE;
            }
            if( aStrm.GetError() != SVSTREAM_OK )
                bOk = FALSE;
        }
        SetText( aText );
    }
    else
        bOk = FALSE;
    return bOk;
}

BOOL TextEdit::Save( const String& aName )
{
DBG_CHKTHIS(TextEdit,0);
    BOOL bOk = TRUE;
    SvFileStream aStrm( aName, STREAM_STD_WRITE | STREAM_TRUNC );
    rtl_TextEncoding aFileEncoding = RTL_TEXTENCODING_IBM_850;
    if( aStrm.IsOpen() )
    {
        if ( bFileWasUTF8 || bSaveAsUTF8 )
        {
            aStrm << TT_SIGNATURE_FOR_UNICODE_TEXTFILES;
            aStrm << sal_Char(_CR);
            aStrm << sal_Char(_LF);
            aFileEncoding = RTL_TEXTENCODING_UTF8;
        }
        String aSave = GetText();
        aSave.ConvertLineEnd(LINEEND_CRLF);
        aStrm << ByteString( aSave, aFileEncoding ).GetBuffer();
        if( aStrm.GetError() != SVSTREAM_OK )
            bOk = FALSE;
        else
            aEdit.pTextEngine->SetModified(FALSE);
    } else bOk = FALSE;
    return bOk;
}


void TextEdit::BuildKontextMenu( PopupMenu *&pMenu )
{
    DataEdit::BuildKontextMenu( pMenu );
    aEdit.BuildKontextMenu( pMenu );
}


