/***************************************************************************
 *   Copyright (C) 2005 by Thierry CHARLES                                 *
 *   thierry@les-charles.net                                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "tcodeeditor.h"


#include <wx/dcclient.h>
#include <wx/dcmemory.h>
#include <wx/font.h>
#include <wx/strconv.h>
#include <wx/settings.h>
#include <wx/clipbrd.h>
#include <wx/dataobj.h>
#include "components/stdgui/tlistctrl.h"
#include <wx/sizer.h>

#include <iostream>
#include <math.h>

#include "components/stdgui/tpanel.h"


#include "lib/lib_string.h"
#include "lib/lib_logging.h"
#include "lib/lib_file.h"

#define TAB_SIZE 4

#define VALIDATE_AUTO_COMPLETE_EVENT_ID 65535
#define TERMINATE_AUTO_COMPLETE_EVENT_ID 65534

DEFINE_EVENT_TYPE(EVENT_AUTO_COMPLETION)


BEGIN_EVENT_TABLE( TCodeEditor, wxScrolledWindow )
    EVT_CHAR(TCodeEditor::OnCharEvent)
    EVT_KEY_DOWN(TCodeEditor::OnKeyDownEvent)
    EVT_MOUSE_EVENTS(TCodeEditor::OnMouseEvent)
    EVT_SET_FOCUS(TCodeEditor::OnFocusIn)
    EVT_KILL_FOCUS(TCodeEditor::OnFocusOut)
    EVT_COMMAND(VALIDATE_AUTO_COMPLETE_EVENT_ID,EVENT_AUTO_COMPLETION,TCodeEditor::onAutoCompletionEvent)
    EVT_COMMAND(TERMINATE_AUTO_COMPLETE_EVENT_ID,EVENT_AUTO_COMPLETION,TCodeEditor::onAutoCompletionEvent)
END_EVENT_TABLE()


TCodeEditor::TCodeEditor(wxWindow* parent, wxWindowID id, const wxString& name)
    : wxScrolledWindow(parent, id, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER | wxWANTS_CHARS | wxVSCROLL | wxHSCROLL, name),
    iLastMaxLineLength(1),
    enlighter(NULL),
    autoCompletionCtrl(NULL),
    autoCompletionSrc(NULL),
    bUpdatingDefaultSelection(false),
#ifdef __WXMSW__
    bLinuxSelectionToClipboard(false),
#else
    bLinuxSelectionToClipboard(true),
#endif
    bRemovingText(false),
    typeMode(Insert),
    bBracketsHighlightingEnabled(true)
{
    this->SetCursor(wxCursor(wxCURSOR_IBEAM));
    this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));

    this->SetFont(this->GetFont()); // init la hauteur de ligne

    this->cursor = new TCursor(this);
    this->cursor->setVisible(false);
    this->cursor->addCursorListener(this);

    this->selections[TCODEEDITOR_DEFAULT_SELECTION].addSelectionListener(this);
    this->selections[TCODEEDITOR_OPEN_BRACKET_SELECTION].addSelectionListener(this);
    this->selections[TCODEEDITOR_CLOSE_BRACKET_SELECTION].addSelectionListener(this);

    this->document.addDocumentListener(this);
    this->document.setUndoBufferCapacity(1000);

    this->defaultEnlighter = new TDefaultEnlighter(this);
    this->setEnlighter(NULL);
    this->removeStyle(DEFAULT_STYLE);
    this->removeStyle(SELECTED_STYLE);
}


TCodeEditor::~TCodeEditor()
{
    delete this->cursor;
    if(this->enlighter && this->enlighter != this->defaultEnlighter)
        delete this->enlighter; // autrement on ne pourra plus le faire
    TEnlighter * elighter = this->defaultEnlighter;
    this->defaultEnlighter = NULL;
    if(elighter)
        delete elighter;
    this->document.removeDocumentListener(this);
}

/** calcule la taille optimale de la zone de saisie */
wxSize TCodeEditor::DoGetBestSize() const
{
    wxSize scrollSize = wxScrolledWindow::GetMinSize();
    wxSize bestSize;
    int iMaxLen = this->document.calcMaxLineLength();
    if( iMaxLen < 10)
        iMaxLen = 10;
    int iLinesCount = this->document.getLinesCount();
    if(iLinesCount < 2)
        iLinesCount = 2;
    bestSize.SetWidth(scrollSize.GetWidth() + (this->getCharWidth() * iMaxLen));
    bestSize.SetHeight(scrollSize.GetHeight() + (this->getLineHeight() * iLinesCount));

    return bestSize;
}

void TCodeEditor::OnDraw(wxDC& dc)
{
    static bool bDrawing = false;

    if(bDrawing || !this->IsShown())
        return;
    bDrawing = true;

    wxScrolledWindow::OnDraw(dc);

    wxSize newSize = this->GetVirtualSize();
    wxSize clientSize = this->GetClientSize();
    if(clientSize.GetHeight() > newSize.GetHeight())
        newSize.SetHeight(clientSize.GetHeight());
    if(clientSize.GetWidth() > newSize.GetWidth())
        newSize.SetWidth(clientSize.GetWidth());

    int iYMin = 0;
    int iYMax = 0;

    this->GetDrawableArea(iYMin, iYMax);

    dc.SetFont(this->GetFont());


    int iYPos = 0;

    for(uint i = 0 ; i < this->document.getLinesCount() ; i++)
    {
        if(iYPos >= iYMin)
        {
            this->drawLine(dc,i,this->enlighter->getLineEnlightenmentInformation(i),iYPos);
        }

        iYPos += this->iLineHeight;

        if(iYPos > iYMax)
            break;
    }

    this->redrawSelections(dc,false);

    bDrawing = false;
}

void TCodeEditor::GetDrawableArea(int & iYMin, int & iYMax, bool bWithMarge)
{
    int i = 0, i2 = 0;
    iYMin = 0;
    this->GetClientSize(&i,&iYMax);
    i = iYMax;
    this->CalcUnscrolledPosition( i2, iYMin, &i2, &iYMin);
    this->CalcUnscrolledPosition( i2, iYMax, &i2, &iYMax);

    if(!bWithMarge)
        return;

    // calcul de la marge anti-flicker (de 30px � 200px sur la base de 10% de la hauteur de la fenetre)
    i /= 10;
    if(i < 30)
        i = 30;
    if(i > 200)
        i = 200;
    iYMin -= i;
    iYMax += i;
}

void TCodeEditor::OnKeyDownEvent(wxKeyEvent & _e)
{
    switch(_e.GetKeyCode())
    {
        case WXK_MENU:
            {
                wxMouseEvent me(wxEVT_RIGHT_DOWN);
                me.SetEventObject(this);
                me.m_x = this->getColumnXPos(this->cursor->getLine(),this->cursor->getColumn()) + 2;
                me.m_y = this->getLineYPos(this->cursor->getLine()) + (this->iLineHeight/2);
                this->ProcessEvent(me);
                me.SetId(wxEVT_RIGHT_UP);
                this->ProcessEvent(me);
            }
            break;
        case WXK_HOME:
        case WXK_END:
        case WXK_UP:
        case WXK_DOWN:
        case WXK_RIGHT:
        case WXK_LEFT:
        case WXK_PAGEUP:
        case WXK_PAGEDOWN:
//         case WXK_PRIOR:
//         case WXK_NEXT:
            if(this->autoCompletionCtrl && (_e.GetKeyCode() != WXK_RIGHT && _e.GetKeyCode() != WXK_LEFT))
            {
                this->autoCompletionCtrl->ProcessEvent(_e);
            }
            else
            {
                this->requestCursorMovment(_e.GetKeyCode(),_e.ControlDown(),_e.AltDown(),_e.ShiftDown());
                this->updateCompletionsList();
            }
            break;
        case WXK_ESCAPE:
            this->endAutoCompletion();
            break;
        case WXK_RETURN:
        case WXK_NUMPAD_ENTER:
            if(this->autoCompletionCtrl)
            {
                this->validateAutoCompletion();
            }
            else
                this->insert(wxT('\n'));
            break;
        case WXK_INSERT:
            if(this->getTypeMode() == Insert)
                this->setTypeMode(Overwrite);
            else
                this->setTypeMode(Insert);
            break;
        case WXK_DELETE:
            if(!this->eraseSelection(TCODEEDITOR_DEFAULT_SELECTION))
                this->document.removeCharAt(this->cursor->getPosition());
            break;
        case WXK_BACK:
            if(!this->eraseSelection(TCODEEDITOR_DEFAULT_SELECTION))
            {
                if(this->cursor->getPosition().y || this->cursor->getPosition().x)
                {
                    this->document.removeCharAt(this->document.getPosition(this->cursor->getPosition(),-1,TDocument::Character));
                }
            }
            break;
        case WXK_TAB:
        {
            const TSelection sel = this->selections[TCODEEDITOR_DEFAULT_SELECTION];
            this->document.startComposedAction();
            if(sel.isValid())
            {
                int iFirstLine = sel.getBegin().y > sel.getEnd().y ? sel.getEnd().y : sel.getBegin().y;
                int iLastLine = sel.getBegin().y < sel.getEnd().y ? sel.getEnd().y : sel.getBegin().y;
                for(int i = iFirstLine ; i <= iLastLine ; i++)
                {
                    if(_e.ShiftDown())
                        this->unindentLine(i);
                    else
                        this->indentLine(i);
                }
                this->setSelection(sel.getBegin(),sel.getEnd(),TCODEEDITOR_DEFAULT_SELECTION);
            }
            else
            {
                if(_e.ShiftDown())
                    this->unindentLine(this->cursor->getLine());
                else
                {
                    do
                    {
                        if(this->typeMode == Insert)
                            this->insert( ' ' );
                        else
                            this->overwrite( ' ' );
                    }
                    while(this->cursor->getColumn() % TAB_SIZE);
                }
            }
            this->document.stopComposedAction();
            break;
        }
        case WXK_SPACE:
        {
            if((!_e.ShiftDown()) && _e.ControlDown())
            {
                this->startAutoCompletion();
            }
            else
            {
                this->endAutoCompletion();
                _e.Skip();
            }
            break;
        }



// ne devrait pas etre gere ici ================================
//         case 'A':
//             if(_e.ControlDown())
//             {
//                 this->selections[TCODEEDITOR_DEFAULT_SELECTION].setBegin(TPoint(0,0));
//                 long iLine = this->document.getLinesCount() - 1;
//                 long iCol = this->document.getLine(iLine).length();
//                 this->selections[TCODEEDITOR_DEFAULT_SELECTION].setEnd(TPoint(iCol,iLine));
//             }
//             else
//                 _e.Skip();
//             break;
//
//         case 'C':
//             if(_e.ControlDown())
//             {
//                 this->clipboardCopy(false);
//             }
//             else
//                 _e.Skip();
//             break;
//
//         case 'X':
//             if(_e.ControlDown())
//             {
//                 this->clipboardCut();
//             }
//             else
//                 _e.Skip();
//             break;
//
//         case 'V':
//             if(_e.ControlDown())
//             {
//                 this->clipboardPaste();
//             }
//             else
//                 _e.Skip();
//             break;
//
//         case 'Z':
//             if(_e.ControlDown())
//             {
//                 if(_e.ShiftDown())
//                     this->redo();
//                 else
//                     this->undo();
//             }
//             else
//                 _e.Skip();
//             break;

// =============================================================

        default:
            _e.Skip();
    }
}

void TCodeEditor::OnCharEvent(wxKeyEvent & _e)
{
    if(_e.ControlDown() || _e.AltDown() || _e.CmdDown() || _e.MetaDown())
    {
        _e.Skip();
        return;
    }

    if(( _e.GetKeyCode() >= 0x20 &&  _e.GetKeyCode() <= 0x7E) ||  (_e.GetKeyCode() >= 0xA0 &&  _e.GetKeyCode() <= 0xFF))
    {
        this->eraseSelection(TCODEEDITOR_DEFAULT_SELECTION);
        if(this->typeMode == Insert)
            this->insert( _e.GetKeyCode() );
        else
            this->overwrite( _e.GetKeyCode() );
    }

    this->updateCompletionsList();
}

void TCodeEditor::overwrite(const wxChar c)
{
    TPoint cursorPosition = this->cursor->getPosition();
    if(static_cast<unsigned long>(cursorPosition.x) < this->document.getLineLength(cursorPosition.y))
        this->document.removeCharAt(cursorPosition);
    this->insert(c);
}

void TCodeEditor::insert(const wxChar c)
{
    TPoint insertPos = this->cursor->getPosition();

    this->document.insertAt(c, insertPos.y,insertPos.x);

    if(c == wxT('\n'))
    {
        const wxString & sLine = this->document.getLine(insertPos.y);
        uint iSpacesCount = 0;
        while(iSpacesCount < sLine.length())
        {
            if(sLine.GetChar(iSpacesCount) == wxT(' '))
            {
                iSpacesCount++;
                this->insert(' ');
            }
            else
                break;
        }
    }
}

void TCodeEditor::insert(const wxChar * sz)
{
    TPoint insertPos = this->cursor->getPosition();

    this->document.insertAt(sz, insertPos.y,insertPos.x);
}

TCursor * TCodeEditor::getCursor()
{
    return this->cursor;
}

void TCodeEditor::ensureCursorIsVisible()
{
    int iLineHeight = this->iLineHeight;

    int iXMax = 0;
    int iYMax = 0;
    this->GetClientSize(&iXMax,&iYMax);
    iYMax -= iLineHeight;

    int iPosY = this->cursor->getCursorPosY();
    int iYMove = 0;

    if(iPosY < 0)
    {
        iYMove = iPosY - int(fmin(iLineHeight*5,iYMax/2));
    }
    else if(iPosY > iYMax)
    {
        iYMove = iPosY - iYMax + int(fmin(iLineHeight*5,iYMax/2));
    }

    int iPosX = this->cursor->getCursorPosX();
    int iXMove = 0;
    if(iPosX <= 0)
    {
        iXMove = iPosX - int(fmin(this->iCharWidth*10,iXMax/2));
    }
    else if(iPosX >= iXMax)
    {
        iXMove = iPosX - iXMax + int(fmin(this->iCharWidth*10,iXMax/2));
    }

    if(iYMove || iXMove)
    {
        iPosY = 0;
        iPosX = 0;
        this->CalcUnscrolledPosition( iPosX, iPosY, &iPosX, &iPosY);

        iPosY += iYMove;
        iPosX += iXMove;

        int iXUnit;
        int iYUnit;
        GetScrollPixelsPerUnit(&iXUnit, &iYUnit);

        if(iYUnit != 0 && iXUnit != 0)
        {
            iPosY /= iYUnit;
            iPosX /= iXUnit;

            this->updateVirtualSize();
            this->Scroll(iPosX,iPosY);
        }
    }
}

void TCodeEditor::moveCursor(TDirection direction, uint iQuantity)
{
    if(iQuantity <= 0)
        return;

    uint iLine = this->cursor->getLine();
    uint iColumn = this->cursor->getColumn();

    switch(direction)
    {
        case Left:
            if((iLine != 0) || (iColumn != 0))
            {
                if(iColumn >= iQuantity)
                {
                    this->cursor->setColumn(iColumn - iQuantity);
                }
                else
                {
                    iQuantity -= (iColumn + 1);
                    this->moveCursor(Up,1);
                    this->cursor->setColumn(this->document.getLineLength(this->cursor->getLine()));
                    this->moveCursor(Left,iQuantity);
                }
            }
            break;
        case Right:
            if((iLine < (this->document.getLinesCount()-1))
                || (iColumn < this->document.getLineLength(this->document.getLinesCount()-1)))
            {
                if((iColumn + iQuantity) <= this->document.getLineLength(iLine))
                {
                    this->cursor->setColumn( iColumn + iQuantity );
                }
                else
                {
                    iQuantity -= (this->document.getLineLength(iLine) - iColumn + 1);
                    this->moveCursor(Down,1);
                    this->cursor->setColumn(0);
                    this->moveCursor(Right,iQuantity);
                }
            }
            break;
        case Up:
            if(iLine >= iQuantity)
            {
                this->cursor->setLine(iLine - iQuantity);
            }
            else
            {
                this->cursor->setLine(0);
            }
            break;
        case Down:
            if((iLine + iQuantity) < this->document.getLinesCount())
            {
                this->cursor->setLine(iLine + iQuantity);
            }
            else
            {
                this->cursor->setLine(this->document.getLinesCount() - 1);
            }
            break;
    }

    if((direction == Up) || (direction == Down))
    {
        if(this->cursor->getColumn() > int(this->document.getLineLength(this->cursor->getLine())))
        {
            this->cursor->setColumn(this->document.getLineLength(this->cursor->getLine()));
        }
    }
}

/**
 * ecris un fichier
 */
bool TCodeEditor::writeFile(const wxString &  sFileName)
{
    return this->document.writeFile(sFileName);
}

bool TCodeEditor::loadFile(const wxString & sFileName)
{
    if(libfile::isBinary(sFileName))
        return false;

    bool b = this->document.loadFile(sFileName);

    if(b)
    {
        this->getCursor()->setLine(0);
        this->getCursor()->setColumn(0);
    }

    return b;
}

void TCodeEditor::clear()
{
    this->document.clear();
    this->cursor->setLine(0);
    this->cursor->setColumn(0);
}

int TCodeEditor::getLineHeight() const
{
    return this->iLineHeight;
}

int TCodeEditor::getLineYPos(uint iLine)
{
    int iYMin = 0;
    int iYMax = 0;
    this->GetDrawableArea(iYMin, iYMax, false);

    // on est cense calculer la hauteur pour chacune des lignes
    int iLineHeight = this->iLineHeight;
    return iLine*iLineHeight - iYMin;
}

int TCodeEditor::getColumnXPos(uint iLine,uint iColumn)
{
    int x,y, x2,y2;
    this->GetViewStart(&x,&y);
    this->GetScrollPixelsPerUnit(&x2,&y2);
    x *= x2;

    wxClientDC dc(this);
    this->DoPrepareDC(dc);
    dc.SetFont(this->GetFont());

    int iLineHeight = 0;
    int iLineWidth = 0;
    try
    {
        wxString substr(this->document.getLine(iLine),0,iColumn);
        dc.GetTextExtent(substr, &iLineWidth, &iLineHeight);
    }
    catch(std::exception &e)
    {
        // la ligne n'existe pas
    }
    return iLineWidth - x;
}


void TCodeEditor::OnMouseEvent(wxMouseEvent & _e)
{
    // force Windows a donner le focus
    if(_e.LeftDown() || _e.MiddleDown() || _e.RightDown())
    {
        this->SetFocus();
        this->endAutoCompletion();
    }
    // indicateur pour savoir si une selection est en cours
    static bool bStartedSelection = false;
    if(_e.LeftIsDown())
    {
        TPoint cursorLocationBefore = this->cursor->getPosition();

        this->bUpdatingDefaultSelection = true;
        // clic gauche
        wxPoint pt = _e.GetPosition();
        this->cursor->setLine(this->getScreenLineAt(pt.y));
        this->cursor->setColumn(this->getScreenColumnAt(this->cursor->getLine(),pt.x));

        TSelection * selection = &(this->selections[TCODEEDITOR_DEFAULT_SELECTION]);
        if(_e.LeftDown())
        {
            bStartedSelection = true;
            if(_e.ShiftDown())
            {
                if(!selection->isValid())
                {
                    selection->setBegin(cursorLocationBefore);
                    selection->setEnd(this->cursor->getPosition());
                }
                else
                {
                    TPoint begin, end;
                    if(this->cursor->getPosition() > selection->getEnd())
                    {
                        begin = selection->getBegin();
                    }
                    else if(this->cursor->getPosition() < selection->getBegin())
                    {
                        begin = selection->getEnd();
                    }
                    end = this->cursor->getPosition();
                    selection->clear();
                    selection->setBegin(begin);
                    selection->setEnd(end);
                }
            }
            else
            {
                selection->clear();
                selection->setBegin(this->cursor->getPosition());
            }
        }
        else if(bStartedSelection)
        {
            selection->setEnd(this->cursor->getPosition());
        }
        this->bUpdatingDefaultSelection = false;
        return;
    }
    if(_e.LeftUp())
    {
        bStartedSelection = false;

        static long iLastClickTime = 0;
        static long iClickCounter = 0;

        long iCurrentTime = time(NULL);
        if(iCurrentTime == iLastClickTime)
        {
            iClickCounter++;
            TSelection * selection = &(this->selections[TCODEEDITOR_DEFAULT_SELECTION]);
            if(iClickCounter == 2)
            {
                selection->setBegin(this->document.getPosition(this->cursor->getPosition(),-1,TDocument::Word));
                selection->setEnd(this->document.getPosition(this->cursor->getPosition(),1,TDocument::Word));
            }
            else if(iClickCounter == 3)
            {
                selection->setBegin( TPoint(0,this->cursor->getLine()) );
                selection->setEnd(TPoint(this->document.getLineLength(this->cursor->getLine()),this->cursor->getLine()));
            }
        }
        else
        {
            iClickCounter = 1;
            iLastClickTime = iCurrentTime;
        }

        return;
    }
    if(_e.MiddleUp())
    {
        if(this->bLinuxSelectionToClipboard)
        {
            wxPoint pt = _e.GetPosition();
            this->cursor->setLine(this->getScreenLineAt(pt.y));
            this->cursor->setColumn(this->getScreenColumnAt(this->cursor->getLine(),pt.x));
            this->clipboardPaste(true);
            return;
        }
    }
    _e.Skip();
}

/**
 * renvoie la ligne a la position indiquee en convertissant la position
 * iScreenY de l'ecran en position absolue
 */
int TCodeEditor::getScreenLineAt(int iScreenY)
{
    int iX = 0;

    this->CalcUnscrolledPosition( iX, iScreenY, &iX, &iScreenY);
    return this->getLineAt(iScreenY);
}

/**
 * renvoie la ligne a la position absolue indiquee
 */
int TCodeEditor::getLineAt(int iYPos)
{
    int iLine = -1;
    int iLinePos = 0;
    while((iLinePos <= iYPos) && (iLine < int(this->document.getLinesCount() - 1) ))
    {
        iLine++;
        iLinePos += this->iLineHeight;
    }
    if(iLine < 0)
        iLine = 0;
    else if (uint(iLine) >= this->getDocument()->getLinesCount())
        iLine = this->getDocument()->getLinesCount()-1;
    return iLine;
}

/**
 * renvoie la colonne a la position indiquee en convertissant la position
 * iScreenX de l'ecran en position absolue en se basant sur le contenu de
 * la ligne iLine
 */
int TCodeEditor::getScreenColumnAt(int iLine, int iScreenX)
{
    int iY = 0;

    this->CalcUnscrolledPosition( iScreenX, iY, &iScreenX, &iY);
    return this->getColumnAt(iLine,iScreenX);
}

/**
 * renvoie la colonne a la position absolue indiquee en se basant sur
 * le contenu de la ligne iLine
 */
int TCodeEditor::getColumnAt(int iLine, int iXPos)
{
    if(iLine < 0)
        return false;

    if(uint(iLine) >= this->document.getLinesCount())
        return 0;

    if(iXPos <= 0 )
        return 0;

    wxString sLine = this->document.getLine(iLine);

    wxClientDC dc(this);
    this->DoPrepareDC(dc);
    dc.SetFont(this->GetFont());

    int iLineHeight = 0;
    int iLineWidth = 0;
    int iColumn = 0;


    int iLineWidth1 = 0;
    int iLineWidth2 = 0;
    int iColumn1 = 0;
    int iColumn2 = this->document.getLineLength(iLine);

    dc.GetTextExtent(sLine, &iLineWidth2, &iLineHeight);

    if(iXPos >= iLineWidth2)
        return iColumn2;

    try
    {
        while((iColumn2 - iColumn1) > 1)
        {
            iColumn = iColumn1 + ((iColumn2-iColumn1) * (iXPos-iLineWidth1) / (iLineWidth2-iLineWidth1));

            if(iColumn == iColumn1)
                iColumn++;
            if(iColumn == iColumn2)
                iColumn--;

            wxString substr(sLine,0,iColumn);

            dc.GetTextExtent(substr, &iLineWidth, &iLineHeight);

            if(iLineWidth > iXPos)
            {
                iColumn2 = iColumn;
                iLineWidth2 = iLineWidth;
            }
            else
            {
                iColumn1 = iColumn;
                iLineWidth1 = iLineWidth;
            }
        }
    }
    catch(...)
    {
        char szError[128];
        sprintf(szError,"colonne = %d   |   colonne1 = %d    |    colonne2 = %d\n",iColumn, iColumn1, iColumn2);
        LOG_MESSAGE(szError,logging::_ERROR_);
    }

    if( (iXPos-iLineWidth1) > (this->getCharWidth()/2) )
        return iColumn2;
    else
        return iColumn1;
}

/**
 * gestion des changements de selection ecoutee
 */
void TCodeEditor::selectionChanged(TSelection * sel)
{
    if(this->bRemovingText)
        return;

    if(this->bLinuxSelectionToClipboard)
        this->clipboardCopy(true);

    wxClientDC dc(this);
    this->DoPrepareDC(dc);
    dc.SetFont(this->GetFont());
    this->drawSelection(dc,sel,sel->getStyle());
}

/**
 * nettoyage d'une selection (definitif ou avant un reaffichage)
 */
void TCodeEditor::clearSelection(TSelection * sel)
{
    if(this->bRemovingText)
        return;

    this->Refresh(true);
/*    wxClientDC dc(this);
    this->DoPrepareDC(dc);
    dc.SetFont(this->GetFont());
    this->drawSelection(dc,sel,DEFAULT_STYLE);*/
}

/**
 * dessine une selection avec le style demande
 */
void TCodeEditor::drawSelection(wxDC & dc, TSelection * sel, sbyte iStyleID)
{

    if(!sel->isValid())
        return;

    if(sel->getBegin() == sel->getEnd())
        return;

    int iYMin = 0;
    int iYMax = 0;

    this->GetDrawableArea(iYMin, iYMax);

    TPoint begin, end;

    if(sel->getBegin() < sel->getEnd())
    {
        begin = sel->getBegin();
        end = sel->getEnd();
    }
    else
    {
        begin = sel->getEnd();
        end = sel->getBegin();
    }


    int iYPos = (begin.y * this->iLineHeight);

//    dc.BeginDrawing();

    for(int i = begin.y ;
        (iYPos < iYMax) && (i <= end.y) && static_cast<uint>(i) < this->document.getLinesCount() ;
        i++)
    {
        if((iYPos >= iYMin) && (i >= begin.y))
        {
            TLineEnlightenmentInformation info;
            int iLineSelBegin = 0;
            int iLineSelEnd = -1;
            if( i == begin.y )
                iLineSelBegin = begin.x;
            if( i == end.y )
                iLineSelEnd = end.x;

            if( i == end.y || i == begin.y )
                info = this->enlighter->getLineEnlightenmentInformation(i);

            info.setStyle(iStyleID, iLineSelBegin, iLineSelEnd);

            this->drawLine(dc,i,info,iYPos, iLineSelBegin, iLineSelEnd);
        }

        iYPos += this->iLineHeight;
        if(iYPos > iYMax)
            break;

        if(i >= end.y)
            break;
    }
//    dc.EndDrawing();

}

/**
 * ecoute les mouvements du curseur
 */
void TCodeEditor::cursorMoved(TCursor * cursor)
{
    this->ensureCursorIsVisible();

    TSelectionMap::iterator itB = this->selections.begin();
    TSelectionMap::iterator itE = this->selections.end();
    itB++;
    while(itB != itE)
    {
        if((!this->bUpdatingDefaultSelection) || ((*itB).first != TCODEEDITOR_DEFAULT_SELECTION))
             (*itB).second.clear();
        itB++;
    }

    this->highlightBrackets();
}

/**
 * redessine toutes les selections
 */
void TCodeEditor::redrawSelections(wxDC & dc, bool bDoClear)
{
    if(bDoClear)
    {
        TSelectionMap::iterator itB = this->selections.begin();
        TSelectionMap::iterator itE = this->selections.end();
        while(itB != itE)
        {
            this->drawSelection(dc,&((*itB).second),DEFAULT_STYLE);
            itB++;
        }
    }

    TSelectionMap::iterator itB = this->selections.begin();
    TSelectionMap::iterator itE = this->selections.end();
    while(itB != itE)
    {
        this->drawSelection(dc,&((*itB).second),(*itB).second.getStyle());
        itB++;
    }
}

/** mets a jour la taille virtuelle du composant en fonction de la derni�re collecte d'infos */
void TCodeEditor::updateVirtualSize()
{
    int x,y;
    this->GetViewStart(&x,&y);

//     if(this->iLastMaxLineLength <= 0)
//     {
//         this->iLastMaxLineLength = this->document.calcMaxLineLength();
//     }


    int i = this->iLineHeight;
    int j = this->iCharWidth + 1;
    int iWidthUnits = (this->iLastMaxLineLength / 40 + 1) * 40 + 1;
    int iHeightUnits = int(fmax(this->document.getLinesCount() + 5, (this->GetSize().GetHeight() / this->iLineHeight)));
    this->SetScrollbars(j, i, iWidthUnits, iHeightUnits, x, y, false);
}

/**
 * ecoute les modifications sur un document
 */
void TCodeEditor::documentChanged(TDocument * doc)
{
    if(!this->IsShown())
        return;

    if(this->futureCursorPosition.isValid())
    {
        this->cursor->setPosition(this->futureCursorPosition);
        this->futureCursorPosition.x = -1;
    }
//    this->iLastMaxLineLength = -1;
    this->updateVirtualSize();

    if(this->cursor->getLine() >= int(this->document.getLinesCount()))
    {
        this->cursor->setLine(this->document.getLinesCount()-1);
    }

    if(this->cursor->getColumn() > int(this->document.getLineLength(this->cursor->getLine())))
    {
        this->cursor->setColumn(this->document.getLineLength(this->cursor->getLine()));
    }

    this->ensureCursorIsVisible();

    this->Refresh();
}

/**
 * signale l'insertion de texte dans le document.
 * rien ne doit etre fait qui puisse demander le contenu de l'enlightenment ici
 * car l'enlighter peut ne pas encore avoir recu l'�v�nement de texte ins�r�
 */
void TCodeEditor::documentTextInserted(TDocument * doc, const TPoint & ptFrom, const TPoint & ptTo)
{
    // on va d�placer le curseur en fonction des insertions
    TPoint currentPos = this->cursor->getPosition();
    if(ptFrom == currentPos)
    {
        // sp�cialisation du cas suivant
        this->futureCursorPosition = ptTo;
    }
    else if(ptFrom < currentPos)
    {
        if(ptFrom.y == currentPos.y)
        {
            TPoint newPos;
            newPos.y = ptTo.y;
            newPos.x = currentPos.x - ptFrom.x + ptTo.x;
            this->futureCursorPosition = newPos;
        }
        else
        {
            TPoint newPos;
            newPos.y = currentPos.y + ptTo.y - ptFrom.y;
            newPos.x = currentPos.x;
            this->futureCursorPosition = newPos;
        }
    }
}

/**
 * signale la suppression de texte dans le document.
 * rien ne doit etre fait qui puisse demander le contenu de l'enlightenment ici
 * car l'enlighter peut ne pas encore avoir recu l'�v�nement de texte supprim�
 */
void TCodeEditor::documentTextRemoved(TDocument * doc, const TPoint & ptFrom, const TPoint & ptTo)
{
    // on va deplacer le curseur en fonction des suppressions
    TPoint currentPos = this->cursor->getPosition();
    if(ptFrom < currentPos)
    {
        this->bRemovingText = true;
        if(ptTo < currentPos)
        {
            if(ptTo.y == currentPos.y)
            {
                TPoint newPos;
                newPos.y = ptFrom.y;
                newPos.x = currentPos.x - ptTo.x + ptFrom.x;
                this->futureCursorPosition = newPos;
            }
            else
            {
                TPoint newPos;
                newPos.y = currentPos.y + ptFrom.y - ptTo.y;
                newPos.x = currentPos.x;
                this->futureCursorPosition = newPos;
            }
        }
        else
        {
            this->futureCursorPosition = ptFrom;
        }
        this->bRemovingText = false;
    }
}

/**
 * signale de grosses modifs sur le document. puis appelle documentChanged
 * rien ne doit etre fait qui puisse demander le contenu de l'enlightenment ici
 * car l'enlighter peut ne pas encore avoir recu l'�v�nement de texte modifi�
 */
void TCodeEditor::documentHeavilyModified(TDocument * doc)
{
    // gestion d'une liste de lignes modifi�s pour optimiser le redessin du composant
//    this->emptyModifiedLines();
    this->futureCursorPosition = TPoint(0,0);
}

/**
 * defini le style de la selection
 * @param iStyle ID du style a appliquer a la s�lection (voir setStyle)
 * @param iSelection id de la selection a manipuler
 */
void TCodeEditor::setSelectionStyle(const sbyte iStyleId, const int iSelection)
{
    this->selections[iSelection].setStyle(iStyleId);
    this->Refresh();
}

/**
 * renvoie le style de la selection
 * @param iSelection id de la selection a manipuler
 */
sbyte TCodeEditor::getSelectionStyle(const int iSelection)
{
    return this->selections[iSelection].getStyle();
}


/**
 * defini la selection
 * @param selBegin point de depart de la selection
 * @param selEnd point de fin de la selection
 * @param iSelection id de la selection a manipuler
 */
void TCodeEditor::setSelection(const TPoint & selBegin, const TPoint & selEnd, const int iSelection)
{
    TSelection & sel = this->selections[iSelection];
    sel.setBegin( selBegin );
    sel.setEnd( selEnd );
}

/**
 * recupere la selection dans selBegin et selEnd
 * @param selBegin point de depart de la selection
 * @param selEnd point de fin de la selection
 * @param iSelection id de la selection a manipuler
 * @return true si la selection est valide, false dans le cas contraire
 */
bool TCodeEditor::getSelection(TPoint & selBegin, TPoint & selEnd, const int iSelection) const
{
    TSelectionMap::const_iterator it = this->selections.find(iSelection);
    if(it == this->selections.end())
        return false;
    if((*it).second.isValid())
    {
        selBegin = (*it).second.getBegin();
        selEnd = (*it).second.getEnd();
        return true;
    }
    else
        return false;
}

/** renvoie le texte de la selection */
wxString TCodeEditor::getSelectedText(const int iSelection) const
{
    TSelectionMap::const_iterator it = this->selections.find(iSelection);
    if(it == this->selections.end())
        return wxString();
    if((*it).second.isValid())
    {
        return this->document.getTextBetween((*it).second.getBegin(),(*it).second.getEnd());
    }
    else
        return wxString();
}

/** invalide une selection */
void TCodeEditor::discardSelection(const int iSelection)
{
    TSelection & sel = this->selections[iSelection];
    if(sel.isValid())
        sel.clear();
}

/**
 * suprime la selection et renvoie true.
 * Si la selection est invalide renvoie fals et ne fait rien
 */
bool TCodeEditor::eraseSelection(int iSelection)
{
    TSelection & sel = this->selections[iSelection];
    if(sel.isValid())
    {
        this->document.removeRange(sel.getBegin(),sel.getEnd());
        sel.clear();
        return true;
    }
    else
        return false;
}


/**
 * gere le mouvement du curseur sur une pression de touche
 */
void TCodeEditor::requestCursorMovment(int iKeyCode, bool bControl, bool bAlt, bool bShift)
{
    if(bControl && (iKeyCode == WXK_UP || iKeyCode == WXK_DOWN))
    {
        int x,y;
        this->GetViewStart(&x,&y);
        if(iKeyCode == WXK_UP) y--;
        else y++;
        this->Scroll(x,y);
        return;
    }

    if(bShift)
    {
        this->bUpdatingDefaultSelection = true;
        if(!this->selections[TCODEEDITOR_DEFAULT_SELECTION].isValid())
        {
            this->selections[TCODEEDITOR_DEFAULT_SELECTION].setBegin(this->cursor->getPosition());
        }
    }
    else
        this->selections[TCODEEDITOR_DEFAULT_SELECTION].clear();
    switch(iKeyCode)
    {
        case WXK_UP:
        case WXK_DOWN:
            this->moveCursor((iKeyCode == WXK_UP) ? Up : Down, 1);
            break;
        case WXK_RIGHT:
        case WXK_LEFT:
            if(bControl)
            {
                this->cursor->setPosition(this->document.getPosition(this->cursor->getPosition(),(iKeyCode == WXK_RIGHT) ? 1 : -1, TDocument::Word));
            }
            else
                this->moveCursor((iKeyCode == WXK_RIGHT) ? Right : Left, 1);
            break;
        case WXK_PAGEUP:
        case WXK_PAGEDOWN:
//         case WXK_PRIOR:
//         case WXK_NEXT:
            {
                TPoint cursorPos(this->cursor->getCursorPosX(),this->cursor->getCursorPosY());
                TPoint viewStartBefore;
                TPoint viewStart;
                TPoint viewSize;
                TPoint scrollUnits;
                this->GetViewStart(&viewStart.x,&viewStart.y);
                viewStartBefore = viewStart;
                this->GetSize(&viewSize.x,&viewSize.y);
                viewSize.y -= wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y, this);
                this->GetScrollPixelsPerUnit(&scrollUnits.x,&scrollUnits.y);
                viewSize.y /= scrollUnits.y;
                if((iKeyCode == WXK_PAGEUP) || (iKeyCode == WXK_PRIOR))
                    viewStart.y -= viewSize.y;
                else
                    viewStart.y += viewSize.y;
                this->Scroll(viewStart.x,viewStart.y);
                this->GetViewStart(&viewStart.x,&viewStart.y);

                if(viewStart == viewStartBefore)
                {
                    //  gestion premiere / derniere page
                    if(viewStart.y == 0)
                        this->cursor->setLine(0);
                    else
                        this->cursor->setLine(this->document.getLinesCount() -1);
                }
                else
                    this->cursor->setLine(this->getScreenLineAt(cursorPos.y + 5));
            }
            break;
        case WXK_HOME:
            if(bControl)
            {
                this->cursor->setColumn(0);
                this->cursor->setLine(0);
            }
            else
            {
                const wxString & sLine = this->document.getLine(this->cursor->getLine());
                uint iNbSpaces = 0;
                while(iNbSpaces < sLine.length() && sLine[iNbSpaces] == wxT(' '))
                    iNbSpaces++;
                if(this->cursor->getColumn() == int(iNbSpaces) || iNbSpaces >= sLine.length())
                    this->cursor->setColumn(0);
                else
                    this->cursor->setColumn(iNbSpaces);
            }
            break;
        case WXK_END:
            if(bControl)
            {
                this->cursor->setLine(this->document.getLinesCount() - 1);
            }
            this->cursor->setColumn(this->document.getLine(this->cursor->getLine()).length());
            break;
    }
    if(bShift)
    {
        this->bUpdatingDefaultSelection = false;
        this->selections[TCODEEDITOR_DEFAULT_SELECTION].setEnd(this->cursor->getPosition());
    }
}


/**
 * copie la selection dans le presse papier et l'efface
 */
void TCodeEditor::clipboardCut()
{
    this->clipboardCopy(false);
    this->eraseSelection(TCODEEDITOR_DEFAULT_SELECTION);
}

/**
 * copie la selection dans le presse papier
 * @param bX11Primary copier dans le presse papier de la souris sous X11
 */
void TCodeEditor::clipboardCopy(bool bX11Primary)
{
    wxString sSelection = this->getSelectedText();
    if(sSelection.length())
    {
        wxTheClipboard->UsePrimarySelection(bX11Primary);
        if (wxTheClipboard->Open())
        {
            if(!bX11Primary)
                wxTheClipboard->Clear();
            // This data objects are held by the clipboard,
            // so do not delete them in the app.
            wxTheClipboard->AddData( new wxTextDataObject(sSelection) );
            wxTheClipboard->Close();
        }
    }
}

/**
 * colle le contenu du presse papier
 * @param bX11Primary copier dans le presse papier de la souris sous X11
 */
void TCodeEditor::clipboardPaste(bool bX11Primary)
{
    wxTheClipboard->UsePrimarySelection(bX11Primary);
    if (wxTheClipboard->Open())
    {
        if (wxTheClipboard->IsSupported( wxDF_TEXT ))
        {
            wxTextDataObject data;
            wxTheClipboard->GetData( data );
            if(data.GetTextLength())
            {
                this->eraseSelection(TCODEEDITOR_DEFAULT_SELECTION);
                this->insert(data.GetText());
            }
        }
        wxTheClipboard->Close();
    }
}


/**
 * defini un colorateur syntaxique
 */
void TCodeEditor::setEnlighter(TEnlighter * enlighter)
{
    if(enlighter == NULL && this->defaultEnlighter != NULL)
    {
        enlighter = this->defaultEnlighter;
        this->getDocument()->addDocumentListener(enlighter);
    }
    else if(this->enlighter == this->defaultEnlighter)
        this->getDocument()->removeDocumentListener( this->defaultEnlighter );

    this->enlighter = enlighter;

    if(this->enlighter)
        static_cast<TDocumentListener*>(this->enlighter)->documentHeavilyModified(this->getDocument());
}


/** defini le style pour un ID donne */
void TCodeEditor::setStyle(sbyte iStyleID, const TStyle & style)
{
    this->styles[iStyleID] = style;
}

/** supprime le style pour un ID donne */
void TCodeEditor::removeStyle(sbyte iStyleID)
{
    if(iStyleID == DEFAULT_STYLE)
    {
        this->styles[DEFAULT_STYLE].setPredefined(DEFAULT_STYLE);
    }
    else if(iStyleID == SELECTED_STYLE)
    {
        this->styles[SELECTED_STYLE].setPredefined(SELECTED_STYLE);
    }
    else
    {
        TStylesMap::iterator it = this->styles.find(iStyleID);
        if(it != this->styles.end())
            this->styles.erase(it);
    }
}


/**
 * dessine une selection avec le style demande
 */
void TCodeEditor::drawLine(wxDC & dc, int iLine, const TLineEnlightenmentInformation & info,
                            int iYPos, int iFirstCol, int iLastCol)
{
    if(iFirstCol > 0 && iLastCol >= 0 && iFirstCol >= iLastCol)
        return;

    dc.SetPen(wxPen(this->GetBackgroundColour()));

    int iXPos = 0;
    int iSegmentBegin = 0;
    int iSegmentEnd = iFirstCol > 0 ? iFirstCol : info.getSegmentEnd(0);
    int iSegmentWidth = 0;
    int iSegmentHeight = 0;
    const wxChar * szLine = this->document.getLine(iLine).c_str();
    int iLineLength = this->document.getLine(iLine).length();

    if(iLineLength > this->iLastMaxLineLength)
        this->iLastMaxLineLength = iLineLength;

    while(iSegmentBegin >= 0 && iSegmentBegin < iLineLength)
    {
        bool bSkipDrawing = false;
        if(iFirstCol > 0 && iSegmentBegin < iFirstCol)
            bSkipDrawing = true;

        if(!bSkipDrawing)
        {
            sbyte iStyleID = info.getStyleAt(iSegmentBegin);
            TStylesMap::const_iterator it = this->styles.find(iStyleID);
            if(it == this->styles.end())    // fallback sur le style par defaut dont on est certains de l'existance
                it = this->styles.find(DEFAULT_STYLE);
            (*it).second.prepareDC(dc,this->GetBackgroundColour());
        }

        int iSubLength;
        if(iSegmentEnd < 0)
            iSubLength = wxSTRING_MAXLEN;
        else
            iSubLength = iSegmentEnd - iSegmentBegin;

        wxString sSub((szLine + iSegmentBegin),iSubLength);
        dc.GetTextExtent(sSub, &iSegmentWidth, &iSegmentHeight);
        if(iSegmentEnd < 0)
            iSegmentWidth = this->GetVirtualSize().GetWidth() - iXPos;

        if(!bSkipDrawing)
        {
            dc.DrawRectangle(iXPos, iYPos, iSegmentWidth, this->iLineHeight);

            dc.DrawText(sSub, iXPos, iYPos);
        }

        iXPos += iSegmentWidth;
        iSegmentBegin = iSegmentEnd;
        if(iLastCol >= 0 && iSegmentBegin >= iLastCol)
            return;
        iSegmentEnd = info.getSegmentEnd(iSegmentBegin);
        if(iLastCol >= 0 && iSegmentEnd >= iLastCol)
            iSegmentEnd = iLastCol;
    }

    // on force le dessin du curseur car le fait de ne pas avoir le focus le d�sactive
    if(this->autoCompletionCtrl && this->cursor->getLine() == iLine)
    {
        dc.SetBrush(wxBrush(*wxBLACK));
        dc.SetPen(wxPen(*wxBLACK));
        iXPos = this->cursor->getCursorPosX();
        dc.DrawRectangle(iXPos,iYPos,2,this->iLineHeight);
    }
}


/** Sets the font for this window. This function should not be called for the parent window if you don't want its font to be inherited by its children, use SetOwnFont instead in this case and see InheritAttributes for more explanations. */
bool TCodeEditor::SetFont(const wxFont& font)
{
    bool b = this->wxScrolledWindow::SetFont(font);
    if(b)
    {
        wxClientDC dc(this);
        this->DoPrepareDC(dc);
        dc.SetFont(this->GetFont());
        int i = 0;
        dc.GetTextExtent(wxT("Mq}"), &i, &(this->iLineHeight));
        dc.GetTextExtent(wxT("M"), &(this->iCharWidth), &i);
    }
    return b;
}

/** le composant recoit le focus */
void TCodeEditor::OnFocusIn(wxFocusEvent & e)
{
    this->cursor->setVisible(true);
}

/** le composant perd le focus */
void TCodeEditor::OnFocusOut(wxFocusEvent & e)
{
    this->cursor->setVisible(false);
}

/**
 * annule la derniere modification
 */
void TCodeEditor::undo()
{
    this->document.undo();
}

/**
 * retabli la derniere modification annulee
 */
void TCodeEditor::redo()
{
    this->document.redo();
}

/** defini le mode de saisie (insertion / ecrasement) */
void TCodeEditor::setTypeMode(TypeMode typeMode)
{
     this->typeMode = typeMode;
     this->cursor->updateCaret();
     this->fireTypeModeChanged();
     this->Refresh();
}

bool TCodeEditor::addCodeEditorListener(TCodeEditorListener * cel)
{
    return this->listeners.insert(cel).second;
}

bool TCodeEditor::removeCodeEditorListener(TCodeEditorListener * cel)
{
    return this->listeners.erase(cel) != 0;
}

void TCodeEditor::fireTypeModeChanged()
{
    TCodeEditorListenerList::iterator itB = this->listeners.begin();
    TCodeEditorListenerList::iterator itE = this->listeners.end();

    while(itB != itE)
    {
        (*itB)->typeModeChanged(this);
        itB++;
    }
}

/** active / d�sactive la mise en valeur des parenth�ses/accolades/crochets ... */
void TCodeEditor::setBracketsHighlightingEnabled(bool b)
{
    this->bBracketsHighlightingEnabled = b;
}

/** recherche le caractere apparent� a ce caract�re de fermeture (0 si non trouv�) */
char TCodeEditor::getOpeningBracketFor(char cClosingBracket)
{
    TBracketsMap::iterator it = this->backwardBracketsMap.find(cClosingBracket);
    if(it == this->forwardBracketsMap.end())
        return 0;
    return (*it).second;
}

/** recherche le caractere apparent� a ce caract�re d'ouverture (0 si non trouv�) */
char TCodeEditor::getClosingBracketFor(char cOpeningBracket)
{
    TBracketsMap::iterator it = this->forwardBracketsMap.find(cOpeningBracket);
    if(it == this->forwardBracketsMap.end())
        return 0;
    return (*it).second;
}

/** effectue la mise en valeur des "brackets" � la position courrante du curseur */
void TCodeEditor::highlightBrackets()
{
    if(!this->isBracketsHighlightingEnabled())
        return;

    if(this->selections[TCODEEDITOR_DEFAULT_SELECTION].isValid())
        return;

    TPoint pos = this->cursor->getPosition();

    char c1 = this->document.getCharAt(pos);
    bool bIsForward = true;



    char c2 = this->getClosingBracketFor(c1);
    if(!c2)
    {
        c2 = this->getOpeningBracketFor(c1);
        if(!c2)
        {
            // current char is not bracket, but previous ?
            if(this->typeMode == Overwrite)
                return; // we only check previous char in insert mode

            if(!pos.x) // if we are on the first char, there is no previous char
                return;

            pos.x--;

            c1 = this->document.getCharAt(pos);
            c2 = this->getClosingBracketFor(c1);
            if(!c2)
            {
                c2 = this->getOpeningBracketFor(c1);
                if(!c2)
                {
                    return; // not a bracket
                }
                else
                    bIsForward = false;
            }
            else
                bIsForward = true;
        }
        else
            bIsForward = false;
    }
    else
        bIsForward = true;

    if(this->isInCommentArea(pos)) // nothing to do in comment area
        return;

    TPoint c1Pos = pos;
    typedef std::map<char, int> TBracketStack;
    TBracketStack stack;

    stack[c1] = 1;

    while((bIsForward || (pos.y > 0 || pos.x > 0))
           && ((!bIsForward) || (pos.y < (int(this->document.getLinesCount()-1)) || uint(pos.x) < this->document.getLineLength(pos.y)))
           && (stack[c1] != 0)
         )
    {
        pos = this->document.getPosition(pos,bIsForward ? 1 : -1, TDocument::Character);

        if(this->isInCommentArea(pos))
            continue;

        char cC1 = this->document.getCharAt(pos);

        bool bIsUp;
        char cC2 = this->getClosingBracketFor(cC1);
        if(!cC2)
        {
            cC2 = this->getOpeningBracketFor(cC1);
            if(!cC2)
                continue;
            else
                bIsUp = !bIsForward;
        }
        else
            bIsUp = bIsForward;

        if(!bIsUp)
            cC1 = cC2; // to decrease the one which increased the stacks

        if(stack.find(cC1) == stack.end())
            stack[cC1] = 0;

        stack[cC1] += bIsUp ? 1 : -1;

        if (stack[cC1] < 0)
        {
            if(cC1 == c1 || (!this->multiPurposeBrackets.count(cC1)))
                return; // no pair to find
        }
    }

    if(stack[c1] == 0)
    {
        TPoint pos2 = pos;
        pos2.x++;
        this->setSelection(pos,pos2,TCODEEDITOR_CLOSE_BRACKET_SELECTION);

        pos2 = c1Pos;
        pos2.x++;
        this->setSelection(c1Pos,pos2,TCODEEDITOR_OPEN_BRACKET_SELECTION);
    }
}

/** ajoute une paire de "brackets" a g�rer dans la mise en valeur */
void TCodeEditor::addBracketsPair(char cOpen, char cClose, bool bMultiPurpose)
{
    this->forwardBracketsMap[cOpen] = cClose;
    this->backwardBracketsMap[cClose] = cOpen;
    if(bMultiPurpose)
    {
        this->multiPurposeBrackets.insert(cOpen);
        this->multiPurposeBrackets.insert(cClose);
    }
}

/** indique que les zones qui sont du style indiqu� sont des zones de commentaires */
void TCodeEditor::declareCommentStyle(sbyte iStyleID)
{
    this->commentStyles.insert(iStyleID);
}

/** indique que les zones qui sont du style indiqu� ne sont pas des zones de commentaires */
void TCodeEditor::undeclareCommentStyle(sbyte iStyleID)
{
    TIntSet::iterator it = this->commentStyles.find(iStyleID);
    if(it != this->commentStyles.end())
        this->commentStyles.erase(it);
}

/** indique si la position pass�e en parametre est dans une zone de commentaires */
bool TCodeEditor::isInCommentArea(const TPoint & pt) const
{
    const TLineEnlightenmentInformation & info = this->enlighter->getLineEnlightenmentInformation( pt.y );
    sbyte iStyleID = info.getStyleAt( pt.x );

    TIntSet::iterator it = this->commentStyles.find(iStyleID);
    if(it != this->commentStyles.end())
        return true;
    return false;
}

/** indente une ligne */
void TCodeEditor::indentLine(uint iLine)
{
    const wxString & sLine = this->document.getLine(iLine);
    int iSpaces = 0;
    while(sLine[iSpaces] == ' ')
        iSpaces++;
    int iSpacesToAdd = TAB_SIZE - (iSpaces % TAB_SIZE);
    for(int i = 0 ; i < iSpacesToAdd ; i++)
        this->document.insertAt(' ', iLine, 0);
}

/** d�sindente une ligne */
void TCodeEditor::unindentLine(uint iLine)
{
    const wxString & sLine = this->document.getLine(iLine);
    int iSpaces = 0;
    while(sLine[iSpaces] == ' ')
        iSpaces++;
    int iSpacesToDel = iSpaces % TAB_SIZE;
    if(iSpacesToDel == 0 && iSpaces >= TAB_SIZE)
        iSpacesToDel = TAB_SIZE;
    for(int i = 0 ; i < iSpacesToDel ; i++)
        this->document.removeCharAt( iLine, 0);
}

/** affiche le composant de completion */
void TCodeEditor::startAutoCompletion()
{
    if(!this->autoCompletionSrc)
        return;

    // on accepte la s�lection mais que sur une seule ligne
    const TSelection & sel = this->selections[TCODEEDITOR_DEFAULT_SELECTION];
    if(sel.isValid())
    {
        if(sel.getBegin().y != sel.getEnd().y)
            return;
    }

    if(!this->autoCompletionCtrl)
    {
        wxPoint pos(this->cursor->getCursorPosX(),this->cursor->getCursorPosY() + this->getLineHeight() + 1);
        this->autoCompletionCtrl = new TListCtrl(this, -1, pos, wxSize(10,150),
                                        wxLC_REPORT | wxLC_NO_HEADER | wxLC_SINGLE_SEL
                                        | wxBORDER_SIMPLE | wxVSCROLL | wxALWAYS_SHOW_SB );
        this->autoCompletionCtrl->Show(false);

        // entetes des colonnes (cach�es)
        wxListItem itemCol;
        itemCol.SetText(wxT(" "));
        itemCol.SetImage(-1);
        this->autoCompletionCtrl->InsertColumn(0, itemCol);
        this->autoCompletionCtrl->SetColumnWidth(0,300);
        this->autoCompletionCtrl->Connect(wxEVT_COMMAND_LIST_ITEM_ACTIVATED,wxListEventHandler(TCodeEditor::onAutoCompletionDblClick), NULL, this);
        this->autoCompletionCtrl->Connect(wxEVT_COMMAND_LIST_KEY_DOWN,wxListEventHandler(TCodeEditor::onAutoCompletionKeyDown), NULL, this);
        this->autoCompletionCtrl->Connect(wxEVT_CHAR,wxKeyEventHandler(TCodeEditor::OnCharEvent), NULL, this);
    }
    this->autoCompletionCtrl->SetFocus();

    // remplir le controle avec les possibilit�s
    this->updateCompletionsList();

    this->Refresh(); // pour dessiner le pseudo curseur
}

/** cache le composant de completion */
void TCodeEditor::endAutoCompletion()
{
    if(!this->autoCompletionCtrl)
        return;

    delete this->autoCompletionCtrl;
    this->autoCompletionCtrl = NULL;
    this->SetFocus();
    this->Refresh(); // pour effacer le pseudo curseur
}

/** valide la s�lection actuelle de l'auto completion */
void TCodeEditor::onAutoCompletionEvent( wxCommandEvent& event )
{
    if(event.GetId() == VALIDATE_AUTO_COMPLETE_EVENT_ID)
        this->validateAutoCompletion();
    else
        this->endAutoCompletion();
}

/** validation de la s�lection dans l'auto completion */
void TCodeEditor::validateAutoCompletion()
{
    if(!this->autoCompletionCtrl)
        return;

    for(int iItemId = 0 ; iItemId < this->autoCompletionCtrl->GetItemCount() ; iItemId++)
    {
        if(this->autoCompletionCtrl->GetItemState(iItemId,wxLIST_STATE_SELECTED))
        {
            wxString sTxt = this->autoCompletionCtrl->GetItemText(iItemId);

            TPoint insertPoint = this->cursor->getPosition();

            const TSelection & sel = this->selections[TCODEEDITOR_DEFAULT_SELECTION];
            if(sel.isValid())
            {
                if(sel.getBegin().x < insertPoint.x)
                    insertPoint = sel.getBegin();
                if(sel.getEnd().x < insertPoint.x)
                    insertPoint = sel.getEnd();
            }

            uint iLine = insertPoint.y;
            const wxString & sLine = this->document.getLine(iLine);

            int iWordEnd = insertPoint.x;
            int iWordStart = iWordEnd - 1;
            while(iWordStart >= 0)
            {
                wxChar c = sLine.GetChar(iWordStart);
                if( c != '_' && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') )
                {
                    iWordStart++;
                    break;
                }
                iWordStart--;
            }

            if(iWordStart < 0)
                iWordStart = 0;

            sTxt = sTxt.Mid(iWordEnd-iWordStart);

            this->eraseSelection();
            this->insert(sTxt.c_str());

            break;
        }
    }
    this->endAutoCompletion();
}

/** mets a jour la liste des completions affich�es */
void TCodeEditor::updateCompletionsList()
{
    if((!this->autoCompletionCtrl) || (!this->autoCompletionSrc))
        return;

    wxCommandEvent endEvent( EVENT_AUTO_COMPLETION, TERMINATE_AUTO_COMPLETE_EVENT_ID );
    endEvent.SetEventObject( this );

    TPoint insertPoint = this->cursor->getPosition();

    const TSelection & sel = this->selections[TCODEEDITOR_DEFAULT_SELECTION];
    if(sel.isValid())
    {
        if(sel.getBegin().x < insertPoint.x)
            insertPoint = sel.getBegin();
        if(sel.getEnd().x < insertPoint.x)
            insertPoint = sel.getEnd();
    }

    uint iLine = insertPoint.y;
    const wxString & sLine = this->document.getLine(iLine);

    int iWordEnd = insertPoint.x;
    int iWordStart = iWordEnd - 1;
    while(iWordStart >= 0)
    {
        wxChar c = sLine.GetChar(iWordStart);
        if( c != '_' && (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') )
        {
            iWordStart++;
            break;
        }
        iWordStart--;
    }

    if(iWordStart < 0)
        iWordStart = 0;

    if(iWordEnd - iWordStart == 0)
    {
        this->AddPendingEvent( endEvent );
        return;
    }

    wxString sWord = sLine.Mid(iWordStart,iWordEnd-iWordStart);

    TStringList completions = this->autoCompletionSrc->getCompletionsForWord(this,sWord);

    if(completions.size() == 0)
    {
        this->AddPendingEvent( endEvent );
        return;
    }

    this->autoCompletionCtrl->Show(true);

    this->autoCompletionCtrl->DeleteAllItems();

    TStringList::iterator it = completions.begin();
    while(it != completions.end())
    {
        this->autoCompletionCtrl->InsertItem(this->autoCompletionCtrl->GetItemCount(),(*it));
        it++;
    }

    this->autoCompletionCtrl->SetColumnWidth(0,wxLIST_AUTOSIZE);
    int iWidth = this->autoCompletionCtrl->GetColumnWidth(0) + 3;
    if(this->autoCompletionCtrl->HasScrollbar(wxVERTICAL))
        iWidth += wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 2;
    this->autoCompletionCtrl->SetSize(wxSize(iWidth, 150));

    this->autoCompletionCtrl->SetItemState(0,wxLIST_STATE_SELECTED,wxLIST_STATE_SELECTED);
}

/** double clic dans l'auto completion */
void TCodeEditor::onAutoCompletionDblClick(wxListEvent & e)
{
    wxCommandEvent event( EVENT_AUTO_COMPLETION, VALIDATE_AUTO_COMPLETE_EVENT_ID );
    event.SetEventObject( this );
    this->AddPendingEvent( event );
}

/** touche press�e l'auto completion */
void TCodeEditor::onAutoCompletionKeyDown(wxListEvent & _e)
{
    switch(_e.GetKeyCode())
    {
        case WXK_HOME:
        case WXK_END:
        case WXK_UP:
        case WXK_DOWN:
        case WXK_PAGEUP:
        case WXK_PAGEDOWN:
            _e.Skip();
        default:
        {
            wxKeyEvent evt(wxEVT_CHAR);
            evt.m_keyCode = _e.GetKeyCode();
            if(_e.GetKeyCode() == WXK_LEFT || _e.GetKeyCode() == WXK_RIGHT)
                this->OnKeyDownEvent(evt);
        }
    }
}

