#!/usr/bin/env python
# -*- coding: latin-1 -*-

# MandelbrotSetTk.py
# (c) Olivier Pirson --- DragonSoft
# http://www.opimedia.be/DS/
# Débuté le 9 août 2009
# v.01.00 --- 10 août 2009
# v.01.10 --- 13 août 2009 : supprimé le fichier temporaire
# v.01.11 --- 17 août 2009 : calc_state corrigé
# v.01.12 --- 17 août 2009 : sélection par bouton droit
# v.01.13 --- 27 septembre 2009 : nouveau site web
# v.01.14 --- 15 mars 2010 : nouveau site web et % remplacé par .format()
#         --- 2 janvier 2012 : nouveau site web
##########################################################################
VERSION = 'v.01.14 --- 2012 January 2'

import array
import sys
import time

import tkinter
import tkinter.filedialog
import tkinter.messagebox
import tkinter.ttk

import tkinter.tix

if __debug__:
    assert sys.version_info[0] >= 3, ('Require Python 3 or better', sys.version)



#############
# Fonctions #
#############
def fC(C, z):
    """Renvoie z**2 + C"""
    return z**2 + C


def help():
    """Message d'aide envoyé sur la sortie des erreurs"""
    print("""Mandelbrot Set Tk
  Little application Python/Tk to play with Mandelbrot and Julia sets.

{0}
(c) Olivier Pirson --- DragonSoft
http://www.opimedia.be/DS/""".format(VERSION),
          file=sys.stderr)
    exit()


def imag_to_str(y, k=3):
    """Renvoie y sous forme de string formatée"""
    y = round(y*(10**k))
    if y%(10**k) == 0:
        y //= (10**k)
    else:
        y /= (10**k)

    if y > 0:
        return '+ i {0}'.format(real_to_str(y, k=k) if y != 1
                                else '')
    elif y < 0:
        return '- i {0}'.format(real_to_str(-y, k=k) if y != -1
                                else '')
    else:
        return ''


def JuliaSet(C, z0, z1, max_ite, black):
    """Calcule et affiche une image de taille WIDTH x HEIGHT
    de l'ensemble de Julia pour la constante C
    pre: C: constante complexe pour cet ensemble de Julia
         z0: coordonnée complexe du coin supérieur gauche
         z1: coordonnée complexe du coin inférieur droit
         max_ite: nombre maximum d'itérations (pour chaque point),
         black: noir&blanc, sinon en niveau gris"""
    # Coefficients pour calculer les coordonnées correspondant aux pixels
    COEF_z_real = (z1.real - z0.real)/(WIDTH - 1)
    COEF_z_imag = (z0.imag - z1.imag)/(HEIGHT - 1)

    # Calcule chaque pixel de l'image, ligne par ligne
    for y in range(HEIGHT):
        # Affiche l'état d'avancement du calcul
        ttk_Progressbar.config(value=HEIGHT - 1 - y)
        ttk_Progressbar.update()

        line = []  # ligne de pixels

        z_imag = z0.imag - COEF_z_imag*y
        for x in range(WIDTH):
            z = complex(z0.real + COEF_z_real*x,
                        z_imag)

            nb_ite = 0
            while (max_ite > nb_ite) and (2.0 >= abs(z)):
                z = z**2 + C
                nb_ite += 1

            color = (0 if 2 >= abs(z)
                     else (255 if black
                           else 255 - nb_ite*254//max_ite))
            line.append(('{0:02x}'.format(color))*3)  # composantes rouge, vert, bleue

            # Ajoute aussi le pixel
            #   dans le tableau array_PPM pour pouvoir sauver le fichier
            array_PPM.extend((color, color, color))  # rouge, vert, bleue

            if not calc_state:  # interrompt le calcul
                break

        # Affiche la ligne de pixels
        tk_PhotoImage.put(data='{#' + ' #'.join(line) + '}', to=(0, y))

        if not calc_state:  # interrompt le calcul
            break


def MandelbrotSet(z0, z1, max_ite, black):
    """Calcule et affiche une image de taille WIDTH x HEIGHT
    de l'ensemble de Mandelbrot
    pre: z0: coordonnée complexe du coin supérieur gauche
         z1: coordonnée complexe du coin inférieur droit
         max_ite: nombre maximum d'itérations (pour chaque point),
         black: noir&blanc, sinon en niveau gris"""
    # Coefficients pour calculer les coordonnées correspondant aux pixels
    COEF_C_real = (z1.real - z0.real)/(WIDTH - 1)
    COEF_C_imag = (z0.imag - z1.imag)/(HEIGHT - 1)

    # Calcule chaque pixel de l'image, ligne par ligne
    for y in range(HEIGHT):
        # Affiche l'état d'avancement du calcul
        ttk_Progressbar.config(value=HEIGHT - 1 - y)
        ttk_Progressbar.update()

        line = []  # ligne de pixels

        C_imag = z0.imag - COEF_C_imag*y
        for x in range(WIDTH):
            C = complex(z0.real + COEF_C_real*x,
                        C_imag)

            z = C
            nb_ite = 0
            while (max_ite > nb_ite) and (2.0 >= abs(z)):
                z = z**2 + C
                nb_ite += 1

            color = (0 if 2 >= abs(z)
                     else (255 if black
                           else 255 - nb_ite*254//max_ite))
            line.append(('{0:02x}'.format(color))*3)  # composantes rouge, vert, bleue

            # Ajoute aussi le pixel
            #   dans le tableau array_PPM pour pouvoir sauver le fichier
            array_PPM.extend((color, color, color))  # rouge, vert, bleue

            if not calc_state:  # interrompt le calcul
                break

        # Affiche la ligne de pixels
        tk_PhotoImage.put(data='{#' + ' #'.join(line) + '}', to=(0, y))

        if not calc_state:  # interrompt le calcul
            break


def pixel_x_to_z_real(x):
    """Renvoie la coordonnée réelle du point complexe
    correspondant au pixel d'abscisse x :
                    z1 - z0
    z_real = z0 + ----------- * x
                   WIDTH - 1"""
    return tk_Scale_z0_real.get() + (tk_Scale_z1_real.get()
                                     - tk_Scale_z0_real.get())/(WIDTH - 1)*x


def pixel_y_to_z_imag(y):
    """Renvoie la coordonnée imaginaire du point complexe
    correspondant au pixel d'ordonnée y :
                    z0 - z1
    z_imag = z0 - ------------ * y
                   HEIGHT - 1"""
    return tk_Scale_z0_imag.get() - (tk_Scale_z0_imag.get()
                                     - tk_Scale_z1_imag.get())/(HEIGHT - 1)*y


def real_to_str(x, k=3):
    """Renvoie x sous forme de string formatée"""
    x = round(x*(10**k))
    if x%(10**k) == 0:
        x //= (10**k)
    else:
        x /= (10**k)
    return str(x)


def z_imag_to_pixel_y(z_imag):
    """Renvoie l'ordonnée du pixel
    correspondant à la coordonnée imaginaire du point complexe z_imag
         z0 - z_imag
    y = ------------- * (HEIGHT - 1) arrondi à l'entier le plus proche
           z0 - z1"""
    return round((tk_Scale_z0_imag.get() - z_imag)
                 / (tk_Scale_z0_imag.get() - tk_Scale_z1_imag.get())
                 * (HEIGHT - 1))


def z_real_to_pixel_x(z_real):
    """Renvoie l'abscisse du pixel
    correspondant à la coordonnée réelle du point complexe z_real
         z_real - z0
    x = ------------- * (WIDTH - 1) arrondi à l'entier le plus proche
           z1 - z0"""
    return round((z_real - tk_Scale_z0_real.get())
                 / (tk_Scale_z1_real.get() - tk_Scale_z0_real.get())
                 * (WIDTH - 1))



##############################
# Fonctions pour l'interface #
##############################
def cmd_about():
    """MessageBox About..."""
    tkinter.messagebox.showinfo('About' + chr(8230),
                                """Mandelbrot Set Tk

{0}
(c) Olivier Pirson --- DragonSoft
http://www.opimedia.be/DS/


Infos:
Python {1}""".format(VERSION, sys.version))


def cmd_axis():
    """Ajoute ou enlève les axes et cercles superposés sur l'image"""
    tk_Canvas.delete('axis_imag')
    tk_Canvas.delete('axis_real')
    tk_Canvas.delete('axis_circle_2')
    tk_Canvas.delete('axis_circle_1')
    tk_Canvas.delete('axis_circle_.25')
    if tk_Var_axis.get():
        # Cercles
        tk_Canvas.create_oval(z_real_to_pixel_x(-.25), z_imag_to_pixel_y(-.25),
                              z_real_to_pixel_x(.25), z_imag_to_pixel_y(.25),
                              tags='axis_circle_.25', outline='green')
        tk_Canvas.create_oval(z_real_to_pixel_x(-1), z_imag_to_pixel_y(-1),
                              z_real_to_pixel_x(1), z_imag_to_pixel_y(1),
                              tags='axis_circle_1', outline='red')
        tk_Canvas.create_oval(z_real_to_pixel_x(-2), z_imag_to_pixel_y(-2),
                              z_real_to_pixel_x(2), z_imag_to_pixel_y(2),
                              tags='axis_circle_2', outline='green')

        # Axes
        y = z_imag_to_pixel_y(0)
        if 0 <= y < HEIGHT:
            tk_Canvas.create_line(0, y, WIDTH - 1, y, tags='axis_real',
                                  fill='blue')
        x = z_real_to_pixel_x(0)
        if 0 <= x < WIDTH:
            tk_Canvas.create_line(x, 0, x, HEIGHT - 1, tags='axis_imag',
                                  fill='blue')


def cmd_C_imag(event):
    """Actualise et vérifie la valeur imaginaire de C"""
    tk_Label_C_imag.config(text=imag_to_str(tk_Scale_C_imag.get()))
    cmd_fC_k()
    cmd_axis()


def cmd_C_real(event):
    """Actualise et vérifie la valeur réelle de C"""
    tk_Label_C_real.config(text='C = f_C(0) = {0}'.format(real_to_str(tk_Scale_C_real.get())))
    cmd_fC_k()
    cmd_axis()


def cmd_calc():
    """Calcule et affiche l'image.
    Si un calcul est déjà en cours alors l'arrête"""
    global array_PPM, calc_state

    if calc_state:  # si déjà un calcul en cours alors l'arrête
        calc_state = False
        return

    calc_state = True
    tk_Button_calc.config(relief=tkinter.SUNKEN)

    array_PPM = array.array('B')  # réinitialise le tableau

    array_PPM.fromstring("""P6
# Mandelbrot Set Tk
# {0}
# (c) Olivier Pirson --- DragonSoft
# http://www.opimedia.be/DS/
# {1}: {2} {3} {4} {5}
# C: {6} {7}
# max_ite: {8}
# black: {9}
# axis: {10}
{11} {12}
255
""".format(VERSION,
           'Julia' if tk_Var_Julia.get() else 'Mandelbrot',
           tk_Scale_z0_real.get(), tk_Scale_z0_imag.get(),
           tk_Scale_z1_real.get(), tk_Scale_z1_imag.get(),
           tk_Scale_C_real.get(), tk_Scale_C_imag.get(),
           tk_Scale_max_ite.get(), tk_Var_black.get(), tk_Var_axis.get(),
           WIDTH, HEIGHT))

    # Calcul et affiche l'image
    t = time.clock()
    if tk_Var_Julia.get():
        JuliaSet(C=complex(tk_Scale_C_real.get(),
                           tk_Scale_C_imag.get()),
                 z0=complex(tk_Scale_z0_real.get(),
                            tk_Scale_z0_imag.get()),
                 z1=complex(tk_Scale_z1_real.get(),
                            tk_Scale_z1_imag.get()),
                 max_ite=tk_Scale_max_ite.get(),
                 black=tk_Var_black.get())
    else:
        MandelbrotSet(z0=complex(tk_Scale_z0_real.get(),
                                 tk_Scale_z0_imag.get()),
                      z1=complex(tk_Scale_z1_real.get(),
                                 tk_Scale_z1_imag.get()),
                      max_ite=tk_Scale_max_ite.get(),
                      black=tk_Var_black.get())
    tk_Label_infos.config(text='Time of last calculation: {0:.1f} seconds'.format(time.clock() - t))

    tk_Button_calc.config(relief=tkinter.RAISED)
    calc_state = False


def cmd_coords(event):
    """Affiche les coordonnées du point sous le pointeur de la souris"""
    x = event.x
    y = event.y
    if (x < 0) or (y < 0) or (x >= WIDTH) or (y >= HEIGHT):
        tk_Label_coord_real.config(text='')
        tk_Label_coord_imag.config(text='')
    else:
        tk_Label_coord_real.config(text='Mouse: {0}'.format(real_to_str(pixel_x_to_z_real(x))))
        tk_Label_coord_imag.config(text=imag_to_str(pixel_y_to_z_imag(y)))


def cmd_coords_to_C(event):
    """Fixe la valeur de C à partir du point sous la souris"""
    tk_Scale_C_real.set(pixel_x_to_z_real(event.x))
    tk_Scale_C_imag.set(pixel_y_to_z_imag(event.y))


def cmd_default(force=False):
    """Réinitialise les paramètres à leur valeur par défaut
    Si not force
    alors ouvre une boîte de dialogue pour demander une confirmation"""
    if force or tkinter.messagebox.askyesno('Default?',
                                            'Assign to default values?'):
        tk_Scale_z0_real.set(-2.0)
        tk_Scale_z0_imag.set(2.0)

        tk_Scale_z1_real.set(2.0)
        tk_Scale_z1_imag.set(-2.0)

        tk_Scale_C_real.set(-1.0)
        tk_Scale_C_imag.set(.0)

        tk_Scale_max_ite.set(50)

        tk_Var_axis.set(True)
        tk_Var_black.set(False)
        tk_Var_Julia.set(False)
        tk_Var_ratio.set(False)

        cmd_axis()


def cmd_fC_k():
    """Actualise les valeurs des premières itérées de C"""
    C = complex(tk_Scale_C_real.get(), tk_Scale_C_imag.get())
    z = C
    for i in range(4):
        z = fC(C, z)
        tk_Label_fC_real[i].config(text='f_C^{0}(0) = {1}'.format(i + 2, real_to_str(z.real)))
        tk_Label_fC_imag[i].config(text=imag_to_str(z.imag))


def cmd_load():
    """Charge une image au format PPM binary avec ses paramètres et l'affiche.
    Si un problème est rencontré alors exécute cmd_default(force=True)"""
    global tk_PhotoImage

    filename = tkinter.filedialog.askopenfilename(filetypes
                                                  =(('Portable Pixelmap',
                                                     '*.ppm'),
                                                    ('All files', '*')),
                                                  defaultextension='.ppm',
                                                  title='Load')
    if filename == '':
        return

    # Lecture de l'en-tête avec les paramètres
    try:
        f = open(filename)
    except:
        cmd_default(force=True)
        print("Load file '{0}' failed!".format(filename), file=sys.stderr)
        sys.stderr.flush()
        tix_Win.bell()
    else:
        l = f.readline()
        if l != 'P6\n':  # le fichier n'est pas un fichier PPM binary valide
            cmd_default(force=True)
            print("Load file '{0}' failed!".format(filename), file=sys.stderr)
            sys.stderr.flush()
            tix_Win.bell()
            return

        # Lecture des paramètres
        for l in f:
            if l[:2] != '# ':
                break
            l = l[2:].split(' ')
            if l[0] == 'Mandelbrot:':
                tk_Var_Julia.set(False)
                tk_Scale_z0_real.set(float(l[1]))
                tk_Scale_z0_imag.set(float(l[2]))
                tk_Scale_z1_real.set(float(l[3]))
                tk_Scale_z1_imag.set(float(l[4]))
            elif l[0] == 'Julia:':
                tk_Var_Julia.set(True)
                tk_Scale_z0_real.set(float(l[1]))
                tk_Scale_z0_imag.set(float(l[2]))
                tk_Scale_z1_real.set(float(l[3]))
                tk_Scale_z1_imag.set(float(l[4]))
            elif l[0] == 'C:':
                tk_Scale_C_real.set(float(l[1]))
                tk_Scale_C_imag.set(float(l[2]))
            elif l[0] == 'max_ite:':
                tk_Scale_max_ite.set(int(l[1]))
            elif l[0] == 'axis:':
                tk_Var_axis.set(int(l[1]))
            elif l[0] == 'black:':
                tk_Var_black.set(int(l[1]))
        f.close()

        # Lit l'image et l'affiche
        try:
            tk_PhotoImage = tkinter.PhotoImage(file=filename)
        except:
            cmd_default(force=True)
            print("Load file '{0}' failed!".format(filename), file=sys.stderr)
            sys.stderr.flush()
            tix_Win.bell()
        else:
            tk_Canvas.create_image(0, 0, anchor=tkinter.NW, image=tk_PhotoImage)
            cmd_axis()


def cmd_max_ite(event):
    """Actualise la valeur max_ite"""
    tk_Label_max_ite.config(text='Maximum of iterations (for all point): {0}'
                            .format(tk_Scale_max_ite.get()))


def cmd_quit():
    """Callback pour le Button 'Quit'"""
    global calc_state

    if tkinter.messagebox.askyesno('Quit?', 'Quit Mandelbrot Set Tk?'):
        calc_state = False
        tix_Win.quit()


def cmd_ratio_modif_imag():
    """Si l'option ratio est activée
    alors modifie les paramètres imaginaires pour corriger le ratio"""
    if tk_Var_ratio.get():
        d_real = tk_Scale_z1_real.get() - tk_Scale_z0_real.get()  # largeur
        d_imag = tk_Scale_z0_imag.get() - tk_Scale_z1_imag.get()  # hauteur

        tk_Scale_z1_imag.set(tk_Scale_z1_imag.get() + (d_imag - d_real)/2)
        tk_Scale_z0_imag.set(tk_Scale_z1_imag.get() + d_real)
    cmd_axis()


def cmd_ratio_modif_real():
    """Si l'option ratio est activée
    alors modifie les paramètres réels pour corriger le ratio"""
    if tk_Var_ratio.get():
        d_real = tk_Scale_z1_real.get() - tk_Scale_z0_real.get()  # largeur
        d_imag = tk_Scale_z0_imag.get() - tk_Scale_z1_imag.get()  # hauteur

        tk_Scale_z0_real.set(tk_Scale_z0_real.get() + (d_real - d_imag)/2)
        tk_Scale_z1_real.set(tk_Scale_z0_real.get() + d_imag)
    cmd_axis()


def cmd_save_as():
    """Sauve la dernière image calculée dans un fichier à choisir
    au format PPM binary"""
    filename = tkinter.filedialog.asksaveasfilename(filetypes
                                                    =(('Portable Pixelmap',
                                                       '*.ppm'),
                                                      ('All files', '*')),
                                                    defaultextension='.ppm',
                                                    title='Save As' + chr(8230))
    if filename == '':
        return

    try:
        f = open(filename, mode='wb')
    except:
        print("Save file '{0}' failed!".format(filename), file=sys.stderr)
        sys.stderr.flush()
        tix_Win.bell()
    else:
        f.write(array_PPM)
        f.close()


def cmd_select_zone(event):
    """Sélectionne la zone d'affichage et enlève le cadre"""
    global zone0, zone1

    if zone0 != None:
        z0_real = round(pixel_x_to_z_real(zone0[0])*100)/100
        z0_imag = round(pixel_y_to_z_imag(zone0[1])*100)/100
        z1_real = round(pixel_x_to_z_real(zone1[0])*100)/100
        z1_imag = round(pixel_y_to_z_imag(zone1[1])*100)/100

        if (z0_real != z1_real) and (z0_imag != z1_imag):
            if z0_real > z1_real:
                z0_real, z1_real = z1_real, z0_real
            if z0_imag < z1_imag:
                z0_imag, z1_imag = z1_imag, z0_imag

            tk_Scale_z0_real.set(z0_real)
            tk_Scale_z0_imag.set(z0_imag)
            tk_Scale_z1_real.set(z1_real)
            tk_Scale_z1_imag.set(z1_imag)

    tk_Canvas.delete('zone_frame')
    zone0 = None
    zone1 = None


def cmd_select_zone_move(event):
    """Affiche un cadre pour sélectionner une zone d'affichage"""
    global zone0, zone1

    cmd_coords(event)

    if zone0 == None:  # fixe le premier coin
        zone0 = (event.x, event.y)
        zone1 = zone0
    else:                 # affiche le cadre
        if tk_Var_ratio.get():
            y = abs(event.x - zone0[0])
            if zone0[1] >= event.y:
                y = zone0[1] - y
            else:
                y += zone0[1]
        else:
            y = event.y
        zone1 = (event.x, y)
        tk_Canvas.delete('zone_frame')
        tk_Canvas.create_line(zone0[0], zone0[1],
                              zone1[0], zone0[1],
                              zone1[0], zone1[1],
                              zone0[0], zone1[1],
                              zone0[0], zone0[1],
                              tags='zone_frame', fill='red')


def cmd_z0_imag(event):
    """Actualise et vérifie la valeur imaginaire de z0"""
    tk_Label_z0_imag.config(text=imag_to_str(tk_Scale_z0_imag.get()))
    if tk_Scale_z0_imag.get() <= tk_Scale_z1_imag.get():
        tk_Scale_z1_imag.set(tk_Scale_z0_imag.get() - SCALE_RESOLUTION)
    cmd_ratio_modif_real()


def cmd_z0_real(event):
    """Actualise et vérifie la valeur réelle de z0"""
    tk_Label_z0_real.config(text='z0 = {0}'.format(real_to_str(tk_Scale_z0_real.get())))
    if tk_Scale_z0_real.get() >= tk_Scale_z1_real.get():
        tk_Scale_z1_real.set(tk_Scale_z0_real.get() + SCALE_RESOLUTION)
    cmd_ratio_modif_imag()


def cmd_z1_imag(event):
    """Actualise et vérifie la valeur imaginaire de z1"""
    tk_Label_z1_imag.config(text=imag_to_str(tk_Scale_z1_imag.get()))
    if tk_Scale_z0_imag.get() <= tk_Scale_z1_imag.get():
        tk_Scale_z0_imag.set(tk_Scale_z1_imag.get() + SCALE_RESOLUTION)
    cmd_ratio_modif_real()


def cmd_z1_real(event):
    """Actualise et vérifie la valeur réelle de z1"""
    tk_Label_z1_real.config(text='z1 = {0}'.format(real_to_str(tk_Scale_z1_real.get())))
    if tk_Scale_z0_real.get() >= tk_Scale_z1_real.get():
        tk_Scale_z0_real.set(tk_Scale_z1_real.get() - SCALE_RESOLUTION)
    cmd_ratio_modif_imag()



########
# Main #
########
if len(sys.argv) > 1:
    help()

WIDTH = 500   # largeur de l'image
HEIGHT = 500  # hauteur

SCALE_LENGTH = 300  # longueur des widgets Scale
SCALE_RESOLUTION = .01  # résolution du pas des widgets Scale


# Tableau qui contiendra l'image au format PPM binary avec ses paramètres
array_PPM = array.array('B')

# True si en train de calculer une image
calc_state = False

# Coordonnée de départ de la zone d'affichage qui sera sélectionnée
zone0 = None

# Coordonnée de fin de la zone d'affichage qui sera sélectionnée
zone1 = None



# Interface graphique
tix_Win = tkinter.tix.Tk()
tix_Win.title('Mandelbrot Set Tk')
tix_Win.resizable(0,0)
tix_Win.protocol('WM_DELETE_WINDOW', cmd_quit)


# Image
tk_Canvas = tkinter.Canvas(tix_Win, width=WIDTH, height=HEIGHT)
tk_PhotoImage = tkinter.PhotoImage(width=WIDTH, height=HEIGHT)
tk_Canvas.create_image(0, 0, anchor=tkinter.NW, image=tk_PhotoImage)
tk_Canvas.pack(side=tkinter.LEFT)
tk_Canvas.bind('<Leave>', cmd_coords)
tk_Canvas.bind('<Motion>', cmd_coords)
tk_Canvas.bind('<Button-1>', cmd_coords_to_C)
tk_Canvas.bind('<B3-Motion>', cmd_select_zone_move)
tk_Canvas.bind('<ButtonRelease-3>', cmd_select_zone)


# Barre de progression
ttk_Progressbar = tkinter.ttk.Progressbar(tix_Win,
                                          length=HEIGHT - 3, maximum=HEIGHT - 1,
                                          orient=tkinter.VERTICAL)
ttk_Progressbar.pack(side=tkinter.LEFT)


tk_Frame = tkinter.Frame(tix_Win)

tkinter.Frame(tk_Frame, height=12).grid()
# Boutons de commande
tk_Frame2 = tkinter.Frame(tk_Frame)
tkinter.Frame(tk_Frame2, width=2).pack(side=tkinter.LEFT)
tk_Button_calc = tkinter.Button(tk_Frame2, text=' Calc. ', command=cmd_calc)
tk_Button_calc.pack(side=tkinter.LEFT)
tkinter.tix.Balloon().bind_widget(tk_Button_calc,
                            msg="""Calculation of picture with this parameters.
Warning: very long time is possible!""")

tkinter.Frame(tk_Frame2, width=10).pack(side=tkinter.LEFT)
tk_tmp = tkinter.Button(tk_Frame2, text=' Default ', command=cmd_default)
tk_tmp.pack(side=tkinter.LEFT)
tkinter.tix.Balloon().bind_widget(tk_tmp,
                                  msg='Set paramaters to default values')

tkinter.Frame(tk_Frame2, width=10).pack(side=tkinter.LEFT)
tk_tmp = tkinter.Button(tk_Frame2, text=' Load ', command=cmd_load)
tk_tmp.pack(side=tkinter.LEFT)
tkinter.tix.Balloon().bind_widget(tk_tmp, msg='Load PPM file')

tk_tmp = tkinter.Button(tk_Frame2, text=' Save As' + chr(8230),
                        command=cmd_save_as)
tk_tmp.pack(side=tkinter.LEFT)
tkinter.tix.Balloon().bind_widget(tk_tmp, msg='Save PPM file')

tk_tmp = None

tkinter.Frame(tk_Frame2, width=10).pack(side=tkinter.LEFT)
tkinter.Button(tk_Frame2, text=' About ',
               command=cmd_about).pack(side=tkinter.LEFT)
tkinter.Button(tk_Frame2, text=' Quit ',
               command=cmd_quit).pack(side=tkinter.LEFT)

tk_Frame2.grid(sticky=tkinter.W, columnspan=2)


tkinter.Frame(tk_Frame, height=12).grid()
# Boutons à cocher
tk_Frame2 = tkinter.Frame(tk_Frame)
tk_Var_black = tkinter.BooleanVar()
tk_Checkbutton_black = tkinter.Checkbutton(tk_Frame2, text='B&W',
                                           variable=tk_Var_black)
tk_Checkbutton_black.pack(side=tkinter.LEFT)
tkinter.tix.Balloon().bind_widget(tk_Checkbutton_black,
                            msg="""Picture in black & white or in gray level.
Require 'Calc'""")

tk_Var_axis = tkinter.BooleanVar()
tk_Checkbutton_axis = tkinter.Checkbutton(tk_Frame2, text='Axis',
                                          variable=tk_Var_axis,
                                          command=cmd_axis)
tk_Checkbutton_axis.pack(side=tkinter.LEFT)
tkinter.tix.Balloon().bind_widget(tk_Checkbutton_axis,
                            msg='Show axis and circles (radius: 0.25, 1 and 2)')

tk_Var_ratio = tkinter.BooleanVar()
tk_Checkbutton_ratio = tkinter.Checkbutton(tk_Frame2, text='Ratio',
                                           variable=tk_Var_ratio,
                                           command=cmd_ratio_modif_imag)
tk_Checkbutton_ratio.pack(side=tkinter.LEFT)
tkinter.tix.Balloon().bind_widget(tk_Checkbutton_ratio, msg='Preserve ratio')

tkinter.Frame(tk_Frame2, width=30).pack(side=tkinter.LEFT)
tk_Var_Julia = tkinter.BooleanVar()
tk_Checkbutton_Julia = tkinter.Checkbutton(tk_Frame2,
                                           text='Julia', variable=tk_Var_Julia)
tk_Checkbutton_Julia.pack(side=tkinter.LEFT)
tkinter.tix.Balloon().bind_widget(tk_Checkbutton_Julia,
                                  msg='Julia set instead of Mandelbrot set')

tk_Frame2.grid(sticky=tkinter.W, columnspan=2)
tk_Frame2 = None


# Coordonnées sous le pointeur de la souris
tk_Label_coord_real = tkinter.Label(tk_Frame, text='', width=27,
                                    anchor=tkinter.W)
tk_Label_coord_real.grid(sticky=tkinter.W)
tk_Label_coord_imag = tkinter.Label(tk_Frame, text='', width=20,
                                    anchor=tkinter.W)
tk_Label_coord_imag.grid(sticky=tkinter.W, column=1, row=4)


tkinter.Frame(tk_Frame, height=12).grid()
# z0
tk_Label_z0_real = tkinter.Label(tk_Frame, text='', anchor=tkinter.W)
tk_Label_z0_real.grid(sticky=tkinter.W)
tkinter.tix.Balloon().bind_widget(tk_Label_z0_real,
                                  msg='Top left corner: real component')

tk_Label_z0_imag = tkinter.Label(tk_Frame, text='', anchor=tkinter.W)
tk_Label_z0_imag.grid(sticky=tkinter.W, column=1, row=6)
tkinter.tix.Balloon().bind_widget(tk_Label_z0_imag,
                                  msg='Top left corner: imaginary component')

# z1
tk_Label_z1_real = tkinter.Label(tk_Frame, text='', anchor=tkinter.W)
tk_Label_z1_real.grid(sticky=tkinter.W)
tkinter.tix.Balloon().bind_widget(tk_Label_z1_real,
                                  msg='Bottom right corner: real component')

tk_Label_z1_imag = tkinter.Label(tk_Frame, text='', anchor=tkinter.W)
tk_Label_z1_imag.grid(sticky=tkinter.W, column=1, row=7)
tkinter.tix.Balloon().bind_widget(tk_Label_z1_imag,
                                msg='Bottom right corner: imaginary component')

# z0 partie réelle
tk_Scale_z0_real = tkinter.Scale(tk_Frame,
                                 from_=-2.0, to=2.0 - SCALE_RESOLUTION,
                                 resolution=SCALE_RESOLUTION,
                                 length=SCALE_LENGTH, orient=tkinter.HORIZONTAL,
                                 showvalue=0, command=cmd_z0_real)
tk_Scale_z0_real.grid(sticky=tkinter.W, columnspan=2)
tkinter.tix.Balloon().bind_widget(tk_Scale_z0_real,
                                  msg='Top left corner: real component')

# z1 partie réelle
tk_Scale_z1_real = tkinter.Scale(tk_Frame,
                                 from_=-2.0 + SCALE_RESOLUTION, to=2.0,
                                 resolution=SCALE_RESOLUTION,
                                 length=SCALE_LENGTH, orient=tkinter.HORIZONTAL,
                                 showvalue=0, command=cmd_z1_real)
tk_Scale_z1_real.grid(sticky=tkinter.W, columnspan=2)
tkinter.tix.Balloon().bind_widget(tk_Scale_z1_real,
                                  msg='Bottom right corner: real component')

tkinter.Frame(tk_Frame, height=12).grid()
# z0 partie imaginaire
tk_Scale_z0_imag = tkinter.Scale(tk_Frame,
                                 from_=-2.0 + SCALE_RESOLUTION, to=2.0,
                                 resolution=SCALE_RESOLUTION,
                                 length=SCALE_LENGTH, orient=tkinter.HORIZONTAL,
                                 showvalue=0, command=cmd_z0_imag)
tk_Scale_z0_imag.grid(sticky=tkinter.W, columnspan=2)
tkinter.tix.Balloon().bind_widget(tk_Scale_z0_imag,
                                  msg='Top left corner: imaginary component')

# z1 partie imaginaire
tk_Scale_z1_imag = tkinter.Scale(tk_Frame,
                                 from_=-2.0, to=2.0 - SCALE_RESOLUTION,
                                 resolution=SCALE_RESOLUTION,
                                 length=SCALE_LENGTH, orient=tkinter.HORIZONTAL,
                                 showvalue=0, command=cmd_z1_imag)
tk_Scale_z1_imag.grid(sticky=tkinter.W, columnspan=2)
tkinter.tix.Balloon().bind_widget(tk_Scale_z1_imag,
                                msg='Bottom right corner: imaginary component')


tkinter.Frame(tk_Frame, height=12).grid()
# C
tk_Label_C_real = tkinter.Label(tk_Frame, text='', anchor=tkinter.W)
tk_Label_C_real.grid(sticky=tkinter.W)
tkinter.tix.Balloon().bind_widget(tk_Label_C_real,
                                  msg='C: real component (Julia Set)')

tk_Label_C_imag = tkinter.Label(tk_Frame, text='', anchor=tkinter.W)
tk_Label_C_imag.grid(sticky=tkinter.W, column=1, row=14)
tkinter.tix.Balloon().bind_widget(tk_Label_C_imag,
                                  msg='C: imaginary component (Julia Set)')

tk_Scale_C_real = tkinter.Scale(tk_Frame, from_=-2.0, to=2.0,
                                resolution=SCALE_RESOLUTION,
                                length=SCALE_LENGTH, orient=tkinter.HORIZONTAL,
                                showvalue=0, command=cmd_C_real)
tk_Scale_C_real.grid(sticky=tkinter.W, columnspan=2)
tkinter.tix.Balloon().bind_widget(tk_Scale_C_real, msg='C: real component')

tkinter.Frame(tk_Frame, height=12).grid()
tk_Scale_C_imag = tkinter.Scale(tk_Frame, from_=-2, to=2.0,
                                resolution=SCALE_RESOLUTION,
                                length=SCALE_LENGTH, orient=tkinter.HORIZONTAL,
                                showvalue=0, command=cmd_C_imag)
tk_Scale_C_imag.grid(sticky=tkinter.W, columnspan=2)
tkinter.tix.Balloon().bind_widget(tk_Scale_C_imag, msg='C: imaginary component')


# Valeur des premières itérées de C
tk_Frame2 = tkinter.Frame(tk_Frame)
tk_Label_fC_real = [None]*4
tk_Label_fC_imag = [None]*4
for i in range(4):
    tk_Label_fC_real[i] = tkinter.Label(tk_Frame2, text='', width=27,
                                        anchor=tkinter.W)
    tk_Label_fC_real[i].grid(sticky=tkinter.W)

    tk_Label_fC_imag[i] = tkinter.Label(tk_Frame2, text='', width=20,
                                        anchor=tkinter.W)
    tk_Label_fC_imag[i].grid(sticky=tkinter.W, column=1, row=i)

tk_Frame2.grid(sticky=tkinter.W, columnspan=2)
tkinter.tix.Balloon().bind_widget(tk_Frame2,
                                  msg='First iterates of function z ' + chr(8594) + ' z^2 + C')
tk_Frame2 = None


tkinter.Frame(tk_Frame, height=12).grid()
# max_ite
tk_Label_max_ite = tkinter.Label(tk_Frame, text='', anchor=tkinter.W)
tk_Label_max_ite.grid(sticky=tkinter.W, columnspan=2)

tk_Scale_max_ite = tkinter.Scale(tk_Frame, label='', from_=1, to=300,
                                 length=SCALE_LENGTH, orient=tkinter.HORIZONTAL,
                                 showvalue=0, command=cmd_max_ite)
tk_Scale_max_ite.grid(sticky=tkinter.W, columnspan=2)


tkinter.Frame(tk_Frame, height=12).grid()
# Time
tk_Label_infos = tkinter.Label(tk_Frame, text='', anchor=tkinter.W)
tk_Label_infos.grid(sticky=tkinter.W, columnspan=2)


tk_Frame.pack()
tk_Frame = None


cmd_default(force=True)

tix_Win.focus_force()
tix_Win.mainloop()
