/* vim:expandtab:ts=2 sw=2:
*/
/*  Grafx2 - The Ultimate 256-color bitmap paint program

	Copyright owned by various GrafX2 authors, see COPYRIGHT.txt for details.

    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 <http://www.gnu.org/licenses/>
*/

#if defined(__amigaos4__) || defined(__AROS__) || defined(__MORPHOS__) || defined(__amigaos__)
    #include <proto/dos.h>
    #include <sys/types.h>
#elif defined (__MINT__)
    #include <mint/sysbind.h>
#elif defined(WIN32)
    #include <windows.h>  // Native_filesel() is currently disabled
    //#include <commdlg.h>
#endif

#include <assert.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#ifdef _MSC_VER
#include <stdio.h>
#define strdup _strdup
#define wcsicmp _wcsicmp
#if _MSC_VER < 1900
#define snprintf _snprintf
#endif
#else
#include <strings.h>
#include <unistd.h>
#endif
#include <ctype.h>
#include <wctype.h>
#include <sys/types.h>

#include "const.h"
#include "struct.h"
#include "global.h"
#include "misc.h"
#include "osdep.h"
#include "errors.h"
#include "io.h"
#include "windows.h"
#include "screen.h"
#include "loadsave.h"
#include "mountlist.h"
#include "engine.h"
#include "readline.h"
#include "input.h"
#include "help.h"
#include "unicode.h"
#include "filesel.h"
#include "fileseltools.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

// -- Native fileselector for WIN32

#if 0 // Native fileselector is disabled
// Returns 0 if all ok, something else if failed
byte Native_filesel(byte load)
{
  //load = load;
#if WIN32
  OPENFILENAME ofn;
  char szFileName[MAX_PATH] = "";

  ZeroMemory(&ofn, sizeof(ofn));

  ofn.lStructSize = sizeof(ofn);
  ofn.hwndOwner = GFX2_Get_Window_Handle();
  ofn.lpstrFilter = TEXT("Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0");
#ifdef UNICODE
#else
  ofn.lpstrFile = szFileName;
#endif
  ofn.nMaxFile = MAX_PATH;
  ofn.Flags = OFN_EXPLORER;
  if(load) ofn.Flags |= OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
  ofn.lpstrDefExt = TEXT("txt");

  if(load)
  {
    if (GetOpenFileName(&ofn))
      // Do something usefull with the filename stored in szFileName 
      return 0;
    else
      // error - check if its just user pressing cancel or something else
      return CommDlgExtendedError();
  } else if(GetSaveFileName(&ofn)) {
    return 0;
  } else {
    // Check if cancel
    return CommDlgExtendedError();
  }
#else
  (void)load; // unused
#if 0
  /* if the native fileselector is used, we should implement it where needed
   * OS X ? GTK ? etc. */
  #ifndef __linux__ // This makes no sense on X11-oriented platforms. Nothing is really native there.
    #warning "EXPERIMENTAL function for native fileselector not available for this platform!"
  #endif
#endif
    return 255; // fail !
#endif
}
#endif

// -- "Standard" fileselector for other platforms

/// true if the preview timer need to be restarted
static byte New_preview_is_needed;

// -- Fileselector data

static T_Fileselector Filelist;

/// Selector settings to use, for all functions called by Load_or_save
static T_Selector_settings * Selector;

// Conventions:
//
// * Le fileselect modifie le répertoire courant. Ceci permet de n'avoir
//   qu'un findfirst dans le répertoire courant à faire:

/**
 * Update T_Fileselector::Nb_files T_Fileselector::Nb_directories T_Fileselector::Nb_elements
 * counts.
 * Also update the list index T_Fileselector::Index
 * @param list the linked list to update
 */
static void Recount_files(T_Fileselector *list)
{
  T_Fileselector_item *item;

  list->Nb_files=0;
  list->Nb_directories=0;
  list->Nb_elements=0;
  
  for (item = list->First; item != NULL; item = item->Next)
  {
    if (item->Type == FSOBJECT_FILE)
      list->Nb_files ++;
    else
      list->Nb_directories ++;
    list->Nb_elements ++;
  }
  
  if (list->Index)
  {
    free(list->Index);
    list->Index = NULL;
  }
  
  if (list->Nb_elements>0)
  {
    int i;
    
    list->Index = (T_Fileselector_item **) malloc(list->Nb_elements * sizeof(T_Fileselector_item *));
    if (list->Index)
    {
      // Fill the index
      for (item = list->First, i=0; item != NULL; item = item->Next, i++)
      {
        list->Index[i] = item;
      }
    }
    // If the malloc failed, we're probably in trouble, but I don't know
    // how to recover from that..I'll just make the index bulletproof.
  }
}

/**
 * This function free all item in the list, but not the list itself.
 * @param list the linked list
 */
void Free_fileselector_list(T_Fileselector *list)
{
  // Pointeur temporaire de destruction
  T_Fileselector_item * temp_item;

  while (list->First!=NULL)
  {
    // On mémorise l'adresse du premier élément de la liste
    temp_item =list->First;
    // On fait avancer la tête de la liste
    list->First=list->First->Next;
    // Et on efface l'ancien premier élément de la liste
    free(temp_item->Unicode_full_name);
    free(temp_item->Unicode_short_name);
    free(temp_item);
    temp_item = NULL;
  }
  Recount_files(list);
}


///
/// Checks if a file has the requested file extension.
/// The extension string can end with a ';' (remainder is ignored).
/// This function allows wildcard '?', and '*' if it's the only character.
int Check_extension(const char *filename_ext, const char * filter)
{
  int c;
  
  if (filter[0] == '*')
    return 1;

  // filename without extension
  if (filename_ext == NULL || filename_ext[0] == '\0')
    return (filter[0] == '\0' || filter[0] == ';');

  // Vérification caractère par caractère, case-insensitive.
  for (c = 0; !(filter[c] == '\0' || filter[c] == ';'); c++)
  {
    if (filter[c] != '?' &&
      tolower(filter[c]) != tolower(filename_ext[c]))
      return 0;
  }
  return filename_ext[c] == '\0';
}


// -- Lecture d'une liste de fichiers ---------------------------------------
struct Read_dir_pdata
{
  T_Fileselector *list;
  const char * filter;
};

static void Read_dir_callback(void * pdata, const char *file_name, const word *unicode_name, byte is_file, byte is_directory, byte is_hidden)
{
  T_Fileselector_item * item;
  struct Read_dir_pdata * p = (struct Read_dir_pdata *)pdata;

  if (p == NULL) // error !
    return;

  // Ignore 'current directory' entry
  if ( !strcmp(file_name, "."))
    return;

  // entries tagged "directory" :
  if (is_directory)
  {
    // On Windows, the presence of a "parent directory" entry has proven
    // unreliable on non-physical drives :
    // Sometimes it's missing, sometimes it's present even at root...
    // We skip it here and add a specific check after the loop
#if defined(WIN32)
    if (!strcmp(file_name, PARENT_DIR))
      return;
#endif

    // Don't display hidden file, unless requested by options
    if (!Config.Show_hidden_directories && is_hidden)
      return;

    // Add to list
    item = Add_element_to_list(p->list, file_name, Format_filename(file_name, 19, 1), FSOBJECT_DIR, ICON_NONE);
    if (item != NULL && unicode_name != NULL)
    {
      item->Unicode_full_name = Unicode_strdup(unicode_name);
      item->Unicode_short_name = Unicode_strdup(Format_filename_unicode(unicode_name, 19, 1));
    }
    p->list->Nb_directories++;
  }
  else if (is_file && // It's a file
          (Config.Show_hidden_files || !is_hidden))
  {
    const char * ext = p->filter;
    const char * file_name_ext = NULL;
#ifdef WIN32
	char long_ext[16];
#endif
	int pos_last_dot = Position_last_dot(file_name);

	if (pos_last_dot >= 0)
      file_name_ext = file_name + pos_last_dot + 1;
#ifdef WIN32
    if (unicode_name && unicode_name[0] != 0)
    {
      pos_last_dot = Position_last_dot_unicode(unicode_name);
      if (pos_last_dot >= 0)
      {
        int i;
        pos_last_dot++;
        for (i = 0; i < (int)sizeof(long_ext) - 1 && unicode_name[pos_last_dot + i] != 0; i++)
          long_ext[i] = (unicode_name[pos_last_dot + i] < 256) ? unicode_name[pos_last_dot + i] : '?';
        long_ext[i] = '\0';
        file_name_ext = long_ext;
      }
    }
#endif
    while (ext!=NULL)
    {
      if (Check_extension(file_name_ext, ext))
      {
        // Add to list
        item = Add_element_to_list(p->list, file_name, Format_filename(file_name, 19, 0), FSOBJECT_FILE, ICON_NONE);
        if (item != NULL && unicode_name != NULL)
        {
          item->Unicode_full_name = Unicode_strdup(unicode_name);
          item->Unicode_short_name = Unicode_strdup(Format_filename_unicode(unicode_name, 19, 0));
        }
        p->list->Nb_files++;
        // Stop searching
        break;
      }
      else
      {
        ext = strchr(ext, ';');
        if (ext)
          ext++;
      }
    }
  }
}



void Read_list_of_files(T_Fileselector *list, byte selected_format)
//  Cette procédure charge dans la liste chainée les fichiers dont l'extension
// correspond au format demandé.
{
  struct Read_dir_pdata callback_data;
  char * current_path;
#if defined (__MINT__)
  T_Fileselector_item *item=0;
  bool bFound=false;
#endif

  callback_data.list = list;
  // Tout d'abord, on déduit du format demandé un filtre à utiliser:
  callback_data.filter = Get_fileformat(selected_format)->Extensions;

  // Ensuite, on vide la liste actuelle:
  Free_fileselector_list(list);
  // Après effacement, il ne reste ni fichier ni répertoire dans la liste

  // On lit tous les répertoires:
  current_path = Get_current_directory(NULL, NULL, 0);

  For_each_directory_entry(current_path, &callback_data, Read_dir_callback);
  
  // Now here's OS-specific code to determine if "parent directory" entry
  // should appear.

#if defined(__MORPHOS__) || defined(__AROS__) || defined (__amigaos4__) || defined(__amigaos__) || defined(__SWITCH__)
  // Amiga systems: always
  Add_element_to_list(list, PARENT_DIR, Format_filename(PARENT_DIR,19,1), FSOBJECT_DIR, ICON_NONE);
  list->Nb_directories ++;
  
#elif defined (WIN32)
  // Windows :
  if (((current_path[0]>='a'&&current_path[0]<='z')||(current_path[0]>='A'&&current_path[0]<='Z')) &&
    current_path[1]==':' &&
    (
      (current_path[2]=='\0') ||
      (current_path[2]=='/'&&current_path[3]=='\0') ||
      (current_path[2]=='\\'&&current_path[3]=='\0')
    ))
  {
    // Path is X:\ or X:/ or X:
    // so don't display parent directory
  }
  else
  {
    Add_element_to_list(list, PARENT_DIR, Format_filename(PARENT_DIR,19,1), FSOBJECT_DIR, ICON_NONE);
    list->Nb_directories ++;
  }
  
#elif defined (__MINT__)
  // check if ".." exists if not add it
  // FreeMinT lists ".." already, but this is not so for TOS 
  // simply adding it will cause double PARENT_DIR under FreeMiNT
  
  bFound= false;
  
  for (item = list->First; item != NULL; item = item->Next)
  {
    if (item->Type == FSOBJECT_DIR && (strncmp(item->Full_name, PARENT_DIR, (sizeof(char)*2))==0) )
    {
      bFound=true;
      break;
    }
  }
  
  if(!bFound)
  {
    Add_element_to_list(list,PARENT_DIR,Format_filename(PARENT_DIR,19,1),FSOBJECT_DIR,ICON_NONE); // add if not present
    list->Nb_directories ++;  
  }
  
#endif

  free(current_path);

  if (list->Nb_files==0 && list->Nb_directories==0)
  {
    // This can happen on some empty network drives.
    // Add a dummy entry because the fileselector doesn't
    // seem to support empty list.
    Add_element_to_list(list, ".",Format_filename(".",19,1),FSOBJECT_DIR,ICON_NONE);
  }

  Recount_files(list);
}

#if defined(__amigaos4__) || defined(__AROS__) || defined(__MORPHOS__) || defined(__amigaos__)
void bstrtostr( BSTR in, STRPTR out, TEXT max )
{
  STRPTR iptr;
  dword i;
  dword len;

#if defined(__AROS__)
  iptr = AROS_BSTR_ADDR( in );
  len = AROS_BSTR_strlen( in );
#else
  iptr = BADDR( in );
  len = iptr[0];
  iptr++;
#endif

  if( max > len ) max = len;

  for( i=0; i<max; i++ , iptr++ ) out[i] = *iptr;

  out[i] = 0;
}
#endif

// -- Lecture d'une liste de lecteurs / volumes -----------------------------
void Read_list_of_drives(T_Fileselector *list, byte name_length)
{
#if defined(__MINT__)
    char drive_name[]="A:\\";
    unsigned long drive_bits=0;
    int drive_index=0;
    int bit_index=0;
#endif
  
  // Empty the current content of fileselector:
  Free_fileselector_list(list);
  // Reset number of items
  list->Nb_files=0;
  list->Nb_directories=0;

  #if defined(__amigaos4__) || defined(__AROS__) || defined(__MORPHOS__) || defined(__amigaos__)
  {
    struct DosList *dl;
    char tmp[256];

    dl = LockDosList( LDF_VOLUMES | LDF_ASSIGNS | LDF_READ );
    if( dl )
    {
      while( ( dl = NextDosEntry( dl, LDF_VOLUMES | LDF_ASSIGNS | LDF_READ ) ) )
      {
        bstrtostr( dl->dol_Name, tmp, 254 );
        strcat( tmp, ":" );
        Add_element_to_list(list, tmp, Format_filename(tmp, name_length, 2), FSOBJECT_DRIVE, ICON_NONE );
        list->Nb_directories++;
      }
      UnLockDosList( LDF_VOLUMES | LDF_ASSIGNS | LDF_READ );
    }
  }
  #elif defined (WIN32)
  {
    char drive_name[32];
    int drive_bits = GetLogicalDrives();
    int bit_index;
    enum ICON_TYPES icon;
    
    // Sous Windows, on a la totale, presque aussi bien que sous DOS:
    for (bit_index=0; bit_index < 26; 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.
        char drive_path[] = "A:\\";
        char volume_name[32];
        char file_system[32];

        drive_path[0] = 'A'+bit_index;
        switch (GetDriveTypeA(drive_path))
        {
          case DRIVE_CDROM:
            icon=ICON_CDROM;
            break;
          case DRIVE_REMOTE:
            icon=ICON_NETWORK;
            break;
          case DRIVE_REMOVABLE:
            icon=ICON_FLOPPY_3_5;
            break;
          case DRIVE_FIXED:
            icon=ICON_HDD;
            break;
          case DRIVE_RAMDISK:
          default:
            icon=ICON_NETWORK;
            break;
        }
        if (GetVolumeInformationA(drive_path, volume_name, sizeof(volume_name), NULL, NULL, NULL, file_system, sizeof(file_system)))
          snprintf(drive_name, sizeof(drive_name), "%s %s", drive_path, volume_name);
        else
          snprintf(drive_name, sizeof(drive_name), "%s  - empty -", drive_path);
        Add_element_to_list(list, drive_path,
                            Format_filename(drive_name, name_length-1, FSOBJECT_DRIVE),
                            FSOBJECT_DRIVE, icon);
        list->Nb_directories++;
      }
    }
    Enumerate_Network(list);
  }
  #elif defined(__MINT__)
    drive_bits = Drvmap(); //get drive map bitfield

    for (bit_index=0; bit_index<32; bit_index++)
    {
      if ( (1 << bit_index) & drive_bits )
      {
        drive_name[0]='A'+bit_index;
        Add_element_to_list(list, drive_name,Format_filename(drive_name,name_length,2),FSOBJECT_DRIVE,ICON_NONE);
        list->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* mount_points_list;
    struct mount_entry* next;

    char * home_dir = getenv("HOME");
    Add_element_to_list(list, "/", Format_filename("/",name_length-1,2), FSOBJECT_DRIVE, ICON_HDD);
    list->Nb_directories++;
    if(home_dir)
    {
        Add_element_to_list(list, home_dir, Format_filename(home_dir, name_length, 2), FSOBJECT_DRIVE, ICON_NONE);
        list->Nb_directories++;
    }

    mount_points_list = read_file_system_list(0);

    while(mount_points_list != NULL)
    {
      enum ICON_TYPES icon = ICON_NONE;
      if (mount_points_list->me_remote)
        icon = ICON_NETWORK;
      else if (strcmp(mount_points_list->me_type, "cd9660") == 0)
        icon = ICON_CDROM;
      else if (strcmp(mount_points_list->me_type, "nfs") == 0)
        icon = ICON_NETWORK;
      else if (strcmp(mount_points_list->me_type, "msdos") == 0)
        icon = ICON_FLOPPY_3_5; // Only a guess...
      else if (strcmp(mount_points_list->me_type, "ext2fs") == 0)
        icon = ICON_HDD; // Only a guess...
      else if (strcmp(mount_points_list->me_type, "hfs") == 0)
        icon = ICON_HDD; // Only a guess...
      else if (strcmp(mount_points_list->me_type, "ufs") == 0)
        icon = ICON_HDD; // Only a guess...
      else if (strcmp(mount_points_list->me_type, "zfs") == 0)
        icon = ICON_HDD; // Only a guess...

      GFX2_Log(GFX2_DEBUG, "dummy=%u type=%s path=%s icon=%d\n", mount_points_list->me_dummy, mount_points_list->me_type, mount_points_list->me_mountdir, (int)icon);

      if(mount_points_list->me_dummy == 0 && strcmp(mount_points_list->me_mountdir,"/") && strcmp(mount_points_list->me_mountdir,"/home"))
        {
          Add_element_to_list(list, mount_points_list->me_mountdir,
              Format_filename(mount_points_list->me_mountdir, name_length + (icon==ICON_NONE?0:-1), 2), FSOBJECT_DRIVE, icon);
          list->Nb_directories++;
        }
        next = mount_points_list -> me_next;
        if (mount_points_list->me_type_malloced)
        {
          free(mount_points_list -> me_type);
        }
        free(mount_points_list -> me_devname);
        free(mount_points_list -> me_mountdir);
        free(mount_points_list);
        mount_points_list = next;
    }

  }
  #endif

  Recount_files(list);
}

// Comparison of file names:
#ifdef WIN32
// case-insensitive
static int Windows_Path_Compare(const char * s1, const char * s2)
{
  // trick to make network shares path (starting with \\)
  // at the end of the list
  if (s1[0] == '\\')
  {
    if (s2[0] != '\\')
      return 1;
  }
  else
  {
    // s1[0] != '\\'
    if (s2[0] == '\\')
      return -1;
  }
  return strcasecmp(s1, s2);
}
#define FILENAME_COMPARE Windows_Path_Compare
#define FILENAME_COMPARE_UNICODE wcsicmp
#else
// case-sensitive
#define FILENAME_COMPARE strcmp
#define FILENAME_COMPARE_UNICODE Unicode_strcmp
#endif


/**
 * Sort a file/directory list.
 * The sord is done in that order :
 * Directories first, in alphabetical order,
 * then Files, in alphabetical order.
 *
 * List counts and index are updated.
 * @param list the linked list
 */
void Sort_list_of_files(T_Fileselector *list)
{
  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;

  // Check there are at least two elements before sorting
  if (list->First && list->First->Next)
  {
    do
    {
      // Par défaut, on considère que la liste est triée
      list_is_sorted=1;

      current_item=list->First;
      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:

          // Drives go at the top of the list, and files go after them
        if ( (int)current_item->Type < (int)next_item->Type )
          need_swap=1;
          // If both elements have the same type, compare the file names, if
		  // current is alphabetically before, we need to swap, unless it is
          // parent directory, which should always go first
        else if (current_item->Type == next_item->Type)
        {
          if (FILENAME_COMPARE(next_item->Full_name, PARENT_DIR) == 0)
            need_swap = 1;
          else if (FILENAME_COMPARE(current_item->Full_name, PARENT_DIR) != 0)
          {
            if (current_item->Unicode_full_name != NULL && next_item->Unicode_full_name != NULL)
            {
              // compare unicode file names if they are available
              if (FILENAME_COMPARE_UNICODE(current_item->Unicode_full_name, next_item->Unicode_full_name) > 0)
                need_swap = 1;
            }
            else
            {
              if (FILENAME_COMPARE(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==list->First)
            list->First=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);
  }
  // Force a recount / re-index
  Recount_files(list);
}

T_Fileselector_item * Get_item_by_index(T_Fileselector *list, unsigned short index)
{
  // Safety
  if (list->Nb_elements == 0)
    return NULL;
  if (index >= list->Nb_elements)
    index=list->Nb_elements-1;

  if (list->Index)
  {
    return list->Index[index];
  }
  else
  {
    // Index not available.
    // Can only happen in case of malloc error.
    // Fall back anyway on iterative search
 
    T_Fileselector_item * item = list->First;
    for (; index > 0 && item != NULL; index--)
      item = item->Next;
    
    return item;
  }

}


/**
 * Display of the file/directory list items.
 * @param list the file list
 * @param offset_first offset between the 1st visible file and the first file in list.
 * @param selector_offset offset between the 1st visible file and the selected file.
 */
void Display_file_list(T_Fileselector *list, short offset_first,short selector_offset)
{
  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 (list->Nb_elements>0)
  {
    // On commence par chercher à pointer sur le premier fichier visible:
    current_item = Get_item_by_index(list, offset_first);

    // 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
      if (current_item->Icon != ICON_NONE)
      {
        // Name preceded by an icon
        Print_in_window(16,95+index*8,current_item->Short_name,text_color,background_color);
        Window_display_icon_sprite(8,95+index*8,current_item->Icon);
      }
      else
      {
        // Name without icon
        if (current_item->Unicode_short_name)
          Print_in_window_unicode(8,95+index*8,current_item->Unicode_short_name,text_color,background_color);
        else
          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
}


/**
 * Get the label of a list item.
 *
 * selector->filename and selector->filename_unicode are updated
 *
 * @param list the file list
 * @param selector the current File selector
 * @param type NULL or a pointer to receive the type : 0 = file, 1 = directory, 2 = drive.
 */
static void Get_selected_item(T_Fileselector *list, T_Selector_settings * selector, enum FSOBJECT_TYPE *type)
{
  T_Fileselector_item * current_item;

  // On vérifie s'il y a au moins 1 fichier dans la liste:
  if (list->Nb_elements > 0)
  {
    // On commence par chercher à pointer sur le premier fichier visible:
    // Ensuite, on saute autant d'éléments que le décalage demandé:
    current_item = Get_item_by_index(list, selector->Position + selector->Offset);

    // On recopie la chaîne
    free(selector->filename);
    selector->filename = strdup(current_item->Full_name);
    free(selector->filename_unicode);
    if (current_item->Unicode_full_name)
      selector->filename_unicode = Unicode_strdup(current_item->Unicode_full_name);
    else
      selector->filename_unicode = NULL;

    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(&Filelist, *offset_first,++(*selector_offset));
  else // Sinon, descendre la fenêtre (si possible)
  if ((*offset_first)+10<Filelist.Nb_elements)
    Display_file_list(&Filelist, ++(*offset_first),*selector_offset);
}


void Selector_scroll_up(short * offset_first,short * selector_offset)
// Fait scroller vers le haut le sélecteur de fichier... (si possible)
{
  if ((*selector_offset)>0)
    // Si la sélection peut monter
    Display_file_list(&Filelist, *offset_first,--(*selector_offset));
  else // Sinon, monter la fenêtre (si possible)
  if ((*offset_first)>0)
    Display_file_list(&Filelist, --(*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(&Filelist, *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(&Filelist, *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(&Filelist, *offset_first,*selector_offset);
}


void Selector_home(short * offset_first,short * selector_offset)
{
  Display_file_list(&Filelist, (*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;
}

static void Display_bookmark(T_Dropdown_button * Button, int bookmark_number)
{
  if (Config.Bookmark_directory[bookmark_number])
  {
    int label_size;
    // Label
#ifdef ENABLE_FILENAMES_ICONV
    word label[16];
    char * input = Config.Bookmark_label[bookmark_number];
    size_t inbytesleft = strlen(Config.Bookmark_label[bookmark_number]);
    char * output = (char *)label;
    size_t outbytesleft = sizeof(label) - 2;
    if (cd_utf16 != (iconv_t)-1 && iconv(cd_utf16, &input, &inbytesleft, &output, &outbytesleft) != (size_t)-1)
    {
      output[0] = '\0';
      output[1] = '\0';
      Print_in_window_limited_unicode(Button->Pos_X+3+10, Button->Pos_Y+2,
                                      label, 8, MC_Black, MC_Light);
      label_size = Unicode_strlen(label);
    }
    else
#endif
    {
      // Fallback
      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);
    // the menu is activated with right clic
    Button->Active_button = RIGHT_SIDE;
  }
  else
  {
    // Label
    Print_in_window(Button->Pos_X+3+10, Button->Pos_Y+2, "--------",
                    MC_Dark, MC_Light);
    // the menu is activated with right or left clic
    Button->Active_button = RIGHT_SIDE|LEFT_SIDE;
  }
  // item actifs
  Window_dropdown_clear_items(Button);
  Window_dropdown_add_item(Button,0,"Set");
#if !(defined(__amigaos4__) || defined(__AROS__) || defined(__MORPHOS__) || defined(__amigaos__))
  if (Portable_Installation_Detected)
    Window_dropdown_add_item(Button,3,"Set Rel");
#endif
  if (Config.Bookmark_directory[bookmark_number])
  {
    Window_dropdown_add_item(Button,1,"Rename");
    Window_dropdown_add_item(Button,2,"Clear");
  }
}

//------------------------ Chargements et sauvegardes ------------------------

/// Shows Selector->Directory on 37 chars
static void Print_current_directory(void)
{
  size_t length; // length du répertoire courant
  size_t index;  // index de parcours de la chaine complète

  Window_rectangle(10,84,37*8,8,MC_Light);

  if (Selector->Directory_unicode != NULL && Selector->Directory_unicode[0] != 0)
  {
    length=Unicode_strlen(Selector->Directory_unicode);
    if (length>MAX_DISPLAYABLE_PATH)
    { // We need to truncate the directory
      word temp_name[MAX_DISPLAYABLE_PATH+1]; // truncated name

      memcpy(temp_name, Selector->Directory_unicode, 3*2);  // first 3 chars "C:\"
      temp_name[3] = (byte)ELLIPSIS_CHARACTER;
      temp_name[4] = 0;

      // next we look for a place to fit everything ;)
      for (index=3;index<length;index++)
        if ( (Selector->Directory_unicode[index]==PATH_SEPARATOR[0]) &&
            (length-index<=MAX_DISPLAYABLE_PATH-4) )
        {
          // we found the place !
          Unicode_strlcpy(temp_name+4,Selector->Directory_unicode+index, MAX_DISPLAYABLE_PATH+1-4);
          break;
        }

      Print_in_window_unicode(10,84,temp_name,MC_Black,MC_Light);
    }
    else // The string is short enough
      Print_in_window_unicode(10,84,Selector->Directory_unicode,MC_Black,MC_Light);
  }
  else
  {
    char * converted_name;
#ifdef ENABLE_FILENAMES_ICONV
    /* convert file name from UTF8 to ANSI */
    char * input = Selector->Directory;
    size_t inbytesleft = strlen(input);
    char * output;
    size_t outbytesleft = inbytesleft + 1;
    converted_name = malloc(outbytesleft);
    output = converted_name;
    if(cd != (iconv_t)-1 && (ssize_t)iconv(cd, &input, &inbytesleft, &output, &outbytesleft) >= 0)
      *output = '\0';
    else
      strcpy(converted_name, Selector->Directory);
#else
    converted_name = strdup(Selector->Directory);
#endif /* ENABLE_FILENAMES_ICONV */

    length = strlen(converted_name);
    if (length>MAX_DISPLAYABLE_PATH)
    { // We need to truncate the directory
      char temp_name[MAX_DISPLAYABLE_PATH+1]; // truncated name

      for (index=0;index<3;index++) // copy the first 3 chars "C:\"
        temp_name[index]=converted_name[index];

      temp_name[3] = ELLIPSIS_CHARACTER;
      temp_name[4] = '\0';

      // next we look for a place to fit everything ;)
      for (index++;index<length;index++)
        if ( (converted_name[index]==PATH_SEPARATOR[0]) &&
            (length-index<=MAX_DISPLAYABLE_PATH-4) )
        {
          // we found the place !
          memcpy(temp_name + 4, converted_name + index, length - index + 1);
          break;
        }

      // display truncated string
      Print_in_window(10,84,temp_name,MC_Black,MC_Light);
    }
    else // The string is short enough
      Print_in_window(10,84,converted_name,MC_Black,MC_Light);
    free(converted_name);
  }

  Update_window_area(10,84,37*8,8);
}

//
// Print the current file name
//
void Print_filename_in_fileselector(void)
{
  char filename[32];
  if (Selector->filename != NULL)
    strncpy(filename, Selector->filename, sizeof(filename));
  else
    filename[0] = '\0';
#ifdef ENABLE_FILENAMES_ICONV
  if (Selector->filename != NULL)
  {
    char * input = (char *)Selector->filename;
    size_t inbytesleft = strlen(Selector->filename);
    char * output = filename;
    size_t outbytesleft = sizeof(filename)-1;
    if(cd != (iconv_t)-1 && (ssize_t)iconv(cd, &input, &inbytesleft, &output, &outbytesleft) >= 0)
      *output = '\0';
  }
#endif /* ENABLE_FILENAMES_ICONV */
  Window_rectangle(82,48,27*8,8,MC_Light);
  if (Selector->filename_unicode != NULL)
  {
    word filename_unicode[32];
    Unicode_strlcpy(filename_unicode, Selector->filename_unicode, 28); // 28 including the terminating 0
    Print_in_window_unicode(82,48,filename_unicode,MC_Black,MC_Light);
  }
  else
    Print_in_window_limited(82,48,filename,27,MC_Black,MC_Light);
  Update_window_area(82,48,27*8,8);
}

/// Type of the selected entry in the file selector.
static enum FSOBJECT_TYPE Selected_type;

/// Displays the file list with sliders, etc.
/// also optionally updates the current file name (Selector_filename)
/// @param Position the current position in the file list
/// @param offset   the offset of the selected item in the file list
/// @param button   the scrollbar/slider GUI control
/// @param setfilename option to update Selector_filename
static void Prepare_and_display_filelist(short Position, short offset, T_Scroller_button * button, int setfilename)
{
  button->Nb_elements=Filelist.Nb_elements;
  button->Position=Position;
  Compute_slider_cursor_length(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(&Filelist, Position,offset);

  Update_window_area(8-1,95-1,144+2,80+2);

  if (setfilename)
  {
    // On récupère le nom du schmilblick à "accéder"
    Get_selected_item(&Filelist, Selector, &Selected_type);
  }
  // On affiche le nouveau nom de fichier
  Print_filename_in_fileselector();
  // On affiche le nom du répertoire courant
  Print_current_directory();
}


static void Reload_list_of_files(byte filter, T_Scroller_button * button)
{
  Read_list_of_files(&Filelist, filter);
  Sort_list_of_files(&Filelist);
  //
  // Check and fix the fileselector positions, because 
  // the directory content may have changed.
  //
  // Make the offset absolute
  Selector->Offset += Selector->Position;
  // Ensure it's within limits
  if (Selector->Offset >= Filelist.Nb_elements)
  {
    Selector->Offset = Filelist.Nb_elements-1;
  }
  // Ensure the position doesn't show "too many files"
  if (Selector->Position!=0 && Selector->Position>(Filelist.Nb_elements-10))
  {
    if (Filelist.Nb_elements<10)
    {
      Selector->Position=0;
    }
    else
    {
      Selector->Position=Filelist.Nb_elements-10;
    }
  }
  // Restore the offset as relative to the position.
  Selector->Offset -= Selector->Position;

  Prepare_and_display_filelist(Selector->Position,Selector->Offset,button,1);
}

void Scroll_fileselector(T_Scroller_button * file_scroller)
{
  char * old_filename = strdup(Selector->filename);

  // On regarde si la liste a bougé
  if (file_scroller->Position!=Selector->Position)
  {
    // Si c'est le cas, il faut mettre à jour la jauge
    file_scroller->Position=Selector->Position;
    Window_draw_slider(file_scroller);
  }
  // On récupére le nom du schmilblick à "accéder"
  Get_selected_item(&Filelist, Selector, &Selected_type);
  if (strcmp(old_filename, Selector->filename))
    New_preview_is_needed=1;
  free(old_filename);

  // On affiche le nouveau nom de fichier
  Print_filename_in_fileselector();
  Display_cursor();
}


short Find_file_in_fileselector(T_Fileselector *list, const char * fname)
{
  T_Fileselector_item * item;
  short  index;
  short  close_match = -1;

  if (fname == NULL)
    return -1;
  index=0;
  for (item=list->First; item!=NULL; item=item->Next)
  {
    if (strcmp(item->Full_name,fname)==0)
      return index; // exact match
    if (strcasecmp(item->Full_name,fname)==0)
      close_match=index;
      
    index++;
  }

  return close_match;
}

/// Set the position and index of the file list according
/// to the selected index
/// @param index index of selected file
static void Highlight_file(short index)
{

  if ((Filelist.Nb_elements<=10) || (index<5))
  {
    Selector->Position=0;
    Selector->Offset=index;
  }
  else
  {
    if (index>=Filelist.Nb_elements-5)
    {
      Selector->Position=Filelist.Nb_elements-10;
      Selector->Offset=index-Selector->Position;
    }
    else
    {
      Selector->Position=index-4;
      Selector->Offset=4;
    }
  }
}


/// Find the item best matching the searched filename
///
/// used by quicksearch
/// @return -1 if not matching name found
static short Find_filename_match(const T_Fileselector *list, const word * fname)
{
  short best_match;
  T_Fileselector_item * current_item;
  short item_number;
  byte matching_letters=0;
  byte counter;

  best_match=-1;
  item_number=0;
  
  for (current_item=list->First; 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
      if (current_item->Unicode_full_name != NULL)
      {
        for (counter = 0; fname[counter] != 0; counter++)
        {
          if (towlower(current_item->Unicode_full_name[counter]) != towlower(fname[counter]))
            break;
        }
      }
      else
      {
        for (counter=0; fname[counter] != 0; counter++)
        {
          if ((wint_t)tolower(current_item->Full_name[counter]) != towlower(fname[counter]))
            break;
        }
      }
      if (counter>matching_letters)
      {
        matching_letters=counter;
        best_match=item_number;
      }
    }
    item_number++;
  }

  return best_match;
}

// Quicksearch system
#define MAX_QUICKSEARCH_LEN 50

/// Current quicksearch string
static word quicksearch_filename[MAX_QUICKSEARCH_LEN+1] = { 0 };

/// Reset the current quicksearch string
void Reset_quicksearch(void)
{
  quicksearch_filename[0] = 0;
}

/// Select the item based on what the user type
static short Quicksearch(const T_Fileselector *selector)
{
  word key;
  size_t len;
  short most_matching_item;
  
  // Go to the filename which matches what is typed
  len = Unicode_strlen(quicksearch_filename);
  key = Key_UNICODE;
  if (key == 0)
    key = Key_ANSI;

  if (key >= ' ' && len < MAX_QUICKSEARCH_LEN)
  {
    quicksearch_filename[len] = key;
    quicksearch_filename[len+1] = 0;
    most_matching_item = Find_filename_match(selector, quicksearch_filename);
    if ( most_matching_item >= 0 )
      return most_matching_item;
    else
      Reset_quicksearch();
  }
  return -1;
}

// Translated from Highlight_file
void Locate_list_item(T_List_button * list, short selected_item)
{

  // Safety bounds
  if (selected_item<0)
    selected_item=0;
  else if (selected_item>=list->Scroller->Nb_elements)
    selected_item=list->Scroller->Nb_elements-1;
    
    
  if ((list->Scroller->Nb_elements<=list->Scroller->Nb_visibles) || (selected_item<(list->Scroller->Nb_visibles/2)))
  {
    list->List_start=0;
    list->Cursor_position=selected_item;
  }
  else
  {
    if (selected_item>=list->Scroller->Nb_elements-(list->Scroller->Nb_visibles/2))
    {
      list->List_start=list->Scroller->Nb_elements-list->Scroller->Nb_visibles;
      list->Cursor_position=selected_item-list->List_start;
    }
    else
    {
      list->List_start=selected_item-(list->Scroller->Nb_visibles/2-1);
      list->Cursor_position=(list->Scroller->Nb_visibles/2-1);
    }
  }
}

int Quicksearch_list(T_List_button * list, T_Fileselector * selector)
{
  // Try Quicksearch
  short selected_item=Quicksearch(selector);
  if (selected_item>=0 && selected_item!=list->Cursor_position+list->List_start)
  {
    Locate_list_item(list, selected_item);
    
    Hide_cursor();
    // Mise à jour du scroller
    list->Scroller->Position=list->List_start;
    Window_draw_slider(list->Scroller);
    
    Window_redraw_list(list);
    Display_cursor();
    // Store the selected value as attribute2
    Window_attribute2=list->List_start + list->Cursor_position;
    // Return the control ID of the list.
    return list->Number;
  }
  return 0;
}

byte Button_Load_or_Save(T_Selector_settings *settings, byte load, T_IO_Context *context)
  // 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;
  unsigned int format;
  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.
  byte  initial_back_color; // preview destroys it (how nice)
  char *save_filename = NULL;
  char  initial_comment[COMMENT_SIZE+1];
  short window_shortcut;
  const char * directory_to_change_to = NULL;
  int   load_from_clipboard = 0;
  size_t filename_length = 0;

  Selector=settings;

  Reset_quicksearch();
  
  //if (Native_filesel(load) != 0); // TODO : handle this
  
  if (context->Type == CONTEXT_MAIN_IMAGE)
    window_shortcut = load?(0x100+BUTTON_LOAD):(0x100+BUTTON_SAVE);
  else
    window_shortcut = load?SPECIAL_LOAD_BRUSH:SPECIAL_SAVE_BRUSH;

  // Backup data that needs be restored on "cancel"  
  initial_back_color=Back_color;
  strcpy(initial_comment,context->Comment);
  
  if (load)
  {
    if (context->Type == CONTEXT_MAIN_IMAGE)
      Open_window(310,200,"Load picture");
    else if (context->Type == CONTEXT_PALETTE)
      Open_window(310,200,"Load palette");
    else
      Open_window(310,200,"Load brush");
    Window_set_normal_button(198,180,51,14,"Load",0,1,KEY_RETURN); // 1
  }
  else
  {
    if (context->Type == CONTEXT_MAIN_IMAGE)
      Open_window(310,200,"Save picture");
    else if (context->Type == CONTEXT_BRUSH)
      Open_window(310,200,"Save brush");
    else if (context->Type == CONTEXT_PALETTE)
      Open_window(310,200,"Save palette");
    else
      assert(0);
    Window_set_normal_button(198,180,51,14,"Save",0,1,KEY_RETURN); // 1
    if (Selector->Format_filter<=FORMAT_ALL_FILES) // Correction du *.*
    {
      Selector->Format_filter=context->Format;
      Selector->Position=0;
      Selector->Offset=0;
    }

    if (Get_fileformat(Selector->Format_filter)->Save == NULL) // Correction d'un format insauvable
    {
      Selector->Format_filter=DEFAULT_FILEFORMAT;
      Selector->Position=0;
      Selector->Offset=0;
    }
    // Affichage du commentaire
    if (Get_fileformat(Selector->Format_filter)->Comment)
      Print_in_window(45,70,context->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,KEY_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,0); // 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(68,28,52,11,0,
      Get_fileformat(Selector->Format_filter)->Label,
      1,0,1,RIGHT_SIDE|LEFT_SIDE,0); // 6

  for (format=0; format < Nb_known_formats(); format++)
  {
    if (File_formats[format].Identifier > FORMAT_ALL_FILES)
    {
      if (load && !File_formats[format].Load) //filter out formats without Load function
        continue;
      if (!load && !File_formats[format].Save) // filter out formats without Save function
        continue;
    }
    if (!load && ((context->Type == CONTEXT_PALETTE) != File_formats[format].Palette_only))
      continue;   // Only Palette only format when SAVING palette and not Palette only when saving image
    Window_dropdown_add_item(formats_dropdown,File_formats[format].Identifier,File_formats[format].Label);
  }
  Print_in_window(70,18,"Format",MC_Dark,MC_Light);
  
  // Texte de commentaire des dessins
  Print_in_window(9,70,"Txt:",MC_Dark,MC_Light);
  Window_set_input_button(43,68,COMMENT_SIZE); // 7

  // Saisie du nom de fichier
  Window_set_input_button(80,46,27); // 8

  Print_in_window(9,47,"Filename",MC_Dark,MC_Light);
  Print_in_window(9,59,"Image:",MC_Dark,MC_Light);
  Print_in_window(228,59,"(",MC_Dark,MC_Light);
  Print_in_window(292,59,")",MC_Dark,MC_Light);

  // Selecteur de Lecteur / Volume
  Window_set_normal_button(7,18,53,23,"",0,1,KEY_NONE); // 9
  Print_in_window(10,22,"Select",MC_Black,MC_Light);
  Print_in_window(14,30,"drive",MC_Black,MC_Light);
 
  // Bookmarks
  for (temp=0;temp<NB_BOOKMARKS;temp++)
  {
    bookmark_dropdown[temp]=
      Window_set_dropdown_button(127+(88+1)*(temp%2),18+(temp/2)*12,88,11,64,"",0,0,1,RIGHT_SIDE,0); // 10-13
    Window_display_icon_sprite(bookmark_dropdown[temp]->Pos_X+3,bookmark_dropdown[temp]->Pos_Y+2,ICON_STAR);
    Display_bookmark(bookmark_dropdown[temp],temp);
  }

#if defined(WIN32) || defined(__macosx__) || defined(USE_X11) || (defined(SDL_VIDEO_DRIVER_X11) && !defined(NO_X11))
  if (load)
    Window_set_normal_button(62,180,115,14,"From Clipboard",0,1,SHORTCUT_PASTE); // 14
  else
    Window_set_normal_button(62,180,115,14,"To Clipboard",0,1,SHORTCUT_COPY); // 14
#endif

  Change_directory(context->File_directory);
  free(Selector->Directory);
  free(Selector->Directory_unicode);
  Selector->Directory = Get_current_directory(NULL, &Selector->Directory_unicode, 0);
  
  // Affichage des premiers fichiers visibles:
  Reload_list_of_files(Selector->Format_filter,file_scroller);

  if (!load)
  {
    short pos = Find_file_in_fileselector(&Filelist, context->File_name);
    Highlight_file((pos >= 0) ? pos : 0);
    Selected_type = (pos >= 0) ? FSOBJECT_FILE : FSOBJECT_DIR;
    Prepare_and_display_filelist(Selector->Position,Selector->Offset,file_scroller,0);

    // On initialise le nom de fichier à celui en cours et non pas celui sous
    // la barre de sélection
    free(Selector->filename);
    Selector->filename = strdup(context->File_name);
    free(Selector->filename_unicode);
    Selector->filename_unicode = Unicode_strdup(context->File_name_unicode);
    // On affiche le nouveau nom de fichier
    Print_filename_in_fileselector();
  }

  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_from_clipboard)
          Selected_type = FSOBJECT_FILE;
        else if(load) // Determine the type
          Selected_type = (File_exists(Selector->filename) && !Directory_exists(Selector->filename)) ? FSOBJECT_FILE : FSOBJECT_DIR;
        else
          Selected_type = Directory_exists(Selector->filename) ? FSOBJECT_DIR : FSOBJECT_FILE;
        has_clicked_ok=1;
        break;

      case  2 : // Cancel
        break;

      case  3 : // Delete
        if (Filelist.Nb_elements && (Selector->filename[0] != '.') && Selected_type != FSOBJECT_DRIVE)
        {
          char * message;
          Hide_cursor();
          // On affiche une demande de confirmation
          if (Selector->Position+Selector->Offset>=Filelist.Nb_directories)
          {
            message="Delete file ?";
          }
          else
          {
            message="Remove directory ?";
          }
          if (Confirmation_box(message))
          {
            // Si c'est un fichier
            if (Selector->Position+Selector->Offset>=Filelist.Nb_directories)
              // On efface le fichier (si on peut)
              temp=(!Remove_path(Selector->filename));
            else // Si c'est un repertoire
              // On efface le repertoire (si on peut)
              temp=(!Remove_directory(Selector->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 (Selector->Position+Selector->Offset==Filelist.Nb_elements-1)
              {
                if (Selector->Position)
                  Selector->Position--;
                else
                  if (Selector->Offset)
                    Selector->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 ( (Selector->Position)
                  && (Selector->Position+10==Filelist.Nb_elements) )
                  {
                    Selector->Position--;
                    Selector->Offset++;
                  }
              }
              // On relit les informations
              Reload_list_of_files(Selector->Format_filter,file_scroller);
              // On demande la preview du nouveau fichier sur lequel on se trouve
              New_preview_is_needed=1;
            }
            else
              Error(0);
          }
        }
        break;

      case  4 : // Zone d'affichage de la liste de fichiers
        Hide_cursor();

        temp=Compute_click_offset_in_fileselector();
        if (temp>=0)
        {
          load_from_clipboard = 0;
          if (temp!=Selector->Offset)
          {
            // first click on an item.
            // update offset
            Selector->Offset=temp;

            // get item name and details
            Get_selected_item(&Filelist, Selector, &Selected_type);
            // display the new filename
            Print_filename_in_fileselector();
            // and update list
            Display_file_list(&Filelist, Selector->Position,Selector->Offset);

            // A new file is selected so a new preview is needed
            New_preview_is_needed=1;
            Reset_quicksearch();
          }
          else
          {
            // This is the second click on the same item => Double click

            //   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 (Selector->Position+Selector->Offset < Filelist.Nb_directories)
            { // clicked on a directory
              // retrieve again the item name
              Get_selected_item(&Filelist, Selector, &Selected_type);
            }

            has_clicked_ok=1;
            New_preview_is_needed=1;
            Reset_quicksearch();
          }
        }
        Display_cursor();
        Wait_end_of_click();
        break;

      case  5 : // Scroller de fichiers
        Hide_cursor();
        Selector->Position=Window_attribute2;
        // On récupére le nom du schmilblick à "accéder"
        Get_selected_item(&Filelist, Selector, &Selected_type);
        load_from_clipboard = 0;
        // On affiche le nouveau nom de fichier
        Print_filename_in_fileselector();
        // On affiche à nouveau la liste
        Display_file_list(&Filelist, Selector->Position,Selector->Offset);
        Display_cursor();
        New_preview_is_needed=1;
        Reset_quicksearch();
        break;

    case  6 : // File Format dropdown
        // Refresh fileselector according to new filter
        if (Selector->Format_filter != Window_attribute2)
        {
          int pos_last_dot;
          char* savename = NULL;
          word* Selector_filename_unicode_save = NULL;

          GFX2_Log(GFX2_DEBUG, "fileselector format changed from %d to %d\n", (int)Selector->Format_filter, (int)Window_attribute2);
          Selector->Format_filter = Window_attribute2;
          if (load_from_clipboard)
            break;
          if (!load)
          {
            // In "save" box, if current name is a (possible) filename
            // with extension, set it to new format's extension
            pos_last_dot = Position_last_dot(Selector->filename);
            if (Get_fileformat(Selector->Format_filter)->Default_extension[0] != '\0' &&
              pos_last_dot!=-1 &&
              Selector->filename[pos_last_dot+1]!='\0')
            {
              GFX2_Log(GFX2_DEBUG, "extension %s => %s\n", Selector->filename + pos_last_dot + 1, Get_fileformat(Selector->Format_filter)->Default_extension);
              strcpy(Selector->filename + pos_last_dot + 1,
                Get_fileformat(Selector->Format_filter)->Default_extension);
              if (Selector->filename_unicode != NULL)
              {
                pos_last_dot = Position_last_dot_unicode(Selector->filename_unicode);
                if (pos_last_dot != -1)
                  Unicode_char_strlcpy(Selector->filename_unicode + pos_last_dot + 1,
                    Get_fileformat(Selector->Format_filter)->Default_extension, 256 - pos_last_dot - 1);
              }
            }
          }
          savename = strdup(Selector->filename);
          Selector_filename_unicode_save = Unicode_strdup(Selector->filename_unicode);
          // By default, position list at the beginning
          Selector->Position = 0;
          Selector->Offset = 0;
          // Print the first visible files
          Hide_cursor();
          Reload_list_of_files(Selector->Format_filter, file_scroller);
          New_preview_is_needed = 1;
          Reset_quicksearch();
          if (savename != NULL && savename[0] != '\0')
          {
            // attempt to find the file name in new list
            short pos=Find_file_in_fileselector(&Filelist, savename);
            if (pos >= 0)
            {
              Highlight_file(pos);
              Prepare_and_display_filelist(Selector->Position,Selector->Offset,file_scroller,load);
            }
            // If the file is (still present) or it's a name with new
            // extension, set it as the proposed file name.
            if (pos >= 0 || !load)
            {
              free(Selector->filename);
              Selector->filename = strdup(savename);
              savename = NULL;
              free(Selector->filename_unicode);
              Selector->filename_unicode = Unicode_strdup(Selector_filename_unicode_save);
              Selector_filename_unicode_save = NULL;
            }
          }
          free(savename);
          free(Selector_filename_unicode_save);
          Print_filename_in_fileselector();
          Display_cursor();
        }
        break;
      case  7 : // Saisie d'un commentaire pour la sauvegarde
        if ( (!load) && (Get_fileformat(Selector->Format_filter)->Comment) )
        {
          Readline(45, 70, context->Comment, 32, INPUT_TYPE_STRING);
          Display_cursor();
        }
        break;
      case  8 : // Saisie du nom de fichier
      {
        char filename_ansi[256];
        word filename_unicode[256];
        word * save_filename_unicode = Unicode_strdup(Selector->filename_unicode);

        free(save_filename);
        save_filename = strdup(Selector->filename);
        // Check if the selected entry is a drive/directory :
        // in, this case, clear the filename
        if (Filelist.Nb_elements>0)
        {
          T_Fileselector_item * current_item;
          current_item = Get_item_by_index(&Filelist, Selector->Position + Selector->Offset);
          if (current_item->Type != FSOBJECT_FILE && !FILENAME_COMPARE(current_item->Full_name, Selector->filename))
          {
            // current name is a highlighted directory
            free(Selector->filename);
            Selector->filename = NULL;
            free(Selector->filename_unicode);
            Selector->filename_unicode = NULL;
          }
        }
        if (Selector->filename != NULL)
          strncpy(filename_ansi, Selector->filename, sizeof(filename_ansi));
        else
          filename_ansi[0] = '\0';
        filename_unicode[0] = '\0';
        if (Selector->filename_unicode != NULL && Selector->filename_unicode[0] != 0)
          Unicode_strlcpy(filename_unicode, Selector->filename_unicode, sizeof(filename_unicode)/sizeof(word));
        else if (Selector->filename != NULL && strlen(Selector->filename) > 0)
          Unicode_char_strlcpy(filename_unicode, Selector->filename, sizeof(filename_unicode)/sizeof(word));
#ifdef ENABLE_FILENAMES_ICONV
        // convert from UTF8 to ANSI
        if (Selector->filename != NULL) {
          filename_length = strlen(Selector->filename);
          if (filename_length > 0) {
            char * input = (char *)Selector->filename;
            size_t inbytesleft = filename_length;
            char * output = filename_ansi;
            size_t outbytesleft = sizeof(filename_ansi)-1;
            if(cd != (iconv_t)-1 && (ssize_t)iconv(cd, &input, &inbytesleft, &output, &outbytesleft) >= 0)
              *output = '\0';
          }
        }
#endif /* ENABLE_FILENAMES_ICONV */
#if defined(WIN32) || defined(ENABLE_FILENAMES_ICONV)
        if (Readline_ex_unicode(82,48,filename_ansi,filename_unicode,27,sizeof(filename_ansi)-1,INPUT_TYPE_FILENAME,0))
#else
        if (Readline_ex_unicode(82,48,filename_ansi,NULL,27,sizeof(filename_ansi)-1,INPUT_TYPE_FILENAME,0))
#endif
        {
#if defined(WIN32)
          GFX2_GetShortPathName(filename_ansi, sizeof(filename_ansi), filename_unicode);
#elif defined(ENABLE_FILENAMES_ICONV)
          /* convert back from UTF16 to UTF8 */
          char * input = (char *)filename_unicode;
          size_t inbytesleft = 2 * Unicode_strlen(filename_unicode);
          char * output = filename_ansi;
          size_t outbytesleft = sizeof(filename_ansi)-1;
          if(cd_utf16_inv != (iconv_t)-1 && (ssize_t)iconv(cd_utf16_inv, &input, &inbytesleft, &output, &outbytesleft) >= 0)
            *output = '\0';
#endif

          //   On regarde s'il faut rajouter une extension. C'est-à-dire s'il
          // n'y a pas de '.' dans le nom du fichier.
          for(temp=0, dummy=0; ((filename_ansi[temp]) && (!dummy)); temp++)
            if (filename_ansi[temp]=='.')
              dummy=1;
          if (!dummy)
          {
            if(!Directory_exists(filename_ansi))
            {
              const char * ext = Get_fileformat(Selector->Format_filter)->Default_extension;
              // put default extension
              // (but maybe we should browse through all available ones until we find
              //  something suitable ?)
              if (ext == NULL)
                ext = "pkm";
              strcat(filename_ansi, ".");
              strcat(filename_ansi, ext);
              Unicode_char_strlcat(filename_unicode, ".", sizeof(filename_unicode)/sizeof(word));
              Unicode_char_strlcat(filename_unicode, ext, sizeof(filename_unicode)/sizeof(word));
            }
          }

          free(Selector->filename);
          Selector->filename = strdup(filename_ansi);
          free(Selector->filename_unicode);
#if defined(WIN32) || defined(ENABLE_FILENAMES_ICONV)
          Selector->filename_unicode = Unicode_strdup(filename_unicode);
#else
          Selector->filename_unicode = NULL;
#endif
          if(load) // Determine the type
            Selected_type = (File_exists(Selector->filename) && !Directory_exists(Selector->filename)) ? FSOBJECT_FILE : FSOBJECT_DIR;
          else
            Selected_type = Directory_exists(Selector->filename) ? FSOBJECT_DIR : FSOBJECT_FILE;

          // Now load immediately, but only if the user exited readline by pressing ENTER
          if (Mouse_K == 0) has_clicked_ok = 1;
        }
        else
        {
          // Restore the old filename
          free(Selector->filename);
          Selector->filename = save_filename; // steal buffer
          save_filename = NULL;
          free(Selector->filename_unicode);
          Selector->filename_unicode = save_filename_unicode; // steal buffer
          save_filename_unicode = NULL;
        }
        free(save_filename_unicode);
        save_filename_unicode = NULL;
        Print_filename_in_fileselector();
        Display_cursor();
        break;
      }
      case  9 : // Volume Select
          Hide_cursor();
          //   Comme on tombe sur un disque qu'on connait pas, on se place en
          // début de liste:
          load_from_clipboard = 0;
          Selector->Position=0;
          Selector->Offset=0;
          // Affichage des premiers fichiers visibles:
          Read_list_of_drives(&Filelist,19);
          Sort_list_of_files(&Filelist);
          Prepare_and_display_filelist(Selector->Position,Selector->Offset,file_scroller,0);
          Display_cursor();
          New_preview_is_needed=1;
          Reset_quicksearch();
          break;
      case 14:  // From/to clipboard
          if (load)
          {
            // paste from clipboard
            load_from_clipboard = 1;
            New_preview_is_needed = 1;
            free(Selector->filename);
            Selector->filename = strdup("* CLIPBOARD *");
            free(Selector->filename_unicode);
            Selector->filename_unicode = NULL;
            Hide_cursor();
            Print_filename_in_fileselector();
            Display_cursor();
            Reset_quicksearch();
          }
          else
          {
            // copy to clipboard
            T_IO_Context clipboard_context;
            memcpy(&clipboard_context, context, sizeof(T_IO_Context));
            clipboard_context.Format = FORMAT_CLIPBOARD;
            Save_image(&clipboard_context);
            clicked_button = 2; // simulate cancel
          }
          break;
      default:
          if (clicked_button>=10 && clicked_button<10+NB_BOOKMARKS)
          {
            // Bookmark
            char * directory_name;
            char * rel_path;
            
            load_from_clipboard = 0;
            switch(Window_attribute2)
            {
              case -1: // bouton lui-même: aller au répertoire mémorisé
                if (Config.Bookmark_directory[clicked_button-10])
                {
                  GFX2_Log(GFX2_DEBUG,"Go to bookmark %s\n", Config.Bookmark_directory[clicked_button-10]);
                  // backup the currently selected filename
                  free(save_filename);
                  save_filename = strdup(Selector->filename);
                  // simulate a click on the bookmarked directory
                  directory_to_change_to = Config.Bookmark_directory[clicked_button-10];
                  Reset_quicksearch();
                }
                break;
                
              case 0: // Set
                free(Config.Bookmark_directory[clicked_button-10]);
                Config.Bookmark_directory[clicked_button-10] = NULL;
                Config.Bookmark_label[clicked_button-10][0] = '\0';
                Config.Bookmark_directory[clicked_button-10] = strdup(Selector->Directory);
                
                directory_name = Find_last_separator(Selector->Directory);
                if (directory_name && directory_name[1] != '\0')
                  directory_name++;
                else
                  directory_name = Selector->Directory;
                strncpy(Config.Bookmark_label[clicked_button-10], directory_name, sizeof(Config.Bookmark_label[0]) - 1);
                Config.Bookmark_label[clicked_button-10][sizeof(Config.Bookmark_label[0]) - 1]='\0';
                Display_bookmark(bookmark_dropdown[clicked_button-10],clicked_button-10);
                break;
                
              case 1: // Rename
                if (Config.Bookmark_directory[clicked_button-10])
                {
                  char bookmark_label[24];
                  /// @todo convert label to unicode before editing
                  strcpy(bookmark_label, Config.Bookmark_label[clicked_button-10]);
                  if (Readline_ex(bookmark_dropdown[clicked_button-10]->Pos_X+3+10,
                                  bookmark_dropdown[clicked_button-10]->Pos_Y+2,
                                  bookmark_label, 8, sizeof(bookmark_label) - 1, INPUT_TYPE_STRING, 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;
              case 3: // Set Rel
                rel_path = Calculate_relative_path(Data_directory, Selector->Directory);
                if (rel_path != NULL)
                {
                  // Erase old bookmark
                  free(Config.Bookmark_directory[clicked_button-10]);
                  Config.Bookmark_directory[clicked_button-10] = rel_path;
                  directory_name = Find_last_separator(Selector->Directory);
                  if (directory_name && directory_name[1]!='\0')
                    directory_name++;
                  else
                    directory_name=Selector->Directory;
                  strncpy(Config.Bookmark_label[clicked_button-10], directory_name, sizeof(Config.Bookmark_label[0]) - 1);
                  Config.Bookmark_label[clicked_button-10][sizeof(Config.Bookmark_label[0]) - 1] = '\0';
                  Display_bookmark(bookmark_dropdown[clicked_button-10],clicked_button-10);
                }
                else
                {
                  GFX2_Log(GFX2_INFO, "Failed to compute relative path from '%s' to '%s'\n",
                           Data_directory, Selector->Directory);
                  Error(0); // red flash
                }
                break;
            }
          }
          break;
    }

    switch (Key)
    {
      case KEY_UNKNOWN : break;
      case KEY_DOWN : // Bas
        load_from_clipboard = 0;
        Reset_quicksearch();
        Hide_cursor();
        Selector_scroll_down(&Selector->Position,&Selector->Offset);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case KEY_UP : // Haut
        load_from_clipboard = 0;
        Reset_quicksearch();
        Hide_cursor();
        Selector_scroll_up(&Selector->Position,&Selector->Offset);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case KEY_PAGEDOWN : // PageDown
        load_from_clipboard = 0;
        Reset_quicksearch();
        Hide_cursor();
        Selector_page_down(&Selector->Position,&Selector->Offset,9);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case KEY_PAGEUP : // PageUp
        load_from_clipboard = 0;
        Reset_quicksearch();
        Hide_cursor();
        Selector_page_up(&Selector->Position,&Selector->Offset,9);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case KEY_END : // End
        load_from_clipboard = 0;
        Reset_quicksearch();
        Hide_cursor();
        Selector_end(&Selector->Position,&Selector->Offset);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case KEY_HOME : // Home
        load_from_clipboard = 0;
        Reset_quicksearch();
        Hide_cursor();
        Selector_home(&Selector->Position,&Selector->Offset);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case KEY_MOUSEWHEELDOWN :
        load_from_clipboard = 0;
        Reset_quicksearch();
        Hide_cursor();
        Selector_page_down(&Selector->Position,&Selector->Offset,3);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case KEY_MOUSEWHEELUP :
        load_from_clipboard = 0;
        Reset_quicksearch();
        Hide_cursor();
        Selector_page_up(&Selector->Position,&Selector->Offset,3);
        Scroll_fileselector(file_scroller);
        Key=0;
        break;
      case KEY_BACKSPACE : // Backspace
        load_from_clipboard = 0;
        Reset_quicksearch();
        // Si le choix ".." est bien en tête des propositions...
        if (Filelist.Nb_elements && !strcmp(Filelist.First->Full_name,PARENT_DIR))
        {                              
          // On va dans le répertoire parent.
          free(Selector->filename);
          Selector->filename = strdup(PARENT_DIR);
          free(Selector->filename_unicode);
          Selector->filename_unicode = NULL;
          Selected_type = FSOBJECT_DIR;
          has_clicked_ok = 1;
        }
        Key=0;
        break;
      default:
        if (clicked_button<=0)
        {
          short selected_item;
          
          if (Is_shortcut(Key,0x100+BUTTON_HELP))
          {
            Window_help(load?BUTTON_LOAD:BUTTON_SAVE, NULL);
            break;
          }
          if (Is_shortcut(Key,window_shortcut))
          {
            clicked_button=2;
            break;
          }
          
          selected_item=Quicksearch(&Filelist);
          if (selected_item>=0)
          {
              temp=Selector->Position+Selector->Offset;
              Hide_cursor();
              load_from_clipboard = 0;
              Highlight_file(selected_item);
              Prepare_and_display_filelist(Selector->Position,Selector->Offset,file_scroller,1);
              Display_cursor();
              if (temp!=Selector->Position+Selector->Offset)
                New_preview_is_needed=1;
          }
          // Key=0; ?
        }
        else
          Reset_quicksearch();
    }

    if (has_clicked_ok || (directory_to_change_to != NULL))
    {
      if (load_from_clipboard)
        save_or_load_image=1;
      else if (Selected_type!=FSOBJECT_FILE || (directory_to_change_to != NULL))
      {
        // it is a directory, change to it
        Hide_cursor();
        has_clicked_ok=0;

        if (directory_to_change_to == NULL)
          directory_to_change_to = Selector->filename;

        // We must enter the directory
      
        if (directory_to_change_to[0] == '.' &&
            (directory_to_change_to[1] == '\0' || directory_to_change_to[1] == '/' || directory_to_change_to[1] == '\\' ||
            (directory_to_change_to[1] == '.' &&
             (directory_to_change_to[2] == '/' || directory_to_change_to[2] == '\\')))) // Relative path
        {
          GFX2_Log(GFX2_DEBUG, "Relative bookmark \"%s\", Change to \"%s\" first.\n",
                   directory_to_change_to, Data_directory);
          Change_directory(Data_directory);
        }

        if (Change_directory(directory_to_change_to) == 0)
        {
          short pos;
          char * previous_directory; // Directory when we are coming from
        #if defined (__MINT__)
          static char path[1024]={0};
          char currentDrive='A';
        #endif

          // save the previous current directory
          if (strcmp(directory_to_change_to,PARENT_DIR) != 0)
          {
            previous_directory = strdup(PARENT_DIR);
          }
          else
          {
            previous_directory = Extract_filename(Selector->Directory);
          }

          free(Selector->Directory);
          free(Selector->Directory_unicode);
          Selector->Directory = Get_current_directory(NULL, &Selector->Directory_unicode, 0);
          // read the new directory
          Read_list_of_files(&Filelist, Selector->Format_filter);
          Sort_list_of_files(&Filelist);
          // Set the fileselector bar on the directory we're coming from
          pos = Find_file_in_fileselector(&Filelist, previous_directory);
          free(Selector->filename);
          Selector->filename = previous_directory;  // Steal heap buffer
          previous_directory = NULL;
          free(Selector->filename_unicode);
          Selector->filename_unicode = Get_Unicode_Filename(NULL, Selector->filename, ".");
          Highlight_file((pos >= 0) ? pos : 0);
          // display the 1st visible files
          Prepare_and_display_filelist(Selector->Position,Selector->Offset,file_scroller,0);
          Display_cursor();
          New_preview_is_needed=1;

          // New directory, so we need to reset the quicksearch
          Reset_quicksearch();
        }
        else
        {
          char * current_dir;
          Display_cursor();
          GFX2_Log(GFX2_WARNING, "cannot chdir to \"%s\" !\n", directory_to_change_to);
          current_dir = Get_current_directory(NULL, NULL, 0);
          GFX2_Log(GFX2_WARNING, "Current directory is \"%s\"\n", current_dir);
          free(current_dir);
          // restore Selector_filename
          free(Selector->filename);
          Selector->filename = save_filename; // steal heap buffer
          save_filename = NULL;
          Error(0);
        }
        directory_to_change_to = NULL;
      }
      else  // Sinon on essaye de charger ou sauver le fichier
      {
        free(context->File_directory);
        context->File_directory = strdup(Selector->Directory);
        context->Format = Selector->Format_filter;
        save_or_load_image=1;
      }
    }
    free(save_filename);
    save_filename = NULL;

    // 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,PREVIEW_WIDTH,PREVIEW_HEIGHT,MC_Light);
        // On efface les dimensions de l'image
        Window_rectangle(101,59,120,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,5*8,8,MC_Light);
        // Affichage du commentaire
        if ( (!load) && (Get_fileformat(Selector->Format_filter)->Comment) )
        {
          Print_in_window(45,70,context->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,PREVIEW_WIDTH,PREVIEW_HEIGHT);
      }

      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 ( load_from_clipboard || ((Selector->Position+Selector->Offset>=Filelist.Nb_directories) && (Filelist.Nb_elements)) )
      {
        T_IO_Context preview_context;

        if (load_from_clipboard)
        {
          Init_context_preview(&preview_context, NULL, NULL);
          preview_context.Format = FORMAT_CLIPBOARD;
        }
        else
        {
          Init_context_preview(&preview_context, Selector->filename, Selector->Directory);
          preview_context.Format = Selector->Format_filter;
          preview_context.File_name_unicode = Unicode_strdup(Selector->filename_unicode);
        }
        Hide_cursor();
        if (context->Type == CONTEXT_PALETTE)
          preview_context.Type = CONTEXT_PREVIEW_PALETTE;

        Load_image(&preview_context);
        if (load_from_clipboard && (preview_context.File_directory != NULL))
        {
          short pos;
          Change_directory(preview_context.File_directory);
          free(Selector->Directory);
          free(Selector->Directory_unicode);
          Selector->Directory = Get_current_directory(NULL, &Selector->Directory_unicode, 0);
          if ((preview_context.Format != FORMAT_CLIPBOARD) &&
              ((int)Selector->Format_filter > (int)FORMAT_ALL_FILES))
          {
            Selector->Format_filter = preview_context.Format;
            // update dropdown button
            Print_in_window(68+2, 28+(11-7)/2,
                Get_fileformat(Selector->Format_filter)->Label,
                MC_Black,MC_Light);
          }
          // read the new directory
          Read_list_of_files(&Filelist, Selector->Format_filter);
          Sort_list_of_files(&Filelist);

          if (preview_context.File_name != NULL)
          {
            free(Selector->filename);
            Selector->filename = strdup(preview_context.File_name);
            free(Selector->filename_unicode);
            Selector->filename_unicode = Get_Unicode_Filename(NULL, Selector->filename, ".");
          }

          pos = Find_file_in_fileselector(&Filelist, Selector->filename);
          Highlight_file((pos >= 0) ? pos : 0);
          // display the 1st visible files
          Prepare_and_display_filelist(Selector->Position, Selector->Offset, file_scroller, 0);

          // New directory, so we need to reset the quicksearch
          Reset_quicksearch();
        }
        Destroy_context(&preview_context);

        Update_window_area(0,0,Window_width,Window_height);
        Display_cursor();
      }

      Timer_state=2; // On arrête le chrono
    }
  }
  while ( (!has_clicked_ok) && (clicked_button!=2) && !Quit_is_required);

  if (has_clicked_ok)
  {
    free(context->File_name);
    free(context->File_name_unicode);
    if (load_from_clipboard)
    {
      context->File_name = strdup("CLIPBOARD.GIF");
      context->File_name_unicode = NULL;
      context->Format = FORMAT_CLIPBOARD;
    }
    else
    {
      context->File_name = strdup(Selector->filename);
      context->File_name_unicode = Unicode_strdup(Selector->filename_unicode);
      free(context->File_directory);
      context->File_directory = strdup(Selector->Directory);
      if (!load)
        context->Format = Selector->Format_filter;
    }
  }
  else
  {
    // Data to restore
    strcpy(context->Comment, initial_comment);
  }
  

  //   On restaure les données de l'image qui ont certainement été modifiées
  // par la preview.
  Back_color=initial_back_color;
  if (Windows_open <= 1)
  {
    // Restore Main.palette only when we are not going back to another window
    // (we let the other window take care of its palette and remapping)
    // This test was added for Load/Save dialog called from the Palette window
    Set_palette(Main.palette);

    Compute_optimal_menu_colors(Main.palette);
  }
  temp=(Window_pos_Y+(Window_height*Menu_factor_Y)<Menu_Y_before_window);

  Close_window();

  if (temp && Windows_open < 1)
    Display_menu();

  Unselect_button((load)?BUTTON_LOAD:BUTTON_SAVE);
  Display_cursor();
  Free_fileselector_list(&Filelist);

  return save_or_load_image;
}
