/*
    Fichier vmailsj.c
    Auteur Bernard Chardonneau

    Logiciel libre, droits d'utilisation précisés en français
    dans le fichier : licence-fr.txt

    Traductions des droits d'utilisation dans les fichiers :
    licence-de.txt , licence-en.txt , licence-es.txt ,
    licence-it.txt , licence-nl.txt , licence-pt.txt ,
    licence-eo.txt , licence-eo-utf.txt

    Droits d'utilisation également sur la page web :
    http://libremail.tuxfamily.org/voir.php?page=droits


    Ce programme affiche les caractéristique de chaque mail
    mémorisé dans un fichier.

    Pour chaque fichier mail, il y a une ligne d'affichage de la forme :
     expéditeur      sujet     date et heure d'envoi
     destinataire    sujet     date et heure d'envoi
    selon qu'il s'agisse d'un mail reçu ou envoyé (ou à envoyer)

    On peut naviguer dans la liste avec les flèches pour sélectionner
    le mail à visualiser.
    Une fois sélectionné, on pourra répondre à ce mail ou le transférer.

    Par défaut, on analyse les mails du répertoire courant.
    On peut aussi indiquer un autre répertoire de recherche.

    Cet outil peut être lancé automatiquement par la commande
    vmaildir lorsqu'on sélectionne un répertoire de l'arborescence
    des mails.
    Il lance à son tour vmailfic pour la visalisation des mails.
*/


#define appli   // pour la déclaration de variables globales à l'application

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include "messages.h"
#include "buflect.h"
#include "fmail.h"
#include "trtentete.h"
#include "modepage.h"
#include "carspe.h"
#include "szchemin.h"


// mettez le #define qui suit en commentaire si vous ne
// voulez aucune correction du fuseau horaire sur les mails
#define correct_fh


#define max_nbfic_ini 500  // nombre max initial de fichiers mail

#define largeurmax    120  // largeur maximale d'une ligne affichée

// largeur ou largeur maximum des différents champs d'une ligne
#define sz_date        14
#define szmax_nom     ((largeurmax / 4) - 1)
#define szmax_sujet   (largeurmax - szmax_nom - sz_date - 2)

#define an_pivot       90  // l'année des mails les plus vieux sur 2 chiffres


/* type de données */
typedef struct
{
    char nomfic [12];
    long ordre;
    char nom [szmax_nom];
    char sujet [szmax_sujet];
    char date [sz_date];
} descmail;


/* prototypes */
void lister_mails ();
int  memfic (char *nomfic);
void lect_fuseau_h ();
void corrige_fh (int *jour, int *mois, int *an, int *heure,
                                       int *min, int decal);
int  ajoutlistepossible ();
void tridate ();
void tridate_inv ();
void navigation ();
void affligne (int numlig);
void suprefmail ();
void majliste_mails ();
void recherche (int sens);
int  dansligne (char *chaine, int numlig);


/* variable globale au source
   (pour éviter des tonnes de passages de paramètres) */


descmail **fichmail; // noms des fichiers mail
int nbmaxfic;        // nombre maximun de fichiers mails mémorisés
int fh_loc;          // fuseau horaire local
int test_xorig;      // traitement ou non du champ X-Original-From


/* programme principal */

int main (int nbarg, char *varg[])
{
    int opt_inv;     // option d'inversion de l'ordre d'affichage
    char fic_cmd_trad [20];  // nom fichier qui peut avoir été créé par vmailfic


    // récupération du nom de l'exécutable
    memcom (*varg);

    // mémorisation de l'option -i (ou -r) éventuelle
    if (--nbarg > 0  && ((strcmp (varg [1], "-i") == 0)
                      || (strcmp (varg [1], "-r") == 0)))
    {
        opt_inv = 1;

        // passage à l'argument suivant
        varg ++;
        nbarg --;
    }
    else
        opt_inv = 0;

    // récupération éventuelle du nom du répertoire des mails
    if (nbarg == 1)
    {
        // positionnement dans ce répertoire
        if (chdir (varg [1]) < 0)
        {
            // "Répertoire %s inexistant"
            aff_err_arg ("REPERT_INEXISTANT", varg [1]);
            return (0);
        }
    }

    // si aucune erreur dans la liste des paramètres
    if (nbarg < 2)
    {
        // fixer la taille initiale de la liste des fichiers mail
        nbmaxfic = max_nbfic_ini;

        // allouer la liste des fichiers mail
        fichmail = malloc (nbmaxfic * sizeof (descmail *));

        // vérification allocation
        if (! fichmail)
            // "Manque de place mémoire, le logiciel %s ne peut fonctionner"
            errfatale ("MANQUE_MEMOIRE", nomcom ());

#ifdef correct_fh
        // récupérer et mémoriser le fuseau horaire local
        lect_fuseau_h ();
#endif

        // récupération de la liste des fichiers mail
        lister_mails ();

        // si des fichiers mails ont été trouvés
        if (nb_lignes > 0)
        {
            // les ordonner par date
            if (opt_inv)
                tridate_inv ();
            else
                tridate ();

            // générer le nom d'un fichier destiné à contenir une commande de
            // traduction. Il pourra être créé lors d'un appel de vmailfic
            // envoyer ce nom de fichier aux processus fils via une variable
            // d'environement.
            sprintf (fic_cmd_trad, "/tmp/cmd-trad-%04X", getpid ());
            setenv ("libremail-cmd-trad", fic_cmd_trad, 1);

            // remarque : on procède ainsi pour pouvoir réutiliser le même
            // choix de traduction pour tous les mails du répertoire.
            // avec une version de gnu/linux assez ancienne, on pouvait
            // fabriquer ce nom de fichier dans les processus fils en utilisant
            // la fonction getppid, mais ça ne fonctionne plus sur les
            // distributions récentes.

            // afficher le caractéristiques des mails et naviguer dans la liste
            navigation ();

            // détruire le fichier fic_cmd_trad s'il a été créé
            unlink (fic_cmd_trad);
        }
        else
            // sinon, pas nécessaire de rester dans l'outil
            // "pas de mails dans ce répertoire"
            affiche_msg ("REPERT_SANS_MAIL");
    }
    else
        // "Syntaxe : %s [-i] [répertoire]"
        psyntaxe ("SYNT_VMAILSJ");


    // pour faire plaisir à gcc qui veut une fonction main de type int
    return (0);
}


/* récupère les caractéristiques des mails */

void lister_mails ()
{
    DIR    *repert;
    struct dirent *entree;
    struct stat   descfic;
    int    errmem;
    char   *varenv_xorig;


    // initialisation
    nb_lignes = 0;
    errmem = 0;

    // déterminer si on va traiter les champs X-Original-From
    varenv_xorig = getenv ("libremail_xorig");

    if (varenv_xorig)
        test_xorig = (tolower (*varenv_xorig) != 'n');
    else
        test_xorig = 1;

    // accéder au répertoire
    repert = opendir (".");

    // lire un nom de fichier
    entree = readdir (repert);

    // tant qu'il y a des noms de fichier à lire et qu'on peut les traiter
    while (entree && ! errmem)
    {
        // mémoriser le fichier si son nom convient
        if (strlen (entree->d_name) == 8 ||
           (strlen (entree->d_name) == 10 && entree->d_name [8] == '.'))
        {
            // et si ce n'est pas un répertoire
            stat (entree->d_name, &descfic);

            if ((descfic.st_mode & S_IFMT) == S_IFREG)
                errmem = memfic (entree->d_name);
        }

        // et lire le nom du fichier suivant
        entree = readdir (repert);
    }

    // avertissement si on a atteint les limites de stockage
    if (errmem)
    {
        effpage ();

        // "Manque de place mémoire, liste des mails tronquée"
        affiche_msg_nocr ("DEBORD_MEM_TRONCAT");

        attendre (3);
    }
}


/* mémorisation des caractéristiques d'un mail
   retourne 1 si problème d'espace mémoire, 0 dans tous les autres cas */


int memfic (char *nomfic)
{
    // buffers pour mémoriser les caractéristiques principales du message
    char bufFromTo [120], bufSubject [120], bufDate [120];

    // les noms de mois dans les champs Date: des mails
    static char *mois [] = { "XXX", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

    descmail *mailcourant;  // la description en cours de génération
    int jour, numois, an, heure, min;  // date et heure du mail
    int fhoraire, decalh; // fuseau horaire et décalage horaire
    int affichedest; // indique si on doit afficher le champ destinataire
    int i, j;        // simples compteurs


    // ouverture du fichier mail
    fmail = fopen (nomfic, "r");

    // sortie si erreur imprévue
    if (fmail == 0)
    {
        affpage ();

        // "Fichier %s protégé en lecture"
        aff_err_arg ("ACCES_FICH_LECT", nomfic);
        montecurs ();

        attendre (2);

        return (0);  // erreur non remontée
    }

    // déterminer le champ à afficher entre expéditeur et destinataire
    affichedest = (tolower (*nomfic) == 'e') || (tolower (*nomfic) == 's');

    // initialisation
    bufFromTo [0]  = '\0';
    bufSubject [0] = '\0';
    bufDate [0]    = '\0';

    // tantqu'on peut lire une ligne de l'entête du mail
    while (lire_fmail () && buf_lect [0] != '\0')
    {
        // si la ligne correspond au champ sujet
        if (start ("Subject"))
        {
            // mémoriser la ligne
            memconvbuf (bufSubject);

            // on va tester la ligne qui suit
            lire_fmail ();
            majlignentete ();

            // si c'est encore une ligne du sujet
            if (*buf_lect == ' ' || *buf_lect == '\t')
            {
                // initialisation compteurs
                i = 1;
                j = strlen (bufSubject);

                // compléter le sujet du mail
                while (buf_lect [i] && j < sizeof (bufSubject) - 1)
                    bufSubject [j++] = buf_lect [i++];

                // terminer la chaine de caractères
                bufSubject [j] = '\0';
            }
        }

        // mémorisation des autres caractéristiques importantes
        if (start ("Date"))
            membuf (bufDate);
        else if (start ("From") && ! affichedest && ! *bufFromTo)
            memconvbuf (bufFromTo);
        else if (start ("X-Original-From") && test_xorig)
            memconvbuf (bufFromTo);
        else if (start ("To") && affichedest)
            memconvbuf (bufFromTo);
    }

    // libérer le fichier mail
    fclose (fmail);

    // si l'on n'a pas trouvé de ligne Date: From: ou Subject:
    if (! *bufDate && ! *bufFromTo && ! *bufSubject)
        return (0);   // ce n'est pas un fichier mail

    // si on ne peut pas mémoriser la description dans la liste
    if (! ajoutlistepossible ())
        return (1);   // on travaillera avec une liste partielle

    // allouer de l'espace mémoire pour stocker les caractéristiques du mail
    mailcourant = (descmail *) malloc (sizeof (descmail));

    // si manque de place mémoire
    if (! mailcourant)
        return (1);   // on travaillera avec une liste partielle

    // mémoriser le nom du fichier
    strcpy (mailcourant->nomfic, nomfic);


    // mémorisation du nom de l'expéditeur ou du destinataire du message
    j = 0;

    if (*bufFromTo)
    {
        // sauter le mot clé  From: , To:  ou  X-Original-From:
        if (affichedest)
            i = 4;
        else if (tolower (*bufFromTo) == 'x')
            i = 16;
        else
            i = 6;

        // se positionner sur le 1er caractère significatif du nom
        while (bufFromTo [i] == '"' || bufFromTo [i] == ' ')
            i++;

        // mémorisation du nom
        mailcourant->nom [j++] = bufFromTo [i++];

        while (j < szmax_nom && bufFromTo [i] && bufFromTo [i] != '<'
                     && bufFromTo [i] != '"' && bufFromTo [i] != '=')
            mailcourant->nom [j++] = bufFromTo [i++];
    }

    // compléter le nom par des blancs
    while (j < szmax_nom)
        mailcourant->nom [j++] = ' ';


    // mémorisation du sujet du message
    j = 0;

    if (*bufSubject)
    {
        // sauter le mot clé  Subject:
        i = 8;

        // se positionner sur le 1er caractère significatif du sujet
        while (bufSubject [i] == ' ')
            i++;

        // mémoriser le sujet
        while (j < szmax_sujet && bufSubject [i])
            mailcourant->sujet [j++] = bufSubject [i++];
    }

    // compléter le sujet par des blancs
    while (j < szmax_sujet)
        mailcourant->sujet [j++] = ' ';


    // mémorisation de la date et de l'heure du message
    if (*bufDate)
    {
        // sauter le mot clé  Date:
        i = 6;

        // récupérer le jour
        while (bufDate [i] < '0' || bufDate [i] > '9')
            i++;

        jour = atoi (bufDate + i);

        // se positionner sur le mois
        while (bufDate [i] > ' ')
            i++;

        i++;

        // récupérer le numéro du mois
        numois = 12;

        while (numois > 0 && (toupper (bufDate [i])  != mois [numois][0]
                           || tolower (bufDate [i+1]) != mois [numois][1]
                           || tolower (bufDate [i+2]) != mois [numois][2]))
            numois--;

        // récupérer l'année sur 2 chiffres
        while (bufDate [i] < '0' || bufDate [i] > '9')
            i++;

        an = atoi (bufDate + i) % 100;

        // récupérer l'heure et les minutes
        while (bufDate [i] > ' ')
            i++;

        heure = atoi (bufDate + i);
        min = atoi (bufDate + i + 4);

#ifdef correct_fh
        // se positionner sur le fuseau horaire
        while (bufDate [i] && bufDate [i] != '+' && bufDate [i] != '-')
            i++;

        // récupérer le fuseau horaire
        if (bufDate [i])
        {
            // on traitera les décalages sur les heures et les minutes
            fhoraire = atoi (bufDate + i);

            // s'il faut faire une correction de fuseau horaire
            if (fhoraire != fh_loc && fh_loc > -2400)
                // la faire
                corrige_fh (&jour, &numois, &an, &heure, &min,
                                                 fh_loc - fhoraire);
        }
#endif

        // mémoriser la date sous forme de chaine de caractères
        sprintf (mailcourant->date, "%02d/%02d/%02d %02d:%02d",
                                    jour, numois, an, heure, min);

        // les années 90 à 99 sont considérées du siècle passé
        if (an >= an_pivot)
            an = an - 100;

        // mémoriser aussi la date sous forme numérique pour tri ultérieur
        /* on tient compte du réglage le plus fréquent sur les ordinateurs
           européens : l'ordinateur est réglé sur l'heure locale */


        mailcourant->ordre = ((((((((long) an * 12) + numois) * 31)
                                + jour) * 24) + heure) * 60) + min;
    }
    else
    {
        mailcourant->date [0] = '\0';
        mailcourant->ordre = 0;
    }

    // mémoriser la description dans la liste
    fichmail [nb_lignes++] = mailcourant;

    // tout s'est bien déroulé
    return (0);
}


/* récupérer et mémoriser le fuseau horaire local */

void lect_fuseau_h ()
{
    char commande [26];
    char *nomfic;
    FILE *fcaract;


    // initialisation avec une valeur "impossible"
    fh_loc = -9999;

    // récupérer le décalage de l'heure locale par rapport au temps universel
    sprintf (commande, "date +%%z > /tmp/fich%04X", getpid ());
    system (commande);

    // ouvrir le fichier créé par la commande  date
    nomfic = commande + 11;
    fcaract = fopen (nomfic, "r");

    // si ouverture réussie
    if (fcaract)
    {
        // lire le fuseau horaire sous forme numérique
        fscanf (fcaract, "%d", &fh_loc);

        // fermer et détruire le fichier
        fclose (fcaract);
        unlink (nomfic);
    }
}


/* corriger le décalage horaire d'un mail */

void corrige_fh (int *jour, int *mois, int *an, int *heure, int *min, int decal)
{
    // nombre de jours des différents mois de l'année
    // le mois 0 correspond à décembre de l'année précédente
    int dureemois [] = {31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};


    // correction des minutes
    *min = *min + (decal % 100);

    // changement d'heure éventuel
    if (*min < 0)
    {
        *min = *min + 60;
        (*heure) --;
    }
    else if (*min >= 60)
    {
        *min = *min - 60;
        (*heure) ++;
    }

    // correction de l'heure
    *heure = *heure + (decal / 100);

    // si retour au jour précédent
    if (*heure < 0)
    {
        // corriger l'heure et le jour du mois
        *heure = *heure + 24;
        *jour = *jour - 1;

        // si retour au mois précédent
        if (*jour < 1)
        {
            // éviter un plantage si le mois n'a pas pu être décodé
            if (*mois < 1)
                return;

            // corriger le mois et le jour
            *mois = *mois - 1;
            *jour = dureemois [*mois];

            // si retour à l'année précédente
            if (! *mois)
            {
                // corriger le mois et l'année
                *mois = 12;
                *an = *an - 1;
            }
            // sinon si mois de février d'une année bisextile
            else if (*mois == 2 && (*an % 4) == 0)
                // corriger le jour
                *jour = *jour + 1;
        }
    }
    // sinon si passage au jour suivant
    else if (*heure > 23)
    {
        // corriger l'heure et le jour du mois
        *heure = *heure - 24;
        *jour = *jour + 1;

        // si passage au mois suivant
        if (*jour > dureemois [*mois])
        {
            // test suplémentaire par rapport au 29 février
            if (*mois != 2 || (*an % 4) || *jour > 29)
            {
                // corriger le mois et le jour
                *mois = *mois + 1;
                *jour = 1;

                // si changement d'année
                if (*mois > 12)
                {
                    // corriger le mois et l'année
                    *mois = 1;
                    *an = *an + 1;
                }
            }
        }
    }
}


/* vérifie si l'on peut insérer un élément de plus dans le tableau
   fichmail, et redimmensionne ce tableau si nécessaire */


int ajoutlistepossible ()
{
    descmail **nouvtableau; // adresse du tableau de remplacement
    int  nouvtaille;        // et sa taille
    int  element;           // compteur : numéro d'élément dans les tableaux


    // cas simple : il reste au moins une place de libre dans le tableau
    if (nb_lignes + 1 < nbmaxfic)
        return (1);

    // calculer la nouvelle taille du tableau
    // l'augmentation est alternativement de 50 % ou 33 % de manière
    // à ce que la taille double après 2 réallocations
    if (nbmaxfic % 3)
        nouvtaille = nbmaxfic + (nbmaxfic / 2);
    else
        nouvtaille = nbmaxfic + (nbmaxfic / 3);

    // allocation mémoire
    nouvtableau = malloc (nouvtaille * sizeof (descmail *));

    // vérification allocation
    if (nouvtableau)
    {
        // copie du contenu de l'ancien tableau dans le nouveau
        for (element = 0; element < nbmaxfic; element ++)
            nouvtableau [element] = fichmail [element];

        // destruction de l'ancien tableau
        free (fichmail);

        // que l'on remplace par le nouveau
        fichmail = nouvtableau;
        nbmaxfic = nouvtaille;
    }

    // retourne le résultat de la possibilité d'insertion d'éléments
    return (nb_lignes + 1 < nbmaxfic);
}


/* tri des mails par date et heure croissantes */

void tridate ()
{
    descmail *tampon; // adresse d'une description de mail pour permutation
    int  i;           // indice dans la liste


    // initialisation
    i = 0;

    while (i < nb_lignes - 1)
    {
        // on compare 2 éléments consécutifs
        if (fichmail [i]->ordre > fichmail [i+1]-> ordre)
        {
            // interversion de 2 enregistrements
            tampon = fichmail [i];
            fichmail [i] = fichmail [i+1];
            fichmail [i+1] = tampon;

            // ce qui peut supposer de comparer un nom déplacé avec le précédent
            if (i > 0)
                i--;
            else
                i++;
        }
        else
            i++;    // on progresse dans la liste
    }
}


/* tri des mails par date et heure décroissantes */

void tridate_inv ()
{
    descmail *tampon; // adresse d'une description de mail pour permutation
    int  i;           // indice dans la liste


    // initialisation
    i = 0;

    while (i < nb_lignes - 1)
    {
        // on compare 2 éléments consécutifs
        if (fichmail [i]->ordre < fichmail [i+1]-> ordre)
        {
            // interversion de 2 enregistrements
            tampon = fichmail [i];
            fichmail [i] = fichmail [i+1];
            fichmail [i+1] = tampon;

            // ce qui peut supposer de comparer un nom déplacé avec le précédent
            if (i > 0)
                i--;
            else
                i++;
        }
        else
            i++;    // on progresse dans la liste
    }
}


/* affiche la liste des mails du répertoire et permet de
   la parcourir même si elle tient sur plusieurs pages */


void navigation ()
{
    int  car;          // caractère tapé au clavier
    int  erreurs;      // nombre frappes caractères inconnus comme commandes
    int  choix;        // choix de ce qu'on veut imprimer
    char curdir [szchemin]; // répertoire courant
    int  dans_mailenv; // indicateur : on est dans le répertoire d'envoi
    char *editeur;     // éditeur utilisé pour modifier les mails
    // chaine de caractères utilisée pour rebaptiser,
    // mettre à la poubelle ou imprimer un fichier mail
    char tampon [szchemin + 16];


    // configurer la liaison clavier pour lecture directe avec timeout
    mode_raw ();

    // initialisation
    lignecran = 1;
    lignecour = 0;
    erreurs = 0;
    affpage ();

    // récupérer le nom du répertoire courant
    getcwd (curdir, szchemin);

    // tester si on est dans le répertoire d'envoi des mails
    if (getenv ("mailenv"))
        dans_mailenv = (strcmp (curdir, getenv ("mailenv")) == 0);
    else
        dans_mailenv = 0;

    do
    {
        // lire et traiter une touche du clavier
        car = leccar ();

        switch (car)
        {                  // déplacement dans la liste des mails
            case MONTE   : monte (1);
                           erreurs = 0;
                           break;

            case DESCEND : descend (1);
                           erreurs = 0;
                           break;

            case PAGEUP  : monte (lignepage + lignecran - 2);
                           erreurs = 0;
                           break;

            case PAGEDOWN: descend (2 * lignepage - lignecran - 2);
                           erreurs = 0;
                           break;

            case HOME    :
            case HOMEg   : monte (lignecour);
                           erreurs = 0;
                           break;

            case FIN     :
            case FINg    : descend (nb_lignes - lignecour - 1);
                           erreurs = 0;
                           break;

            case 0       : // aide si trop d'erreurs ou à la demande
            case F1      : effpage ();
                           // "Touches utilisables :\n"
                           // "flèches Pageup, Pagedown, Home et Fin"
                           // "pour se déplacer d'une ou plusieurs lignes"
                           // "v ou l ou entrée pour Voir (Lire) un mail"
                           // "r pour y Répondre, t pour le Transférer"
                           // "s pour le Supprimer, m pour Modifier avant envoi"
                           // "u pour restaurer un mail dans la poubelle"
                           // "j pour Joindre des fichiers à un mail à envoyer"
                           // "n ou Inser pour en créer un Nouveau"
                           // "o pour voir le fichier Original (non converti)"
                           // "i pour Identifier le fichier mail"
                           // "p pour imPrimer un mail (ou la liste complète)"
                           // "/ ou ? pour rechercher une chaine dans la liste"
                           // "Control L pour réafficher la page"
                           affiche_msg ("AIDE_CHOIX_ADR-1");
                           affiche_msg ("AIDE_CHOIX_ADR-2");
                           affiche_msg ("AIDE_CHOIX_ADR-3");
                           affiche_msg ("AIDE_VMAILSJ-1");
                           affiche_msg ("AIDE_VMAILSJ-2");
                           affiche_msg ("AIDE_VMAILFIC-2");
                           affiche_msg ("AIDE_VMAILFIC-3");
                           affiche_msg ("AIDE_VMAILFIC_SJ2");
                           affiche_msg ("AIDE_VMAILDIR_SJ");
                           affiche_msg ("AIDE_VMAILSJ-3");
                           affiche_msg ("AIDE_VMAILFIC_SJ1");
                           affiche_msg ("AIDE_VMAILSJ-4");
                           affiche_msg ("AIDE_VMAILSJ-5");
                           affiche_msg ("AIDE_CHOIX_ADR-7");

                           // variante pour la touche Esc
                           if (util_systemd ())
                               // "q ou Esc (2 fois) pour Quitter ce programme"
                               affiche_msg ("AIDE_VMAIL2");
                           else
                               // "q ou Esc pour Quitter ce programme"
                               affiche_msg ("AIDE_VMAIL");

                           // "Appuyer sur une touche pour continuer"
                           affiche_msg ("ATTENTE_CLAVIER");
                           leccar ();
                           affpage ();
                           erreurs = 0;
                           break;

            case 'l'     :
            case 'v'     : // on va visualiser un mail
            case '\n'    : execom ("vmailfic", fichmail [lignecour]->nomfic);

                           // pas de surbrillance pour les mails déjà visualisés
                           // (le test de la lettre  n  comme 10ème caractère
                           // n'est utile que pour assurer la compatibilité en
                           // lecture avec Micro$oft Internet mail)
                           if (fichmail [lignecour]->nomfic [8] == '.'
                                && tolower(fichmail[lignecour]->nomfic[9])== 'n'
                                && access (fichmail [lignecour]->nomfic, 0) < 0)
                               fichmail [lignecour]->nomfic [8] = '\0';

                           // si le mail a été supprimé
                           if (access (fichmail [lignecour]->nomfic, 0) < 0)
                               // on l'enlève de la liste à afficher
                               suprefmail ();

                           // on peut réafficher la page
            case CTRL    : affpage ();
                           erreurs = 0;
                           break;

                           // identifie le fichier mail (donne son nom)
            case 'i'     : effpage ();
                           // "\nFichier : %s/%s\n\n"
                           printf (message ("AFF_CHEMFICH"), curdir,
                                             fichmail [lignecour]->nomfic);

                           // "Appuyer sur une touche pour continuer"
                           affiche_msg_nocr ("ATTENTE_CLAVIER");

                           leccar ();
                           affpage ();
                           erreurs = 0;
                           break;

                           // voir le fichier mail original sans conversion
            case 'o'     : execom ("less -r", fichmail [lignecour]->nomfic);
                           affpage ();
                           erreurs = 0;
                           break;

            case INSERT  : // création d'un nouveau mail
            case 'n'     : execom ("cremail", "");

                           // si on est dans le répertoire des mails à envoyer
                           if (dans_mailenv)
                               // réactualiser la liste des mails affichés
                               majliste_mails ();

                           affpage ();
                           erreurs = 0;
                           break;

                           // réponse à un mail
            case 'r'     : execom ("repmail", fichmail [lignecour]->nomfic);

                           // si on est dans le répertoire des mails à envoyer
                           if (dans_mailenv)
                               // réactualiser la liste des mails affichés
                               majliste_mails ();

                           affpage ();
                           erreurs = 0;
                           break;

                           // transfert d'un mail
            case 't'     : execom ("trsfmail", fichmail [lignecour]->nomfic);

                           // si on est dans le répertoire des mails à envoyer
                           if (dans_mailenv)
                               // réactualiser la liste des mails affichés
                               majliste_mails ();

                           affpage ();
                           erreurs = 0;
                           break;

      //    case SUPR    : // suppression d'un mail
            case 's'     : // récupérer le répertoire des mails supprimés
                           if (getenv ("mailpoub"))
                               strcpy (tampon, getenv ("mailpoub"));
                           else
                               *tampon = '\0';

                           // si c'est le répertoire courant
                           if (strcmp (curdir, tampon) == 0)
                           {
                               // supprimer le fichier mail
                               unlink (fichmail [lignecour]->nomfic);

                               // supprimer le mail de la liste à afficher
                               suprefmail ();
                           }
                           // sinon (cas général)
                           else if (*tampon)
                           {
                               // créer le répertoire poubelle si nécessaire
                               mkdir (tampon, 0755);

                               // et y mettre le fichier mail
                               strcat (tampon, "/");
                               strcat (tampon, fichmail [lignecour]->nomfic);
                               rename (fichmail [lignecour]->nomfic, tampon);

                               // supprimer le mail de la liste à afficher
                               suprefmail ();
                           }
                           // sinon (racine des mails inconnue)
                           else
                           {
                               effpage ();

                            // "Racine des mails inconnue, pas de suppression"
                               affiche_msg_nocr ("REP_RACINE_INCONNU");

                               attendre (2);
                           }

                           affpage ();
                           erreurs = 0;
                           break;

                           // restauration d'un mail
            case 'u'     : // si on est dans le répertoire poubelle
                           if (strcmp (curdir, getenv ("mailpoub")) == 0)
                           {
                               // déplacer le fichier mail dans entree
                               sprintf (tampon, "../%s/%s",
                          ficdir ("DIR_ENTREE"), fichmail [lignecour]->nomfic);
                               rename (fichmail [lignecour]->nomfic, tampon);

                               // supprimer le mail de la liste à afficher
                               suprefmail ();
                           }
                           // sinon, message d'erreur
                           else
                           {
                               effpage ();

                    // "Seuls les mails dans la poubelle peuvent être restaurés"
                               affiche_msg_nocr ("MAIL_NON_RESTAUR");

                               attendre (2);
                           }

                           affpage ();
                           erreurs = 0;
                           break;

                           // modification d'un mail à envoyer
            case 'm'     : // si on est dans le répertoire des mails à envoyer
                           if (dans_mailenv)
                           {
                               // modifier le mail
                               editeur = getenv ("EDITOR");

                               if (editeur)
                                   execom (editeur,fichmail[lignecour]->nomfic);
                               else
                                   execom ("vi", fichmail [lignecour]->nomfic);
                           }
                           else
                           {
                               // sinon, message d'erreur
                               effpage ();

                   // "Seuls les mails en attente d'envoi peuvent être modifiés"
                               affiche_msg_nocr ("MAIL_NON_MODIF");

                               attendre (2);
                           }

                           affpage ();
                           erreurs = 0;
                           break;

                           // impression de la liste ou du mail courant
            case 'p'     : // choix de ce qu'on veut imprimer
                           putchar ('\n');
                           effpage ();
                           // "1) impression de la liste des mails"
                           // "2) impression du mail de la ligne courante"
                           // "Votre choix ? "
                           affiche_msg ("CHOIX_IMPR-1");
                           affiche_msg ("CHOIX_IMPR-2");
                           affiche_msg_nocr ("VOTRE_CHOIX");

                           choix = leccar () - '0';

                           // lancer la commande d'impression correspondante
                           if (choix == 1)
                               system ("sjfmails . | lp");
                           else if (choix == 2)
                           {
                               sprintf (tampon, "voirfmail %s | lp",
                                        fichmail [lignecour]->nomfic);
                               system (tampon);
                           }
                           else
                           {
                               // "Choix erronné"
                               affiche_msg_nocr ("CHOIX_IMPR_ERR");

                               attendre (2);
                           }

                           affpage ();
                           erreurs = 0;
                           break;

                           // rajout de fichiers joints à un mail à envoyer
            case 'j'     : // si on est dans le répertoire des mails à envoyer
                           if (dans_mailenv)
                               // rajouter des fichiers joints
                               execom ("joindre", fichmail [lignecour]->nomfic);
                           else
                           {
                               // sinon, message d'erreur
                               effpage ();

                   // "Seuls les mails en attente d'envoi peuvent être modifiés"
                               affiche_msg_nocr ("MAIL_NON_MODIF");

                               attendre (2);
                           }

                           affpage ();
                           erreurs = 0;
                           break;

            case '/'     : // recherche d'une chaine de caractères
            case '?'     : recherche (car);
                           erreurs = 0;

            case 'q'     : // sortie du programme
            case ESC     : break;

                           // sortie aussi sur :q
            case ':'     : car = leccar ();

                           if (car == 'q')
                           {
                               car = ESC;
                               break;
                           }

            default      : putchar (7);  // bip

                           // si trop d'erreurs, afficher l'aide
                           if (++erreurs == 5)
                               ungetc (0, stdin);  // on ira sur l'aide
        }
    }
    while (car != 'q' && car != ESC && nb_lignes > 0);

    // descendre en bas de page (pour sortir proprement du programme)
    while (lignecran++ < lignepage && lignecour++ < nb_lignes)
        putchar ('\n');

    // retour à la configuration standard du clavier
    mode_normal ();
}


/* affiche les caractéristiques d'un mail */

void affligne (int numlig)
{
    // la largeur des différents champs affichés est calculée
    // à partir de la largeur de la fenêtre d'affichage
    // on utilise des variables statiques pour ne faire le
    // calcul qu'en cas de nécessité
    static int old_colonpage = -1; // pour détecter changement largeur de page
    static int sz_nom, sz_sujet;   // longueur des différentes zones affichées
    static char car_nonlu = 0;     // caractère indiquant les mails non lus
    int tot;    // pour calcul
    int i, j;   // simples compteurs


    // si la largeur de la zone d'affichage a changé
    if (colonpage != old_colonpage)
    {
        // répartir la largeur d'affichage entre les champs
        sz_nom = (colonpage / 4) - 1;

        if (sz_nom > szmax_nom)
            sz_nom = szmax_nom;

        sz_sujet = colonpage - sz_date - sz_nom - 2;

        if (sz_sujet > szmax_sujet)
            sz_sujet = szmax_sujet;

        // cette mémorisation évitera de refaire le calcul à chaque ligne
        old_colonpage = colonpage;

        // si la variable d'environnement libremail_new est utilisée
        if (getenv ("libremail_new"))
        {
            // mémoriser ce caractère
            car_nonlu = *getenv ("libremail_new");

            // corriger la largeur d'affichage des 2 premiers champs
            sz_nom --;
            sz_sujet --;
        }
    }

    // afficher en surbrillance les mails non lus
    if (fichmail [numlig]->nomfic [8] == '.' &&
           tolower (fichmail [numlig]->nomfic [9]) == 'n')
    {
        clair ();

        // si nécessaire avec le caractère d'identification des mails non lus
        if (car_nonlu)
        {
            putchar (car_nonlu);
            putchar (' ');
        }
    }
    else if (car_nonlu)
    {
        putchar (' ');
        putchar (' ');
    }

    // si affichage avec un jeu de caractères ISO-8859
    if (! util_utf8 ())
    {
        // affichage du nom de l'expéditeur
        for (i = 0; i < sz_nom; i++)
            putchar (fichmail [numlig]->nom [i]);

        putchar (' ');

        // affichage du sujet
        for (i = 0; i < sz_sujet; i++)
        {
            // si présence d'une tabulation
            if (fichmail [numlig]->sujet [i] == '\t')
                // afficher un blanc pour une meilleure mise en page
                putchar (' ');
            else
                // sinon affichage du caractère
                putchar (fichmail [numlig]->sujet [i]);
        }
    }
    // sinon (affichage avec le jeu de caractères UTF-8)
    else
    {
        // initialisation
        j = 0;

        // affichage du nom de l'expéditeur
        for (i = 0; i < sz_nom + j && i < szmax_nom; i++)
        {
            putchar (fichmail [numlig]->nom [i]);

            // mais certains caractères ne seront pas comptés

                /* il serait plus logique de ne pas compter les caractères
                   entre 0x80 et 0xBF, mais certains caractères UTF8 codés
                   sur 3 (ou 4 ?) octets s'affichent en largeur double, ce
                   qui décale les autres colonnes et génère un saut de ligne
                   intempestif après la date */

            if ((fichmail [numlig]->nom [i] & 0xC0) == 0xC0)
                j++;
        }

        // complété par des blancs
        while (i++ <= sz_nom + j)
            putchar (' ');

        // réinitialisation
        j = 0;

        // affichage du sujet
        for (i = 0; i < sz_sujet + j && i < szmax_sujet; i++)
        {
            // si présence d'une tabulation
            if (fichmail [numlig]->sujet [i] == '\t')
                // afficher un blanc pour une meilleure mise en page
                putchar (' ');
            // sinon
            else
            {
                // affichage du caractère
                putchar (fichmail [numlig]->sujet [i]);

                // mais certains caractères ne seront pas comptés
                // même remarque que pour le nom
                if ((fichmail [numlig]->sujet [i] & 0xC0) == 0xC0)
                    j++;
            }
        }

        // complété par des blancs
        while (i++ < sz_sujet + j)
            putchar (' ');
    }

    putchar (' ');

    // affichage de la date et de l'heure (sans fuseau horaire)
    printf ("%s", fichmail [numlig]->date);

    // retour à l'affichage normal
    lumnor ();
}



/* supprime de la liste un mail que l'on vient de détruire */

void suprefmail ()
{
    int i;  // compteur


    // on l'enlève de la liste à afficher
    for (i = lignecour + 1; i < nb_lignes; i++)
        fichmail [i - 1] = fichmail [i];

    nb_lignes --;

    // on reste positionné sur un mail voisin
    if (lignecour == nb_lignes)
        monte (1);
}



/* réactualiser l'affichage de la liste des mails

   Cette fonction ne sert que dans le cas particulier d'un
   mail rajouté alors qu'on est dans le répertoire d'envoi.
   La méthode de mise à jour n'a pas besoin d'être optimisée.
   On se contente d'effacer l'ancienne liste et de la recréer. */


void majliste_mails ()
{
    // libérer la mémoire occupée par l'ancienne liste
    while (nb_lignes > 0)
        free (fichmail [--nb_lignes]);

    // récupérer la liste des fichiers mail à envoyer
    lister_mails ();

    // et les trier par date
    tridate ();
}



/* recherche d'une chaine de caractères dans la liste des mails */

void recherche (int sens)
{
    // chaine à rechercher, on utilise une variable statique
    // pour garder sa valeur aux appels suivants de la fonction
    static char chaine [120];
    int car;    // caractère lu au clavier
    int limite; // ligne marquant une extrémité de la liste
    int trouve; // indicateur : ligne contenant la chaine trouvée
    int i, j;   // compteurs


    // descendre en bas de page et effacer la dernière ligne
    baspage ();
    effligne ();

    // saisir la chaine à rechercher
    putchar (sens);
    car = leccar ();

    // si chaine vide, on conserve la précédente
    if (car != '\n')
    {
        // sinon on mémorise la nouvelle chaine
        i = 0;

        do
        {
            // cas particulier : caractère d'effacement
            if (car == RECULE || car == EFFCAR)
            {
                if (i > 0)
                {
                    putchar (CTRH);
                    putchar (' ');
                    putchar (CTRH);
                    i--;
                }
            }
            // cas général : affichage et mémorisation d'un caractère
            else if (i < sizeof (chaine))
            {
                putchar (car);
                chaine [i++] = car;
            }

            // lire le caractère suivant
            car = leccar ();
        }
        // jusqu'à ce que chaine entièrement saisie
        while (car != '\n');

        // terminer la chaine
        chaine [i] = '\0';
    }

    // initialisation variables pour recherche
    if (sens == '/')
    {
        sens = 1;
        limite = nb_lignes - 1;
    }
    else
    {
        sens = -1;
        limite = 0;
    }

    // début de la recherche à partir d'une ligne voisine de la ligne courante
    i = lignecour;

    // répeter
    do
    {
        // si on n'a pas atteint l'extrémité de la liste des mails
        if (i != limite)
            // descendre ou remonter d'une ligne selon le sens de recherche
            i = i + sens;
        else
            // sinon aller à l'autre extrémité de la liste
            i = nb_lignes - 1 - limite;

        // chercher si la chaine est présente dans la nouvelle ligne
        trouve = dansligne (chaine, i);
    }
    // jusque chaine de caractères trouvée ou toute la liste explorée
    while (! dansligne (chaine, i) && i != lignecour);

    // si chaine non trouvée
    if (! trouve)
    {
        // afficher un message d'erreur
        // " Chaine non trouvée"
        affiche_msg_nocr ("CHAINE_ABSENTE");
        putchar (7);
    }

    // revenir en début de ligne
    putchar ('\r');

    // remonter à la ligne sur laquelle on était
    j = lignepage;

    while (j-- > lignecran)
        montecurs ();

    // si chaine trouvée
    if (trouve)
    {
        // se positionner sur la ligne de la chaine s'il y a lieu
        if (i < lignecour)
            monte (lignecour - i);
        else if (i > lignecour)
            descend (i - lignecour);

        // réafficher la ligne contenant la chaine dans une autre couleur
        putseq ("33m");
        affligne (lignecour);
        putchar ('\r');
        lumnor ();
    }
}



/* vérification de la présence d'une chaine dans la numligième ligne */

int dansligne (char *chaine, int numlig)
{
    char *maligne; // pour éviter de trop manipuler des indices de tableau
    int i, j;      // simples compteurs


    // initialisation
    i = 0;

    // le nom de l'expéditeur/destinataire, le sujet et la
    // date sont regroupés sur une même chaine de caractères
    maligne = (char*) &fichmail [numlig]->nom;

    // tantque ligne non explorée en entier
    while (maligne [i])
    {
        // si caractère courant = premier caractère de la chaine
        if (maligne [i] == *chaine)
        {
            // vérifier la concordance des caractères suivants
            j = 1;

            while (chaine [j] && (chaine [j] == maligne [i+j]))
                j++;

            // si chaine trouvée, sortir de la fonction
            if (! chaine [j])
                return 1;
        }

        // sinon, essayer avec la suite de la ligne
        i++;
    }

    // chaine non trouvée dans la ligne
    return 0;
}