/**
© Florian Joncour, 2013-2018 florian@zetta-sys.com

Ce logiciel est un programme informatique faisant interface à la bibliothèque
Perspective3D, un outil de modélisation 3D à partir de vues orthographiques 2D.

Ce logiciel est régi par la licence CeCILL-C soumise au droit français et
respectant les principes de diffusion des logiciels libres. Vous pouvez
utiliser, modifier et/ou redistribuer ce programme sous les conditions
de la licence CeCILL-C telle que diffusée par le CEA, le CNRS et l'INRIA
sur le site "http://www.cecill.info".

En contrepartie de l'accessibilité au code source et des droits de copie,
de modification et de redistribution accordés par cette licence, il n'est
offert aux utilisateurs qu'une garantie limitée.  Pour les mêmes raisons,
seule une responsabilité restreinte pèse sur l'auteur du programme,  le
titulaire des droits patrimoniaux et les concédants successifs.

A cet égard  l'attention de l'utilisateur est attirée sur les risques
associés au chargement,  à l'utilisation,  à la modification et/ou au
développement et à la reproduction du logiciel par l'utilisateur étant
donné sa spécificité de logiciel libre, qui peut le rendre complexe à
manipuler et qui le réserve donc à des développeurs et des professionnels
avertis possédant  des  connaissances  informatiques approfondies.  Les
utilisateurs sont donc invités à charger  et  tester  l'adéquation  du
logiciel à leurs besoins dans des conditions permettant d'assurer la
sécurité de leurs systèmes et ou de leurs données et, plus généralement,
à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.

Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
pris connaissance de la licence CeCILL-C, et que vous en avez accepté les
termes.
**/

#include "animeQt.h"

#include <cmath>
#include <QCursor>
#include <QWindow>
#include <QApplication>
//#include <QDesktopWidget>
#include <QPixmap>
#include <QTabBar>
#include <QMutex>
#include <QKeyEvent>

//#include <QSoundEffect>

#ifdef DEBUG
#include <QDebug>
#endif

#include "API/perspective_api.h"
#include "API/Qt/Utils.h"

#ifdef SUPPORT_DICTION
#include "qDicte.h"
#define DICTION_THREAD /* Doit-on exécuter la diction dans un fil d'exécution séparé ? */

static bool ActiveDiction;
#ifdef DICTION_THREAD
static Th_qDicte Diction;
#else
static qDicte Diction;
#endif
#endif

static qAudioWave Audio;
static qAudioWave Audio_clic;
static qAudioWave Audio_ding;
static qAudioWave &Audio_test = Audio_ding;

/* Mutex sur la temporisation des animations afin de s'assurer de ne pas les faire se supperposer et également pour permettre un certain contrôle.
    En cas d'arrêt brutal de l'animation, comme par exemple à la suite de la fermeture d'une fenêtre,
    il suffira d'appeler le temporisateur pour placer le fil d'exécution en attente avant, si nécessaire, de libérer la mémoire. */
QMutex Mutex_Tempo_Animation;

/* Distance maximum qui peut être parcourue pour les déplacement (curseur, fenêtres...). Sert de garde-fou pour éviter de bloquer indéfiniment. */
#define DIST_DEPLACEMENT_MAX 6000


void RedimHWidgetAnim(QWidget *w, int taille)
/* Etirement horizontal d'un widget version animée. */
{
    const int pas = 25;
    const int tempo = 30;

    if (taille == w->width())
    {
        return;
    }
    else if ((taille > w->width())) /* Etirement */
    {
        const int n = taille-pas;
        for(int i=w->width(); i<n; i+=pas)
        {
            w->setMinimumWidth(i);
            SommeilTempoQt(tempo);
        }
    }
    else /* Rétrécissement */
    {
        const int n = taille+pas;
        for(int i=w->width(); i>n; i-=pas)
        {
            w->setMinimumWidth(i);
            SommeilTempoQt(tempo);
        }
    }
    w->setMinimumWidth(taille);
}

inline int DivisionCoord(int n1, int n2, float f)
{
    return n1 + ((n2 - n1) * f);
}

QScreen *EcranWidget(const QWidget *w)
/* Renvoi l'écran qui contient le QWidget. */
{
    if (!w)
    {
        return nullptr;
    }
    QWindow *fenetre = w->windowHandle();
    if (!fenetre)
    {
        QWidget *fenetre_parent = w->window();
        if (fenetre_parent)
        {
            fenetre = fenetre_parent->windowHandle();
        }

        if (!fenetre)
        {
#ifdef DEBUG
            qDebug() << "EcranWidget :: Impossible de trouver la fenêtre du widget !";
#endif
            return QGuiApplication::primaryScreen();
        }
    }
    return fenetre->screen();
}

QScreen *EcranMenu(const QMenu *w)
/* Renvoi l'écran qui contient le QWidget. */
{
    if (!w)
    {
        return nullptr;
    }
    QWindow *fenetre = w->windowHandle();
    if (!fenetre)
    {
        QWidget *fenetre_parent = w->window();
        if (fenetre_parent)
        {
            fenetre = fenetre_parent->windowHandle();
        }

        if (!fenetre)
        {
#ifdef DEBUG
            qDebug() << "EcranWidget :: Impossible de trouver la fenêtre du menu !";
#endif
            return QGuiApplication::primaryScreen();
        }
    }
    return fenetre->screen();
}

QPoint PositionCurseurWidget(const QWidget *w)
/* Renvoi la position du curseur sur l'écran contenant le widget. */
{
    QScreen *ecran = EcranWidget(w);
    return ( (ecran) ? QCursor::pos(ecran) : QPoint() );
}

KeyPressFilter::KeyPressFilter()
{
}

KeyPressFilter::~KeyPressFilter()
{
}

bool KeyPressFilter::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::KeyPress)
    {
//        QKeyEvent *keyEvent = reinterpret_cast<QKeyEvent *>(event);
//        bool res = QObject::eventFilter(obj, event);

//        if (keyEvent->key() == Qt::Key_Return)
//        {
//            return true;
//        }
//        else
//        {
//            return res;
//        }

        event->ignore();
        return true; // Ignore systématiquement
    }
    else
    {
        return QObject::eventFilter(obj, event);
    }
}

#ifdef SUPPORT_DICTION
AnimationQt::AnimationQt() : pas_deplacement(15), peripherique_audio(0)
#else
AnimationQt::AnimationQt() : pas_deplacement(15), peripherique_audio(0)
#endif
{
#ifdef SUPPORT_DICTION
    ActiveDiction = true;
#endif // SUPPORT_DICTION

    arret_animation = true;

    if (!Audio_clic.Charge(":/audio/clic.wav"))
    {
#ifdef DEBUG
        qDebug() << "AnimationQt::AnimationQt() :: Erreur de chargement du fichier audio pour 'cliv.wav'";
#endif
    }

    if (!Audio_ding.Charge(":/audio/ding.wav"))
    {
#ifdef DEBUG
        qDebug() << "AnimationQt::AnimationQt() :: Erreur de chargement du fichier audio pour 'ding.wav'";
#endif
    }

    /* Configuration par défaut: */
    tempo_reference = 260;
    tempo_deplacement = 30; /* Temporisateur entre chaque mouvements de la souris. */
    tempo_clic_souris = 50; /* Temporisateur entre chaque images de l'animation du clic de la souris. */

    active_bruitages = false;
    volume_audio = 0.75;
}

inline bool ChargeCurseur(const char s[])
{
    QPixmap r(s, nullptr, Qt::ColorOnly | Qt::DiffuseDither | Qt::DiffuseAlphaDither);
//    QPixmap r(s);
    if (!r.isNull())
    {
        QGuiApplication::setOverrideCursor(QCursor(r));
        return true;
    }
    return false;
}

AnimationQt::~AnimationQt()
{
//    Audio().stop();
}

void AnimationQt::ConfigurationTempsReference(int tempo)
{
    tempo_reference = tempo;
}

void AnimationQt::ConfigurationSouris(int vitesse_deplacement_px_s, int temps_clic_ms)
/* Configuration de la souris : Vitesse de déplacement en pixels par seconde, temps du clic en millisecondes */
{
    const float pas_d = (float) vitesse_deplacement_px_s / (float) pas_deplacement;
    tempo_deplacement = (int) (1000./pas_d);
    tempo_clic_souris = temps_clic_ms / 12; /* Il y a l'équivalent de 12 appels du temporisateur par clic (8 + un tempo*4). */
}

void AnimationQt::ConfigurationAudio(bool active_bruitages_audio, qreal volume, QAudioDeviceInfo const *peripherique)
{
    peripherique_audio = peripherique;
    active_bruitages = active_bruitages_audio;
    volume_audio = volume;

    if (Audio_clic.ValideChargement())
    {
        Audio_clic.defVolume(volume_audio);
    }

    if (Audio_ding.ValideChargement())
    {
        Audio_ding.defVolume(volume_audio);
    }
}

void AnimationQt::InitAnimations()
{
    arret_animation = false;
}

void AnimationQt::AttendFinAnimation()
{
    while (!arret_animation)
    {
        SommeilTempoQt(50);
    }
}

void AnimationQt::ArretAnimations()
{
    arret_animation = true;
    QApplication::setOverrideCursor(Qt::ArrowCursor);
}

void AnimationQt::ArretAnimationsInterne() const
/* Comme ArretAnimations(), mais avec le qualificatif const via la propriété mutable sur le booléen. */
{
    arret_animation = true;
}

bool AnimationQt::Sommeil(int ms, bool ignore_etat_animation) const
{
    if (arret_animation && !ignore_etat_animation)
    {
        return false;
    }
    if (ms != 0)
    {
        if (Mutex_Tempo_Animation.tryLock(1000))
        {
            SommeilTempoQt((ms > 0) ? ms : tempo_reference);
            Mutex_Tempo_Animation.unlock();
        }
        else
        {
            /* On a une double animation en cours (peut survenir si l'utilisateur touche à certains éléments de l'interface pendant une démo, notamment en validant de nouveaux paramètres).
                On arrête tout pour éviter le bloquage.  */
            ArretAnimationsInterne();
        }
    }
    return true;
}

void AnimationQt::ConfigurationDiction(bool active, int lang_p3d, qreal volume, const QString &chemin_ressources)
{
#ifdef SUPPORT_DICTION
    ActiveDiction = active;

#ifdef DICTION_THREAD
    const int langue = (lang_p3d == PENUM_CAST_INT(Perspective3D::i18n::lang_p3d_t::P3D_LANG_FR)) ? qDicte::LANG_FR : qDicte::LANG_EN_US;
    Diction.configurationDiction(langue, volume, peripherique_audio, chemin_ressources);
#else
    if (!chemin_ressources.isEmpty()) /* Impérativement avant l'init de Pico ! */
    {
        Diction.defCheminRessources(chemin_ressources);
    }

    if (lang_p3d == PENUM_CAST_INT(Perspective3D::i18n::lang_p3d_t::P3D_LANG_FR))
        Diction.Reinit(qDicte::LANG_FR);
    else
        Diction.Reinit(qDicte::LANG_EN_US);

    Diction.defVolumeAudio(volume);

    if (peripherique_audio)
    {
        if (!peripherique_audio->isNull())
        {
            Diction.defPeripherique(peripherique_audio);
        }
    }
#endif

#else
    P_UNUSED(active);
    P_UNUSED(lang_p3d);
    P_UNUSED(volume);
    P_UNUSED(chemin_ressources);
#endif
}

bool AnimationQt::ValideDiction() const
{
#ifdef SUPPORT_DICTION
    #ifdef DICTION_THREAD
        return Diction.Diction().Valide();
    #else
        return Diction.Valide();
    #endif
#else
    return false;
#endif
}

bool AnimationQt::ExecDiction(const QString &texte, int langue, qreal volume, bool force, QAudioDeviceInfo const * peripherique) const
/* Exécute la synthèse vocale du texte donné en argument. */
{
#ifdef SUPPORT_DICTION
    if (!force)
    {
        if (!ValideDiction() || !ActiveDiction)
        {
            return false;
        }
    }

#ifdef DICTION_THREAD
    return Diction.execDiction(texte, langue, volume, peripherique);
#else
    if (langue != Diction.Langue())
    {
        if (!Diction.Reinit(langue))
        {
            return false;
        }
    }

    if (!CompareE(Diction.VolumeAudio(), volume))
    {
        Diction.defVolumeAudio(volume);
    }

    return Diction.Dicte(texte, peripherique);
#endif

#else
    P_UNUSED(texte);
    P_UNUSED(langue);
    P_UNUSED(volume);
    P_UNUSED(force);
    return false;
#endif
}

void AnimationQt::AttendFinDiction() const
/* Attend la fin de la diction en cours. */
{
#ifdef SUPPORT_DICTION
    while (Diction.DictionEnCours())
    {
        SommeilTempoQt(10);
    }
#endif // SUPPORT_DICTION
}

bool AnimationQt::ArretDiction() const
/* Arrêt de la diction en cours. */
{
#ifdef SUPPORT_DICTION
    if (Diction.DictionEnCours())
    {
        Diction.stopDiction();
        return true;
    }
#endif // SUPPORT_DICTION
    return false;
}


QString AnimationQt::ErreurDiction() const
{
#ifdef SUPPORT_DICTION
    #ifdef DICTION_THREAD
        return Diction.Diction().MessageErreur();
    #else
        return Diction.MessageErreur();
    #endif
#else
    return QString();
#endif
}

QString AnimationQt::ErreurAudio() const
{
    return Audio.DerniereErreur();
}

QString AnimationQt::ErreurTestAudio() const
{
    return Audio_test.DerniereErreur();
}

inline void BloqueExecAudio(const qAudioWave &a)
/* Bloque le processus appelant tant qu'une lecture sur le son donné en argument est en cours. */
{
    while (a.LectureEnCours())
    {
        SommeilTempoQt(15);
    }
}

bool AnimationQt::TestAudio(qreal volume, const QAudioDeviceInfo *peripherique) const
{
    if (Audio_test.ValideChargement())
    {
        Audio_test.defVolume(volume_audio);
        return Audio_test.Lecture((peripherique != 0) ? peripherique : peripherique_audio, volume, false);
    }
    return false;
}

bool AnimationQt::LectureAudio(const QString &fichier, qreal volume, const QAudioDeviceInfo *peripherique, bool bloque_exec) const
/* Lecture d'un fichier audio (si autorisé par la configuration) */
{
    if (Audio.Charge(fichier))
    {
        Audio.defVolume(volume_audio);
        return Audio.Lecture((peripherique != 0) ? peripherique : peripherique_audio, volume, bloque_exec);
    }
    return false;
}

bool AnimationQt::CurseurPoint(const QPoint &cible, QScreen *ecran, int sommeil_post, bool ignore_etat_animation) const
{
    const int deplacement_max = DIST_DEPLACEMENT_MAX;
    const int garde_fou = deplacement_max / pas_deplacement;

    if (arret_animation && !ignore_etat_animation)
    {
        return false;
    }
    if (!ecran)
    {
        ecran = QGuiApplication::primaryScreen();
    }

    if (!ecran)
    {
        return false;
    }

    if (!(ecran->geometry().contains(cible, false)))
    {
#ifdef DEBUG
        qDebug() << "Cible curseur en dehors de l'écran -> " << cible << " (" << ecran->geometry() << ")";
#endif
        return false;
    }

    int i = 0;

    QPoint p_origine = QCursor::pos(ecran);
    QPoint delta_cible = cible - p_origine;
    float dist_cible = sqrtf((delta_cible.x()*delta_cible.x()) + (delta_cible.y()*delta_cible.y()));
    int n_points = int(dist_cible / pas_deplacement);

    const float mult_n_points = 1.f / float(n_points);

    if ((dist_cible > (pas_deplacement * 10)) && (n_points > 1)) /* Si déplacement long, courbe de bézier */
    {
        QPoint courbure = p_origine + (delta_cible / 2); /* Milieu entre les points */
        int diff_courbure = int(dist_cible * .11f); /* Niveau de courbure. */
        courbure += QPoint(diff_courbure, diff_courbure);

        for(int i=0; i<=n_points; ++i) /* Courbe de Bézier */
        {
            float f = float(i) * mult_n_points;
            int x = DivisionCoord(DivisionCoord(p_origine.x(), courbure.x(), f), DivisionCoord(courbure.x(), cible.x(), f), f);
            int y = DivisionCoord(DivisionCoord(p_origine.y(), courbure.y(), f), DivisionCoord(courbure.y(), cible.y(), f), f);
            QCursor::setPos(ecran, x, y);
            Sommeil(tempo_deplacement, ignore_etat_animation);
            if (i >= garde_fou)
                break;
        }
    }
    else /* Tracé linéaire (déplacement court) */
    {
        for(int i=0; i<n_points; ++i) /* Linéaire */
        {
            float f = float(i) * mult_n_points;
            int x = DivisionCoord(p_origine.x(), cible.x(), f);
            int y = DivisionCoord(p_origine.y(), cible.y(), f);

            QCursor::setPos(ecran, x, y);
            Sommeil(tempo_deplacement, ignore_etat_animation);
            if (i >= garde_fou)
                break;
        }
    }

    QCursor::setPos(ecran, cible.x(), cible.y()); /* On s'assure de tomber exactement où demandé, même si la boucle a quitté précipitament. */

    if (sommeil_post > 0)
        Sommeil(sommeil_post, ignore_etat_animation);

    return (i < garde_fou);
}

void AnimationQt::ClicPoint(const QCursor *curseur_original, int sommeil_post, bool ignore_etat_animation) const
{
    if (arret_animation && !ignore_etat_animation)
        return;

    if (!curseur_original)
    {
        curseur_original = QGuiApplication::overrideCursor();
    }

    ChargeCurseur(":/icones/clic_souris1.png");
    Sommeil(tempo_clic_souris, ignore_etat_animation);
    ChargeCurseur(":/icones/clic_souris2.png");
    Sommeil(tempo_clic_souris, ignore_etat_animation);

    if (active_bruitages)
    {
        if (Audio_clic.ValideChargement())
        {
            Audio_clic.Lecture(peripherique_audio, volume_audio, false);
        }
    }

    ChargeCurseur(":/icones/clic_souris3.png");
    Sommeil(tempo_clic_souris, ignore_etat_animation);
    ChargeCurseur(":/icones/clic_souris4.png");
    Sommeil(tempo_clic_souris, ignore_etat_animation);
    ChargeCurseur(":/icones/clic_souris5.png");
    Sommeil(tempo_clic_souris, ignore_etat_animation);
    ChargeCurseur(":/icones/clic_souris4.png");
    Sommeil(tempo_clic_souris, ignore_etat_animation);
    ChargeCurseur(":/icones/clic_souris3.png");
    Sommeil(tempo_clic_souris, ignore_etat_animation);
    ChargeCurseur(":/icones/clic_souris2.png");
    Sommeil(tempo_clic_souris, ignore_etat_animation);
    ChargeCurseur(":/icones/clic_souris1.png");
    Sommeil(tempo_clic_souris*4, ignore_etat_animation);

    if (active_bruitages)
    {
        if (Audio_clic.ValideChargement())
        {
            BloqueExecAudio(Audio_clic);
        }
    }


    if (curseur_original)
    {
        QGuiApplication::setOverrideCursor(*curseur_original);
    }
    else
    {
        QGuiApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
    }
    if (sommeil_post > 0)
        Sommeil(sommeil_post, ignore_etat_animation);
}

bool AnimationQt::CurseurWidget(const QWidget *w, const int sommeil_post, bool ignore_etat_animation) const
/* Déplace le curseur de la souris au centre du QWidget donné en argument. L'interface sera bloquée jusqu'à arriver à destination. */
{
    if (arret_animation && !ignore_etat_animation)
        return false;

    if (!w)
    {
#ifdef DEBUG
    qDebug() << "CurseurWidget :: Widget nul, abandon !";
#endif
        return false;
    }

    if (!w->isVisible())
        return false;

    QRect r = w->rect();
    if (r.isNull())
        return false;

    QPoint cible = w->mapToGlobal( QPoint((r.x() + (r.width()/3)), r.y() + (r.height()/2) ));

//    if (cible.isNull())
//        return false;

    QScreen *ecran = EcranWidget(w);
    if (!ecran)
        return false;

    CurseurPoint(cible, ecran);

    if (sommeil_post > 0)
        Sommeil(sommeil_post, ignore_etat_animation);

    return true;
}

bool AnimationQt::CurseurMenu(const QMenu *menu, QAction *action, const QMenu *sous_menu, int sommeil_post, bool ignore_etat_animation) const
{
    if (arret_animation && !ignore_etat_animation)
        return false;

    if (!menu || !action)
    {
#ifdef DEBUG
        qDebug() << "CurseurMenu :: Action ou menu nuls, abandon  : " << menu << " : " << action;
#endif
        return false;
    }

    if (!menu->isVisible())
    {
#ifdef DEBUG
        qDebug() << "CurseurMenu :: Menu non visible, abandon.";
#endif
        return false;
    }

    QRect r = menu->actionGeometry(action);
    if (r.isNull())
    {
        if (sous_menu)
        {
            r = sous_menu->actionGeometry(action); /* Second essai, le menu peut être différent dans le cas d'un sous-menu. */
        }

        if (r.isNull())
        {
#ifdef DEBUG
            qDebug() << "CurseurMenu :: Rectangle de l'action indéfini, abandon. : " << r;
#endif
            return false;
        }
    }

    QPoint cible = menu->mapToGlobal( QPoint((r.x() + (r.width()/3)), r.y() + (r.height()/2) ));
//    if (cible.isNull())
//    {
//        return false;
//    }

    QScreen *ecran = EcranMenu(menu);
    if (!ecran)
    {
#ifdef DEBUG
        qDebug() << "CurseurMenu :: Impossible de déterminer l'écran pour le menu : (rect=" << r << ")";
#endif
        return false;
    }

    CurseurPoint(cible, ecran);

    if (sommeil_post > 0)
        Sommeil(sommeil_post, ignore_etat_animation);

    return true;
}

bool AnimationQt::DeplaceWidget(QWidget *w, const QPoint &pos, int sommeil_post, bool ignore_etat_animation) const
/* Déplace un widget à la position absolue donnée en argument. */
{
    if (!w)
    {
#ifdef DEBUG
    qDebug() << "DeplaceWidget :: Widget nul, abandon !";
#endif
        return false;
    }

    QScreen *ecran = EcranWidget(w);
    if (!ecran)
        return false;

    DeplaceWidgetPoint(w, pos, ecran, sommeil_post, ignore_etat_animation);

    if (sommeil_post > 0)
        Sommeil(sommeil_post, ignore_etat_animation);

    return true;
}

bool AnimationQt::DeplaceWidgetCurseur(QWidget *w, int sommeil_post, bool ignore_etat_animation) const
/* Déplace le widget pour le placer à proximité du curseur. */
{
    if (!w)
        return false;

    QPoint pos = PositionCurseurWidget(w);
    if (pos.isNull())
    {
        return false;
    }

    QScreen *ecran = EcranWidget(w);
    if (ecran)
    {
        const int decalage_x = 30;
        const int decalage_y = 30;

        const QRect rect_ecran = ecran->geometry();
        if (pos.x() < (rect_ecran.width() / 2))
        {
            pos.setX(pos.x() + decalage_x);
        }
        else
        {
            pos.setX(pos.x() - (w->width() + decalage_x));
        }

        if (pos.y() < (rect_ecran.height() / 2))
        {
            pos.setY(pos.y() - decalage_y);
        }
        else
        {
            pos.setY(pos.y() - (w->height() - decalage_y));
        }
    }

    return DeplaceWidget(w, pos, sommeil_post, ignore_etat_animation);
}

bool AnimationQt::DeplaceWidgetPoint(QWidget *w, const QPoint &dest, QScreen *ecran, int sommeil_post, bool ignore_etat_animation) const
/* Effectue le déplacement du widget. */
{
    const int deplacement_max = DIST_DEPLACEMENT_MAX;
    const int garde_fou = deplacement_max / pas_deplacement;

    if (!ecran)
    {
        ecran = QGuiApplication::primaryScreen();
    }

    if (!ecran)
        return false;

    QPoint cible = dest;
    QPoint bord_opp = cible + QPoint(w->geometry().width(), w->geometry().height());

    /* Contrôle débordement de l'écran... */

    const int marge = 1; /* Marge par rapport aux bords de l'écran */
    if (cible.x() < 0)
    {
        bord_opp.setX(w->geometry().width()+marge);
        cible.setX(marge);
    }
    if (cible.y() < 0)
    {
        bord_opp.setY(w->geometry().height()+marge);
        cible.setY(marge);
    }

    if (bord_opp.x() > ecran->geometry().width())
    {
        bord_opp.setX(ecran->geometry().width()-marge);
        cible.setX(ecran->geometry().width()-w->geometry().width()-marge);
    }
    if (bord_opp.y() > ecran->geometry().height())
    {
        bord_opp.setY(ecran->geometry().height()-marge);
        cible.setY(ecran->geometry().height()-w->geometry().height()-marge);
    }

    if (arret_animation && !ignore_etat_animation)
    {
        w->move(cible);
        return true;
    }

    if ((!ecran->geometry().contains(cible, false)) || (!ecran->geometry().contains(bord_opp, false)))
    {
#ifdef DEBUG
        qDebug() << "Cible fenêtre en dehors de l'écran -> " << cible << " : " << bord_opp << " (" << ecran->geometry() << ")";
#endif
        return false;
    }

    int i = 0;

    while (true)
    {
        QPoint p = w->pos();

        QPoint delta = cible - p;

        int abs_x = ((delta.x() < 0) ? (-delta.x()) : (delta.x()));
        int abs_y = ((delta.y() < 0) ? (-delta.y()) : (delta.y()));

        int deplacement_x = 0;
        int deplacement_y = 0;

        QPointF vecteur((float) delta.x(), (float) delta.y());
        float lg = (vecteur.x()*vecteur.x()) + (vecteur.y()*vecteur.y());
        vecteur /= sqrtf(lg);

        if (abs_x > pas_deplacement)
        {
            deplacement_x = (int) ((float)(pas_deplacement) * vecteur.x());
        }

        if (abs_y > pas_deplacement)
        {
            deplacement_y = (int) ((float)(pas_deplacement) * vecteur.y());
        }

        if (!deplacement_x && !deplacement_y)
        {
            break;
        }

        w->move(p.x()+deplacement_x, p.y()+deplacement_y);
        w->update();
        Sommeil(tempo_deplacement, ignore_etat_animation);
        if (i >= garde_fou)
            break;
        ++i;
    }

    w->move(cible); /* On s'assure de tomber exactement où demandé, même si la boucle a quitté précipitament. */

    if (sommeil_post > 0)
        Sommeil(sommeil_post, ignore_etat_animation);

    return (i < garde_fou);
}

bool AnimationQt::ClicWidget(QWidget *w, int sommeil_post, bool ignore_etat_animation) const
/* Simule le clic sur un QWidget en déplacant le curseur sur son centre et en animant le curseur. */
{
    if (arret_animation && !ignore_etat_animation)
        return false;

    if (!w)
    {
#ifdef DEBUG
    qDebug() << "ClicWidget :: Widget nul, abandon !";
#endif
        return false;
    }

    if (!CurseurWidget(w))
    {
        return false;
    }

    QCursor curseur_original = w->cursor();
    ClicPoint(&curseur_original, sommeil_post, false);
    return true;
}


bool AnimationQt::ClicMenu(const QMenu *menu, QAction *action, const QMenu *sous_menu, int sommeil_post, bool ignore_etat_animation) const
/* Simule le clic sur une action dans un menu en déplacant le curseur sur sur centre et en animant le curseur. */
{
    if (arret_animation && !ignore_etat_animation)
        return false;

    if (!CurseurMenu(menu, action, sous_menu, sommeil_post, ignore_etat_animation))
    {
#ifdef DEBUG
    qDebug() << "ClicMenu :: Menu nul, abandon !";
#endif
        return false;
    }

    QCursor curseur_original = menu->cursor();
    ClicPoint(&curseur_original, sommeil_post, false);
    return true;
}

bool AnimationQt::ClicTabBar(QTabWidget *w, int id, int sommeil_post, bool ignore_etat_animation) const
{
    if (arret_animation && !ignore_etat_animation)
        return false;

    if (!w)
    {
#ifdef DEBUG
        qDebug() << "ClicTabBar :: Widget nul, abandon !";
#endif
        return false;
    }

    if ((id < 0) || (id >= w->count()))
        return false;

    if (w->currentIndex() == id)
        return false;

    QPoint p = w->mapToGlobal(w->tabBar()->tabRect(id).center());

    QScreen *ecran = EcranWidget(w);
    if (!ecran)
        return false;

    if (!CurseurPoint(p, ecran))
    {
        return false;
    }

    QCursor curseur_original = w->cursor();
    ClicPoint(&curseur_original, sommeil_post);
    return true;
}

void PeupleCmb_RAL(QComboBox &cmb)
/* Rempli la liste des RAL (choix de la couleur). */
{
    cmb.clear();
    QString texte;

    const unsigned int n_ral = Perspective3D::RAL::Nombre();

    for(unsigned int i=0; i<n_ral; ++i)
    {
        const Perspective3D::PCouleur_RAL &ral = Perspective3D::RAL::Index(i);
        QPixmap pix(16,16);

        texte = QString::number(ral.id);
        if (ral.nom)
        {
            texte += QString(" ") + ral.nom;
        }

        if (i) // RALs
        {
            pix.fill(QColor(ral.r, ral.v, ral.b));
            QIcon ic(pix);
            cmb.addItem(ic, texte);
        }
        else // Couleur indéfinie
        {
            QIcon ic;
            ic.addFile(":/icones/vueperspective.png", QSize(), QIcon::Normal, QIcon::Off);
            cmb.addItem(ic, texte);
        }
        cmb.setItemData(cmb.count()-1, QVariant(ral.id));
    }
}
