/***************************************************************************
 *   Copyright (C) 2006 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 "tgenbutton.h"
#include <wx/dcclient.h>
#include <wx/settings.h>
#include <wx/pen.h>
#include <wx/brush.h>

#include "tbitmap.h"
#include "tgenbuttonlistener.h"
#include "lib/lib_logging.h"

#define min(x,y) (((x) < (y)) ? (x) : (y))

#define TGENBUTTON_SHADOW_MULT 0.7
#define TGENBUTTON_HIGHLIGHT_MULT 1.2

IMPLEMENT_DYNAMIC_CLASS(TGenButton,wxControl);

BEGIN_EVENT_TABLE(TGenButton, wxControl)
    EVT_PAINT(TGenButton::onPaint)
    EVT_ENTER_WINDOW(TGenButton::onMouseEnter)
    EVT_LEAVE_WINDOW(TGenButton::onMouseLeave)
    EVT_LEFT_DOWN(TGenButton::onMouseLeftDown)
    EVT_LEFT_UP(TGenButton::onMouseLeftUp)
    EVT_RIGHT_DOWN(TGenButton::onMouseRightDown)
    EVT_SIZE(TGenButton::onResize)
    EVT_SET_FOCUS(TGenButton::onFocusChange)
    EVT_KILL_FOCUS(TGenButton::onFocusChange)
    EVT_KEY_DOWN(TGenButton::onKeyDown)
    EVT_KEY_UP(TGenButton::onKeyUp)
END_EVENT_TABLE()


TGenButton::TGenButton(wxWindow * parent)
    : wxControl(parent, -1, wxDefaultPosition, wxDefaultSize, wxNO_BORDER | wxTAB_TRAVERSAL), bMouseInside(false), bDown(false), bFlat(false), bToggle(false), bToggled(false), iRoundEdgeSize(2), textOrientation(LeftToRight), iOptimTextWidth(-1), iOptimTextHeight(-1), bitmap(NULL), bitmapSide(Left)
{
    this->InheritAttributes();

    if(parent && this->ShouldInheritColours()) // InheritAttributes marche pu ?!
    {
        this->SetForegroundColour(parent->GetForegroundColour());
        this->SetBackgroundColour(parent->GetBackgroundColour());
    }
}

/** calcule si necessaire la taille des elements composant l'image et les remplies */
void TGenButton::getTextElementSize(int & iTextWidth, int & iTextHeight) const
{
    if(this->iOptimTextWidth < 0 || this->iOptimTextHeight < 0)
    {
        // calcul de l'encombrement du texte
        if(this->sText.length())
        {
            wxPaintDC dc(const_cast<TGenButton*>(this));
            dc.SetFont(this->GetFont());
            dc.GetTextExtent(this->sText, &iTextWidth, &iTextHeight);
            if(this->textOrientation == TopToBottom || this->textOrientation == BottomToTop)
            {
                int i = iTextWidth;
                iTextWidth = iTextHeight;
                iTextHeight = i;
            }
        }
        else
        {
            iTextWidth = 0;
            iTextHeight = 0;
        }
    }
    else
    {
        iTextWidth = this->iOptimTextWidth;
        iTextHeight = this->iOptimTextHeight;
    }
}

/** calcule si necessaire les tailles des elements composant l'image et les remplies */
void TGenButton::getElementsSize(int & iTextWidth, int & iTextHeight, int & iBmpWidth, int & iBmpHeight) const
{
    this->getTextElementSize(iTextWidth,iTextHeight);

    // calcul de l'encombrement de l'image
    if(this->bitmap)
    {
        iBmpWidth = this->bitmap->GetWidth();
        iBmpHeight = this->bitmap->GetHeight();
    }
    else
    {
        iBmpWidth = 0;
        iBmpHeight = 0;
    }
}

/** precalcule les dimentions des composants et les conserve de maniere a optimiser les traitements ulterieurs */
void TGenButton::prepareElementsSize()
{
    if(this->iOptimTextWidth < 0 || this->iOptimTextHeight < 0)
    {
        int iTextWidth = 0, iTextHeight = 0;
        this->getTextElementSize(iTextWidth, iTextHeight);
        this->iOptimTextWidth = iTextWidth;
        this->iOptimTextHeight = iTextHeight;
    }
}

/** calcule la taille optimale du bouton */
wxSize TGenButton::DoGetBestSize() const
{
    // Do not return any arbitrary default value...
    wxASSERT_MSG( m_widget, wxT("TGenButton::DoGetBestSize called before creation") );

    int iButtonWidth = 0, iButtonHeight = 0;
    int iTextWidth = 0, iTextHeight = 0;
    int iBmpWidth = 0, iBmpHeight = 0;

    this->getElementsSize(iTextWidth, iTextHeight, iBmpWidth, iBmpHeight);

    // calcul de la taille preferee du bouton
    if(bitmapSide == Left || bitmapSide == Right)
    {
        iButtonWidth = iBmpWidth + iTextWidth + (( iBmpWidth && iTextWidth ) ? 2 : 0);
        iButtonHeight = (iBmpHeight > iTextHeight) ? iBmpHeight : iTextHeight;
    }
    else
    {
        iButtonWidth = (iBmpWidth > iTextWidth) ? iBmpWidth : iTextWidth;
        iButtonHeight = iBmpHeight + iTextHeight + ((iBmpHeight + iTextHeight) ? 2 : 0);
    }

    iButtonWidth += 2*(this->iRoundEdgeSize + 2);
    iButtonHeight += 2*(this->iRoundEdgeSize + 2);
    return wxSize( iButtonWidth, iButtonHeight );
}

void TGenButton::onPaint(wxPaintEvent& WXUNUSED(event))
{
    wxPaintDC dc(this);

    wxColour face = this->GetBackgroundColour();
    wxColour shadow((unsigned char) min(255.0, double(face.Red())*TGENBUTTON_SHADOW_MULT),
                     (unsigned char) min(255.0, double(face.Green())*TGENBUTTON_SHADOW_MULT),
                     (unsigned char) min(255.0, double(face.Blue())*TGENBUTTON_SHADOW_MULT));
    wxColour highlight((unsigned char) min(255.0, double(face.Red())*TGENBUTTON_HIGHLIGHT_MULT),
                        (unsigned char) min(255.0, double(face.Green())*TGENBUTTON_HIGHLIGHT_MULT),
                        (unsigned char) min(255.0, double(face.Blue())*TGENBUTTON_HIGHLIGHT_MULT));
    wxColour halfshadow(shadow.Red()/2 + face.Red()/2,
                        shadow.Green()/2 + face.Green()/2,
                        shadow.Blue()/2 + face.Blue()/2);

    wxColour * background;
    wxColour * border;
    wxColour * topLeftBorder;
    wxColour * bottomRightBorder;

    // couleur de fond
    if(this->bDown)
        background = &shadow;
    else if(this->bToggle && this->bToggled)
        background = &halfshadow;
    else
        background = &face;

    // couleur de la bordure
    if(this->bFlat)
    {
        if(this->bMouseInside || (this->bToggle && this->bToggled))
            border = &shadow;
        else
            border = &face;
    }
    else
        border = &shadow;

    // couleur de la bordure 3D
    if(border != &face)
    {
        if(this->bDown || (this->bToggle && this->bToggled))
        {
            topLeftBorder = &halfshadow;
            bottomRightBorder = &highlight;
        }
        else
        {
            topLeftBorder = &highlight;
            bottomRightBorder = &halfshadow;
        }
    }
    else
    {
        topLeftBorder = border;
        bottomRightBorder = border;
    }


    dc.SetTextForeground ( (this->IsEnabled()) ? this->GetForegroundColour() : shadow );
    dc.SetTextBackground ( *background );
    dc.SetFont(this->GetFont());


    int iButtonWidth = 0, iButtonHeight = 0;
    int iTextWidth = 0, iTextHeight = 0;
    int iBmpWidth = 0, iBmpHeight = 0;
    wxSize contentSize = this->DoGetBestSize();

    GetClientSize( &iButtonWidth, &iButtonHeight );
    this->getElementsSize(iTextWidth, iTextHeight, iBmpWidth, iBmpHeight);
    int iLeftMargin = 2 + this->iRoundEdgeSize + ((iButtonWidth - contentSize.GetWidth()) / 2);
    int iTopMargin = 2 + this->iRoundEdgeSize + ((iButtonHeight - contentSize.GetHeight()) / 2);

    // dessin du composant
    wxPen pen(*border);
    dc.SetPen(pen);

    wxBrush brush(*background);
    dc.SetBrush(brush);

    dc.DrawRoundedRectangle(0,0,iButtonWidth,iButtonHeight,this->iRoundEdgeSize);
    pen.SetColour(*topLeftBorder);
    dc.SetPen(pen);
    dc.DrawLine(this->iRoundEdgeSize - 1, 1, iButtonWidth - this->iRoundEdgeSize + 1, 1);
    dc.DrawLine(1, this->iRoundEdgeSize - 1, 1, iButtonHeight - this->iRoundEdgeSize + 1);
    pen.SetColour(*bottomRightBorder);
    dc.SetPen(pen);
    dc.DrawLine(this->iRoundEdgeSize - 1, iButtonHeight - 2, iButtonWidth - this->iRoundEdgeSize + 1, iButtonHeight - 2);
    dc.DrawLine(iButtonWidth - 2, this->iRoundEdgeSize - 1, iButtonWidth - 2, iButtonHeight - this->iRoundEdgeSize + 1);

    if(this->FindFocus() == this)
    {
        pen.SetColour(dc.GetTextForeground());
        pen.SetStyle(wxDOT);
        dc.SetPen(pen);
        dc.DrawRoundedRectangle(this->iRoundEdgeSize + 1, this->iRoundEdgeSize + 1,
                                iButtonWidth - (2*this->iRoundEdgeSize) - 2,
                                iButtonHeight - (2*this->iRoundEdgeSize) - 2,
                                        2);
    }

    
    // dessin du texte
    if(this->sText.length())
    {
        int iCornerTop = iTopMargin;
        int iCornerLeft = iLeftMargin;
        if(this->bitmap)
        {
            if(bitmapSide == Left)
                iCornerLeft += 2 + iBmpWidth;
            else if(bitmapSide == Top)
                iCornerTop += 2 + iBmpHeight;

            if(bitmapSide == Left || bitmapSide == Right)
            {
                if(iTextHeight < iBmpHeight)
                    iCornerTop = (iButtonHeight - iTextHeight) / 2;
            }
            else
            {
                if(iTextWidth < iBmpWidth)
                    iCornerLeft = (iButtonWidth - iTextWidth) / 2;
            }
        }

        switch(this->textOrientation)
        {
            case LeftToRight:
                break;
            case TopToBottom:
                iCornerLeft += iTextWidth;
                break;
            case RightToLeft:
                iCornerLeft += iTextWidth;
                iCornerTop += iTextHeight;
                break;
            case BottomToTop:
                iCornerTop += iTextHeight;
                break;
        }

        dc.DrawRotatedText(this->sText, iCornerLeft, iCornerTop, -1.0 * this->textOrientation);
    }

    // dessin du bitmap
    if(this->bitmap)
    {
        int iCornerTop = iTopMargin;
        int iCornerLeft = iLeftMargin;
        const TBitmap * bmp = this->bitmap;
        if(this->sText.length())
        {
            if(bitmapSide == Right)
                iCornerLeft += 2 + iTextWidth;
            else if(bitmapSide == Bottom)
                iCornerTop += 2 + iTextHeight;

            if(bitmapSide == Left || bitmapSide == Right)
            {
                if(iTextHeight > iBmpHeight)
                    iCornerTop = (iButtonHeight - iBmpHeight) / 2;
            }
            else
            {
                if(iTextWidth > iBmpWidth)
                    iCornerLeft = (iButtonWidth - iBmpWidth) / 2;
            }
        }

        if(!this->IsEnabled())
            bmp = bmp->getGreyedCopy();
        dc.DrawBitmap(*bmp, iCornerLeft, iCornerTop, true);
        if(!this->IsEnabled())
            delete bmp;
    }
}

void TGenButton::onMouseEnter(wxMouseEvent& WXUNUSED(event))
{
    this->bMouseInside = true;
    this->Refresh();
}

void TGenButton::onMouseLeave(wxMouseEvent& WXUNUSED(event))
{
    this->bMouseInside = false;
    this->Refresh();
}

void TGenButton::onMouseLeftDown(wxMouseEvent& WXUNUSED(event))
{
    this->bDown = true;
    this->Refresh();
}

void TGenButton::onMouseLeftUp(wxMouseEvent& WXUNUSED(event))
{
    if(!this->bDown)
        return;
    this->bDown = false;
    if(this->bToggle)
        this->setToggled(!this->bToggled);
    else
        this->fireButtonActivated();
    this->Refresh();
}

void TGenButton::onMouseRightDown(wxMouseEvent& WXUNUSED(event))
{
    if(!this->bDown)
        this->fireButtonRightClicked();
}

/** defini le bouton comme plat */
void TGenButton::setFlat(bool b)
{
    this->bFlat = b;
    this->Refresh();
}

/** defini le bouton comme toggleable */
void TGenButton::setToggleable(bool b)
{
    if(!b)
        this->setToggled(false);
    this->bToggle = b;
    this->Refresh();
}

/** defini le bouton comme enfonce */
void TGenButton::setToggled(bool b)
{
    if(!this->bToggle)
        return;
    if(this->bToggled != b)
    {
        this->bToggled = b;
        this->fireButtonToggled();
    }
    this->Refresh();
}

/** defini le texte du bouton */
void TGenButton::setText(const wxString & s)
{
    this->sText = s;
    this->iOptimTextWidth = -1;
    this->iOptimTextHeight = -1;
    this->prepareElementsSize();
    this->Refresh();
}

/** defini l'orientation du texte */
void TGenButton::setTextOrientation(TextOrientation orient)
{
    this->textOrientation = orient;
    this->iOptimTextWidth = -1;
    this->iOptimTextHeight = -1;
    this->prepareElementsSize();
    this->Refresh();
}

/** defini l'image a afficher (NULL si aucune) */
void TGenButton::setBitmap(const TBitmap * bmp)
{
    this->bitmap = bmp;
    this->prepareElementsSize();
    this->Refresh();
}

/** defini la position de l'image par rapport au texte */
void TGenButton::setBitmapSide(BitmapSide side)
{
    this->bitmapSide = side;
    this->Refresh();
}

/** ajoute un ecouteur */
bool TGenButton::addButtonListener(TGenButtonListener * l)
{
    return this->listeners.insert(l).second;
}

/** enleve un ecouteur */
bool TGenButton::removeButtonListener(TGenButtonListener * l)
{
    return this->listeners.erase(l) != 0;
}

/** propage l'evenement "bouton active aux ecouteurs */
void TGenButton::fireButtonActivated()
{
    TGenButtonListenersList::iterator itB = this->listeners.begin();
    TGenButtonListenersList::iterator itE = this->listeners.end();

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

/** propage l'evenement "bouton bascule aux ecouteurs */
void TGenButton::fireButtonToggled()
{
    TGenButtonListenersList::iterator itB = this->listeners.begin();
    TGenButtonListenersList::iterator itE = this->listeners.end();

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

/** propage l'evenement "clic droit sur le bouton" aux ecouteurs */
void TGenButton::fireButtonRightClicked()
{
    TGenButtonListenersList::iterator itB = this->listeners.begin();
    TGenButtonListenersList::iterator itE = this->listeners.end();

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

void TGenButton::onResize(wxSizeEvent & WXUNUSED(event))
{
    this->Refresh();
}

void TGenButton::onFocusChange(wxFocusEvent & WXUNUSED(event))
{
    this->Refresh();
}

void TGenButton::onKeyDown(wxKeyEvent & _e)
{
    if(_e.ControlDown() || _e.AltDown() || _e.CmdDown() || _e.ShiftDown())
    {
        _e.Skip();
        return;
    }
    
    if(_e.GetKeyCode() == WXK_SPACE || _e.GetKeyCode() == WXK_RETURN)
    {
        wxMouseEvent e2(wxEVT_LEFT_DOWN);
        this->onMouseLeftDown(e2);
        return;
    }
    _e.Skip();
}

void TGenButton::onKeyUp(wxKeyEvent & _e)
{
    if(_e.ControlDown() || _e.AltDown() || _e.CmdDown() || _e.ShiftDown())
    {
        _e.Skip();
        return;
    }
    
    if(_e.GetKeyCode() == WXK_SPACE || _e.GetKeyCode() == WXK_RETURN)
    {
        wxMouseEvent e2(wxEVT_LEFT_UP);
        this->onMouseLeftUp(e2);
        return;
    }
    _e.Skip();
}
