# -*- coding: iso-8859-1 -*-
from __future__ import division # 1/2 == .5 (par defaut, 1/2 == 0)
from __future__ import with_statement

#    WxGeometrie
#    Dynamic geometry, graph plotter, and more for french mathematic teachers.
#    Copyright (C) 2005-2007  Nicolas Pourcelot
#
#    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

# version unicode


from LIB import *
import matplotlib


# uncomment the following to use wx rather than wxagg
# matplotlib.use('WX')
# from matplotlib.backends.backend_wx import FigureCanvasWx as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import FigureCanvasAgg
from matplotlib.backends.backend_agg import RendererAgg
from matplotlib._image import frombyte



##        artistes = list(self.axes.artists)
##        artistes.extend(self.axes.lines)
##        artistes.extend(self.axes.patches)
##        artistes.extend(self.axes.texts)
##        artistes.extend(self.axes.tables)
##        artistes.extend(self.axes.collections)
##        artistes.sort(key = lambda x: x.zorder)
##        regler_textes = abs(param.zoom_texte - 1) > 0.05
##        regler_lignes = abs(param.zoom_ligne - 1) > 0.05
##        # C'est ici qu'a lieu le dessin proprement dit.
####        print "artistes:", len(artistes), artistes
##        for artiste in artistes:
##            if artiste._visible:
##                try:
##                    if regler_textes:
##                        if isinstance(artiste, matplotlib.text.Text):
##                            artiste._size_init_ = artiste.get_size()
##                            artiste.set_size(artiste._size_init_*param.zoom_texte)
##                    if regler_lignes:
##                        if isinstance(artiste, matplotlib.lines.Line2D):
##                            artiste._linewidth_init_ = artiste.get_linewidth()
##                            artiste._markersize_init_ = artiste.get_markersize()
##                            artiste._markeredgewidth_init_ = artiste.get_markeredgewidth()
##                            artiste.set_linewidth(artiste._linewidth_init_*param.zoom_ligne)
##                            artiste.set_markersize(artiste._markersize_init_*param.zoom_ligne)
##                            artiste.set_markeredgewidth(artiste._markeredgewidth_init_*param.zoom_ligne)
##                    self.axes.draw_artist(artiste)
##                finally:
##                    if regler_textes:
##                        if isinstance(artiste, matplotlib.text.Text):
##                            artiste.set_size(artiste._size_init_)
##                    if regler_lignes:
##                        if isinstance(artiste, matplotlib.lines.Line2D):
##                            artiste.set_linewidth(artiste._linewidth_init_)
##                            artiste.set_markersize(artiste._markersize_init_)
##                            artiste.set_markeredgewidth(artiste._markeredgewidth_init_)


class ZoomArtistes(object):
    def __init__(self, axes, zoom_texte, zoom_ligne):
        self.zoom_texte = zoom_texte
        self.zoom_ligne = zoom_ligne
        self.size = {}
        self.linewidth = {}
        self.markersize = {}
        self.markeredgewidth = {}
        self.artistes = list(itertools.chain(axes.artists, axes.lines, axes.patches, axes.texts, axes.tables, axes.collections))

    def __enter__(self):
        self.regler_textes = abs(self.zoom_texte - 1) > 0.05
        self.regler_lignes = abs(self.zoom_ligne - 1) > 0.05
        if self.regler_textes or self.regler_lignes:
            for artiste in self.artistes:
                if artiste._visible:
                    if self.regler_textes:
                         if isinstance(artiste, matplotlib.text.Text):
                            size = self.size[id(artiste)] = artiste.get_size()
                            artiste.set_size(size*self.zoom_texte)
                    if self.regler_lignes:
                        if isinstance(artiste, matplotlib.lines.Line2D):
                            lw = self.linewidth[id(artiste)] = artiste.get_linewidth()
                            artiste.set_linewidth(lw*self.zoom_ligne)
                            ms = self.markersize[id(artiste)] = artiste.get_markersize()
                            artiste.set_markersize(ms*self.zoom_ligne)
                            mw = self.markeredgewidth[id(artiste)] = artiste.get_markeredgewidth()
                            artiste.set_markeredgewidth(mw*self.zoom_ligne)
                        elif isinstance(artiste, matplotlib.collections.LineCollection):
                            lws = self.linewidth[id(artiste)] = artiste.get_linewidth()
                            artiste.set_linewidth(tuple(lw*self.zoom_ligne for lw in lws))
        return self.artistes

    def __exit__(self, type, value, traceback):
        if self.regler_textes or self.regler_lignes:
            for artiste in self.artistes:
                if artiste._visible:
                    if self.regler_textes:
                        if isinstance(artiste, matplotlib.text.Text):
                            artiste.set_size(self.size[id(artiste)])
                    if self.regler_lignes:
                        if isinstance(artiste, matplotlib.lines.Line2D):
                            artiste.set_linewidth(self.linewidth[id(artiste)])
                            artiste.set_markersize(self.markersize[id(artiste)])
                            artiste.set_markeredgewidth(self.markeredgewidth[id(artiste)])
                        elif isinstance(artiste, matplotlib.collections.LineCollection):
                            artiste.set_linewidth(self.linewidth[id(artiste)])




class Moteur_graphique(object):
    def __init__(self, canvas):
        self.canvas = canvas
##        # Éléments en attente d'être dessinés
##        self._queue = []
        self._dernier_objet_deplace = None
        self.axes = self.canvas.axes
        # Légère optimisation :
        # les objets sont copiés et modifiés plutôt que recréés
        self.__empty_plot = self.axes.plot([0,1], [1,0])[0]
        self.__empty_fill = self.axes.fill([0, 1, 1, 0], [1, 0, 0, 1])[0]
        self.__empty_text = self.axes.text(0, 0, '')
        self.__empty_text._fontproperties.set_family('serif')
        self.__empty_arrow = self.axes.arrow(0, 0, 1, 1)
        self._effacer_artistes()
        # Buffer contenant la dernière image.
        self._dernier_dessin = None
##        self.mise_en_cache_axes = [[],[], [], []] # plot, fill, text, artist
        #if self.canvas.transformation is not None:
            #self.__empty_plot.set_transform(self.canvas.transformation)
            #self.__empty_fill.set_transform(self.canvas.transformation)
            #self.__empty_text.set_transform(self.canvas.transformation)

    # -------------------
    # Fonctions de dessin
    # -------------------

    @property
    def zoom_ligne(self):
        return self.canvas.zoom_ligne

    @property
    def zoom_texte(self):
        return self.canvas.zoom_texte

    def _ajouter_objet(self, artiste):
        self.axes.artists.append(artiste)

    def _ajouter_objets(self, liste_artistes):
        self.axes.artists.extend(liste_artistes)

    def ajouter(self, artiste):
        u"""Ajoute un artiste (objet graphique de matplotlib) à dessiner.

        NB: 'artiste' peut aussi être une liste d'artistes, ou une liste de listes..."""
        if isinstance(artiste, (list, tuple)):
            for elt in artiste:
                self.ajouter(elt)
        else:
            self._ajouter_objet(artiste)


    def ligne(self, x = None, y = None, couleur = None, pixel = False, **kw):
        ligne = module_copy.copy(self.__empty_plot)
        if x is not None and y is not None:
            if pixel:
                x, y = self.canvas.pix2coo(x, y)
            if self.canvas.transformation is not None:
                x, y = zip(*(zip(x, y)*self.canvas.transformation).array)
            ligne.set_data(x, y)
        if couleur is not None:
            ligne.set_color(couleur)
        if kw:
            ligne.set(**kw)
        return ligne

    def ajouter_ligne(self, x = None, y = None, couleur = None, pixel = False, **kw):
        self._ajouter_objet(self.ligne(x, y, couleur, pixel, **kw))


    def polygone(self, x = None, y = None, couleur = None, pixel = False, **kw):
        polygone = module_copy.copy(self.__empty_fill)
        if x is not None and y is not None:
            if pixel:
                x, y = self.canvas.pix2coo(x, y)
            if self.canvas.transformation is not None:
                x, y = zip(*(zip(x, y)*self.canvas.transformation).array)
            polygone.xy = zip(x, y)
        if couleur is not None:
            polygone.set_facecolor(couleur)
        if kw:
            polygone.set(**kw)
        return polygone

    def ajouter_polygone(self, x = None, y = None, couleur = None, pixel = False, **kw):
        self._ajouter_objet(self.polygone(x, y, couleur, pixel, **kw))


    def texte(self, x = None, y = None, txt = None, pixel = False, **kw):
        texte = module_copy.copy(self.__empty_text)
        texte._fontproperties = matplotlib.font_manager.FontProperties()
        if x is not None and y is not None:
            if pixel:
                x, y = self.canvas.pix2coo(x, y)
            if self.canvas.transformation is not None:
                x, y = ((x, y)*self.canvas.transformation).array[0]
            texte.set_x(x)
            texte.set_y(y)
        if txt is not None:
            if param.unicode and not isinstance(txt, unicode):
                txt = unicode(txt, param.encodage)
            texte.set_text(txt)
        if kw:
            texte.set(**kw)
        return texte

    def ajouter_texte(self, x = None, y = None, txt = None, pixel = False, **kw):
        self._ajouter_objet(self.texte(x, y, txt, pixel, **kw))


    def arc(self, x, y, vecteur, couleur = "k", **kw):
        # affiche un petit demi-cercle d'orientation choisie
        # Par ex, pour le vecteur (1,0), ie. ->, l'arc est orienté comme ci: (
        # Pour le vecteur (-1,0), ie. <-, l'arc est orienté comme ci: ).
        # On travaille sur les pixels pour faire de la trigonometrie plus simplement
        # (le repere devient ainsi orthonorme)
        vecteur = self.canvas.dcoo2pix(*vecteur)
        if vecteur[0] == 0:
            if vecteur[1] > 0:
                angle = math.pi/2
            else:
                angle = -math.pi/2
        else:
            angle = math.atan(vecteur[1]/vecteur[0])
            if vecteur[0] < 0:
                angle += math.pi
        # donne l'angle d'incidence à l'extrémité
        t = numpy.arange(angle - math.pi/2, angle + math.pi/2, 0.05)
        R = self.canvas.taille["("]*self.zoom_ligne

        x, y = self.canvas.coo2pix(x, y)
        return self.ligne(x + R*(numpy.cos(t) - math.cos(angle)), \
                                     y + R*(numpy.sin(t) - math.sin(angle)), \
                                     couleur = couleur, pixel = True, **kw)

    def ajouter_arc(self, x, y, vecteur, couleur = "k", **kw):
        self._ajouter_objet(self.arc(x, y, vecteur, couleur, **kw))



    def point(self, x, y, couleur = 'k', plein = True, **kw):
        kw.setdefault('zorder', 2.1)
        kw.setdefault('markeredgecolor', couleur)
        kw.setdefault('markeredgewidth', 1)
        return self.ligne([x], [y], couleur if plein else 'w', \
                      markersize = 2*self.canvas.taille["o"], marker = "o", **kw)
        # Le zorder doit être supérieur à celui d'une courbe (2 par défaut),
        # et la largeur du trait doit être celle d'une courbe (1 par défaut).

    def ajouter_point(self, x, y, couleur = 'k', plein = True, **kw):
        self._ajouter_objet(self.point(x, y, couleur, plein, **kw))


    def fleche(self, x0, y0, x1, y1, **kw):
        arrow = module_copy.copy(self.__empty_arrow)
        #TODO: continuer code (matplotlib 0.99+ requis)
        # cf. matplotlib.patches.ArrowStyle.get_styles() pour les
        # styles de flêches.
        return arrow

    def ajouter_fleche(self, x0, y0, x1, y1, **kw):
        self._ajouter_objet(self.fleche(self, x0, y0, x1, y1, **kw))


    # ----------------------
    # Gestion de l'affichage
    # ----------------------



    def _creer_arriere_plan(self, rafraichir_axes):
        if rafraichir_axes:
            dict_artistes = self._effacer_artistes()
            self.canvas.parent._affiche()
            self._dessine_axes()
            # Les axes et légendes de matplotlib ne conviennent pas, on les désactive.
            self.axes.legend_ = None
            self.axes.axison = False
            # Synchronisation de la fenêtre de matplotlib avec celle de la feuille
            self._regler_fenetre()
            # Attention, la synchronisation de la fenêtre doit avoir lieu :
            # - APRÈS self._canvas.parent._affiche(), qui peut encore modifier la fenêtre
            # - avant FigureCanvasAgg.draw(), qui dessine la figure
            with ZoomArtistes(self.axes, self.zoom_texte, self.zoom_ligne):
                FigureCanvasAgg.draw(self.canvas)
##            self.canvas.draw(repaint = False)
##            self._dessiner_artistes()
            self._mise_en_cache_axes = self._en_cache()
            # _artistes_repere sert pour déboguer. Cf. '.infos()'
            self._artistes_repere = self._restaurer_artistes(dict_artistes)
        else:
            self._restaurer(self._mise_en_cache_axes)


    def _regler_fenetre(self):
        xmin, xmax, ymin, ymax = self.canvas.fenetre
        if param.old_matplotlib:
            self.axes.viewLim.intervalx().set_bounds(xmin, xmax)
            self.axes.viewLim.intervaly().set_bounds(ymin, ymax)
        else:
            self.axes.viewLim.set_points(pylab.array([[xmin, ymin], [xmax, ymax]]))


    def dessiner(self, dessin_temporaire = False, rafraichir_axes = False):
        # Affichage bloqué
        if self.canvas.affichage_gele:
            return
##        print 'rafraichissement'

        # Quand un objet est déplacé, on optimise en ne redessinant
        # que l'objet, et ce qui en dépend.
        objet_deplace = self.canvas.feuille_actuelle._objet_deplace

        if dessin_temporaire:
            self._restaurer(self._dernier_dessin)
        else:
            self._objets_fixes, self._objets_mobiles = self.canvas.feuille_actuelle.lister_figures()
            if objet_deplace is None or rafraichir_axes:
                # Cas par défaut : on dessine tout.
                self._creer_arriere_plan(rafraichir_axes)
                self._ajouter_objets(self._objets_fixes)
            elif objet_deplace is self._dernier_objet_deplace:
                # Un cache spécifique existe déjà : on l'utilise.
##                print "Optimisation."
                self._restaurer(self._mise_en_cache_objets_fixes)
            else:
                # On génère un cache (spécifique à l'objet déplacé).
                self._creer_arriere_plan(rafraichir_axes)
                dict_artistes = self._effacer_artistes()
                self._ajouter_objets(self._objets_fixes)
                self._dessiner_artistes()
                self._mise_en_cache_objets_fixes = self._en_cache()
                self._restaurer_artistes(dict_artistes)
            self._ajouter_objets(self._objets_mobiles)
            # Résumé du bloc de code précédent :
            # Les objets fixes ne doivent pas être redessinés,
            # mais restitués à partir d'un cache.
            # On génère le cache la première fois que objet_deplace is not None
            # et on utilise le cache les autres fois si objet_deplace
            # correspond toujours au même objet.

        # On dessine dans le buffer
        self._dessiner_artistes()
        # Affichage proprement dit (copie du buffer à l'écran)
        self.canvas.blit()
        # Garde en mémoire l'affichage pour éviter de redessiner la fenêtre
        # dans certains cas (fenêtre masquée, etc.)
        if not dessin_temporaire:
            self._dernier_dessin = self._en_cache()
        self._dernier_objet_deplace = objet_deplace
        self.canvas.feuille_actuelle._objet_deplace = None
        # _artistes_dessin sert pour le débogage
        self._artistes_dessin = self._effacer_artistes()


    def exporter(self, nom, dpi = None, zone = None):
        u"Exporter la figure."
        # On est obligé de reréférencer tous les artistes.
        dict_artistes = self._effacer_artistes()
        self.canvas.parent._affiche()
        self._dessine_axes()
        artistes = self.canvas.feuille_actuelle.lister_figures()[0]
        self.axes.artists.extend(artistes)
        # Export proprement dit:
        if zone is None:
            with ZoomArtistes(self.axes, self.zoom_texte, self.zoom_ligne):
                self.canvas.figure.savefig(nom, dpi = dpi)
        else:
            # TODO: À réécrire en utilisant les nouveautés de matplotlib 0.99
            x0, x1, y0, y1 = zone
            pixelx0, pixely0 = self.coo2pix(x0, y0)
            pixelx1, pixely1 = self.coo2pix(x1, y1)
            l, h = self.GetSize()
            pixelx0, pixelx1 = max(0, min(pixelx0, pixelx1)), min(l, max(pixelx0, pixelx1))
            pixely0, pixely1 = max(0, min(pixely0, pixely1)), min(h, max(pixely0, pixely1))
            if pixelx1 - pixelx0 < 1 or pixely1 - pixely0 < 1:
                raise RuntimeError, "Erreur: image de dimensions nulles !"
            buffer = self.get_renderer().buffer_rgba(0,0)
            # Note: len(buffer) == l*h*4 (Rouge/Vert/Bleu/Alpha)
            matrice = numpy.fromstring(buffer, dtype=numpy.ubyte).reshape(h, l, 4)[pixely0:pixely1, pixelx0:pixelx1]
            im = frombyte(matrice, 0)
            im.resize(pixelx1 - pixelx0, pixely1 - pixely0)
            im.write_png(nom)

        # On remet la liste des artistes dans son état d'origine (vide en général)
        self._restaurer_artistes(dict_artistes)


    def restaurer_dessin(self):
        u'''Restaure le dernier dessin.'''
        self._restaurer(self._dernier_dessin)
        self.canvas.blit()


    def _dico_artistes(self):
        dico = {}
        for rubrique in ("lines", "patches", "texts", "tables", "artists", "images", "collections"):
            dico[rubrique] = getattr(self.axes, rubrique)
        return dico

    def _effacer_artistes(self):
        u"Supprime tous les artistes (objets graphiques de matplotlib)."
        dico = self._dico_artistes()
        self.axes.artists = []
        self.axes.lines = []
        self.axes.patches = []
        self.axes.texts = []
        self.axes.tables = []
        self.axes.images = []
        self.axes.collections = []  # collection.Collection instances
        return dico


    def _restaurer_artistes(self, dictionnaire):
        u"Restaure les artistes précédemment supprimés."
        dico = self._dico_artistes()
        self.axes.__dict__.update(dictionnaire)
        return dico


    def _dessiner_artistes(self):
        with ZoomArtistes(self.axes, self.zoom_texte, self.zoom_ligne) as artistes:
            artistes.sort(key = operator.attrgetter('zorder'))
            for artiste in artistes:
                if artiste._visible:
                    try:
                        self.axes.draw_artist(artiste)
                    except:
                        print_error()

    def _en_cache(self):
        return self.canvas.copy_from_bbox(self.axes.bbox)

    def _restaurer(self, buffer):
        if buffer is not None:
            self.canvas.restore_region(buffer)


##    def ajouter(self, *artistes):
##        self._queue.extend(artistes)



    def _dessine_axe(self, num = 0, nbr_axes = 1):
        u"Dessine l'axe des abscisses (num = 0) ou des ordonnées (num = 1)."

        #un = nbr_axes == 1              # un seul axe
        #x = num == 0; y = not x         # axe des abscisses / des ordonnees
        #rep = self.utiliser_repere      # repere ou non
        vect = self.canvas.utiliser_repere and self.canvas.repere[1 + num] in string.ascii_lowercase
        # repere defini ou non par des vecteurs
        correspondance = {"b" : "bottom", "c": "center", "l": "left", "r": "right", "t": "top"}

        def f(a, b):
            return a if num == 0 else b

        signe = f(-1, 1)
        origine = self.canvas.origine_axes[1 - num]
        fenetre = self.canvas.fenetre
        repere = self.canvas.repere

        # On cree l'axe proprement dit:
        self.ajouter_ligne(f([fenetre[0], fenetre[1]], [origine, origine]), f([origine, origine], [fenetre[2], fenetre[3]]), "k")

        if self.canvas.afficher_fleches:
            # On cree la fleche au bout de l'axe:
            x, y = self.canvas.coo2pix(f(fenetre[1], origine), f(origine, fenetre[3]))
            dx, dy = .5*self.canvas.taille[">"]*self.zoom_ligne*pylab.array([f(math.sqrt(3), 1), f(1, math.sqrt(3))])
            self.ajouter_ligne([x - dx, x, x + signe*dx], [y + dy, y, y + signe*dy], "k",  pixel = True)


        # FORMAT (_origine_ et _graduation_) : [texte, x, y, alignement horizontal, alignement vertical]
        # _origine_ : infos sur l'origine de l'axe
        # _graduation_ : infos sur le 2eme point de l'axe

        ox, oy = self.canvas.origine_axes
        ux, uy = self.canvas.gradu
        # TODO: à étendre à un repère non orthogonal
        kx, ky = self.canvas.dpix2coo(1, 1)

        x, y = self.canvas.coo2pix(f(ox + ux, ox), f(oy, oy + uy))
        if self.canvas.gradu[num] and self.canvas.saturation(num) < param.saturation:
            if vect:
                self.ajouter_ligne([x - dx, x, x + signe*dx], [y + dy, y, y + signe*dy], "k", pixel = True)
            elif self.canvas.afficher_quadrillage:
                d = .5*self.canvas.taille["|"]*self.zoom_ligne
                self.ajouter_ligne([f(x, x - d), f(x, x + d)], [f(y - d, y), f(y + d, y)], "k", pixel = True)


        if nbr_axes == 1:                   # un seul axe
            if num:                         # axe des ordonnees
                if self.canvas.utiliser_repere:    # utiliser un repere (meme origine sur chaque axe...)
                    _origine_ = [repere[0], ox - 8*kx, oy, "r", "c"]
                    if vect:                # reperage par des vecteurs
                        _graduation_ = [u"$\\tt{\\vec{" + repere[2] + "}}$", ox - 8*kx, oy + .5*uy, "r", "c"]
                    else:                   # reperage par des points
                        _graduation_ = [repere[2], ox - 8*kx, oy + uy, "r", "c"]
                else:                       # pas un repere
                        _origine_ = [str(oy), ox - 8*kx, oy, "r", "c"]
                        _graduation_ = [str(oy + uy), ox - 8*kx, oy + uy, "r", "c"]
            else:                           # axe des abscisses
                if self.canvas.utiliser_repere:    # utiliser un repere (meme origine sur chaque axe...)
                    _origine_ = [repere[0], ox, oy - 8*ky, "c", "t"]
                    if vect:                # reperage par des vecteurs
                        _graduation_ = [u"$\\tt{\\vec{" + repere[1] + "}}$", ox + .5*ux, oy - 8*ky, "c", "t"]
                    else:                   # reperage par des points
                        _graduation_ = [repere[1], ox + ux, oy - 8*ky, "c", "t"]
                else:                       # pas un repere
                        _origine_ = [str(ox), ox, oy - 8*ky, "c", "t"]
                        _graduation_ = [str(ox + ux), ox + ux, oy - 8*ky, "c", "t"]
        else:                               # 2 axes
            if num:                         # axe des ordonnees
                if self.canvas.utiliser_repere:
                    _origine_ = None
                    if vect:                # reperage par des vecteurs
                        _graduation_ = ["\\vec{" + repere[2] + "}", ox - 8*kx, oy + .5*uy, "r", "c"]
                    else:                   # reperage par des points
                        _graduation_ = [repere[2], ox - 8*kx, oy + uy, "r", "c"]
                else:                       # pas un repere
                        _origine_ = [str(oy), ox - 8*kx, oy + 3*ky, "r", "b"]
                        _graduation_ = [str(oy + uy), ox - 8*kx, oy + uy, "r", "b"]
            else:                           # axe des abscisses
                if self.canvas.utiliser_repere:
                    _origine_ = [repere[0], ox - 8*kx, oy - 8*ky, "r", "t"]
                    if vect:                # reperage par des vecteurs
                        _graduation_ = ["\\vec{" + repere[1] + "}", ox + .5*ux, oy - 8*ky, "c", "t"]
                    else:                   # reperage par des points
                        _graduation_ = [repere[1], ox + ux, oy - 8*ky, "c", "t"]
                else:                       # pas un repere
                        _origine_ = [str(ox), ox + 3*kx, oy - 8*ky, "l", "t"]
                        _graduation_ = [str(ox + ux), ox + ux, oy - 8*ky, "l", "t"]

        if _origine_ and self.canvas.gradu[num]:
            self.ajouter_texte(_origine_[1], _origine_[2], "$" + _origine_[0] + "$", \
                horizontalalignment = correspondance[_origine_[3]], \
                verticalalignment = correspondance[_origine_[4]], size = 14)

        if _graduation_ and self.canvas.gradu[num] and self.canvas.saturation(num) < param.saturation:
            self.ajouter_texte(_graduation_[1], _graduation_[2], "$" + _graduation_[0] + "$", \
                horizontalalignment = correspondance[_graduation_[3]], \
                verticalalignment = correspondance[_graduation_[4]], size = 14)






    def _quadriller(self, hauteur = 3, pas = (None, None), style = None, epaisseur = 1, couleur = "k"):
        u"""Crée des graduations ou un quadrillage (si hauteur = None).
        Si pas[0] = 0 (respectivement, pas[1] = 0), les graduations seront
        uniquement horizontales (respectivement verticales)."""

        args = []
        origine = self.canvas.origine_axes
        fenetre = self.canvas.fenetre
        pas = list(pas)
        # Je n'utilise pas des listes directement, mais des tuples.
        # Ceci afin de n'avoir jamais la MEME liste.

        if hauteur == None: # quadrillage
            xmin, xmax, ymin, ymax = fenetre
            _min = ymin, xmin ; _max = ymax, xmax
            if not style:
                style = ":"
        else:               # graduations
##            _min = [origine[i] - hauteur*self.canvas.coeff(i) for i in (1, 0)] # ymin, xmin
##            _max = [origine[i] + hauteur*self.canvas.coeff(i) for i in (1, 0)] # ymax, xmax
            # ymin, xmin:
            _min = [origine[1] - self.canvas.dpix2coo(0, hauteur)[1],
                    origine[0] - self.canvas.dpix2coo(hauteur, 0)[0]]
            # ymax, xmax:
            _max = [origine[1] + self.canvas.dpix2coo(0, hauteur)[1],
                    origine[0] + self.canvas.dpix2coo(hauteur, 0)[0]]
            if not style:
                style = "-"

##        #type_ligne = couleur + style

        # Format de 'segments': [[(x0,y0), (x1,y1), ...], [...], [...], ...]
        # cf. matplotlib.collections.LineCollection
        segments = []

        for n in (0, 1):
            if pas[n] == None:  pas[n] = self.canvas.gradu[n]
            pas[n] = 1.*abs(pas[n])

            # graduations sur l'axe:
            if pas[n] and self.canvas.coeff(n)/pas[n] < param.saturation:

                maxi = fenetre[2*n + 1]
                if self.canvas.afficher_axes and (n in self.canvas.liste_axes) and hauteur:
                    # les graduations qui chevauchent les fleches, c'est pas joli
                    maxi -= .7*self.canvas.taille[">"]*self.zoom_ligne*math.sqrt(3)*self.canvas.coeff(n)

                valeurs = pylab.concatenate((pylab.arange(origine[n], fenetre[2*n], -pas[n]), pylab.arange(origine[n] + pas[n], maxi, pas[n])))
                for val in valeurs:
                    # si on affiche le quadrillage ou les graduations sur les axes, ca fait pas net:
                    if val != origine[n] or not self.canvas.afficher_axes or not (1 - n in self.canvas.liste_axes):
                        if n:
                            segments.append([(_min[n], val), (_max[n], val)])
                        else:
                            segments.append([(val, _min[n]), (val, _max[n])])


##        m = len(segments)
##        width = m*[epaisseur]
##        color = m*[matplotlib.colors.colorConverter.to_rgba(couleur)]
        couleur = matplotlib.colors.colorConverter.to_rgba(couleur)
        style_dict = {':': 'dotted', '--': 'dashed', '-.': 'dashdot', '-': 'solid'}
        style = style_dict.get(style, style)
        collection = matplotlib.collections.LineCollection(segments, linewidths = epaisseur, colors = couleur,  linestyle = style)
        self.axes.add_collection(collection)




    def _dessine_axes(self):
            if self.canvas.afficher_axes:
                for axe in self.canvas.liste_axes:
                    self._dessine_axe(axe, len(self.canvas.liste_axes))

            if self.canvas.afficher_quadrillage:   # quadrillage
                for quadrillage in self.canvas.quadrillages:
                    self._quadriller(None, *quadrillage)
            elif self.canvas.afficher_axes:
                self._quadriller()   # simples graduations


    def _info_artiste(self, artiste):
        u"""Retourne des informations permettant éventuellement de
        repérer visuellement l'artiste."""
        infos = {}
        infos['parent'] = getattr(getattr(artiste, '_cree_par', None), 'info', None)
        if isinstance(artiste, matplotlib.text.Text):
            infos['text'] = artiste.get_text()
            infos['color'] = artiste.get_color()
            infos['size'] = artiste.get_size()
        elif isinstance(artiste, matplotlib.lines.Line2D):
            infos['xy'] = zip(artiste.get_xdata(), artiste.get_ydata())
            if len(infos['xy']) > 1:
                infos['color'] = artiste.get_color()
            else:
                infos['edge-color'] = artiste.get_markeredgecolor()
                infos['face-color'] = artiste.get_markerfacecolor()
        elif isinstance(artiste, matplotlib.collections.LineCollection):
            infos['color'] = artiste.get_color()
        elif isinstance(artiste, matplotlib.patches.Polygon):
            infos['xy'] = artiste.get_verts()
            infos['edge-color'] = artiste.get_edgecolor()
            infos['face-color'] = artiste.get_edgecolor()
        infos_as_str = ', '.join([key + '=' + unicode2(val) for key, val in infos.iteritems()])
        return artiste.__class__.__name__ + ' (' + str(id(artiste)) + '):\n' + infos_as_str

    def infos(self):
        u"Informations utiles pour le débogage."
        print "---------------"
        print("+ Repere")
        for rubrique in self._artistes_repere:
            print " -> " + rubrique
            for artiste in self._artistes_repere[rubrique]:
                print '  * ' + self._info_artiste(artiste)
        print("+ Objet deplace ?")
        print getattr(self._dernier_objet_deplace, 'info', 'None')
        print "+ Objets fixes:"
        for artiste in self._objets_fixes:
            print '  * ' + self._info_artiste(artiste)
        print "+ Objets mobiles:"
        for artiste in self._objets_mobiles:
            print '  * ' + self._info_artiste(artiste)
        print "+ Autres artistes:"
        for rubrique in self._artistes_dessin:
            print " -> " + rubrique
            for artiste in self._artistes_dessin[rubrique]:
                if not is_in(artiste, self._objets_mobiles) \
                        and not is_in(artiste, self._objets_fixes)\
                        and not any(is_in(artiste, liste) for liste in self._artistes_repere.itervalues()):
                    print '  * ' + self._info_artiste(artiste)
        print "---------------"
