/******************************************************
 *  Fichier   : Lorenz.c
 *  Utilité   : Traçage dynamique de l'attracteur de Lorenz
 *  Auteurs   : Loïc FONTAINE - loic.fontaine@eisti.fr
 *              Pierre RADONDE - pierre.radonde@eisti.fr
 *  Version   : finale
 *  Cration   : 09/06/2005
 *  Remarques : 
 *      ¤  Programme réalisé en GTK 1.2
 *      ¤  Commande de compilation (avec gcc):
 *  gcc -Wall -g Lorenz.c -o Lorenz `gtk-config --cflags` `gtk-config --libs`
 ******************************************************/

/*******************************
 *  INCLUSION DES LIBRAIRIES.
 *******************************/
#include <gtk/gtk.h>
#include <stdlib.h>
#include <stdio.h> 
#include <math.h>
#include <tgmath.h>

/*******************************
 *  AIDE POUR LE PROGRAMME.
 *******************************/
gchar *txt_help="\nBonjour et bienvenue dans le tutorial du programme Lorenz.\n
================\n
Ce programme permet de tracer dynamiquement l'attracteur de Lorenz.
Les coordonnées de chaque point sont obtenues par 3 suites (voir rapport).
La fenêtre est composée de plusieurs champs à remplir ainsi que 4 boutons radios.
Il est impératif de remplir tous les champs pour obtenir un tracé.
Pour lancer le tracé de l'attracteur sélectionnez un des 4 boutons radios.\n\n

================\n
*** Les champs à remplir sont les suivants:\n
 ¤ Les 3 conditions initiales (Xo, Yo et Zo) : il est intéressant de remarquer que
quelques soient les conditons initiales on retombe toujours sur le même attracteur.\n
 ¤ La constante r : comme il est dit dans le rapport cette constante modifie la 
nature de l'attracteur. Amusez vous a modifier cette valeur, entre 0 et 30, pour 
modifier la nature chaotique de l'attracteur.\n
 ¤ Le pas h et le nombre de points Nbr. pt. : le pas est l'espacement entre deux 
points, il défini la précision du système. Plus le pas est faible, plus la précision 
du graphique sera élevé. \n
Attention si le pas est diminué il est nécessaire d'augmenter le nombre de points.\n\n
  Pour obtenir un tracé dynamique de l'attraceur, il est intéressant de diminuer 
fortement le pas (de l'ordre de 10^-5) et d'augmenter le nombre de point à 10^6.\n\n

================\n
*** Les boutons radios : l'attracteur de Lorenz est une figure en trois dimension, 
pour le visualiser en 2D il est donc nécessaire de le projetter sur 2 axes. 
Le quatrième bouton radio d'intitulé 'Vue 3D' est en réalité une vue en 3D de 
l'attracteur.\n\n

Les valeurs par défaut présentent au démarrage du programme permettent la
visualisation d'un papillon de Lorenz convenable.";


/******************************************************
 *  Fonction  : close_fenetre
 *  Utilité   : Quitte le programme
  *  Remarques : Fonction appelée lors de la fermeture 
 *               de la fenetre (par la croix) ou de l'appui
 *               sur le bouton fermer.
 ******************************************************/
gint close_fenetre (GtkWidget* widget, GdkEvent* event, gpointer user_data) {
	gtk_main_quit();
	return (FALSE);
}

/******************************************************
 *  Fonction  : effacer
 *  Utilité   : ¤ Efface la zone de dessin
 *              ¤ Déselectionne les boutons radios
 *  Remarques : Fonction appelée uniquement lors de l'appui
 *              sur le bouton 'effacer'.
 *              On detruit la zone de dessin, 
 *               puis on en recrée une autre.
 ******************************************************/
void effacer (GtkWidget* widget, gpointer user_data) {
  GtkWidget* zone;  //  Zone de dessin 
  GtkWidget* scroll;//  Fenetre de dessin
  GtkWidget *radio;  // bouton radio fictif

  //  Récupération de la zone de dessin
  zone = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (user_data), "zone"));
  scroll = gtk_object_get_data (GTK_OBJECT (user_data), "scroll");

  //   Destruction de la zone de dessin
  gtk_widget_destroy (zone);
  
  //	 Création de la zone de dessin
  zone = gtk_drawing_area_new ();
  gtk_widget_set_usize (scroll, 1024, 900);

  gtk_container_add (GTK_CONTAINER (scroll), zone);

  gtk_widget_show(scroll);
  gtk_object_set_data (GTK_OBJECT (user_data), "zone", zone);
  gtk_object_set_data (GTK_OBJECT (user_data), "scroll", scroll);
   
  // déselection des bouton radio a l'aide du bouton fictif...
  radio = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (user_data), "radio"));
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio),TRUE);


  //   Affichage de cette nouvelle zone
  gtk_widget_show (zone);
  gtk_widget_show(user_data);
}

/******************************************************
 *  Fonction  : fermer_dialogue(1)
 *  Utilité   : Ferme correctement la fenetre de dialogue
 *               qui affiche l'aide de programme.
 *  Remarques : Prend en compte le bouton fermer 
 *               ET la croix en haut de la fenetre.
 ******************************************************/
gint fermer_dialogue1 (GtkWidget* widget, GdkEvent* event, gpointer user_data) {
  gtk_widget_destroy (user_data);
  return (FALSE);
}
gint fermer_dialogue (GtkWidget* widget, gpointer user_data) {
  gtk_widget_destroy (user_data);
  return (FALSE);
}

/******************************************************
 *  Fonction  : help
 *  Utilité   : Ouvre une pop-up qui contient l'aide pour
 *               utiliser ce programme.
 *  Remarques : Fonction appelée lors de l'appui sur le 
 *               bouton 'Aide'.
 ******************************************************/
void help (GtkWidget* widget, gpointer user_data)
{
  GtkWidget *popup;          // fenetre pop-up
  GtkWidget* boutonfermer;   // Bouton pour la fermer
  GtkWidget* scroll;         // scrollbar de la fenetre
  GtkWidget* label;          // texte de l'aide


  // Création de la fenetre et son remplissage
  popup =  gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (popup), "Aide");
  gtk_widget_set_usize (popup,660,600);

  label = gtk_label_new (txt_help);
  scroll= gtk_scrolled_window_new(NULL,NULL);
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scroll), 
					 label);
  gtk_container_add (GTK_CONTAINER (GTK_DIALOG(popup)->vbox), 
			   scroll); 

  boutonfermer = gtk_button_new_with_label("ok");
  gtk_container_add (GTK_CONTAINER (GTK_DIALOG(popup)->action_area), boutonfermer);
  gtk_signal_connect (GTK_OBJECT (boutonfermer), "clicked",
		      GTK_SIGNAL_FUNC (fermer_dialogue), popup);
  gtk_signal_connect (GTK_OBJECT (popup), "delete-event",
		      GTK_SIGNAL_FUNC (fermer_dialogue1), popup);

  /* Option de la fenetre de dialogue et affichage */
  gtk_window_set_modal (GTK_WINDOW (popup), TRUE);
  gtk_widget_show_all (popup);
}

/******************************************************
 *  Fonctions : dxdt / dydt / dzdt
 *  Utilité   : Retourne les valeurs des fonctions correspondantes
 *  Remarques : Fonction utilisées pour résoudre le système de Lorenz.
 ******************************************************/
//définition de la fonction f(x,y) qui prend donc en argument les 2 variables x et y ainsi que la constante Pr.
double dxdt (double x, double y,double Pr)
{return (Pr*(y-x));}
//définition de la fonction g(x,y,z) qui prend donc en argument les 3 variables x,y et z ainsi que la constante r.
double dydt (double x,double y, double z,double r)
{return (r*x-x*z-y);}
//définition de la fonction h(y,z) qui prend donc en argument les 2 variables y et z ainsi que la constante b.
double dzdt (double x,double y, double z,double b)
{return (x*y-b*z);}

/******************************************************
 *  Fonction  : tracer
 *  Utilité   : Trace l'attracteur de Lorenz
 *  Remarques : Cette fonction utilise la méthode mathématique 
 *               de Runge-Kutta pour résoudre le système 
 *               non-linéaire de Lorenz.
 ******************************************************/
void tracer (GtkWidget* widget, gpointer user_data) 
{
  GtkWidget* zone;  // Zone de dessin
  GdkGC* gc;        // Contexte Graphique 
  GdkWindow* graph; //  Zone d'affichage
  gint choix;

  float i;      // Itérateur
  double xn,yn,zn,xnn,ynn,znn;                // Valeurs des suites
  double a1,a2,a3,a4,b1,b2,b3,b4,c1,c2,c3,c4; // Variables des suites
  double A,B,C,D;                             // Necessaire pour le tracer 3D
  float sigma,b,r;                            // Constantes des suites 
  int Nb;                                     // Nombre de points
  double h;                                   // Pas 
  gboolean ok;                                // test pour les boutons radios
 
  // Récupération du booléen
  ok = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
  
  if (ok) {
    // Récupération du choix de l'exace de phase
    choix = GPOINTER_TO_INT (gtk_object_get_data (GTK_OBJECT (widget), "choix"));
	// Récupération de la zone de dessin
	zone = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (user_data), "zone"));
	//    Récupération des paramètres pour permettre le dessin
	graph = zone->window;
	gc = gdk_gc_new (graph);

	// Initialisation des variables et constantes:
	sigma=10;
	b=2.666667;

	// Récupération des valeurs saisies
	xn = g_strtod (gtk_entry_get_text(GTK_ENTRY (gtk_object_get_data 
					      (GTK_OBJECT(user_data), 
						 "xo"))), NULL);
	yn = g_strtod (gtk_entry_get_text(GTK_ENTRY (gtk_object_get_data 
					      (GTK_OBJECT(user_data), 
						 "yo"))), NULL);
	zn = g_strtod (gtk_entry_get_text(GTK_ENTRY (gtk_object_get_data 
					      (GTK_OBJECT(user_data), 
						 "zo"))), NULL);


	r = g_strtod (gtk_entry_get_text(GTK_ENTRY (gtk_object_get_data 
					      (GTK_OBJECT(user_data), 
						 "r"))), NULL);
	h = g_strtod (gtk_entry_get_text(GTK_ENTRY (gtk_object_get_data 
					      (GTK_OBJECT(user_data), 
						 "h"))), NULL);
	Nb = g_strtod (gtk_entry_get_text(GTK_ENTRY (gtk_object_get_data 
					      (GTK_OBJECT(user_data), 
						 "Nb"))), NULL);


	// Début du calcul des points
	for( i=0;i<Nb;i++)
	{
	  /* METHODE DE RUNGE-KUTTA */
	//définition des k1
	a1=h*dxdt(xn,yn,sigma);
	b1=h*dydt(xn,yn,zn,r);
	c1=h*dzdt(xn,yn,zn,b);

	//définition des k2
	a2=h*dxdt(xn+0.5*a1,yn+0.5*a1,sigma);
	b2=h*dydt(xn+0.5*b1,yn+0.5*b1,zn+0.5*b1,r);
	c2=h*dzdt(xn+0.5*c1,yn+0.5*b1,zn+0.5*c1,b);

	//défintion des k3
	a3=h*dxdt(xn+0.5*a2,yn+0.5*a2,sigma);
	b3=h*dydt(xn+0.5*b2,yn+0.5*b2,zn+0.5*b2,r);
	c3=h*dzdt(xn+0.5*c2,yn+0.5*c2,zn+0.5*c2,b);

	//définition des k4
	a4=h*dxdt(xn+a3,yn+a3,sigma);
	b4=h*dydt(xn+b3,yn+b3,zn+b3,r);
	c4=h*dzdt(xn+b3,yn+b3,zn+b3,b);

	//calculs de valeurs suivantes de la suite
	xnn=xn+(a1+2*a2+2*a3+a4)/6;
	ynn=yn+(b1+2*b2+2*b3+b4)/6;
	znn=zn+(c1+2*c2+2*c3+c4)/6;

	// Choix de l'espace de phase  et tracer des points:
	switch (choix) {
	case 1 : 
	  gdk_draw_line (graph, gc, 
			 (xn*10)+500,
			 (yn*10)+350,
			 (xnn*10)+500,
			 (ynn*10)+350);
	break;
	case 2 : gdk_draw_line (graph, gc, 
				(xn*10)+500,
				(zn*10)+50,
				(xnn*10)+500,
				(znn*10)+50);
	break;
	case 3 :
	  gdk_draw_line (graph, gc, 
			 (yn*10)+500,
			 (zn*10)+50,
			 (ynn*10)+500,
			 (znn*10)+50);
	  break;
	case 4 :
	  A=xn*10+550+yn*10;
	  B=zn*10+150+yn*10;
	  C=xnn*10+550+ynn*10;
	  D=znn*10+150+ynn*10;
	  gdk_draw_line (graph, gc,A,B,C,D);
	  break;
	default :
	break;
	}

	// Modification des valeurs de la suite pour le 
	//  calcul des points suivants
	xn=xnn;
	yn=ynn;
	zn=znn;

	while (gtk_events_pending ())
	  gtk_main_iteration ();

	}
  }
}


/******************************************************
 *  PROGRAMME PRINCIPAL
 ******************************************************/
	
int main(int argc, char **argv) {
 	GtkWidget* fenetre;  //   Fenetre de l'application 
	GtkWidget* scroll;   //   Fenetre de dessin
	GtkWidget*   zone;   //   Zone de dessin
	GtkWidget*   boitev; //   Boite qui contient le programme
	GtkWidget*   boiteh; //   Boite des boutons

	GtkWidget*   boutonclear;  // Bouton effacer courbe
	GtkWidget*   boutonhelp;   // Bouton Aide
	GtkWidget*   boutonfermer; // Bouton quitter l'application 

	GtkWidget* boiteh_radio; // Boite des boutons radios
	GtkWidget* groupe;       // Groupe des boutons radios
	GtkWidget *radio_1, *radio_2, *radio_3, *radio_4; // boutons radios
	GtkWidget *radio; // bouton radio fictif pour desactivation des radios
	GtkWidget* phase; // Label d'information
	GtkWidget* var;   // Label d'information
	GtkWidget* hbox1;  // Boite horizontale de saisie 1
	GtkWidget* hbox2;  // Boite horizontale de saisie 2
	GtkWidget* label_xo, *label_yo,*label_zo;// labels pour les saisies
	GtkWidget* label_r, *label_h, *label_Nb; // labels pour les saisies
	GtkWidget* xo, *yo, *zo, *r, *h, *Nb; // valeurs des saisies

	//     Initialisation de gtk 
	gtk_init(&argc,&argv);

	//  Création de la fenetre principale
	fenetre = gtk_window_new (GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title (GTK_WINDOW(fenetre),"Attracteur de Lorenz");
	//gtk_window_set_screen(fenetre, gtk_window_get_screen(fenetre));
	//     Connexion du signal 'delete-event' appelé lors
	//    *  de la fermture de l'application 
	gtk_signal_connect (GTK_OBJECT (fenetre), "delete-event",
			GTK_SIGNAL_FUNC (close_fenetre), NULL);

	//  Création d'une boite verticale. Celle-ci va contenir
	//  *  la boite des saisies
	//  *  celle des boutons radio
	//  *  celle des boutons
	//  *  la zone de dessin
	boitev = gtk_vbox_new (FALSE, 8);
	//     Ajout de la boite dans la fenêtre
	gtk_container_add (GTK_CONTAINER (fenetre), boitev);
	//   Association fentre <-> boitev. Pour permettre de
	//    *  retrouver la boite lors de l'effacage de la fenêtre 
	gtk_object_set_data (GTK_OBJECT (fenetre), "boitev", boitev);

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

	//     Création de la boite de saisie pour les initialisations
	var=gtk_label_new("Initialisation des variables :");
	gtk_container_add(GTK_CONTAINER(boitev),var);

	hbox1 = gtk_hbox_new(FALSE,1);
	gtk_container_add(GTK_CONTAINER(boitev),hbox1);
	hbox2 = gtk_hbox_new(FALSE,1);
	gtk_container_add(GTK_CONTAINER(boitev),hbox2);

	/*  Création des labels et des zones de saisie */
	// §§§§§  Xo=?
	label_xo = gtk_label_new ("Xo =");
	gtk_container_add (GTK_CONTAINER (hbox1), label_xo);
	xo = gtk_entry_new();
	gtk_entry_set_text(GTK_ENTRY(xo),"11");
	gtk_container_add (GTK_CONTAINER (hbox1), xo);
	gtk_object_set_data (GTK_OBJECT (fenetre), "xo", xo);

	// §§§§§  Yo=?
	label_yo = gtk_label_new ("Yo =");
	gtk_container_add (GTK_CONTAINER (hbox1), label_yo);
	yo = gtk_entry_new();
	gtk_entry_set_text(GTK_ENTRY(yo),"0");
	gtk_container_add (GTK_CONTAINER (hbox1), yo);  
	gtk_object_set_data (GTK_OBJECT (fenetre), "yo", yo);

	// §§§§§  Zo=?
	label_zo = gtk_label_new ("Zo =");
	gtk_container_add (GTK_CONTAINER (hbox1), label_zo);
	zo = gtk_entry_new();
	gtk_entry_set_text(GTK_ENTRY(zo),"20");
	gtk_container_add (GTK_CONTAINER (hbox1), zo);
	gtk_object_set_data (GTK_OBJECT (fenetre), "zo", zo);
	
	//=======================================//
	// §§§§§  r=?
	label_r = gtk_label_new ("r = ");
	gtk_container_add (GTK_CONTAINER (hbox2), label_r);
	r = gtk_entry_new();
	gtk_entry_set_text(GTK_ENTRY(r),"28");
	gtk_container_add (GTK_CONTAINER (hbox2), r);
	gtk_object_set_data (GTK_OBJECT (fenetre), "r", r);

	// §§§§§  h=?
	label_h = gtk_label_new ("h = ");
	gtk_container_add (GTK_CONTAINER (hbox2), label_h);
	h = gtk_entry_new();
	gtk_entry_set_text(GTK_ENTRY(h),"0.0001");
	gtk_container_add (GTK_CONTAINER (hbox2), h);
	gtk_object_set_data (GTK_OBJECT (fenetre), "h", h);
	
	// §§§§§  Nbr pt.=?
	label_Nb = gtk_label_new ("Nbr. pt. = ");
	gtk_container_add (GTK_CONTAINER (hbox2), label_Nb);
	Nb = gtk_entry_new();
	gtk_entry_set_text(GTK_ENTRY(Nb),"500000");
	gtk_container_add (GTK_CONTAINER (hbox2), Nb);
	gtk_object_set_data (GTK_OBJECT (fenetre), "Nb", Nb);

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

	/*  Création de la boite radio et du groupe */
	phase=gtk_label_new("Choix de l'espace de phase :");
	gtk_container_add(GTK_CONTAINER(boitev),phase);

	boiteh_radio = gtk_hbox_new (FALSE, 8);
	groupe = gtk_radio_button_new(NULL);
	/*  Création du bouton radio x et y */
	radio_1 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(groupe), "selon x et y");
	gtk_container_add (GTK_CONTAINER (boiteh_radio), radio_1);
	gtk_signal_connect (GTK_OBJECT (radio_1),"toggled",
			    GTK_SIGNAL_FUNC(tracer),fenetre);
	gtk_object_set_data (GTK_OBJECT (radio_1), "choix", GINT_TO_POINTER (1));

	/*  Création du bouton radio x et z */
	radio_2 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(groupe), "selon x et z");
	gtk_container_add (GTK_CONTAINER (boiteh_radio), radio_2);
	gtk_signal_connect (GTK_OBJECT (radio_2),"toggled",
			    GTK_SIGNAL_FUNC(tracer),fenetre);
	gtk_object_set_data (GTK_OBJECT (radio_2), "choix", GINT_TO_POINTER (2));

	/*  Création du bouton radio y et z */
	radio_3 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(groupe), "selon y et z");
	gtk_container_add (GTK_CONTAINER (boiteh_radio), radio_3);
	gtk_object_set_data (GTK_OBJECT (radio_3), "choix", GINT_TO_POINTER (3));
	gtk_signal_connect (GTK_OBJECT (radio_3),"toggled",
			    GTK_SIGNAL_FUNC(tracer),fenetre);

	/*  Création du bouton radio mélange de x, y et z*/
	radio_4 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(groupe), "Vue 3D");
	gtk_container_add (GTK_CONTAINER (boiteh_radio), radio_4);
	gtk_object_set_data (GTK_OBJECT (radio_4), "choix", GINT_TO_POINTER (4));
	gtk_signal_connect (GTK_OBJECT (radio_4),"toggled",
			    GTK_SIGNAL_FUNC(tracer),fenetre);
	
	/*  Création du bouton radio fictif */
	radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(groupe), "fictif");
	gtk_object_set_data (GTK_OBJECT (fenetre), "radio", radio);

	gtk_container_add (GTK_CONTAINER(boitev),boiteh_radio);

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

	//     Création de la "boite à boutons" 
	boiteh = gtk_hbox_new (FALSE, 8);
	//     Ajout de celle-ci dans la boite verticale 
	gtk_container_add (GTK_CONTAINER (boitev), boiteh);

	//  Création du bouton "Aide" pour les variables
	boutonhelp = gtk_button_new_with_label ("Aide");
	gtk_container_add (GTK_CONTAINER (boiteh), boutonhelp);
		gtk_signal_connect (GTK_OBJECT (boutonhelp),"clicked",
			    GTK_SIGNAL_FUNC (help), fenetre);
	GTK_WIDGET_SET_FLAGS (boutonhelp, GTK_CAN_DEFAULT);
	gtk_widget_grab_default (boutonhelp);

	//  Création du bouton pour "Effacer" les trajectoires
	boutonclear = gtk_button_new_with_label ("Effacer");
	gtk_container_add (GTK_CONTAINER (boiteh), boutonclear);
		gtk_signal_connect (GTK_OBJECT (boutonclear),"clicked",
			    GTK_SIGNAL_FUNC (effacer), fenetre);
	GTK_WIDGET_SET_FLAGS (boutonclear, GTK_CAN_DEFAULT);

	//  Création du bouton pour "Fermer" 
	boutonfermer = gtk_button_new_with_label ("Fermer");
	gtk_container_add (GTK_CONTAINER (boiteh), boutonfermer);
		gtk_signal_connect (GTK_OBJECT (boutonfermer),"clicked",
			GTK_SIGNAL_FUNC (close_fenetre), fenetre);
	GTK_WIDGET_SET_FLAGS (boutonfermer, GTK_CAN_DEFAULT);

	//=======================================================
	
	  scroll = gtk_window_new (GTK_WINDOW_TOPLEVEL);
	  
	  //	 Création de la zone de dessin
	  zone = gtk_drawing_area_new ();
	  gtk_container_add (GTK_CONTAINER (scroll), zone);
	  gtk_widget_set_usize (scroll, 1024, 900);
	  gtk_widget_show_all (scroll);
	  gtk_object_set_data (GTK_OBJECT (fenetre), "zone", zone);
	  gtk_object_set_data (GTK_OBJECT (fenetre), "scroll", scroll);

	// On affiche tout ce que l'on vient de créer
	gtk_widget_show_all (fenetre);

	// On entre dans la boucle de traitement des évènements
	gtk_main ();
	return (0);
}
