/*  Grafx2 - The Ultimate 256-color bitmap paint program
    Copyright 2008 Peter Gordon
    Copyright 2008 Yves Rizoud
    Copyright 2007 Adrien Destugues
    Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud)
    Grafx2 is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; version 2
    of the License.
    Grafx2 is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with Grafx2; if not, see  or
    write to the Free Software Foundation, Inc.,
    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#if defined(__amigaos4__) || defined(__AROS__) || defined(__MORPHOS__)
    #include 
    #include 
    #define isHidden(x) (0)
#elif defined(__WIN32__)
    #include 
    #include 
    #define isHidden(x) (GetFileAttributesA((x)->d_name)&FILE_ATTRIBUTE_HIDDEN)
#else
    #include 
    #define isHidden(x) ((x)->d_name[0]=='.')
#endif
#include "const.h"
#include "struct.h"
#include "global.h"
#include "misc.h"
#include "errors.h"
#include "io.h"
#include "windows.h"
#include "sdlscreen.h"
#include "loadsave.h"
#include "mountlist.h"
#include "engine.h"
#include "readline.h"
#include "input.h"
#include "help.h"
#define NORMAL_FILE_COLOR    MC_Light // color du texte pour une ligne de fichier non sélectionné
#define NORMAL_DIRECTORY_COLOR MC_Dark // color du texte pour une ligne de répertoire non sélectionné
#define NORMAL_BACKGROUND_COLOR       MC_Black  // color du fond  pour une ligne non sélectionnée
#define SELECTED_FILE_COLOR    MC_White // color du texte pour une ligne de fichier    sélectionnée
#define SELECTED_DIRECTORY_COLOR MC_Light // color du texte pour une ligne de repértoire sélectionnée
#define SELECTED_BACKGROUND_COLOR       MC_Dark // color du fond  pour une ligne sélectionnée
// Conventions:
//
// * Le fileselect modifie le répertoire courant. Ceci permet de n'avoir
//   qu'un findfirst dans le répertoire courant à faire:
// -- Déstruction de la liste chaînée ---------------------------------------
void Free_fileselector_list(void)
//  Cette procédure détruit la chaine des fichiers. Elle doit être appelée
// avant de rappeler la fonction Read_list_of_files, ainsi qu'en fin de
// programme.
{
  // Pointeur temporaire de destruction
  T_Fileselector_item * temp_item;
  while (Filelist!=NULL)
  {
    // On mémorise l'adresse du premier élément de la liste
    temp_item =Filelist;
    // On fait avancer la tête de la liste
    Filelist=Filelist->Next;
    // Et on efface l'ancien premier élément de la liste
    free(temp_item);
  }
}
// -- Formatage graphique des noms de fichier / répertoire ------------------
char * Format_filename(char * fname, int type)
{
  static char result[19];
  int         c;
  int         other_cursor;
  int         pos_last_dot;
  if (strcmp(fname,PARENT_DIR)==0)
  {
    strcpy(result,"<-PARENT DIRECTORY");
  }
  else if (fname[0]=='.' || type==2)
  {
    // Fichiers ".quelquechose" ou lecteurs: Calé à gauche sur 18 caractères maximum.
    strcpy(result,"                  ");
    for (c=0;fname[c]!='\0' && c < 18;c++)
      result[c]=fname[c];
    // Un caractère spécial pour indiquer que l'affichage est tronqué
    if (c >= 18)
      result[17]=ELLIPSIS_CHARACTER;
  }
  else
  {
    strcpy(result,"              .   ");
    // On commence par recopier la partie précédent le point:
    for (c=0;( (fname[c]!='.') && (fname[c]!='\0') );c++)
    {
      if (c < 14)
        result[c]=fname[c];
    }
    // Un caractère spécial pour indiquer que l'affichage est tronqué
    if (c > 14)
      result[13]=ELLIPSIS_CHARACTER;
    // On recherche le dernier point dans le reste du nom
    for (pos_last_dot = c; fname[c]!='\0'; c++)
      if (fname[c]=='.')
        pos_last_dot = c;
    // Ensuite on recopie la partie qui suit le point (si nécessaire):
    if (fname[pos_last_dot])
    {
      for (c = pos_last_dot+1,other_cursor=15;fname[c]!='\0' && other_cursor < 18;c++,other_cursor++)
        result[other_cursor]=fname[c];
    }
  }
  return result;
}
// -- Rajouter a la liste des elements de la liste un element ---------------
void Add_element_to_list(char * fname, int type)
//  Cette procedure ajoute a la liste chainee un fichier passé en argument.
{
  // Pointeur temporaire d'insertion
  T_Fileselector_item * temp_item;
  // On alloue de la place pour un nouvel element
  temp_item=(T_Fileselector_item *)malloc(sizeof(T_Fileselector_item));
  // On met a jour le nouvel emplacement:
  strcpy(temp_item->Short_name,Format_filename(fname, type));
  strcpy(temp_item->Full_name,fname);
  temp_item->Type = type;
  temp_item->Next  =Filelist;
  temp_item->Previous=NULL;
  if (Filelist!=NULL)
    Filelist->Previous=temp_item;
  Filelist=temp_item;
}
// -- Vérification si un fichier a l'extension demandée.
// Autorise les '?', et '*' si c'est le seul caractère.
int Check_extension(const char *filename, char * filter)
{
  int pos_last_dot = -1;
  int c = 0;
  if (filter[0] == '*')
    return 1;
  // On recherche la position du dernier . dans le nom
  for (c = 0; filename[c]!='\0'; c++)
    if (filename[c]=='.')
      pos_last_dot = c;
  // Fichier sans extension (ca arrive)
  if (pos_last_dot == -1)
    return (filter[0] == '\0');
  // Vérification caractère par caractère, case-insensitive.
  c = 0;
  do
  {
    if (filter[c] != '?' &&
      tolower(filter[c]) != tolower(filename[pos_last_dot + 1 + c]))
      return 0;
     c++;
  } while (filter[c++] != '\0');
  return 1;
}
// -- Lecture d'une liste de fichiers ---------------------------------------
void Read_list_of_files(byte selected_format)
//  Cette procédure charge dans la liste chainée les fichiers dont l'extension
// correspond au format demandé.
{
  DIR*  Repertoire_Courant; //Répertoire courant
  struct dirent* entry; // Structure de lecture des éléments
  char * filter = "*"; // Extension demandée
  struct stat Infos_enreg;
  char * current_path;
  // Tout d'abord, on déduit du format demandé un filtre à utiliser:
  if (selected_format) // Format (extension) spécifique
    filter = File_formats[selected_format-1].Extension;
  // Ensuite, on vide la liste actuelle:
  Free_fileselector_list();
  // Après effacement, il ne reste ni fichier ni répertoire dans la liste
  Filelist_nb_files=0;
  Filelist_nb_directories=0;
  // On lit tous les répertoires:
  current_path=getcwd(NULL,0);
  Repertoire_Courant=opendir(current_path);
  while ((entry=readdir(Repertoire_Courant)))
  {
    // On ignore le répertoire courant
    if ( !strcmp(entry->d_name, "."))
    {
      continue;
    }
    stat(entry->d_name,&Infos_enreg);
    // et que l'élément trouvé est un répertoire
    if( S_ISDIR(Infos_enreg.st_mode) &&
      // et que c'est ".."
      (!strcmp(entry->d_name, PARENT_DIR) ||
      // ou qu'il n'est pas caché
       Config.Show_hidden_directories ||
     !isHidden(entry)))
    {
      // On rajoute le répertoire à la liste
      Add_element_to_list(entry->d_name, 1);
      Filelist_nb_directories++;
    }
    else if (S_ISREG(Infos_enreg.st_mode) && //Il s'agit d'un fichier
      (Config.Show_hidden_files || //Il n'est pas caché
      !isHidden(entry)))
    {
      if (Check_extension(entry->d_name, filter))
      {
        // On rajoute le fichier à la liste
        Add_element_to_list(entry->d_name, 0);
        Filelist_nb_files++;
      }
    }
  }
#if defined(__MORPHOS__) || defined (__amigaos4__) || defined(__amigaos__)
  Add_element_to_list("/",1); // on amiga systems, / means parent. And there is no ..
  Filelist_nb_directories ++;
#endif
  closedir(Repertoire_Courant);
  free(current_path);
  Filelist_nb_elements=Filelist_nb_directories+Filelist_nb_files;
}
#if defined(__amigaos4__) || defined(__AROS__) || defined(__MORPHOS__)
void bstrtostr( BSTR in, STRPTR out, TEXT max )
{
  STRPTR iptr;
  dword i;
  iptr = BADDR( in );
  if( max > iptr[0] ) max = iptr[0];
  for( i=0; idol_Name, tmp, 254 );
        strcat( tmp, ":" );
        Add_element_to_list( tmp, 2 );
        Filelist_nb_directories++;
      }
      UnLockDosList( LDF_VOLUMES | LDF_READ );
    }
  }
  #elif defined (__WIN32__)
  {
    char drive_name[]="A:\\";
    int drive_bits = GetLogicalDrives();
    int drive_index;
    int bit_index;
    // Sous Windows, on a la totale, presque aussi bien que sous DOS:
    drive_index = 0;
    for (bit_index=0; bit_index<26 && drive_index<23; bit_index++)
    {
      if ( (1 << bit_index) & drive_bits )
      {
        // On a ce lecteur, il faut maintenant déterminer son type "physique".
        // pour profiter des jolies icones de X-man.
        int drive_type;
        char drive_path[]="A:\\";
        // Cette API Windows est étrange, je dois m'y faire...
        drive_path[0]='A'+bit_index;
        switch (GetDriveType(drive_path))
        {
          case DRIVE_CDROM:
            drive_type=ICON_CDROM;
            break;
          case DRIVE_REMOTE:
            drive_type=ICON_NETWORK;
            break;
          case DRIVE_REMOVABLE:
            drive_type=ICON_FLOPPY_3_5;
            break;
          case DRIVE_FIXED:
            drive_type=ICON_HDD;
            break;
          default:
            drive_type=ICON_NETWORK;
            break;
        }
        drive_name[0]='A'+bit_index;
        Add_element_to_list(drive_name,2);
        Filelist_nb_directories++;
        drive_index++;
      }
    }
  }
  #else
  {
    //Sous les différents unix, on va mettre
    // un disque dur qui pointera vers la racine,
    // et un autre vers le home directory de l'utilisateur.
    // Ensuite on utilise read_file_system_list pour compléter
    struct mount_entry* Liste_points_montage;
    struct mount_entry* next;
    #if defined(__BEOS__) || defined(__HAIKU__)
        char * home_dir = getenv("$HOME");
    #else
        char * home_dir = getenv("HOME");
    #endif
    Add_element_to_list("/", 2);
    Filelist_nb_directories++;
    if(home_dir)
    {
        Add_element_to_list(home_dir, 2);
        Filelist_nb_directories++;
    }
    Liste_points_montage = read_file_system_list(0);
    while(Liste_points_montage != NULL)
    {
        if(Liste_points_montage->me_dummy == 0 && strcmp(Liste_points_montage->me_mountdir,"/") && strcmp(Liste_points_montage->me_mountdir,"/home"))
        {
            Add_element_to_list(Liste_points_montage->me_mountdir,2);
            Filelist_nb_directories++;
        }
        next = Liste_points_montage -> me_next;
        #if !(defined(__macosx__) || defined(__FreeBSD__))
          free(Liste_points_montage -> me_type);
        #endif
        free(Liste_points_montage -> me_devname);
        free(Liste_points_montage -> me_mountdir);
        free(Liste_points_montage);
        Liste_points_montage = next;
    }
  }
  #endif
  Filelist_nb_elements=Filelist_nb_directories+Filelist_nb_files;
}
// -- Tri de la liste des fichiers et répertoires ---------------------------
void Sort_list_of_files(void)
// Tri la liste chainée existante dans l'ordre suivant:
//
// * Les répertoires d'abord, dans l'ordre alphabétique de leur nom
// * Les fichiers ensuite, dans l'ordre alphabétique de leur nom
{
  byte   list_is_sorted; // Booléen "La liste est triée"
  byte   need_swap;          // Booléen "Il faut inverser les éléments"
  T_Fileselector_item * prev_item;
  T_Fileselector_item * current_item;
  T_Fileselector_item * next_item;
  T_Fileselector_item * next_to_next_item;
  // Avant de trier quoi que ce soit, on vérifie qu'il y ait suffisamment
  // d'éléments pour qu'il soit possibles qu'ils soient en désordre:
  if (Filelist_nb_elements>1)
  {
    do
    {
      // Par défaut, on considère que la liste est triée
      list_is_sorted=1;
      current_item=Filelist;
      next_item=current_item->Next;
      while ( (current_item!=NULL) && (next_item!=NULL) )
      {
        // On commence par supposer qu'il n'y pas pas besoin d'inversion
        need_swap=0;
        // Ensuite, on vérifie si les deux éléments sont bien dans l'ordre ou
        // non:
          // Si l'élément courant est un fichier est que le suivant est
          // un répertoire -> need_swap
        if ( current_item->Type < next_item->Type )
          need_swap=1;
          // Si les deux éléments sont de même type et que le nom du suivant
          // est plus petit que celui du courant -> need_swap
        else if ( (current_item->Type==next_item->Type) &&
                  (strcmp(current_item->Full_name,next_item->Full_name)>0) )
          need_swap=1;
        if (need_swap)
        {
          // Si les deux éléments nécessitent d'être inversé:
          // On les inverses:
          // On note avant tout les éléments qui encapsulent nos deux amis
          prev_item         =current_item->Previous;
          next_to_next_item=next_item->Next;
          // On permute le chaînage des deux éléments entree eux
          current_item->Next  =next_to_next_item;
          current_item->Previous=next_item;
          next_item->Next  =current_item;
          next_item->Previous=prev_item;
          // On tente un chaînage des éléments encapsulant les compères:
          if (prev_item!=NULL)
            prev_item->Next=next_item;
          if (next_to_next_item!=NULL)
            next_to_next_item->Previous=current_item;
          // On fait bien attention à modifier la tête de liste en cas de besoin
          if (current_item==Filelist)
            Filelist=next_item;
          // Ensuite, on se prépare à étudier les éléments précédents:
          current_item=prev_item;
          // Et on constate que la liste n'était pas encore génialement triée
          list_is_sorted=0;
        }
        else
        {
          // Si les deux éléments sont dans l'ordre:
          // On passe aux suivants
          current_item=current_item->Next;
          next_item=next_item->Next;
        }
      }
    }
    while (!list_is_sorted);
  }
}
// -- Affichage des éléments de la liste de fichier / répertoire ------------
void Display_file_list(short offset_first,short selector_offset)
//
// offset_first = Décalage entre le premier fichier visible dans le
//                   sélecteur et le premier fichier de la liste
//
// selector_offset  = Décalage entre le premier fichier visible dans le
//                   sélecteur et le fichier sélectionné dans la liste
//
{
  T_Fileselector_item * current_item;
  byte   index;  // index du fichier qu'on affiche (0 -> 9)
  byte   text_color;
  byte   background_color;
  // On vérifie s'il y a au moins 1 fichier dans la liste:
  if (Filelist_nb_elements>0)
  {
    // On commence par chercher à pointer sur le premier fichier visible:
    current_item=Filelist;
    for (;offset_first>0;offset_first--)
      current_item=current_item->Next;
    // Pour chacun des 10 éléments inscriptibles à l'écran
    for (index=0;index<10;index++)
    {
      // S'il est sélectionné:
      if (!selector_offset)
      {
        // Si c'est un fichier
        if (current_item->Type==0)
          text_color=SELECTED_FILE_COLOR;
        else
          text_color=SELECTED_DIRECTORY_COLOR;
        background_color=SELECTED_BACKGROUND_COLOR;
      }
      else
      {
        // Si c'est un fichier
        if (current_item->Type==0)
          text_color=NORMAL_FILE_COLOR;
        else
          text_color=NORMAL_DIRECTORY_COLOR;
        background_color=NORMAL_BACKGROUND_COLOR;
      }
      // On affiche l'élément
      Print_in_window(8,95+index*8,current_item->Short_name,text_color,background_color);
      // On passe à la ligne suivante
      selector_offset--;
      current_item=current_item->Next;
      if (!current_item)
        break;
    } // End de la boucle d'affichage
  } // End du test d'existence de fichiers
}
// -- Récupérer le libellé d'un élément de la liste -------------------------
void Get_selected_item(short offset_first,short selector_offset,char * label,int *type)
//
// offset_first = Décalage entre le premier fichier visible dans le
//                   sélecteur et le premier fichier de la liste
//
// selector_offset  = Décalage entre le premier fichier visible dans le
//                   sélecteur et le fichier à récupérer
//
// label          = str de réception du libellé de l'élément
//
// type             = Récupération du type: 0 fichier, 1 repertoire, 2 lecteur.
//                    Passer NULL si pas interessé.
{
  T_Fileselector_item * current_item;
  // On vérifie s'il y a au moins 1 fichier dans la liste:
  if (Filelist_nb_elements>0)
  {
    // On commence par chercher à pointer sur le premier fichier visible:
    current_item=Filelist;
    for (;offset_first>0;offset_first--)
      current_item=current_item->Next;
    // Ensuite, on saute autant d'éléments que le décalage demandé:
    for (;selector_offset>0;selector_offset--)
      current_item=current_item->Next;
    // On recopie la chaîne
    strcpy(label, current_item->Full_name);
    if (type != NULL)
      *type=current_item->Type;
  } // End du test d'existence de fichiers
}
// ----------------- Déplacements dans la liste de fichiers -----------------
void Selector_scroll_down(short * offset_first,short * selector_offset)
// Fait scroller vers le bas le sélecteur de fichier... (si possible)
{
  if ( ((*selector_offset)<9)
    && ( (*selector_offset)+1 < Filelist_nb_elements ) )
    // Si la sélection peut descendre
    Display_file_list(*offset_first,++(*selector_offset));
  else // Sinon, descendre la fenêtre (si possible)
  if ((*offset_first)+100)
    // Si la sélection peut monter
    Display_file_list(*offset_first,--(*selector_offset));
  else // Sinon, monter la fenêtre (si possible)
  if ((*offset_first)>0)
    Display_file_list(--(*offset_first),*selector_offset);
}
void Selector_page_down(short * offset_first,short * selector_offset, short lines)
{
  if (Filelist_nb_elements-1>*offset_first+*selector_offset)
  {
    if (*selector_offset<9)
    {
      if (Filelist_nb_elements<10)
      {
        *offset_first=0;
        *selector_offset=Filelist_nb_elements-1;
      }
      else *selector_offset=9;
    }
    else
    {
      if (Filelist_nb_elements>*offset_first+18)
        *offset_first+=lines;
      else
      {
        *offset_first=Filelist_nb_elements-10;
        *selector_offset=9;
      }
    }
  }
  Display_file_list(*offset_first,*selector_offset);
}
void Selector_page_up(short * offset_first,short * selector_offset, short lines)
{
  if (*offset_first+*selector_offset>0)
  {
    if (*selector_offset>0)
      *selector_offset=0;
    else
    {
      if (*offset_first>lines)
        *offset_first-=lines;
      else
        *offset_first=0;
    }
  }
  Display_file_list(*offset_first,*selector_offset);
}
void Selector_end(short * offset_first,short * selector_offset)
{
  if (Filelist_nb_elements<10)
  {
    *offset_first=0;
    *selector_offset=Filelist_nb_elements-1;
  }
  else
  {
    *offset_first=Filelist_nb_elements-10;
    *selector_offset=9;
  }
  Display_file_list(*offset_first,*selector_offset);
}
void Selector_home(short * offset_first,short * selector_offset)
{
  Display_file_list((*offset_first)=0,(*selector_offset)=0);
}
short Compute_click_offset_in_fileselector(void)
/*
  Renvoie le décalage dans le sélecteur de fichier sur lequel on a clické.
  Renvoie le décalage du dernier fichier si on a clické au delà.
  Renvoie -1 si le sélecteur est vide.
*/
{
  short computed_offset;
  computed_offset=(((Mouse_Y-Window_pos_Y)/Menu_factor_Y)-95)>>3;
  if (computed_offset>=Filelist_nb_elements)
    computed_offset=Filelist_nb_elements-1;
  return computed_offset;
}
void Display_bookmark(T_Dropdown_button * Button, int bookmark_number)
{
  if (Config.Bookmark_directory[bookmark_number])
  {
    int label_size;
    // Libellé
    Print_in_window_limited(Button->Pos_X+3+10,Button->Pos_Y+2,Config.Bookmark_label[bookmark_number],8,MC_Black,MC_Light);
    label_size=strlen(Config.Bookmark_label[bookmark_number]);
    if (label_size<8)
      Window_rectangle(Button->Pos_X+3+10+label_size*8,Button->Pos_Y+2,(8-label_size)*8,8,MC_Light);
    // Menu apparait sur clic droit
    Button->Active_button=RIGHT_SIDE;
    // item actifs
    Window_dropdown_clear_items(Button);
    Window_dropdown_add_item(Button,0,"Set");
    Window_dropdown_add_item(Button,1,"Rename");
    Window_dropdown_add_item(Button,2,"Clear");    
  }
  else
  {
    // Libellé
    Print_in_window(Button->Pos_X+3+10,Button->Pos_Y+2,"--------",MC_Dark,MC_Light);
    // Menu apparait sur clic droit ou gauche
    Button->Active_button=RIGHT_SIDE|LEFT_SIDE;
    // item actifs
    Window_dropdown_clear_items(Button);
    Window_dropdown_add_item(Button,0,"Set");
  }
}
//------------------------ Chargements et sauvegardes ------------------------
void Print_current_directory(void)
//
// Affiche Main_current_directory sur 37 caractères
//
{
  char temp_name[MAX_DISPLAYABLE_PATH+1]; // Nom tronqué
  int  length; // length du répertoire courant
  int  index;   // index de parcours de la chaine complète
  Window_rectangle(10,84,37*8,8,MC_Light);
  length=strlen(Main_current_directory);
  if (length>MAX_DISPLAYABLE_PATH)
  { // Doh! il va falloir tronquer le répertoire (bouh !)
    // On commence par copier bêtement les 3 premiers caractères (e.g. "C:\")
    for (index=0;index<3;index++)
      temp_name[index]=Main_current_directory[index];
    // On y rajoute 3 petits points:
    strcpy(temp_name+3,"...");
    //  Ensuite, on cherche un endroit à partir duquel on pourrait loger tout
    // le reste de la chaine (Ouaaaaaah!!! Vachement fort le mec!!)
    for (index++;indexNb_elements=Filelist_nb_elements;
  button->Position=Position;
  Compute_slider_cursor_height(button);
  Window_draw_slider(button);
  // On efface les anciens noms de fichier:
  Window_rectangle(8-1,95-1,144+2,80+2,MC_Black);
  // On affiche les nouveaux:
  Display_file_list(Position,offset);
  Update_window_area(8-1,95-1,144+2,80+2);
  // On récupère le nom du schmilblick à "accéder"
  Get_selected_item(Position,offset,Main_filename,&Selected_type);
  // On affiche le nouveau nom de fichier
  Print_filename_in_fileselector();
  // On affiche le nom du répertoire courant
  Print_current_directory();
}
void Reload_list_of_files(byte filter, short Position, short offset,
                           T_Scroller_button * button)
{
  Read_list_of_files(filter);
  Sort_list_of_files();
  Prepare_and_display_filelist(Position,offset,button);
}
void Scroll_fileselector(T_Scroller_button * file_scroller)
{
  char old_filename[MAX_PATH_CHARACTERS];
  strcpy(old_filename,Main_filename);
  // On regarde si la liste a bougé
  if (file_scroller->Position!=Main_fileselector_position)
  {
    // Si c'est le cas, il faut mettre à jour la jauge
    file_scroller->Position=Main_fileselector_position;
    Window_draw_slider(file_scroller);
  }
  // On récupére le nom du schmilblick à "accéder"
  Get_selected_item(Main_fileselector_position,Main_fileselector_offset,Main_filename,&Selected_type);
  if (strcmp(old_filename,Main_filename))
    New_preview_is_needed=1;
  // On affiche le nouveau nom de fichier
  Print_filename_in_fileselector();
  Display_cursor();
}
short Find_file_in_fileselector(char * fname)
{
  T_Fileselector_item * current_item;
  short  index;
  for (index=0, current_item=Filelist;
       ((current_item!=NULL) && (strcmp(current_item->Full_name,fname)));
       index++,current_item=current_item->Next);
  return (current_item!=NULL)?index:0;
}
void Highlight_file(char * fname)
{
  short index;
  index=Find_file_in_fileselector(fname);
  if ((Filelist_nb_elements<=10) || (index<5))
  {
    Main_fileselector_position=0;
    Main_fileselector_offset=index;
  }
  else
  {
    if (index>=Filelist_nb_elements-5)
    {
      Main_fileselector_position=Filelist_nb_elements-10;
      Main_fileselector_offset=index-Main_fileselector_position;
    }
    else
    {
      Main_fileselector_position=index-4;
      Main_fileselector_offset=4;
    }
  }
}
char FFF_best_name[MAX_PATH_CHARACTERS];
char * Find_filename_match(char * fname)
{
  char * best_name_ptr;
  T_Fileselector_item * current_item;
  byte   matching_letters=0;
  byte   counter;
  strcpy(FFF_best_name,Main_filename);
  best_name_ptr=NULL;
  for (current_item=Filelist; current_item!=NULL; current_item=current_item->Next)
  {
    if ( (!Config.Find_file_fast)
      || (Config.Find_file_fast==(current_item->Type+1)) )
    {
      // On compare et si c'est mieux, on stocke dans Meilleur_nom
      for (counter=0; fname[counter]!='\0' && tolower(current_item->Full_name[counter])==tolower(fname[counter]); counter++);
      if (counter>matching_letters)
      {
        matching_letters=counter;
        strcpy(FFF_best_name,current_item->Full_name);
        best_name_ptr=current_item->Full_name;
      }
    }
  }
  return best_name_ptr;
}
byte Button_Load_or_Save(byte load, byte image)
  // load=1 => On affiche le menu du bouton LOAD
  // load=0 => On affiche le menu du bouton SAVE
{
  short clicked_button;
  T_Scroller_button * file_scroller;
  T_Dropdown_button * formats_dropdown;
  T_Dropdown_button * bookmark_dropdown[4];
  short temp;
  int dummy=0;       // Sert à appeler SDL_GetKeyState
  byte  save_or_load_image=0;
  byte  has_clicked_ok=0;// Indique si on a clické sur Load ou Save ou sur
                             //un bouton enclenchant Load ou Save juste après.
  T_Components * initial_palette; // |  Données concernant l'image qui
  byte  initial_image_is_modified;         // |  sont mémorisées pour pouvoir
  short initial_image_width;          // |- être restaurées en sortant,
  short initial_image_height;          // |  parce que la preview elle les
  byte  old_back_color;             // |  fout en l'air (c'te conne).
  char  initial_filename[MAX_PATH_CHARACTERS]; // Sert à laisser le nom courant du fichier en cas de sauvegarde
  char  previous_directory[MAX_PATH_CHARACTERS]; // Répertoire d'où l'on vient après un CHDIR
  char  initial_comment[COMMENT_SIZE+1];
  char  quicksearch_filename[MAX_PATH_CHARACTERS]="";
  char  save_filename[MAX_PATH_CHARACTERS];
  char * most_matching_filename;
  initial_palette=(T_Components *)malloc(sizeof(T_Palette));
  memcpy(initial_palette,Main_palette,sizeof(T_Palette));
  old_back_color=Back_color;
  initial_image_is_modified=Main_image_is_modified;
  initial_image_width=Main_image_width;
  initial_image_height=Main_image_height;
  strcpy(initial_filename,Main_filename);
  strcpy(initial_comment,Main_comment);
  if (load)
  {
    if (image)
      Open_window(310,200,"Load picture");
    else
      Open_window(310,200,"Load brush");
    Window_set_normal_button(198,180,51,14,"Load",0,1,SDLK_RETURN); // 1
  }
  else
  {
    if (image)
      Open_window(310,200,"Save picture");
    else
      Open_window(310,200,"Save brush");
    Window_set_normal_button(198,180,51,14,"Save",0,1,SDLK_RETURN); // 1
    if (Main_format==0) // Correction du *.*
    {
      Main_format=Main_fileformat;
      Main_fileselector_position=0;
      Main_fileselector_offset=0;
    }
    if (Main_format>NB_FORMATS_SAVE) // Correction d'un format insauvable
    {
      Main_format=DEFAULT_FILEFORMAT;
      Main_fileselector_position=0;
      Main_fileselector_offset=0;
    }
    // Affichage du commentaire
    if (File_formats[Main_format-1].Comment)
      Print_in_window(47,70,Main_comment,MC_Black,MC_Light);
  }
  Window_set_normal_button(253,180,51,14,"Cancel",0,1,KEY_ESC); // 2
  Window_set_normal_button(7,180,51,14,"Delete",0,1,SDLK_DELETE); // 3
  // Frame autour des infos sur le fichier de dessin
  Window_display_frame_in(6, 44,299, 37);
  // Frame autour de la preview
  Window_display_frame_in(181,93,124,84);
  // Frame autour du fileselector
  Window_display_frame_in(6,93,148,84);
  // Fileselector
  Window_set_special_button(9,95,144,80); // 4
  // Scroller du fileselector
  file_scroller = Window_set_scroller_button(160,94,82,1,10,0);               // 5
  // Dropdown pour les formats de fichier
  formats_dropdown=
    Window_set_dropdown_button(69,28,49,11,0,
      (Main_format==0)?"*.*":File_formats[Main_format-1].Extension,
      1,0,1,RIGHT_SIDE|LEFT_SIDE); // 6
  if (load)
    Window_dropdown_add_item(formats_dropdown,0,"*.*");
  for (temp=0;tempPos_X+3,bookmark_dropdown[temp]->Pos_Y+2,5);
    Display_bookmark(bookmark_dropdown[temp],temp);
  }
  // On prend bien soin de passer dans le répertoire courant (le bon qui faut! Oui madame!)
  if (load)
  {
    chdir(Main_current_directory);
    getcwd(Main_current_directory,256);
  }
  else
  {
    chdir(Main_file_directory);
    getcwd(Main_current_directory,256);
  }
  
  // Affichage des premiers fichiers visibles:
  Reload_list_of_files(Main_format,Main_fileselector_position,Main_fileselector_offset,file_scroller);
  if (!load)
  {
    // On initialise le nom de fichier à celui en cours et non pas celui sous
    // la barre de sélection
    strcpy(Main_filename,initial_filename);
    // On affiche le nouveau nom de fichier
    Print_filename_in_fileselector();
  }
  Pixel_load_function=Pixel_load_in_preview;
  New_preview_is_needed=1;
  Update_window_area(0,0,Window_width, Window_height);
  Display_cursor();
  do
  {
    clicked_button=Window_clicked_button();
    switch (clicked_button)
    {
      case -1 :
      case  0 :
        break;
      case  1 : // Load ou Save
      if(load)
        {
          // Determine the type
          if(File_exists(Main_filename)) 
          {
            Selected_type = 0;
            if(Directory_exists(Main_filename)) Selected_type = 1;
          }
          else
          {
            Selected_type = 1;
          }
        }
        else
        {
          if(Directory_exists(Main_filename)) Selected_type = 1;
          else Selected_type = 0;
        }
        has_clicked_ok=1;
        break;
      case  2 : // Cancel
        break;
      case  3 : // Delete
        if (Filelist_nb_elements && (*Main_filename!='.') && Selected_type!=2)
        {
          char * message;
          Hide_cursor();
          // On affiche une demande de confirmation
          if (Main_fileselector_position+Main_fileselector_offset>=Filelist_nb_directories)
          {
            message="Delete file ?";
          }
          else
          {
            message="Remove directory ?";
          }
          if (Confirmation_box(message))
          {
            // Si c'est un fichier
            if (Main_fileselector_position+Main_fileselector_offset>=Filelist_nb_directories)
              // On efface le fichier (si on peut)
              temp=(!remove(Main_filename));
            else // Si c'est un repertoire
              // On efface le repertoire (si on peut)
              temp=(!rmdir(Main_filename));
            if (temp) // temp indique si l'effacement s'est bien passé
            {
              // On remonte si c'était le dernier élément de la liste
              if (Main_fileselector_position+Main_fileselector_offset==Filelist_nb_elements-1)
              {
                if (Main_fileselector_position)
                  Main_fileselector_position--;
                else
                  if (Main_fileselector_offset)
                    Main_fileselector_offset--;
              }
              else // Si ce n'était pas le dernier, il faut faire gaffe à ce
              {    // que ses copains d'en dessous ne remontent pas trop.
                if ( (Main_fileselector_position)
                  && (Main_fileselector_position+10==Filelist_nb_elements) )
                  {
                    Main_fileselector_position--;
                    Main_fileselector_offset++;
                  }
              }
              // On relit les informations
              Reload_list_of_files(Main_format,Main_fileselector_position,Main_fileselector_offset,file_scroller);
              // On demande la preview du nouveau fichier sur lequel on se trouve
              New_preview_is_needed=1;
            }
            else
              Error(0);
            // On place la barre de sélection du brouillon au début s'il a le
            // même répertoire que l'image principale.
            if (!strcmp(Main_current_directory,Spare_current_directory))
            {
              Spare_fileselector_position=0;
              Spare_fileselector_offset=0;
            }
          }
        }
        break;
      case  4 : // Zone d'affichage de la liste de fichiers
        Hide_cursor();
        temp=Compute_click_offset_in_fileselector();
        if (temp>=0)
        {
          if (temp!=Main_fileselector_offset)
          {
            // On met à jour le décalage
            Main_fileselector_offset=temp;
            // On récupére le nom du schmilblick à "accéder"
            Get_selected_item(Main_fileselector_position,Main_fileselector_offset,Main_filename,&Selected_type);
            // On affiche le nouveau nom de fichier
            Print_filename_in_fileselector();
            // On affiche à nouveau la liste
            Display_file_list(Main_fileselector_position,Main_fileselector_offset);
            // On vient de changer de nom de fichier, donc on doit s'appreter
            // a rafficher une preview
            New_preview_is_needed=1;
            *quicksearch_filename=0;
          }
          else
          {
            //   En sauvegarde, si on a double-clické sur un répertoire, il
            // faut mettre le nom de fichier au nom du répertoire. Sinon, dans
            // certains cas, on risque de sauvegarder avec le nom du fichier
            // actuel au lieu de changer de répertoire.
            if (Main_fileselector_position+Main_fileselector_offset=10 && clicked_button<10+NB_BOOKMARKS)
          {
            // Bookmark
            char * directory_name;
            
            switch(Window_attribute2)
            {
              case -1: // bouton lui-même: aller au répertoire mémorisé
                if (Config.Bookmark_directory[clicked_button-10])
                {
                  *quicksearch_filename=0;
                  strcpy(Main_filename,Config.Bookmark_directory[clicked_button-10]);
                  Selected_type=1;
                  has_clicked_ok=1;
                  *quicksearch_filename=0;
                }
                break;
                
              case 0: // Set
                if (Config.Bookmark_directory[clicked_button-10])
                  free(Config.Bookmark_directory[clicked_button-10]);
                Config.Bookmark_label[clicked_button-10][0]='\0';
                temp=strlen(Main_current_directory);
                Config.Bookmark_directory[clicked_button-10]=malloc(temp+1);
                strcpy(Config.Bookmark_directory[clicked_button-10],Main_current_directory);
                
                directory_name=Find_last_slash(Main_current_directory);
                if (directory_name && directory_name[1]!='\0')
                  directory_name++;
                else
                  directory_name=Main_current_directory;
                temp=strlen(directory_name);
                strncpy(Config.Bookmark_label[clicked_button-10],directory_name,8);
                if (temp>8)
                {
                  Config.Bookmark_label[clicked_button-10][7]=ELLIPSIS_CHARACTER;
                  Config.Bookmark_label[clicked_button-10][8]='\0';
                }
                Display_bookmark(bookmark_dropdown[clicked_button-10],clicked_button-10);
                break;
                
              case 1: // Rename
                if (Config.Bookmark_directory[clicked_button-10])
                {
                  // On enlève les "..." avant l'édition
                  char bookmark_label[8+1];
                  strcpy(bookmark_label, Config.Bookmark_label[clicked_button-10]);
                  if (bookmark_label[7]==ELLIPSIS_CHARACTER)
                    bookmark_label[7]='\0';
                  if (Readline_ex(bookmark_dropdown[clicked_button-10]->Pos_X+3+10,bookmark_dropdown[clicked_button-10]->Pos_Y+2,bookmark_label,8,8,0))
                    strcpy(Config.Bookmark_label[clicked_button-10],bookmark_label);
                  Display_bookmark(bookmark_dropdown[clicked_button-10],clicked_button-10);
                  Display_cursor();
                }
                break;
              case 2: // Clear
                if (Config.Bookmark_directory[clicked_button-10] && Confirmation_box("Erase bookmark ?"))
                {
                  free(Config.Bookmark_directory[clicked_button-10]);
                  Config.Bookmark_directory[clicked_button-10]=NULL;
                  Config.Bookmark_label[clicked_button-10][0]='\0';
                  Display_bookmark(bookmark_dropdown[clicked_button-10],clicked_button-10);
                }
                break;
            }
          }
          break;
    }
    switch (Key)
    {
      case SDLK_UNKNOWN : break;
      case SDLK_DOWN : // Bas
        *quicksearch_filename=0;
        Hide_cursor();
        Selector_scroll_down(&Main_fileselector_position,&Main_fileselector_offset);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case SDLK_UP : // Haut
        *quicksearch_filename=0;
        Hide_cursor();
        Selector_scroll_up(&Main_fileselector_position,&Main_fileselector_offset);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case SDLK_PAGEDOWN : // PageDown
        *quicksearch_filename=0;
        Hide_cursor();
        Selector_page_down(&Main_fileselector_position,&Main_fileselector_offset,9);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case SDLK_PAGEUP : // PageUp
        *quicksearch_filename=0;
        Hide_cursor();
        Selector_page_up(&Main_fileselector_position,&Main_fileselector_offset,9);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case SDLK_END : // End
        *quicksearch_filename=0;
        Hide_cursor();
        Selector_end(&Main_fileselector_position,&Main_fileselector_offset);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case SDLK_HOME : // Home
        *quicksearch_filename=0;
        Hide_cursor();
        Selector_home(&Main_fileselector_position,&Main_fileselector_offset);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case KEY_MOUSEWHEELDOWN :
        *quicksearch_filename=0;
        Hide_cursor();
        Selector_page_down(&Main_fileselector_position,&Main_fileselector_offset,3);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case KEY_MOUSEWHEELUP :
        *quicksearch_filename=0;
        Hide_cursor();
        Selector_page_up(&Main_fileselector_position,&Main_fileselector_offset,3);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case SDLK_BACKSPACE : // Backspace
        *quicksearch_filename=0;
        // Si le choix ".." est bien en tête des propositions...
        if (!strcmp(Filelist->Full_name,PARENT_DIR))
        {                              
          // On va dans le répertoire parent.
          strcpy(Main_filename,PARENT_DIR);
          Selected_type=1;
          has_clicked_ok=1;
        }
        Key=0;
        break;
      default: // Autre => On se place sur le nom de fichier qui correspond
        if (clicked_button<=0)
        {
          if (Is_shortcut(Key,0x100+BUTTON_HELP))
          {
            Window_help(load?BUTTON_LOAD:BUTTON_SAVE, NULL);
            break;
          }
          temp=strlen(quicksearch_filename);
          if (Key_ANSI>= ' ' && Key_ANSI < 255 && temp<50)
          {
            quicksearch_filename[temp]=Key_ANSI;
            quicksearch_filename[temp+1]='\0';
            most_matching_filename=Find_filename_match(quicksearch_filename);
            if ( (most_matching_filename) )
            {
              temp=Main_fileselector_position+Main_fileselector_offset;
              Hide_cursor();
              Highlight_file(most_matching_filename);
              Prepare_and_display_filelist(Main_fileselector_position,Main_fileselector_offset,file_scroller);
              Display_cursor();
              if (temp!=Main_fileselector_position+Main_fileselector_offset)
                New_preview_is_needed=1;
            }
            else
              *quicksearch_filename=0;
            Key=0;
          }
        }
        else
          *quicksearch_filename=0;
    }
    if (has_clicked_ok)
    {
      //   Si c'est un répertoire, on annule "has_clicked_ok" et on passe
      // dedans.
      if (Selected_type!=0)
      {
        Hide_cursor();
        has_clicked_ok=0;
        // On mémorise le répertoire dans lequel on était
        if (strcmp(Main_filename,PARENT_DIR))
          strcpy(previous_directory,Format_filename(PARENT_DIR, 1));
        else
        {
          strcpy(previous_directory,
            Format_filename(Find_last_slash(Main_current_directory), 1)
            );
        }
        // On doit rentrer dans le répertoire:
        if (!chdir(Main_filename))
        {
          getcwd(Main_current_directory,256);
  
          // On lit le nouveau répertoire
          Read_list_of_files(Main_format);
          Sort_list_of_files();
          // On place la barre de sélection sur le répertoire d'où l'on vient
          Highlight_file(previous_directory);
        }
        else
          Error(0);
        // Affichage des premiers fichiers visibles:
        Prepare_and_display_filelist(Main_fileselector_position,Main_fileselector_offset,file_scroller);
        Display_cursor();
        New_preview_is_needed=1;
      }
      else  // Sinon on essaye de charger ou sauver le fichier
      {
        strcpy(Main_file_directory,Main_current_directory);
        if (!load)
          Main_fileformat=Main_format;
        save_or_load_image=1;
      }
    }
    // Gestion du chrono et des previews
    if (New_preview_is_needed)
    {
      // On efface les infos de la preview précédente s'il y en a une
      // d'affichée
      if (Timer_state==2)
      {
        Hide_cursor();
        // On efface le commentaire précédent
        Window_rectangle(45,70,32*8,8,MC_Light);
        // On nettoie la zone où va s'afficher la preview:
        Window_rectangle(183,95,120,80,MC_Light);
        // On efface les dimensions de l'image
        Window_rectangle(143,59,72,8,MC_Light);
        // On efface la taille du fichier
        Window_rectangle(236,59,56,8,MC_Light);
        // On efface le format du fichier
        Window_rectangle(59,59,3*8,8,MC_Light);
        // Affichage du commentaire
        if ( (!load) && (File_formats[Main_format-1].Comment) )
        {
          Print_in_window(45,70,Main_comment,MC_Black,MC_Light);
        }
        Display_cursor();
        // Un update pour couvrir les 4 zones: 3 libellés plus le commentaire
        Update_window_area(45,48,256,30);
        // Zone de preview
        Update_window_area(183,95,120,80);
      }
      New_preview_is_needed=0;
      Timer_state=0;         // State du chrono = Attente d'un Xème de seconde
      // On lit le temps de départ du chrono
      Init_chrono(Config.Timer_delay);
    }
    if (!Timer_state)  // Prendre une nouvelle mesure du chrono et regarder
      Check_timer(); // s'il ne faut pas afficher la preview
    if (Timer_state==1) // Il faut afficher la preview
    {
      if ( (Main_fileselector_position+Main_fileselector_offset>=Filelist_nb_directories) && (Filelist_nb_elements) )
      {
        strcpy(Main_file_directory,Main_current_directory);
        Hide_cursor();
        Load_image(image);
        //Update_window_area(183,95,120,80);
        Update_window_area(0,0,Window_width,Window_height);
        Display_cursor();
        // Après le chargement de la preview, on restaure tout ce qui aurait
        // pu être modifié par le chargement de l'image:
        memcpy(Main_palette,initial_palette,sizeof(T_Palette));
        Main_image_is_modified=initial_image_is_modified;
        Main_image_width=initial_image_width;
        Main_image_height=initial_image_height;
      }
      Timer_state=2; // On arrête le chrono
    }
  }
  while ( (!has_clicked_ok) && (clicked_button!=2) );
  // Si on annule, on restaure l'ancien commentaire
  if (clicked_button==2)
    strcpy(Main_comment,initial_comment);
  //   On restaure les données de l'image qui ont certainement été modifiées
  // par la preview.
  memcpy(Main_palette,initial_palette,sizeof(T_Palette));
  Set_palette(Main_palette);
  Back_color=old_back_color;
  Main_image_is_modified=initial_image_is_modified;
  Main_image_width=initial_image_width;
  Main_image_height=initial_image_height;
  Set_palette(Main_palette);
  Compute_optimal_menu_colors(Main_palette);
  temp=(Window_pos_Y+(Window_height*Menu_factor_Y)