mardi 5 décembre 2017

Filtrer les guillements parasites d'un fichier CSV destiné à être importé dans une base de donnée

Les imports de bases de données à l'aide de fichiers CSV sont toujours critiques, car de nombreux facteurs peuvent déraper. Pour rappel, un fichier CSV est:
  • 1 fichier texte
  • dont chaque ligne correspond à un enregistrement d'une table
  • et dont les lignes sont découpées en colonne par un délimiteur
Pour importer correctement un fichier CSV, il faut se mettre d'accord sur de nombreux éléments: le caractère délimiteur de colonne (souvent le ;), le ou les délimiteurs de ligne (CR-LF sous DOS, LF sous unix, ou CR pour d'autre), et sur le "charset" (la table de correspondance ASCCI, UTF8, unicode, etc...).

Malheureusement, il faut également se préoccuper du caractère délimiteur des chaines de caractères que l'on trouve dans la plupart des colonnes d'un CSV, comme par exemple des libellés. Traditionnellement, on utilise les guillemets (") pour délimiter une colonne de type texte (chaine de caractère).  Ainsi, une ligne de fichier CSV peut se présenter comme cela:
"CODEnnn";"xxxxxxxxx xxxx xxxx"
Le problème est que très souvent, il y a des caractères guillemets dans ces colonnes textes. Voici un exemple d'un CSV de 2 colonnes, contenant des guillemets parasites:
"CODEnnn";"xxxxxxxxx "xxxx" x""xxx""
Dans ce cas, l'utilitaire d'import ne pourra pas fonctionner correctement. Le but de cet article est de filtrer ces caractères guillemets parasites, présents dans des colonnes d'un CSV.

Pour filtrer ces guillemets parasites, utilisons l'utilitaire SED, outils unix rapide et pratique pour ce genre d'actions. SED applique des actions ligne par ligne sur des fichier texte. L'action (la commande) SED qui nous intéresse est la substitution de caractère. On va demander à SED, de remplacer tous les guillemets parasites par une simple quotte ('). On demande à SED de réaliser cette substitution uniquement sur les guillemets précédés par un caractère différent de ";", et suivi par un caractère lui aussi différent de ";". Le ";" étant le délimiteur de colonne du fichier CSV. En résumé, tous les guillemets seront remplacés, sauf ceux en début de ligne, sauf ceux en fin de ligne, et sauf ceux qui se présente comme cela (";"), cad entourés par au moins 1 point-virgule.

La syntaxe SED que j'utilise est la suivante:
sed -f instructions.sed fichier_CSV
Je préfère utiliser la syntaxe permettant d'écrire les instructions (les commandes) SED dans un fichier, car il est plus facile à éditer, et permet d'insérer des commentaires salutaires pour une relecture quelques jours ou quelques mois plus tard.

La syntaxe de la commande de substitution que j'utilise est la suivante:
s/Exp_Reg_chaine_à_remplacer/nouvelle_chaine/g
Le dernier caractère "g" signifie que cette action de substitution se fera pour toutes les sous-chaines de la ligne respectant l'expression régulière.

L'expression régulière dans notre cas est relativement simple:
[^;]"[^;]
Cela correspond à une sous-chaine de 3 caractères, dont le premier et le dernier ne sont pas un ;, et dont le deuxième est obligatoirement un guillemet.
Cela donne une commande sed:
s/\([^;]\)"\([^;]\)/\1'\2/g

\1 et \2 correspondent respectivement au premier caractère et au 3ème caractère de la sous-chaine correspondante à l'expression régulière. Il suffit de mettre cette commande dans un fichier texte, et d’appeler la commande SED
sed -f instruction.sed fichier.csv > fichier_filtre.csv

Malheureusement, cette commande est inefficace sur certains caractères guillemet parasite.
Comme par exemple cette ligne CSV:
"CODEnnn";"xxxxxx"""""xxx "xxxx" x""xxx""

L'explication est simple: le parseur d'expression régulière ne s'applique pas 2 fois sur la même zone texte. Quand une expression régulière est trouvé par le parseur, la zone trouvée est supprimée pour les recherches suivante du parseur (pour la même instruction sed). Pour une chaine de 4 guillemets consécutifs (xx""""xx), le parseur détectera le premier guillemet, mais pas les deux suivants. Il détectera tout de même le 4 ième.

Pour résoudre le problème, il faut appliquer la commande de substitution 2 fois pour obliger le parseur à refaire la recherche. Il faut même relancer une 3ème fois pour supprimer le cas de guillemets parasites qui serait encore une fois espacés d'un seul caractère.
s/\([^;]\)"\([^;]\)/\1'\2/g  # 1er fois
s/\([^;]\)"\([^;]\)/\1'\2/g  # une 2ème fois
s/\([^;]\)"\([^;]\)/\1'\2/g  # 3ème et dernière fois

Et pour finir, il faut gérer également le problème des CR-LF dans SED. Si votre fichier CSV vient de DOS, SED va considérer le CR (code ASCII 13) comme un caractère comme un autre. Ce qui déclenchera la substitution de dernier guillemet. Il faut donc supprimer préalablement le CR:

s/\x0D$//g

Ne pas oublier si nécessaire de réaliser la commande inverse.
Pour être complet, penser à filtrer les ; en fin de ligne. Le CSV peut parfois avoir ce genre de colonne vide. 2 choix s'offre à vous: les supprimer
 s/;$//
ou les remplacer
 s/;$/; / # on ajoute un espace

Tout ça pour ça. Voici le fichier d’instruction SED pour filtrer nos guillemets parasites:
s/\x0D$//g    # conversion fichier DOS => unix (\r = 13 en ascii)
s/;$// # suppression des ; en fin de ligne
s/\([^;]\)"\([^;]\)/\1'\2/g # conversion des " en ' si le précédent n'est pas ;, ni le suivant
s/\([^;]\)"\([^;]\)/\1'\2/g # à refaire pour les "" contigus
s/\([^;]\)"\([^;]\)/\1'\2/g # à faire en tout 3 fois
s/$/\r/    #conversion fichier texte unix => DOS

La dernière ligne peut poser une soucis selon le SED que vous utilisez. Si votre sed est un exécutif unix, pas de soucis. En revanche, si c'est une version DOS, cela peut poser problème, car ce dernier ajoutera en fin de ligne des CR en plus du LF. Si c'est le cas, voici la commande pour supprimer les CR à postériori:
sed -i "s/\r//" fichier_filtré.csv


L'histoire serait belle si seulement, il n'y avait le cas des délimiteurs parasites.
Exemple de CSV avec 2 colonnes seulement:
"CODEnnnnn";"libellé ";" titre"
Dans ce cas, les 2 guillemets parasites ne seront pas filtrés par la commande sed. Concrètement, les sous-chaines de 2 caractères associant 1 guillemet et 1 point-virgule rendrons inefficace la commande sed conçu ci-dessus. Pour ce dernier cas, je n'ai pas de solution simple. D'ailleurs, comment humainement faire la différence entre un délimiteur et un simple caractère dans ce cas précis: la ligne précédente peut autant être interprétée comme une ligne de 2 colonnes ou une ligne de 3 colonnes. De quoi rendre fou un outil d'import de CSV. Ce cas particulier mérite un article à part entière.

Contentons-nous aujourd'hui de jouir de notre ignorance concernant ce cas perfide, et rappelons nous que dans tout voyage, le chemin est plus important que la destination ;)


PS :
Pour info, voici la commande SED permettant de vérifier la présence de caractère parasite:
/[^;]"[^;]/!d
Cette commande signifie "supprimez toutes les lignes qui ne contienne pas au moins une sous-chaines de 3 caractère décrite par l'expression régulière en question.

liens;
Exemples sed d'une grande clarté 
Introduction rapide à sed
Explication approfondie du fonctionnement de sed
Bases pour comprendre les Expressions Régulières
Boite à outils sed (liste d'exemples très variés)

vendredi 3 novembre 2017

HTML et le désespoir des TABLE en folie: IMG dont la hauteur ne veut pas s'adapter verticalement dans un TD

C'est le drame du HTML, langage qui se voulait simple à l'origine, et qui avec le temps est devenu complexe. Avant de pleurer sur le temps perdu et la nostalgie informatique, voici le drame qui me chagrine.

En ressortant un vieux développement PHP, j'ai découvert que le code HTML basé sur un tag TABLE classique provoquait un affichage aberrant. Voici le principe en pseudo HTML de cet affichage:
<TABLE>
<TR><TD> <IMG src="coin_0.gif"></TD>
<TD> <IMG src="barre_h_h.gif"> </TD>
<TD> <IMG src="coin_1.gif"></TD> </TR>
<TR><TD> <IMG src="barre_v_g.gif" /></TD>
<TD>...texte sur n lignes....</TD>
<TD> <IMG src="barre_v_d.gif" /></TD> </TR>
<TR><TD> <IMG src="coin_3.gif"></TD>
<TD> <IMG src="barre_h_b.gif"></TD>
<TD> <IMG src="coin_2.gif"></TD> </TR>
</TABLE>

Je sais que beaucoup vont parler de code archaïque (en insistant sur le caractère péjoratif), mais j'aime la simplicité, et la logique implacable d'un TABLE sur un DIV, ou autres éléments HTML servant pour les mises en page. Mais cela est une autre histoire. Le code ci dessus marchait à l'aide des attributs permettant de réduire le "border", le "spacing", ou autre "margin" ( border="0" cellspacing="0" cellpadding="0"). Pour que les images s'adaptent parfaitement à la taille dynamique des cellules du tableau, il suffisait de mettre des "height="100%" ou width="100%" sur les bons TD. Le principe est de mettre des dimensions fixes pour les 4 "coins", et de mettre ensuite 100% sur les witdh des images devant s'élargir horizontalement, et 100% sur les "height" des images devant s'élargir en hauteur. En revanche, la case du milieu contenant le texte sur plusieurs lignes, est sans dimensions.

Cela donnait tout simplement cela:
La même chose avec un "border=1" pour visualiser les 9 cases de mon tableau:

Pour la petite histoire, ce type de code HTML, me permettait d'afficher simplement des graphsets, de plusieurs types, avec des bords ronds, carrées ou autres. En final, j'ai amèrement découvert que mon code HTML après quelques années de mise en sommeil donnait ce résultat :

Le plus étonnant, c'est que les images s'adaptent parfaitement en largeur (sauf 0.2px sur la droite, Dieu sait pourquoi), mais seulement partiellement sur la hauteur. En utilisant l'inspecteur d'éléments dans le navigateur, on constate une ligne blanche en dessous de l'image, et une autre au dessus, interprétées par l'inspecteur comme un "padding" (en vert sur la copie d'écran ci-dessus).
Pour Tenter de résoudre mon problème, j'ai donc nettoyé le code en utilisant des propriétés CSS à la place des attributs (souvent devenu obsolètes, mais opérationnel théoriquement). Voici le code en question:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML> <HEAD> <style type="text/css"> table { border-collapse: collapse; /* Colle les bordures entre elles */ border-spacing: 0; margin: 0 0 0 0 ; padding: 0 0 0 0 ;
border: 0; } td { margin: 0; padding: 0 ; border-spacing: 0; } tr { margin: 0; padding: 0 ; border-spacing: 0; } </style> <TITLE>Table avec images extensibles </TITLE> </HEAD> <BODY> <BR> <BR> <CENTER> <TABLE> <tbody> <TR><TD width="19" height="19"><IMG alt="" width="19" height="19" src="el/fin_coin_0.gif"></TD> <TD height="19"><IMG alt="" height="19" width="100%" src="el/fin_barre_h_h.gif"></TD> <TD width="19" height="19"><IMG alt="" width="19" height="19" src="el/fin_coin_1.gif"></TD> </TR> <TR><TD width="19" height="100%"><IMG alt="" width="19" height="100%" src="el/fin_barre_v_g.gif" /></TD> <TD>Les plus désespérés sont les chants les plus beaux,<BR>Et j'en sais d'immortels qui sont de purs sanglots.<BR><BR>La muse<BR>Alfred de Musset (1810-1857)</TD> <TD width="19" height="100%" style="font-size: 0;line-height:0;"><IMG style="font-size: 0;line-height:0;" alt="" width="19" height="100%" src="el/fin_barre_v_d.gif" /></TD> </TR> <TR><TD width="19" height="19"><IMG alt="" width="19" height="19" src="el/fin_coin_3.gif"></TD> <TD height="19"> <IMG alt="" height="19" width="100%" src="el/fin_barre_h_b.gif"></TD> <TD width="19" height="19"><IMG alt="" width="19" height="19" src="el/fin_coin_2.gif"></TD> </TR> </tbody> </TABLE> </CENTER> </BODY>


Je peux vous assurer avoir essayé toutes les propriétés CSS possibles sur les TD et IMG pour faire disparaitre ces deux lignes blanches non désirées, sans succès. (display, box-sizing, line-height, font-size, ...). J'ai tenté également de mettre un DIV pour encapsuler l'IMG, avec un "height" à 100%, sans succès.

Après quelques heures éprouvantes, j'ai testé le même code HTML sur un linux, les tests précédents ont été réalisés sous Windows 7 avec Opera, Chrome, IE. Et bingo, le code HTML s'affichait correctement avec firefox. De nouveau de retour sur W7, et bing encore: Firefox affiche correctement le code HTML. La malchance d'avoir testé sur 3 navigateurs, et d'oublier FF.

Maintenant j'ai une piste. Le problème vient donc de certains navigateurs, certainement racistes envers les tag TABLE. Je m'en doutais, car depuis l'inquisition espagnole, on a rarement vue un tel sectarisme religieux concernant ces pauvres TABLE parmi la communauté HTML.

Me voila donc reparti sur une recherche de solution pour tous les navigateurs.
Suite au prochain épisode (je l'espère, sinon cela veut dire que le sectarisme a gagné ;)

Zip contenant l'exemple

mardi 18 août 2015

GTK3: rudiment pour déplacer un objet sur une fenêtre (drag sans le drop)


GTK3 n'est pas la panacée, mais ayant décidé d'investir du temps et de l'énergie, je persévère à élaborer de simples exemples (qui manque parois cruellement pour GTK3 spécifiquement).

Le but de cet exemple de code, est de pouvoir afficher un "insert" (cad une image qui s'insert sur une autre), et de pouvoir déplacer cette image à l'aide de la souris. Pour varier les plaisirs, j'ai décider de changer l'insert quand on le déplace.
Pour cela, il faut 3 fichiers PNG, le premier pour l'image de fond (sur lequel va se déplacer l'insert), le deuxième pour l'insert "au repos", et un troisième identique en forme et en taille au précédent, mais plus transparent ou plus sombre que le précédent. Le dernier PNG est l'insert affiché quand on le déplace. Le but est d'obtenir un effet visuel pendant la phase de déplacement de l'insert.

Il existe j'imagine de très nombreuses stratégies, plus où moins documentées, pour effectuer ce genre de tache. Le problème est d'économiser les ressources de la machine, car théoriquement, à chaque micro-déplacement de la souris (bouton appuyé), on devrait ré-afficher la totalité du contenu de la fenêtre.

Voici ma proposition de stratégie, résumée en quelques points:
  • Utilisation d'un élément Widget de type DRAW pour "dessiner" le fond de l'image et l'insert qui doit pouvoir se déplacer.
      image = gtk_drawing_area_new();
  • Utilisation d'une "surface" (au sens CAIRO du terme) pour dessiner préalablement la totalité de la fenêtre (cad le widget DRAW.
      cairo_image_surface_create(CAIRO_FORMAT_ARGB32, l, h )
  • La modification effective du Widget ne se fait qu'à travers l'événement "draw" que l'on récupère dans la callBack, avec la fonction suivante
        g_signal_connect(image, "draw",    G_CALLBACK(on_draw), NULL); 
  • Quant on veut "redessiner" le widget 'image', on provoque l'envoie d'un événement 'draw' avec la fonction asynchrone suivante:
       gtk_widget_queue_draw_area(image, 0, 0, l_image, h_image);
  • On traite individuellement les 3 événements concernant la souris et nécessaire à notre besoin, le déplacement, le bouton appuyé, le bouton relâché:
     g_signal_connect(image,"motion-notify-event",G_CALLBACK(on_motion),NULL);
     g_signal_connect(image,"button-release-event", G_CALLBACK(on_button_release),NULL);
     g_signal_connect(image, "button-press-event",G_CALLBACK(on_button_press), NULL);
  • L'événement 'bouton appuyé' permet de positionner le programme en mode 'déplacement', et de modifier l'affichage de l'insert (celui-ci est remplacé par le troisième PNG).
  • L'événement 'déplacement de la souris' permet de repositionner tout simplement l'insert (uniquement si l'on est en mode déplacement).
  • L’événement 'bouton relâché' permet alors de redessiner l'insert à l'aide du deuxième PNG, pour redevenir un insert banal.
  • Pour chacun des trois événements traités (concernant la souris), on adopte le même principe en deux étapes:
     1- on modifie la surface 'image' sans modifier le Widget, à l'aide de routines CAIRO classiques.
     2- on provoque de manière asynchrone un événement 'draw' qui se chargera de modifier effectivement le Widget 'image' (de type Draw), à l'aide de la surface 'image' préalablement modifiée.
Cette méthode qui consiste à ne pas modifier directement le wiget 'image' (contrairement à ce que je fais habituellement avec un pixbuf classique), fût à l'origine une obligation, car je n'ai pas été foutu de trouver la fonction GTK permettant de créer un 'context' (au sens CAIRO du terme) dynamiquement à partir du Widget, qui m'aurait donc permis de dessiner directement sur le Widget. J'ai pour cela utilisé la callback de l'événement draw, par dépit en quelque sorte. Mais cette contrainte est finalement une bonne chose, car la méthode asynchrone est plutôt vertueuse, même si elle n'est peut-être pas efficace en terme de réactivité, car gourmande en ressource machine.

Avant de vous donner le code complet, je dois avouer avoir essayé d'optimiser le code en limitant la modification de la surface à redessiner à la zone rectangulaire de la surface réellement impacté par le déplacement (en calculant la zone entre le futur déplacement et la précédente position de l'insert). Malheureusement, cette gestion oblige à ajouter beaucoup de code relativement complexe et difficile à vérifier. En considérant le bénéfice apporté par rapport à la complexité induite, j'ai lâchement abandonné cette partie du code. En finale, j'ai donc opté pour le plus raisonnable (mais le plus coûteux en ressource machine): à chaque déplacement de la souris bouton appuyé, je redessine la totalité de l'image (en tout cas la partie affichée en fonction de la taille de la fenêtre), tant sur la surface préalable, que sur le Widget.

Voici donc le code. Il est très verbeux, peu optimisé, et provoque énormément d'écriture sur la console, mais tout cela dans un but pédagogique (ou de recherche d'erreur). Il doit bien rester quelques dizaines de bug, mais cela est une autre histoire. Et n'oubliez pas de positionner dans le répertoire de compilation, 3 fichiers PNG ('bjr.png'; 'insert.png', et 'insert_T.png').

A votre bon cœur pour vos remarques.

#include <cairo.h>
#include <gtk/gtk.h>

//--- prototypes internes
static gboolean on_motion (GtkWidget *widget, GdkEventMotion *event, gpointer data);
static gboolean on_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data);
static gboolean on_button_release (GtkWidget *widget, GdkEventButton *event, gpointer data);
static gboolean on_draw( GtkWidget *    widget, cairo_t *cr, gpointer user_data);
static gboolean on_configure(GtkWidget *widget, GdkEventConfigure *event, gpointer data);
static void activate (GtkApplication *app, gpointer user_data);
static void on_close_window (void);
void    dessiner(cairo_surface_t *surface, int x, int y, float ratio);
void redessiner( int x, int y, int l, int h );
cairo_surface_t * dupliquer_surface(cairo_surface_t *surface, float ratio);

// variable static
static  cairo_surface_t * surface_image;    //-- surface destiné à être affichée sur le widget de type "Draw" (image)
static  cairo_surface_t * surface_fond;     //-- surface provenant du PNG servant d'image de fond sur laquelle on ajoute des inserts
static  cairo_surface_t * surface_insert;    //-- surface provenant du PNG représentant un insert
static  cairo_surface_t * surface_insert_T; //-- surface provenant du PNG représentant le même insert mais sombre et plus transparent
static  cairo_surface_t * surface_insert_T_m; //-- insert modifié en applicant un ration pour réduire sa taille affichée
static    GtkWidget *window;
static    GtkWidget *image;
static int h_insert, l_insert;
static int h_image, l_image;
static int flag_deplacement=0;    //-- mode de déplacement (en laissant appuyé le bouton de la souris et en la déplaçant)
static int flag_deplacement_precis=0;    //-- mode de déplacement plus précis (avec le bouton droit)
int compteur=0;                    //-- pour suivre le nombre d'évènement "Draw" traité
int x_precedent=0,y_precedent=0;    //position de l'insert validé       
int x_reference=0,y_reference=0;    // position de référence du tout premier click (pour un déplacement par rapport à l'insert validé précédement
float ratio_insert=0.5;
float coef_precision=0.3;

#define max(a,b) (a>=b?a:b)
#define min(a,b) (a<=b?a:b)



int main(int argc, char *argv[])
{
   
    GtkApplication *app;
    int status;

    app = gtk_application_new ("vb.gtk.example", G_APPLICATION_FLAGS_NONE);
    g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
    status = g_application_run (G_APPLICATION (app), argc, argv);
    g_object_unref (app);
    return status;
}

static void activate (GtkApplication *app, gpointer user_data)

        cairo_t *cr;

    //----- chargement des PNG et superposition  du PNG secondaire (insert)
    // la surface "image" est celle sur laquelle on dessine pour ensuite l'afficher
    // Les 3 autres surfaces "fond", "insert", "insert_t" sont des images
    //   que l'on dessine sur la surface de référence, et sont respectivement
    //   le fond de l'image, un insert transparent, et un insert sombre transparent
    surface_image = cairo_image_surface_create_from_png("bjr.png");
    surface_fond = cairo_image_surface_create_from_png("bjr.png");
    surface_insert = cairo_image_surface_create_from_png("insert.png");
    surface_insert_T = cairo_image_surface_create_from_png("insert_T.png");
    l_insert= cairo_image_surface_get_width(surface_insert_T);
    h_insert= cairo_image_surface_get_height(surface_insert_T);
    surface_insert_T_m= dupliquer_surface(surface_insert_T,ratio_insert);
    if (cairo_surface_status(surface_fond)!=CAIRO_STATUS_SUCCESS)        printf("\n---image de fond introuvable\n");
    if (cairo_surface_status(surface_insert)!=CAIRO_STATUS_SUCCESS)        printf("\n---image 'insert' introuvable\n");
    if (cairo_surface_status(surface_insert_T)!=CAIRO_STATUS_SUCCESS)    printf("\n---image 'insert sombre' introuvable\n");
    if (cairo_surface_status(surface_insert_T_m)!=CAIRO_STATUS_SUCCESS)    printf("\n---image 'insert sombre' non adaptable\n");
   
    printf("\n(insert) l=%i, h=%i\n", l_insert, h_insert);
   
      window = gtk_application_window_new (app);
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
      image = gtk_drawing_area_new();
    gtk_container_add(GTK_CONTAINER (window), image);


  /* Event signals */
    g_signal_connect(window, "destroy",        G_CALLBACK(on_close_window), NULL);
    g_signal_connect(image, "draw",    G_CALLBACK(on_draw), NULL);
    g_signal_connect(image, "motion-notify-event", G_CALLBACK (on_motion), NULL);
    g_signal_connect(image, "button-release-event", G_CALLBACK (on_button_release), NULL);
    g_signal_connect(image, "button-press-event", G_CALLBACK (on_button_press), NULL);
    g_signal_connect(image,"configure-event", G_CALLBACK (on_configure), NULL);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 450, 300);
    gtk_window_set_title(GTK_WINDOW(window), "Drag image");
   
/* Ask to receive events the drawing area doesn't normally
 * subscribe to. In particular, we need to ask for the
 * button press and motion notify events that want to handle.
 */
  gtk_widget_set_events (image, gtk_widget_get_events (image)
                                     | GDK_BUTTON_PRESS_MASK
                                     | GDK_BUTTON_RELEASE_MASK
                                     | GDK_POINTER_MOTION_MASK);

    gtk_widget_show_all(window);
}


static gboolean on_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
    //printf("\non_button_press()\n");
    // event->button: GDK_BUTTON_SECONDARY  GDK_BUTTON_PRIMARY
   
    flag_deplacement_precis=0;   
    if ( event->button==GDK_BUTTON_SECONDARY )
    {
        //---on double la précision du déplacement
        flag_deplacement_precis=1;
        printf("\n--déplacement de précision---\n");
    }
    switch(event->type)
    {
    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
        x_precedent=0;
        y_precedent=0;
    case GDK_BUTTON_PRESS:
        if (!x_precedent && !y_precedent)    //--on modifie la position de l'insert
        {
            x_precedent= event->x - l_insert*ratio_insert/2;
            y_precedent= event->y - h_insert*ratio_insert/2;
        }
        x_reference=event->x;    //-- on sauvegarde la position du click
        y_reference=event->y;    // pour réaliser un déplacement realtif par la suite
        printf(" Déplacement ON; (click) x=%i,y=%i (precedent) x=%i,y=%i  (image: l=%i;h=%i)\n",(int)event->x, (int)event->y, x_precedent, y_precedent,l_image,h_image);
        flag_deplacement=1;
        redessiner(0,0,0,0);    //--redessiner le fond dans la surface destinées à être afichée
        dessiner(surface_insert_T_m, x_precedent, y_precedent, 1);
        gtk_widget_queue_draw_area(image,0, 0, l_image, h_image); // on force l'affichage complet

    }

  /* pas de propagation */
  return TRUE;
}
static gboolean on_button_release (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
    char c[100];
    int t_x=(int)(event->x-x_reference)*(flag_deplacement_precis?coef_precision:1);
    int t_y=(event->y-y_reference)*(flag_deplacement_precis?coef_precision:1);
    int x =x_precedent+t_x, y=y_precedent+t_y;
    flag_deplacement=0;
    printf(" Déplacement OFF; x,y=%i,%i (t=%i;%i)\n",x, y, t_x, t_y);

    //event->button == GDK_BUTTON_PRIMARY; GDK_BUTTON_SECONDARY
    dessiner(surface_fond, 0, 0, 1);    //-- redessine totalement le fond
    dessiner(surface_insert, x, y, ratio_insert);
    gtk_widget_queue_draw_area(image,0, 0, l_image, h_image);
    x_precedent=x;
    y_precedent=y;
    sprintf(c, "Drag Image - insert déplacé=>(%i,%i)", x,y);
    gtk_window_set_title(GTK_WINDOW(window), c);


  /* Pas de propagation */
  return TRUE;
}

/* Handle motion events by continuing to draw if button 1 is
 * still held down. The ::motion-notify signal handler receives
 * a GdkEventMotion struct which contains this information.
 */
static gboolean on_motion(GtkWidget *widget, GdkEventMotion *event, gpointer data)
{
    // if (event->state & GDK_BUTTON1_MASK);
    if (flag_deplacement)
    {
        int x =(int)event->x, y=(int)event->y;
        int t_x=(x-x_reference)*(flag_deplacement_precis?coef_precision:1);
        int t_y=(y-y_reference)*(flag_deplacement_precis?coef_precision:1);
        int x_dest=x_precedent+t_x, y_dest=t_y+y_precedent;
        if (flag_deplacement_precis)
        {
            t_x=t_x/4;
            t_y=t_y/4;
        }

        printf("  déplacement (t=%i,%i) x,y=(%i,%i); précédent=(%i,%i)\n",t_x,t_y, x_dest, y_dest,x_precedent,y_precedent);

        if ( (x_dest+l_insert*ratio_insert)>-1 && (y_dest+h_insert*ratio_insert)>-1 && x_dest<=l_image && (y_dest<=h_image) )
        {
            redessiner(    0, 0, 0,0);
            dessiner(surface_insert_T_m, x_dest, y_dest, 1);
            gtk_widget_queue_draw_area(image, 0, 0, l_image, h_image);
        }
        /* Pas de propagation de cet événement */
         return TRUE;
    }
    //-- propagation de l'événement
    return FALSE;
}

/* CallBack pour traiter les changement de dimensionde la fenêtre */
static gboolean on_configure(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
{
    printf("\non_configure()\n");
   
    h_image=gtk_widget_get_allocated_height(image);
    l_image=gtk_widget_get_allocated_width(image);

 /*--copier la surface image (buffer contenant le plateau)
  *  sur le contexte de l'objet de type Draw
  * avec les inserts déjà positionnés----*/
    cairo_t *cr;
    cr= cairo_create(surface_image);
    cairo_set_source_surface(cr, surface_fond, 0, 0);
    cairo_paint(cr);
    cairo_set_source_surface(cr, surface_insert, 50, 50);
    cairo_paint(cr);
    cairo_scale(cr,ratio_insert,ratio_insert);   
    cairo_set_source_surface(cr, surface_insert_T, 450, 150);
    cairo_paint(cr);

   
    //--- écriture sur le PNG principal
    cairo_scale(cr,1/ratio_insert,1/ratio_insert);    //--on reveient à l'échelle 1 (cad ratio=1)
    cairo_set_font_size(cr, 20);
    cairo_set_source_rgb(cr, 0.9 , 0.9 , 0.9);
    cairo_move_to(cr, 20, 20);
    cairo_show_text(cr, " flippermania ");
    cairo_stroke(cr);
   
    cairo_destroy(cr);
   
  /* Propagation */
  return FALSE;
}

static gboolean on_draw( GtkWidget *    widget, cairo_t *cr, gpointer user_data)
{
    printf("\non_draw() #%i\n", compteur++);
 /*--copier la  surface (buffer contenant le plateau) sur le contexte de l'objet ----*/

    cairo_set_source_surface(cr, surface_image, 0, 0);
    cairo_paint(cr);
   
    return TRUE;
}

static void on_close_window (void)
{

    if (surface_image)    cairo_surface_destroy (surface_image);
    if (surface_insert)   cairo_surface_destroy (surface_insert);

    // gtk_main_quit (); provoque une erreur
}

//---- pour dessiner une image sur la surface destinée à être affichée
void dessiner(cairo_surface_t *surface, int x, int y, float ratio )
{
   
    cairo_t *cr;

    cr= cairo_create(surface_image);
    if (ratio!=1)    cairo_scale(cr,ratio,ratio);
    cairo_set_source_surface(cr, surface, x/ratio, y/ratio);
    cairo_paint(cr);
    cairo_destroy(cr);
}

//--- cad redessiner le fond de l'image (pour faire disparaitre les inserts)
//    sur la surface destiné à être affiché dans l'évènement "Draw"
void redessiner( int x, int y, int l, int h )
{
    cairo_t *cr_out;
    cairo_surface_t *surface;
    if (l==0)    l=l_image;
    if (h==0)    h=h_image;
    surface= cairo_surface_create_for_rectangle (surface_fond, (double)x, (double)y, (double) l, (double) h);
    dessiner(surface,x,y, 1);
    cairo_surface_destroy(surface);
}

//---- duppliquer une surface en appliquant un ratio (réducteur ou non)
cairo_surface_t * dupliquer_surface(cairo_surface_t *surface, float ratio)
{
    cairo_surface_t * s= NULL;
    int l,h;
    cairo_t *cr;
   
    l= cairo_image_surface_get_width(surface_insert_T);
    h= cairo_image_surface_get_height(surface_insert_T);

    if (cairo_surface_status(surface_insert_T)!=CAIRO_STATUS_SUCCESS || l<1 || h<1 || ratio<=0)       return s;
   
    s= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, l*ratio, h * ratio);
    cr= cairo_create(s);
    cairo_scale(cr,ratio,ratio);
    cairo_set_source_surface(cr, surface, 0, 0);
    cairo_paint(cr);
    cairo_destroy(cr);
    return s;
}

jeudi 13 août 2015

GTK3 - le soucis des clicks de souris sur une image munie d'ascenseurs (scrolled window)

Voici un soucis qui m'a fait perdre une journée entière. Et pourtant la solution était si simple.

Description du besoin: traiter les click de souris sur une image, pour déterminer les coordonnées du click (x,y) relatif à cette image. Pas de soucis théoriquement, si vous utilisez la fonction permettant de paramétrer une boucle de traitement des événements concernant le bouton de la souris:
g_signal_connect(G_OBJECT(window),"button-press-event",
                    G_CALLBACK(on_button_press), NULL);
Vous pouvez ainsi récupérer le x,y (et plein d'autre info) concernant chaque click de souris.
Pour illustrer le problème, voici le code source pour afficher le x,y de chaque click sur la fenêtre contenant l'image (widget GTK IMAGE).

#include
#include

gboolean on_button_press (GtkWidget *widget, GdkEventButton *event);

static  GtkWidget *window;
static  GtkWidget *scrolled_window;
static  GtkWidget *image;

static void
activate (GtkApplication *app, gpointer user_data)
{
/* Création des widgets de l'application */
  window = gtk_application_window_new (app);
  gtk_window_set_title (GTK_WINDOW (window), "ScrolledWindow");
  gtk_window_set_default_size (GTK_WINDOW (window), 220, 200);

/* Création de la "scrolled window". Paramètres NULL :
 * création automatique des ascenseurs verticaux et horizontaux.
 * "scrollbar policy" automatique également:
 * la visibilité des ascenseurs est automatique (cad si nécessaire).
 */
    scrolled_window = gtk_scrolled_window_new (NULL, NULL);
    gtk_scrolled_window_set_policy(    GTK_SCROLLED_WINDOW (scrolled_window),
                                    GTK_POLICY_AUTOMATIC,
                                    GTK_POLICY_AUTOMATIC);
/* Bordure permettant de ne pas coller les ascenseurs
 *  aux bords de la fenêtre principale (purement esthétique)
 */
    gtk_container_set_border_width (GTK_CONTAINER (scrolled_window), 10);
/* Création d'un Widget IMAGE à partir d'un fichier */
    image = gtk_image_new_from_file ("bjr.jpg");
/* Encapuslation des widget créés*/
    gtk_container_add( GTK_CONTAINER(scrolled_window),    image);
    gtk_container_add( GTK_CONTAINER(window),            scrolled_window);

/* Boucle de traitement des évènements concernant les boutons de la souris */
    g_signal_connect(G_OBJECT(window),    "button-press-event",
                    G_CALLBACK( on_button_press    ), NULL);

    gtk_widget_show_all (window);
}

gboolean on_button_press (GtkWidget *widget, GdkEventButton *event)
{
    gint x = 0;
    gint y = 0;
    GtkAllocation allocation;


    /* Determine la nature du click (simple, double ou triple) */
    switch (event->type)
    {
    case  GDK_2BUTTON_PRESS:
      printf("Click2 x=%g;y=%g\n",event->x,event->y);
    break;
    case  GDK_3BUTTON_PRESS:
      printf("Click3 x=%g;y=%g\n",event->x,event->y);
    break;
    case  GDK_BUTTON_PRESS:
        printf("\nClick x=%i;y=%i; button=%i; root=(%i,%i)\n",(int)event->x,(int)event->y,event->button,(int)event->x_root,(int)event->y_root);
        gtk_widget_get_allocation(image,&allocation);
        printf("    height=%i; width=%i; x=%i; y=%i; (image) \n",allocation.height, allocation.width, allocation.x, allocation.y);
        gtk_widget_get_allocation(scrolled_window,&allocation);
        printf("    height=%i; width=%i; x=%i; y=%i; (scrolled_window) \n",allocation.height, allocation.width, allocation.x, allocation.y);
        gtk_widget_get_allocation(window,&allocation);
        printf("    height=%i; width=%i; x=%i; y=%i; (window) \n",allocation.height, allocation.width, allocation.x, allocation.y);
     
    break;
    }
    /* Propagation de l'evenement */
    return FALSE;
}

int
main (int argc, char **argv)
{
  GtkApplication *app;
  int status;

  app = gtk_application_new ("vb.gtk.example_scrolling", G_APPLICATION_FLAGS_NONE);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
  status = g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);

  return status;
}
Vous pouvez compiler l'exemple avec cette ligne:
    gcc `pkg-config gtk+-3.0 --cflags` scrolling_exemple.c -o scrolling_exemple `pkg-config gtk+-3.0 --libs`
N'oubliez pas de mettre dans le même répertoire une image relativement grande (800x800 au minimum), ou alors modifiez dans le code le nom du fichier chargé localement (bjr.jpg).

Ensuite, amusez vous à cliquer sur la fenêtre contenant l'image. Vous remarquerez rapidement que si vous jouez sur la position des ascenseurs, les coordonnées affichées ne sont pas influencées par la translation de l'image  asservie aux ascenseurs ("scrolled window" dans la terminologie GTK).

Pour résoudre le problème, j'ai tenté de jouer sur la position des ascenseurs, pour calculer manuellement la translation ("adjustment" dans la terminologie GTK), et ajouter cette translation aux (x,y) transmis dans l’événement. Malheureusement, cette stratégie est une impasse pour différentes raisons trop longues à expliquer (position du widget par rapport à la fenêtre, dilatation du widget Image, etc...).

La solution pour pouvoir récupérer les coordonnées relative à l'image (et non à la fenêtre principale hébergeant la "scrolled window" et le widget Image) est simplissime:
mettre la "scrolled window" dans un "eventBox" (Widget permettant de récupérer et de traiter les événements traditionnels d'une fenêtre pour les widgets ne pouvant pas le faire, à l'instar d'un widget Image).
En faisant cela, miracle, le (x,y) du click sur l'image est dorénavant relatif à l'image. Pour cela, il suffit d'ajouter deux lignes et d'en modifier une:

    /* Encapuslation des widget créés*/
    eventBox=gtk_event_box_new();
    gtk_container_add( GTK_CONTAINER(eventBox),            image);
    gtk_container_add( GTK_CONTAINER(scrolled_window),    eventBox);
    gtk_container_add( GTK_CONTAINER(window),    scrolled_window);

N'oubliez pas d'ajouter la variable static correspondante à ce nouveau widget:
static  GtkWidget *eventBox;
J'avoue avoir trouvé cette solution par hasard, sans vraiment la comprendre. Après réflexion, je crois pouvoir donner une explication: Sans le "EventBox", le click de la souris se comporte comme ci le widget Image n'existait pas, puisque ce Widget ne peut pas traiter ce type d’événement. Alors que si on transforme ce Widget Image en un objet pouvant le faire, son propre référentiel de coordonnées s'applique et prime alors sur celui de la fenêtre. CQFD (enfin je crois).

Pour finir, il reste un problème important: on reçoit également les événements en dehors de l'image. Pour cela, il faut  "filtrer" les événements qui ne concernent pas le Widget Image. Solution la plus simple: "connecter un signal" spécifique à l'objet "eventBox". Solution alternative : filtrer dans la "callBack" les événements avec un simple test sur la valeur du Widget de la CallBack.

    /* Boucle de traitement des événements concernant les boutons de la souris */
    g_signal_connect(G_OBJECT(eventBox),    "button-press-event",
                    G_CALLBACK( on_button_press    ), NULL);

Et pour être totalement franc avec vous, il restera encore un cas particulier à traiter: la dilatation automatique de l'objet Image, qui s’agrandit automatiquement si on agrandit la fenêtre principale au delà des limites de l'image (cad en faisant disparaître les ascenseurs de l'image). Pour résoudre cela, il faut alors tester la taille effective de l'objet Image (que vous pouvez tester avec l'exemple ci-dessous avec la fonction "location" (dans la terminologie GTK), et appliquer une translation des x,y en conséquence, dans le cas ou la taille du Widget Image dépasse celle de l'image originelle.

Pour finir, sachez qu'avec un Widget "Draw" (pour afficher l'image munie d'ascenseurs sur le même principe), le problème est le même, et la solution identique. En revanche, vous n'aurez pas à gérer la dilatation automatique de l'objet (pas besoin de translater si on agrandie la fenêtre). .

J'espère que ce petit exemple de code vous permettra d'économiser la journée que j'ai perdue sur ce si petit problème.

Doc concernant l’événement traité.
Exemple décrivant l'usage d'une "scrolled Window"
Code source complet avec la gestion de l'image dilatée.

GTK3 - attention à bien débuter, sinon vous risquez de perdre beaucoup de temps.


J'ai eu la faiblesse de croire que GTK, la librairie graphique sensée permettre de programmer facilement des applications graphiques, me ferait gagner du temps. Au départ, j'ai commencé à programmer avec Xlib (cad au niveau du serveur X Window). Par la suite, j'ai basculé mon développement en GTK (pour sa popularité et son ancienneté). Malheureusement, j'ai très vite déchanté: le gain de temps (cad en terme de réduction de code) ne fut pas évidente, car les appels aux fonctions à GTK sont finalement presque aussi nombreux que ceux à Xlib.

Mais le point le plus négatif fut le problème initialement sous-estimé des différentes versions de GTK. Pour faire simple, 3 versions majeurs de GTK ont donné lieu par deux fois, à un cortège de fonctions obsolètes ou disparues, et de modules remplaçant certains autres, et de concepts fondamentalement transformés. Le principal inconvénient concerne la documentation, mais surtout les différents exemples présents sur le Net. J'ai donc trop souvent perdu du temps à étudier un exemple de code écrit pour GTK1 ou GTK2, sans même le savoir, car trop souvent cette information n'est même pas mentionnée. On le découvre bien souvent lors de la compilation avec les options et librairies GTK3.

Ayant pris la décision dès le départ de développer en GTK 3, j'ai ainsi perdu trop d'énergie avec la documentation disponible sur le Net, qui très rarement ne concerne pas réellement GTK3. Idem pour les livres, qui bien souvent ne signale même pas le n° de version traitée par l'auteur.

Mon premier conseil: n'achetez pas de livre sur GTK dont la première édition date d'avant GTK3 (début 2011). Ils sont rares, voir inexistant. Sinon, vous risquez de perdre du temps à étudier des fondamentaux inutiles et de programmer avec des concepts de GTK2 (voir GTK1), dorénavant obsolètes.

Mon deuxième conseil est simple:
Pour étudier des exemples de code, n'utilisez que ceux présents sur gnome.org (ci-dessous). Malheureusement, tous les autres risquent de vous envoyer vers des solutions inadaptées, et de vous faire perdre beaucoup de temps. A noter que même sur les exemples GTK3 officiels, vous serez susceptible de rencontrer des fonctions obsolètes, car GTK3 évolue très rapidement.

Donc, ne perdez pas de temps (comme moi) avec des PDF ou documentation "offline" qui ne soit pas téléchargeable sur le site officiel, avec la dernière version en date. Oubliez donc dans un premier temps vos habitudes de recherche de code sur le Net "au petit bonheur la chance". Limitez-vous strictement vous au exemple de code du site officiel (ci-dessous). Tous les concepts y sont présents sans exception.

Et oubliez les conseils des anciens qui vous diront que GTK2 est bien suffisant, et que le passage de GTK2 vers GTK3 peut se faire à peu de frais. Les conseilleurs ne sont pas les payeurs.

Avec un peu de chance, vous ne perdrez pas les jours que j'ai pour ma part dépensé inutilement à cause de cela. Bonne continuation à vous.

Liens vers les exemples de code pour GTK3
Liens vers la référence pour GTK
Wiki concernant GTK


L'intelligence, c'est d'avoir raison la deuxième fois. Avoir raison la première fois, c'est simplement de la chance.

mardi 14 avril 2015

Ecran noir après une mise à jour ubuntu

Cela fait la deuxième fois, sur 2 machines différentes, et sur 2 Linux différents.  Après une simple mise à jour proposée par le logiciel de mise à jour intégré, impossible de rebooter normalement: écran noir.
La première fois, c'était sur un lubuntu, avec un PC de bureau. J'ai préféré réinstaller totalement le PC en question. Mais la deuxième fois, ce fut sur Xubuntu, et sur un portable Asus  relativement récent. Cette fois, j'ai préféré trouvé une solution ne nécessitant pas une réinstallation, et de perdre ainsi les différents profils ou logiciels déjà installés.

Voici donc ma solution, après quelques longues heures de tatonement et de recherches peu fructueuses sur le Net J'en profite tout de même pour rendre hommage à Linux pour sa modularité, et à sa communauté pour la quantité d'information disponible pour tous les publics.

Tout d'abord le contexte:

Portable Notebook Asus F200M-Bing
Xubuntu LTS 14.04.02
Date de la mise à jour fautive: 13/04/2015
Dernier kernel: linux 3.13.0.49
Précédent kernel : linux 3.13.0.39
Intel celeron N2840
VGA ValleyView Gen7

Dans cet état, mon notebook boootait normalement, sauf que j'avais un bel écran noir, et rien d'autre. Le disque dur ou le WIFI fonctionnait semble-t-il normalement. J'ai donc regardé sur le Net pour trouver une solution, avec un bagage de parfait néophyte (les informations ci-dessus ont été collectées après avoir glané différentes informations).

En résumé (et au lieu d'un compte rendu de nombreuses heures infructueuses), voici ma solution:

1- Activer le menu de Grub au démarrage.
Pour cela laisser appuyée la touche SHIFT (majuscule) pendant le boot.
Un menu en mode caractère devrait vous afficher 4 choix:
 Ubuntu
 Advanced options for Ubuntu
 Memory Test (....)
 Memory Test (.......)

2- Choisir l'option "options avancées"
Grub va ensuite vous proposer plusieurs "kernel", les derniers mis à jour logiquement. Il faut alors procéder par essais successifs, sans se poser de question. Personnellement, je les ai tous essayé. En mode recovery, tous fonctionnait. Sinon, les autres modes fonctionnaient, sauf pour le dernier kernel en date (qui est le kernel par défaut évidement).
Conclusion toute personnel: le dernier kernel en mode normal posait un problème (lapalissade tranquillisante).

3 - Supprimer le dernier kernel
en se référant à son nom, avec synaptic, en utilisant un kernel fonctionnel. L'avantage de synaptic, c'est que la suppression est automatique, et la gestion de Grub aussi semble-t-il. Il faut simplement le trouver dans les paquets déjà installés, avec le mot clé "linux-image" (avec un tiret). Après il faut le supprimer, prier, et rebooter. Pour ma part cela a fonctionné


J'avoue avoir rechercher un moyen de réparer le dernier kernel (en mode recovery), mais sans succès. Il faut savoir connaitre les limites d'un combat. Voici plusieurs pistes qui m'ont semblé prometteuses mais sans succès:

a- Réinstallation de xorg (en forçant): choux blanc.

b- Utilisation de l'installeur de driver graphique Intel, que l'on peut trouver facielement le Net. Malheureusement, cet installeur venait juste à l'instant (2 semaines avant) d'être déclaré incompatible avec ma distribution Ubuntu (14.04). C'est ballot. Les raisons de cette "deprecation" semblent lié à mon soucis, mais pas moyen de le déterminer à la lecture des posts traitant le sujet.

Pour finir, j'ai perdu beaucoup de temps à comprendre la logique de Grub (juste assez pour faire des essais). A signaler que même les raccourcis clavier CTRL-ALT-F1 ne fonctionnait pas (jusqu'à F7). En revanche, passer en aveugle la série CTRL-ALT-F1, permettait ensuite de faire un CTRL-ALT-SUPP salutaire pour rebooter relativement proprement après chaque essais infructueux de kernel dans Grub. J'ai aussi perdu du temps à modifier le paramétrage de Grub (GRUB_TERMINAL="console" et GRUB_CMDLINE_LINUX_DEFAUT="quiet")

Un grand merci à Grub, qui malgré son relativement manque d'ergonomie, m'a sauvé d'une perte totale en permettant un retour arrière.

Liens relatifs à cette problématique:

Explication de la non compatibilité de l'installateur Intel de pilote graphique pour linux

Grub et quelques explications

 Installateur (qui ne marche pas pour ma distribution)

PS: je regrette de ne pas avoir pu tester les options accessibles par la touche F6, en particulier l'option nomodeset, pour déterminer si cela aurait résolu mon soucis d'écran noir. Ce sera pour une prochaine fois:
Une belle explication de nomodeset
Une autre explication de la touche F6 et ses options

vendredi 10 avril 2015

Résoudre l'énigme "TOI+MOI+LUI=NOUS" .... en langage C

Il s'agit d'une petite énigme que les enfants s'échangent à la court de récréation (seulement les plus geek). L'énoncé est simple:

Il faut trouver les chiffres correspondant à chaque lettre de l'équation "MOI+TOI+LUI=NOUS", en faisant correspondre une et une seule lettre à chacune de ces huit lettre composant cette équation.

Par dépit vis à vis de ma fille qui m'a mis au défis, j'ai tenté de résoudre le code (cad la correspondance entre les lettres et les chiffres), en utilisant un outil informatique. J'avoue avoir perdu une bonne heure avec Excel, avant de m'apercevoir que le nombre de permutation (cad le nombre de possibilité de code différent à tester) était supérieur au million, ce qui dépasse le raisonnable dans une simple feuille de tableur.

J'ai donc refait l'exercice en langage C, pour tester une à une (de manière récursive pour le plaisir) chacune des près de 2 millions de possibilités. Pour rappel, les permutations possibles de codes différents pour résoudre l'équation se calcule comme cela: 10 x 9 x 8 x 7 x 6 x 5 x 4 x 3 (10 chiffres à répartir parmis 8 lettres, dans tous les ordres possibles). Mon erreur de débutant fut au début de confondre les combinaisons et les permutations (c'est évident après coup, et encore plus après avoir perdu une heure sur cette erreur). Pour rappel, les combinaisons de 8 chiffres parmi 10 sont seulement de 45.


Pour ceux que cela intéresse, le code est en pièce jointe, avec les fichiers de compilation sous linux.
Le résultat des courses: il existe 216 solutions à cette équation. Pour finir, je me suis amusé à étudier les combinaisons les plus prolixes parmi les 45 possibles, cette fois avec un tableur, en quelques minutes. Cela donne 18 solutions pour une seule combinaison de chiffres. Et le chiffre le plus représenté parmi les 216 solutions de code est le "1" (204 fois sur 216 tout de même). Je l'aurais parié. Mais c'est encore mieux de le prouver par la démonstration, avec un compilateur C et un tableur.

Parfaitement inutile, donc totalement indispensable.


0 1 2 3 4 5 6 7 8 9
nb de solution 216
1 1 1 1 1 1 1 1 0 0 1111111100 6
1 1 1 1 1 1 1 0 1 0 1111111010 0
1 1 1 1 1 1 0 1 1 0 1111110110 6
1 1 1 1 1 0 1 1 1 0 1111101110 0
1 1 1 1 0 1 1 1 1 0 1111011110 6
1 1 1 0 1 1 1 1 1 0 1110111110 0
1 1 0 1 1 1 1 1 1 0 1101111110 0
1 0 1 1 1 1 1 1 1 0 1011111110 0
0 1 1 1 1 1 1 1 1 0 0111111110 6
1 1 1 1 1 1 1 0 0 1 1111111001 6
1 1 1 1 1 1 0 1 0 1 1111110101 6
1 1 1 1 1 0 1 1 0 1 1111101101 6
1 1 1 1 0 1 1 1 0 1 1111011101 6
1 1 1 0 1 1 1 1 0 1 1110111101 6
1 1 0 1 1 1 1 1 0 1 1101111101 0
1 0 1 1 1 1 1 1 0 1 1011111101 0
0 1 1 1 1 1 1 1 0 1 0111111101 12
1 1 1 1 1 1 0 0 1 1 1111110011 6
1 1 1 1 1 0 1 0 1 1 1111101011 0
1 1 1 1 0 1 1 0 1 1 1111011011 12
1 1 1 0 1 1 1 0 1 1 1110111011 0
1 1 0 1 1 1 1 0 1 1 1101111011 6
1 0 1 1 1 1 1 0 1 1 1011111011 0
0 1 1 1 1 1 1 0 1 1 0111111011 6
1 1 1 1 1 0 0 1 1 1 1111100111 12
1 1 1 1 0 1 0 1 1 1 1111010111 18
1 1 1 0 1 1 0 1 1 1 1110110111 6
1 1 0 1 1 1 0 1 1 1 1101110111 6
1 0 1 1 1 1 0 1 1 1 1011110111 6
0 1 1 1 1 1 0 1 1 1 0111110111 18
1 1 1 1 0 0 1 1 1 1 1111001111 0
1 1 1 0 1 0 1 1 1 1 1110101111 6
1 1 0 1 1 0 1 1 1 1 1101101111 0
1 0 1 1 1 0 1 1 1 1 1011101111 0
0 1 1 1 1 0 1 1 1 1 0111101111 12
1 1 1 0 0 1 1 1 1 1 1110011111 0
1 1 0 1 0 1 1 1 1 1 1101011111 6
1 0 1 1 0 1 1 1 1 1 1011011111 6
0 1 1 1 0 1 1 1 1 1 0111011111 6
1 1 0 0 1 1 1 1 1 1 1100111111 6
1 0 1 0 1 1 1 1 1 1 1010111111 0
0 1 1 0 1 1 1 1 1 1 0110111111 12
1 0 0 1 1 1 1 1 1 1 1001111111 0
0 1 0 1 1 1 1 1 1 1 0101111111 0


Le paquet Kado contenant le source et les résultats.