/* Copyright 1998 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*---------------------------------------------------------------------------*/
/* File:    c.monitor                                                        */
/* Purpose: Code for reading MDFs.                                           */
/* Author:  Richard Leggett                                                  */
/* History: 26-Nov-97: RML: Begun, borrowing lots of code from the previous  */
/*                          version of Configure.                            */
/*          12-Feb-98: RML: Changed saving of PreDesk config.                */
/*          16-Apr-98: RML: save_monitor_choices now also changes the to the */
/*                          new setup.                                       */
/*          01-May-98: RML: Bug fix.                                         */
/*          21-Jul-98: RML: Bug fix: Monitor name could be displayed wrong   */
/*                          if two very similar filenames existed.           */
/*                                                                           */
/* Copyright � 1998 Acorn Computers Ltd., Cambridge, UK.                     */
/*---------------------------------------------------------------------------*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "swis.h"
#include "toolbox.h"
#include "gadgets.h"
#include "menu.h"
#include "common.h"
#include "defines.h"

#define Service_ModeFileChanged 0x94
#define flags_squarepixel ((unsigned)1<<31)

typedef struct monitor_ref
{
    struct monitor_ref *next;
    char               *monitor_name;
    char               *file_name;
    int                 dpms_state;
}   monitor_ref;

static monitor_ref  *monitors_list=0;
static monitor_ref **monitors_list_tail=0;

static char *current_monitor_file = NULL;
static int   my_screenmodes_instantiation;
static char *desktop_instantiation;
static int   desktop_monitortype;
static int   nummodes=0;
static char *modes_block = NULL;
static int **modes_index;
static int   res_menu_entries=1;
static int   current_x_res, current_y_res, current_flags, current_colours;   /* colours are 0-7 as per the menu */

static int  cmenutodepth[8] = {0,1,2,2,3,3,4,5};
static char cdepth[8]={0,1,2,2,3,3,4,5};        /* menu entry->colour depth */
static char cdeptht[8]={0,0,0,1,0,1,1,1};       /* menu entry->colour/black and white */
static char cdepthc[6]={0,1,3,5,6,7};           /* colour depth->colour menu entry */
static char cdepthg[6]={0,1,2,4,6,7};           /* colour depth->grey menu entry */
static char *cmenutotext[8] = {"C2","C4","G16","C16","G256","C256","C32K","C16M"};

extern int  main_window_id, dpms_window_id, res_menu_id, currently_ticked, menus_are_grey;


/*---------------------------------------------------------------------------*
 * read_current_mode_settings                                                *
 *                                                                           *
 * Read current MDF and chosen resolution from the choices file in PreDesk.  *
 *---------------------------------------------------------------------------*/

void read_current_mode_settings(void)
{
    FILE *fp;
    char *string;
    char  res0[256];
    char  res1[256];
    char  res2[256];
    char  res3[256];
    char  buffer[1024];

    if (current_monitor_file) free(current_monitor_file);
    current_monitor_file=NULL;
    current_x_res = -1;
    current_y_res = -1;
    current_colours = -1;
    current_flags = 0;

    /* Read in the PreDesk config file */
    fp=fopen(PreDeskConfigFile, "r");
    if (fp)
    {
        /* First line should be a LoadModeFile command */
        fgets(buffer, 1024, fp);
        if (strncmp(buffer, LoadModeCmd, strlen(LoadModeCmd))==0)
        {
            if (buffer[strlen(buffer)-1]==NewLine)
                buffer[strlen(buffer)-1]=0;
            /* Get current selected MDF */
            string=strchr(buffer, ' ');
            if (!string)
            {
                fclose(fp);
                return;
            }
            current_monitor_file=malloc(strlen(string));
            if (!current_monitor_file)
            {
                fclose(fp);
                return;
            }
            strcpy(current_monitor_file, string+1);

            /* Next line should be a WimpMode command */
            fgets(buffer, 1024, fp);
            sscanf(buffer, "%s %s %s %s", res0, res1, res2, res3);

            if (   (strncmp(res0, WimpModeCmd, strlen(WimpModeCmd))==0)
                && (res1[0]=='X')
                && (res2[0]=='Y')
                && (res3[0]=='C' || res3[0]=='G') )
            {
                /* Deal with resolution */
                current_x_res=atoi(res1+1);
                current_y_res=atoi(res2+1);

                /* Deal with colours */
                if (strcmp(res3, "C2")==0) current_colours=0;
                if (strcmp(res3, "C4")==0) current_colours=1;
                if (strcmp(res3, "G16")==0) current_colours=2;
                if (strcmp(res3, "C16")==0) current_colours=3;
                if (strcmp(res3, "G256")==0) current_colours=4;
                if (strcmp(res3, "C256")==0) current_colours=5;
                if (strcmp(res3, "C32K")==0) current_colours=6;
                if (strcmp(res3, "C32T")==0) current_colours=6;
                if (strcmp(res3, "C16M")==0) current_colours=7;

                /* Find out if selected mode is rectangular or square pixel */
                if ( current_x_res != -1 && current_y_res != -1 && current_colours != -1 )
                {
                    int modeblock[6];
                    int xeig, yeig;

                    modeblock[0] = 1;
                    modeblock[1] = current_x_res;
                    modeblock[2] = current_y_res;
                    modeblock[3] = cmenutodepth[current_colours];
                    modeblock[4] = -1;
                    modeblock[5] = -1;
                    error_trap(_swix(OS_ReadModeVariable, _IN(0)|_IN(1)|_OUT(2), modeblock, 4, &xeig), 0);
                    error_trap(_swix(OS_ReadModeVariable, _IN(0)|_IN(1)|_OUT(2), modeblock, 5, &yeig), 0);
                    if (xeig==yeig) current_flags |= flags_squarepixel;
                }
            }
        }
        fclose(fp);
    }
}


/*---------------------------------------------------------------------------*
 * update_monitor_displayfields                                              *
 *                                                                           *
 * Update all the monitor displayfields (monitor, resolution, colours)       *
 * according to the current settings.                                        *
 *---------------------------------------------------------------------------*/

void update_monitor_displayfields(void)
{
    monitor_ref *m;
    int          i;
    char         res_string[256];
    char         menu_string[256];

    if ((current_x_res==-1) || (current_y_res==-1))
        error_trap(displayfield_set_value(0, main_window_id, ResolutionDisplay, UnknownString), 0);

    if (current_colours<0)
        error_trap(stringset_set_selected(0, main_window_id, ColoursStringSet, UnknownString), 0);

    if (!current_monitor_file)
    {
        grey_gadget(main_window_id, ColoursStringSet);
        grey_gadget(main_window_id, ResolutionMenu);
        grey_gadget(main_window_id, ResolutionDisplay);
        menus_are_grey=1;
    }

    /* Update monitor name display */
    for (m=monitors_list; m; m =m->next)
    {
        if (strncmp(current_monitor_file, m->file_name, strlen(current_monitor_file))==0)
        {
            error_trap(stringset_set_selected(0, main_window_id, MonitorTypeStringSet, m->monitor_name), 0);
            break;
        }
    }

    /* Update resolution display */
    for ( i=0; i<nummodes; i++ )
    {
        if (modes_index[i][2]==current_x_res &&
            modes_index[i][3]==current_y_res)
        {
            if (*(char*)&modes_index[i][6]=='\0')
            {
                char text[16];
                sprintf(text, "%d x %d", current_x_res, current_y_res);
                error_trap(displayfield_set_value(0, main_window_id, ResolutionDisplay, text), 0);
            }
            else
            {
                error_trap(displayfield_set_value(0, main_window_id, ResolutionDisplay, (char*)&modes_index[i][6]), 0);
            }
            break;
        }
    }

    /* Work out which entry in the resolution menu to tick */
    error_trap(displayfield_get_value(0, main_window_id, ResolutionDisplay, res_string, 256, 0), 0);
    for (i=0; i<res_menu_entries; i++)
    {
        error_trap(menu_get_entry_text(0, res_menu_id, i, menu_string, 256, 0), 0);
        if (strcmp(res_string, menu_string)==0)
        {
            error_trap(menu_set_tick(0, res_menu_id, currently_ticked, 0), 0);
            error_trap(menu_set_tick(0, res_menu_id, i, 1), 0);
            currently_ticked=i;
            break;
        }
    }

    /* Update colours display */
    error_trap(stringset_set_selected(1, main_window_id, ColoursStringSet, (char*)current_colours), 0);
}


/*---------------------------------------------------------------------------*
 * update_dpms_status                                                        *
 *                                                                           *
 * Update the DPMS state icon for the current monitor file.                  *
 *---------------------------------------------------------------------------*/

void update_dpms_status(void)
{
    monitor_ref *m;
    char         message_tag[16];

    /* Find current monitor in our list */
    for (m=monitors_list; m; m =m->next)
    {
        if (strncmp(current_monitor_file, m->file_name, strlen(m->file_name))==0)
        {
            sprintf(message_tag, "DPMS%d", m->dpms_state);
            error_trap(displayfield_set_value(0, dpms_window_id, DPMSStateDisplay, messages_lookup(message_tag)), 0);
            break;
        }
    }
}


/*---------------------------------------------------------------------------*
 * create_screenmodes_instantiation                                          *
 *                                                                           *
 * Create a new instantiation of the ScreenModes module so that we can load  *
 * any newly selected MDF into it.                                           *
 *---------------------------------------------------------------------------*/

void create_screenmodes_instantiation(void)
{
    char tbuf[256];
    int i;

    /* Find highest numeric instantiation currently present */
    for ( i=0; i<1000; i++ )
    {
        sprintf(tbuf, "ScreenModes%%%d", i);
        if (_swix(OS_Module, _IN(0)|_IN(1), 18, tbuf)) break;
    }
    my_screenmodes_instantiation = i;

    /* create new instantiation */
    error_trap(_swix(OS_Module, _IN(0)|_IN(1)|_OUT(5), 18, "ScreenModes", &desktop_instantiation), 0);
    sprintf(tbuf, "ScreenModes%%%d", my_screenmodes_instantiation);
    error_trap(_swix(OS_Module, _IN(0)|_IN(1), 14, tbuf), 0);

#if UseVIDCBandLimitAlias
        /* issue VIDCBandLimit command, if Alias$VIDCBandLimit has been set (should be set by BandLimit app in
           PreDesk.Configure, with VIDCBandwidthLimit parameters as appropriate, eg. for 7500FE with EDO) */
        if (getenv("Alias$VIDCBandLimit")) (void)_swix( OS_CLI, _IN(0), "VIDCBandLimit");
#else
        /* Activate any VIDCBandwidthLimit command if its there */
        (void)_swix( OS_CLI, _IN(0), "IfThere Choices:Boot.PreDesk.BandLimit then /Choices:Boot.PreDesk.BandLimit "
                "else IfThere Boot:Utils.BandLimit then /Boot:Utils.BandLimit" );
            /*Fix bug: JRC 26th Jan 1995*/
#endif

    /* Now load our mode file */
    if (current_monitor_file)
    {
        error_trap(_swix(OS_ReadSysInfo, _IN(0)|_OUT(1), 1, &desktop_monitortype), 0);
        sprintf(tbuf, LoadModeCmd "%s", current_monitor_file);
        /* Only complain if the LoadModeFile doesn't work */
        error_trap(_swix( OS_CLI, _IN(0), tbuf ), 0);
    }

    /* Choose our preferred instantiation */
    sprintf(tbuf, "ScreenModes%%%s", desktop_instantiation);
    error_trap(_swix(OS_Module, _IN(0)|_IN(1), 16, tbuf ), 0);
}


/*---------------------------------------------------------------------------*
 * choose_my_screenmodes_instantiation                                       *
 *                                                                           *
 * Choose the newly created screenmodes instantiation as the current one.    *
 *---------------------------------------------------------------------------*/

static void choose_my_screenmodes_instantiation( void )
{
    char tbuf[256];

    /* read current preferred */
    error_trap(_swix(OS_Module, _IN(0)|_IN(1)|_OUT(5), 18, "ScreenModes", &desktop_instantiation), 0);
    /* read current monitortype */
    error_trap(_swix(OS_ReadSysInfo, _IN(0)|_OUT(1), 1, &desktop_monitortype), 0);
    /* switch to mine */
    sprintf(tbuf, "ScreenModes%%%d", my_screenmodes_instantiation);
    error_trap(_swix( OS_Module, _IN(0)|_IN(1), 16, tbuf), 0);
}


/*---------------------------------------------------------------------------*
 * return_to_base_screenmodes_instantiation                                  *
 *                                                                           *
 * Choose the original screenmodes instantiation as the current one.         *
 *---------------------------------------------------------------------------*/

static void return_to_base_screenmodes_instantiation( void )
{
    char tbuf[256];

    if ( !desktop_instantiation ) return;

    error_trap(_swix(OS_ScreenMode, _IN(0)|_IN(1), 3, desktop_monitortype), 0);

    /* switch instantiation */
    sprintf(tbuf, "ScreenModes%%%s", desktop_instantiation);
    error_trap(_swix(OS_Module, _IN(0)|_IN(1), 16, tbuf), 0);
}


/*---------------------------------------------------------------------------*
 * discard_screenmodes_instantiation                                         *
 *                                                                           *
 * Get rid of our created screenmodes instantiation.                         *
 *---------------------------------------------------------------------------*/

void discard_screenmodes_instantiation(void)
{
    char tbuf[256];

    sprintf(tbuf, "ScreenModes%%%d", my_screenmodes_instantiation);
    error_trap(_swix(OS_Module, _IN(0)|_IN(1), 4, tbuf), 0);
}


/*---------------------------------------------------------------------------*
 * mode_compare                                                              *
 *                                                                           *
 * Function for qsort to decide when a mode descriptor is higher or lower.   *
 *---------------------------------------------------------------------------*/

static int mode_compare( const void *aa, const void *bb )
{
        const int *a = *(int **)aa;
        const int *b = *(int **)bb;

        if ( (a[1] & flags_squarepixel) != (b[1] & flags_squarepixel) )
                return (a[1] & flags_squarepixel) ? 1 : -1 ;

        if ( a[2] != b[2] ) return a[2] - b[2];         /* Xres */

        if ( a[3] != b[3] ) return a[3] - b[3];         /* Yres */

        if ( a[4] != b[4] ) return a[4] - b[4];         /* pixel depth */

        return b[5] - a[5];                             /* frame rate */
}


/*---------------------------------------------------------------------------*
 * create_resolutions_menu                                                   *
 *                                                                           *
 * Given the object ID of the resolution menu, create the menu entries for   *
 * the selected MDF.                                                         *
 *                                                                           *
 * In: menu_handle = object id of resolution menu.                           *
 *---------------------------------------------------------------------------*/

void create_resolutions_menu(void)
{
    MenuTemplateEntry entry;
    int   i, spaceformodes, numdiffmodes, flag, entryno;
    char *rover;

    /* Remove all current menu entries */
    for (i=0; i<res_menu_entries; i++) error_trap(menu_remove_entry(0, res_menu_id, i), 0);
    res_menu_entries=0;

    choose_my_screenmodes_instantiation();

    /* Find spaces needed to enumerate the available screen modes */
    error_trap(_swix(OS_ScreenMode, _IN(0)|_IN(2)|_IN(6)|_IN(7)|_OUT(2)|_OUT(7),
                2, 0, 0, 0, &nummodes, &spaceformodes), 0);

    if (modes_block) free(modes_block);
    if (modes_index) free(modes_index);
    modes_block=NULL;
    modes_index=NULL;

    /* Get memory to store mode information */
    modes_block=malloc(-spaceformodes);
    if (!modes_block)
    {
        nummodes = 0;
        return;
    }

    /* Enumerate the available screen modes */
    error_trap(_swix(OS_ScreenMode, _IN(0)|_IN(2)|_IN(6)|_IN(7),
                2, 0, modes_block, -spaceformodes), 0);
    nummodes=-nummodes;

    return_to_base_screenmodes_instantiation();

    /* Modes index is a list of pointers to each mode in the mode data returned by EnumerateScreenModes */
    modes_index = malloc( sizeof( *modes_index ) * nummodes );
    if (!modes_index)
    {
        nummodes = 0;
        return;
    }

    if (nummodes==0) return;

    /* Go through all the modes and check for square pixel modes (mark these) */
    rover = modes_block;
    for ( i=0; i<nummodes; i++ )
    {
        int save,xeig,yeig;

        /* convert into mode selector */
        save = ((int *)rover)[6];
        ((int *)rover)[6] = -1;
        error_trap(_swix(OS_ReadModeVariable, _IN(0)|_IN(1)|_OUT(2), rover+4, 4, &xeig), 0);
        error_trap(_swix(OS_ReadModeVariable, _IN(0)|_IN(1)|_OUT(2), rover+4, 5, &yeig), 0);
        /* put back what we corrupted */
        ((int *)rover)[6] = save;
        /* naughty, naughty, we're changing mode selector flags! */
        if ( xeig == yeig ) ((int *)rover)[1] |= flags_squarepixel;
        /* Store the index for this mode */
        modes_index[i] = (int *)rover;
        rover = (char *)((int)(rover + 24 + strlen( rover+24 ) + 4) & ~3);
    }

    /* Sort the modes into resolution order */
    qsort( modes_index, nummodes, 4, mode_compare );

    /* We're not bothered about these fields in the menu entry data: */
    entry.click_show=0;
    entry.submenu_show=0;
    entry.submenu_event=0;
    entry.click_event=0;
    entry.help_message=0;
    entry.max_entry_help=0;

    /* Construct menu string */
    flag = modes_index[nummodes-1][1] & flags_squarepixel;
    numdiffmodes=0;
    //for ( i=0; i<nummodes; i++ )
    for (i=nummodes-1; i>=0; i--)
    {
        if ( (i==0 ||
              modes_index[i][2] != modes_index[i-1][2] ||
              modes_index[i][3] != modes_index[i-1][3]) &&
             ((char *)modes_index[i])[24] )
        {
            numdiffmodes++;

            /* Mark divider between square and rectangular pixel modes */
            entry.flags=0;
            if (flag != (modes_index[i][1] & flags_squarepixel))
            {
                flag = modes_index[i][1] & flags_squarepixel;
                entry.flags=2;
            }

            entry.component_id=res_menu_entries;
            entry.text=(char *)modes_index[i]+24;
            entry.max_text=256;
            menu_add_entry(0, res_menu_id, -1, (char*)&entry, &entryno);
            res_menu_entries++;
        }
    }

    currently_ticked=0;
    error_trap(menu_set_tick(0, res_menu_id, currently_ticked, 1), 0);
}


/*---------------------------------------------------------------------------*
 * construct_monitors_list                                                   *
 *                                                                           *
 * Build a structure with the details of all available MDFs.                 *
 *---------------------------------------------------------------------------*/

static void construct_monitors_list(char *dirname, char* buffer_end)
{
    _kernel_oserror *e=NULL;
    monitor_ref     *new_ref = 0;
    FILE            *fp;
    char            *dirname_end;
    char             string[256];
    int              num=0, last_item_read=0, pos=0, type, match=FALSE;

    dirname_end=dirname+strlen(dirname);

    while(last_item_read!=-1)
    {
        /* Get next entry from directory */
        _swi(OS_GBPB, _INR(0,6)|_OUTR(3,4),
                         9, dirname, dirname_end+1, 1, pos,
                         buffer_end - (dirname_end+1), 0,
                         &num, &last_item_read);

        if (!e && num>0)
        {
            *dirname_end = '.';

            pos+=num;

            /* Find the type of the entry we've just read */
            e=_swix(OS_File, _INR(0,1)|_OUT(0), 17, dirname, &type);
            if (type==2)
            {
                /* It was a directory, so scan this as well */
                construct_monitors_list(dirname, buffer_end);
            }
            else
            {
                /* It was a file, so try and read monitor information */
                fp = fopen(dirname, "r");

                /* Check for a file_format:1 string */
                while (!feof(fp) && match==FALSE)
                {
                    fgets(string, 256, fp);
                    if (strncmpa(string, FileFormat, strlen(FileFormat))==0) match=TRUE;
                }

                /* Check for monitor_title:xxx */
                if (match)
                {
                    match=FALSE;
                    while (!feof(fp))
                    {
                        fgets(string, 256, fp);
                        if (string[strlen(string)-1]==10) string[strlen(string)-1]=0;
                        if (strncmpa(string, MonitorTitle, strlen(MonitorTitle))==0)
                        {
                            /* Found a monitor title, so store details in a structure */
                            match=TRUE;
                            new_ref=malloc(sizeof(monitor_ref));
                            new_ref->file_name=malloc(strlen(dirname)+1);
                            new_ref->monitor_name=malloc(strlen(string)-strlen(MonitorTitle)+1);
                            new_ref->next=0;
                            strcpy(new_ref->file_name, dirname);
                            strcpy(new_ref->monitor_name, string+strlen(MonitorTitle));
                            *monitors_list_tail=new_ref;
                            monitors_list_tail=&new_ref->next;
                            break;
                        }
                    }

                    /* Check for DPMS state */
                    if (match)
                    {
                        while (!feof(fp))
                        {
                            fgets(string, 256, fp);
                            if (string[strlen(string)-1]==10) string[strlen(string)-1]=0;
                            if (strncmpa(string, DPMSState, strlen(DPMSState))==0)
                            {
                                /* Found the DPMS_state: string */
                                new_ref->dpms_state=atoi(string+strlen(DPMSState));
                                break;
                            }
                        }
                    }
                }

                fclose(fp);
            }
            *dirname_end = '\0';
        }
    }
}


/*---------------------------------------------------------------------------*
 * read_monitors_list                                                        *
 *                                                                           *
 * Read list of MDFs.                                                        *
 *                                                                           *
 * Returns: Memory required to construct a comma seperated string of all the *
 *          monitor titles (useful for stringset_set_available).             *
 *---------------------------------------------------------------------------*/

int read_monitors_list(void)
{
    monitor_ref *ref;
    char         buffer[1024];
    int          size = 0;

    monitors_list=NULL;
    monitors_list_tail=&monitors_list;
    sprintf(buffer, MonitorsDirectory);

    construct_monitors_list(buffer, buffer+1024);

    ref=monitors_list;
    while (ref->next)
    {
        size+=strlen(ref->monitor_name)+1;
        ref=ref->next;
    }
    size+=strlen(ref->monitor_name)+1;
    size+=16;

    return size;
}


/*---------------------------------------------------------------------------*
 * construct_monitors_menu                                                   *
 *                                                                           *
 * Build a menu string with the details of all available MDFs.               *
 *                                                                           *
 * In: menustring -> memory to construct string in.                          *
 *---------------------------------------------------------------------------*/

void make_monitors_menu(char *menustring)
{
    monitor_ref *ref;

    sprintf(menustring, "%s,", AutoString);
    if (monitors_list==0) menustring[strlen(menustring)]=0;

    ref=monitors_list;
    while (ref->next)
    {
        strcat(menustring, ref->monitor_name);
        strcat(menustring, ",");
        ref=ref->next;
    }
    strcat(menustring, ref->monitor_name);
}


/*---------------------------------------------------------------------------*
 * switch_resolution                                                         *
 *                                                                           *
 * Resolution has been changed - find nearest possible match for other       *
 * settings.                                                                 *
 *---------------------------------------------------------------------------*/

void switch_resolution(void)
{
    char buffer[256];
    int  x_to_find, y_to_find, nbytes, i, bestmatch;

    displayfield_get_value(0, main_window_id, ResolutionDisplay, buffer, 256, &nbytes);

    /* Search for the selected resolution string in our mode data */
    for (i=0; i<nummodes; i++)
    {
        if (strcmp((char*)&modes_index[i][6], buffer)==0) break;
    }

    /* Make sure we found the entry */
    if (i==nummodes) return;

    x_to_find = modes_index[i][2];
    y_to_find = modes_index[i][3];

    /* Now try and match to current_colours */
    bestmatch = -1;
    for (i=0; i<nummodes; i++)
    {
        if (modes_index[i][2]==x_to_find && modes_index[i][3]==y_to_find)
        {
            /* This mode has the right resolution */
            /* Check for going past the current colour depth */
            if (modes_index[i][4] > cdepth[current_colours]) break;

            /* Check if this is a better mode that bestmatch */
            if (bestmatch<0 || modes_index[bestmatch][4]!=modes_index[i][4]) bestmatch=i;
        }
    }

    /* If didn't find any suitable mode... */
    if (bestmatch<0) return;

    /* Found a bestmatch, so switch to that */
    if (modes_index[bestmatch][4]!=cdepth[current_colours])
    {
        /* Basically this goes:
           Lookup the colour depth we can achieve in one of the colour
           or grey downgrade tables. We choose which based on whether
           the old current_colours was colour or greys. */
        current_colours = (cdeptht[current_colours]?cdepthc:cdepthg)[ modes_index[bestmatch][4] ];
    }

    current_flags=modes_index[bestmatch][1] & flags_squarepixel;
    current_x_res=modes_index[bestmatch][2];
    current_y_res=modes_index[bestmatch][3];

    update_monitor_displayfields();
}


/*---------------------------------------------------------------------------*
 * switch_colour                                                             *
 *                                                                           *
 * Colour has been changed - find mode that most matches  resolution.        *
 *---------------------------------------------------------------------------*/

void switch_colour(int menusel)
{
    int i, bestmatch;
    int limit_x_res=current_x_res;
    int limit_y_res=current_y_res;

    /* If unknown resolution, then limit to mode 27 */
    if (limit_x_res<0) limit_x_res=640;
    if (limit_y_res<0) limit_y_res=480;

    /* Try and find the bestmatch */
    bestmatch=-1;
    for (i=0; i<nummodes; i++)
    {
        if (modes_index[i][4]==cdepth[menusel])
        {
            /* This mode has the right colour depth */
            if (bestmatch>=0)
            {
                /* We already have a match, so test for being in menu */
                char c1=*(char*)&modes_index[bestmatch][6];
                char c2=*(char*)&modes_index[i][6];

                if ((c1!='\0') && (c2=='\0')) continue;

                if ((c1!='\0') || (c2=='\0'))
                {
                    /* Either both are not in the menu or both are in the menu */
                    int f1=modes_index[bestmatch][1] & flags_squarepixel;
                    int f2=modes_index[i][1] & flags_squarepixel;
                    if (f1==f2)
                    {
                        /* Both the same pixel shape, so test x resolution */
                        if ( (modes_index[i][2] > limit_x_res) || (modes_index[i][2] < modes_index[bestmatch][2]) )
                            continue;
                    }
                    else
                    {
                        if ( f2 != current_flags ) continue;
                    }
                }
            }
            bestmatch=i;
        }
    }

    /* If didn't find any suitable mode... */
    if (bestmatch<0) return;

    /* Found a bestatch, so switch to that */
    current_colours=menusel;
    current_flags=modes_index[bestmatch][1] & flags_squarepixel;
    current_x_res=modes_index[bestmatch][2];
    current_y_res=modes_index[bestmatch][3];

    update_monitor_displayfields();
}


/*---------------------------------------------------------------------------*
 * switch_monitor                                                            *
 *                                                                           *
 * Monitor has been changed. Try and find mode that most matches colours and *
 * resolution settings.                                                      *
 *---------------------------------------------------------------------------*/

void switch_monitor(int menusel)
{
    monitor_ref *rover;
    char tbuf[256];
    int i;

    if (current_monitor_file) free(current_monitor_file);
    if (modes_block) free (modes_block);
    if (modes_index) free (modes_index);
    current_monitor_file=NULL;
    modes_block=NULL;
    modes_index=NULL;

    /* Find the monitor that's just been selected, in our array */
    i = 0;
    for ( rover = monitors_list; rover; rover = rover->next )
    {
        if (i==menusel ) break;
        i++;
    }

    if (rover)
    {
        /* Record the monitor name */
        current_monitor_file=malloc(strlen(rover->file_name)+1);
        if (!current_monitor_file) return;
        strcpy(current_monitor_file, rover->file_name);

        /* Load that file into our ScreenModes instantiation */
        choose_my_screenmodes_instantiation();
        sprintf(tbuf, "%s %s", LoadModeCmd, current_monitor_file);
        error_trap(_swix(OS_CLI, _IN(0), tbuf), 0);
        return_to_base_screenmodes_instantiation();

        /* Reinform the world that current monitor has changed too. */
        error_trap(_swix(OS_ServiceCall, _IN(1), Service_ModeFileChanged), 0);

        create_resolutions_menu();
    }

    update_monitor_displayfields();
    update_dpms_status();

    if (rover)
        switch_colour(current_colours);
}


/*---------------------------------------------------------------------------*
 * write_monitor_choices                                                     *
 *                                                                           *
 * Write the monitor choices file.                                           *
 *---------------------------------------------------------------------------*/

void write_monitor_choices(void)
{
    FILE *dest_fp;
    char  line1[256];
    char  line2[256];
    int   type;

    if ((current_x_res==-1) || (current_y_res==-1) || (current_colours==-1))
    {
        warning_box(messages_lookup("CantSaveMode"));
        return;
    }

    /* Find monitor type (0=Auto) */
    error_trap(stringset_get_selected(1, main_window_id, MonitorTypeStringSet, &type), 0);

    if (type>0)
    {
        /* Not Auto */
        sprintf(line1, LoadModeCmd "%s", current_monitor_file);
        sprintf(line2, WimpModeCmd " X%d Y%d %s", current_x_res, current_y_res, cmenutotext[current_colours]);
    }
    else
    {
        /* Set auto monitor type (whatever's in CMOS) */
        line1[0]=0;
        line2[0]=0;
        _swix(OS_ScreenMode, _IN(0)|_IN(1), 3, -1);
        _swix(Wimp_SetMode, _IN(0), 27);
    }

    /* Open a scrap file to build the new version of the file */
    dest_fp=fopen(PreDeskConfigFile, "w");
    if (!dest_fp) return;

    /* Write the LoadModeFile and WimpMode commands to the file */
    if (type>0)
    {
        fputs(line1, dest_fp);
        fputc(NewLine, dest_fp);
        fputs(line2, dest_fp);
        fputc(NewLine, dest_fp);
    }

    fclose(dest_fp);
    error_trap(_swix(OS_File, _INR(0, 2), 18, PreDeskConfigFile, 0xFEB), 0);

    _swix(OS_CLI, _IN(0), line1);
    _swix(OS_CLI, _IN(0), line2);
}