/* TTXeditor v0.00g

   Name   : TTXeditor
   Version: v0.00g
   Date   : Wednesday, 3rd March 2004
   Started: Tuesday, 17th February 2004

   Purpose: Teletext frame editor

   Creator: Rick Murray
*/



/* Included files */
/* DeskLib */
#include "DeskLib:WimpSWIs.h" /* Low-level WIMP commands        */
#include "DeskLib:Window.h"   /* Window handling automation     */
#include "DeskLib:Core.h"     /* useful core functions          */
#include "DeskLib:Dialog2.h"  /* windows on menus made easy     */
#include "DeskLib:DragASpr.h" /* Drag sprite handler            */
#include "DeskLib:Error.h"    /* Error handler                  */
#include "DeskLib:Event.h"    /* Event despatcher               */
#include "DeskLib:EventMsg.h" /* Wimp Message event dispatcher  */
#include "DeskLib:File.h"     /* File handling routines         */
#include "DeskLib:GFX.h"      /* Graphics handlers (for redraw) */
#include "DeskLib:Handler.h"  /* Default/example event handlers */
#include "DeskLib:Hourglass.h"/* For those lllooonnnggg things  */
#include "DeskLib:Icon.h"     /* Icon handling automation       */
#include "DeskLib:Kbd.h"      /* For seeing if ALT is held down */
#include "DeskLib:KernelSWIs.h"/* For OS_CLI                    */
#include "DeskLib:Menu.h"     /* Menu create & show support     */
#include "DeskLib:Resource.h" /* Handles finding resource files */
#include "DeskLib:Screen.h"   /* Getting screen size info etc   */
#include "DeskLib:Time.h"     /* For Time_Monotonic()           */
#include "DeskLib:Template.h" /* Template loading and caching   */

/* ANSI C */
#include <ctype.h>            /* Character types                */
#include <errno.h>            /* Error reporting in C library   */
#include <math.h>             /* Maths functions                */
#include <stdio.h>            /* Standard input/output etc      */
#include <stdlib.h>           /* Standard additional library    */
#include <string.h>           /* String handling functions      */

/* Acorn RISC OS */
#include "kernel.h"           /* Access to RiscOS kernel        */




/* Definitions */
#define SWI_TTX_ConvertPage  0x4E444
#define SWI_TTX_Colours      0x4E447

#define CHAR_HEIGHT 36
#define CHAR_WIDTH  24




/* Variables */
/* DeskLib */
icon_handle iconbaricon;
BOOL finished = FALSE;
dialog2_block *info;
menu_ptr main_menu = NULL;
menu_ptr viewer_menu = NULL;
menu_ptr lang_menu = NULL;
window_handle frameinfo_window;
window_handle loader_window;
window_handle mosaicpanel_window;
window_handle remark_window;
window_handle saveframe_window;
window_handle textpanel_window;
window_handle viewer_window;
window_handle panel_window;
/* Other */
_kernel_swi_regs r;

char appname[] = "TTXeditor";
char appvers[] = "0.00g";
char appdate[] = "2004/03/03";
char appauth[] = "Rick Murray";

typedef union
{
   char   rawdata[1080];
   struct
   {
      char bytes[1040];
      int  link_red;
      int  link_green;
      int  link_yellow;
      int  link_cyan;
      int  link_cat;
      int  link_index;
      int  link_status;
      int  crc;
      int  frame;
      int  lang_page    : 8;
      int  lang_header  : 8;
      int  filler       : 16;
   } data;
} frame_def;
frame_def frame;

char ttxpath[256] = "";
int  cursor_x = 0;
int  cursor_y = 0;
int  gspoint_x = 0;
int  gspoint_y = 0;
int  gfpoint_x = 0;
int  gfpoint_y = 0;
int  showgrid = FALSE;
int  showcodes = FALSE;
int  changed = FALSE;
int  caret = FALSE;
int  caret_ours = FALSE;
int  viewer_is_open = FALSE;
int  mosaicmode = FALSE;
int  drawmode = 0;
int  lang_type = 0;
int  lang_page = 0;
int  lang_header = 0;
int  export_format = 0;
int  last_colour = 7; // default to white
extern char *embed_base_address;




/* Function prototypes */
void initialise_wimp(void);
void debug(char *message, ...);
BOOL info_show(event_pollblock *event, void *reference);
void info_handler(dialog2_block *dialog2);
BOOL menu_choice(event_pollblock *event, void *reference);
BOOL open_handler(event_pollblock *event, void *reference);
BOOL close_handler(event_pollblock *event, void *reference);
BOOL redraw_handler(event_pollblock *event, void *reference);
BOOL null_handler(event_pollblock *event, void *reference);
BOOL caretlose_handler(event_pollblock *event, void *reference);
BOOL caretgain_handler(event_pollblock *event, void *reference);
BOOL prequit_handler(event_pollblock *event, void *reference);
void redraw_viewer(void);
BOOL generic_keyhandler(event_pollblock *, void *);
void panel_move(int rightpos, int toppos);
BOOL save_message(event_pollblock *event, void *reference);
BOOL dataloader(event_pollblock *, void *);
BOOL iconbar_handler(event_pollblock *event, void *reference);
BOOL frameinfo_clickhandler(event_pollblock *event, void *reference);
BOOL mosaicpanel_clickhandler(event_pollblock *event, void *reference);
BOOL remark_clickhandler(event_pollblock *event, void *reference);
BOOL saveframe_clickhandler(event_pollblock *event, void *reference);
BOOL textpanel_clickhandler(event_pollblock *event, void *reference);
BOOL viewer_clickhandler(event_pollblock *event, void *reference);
BOOL viewer_keyhandler(event_pollblock *event, void *reference);
BOOL saveframe_keyhandler(event_pollblock *event, void *reference);
void begin_drag(window_handle window, icon_handle icon, char *iconname);
BOOL finish_drag(event_pollblock *event, void *reference);
void extract_filename(char *in, char *out);
void blank_frame(void);
int  load_frame(void);
void frame_refreshdisplay(void);
void frame_addcodes(void);
void frame_addgrid(void);
void open_viewer(void);
void frame_writebyte(int byte);
void frameinfo_refresh(void);
void frameinfo_refreshlanguage(void);
void frameinfo_update(void);
void export_verifyicon(char *);
void saveframe_setupicon(char *icn);
void save_frame(void);
void export_sprite(FILE *fp);
void fwriteword(FILE *fp, int value);
void goto_lastword(void);
void goto_nextword(void);
void goto_nexttab(void);
void goto_lasttab(void);
void insert_col(void);
void delete_col(void);
void editor_settitle(int);
void graphics_plotpoint(int xpos, int ypos, int);
void graphics_plotpointfast(int xpos, int ypos, int doplot);
void graphics_plotline(int);
void graphics_plotline_fastvertical(int, int, int, int);
void graphics_plotline_fasthorizontal(int, int, int, int);
void graphics_plotrectangle(int);
int  convert_from_sprite(void);
int  convert_invalid_sprite(int);
void mosaic_setvalidation(void);
void load_sprite_patchup(void);
int  check_numerical(void);




/* Program entry point */
int main(int argc, char *argv[])
{
   int  ticker = 0;
   int  wimpmsgs[] = { message_DATASAVE, message_DATASAVEACK, message_DATALOAD, \
                       message_PREQUIT, message_HELPREQUEST, message_MENUWARNING, \
                       message_MODECHANGE, message_PRINTFILE, message_PRINTSAVE, 0};

   /* Load enough of DeskLib to provide an error box, if needed */
   Resource_Initialise(appname);

   /* Here you can insert pre-initialise routines, like something to
      check the CLI parameters. */
   argc = argc; /* Avoid compiler warnings. */
   argv = argv;

   /* Set up teletext colours */
   r.r[0] = 7;
   r.r[1] = 11;
   r.r[2] = 10;
   r.r[3] = 9;
   r.r[4] = 8;
   r.r[5] = 13;
   r.r[6] = 15;
   r.r[7] = 0;
   _kernel_swi(SWI_TTX_Colours, &r, &r);

   /* Initialise as a WIMP task */
   Event_Initialise3(appname, 300, wimpmsgs);
   initialise_wimp();

   /* 'Splash' window */
   Window_Show(loader_window, open_CENTERED);
   _kernel_swi(SWI_OS_ReadMonotonicTime, &r, &r);
   ticker = r.r[0] + 200;
   while (ticker > r.r[0] && !finished)
   {
      Wimp_Poll(event_mask, &event_lastevent);
      Event_Process(&event_lastevent);
      _kernel_swi(SWI_OS_ReadMonotonicTime, &r, &r);
   }
   Wimp_CloseWindow(loader_window);

   /* The polling loop... */
   while( !finished )
   {
      Wimp_PollIdle(event_mask, &event_lastevent, (Time_Monotonic() + 50));
      Event_Process(&event_lastevent);
   }

   /* And when finished... */
   Template_ClearAll();
   Event_CloseDown();
   exit(EXIT_SUCCESS);
}



void initialise_wimp(void)
{
   /* Set up libraries, load and create windows, create menus, attach
      events... */

   char spritefile_one[64];
   char spritefile_two[64];
   char spritefile_thr[64];
   icon_createblock icreate;
   icon_handle      icon;
   char             *iconname;

   // Load sprites
   strncpy(spritefile_one, resource_pathname, 48);
   strncpy(spritefile_two, resource_pathname, 48);
   strncpy(spritefile_thr, resource_pathname, 48);
   strcat(spritefile_one, "Resources.Tools");      // control panel tools
   strcat(spritefile_two, "Resources.Codes");      // on-screen 'codes'
   strcat(spritefile_thr, "Resources.Frame");      // the frame as we see it
   resource_sprites = Sprite_LoadFiles(3, spritefile_one, spritefile_two, spritefile_thr);

   EventMsg_Initialise();
   Screen_CacheModeInfo();

   Template_Initialise();
   Template_UseSpriteArea(resource_sprites);
   Template_LoadFile("Resources.Templates"); // we are not 'internationalised'

   info = Dialog2_CreateDialogBlock("info", -1, -1, info_handler, NULL, NULL);
   if (info == 0)
   {
      Error_Report(0, "Unable to load template definition \'%s\' - please reinstall.", "info");
      exit(EXIT_FAILURE);
   }

   /* Create the frameinfo window */
   frameinfo_window = Window_Create("frameinfo", 45);
   if (frameinfo_window == 0)
   {
      Error_Report(0, "Unable to load template definition \'%s\' - please reinstall.", \
                   "frameinfo");
      exit(EXIT_FAILURE);
   }
   Event_Claim(event_CLICK, frameinfo_window, event_ANY, frameinfo_clickhandler, NULL);
   Event_Claim(event_KEY, frameinfo_window, event_ANY, generic_keyhandler, NULL);

   /* Create the loader window */
   loader_window = Window_Create("loader", 45);
   if (loader_window == 0)
   {
      Error_Report(0, "Unable to load template definition \'%s\' - please reinstall.", \
                   "loader");
      exit(EXIT_FAILURE);
   }

   /* Create the mosaicpanel window */
   mosaicpanel_window = Window_Create("mosaicpanel", 45);
   if (mosaicpanel_window == 0)
   {
      Error_Report(0, "Unable to load template definition \'%s\' - please reinstall.", \
                   "mosaicpanel");
      exit(EXIT_FAILURE);
   }
   Event_Claim(event_CLICK, mosaicpanel_window, event_ANY, mosaicpanel_clickhandler, NULL);
   Icon_SetSelect(mosaicpanel_window, 1, TRUE);

   /* Create the remark window */
   remark_window = Window_Create("remark", 45);
   if (remark_window == 0)
   {
      Error_Report(0, "Unable to load template definition \'%s\' - please reinstall.", \
                   "remark");
      exit(EXIT_FAILURE);
   }
   Event_Claim(event_CLICK, remark_window, event_ANY, remark_clickhandler, NULL);

   /* Create the saveframe window */
   saveframe_window = Window_Create("saveframe", 45);
   if (saveframe_window == 0)
   {
      Error_Report(0, "Unable to load template definition \'%s\' - please reinstall.", \
                   "saveframe");
      exit(EXIT_FAILURE);
   }
   Event_Claim(event_CLICK, saveframe_window, event_ANY, saveframe_clickhandler, NULL);
   Event_Claim(event_KEY, saveframe_window, event_ANY, generic_keyhandler, NULL);

   /* Create the textpanel window */
   textpanel_window = Window_Create("textpanel", 45);
   if (textpanel_window == 0)
   {
      Error_Report(0, "Unable to load template definition \'%s\' - please reinstall.", \
                   "textpanel");
      exit(EXIT_FAILURE);
   }
   Event_Claim(event_CLICK, textpanel_window, event_ANY, textpanel_clickhandler, NULL);
   Icon_SetSelect(textpanel_window, 0, TRUE);

   /* Create the viewer window */
   viewer_window = Window_Create("viewer", 64);
   if (viewer_window == 0)
   {
      Error_Report(0, "Unable to load template definition \'%s\' - please reinstall.", \
                   "viewer");
      exit(EXIT_FAILURE);
   }
   Event_Claim(event_CLICK, viewer_window, event_ANY, viewer_clickhandler, NULL);
   Event_Claim(event_KEY, viewer_window, event_ANY, viewer_keyhandler, NULL);
   Event_Claim(event_NULL, event_ANY, event_ANY, null_handler, NULL);

   /* Default handlers */
   Event_Claim(event_CLOSE, event_ANY, event_ANY, close_handler, NULL);
   Event_Claim(event_REDRAW, event_ANY, event_ANY, redraw_handler, NULL);
   Event_Claim(event_OPEN, event_ANY, event_ANY, open_handler, NULL);
   Event_Claim(event_USERDRAG, event_ANY, event_ANY, finish_drag, NULL);
   Event_Claim(event_LOSECARET, viewer_window, event_ANY,  caretlose_handler, NULL);
   Event_Claim(event_GAINCARET, viewer_window, event_ANY,  caretgain_handler, NULL);

   /* Plot iconbar icon */
   // Our icon is larger than usual, so we need to create our own handler...
   icreate.window = iconbar_RIGHT;
   icreate.icondata.flags.value = 0x1700310A;    /* Click-able sprite icon */
   iconname = malloc( wimp_MAXNAME + 1 );
   if (iconname == NULL)
      Error_Report(1,"Not enough memory for iconbar icon");
   strcpy(iconname, "!ttxeditor");
   icreate.icondata.data.indirectsprite.name = iconname;
   icreate.icondata.data.indirectsprite.spritearea = (unsigned int *)resource_sprites;
   icreate.icondata.data.indirectsprite.nameisname = wimp_MAXNAME;
   icreate.icondata.workarearect.min.x = icreate.icondata.workarearect.min.y = 0;
   icreate.icondata.workarearect.max.x = 98; // later, we should read the size
   icreate.icondata.workarearect.max.y = 72; // directly from the sprite...
   Wimp_CreateIcon(&icreate, &icon);
   iconbaricon = icon;

   Event_Claim(event_CLICK, -2, iconbaricon, iconbar_handler, NULL);

   /* Wimp message claims */
   EventMsg_Claim(message_DATALOAD, event_ANY, dataloader, NULL);
   EventMsg_Claim(message_DATASAVE, event_ANY, save_message, NULL);
   EventMsg_Claim(message_DATASAVEACK, event_ANY, save_message, NULL);
   EventMsg_Claim(message_PREQUIT, event_ANY, prequit_handler, NULL);
   EventMsg_Claim(message_MODECHANGE, event_ANY, Handler_ModeChange, NULL);

   // Menus
   main_menu   = Menu_New(appname, ">Info,Quit");
   Menu_Warn(main_menu, 0, TRUE, info_show, info);

   viewer_menu = Menu_New(appname, "Show control codes,Show grid,Frame info...|Save|Remove");
   Menu_AddSubMenu(viewer_menu, 3, (menu_ptr)saveframe_window);

   lang_menu   = Menu_New("Languages", "English,French,Swedish,(unassigned),German,Spanish,Italian,(unassigned)");

   Event_Claim(event_MENU, event_ANY, event_ANY, menu_choice, NULL);

   // default choice
   panel_window = textpanel_window;
   mosaicmode = FALSE;

   // default save type?  (reverse order so it is sane if multiple selected)
   if (Icon_GetSelect(saveframe_window, 3)) export_format = 2; // mode 7
   if (Icon_GetSelect(saveframe_window, 2)) export_format = 1; // sprite
   if (Icon_GetSelect(saveframe_window, 1)) export_format = 0; // teletext
   switch (export_format)
   {
      case  0 : /* Teletext */
                strcpy(spritefile_one, "file_112");
                break;

      case  1 : /* Sprite */
                strcpy(spritefile_one, "file_ff9");
                break;

      case  2 : /* MODE 7 */
                strcpy(spritefile_one, "file_ffd");
                break;
   }
   saveframe_setupicon(spritefile_one);
   Icon_SetSelect(saveframe_window, 1, FALSE);
   Icon_SetSelect(saveframe_window, 2, FALSE);
   Icon_SetSelect(saveframe_window, 3, FALSE);
   Icon_SetSelect(saveframe_window, (export_format + 1), TRUE);

   // default grid/codes setting
   if (Icon_GetSelect(mosaicpanel_window, 20) || \
       Icon_GetSelect(textpanel_window, 17) )
   {
      showcodes = TRUE;
      Menu_SetFlags(viewer_menu, 0, showcodes, FALSE);
      frame_refreshdisplay();
      redraw_viewer();
      Icon_SetSelect(mosaicpanel_window, 20, showcodes);
      Icon_SetSelect(textpanel_window, 17, showcodes);
   }

   if (Icon_GetSelect(mosaicpanel_window, 25))
   {
      showgrid = TRUE;
      Menu_SetFlags(viewer_menu, 1, showgrid, FALSE);
      frame_refreshdisplay();
      redraw_viewer();
      Icon_SetSelect(mosaicpanel_window, 25, showgrid);
   }

   return;
}



void debug(char *message, ...)
{
   /* Accepts printf() style parameters */
   int  items;
   char *out;
   va_list variables;
   message_block msgblk;

   /* if (debug != TRUE) return; */

   if ((out = (char *)malloc(1024)) == NULL) /* Allocate a block of memory */
      return;

   va_start(variables, message);
   items = vsprintf(out, message, variables);

   msgblk.header.size = 256;
   msgblk.header.myref = 0;
   msgblk.header.action = (message_action)0x804C0;
   strcpy(msgblk.data.bytes, out);

   Wimp_SendMessage((event_type)17, &msgblk, 0, 0);

   va_end(variables);
   free(out);

   return;
}



BOOL info_show(event_pollblock *event, void *reference)
{
   Dialog2_OpenDialogMenuLeaf(event, (dialog2_block *) reference);

   return TRUE;
}



void info_handler(dialog2_block *dialog2)
{
   // fill out the info window

   Icon_SetText(dialog2->window, 4, appname);
   Icon_printf(dialog2->window, 12, " 2004 %s", appauth);
   Icon_printf(dialog2->window, 15, "%s %s", appvers, appdate);
   Icon_SetText(dialog2->window,16, "DEVELOPMENT VERSION");
   Icon_SetText(dialog2->window,17, "http://www.heyrick.co.uk/software/ttx/");

   return;
}



BOOL menu_choice(event_pollblock *event, void *reference)
{
   mouse_block ptr;
   int menuitem = event->data.selection[0];

   /* If "main" menu, handle it */
   if (menu_currentopen == main_menu)
   {
      switch(menuitem)
      {
         case  0 : /* Info */
                   Dialog2_OpenDialogMenuLeaf(event,(dialog2_block *)reference);
                   break;

         case  1 : /* Quit */
                   if (changed)
                   {
                      int flags;
                      os_error error;
                      error.errnum = 1;
                      strcpy(error.errmess, "This frame has been changed, are you sure you want to quit?");
                      flags = Wimp_ReportErrorR(&error, 27, event_taskname);
                      if (flags != 1)
                         return TRUE; // user said NO
                   }
                   finished = TRUE;
                   break;
      }
   }

   /* If "viewer" menu, handle it */
   if (menu_currentopen == viewer_menu)
   {
      switch(menuitem)
      {
         case  0 : /* Show control codes */
                   showcodes = showcodes ^ TRUE;
                   Menu_SetFlags(viewer_menu, 0, showcodes, FALSE);
                   frame_refreshdisplay();
                   redraw_viewer();
                   Icon_SetSelect(mosaicpanel_window, 20, showcodes);
                   Icon_SetSelect(textpanel_window, 17, showcodes);
                   break;

         case  1 : /* Show grid */
                   showgrid = showgrid ^ TRUE;
                   Menu_SetFlags(viewer_menu, 1, showgrid, FALSE);
                   frame_refreshdisplay();
                   redraw_viewer();
                   Icon_SetSelect(mosaicpanel_window, 25, showgrid);
                   break;

         case  2 : /* Frame info... */
                   Window_Show(frameinfo_window, open_CENTERED);
                   break;

         case  3 : /* Save */
                   // linked...
                   break;

         case  4 : /* Remove */
                   if (changed)
                   {
                      int flags;
                      os_error error;
                      error.errnum = 1;
                      strcpy(error.errmess, "This frame has been changed, are you sure you want to remove (abandon) it?");
                      flags = Wimp_ReportErrorR(&error, 27, event_taskname);
                      if (flags != 1)
                         return TRUE; // user said NO
                   }
                   blank_frame();
                   changed = FALSE;
                   strcpy(ttxpath, "<TTXeditor$Dir>.temp");
                   Icon_SetText(saveframe_window, 7, ttxpath);
                   editor_settitle(FALSE);
                   cursor_x = 0;
                   cursor_y = 0;
                   if (drawmode > 4)
                      drawmode -= 4;
                   frame_refreshdisplay();
                   break;
      }
   }

   // language menu?
   if (menu_currentopen == lang_menu)
   {
      if ( (menuitem < 0) || (menuitem > 7) )
      {
         Error_Report(1, "Received invalid value from language menu (%d, should be 0-7), assuming language \"English\" (0).", menuitem);
         menuitem = 0;
      }

      switch (lang_type)
      {
         case 22 : // page language
                   lang_page = menuitem;
                   break;

         case 25 : // header language
                   lang_header = menuitem;
                   break;

         default : // anything else
                   Error_Report(1, "Received invalid language type (ID = %d) during language menu decoding, assuming page language is being updated.", lang_type);
                   lang_page = menuitem;
                   break;
      }
      frameinfo_refreshlanguage(); // reflect it in the frame information
   }

   Wimp_GetPointerInfo(&ptr);  /* If ADJUST was used to select, then keep the menu open. */
   if (ptr.button.data.adjust)
      Menu_ShowLast();

   return TRUE;
}



BOOL open_handler(event_pollblock *event, void *reference)
{
   /* Handles window open events so we can know when to reposition panes. */

   window_handle winmove;
   int masterright;
   int mastertop;

   reference = reference;

   Wimp_OpenWindow(&event->data.openblock);

   winmove = event->data.openblock.window;
   masterright = event->data.openblock.screenrect.max.x;
   mastertop = event->data.openblock.screenrect.max.y;

   if ( (int)winmove == (int)viewer_window)
   {
      panel_move(masterright, mastertop);
   }

   return TRUE;
}



BOOL close_handler(event_pollblock *event, void *reference)
{
   /* Handles window closed events so we can know when to unlink panes. */

   window_handle winhandle;

   reference = reference;

   winhandle = event->data.openblock.window;

   Wimp_CloseWindow(winhandle);

   if ( (int)winhandle == (int)viewer_window)
   {
      Wimp_CloseWindow(panel_window);
      viewer_is_open = FALSE;
   }

   if ( (int)winhandle == (int)saveframe_window)
   {
      caret_block carrot;
      carrot.window = viewer_window;
      carrot.icon = -1;
      carrot.offset.x = -256;
      carrot.offset.y = 256;
      carrot.height   = -1;
      carrot.index    = -1;
      Wimp_SetCaretPosition(&carrot);
   }

   return TRUE;
}



BOOL redraw_handler(event_pollblock *event, void *reference)
{
   /* Handles window redraw events, to pass to our redraw routine. */

   window_handle winhandle;
   window_redrawblock redraw;
   BOOL more = TRUE;
   int  x_origin = 0;
   int  y_origin = 0;

   reference = reference;
   winhandle = event->data.openblock.window;

   if ( (int)winhandle == (int)viewer_window )
   {
      scale_block sclblk;
      char pixtrans[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

      sclblk.mul.x = sclblk.mul.y = sclblk.div.x = sclblk.div.y = 1;

      // Wimp_ReadPixTrans has bloody weird call parameters, I'll do it myself...
      r.r[0] = 256;
      r.r[1] = (int)resource_sprites;
      r.r[2] = (int)"ttx_frame";
      r.r[6] = (int)&sclblk;
      r.r[7] = (int)&pixtrans;
      _kernel_swi(0x400ED, &r, &r); // Wimp_ReadPixTrans

      redraw.window = event->data.openblock.window;
      Wimp_RedrawWindow(&redraw, &more);

      x_origin = redraw.rect.min.x - redraw.scroll.x; // this version does not scroll AT ALL
      y_origin = redraw.rect.max.y - redraw.scroll.y; // (others may, or user may hack templates)

      while (more)
      {
         r.r[0] = (52 + 256);
         r.r[1] = (int)resource_sprites;
         r.r[2] = (int)"ttx_frame";
         r.r[3] = x_origin;
         r.r[4] = y_origin - 900;
         r.r[5] = 0;
         r.r[6] = (int)&sclblk;
         r.r[7] = (int)&pixtrans;
         _kernel_swi(0x2E, &r, &r); // OS_SpriteOp

         Wimp_GetRectangle(&redraw, &more);
      }
   }

   return TRUE;
}



BOOL null_handler(event_pollblock *event, void *reference)
{
   /* Handles caret flash. */

   // if viewer open, redraw it
   if (viewer_is_open)
   {
      frame_refreshdisplay();
      redraw_viewer();
   }

   return TRUE;
}



BOOL caretlose_handler(event_pollblock *event, void *reference)
{
   /* Handles caret lossage. */

   if (drawmode > 4)
      drawmode -= 4;
   caret_ours = FALSE;
   caret = TRUE;
   frame_refreshdisplay();
   redraw_viewer();

   return TRUE;
}



BOOL caretgain_handler(event_pollblock *event, void *reference)
{
   /* Handles caret gainification. */

   caret_ours = TRUE;
   caret = TRUE;
   frame_refreshdisplay();
   redraw_viewer();

   return TRUE;
}



BOOL prequit_handler(event_pollblock *event, void *reference)
{
   reference = reference;

   if (changed)
   {
      int flags;
      os_error error;
      error.errnum = 1;
      sprintf(error.errmess, "This frame has been changed, are you sure you want to quit %s?", \
                             appname);
      flags = Wimp_ReportErrorR(&error, 27, event_taskname);
      if (flags != 1)
      {
         // user said NO
         event->data.message.header.yourref = event->data.message.header.myref;
         Wimp_SendMessage(event_USERMESSAGE, &event->data.message, \
                          event->data.message.header.sender, 0);
      }
      return TRUE;
   }

   return TRUE;
}



void redraw_viewer(void)
{
   /* Unlike redraw_handler, this FORCES a full redraw of the viewer window... */

   window_redrawblock redraw;
   BOOL more = TRUE;
   int  x_origin = 0;
   int  y_origin = 0;
   scale_block sclblk;
   char pixtrans[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

   sclblk.mul.x = sclblk.mul.y = sclblk.div.x = sclblk.div.y = 1;

   // Always do this - easiest way to be mode-change-safe.
   r.r[0] = 256;
   r.r[1] = (int)resource_sprites;
   r.r[2] = (int)"ttx_frame";
   r.r[6] = (int)&sclblk;
   r.r[7] = (int)&pixtrans;
   _kernel_swi(0x400ED, &r, &r); // Wimp_ReadPixTrans

   redraw.window = viewer_window;
   redraw.rect.min.x = 0;
   redraw.rect.min.y = -900;
   redraw.rect.max.x = 960;
   redraw.rect.max.y = 0;
   Wimp_UpdateWindow(&redraw, &more);

   x_origin = redraw.rect.min.x - redraw.scroll.x;
   y_origin = redraw.rect.max.y - redraw.scroll.y;

   while (more)
   {
      r.r[0] = (52 + 256);
      r.r[1] = (int)resource_sprites;
      r.r[2] = (int)"ttx_frame";
      r.r[3] = x_origin;
      r.r[4] = y_origin - 900;
      r.r[5] = 0;
      r.r[6] = (int)&sclblk;
      r.r[7] = (int)&pixtrans;
      _kernel_swi(0x2E, &r, &r); // OS_SpriteOp

      Wimp_GetRectangle(&redraw, &more);
   }

   return;
}



BOOL viewer_keyhandler(event_pollblock *event, void *reference)
{
   /* Handles key presses on the 'listing' window... */

   int  key  = -1;
   caret_block carrot;
   char tmpbuf[256];

   reference = reference;

   key = event->data.key.code;

   if (key < 256)
   {
      /* Keypress is a regular key... */
      switch (key)
      {
         case  8 : // Delete : Delete... :-)
                   cursor_x--;
                   if (cursor_x < 0)
                      cursor_x = 0;
                   frame.data.bytes[ ((cursor_y * 40) + cursor_x) ] = ' ';
                   break;

          case 13 : // Enter : Newline
                   cursor_x = 0;
                   cursor_y++;
                   if (cursor_y > 24)
                      cursor_y = 24;
                   break;

         case 27 : // Esc : Cancel any active drawing mode
                   if (drawmode > 4)
                      drawmode -= 4; // return to start form
                   break;

         case 30 : // Home : Go to top of frame
                   cursor_x = 0;
                   cursor_y = 0;
                   break;

         case 127: // BackSpace : Likewise... ;-)
                   break;

         default : // Any other key...
                   if (key < 32)
                      Error_Report(1, "You cannot enter control codes directly. Please use either the function keys or the tool panel(s).");
                   else
                   {
                      if ( (key >= '0') && (key <= '9') )
                      {
                         if (check_numerical() == FALSE)
                            frame_writebyte(key);
                      }
                      else
                      {
                         frame_writebyte(key);
                      }
                   }
                   break;
      }
   }
   else
   {
      /* Keypress is a function key... */
      switch (key)
      {
         case 0x181 : /*  F1 - Frame information */
                      Window_Show(frameinfo_window, open_CENTERED);
                      break;

         case 0x183 : /*  F3 - Save */
                      Window_Show(saveframe_window, open_CENTERED);
                      Icon_GetText(saveframe_window, 7, tmpbuf);
                      carrot.window = saveframe_window;
                      carrot.icon = 7;
                      carrot.offset.x = 0;
                      carrot.offset.y = 0;
                      carrot.height   = -1;
                      carrot.index    = strlen(tmpbuf);
                      Wimp_SetCaretPosition(&carrot);
                      break;

         case 0x185 : /*  F5 - Show codes */
                      showcodes = showcodes ^ TRUE;
                      Menu_SetFlags(viewer_menu, 0, showcodes, FALSE);
                      frame_refreshdisplay();
                      redraw_viewer();
                      Icon_SetSelect(mosaicpanel_window, 20, showcodes);
                      Icon_SetSelect(textpanel_window, 17, showcodes);
                      break;

         case 0x186 : /*  F6 - Show grid */
                      showgrid = showgrid ^ TRUE;
                      Menu_SetFlags(viewer_menu, 1, showgrid, FALSE);
                      frame_refreshdisplay();
                      redraw_viewer();
                      Icon_SetSelect(mosaicpanel_window, 25, showgrid);
                      break;

         case 0x188 : /*  F8 - Reload from disc */
                      if (changed)
                      {
                         int flags;
                         os_error error;
                         error.errnum = 1;
                         strcpy(error.errmess, "This frame has been changed, are you sure you want to reload the (old) version from disc?");
                         flags = Wimp_ReportErrorR(&error, 27, event_taskname);
                         if (flags != 1)
                            return TRUE; // user said NO
                      }
                      blank_frame();
                      load_frame();
                      break;

         case 0x191 : /* sF1 - Red text */
         case 0x192 : /* sF2 - Green text */
         case 0x193 : /* sF3 - Yellow text */
         case 0x194 : /* sF4 - Blue text */
         case 0x195 : /* sF5 - Magenta text */
         case 0x196 : /* sF6 - Cyan text */
         case 0x197 : /* sF7 - White text */
         case 0x198 : /* sF8 - Flash */
         case 0x199 : /* sF9 - Steady */
                      frame_writebyte( (key - 0x190) );
                      break;

         case 0x1DA : /* sF10- Black text [pretending to be F0] */
                      frame_writebyte(0);
                      break;

         case 0x1A1 : /* sF1 - Red mosaic */
         case 0x1A2 : /* sF2 - Green mosaic */
         case 0x1A3 : /* sF3 - Yellow mosaic */
         case 0x1A4 : /* sF4 - Blue mosaic */
         case 0x1A5 : /* sF5 - Magenta mosaic */
         case 0x1A6 : /* sF6 - Cyan mosaic */
         case 0x1A7 : /* sF7 - White mosaic */
                      frame_writebyte( (key - 0x1A0) );
                      break;

         case 0x1EA : /* cF10- Black text [pretending to be F0] */
                      frame_writebyte(16);
                      break;

         case 0x1B1 : /*csF1 - Double height */
                      frame_writebyte(13);
                      break;

         case 0x1B2 : /*csF2 - Normal height */
                      frame_writebyte(12);
                      break;

         case 0x1B3 : /*csF3 - New background */
                      frame_writebyte(29);
                      break;

         case 0x1B4 : /*csF4 - Black background */
                      frame_writebyte(28);
                      break;

         case 0x1B5 : /*csF5 - Separated */
                      frame_writebyte(26);
                      break;

         case 0x1B6 : /*csF6 - Contiguous */
                      frame_writebyte(25);
                      break;

         case 0x1B7 : /*csF7 - Hold */
                      frame_writebyte(30);
                      break;

         case 0x1B8 : /*csF8 - Release */
                      frame_writebyte(31);
                      break;

         case 0x1B9 : /*csF9 - Conceal */
                      frame_writebyte(24);
                      break;

         case 0x18C : /* Cursor left */
                      cursor_x--;
                      if (cursor_x < 0)
                      {
                         cursor_x = 39;
                         cursor_y--;
                         if (cursor_y < 0)
                            cursor_y = 0;
                      }
                      break;

         case 0x18D : /* Cursor right */
                      cursor_x++;
                      if (cursor_x > 39)
                      {
                         cursor_x = 0;
                         cursor_y++;
                         if (cursor_y > 24)
                            cursor_y = 24;
                      }
                      break;

         case 0x18F : /* Cursor up */
                      cursor_y--;
                      if (cursor_y < 0)
                         cursor_y = 0;
                      break;

         case 0x18E : /* Cursor down */
                      cursor_y++;
                      if (cursor_y > 24)
                         cursor_y = 24;
                      break;

         case 0x19F : /* Page up */
                      cursor_y = 0;
                      break;

         case 0x19E : /* Page down */
                      cursor_y = 24;
                      break;

         case 0x1CD : /* Insert */
                      insert_col();
                      break;

         case 0x1ED : /* ^Insert */
                      delete_col();
                      break;

         case 0x18B : /* End - Bottom of list */
                      cursor_x = 39;
                      cursor_y = 24;
                      break;

         case 0x1AC : /* ^Left */
                      cursor_x = 0;
                      break;

         case 0x1AD : /* ^Right */
                      cursor_x = 39;
                      break;

         case 0x1AE : /* ^Down */
                      cursor_y = 24;
                      break;

         case 0x1AF : /* ^Up */
                      cursor_y = 0;
                      break;

         case 0x19C : /* ShLeft */
                      goto_lastword();
                      break;

         case 0x19D : /* ShRight */
                      goto_nextword();
                      break;

         case 0x18A : /* Tab */
                      goto_nexttab();
                      break;

         case 0x19A : /* ShTab */
                      goto_lasttab();
                      break;

         default    : // Pass anything else on...
                      Wimp_ProcessKey(key);		  /* i.e. F12 */
    	   	      break;
      }
   }

   caret = TRUE;
   frame_refreshdisplay();
   redraw_viewer();

   return TRUE;
}



BOOL generic_keyhandler(event_pollblock *event, void *reference)
{
   /* Handles key presses in windows */

   int icon = -1;
   int key  = -1;
   window_handle win = NULL;

   reference = reference;

   icon = event->data.mouse.icon;
   key = event->data.key.code;
   win = event->data.key.caret.window;

   if (key == 13)
   {

      if (win == frameinfo_window)
      {
         frameinfo_update();
      }

      if (win == saveframe_window)
      {
         save_frame();
      }
   }
   else
   {
      if (win != saveframe_window)
         Wimp_ProcessKey(key);
   }

   return TRUE;
}



void panel_move(int rightpos, int toppos)
{
   /* Calculate where the panel is supposed to go, and deal with it...
      I would ideally like the pane to behave like the !Draw pane, but I have
      a feeling it would flicker something chronic. */

   window_state windatum;
   window_outline winoutl;
   int width;
   int height;

   // RECALCULATE RIGHT POSITION:
   winoutl.window = viewer_window;
   Wimp_GetWindowOutline(&winoutl);
   rightpos = winoutl.screenrect.max.x;
   if (rightpos < 960)
      rightpos = 960; // don't let it wander behind the viewer...

   // Now reposition the panel using the co-ordinates given.
   Wimp_GetWindowState(panel_window, &windatum);
   width = windatum.openblock.screenrect.max.x - windatum.openblock.screenrect.min.x;
   height = windatum.openblock.screenrect.max.y - windatum.openblock.screenrect.min.y;
   // if (rightpos > (width/2) ) rightpos = (width/2);  <-- no point, panel is UNDER viewer
   windatum.openblock.screenrect.min.x = rightpos;
   windatum.openblock.screenrect.max.x = rightpos + width;
   windatum.openblock.screenrect.max.y = toppos;
   windatum.openblock.screenrect.min.y = toppos - height;
   windatum.openblock.behind = viewer_window;
   Wimp_OpenWindow(&windatum.openblock); // open panel BEHIND viewer window in stack
   Wimp_GetWindowState(viewer_window, &windatum);
   windatum.openblock.behind = panel_window;
   Wimp_OpenWindow(&windatum.openblock); // reopen viewer BEHIND panel; thus swapping the order
   viewer_is_open = TRUE;

   return;
}



BOOL save_message(event_pollblock *event, void *reference)
{
   /* Deals with save related messages... */

   reference = reference;

   switch(event->data.message.header.action)
   {
      case message_DATASAVE :
         if (event->data.message.header.sender != event_taskhandle)
         {
            strcpy(event->data.message.data.datasave.leafname, "<Wimp$Scrap>");
            event->data.message.data.datasave.estsize = -1;
            event->data.message.header.yourref = event->data.message.header.myref;
            event->data.message.header.action = message_DATASAVEACK;
            event->data.message.header.size = 60;
            Wimp_SendMessage(event_USERMESSAGE, &event->data.message, event->data.message.header.sender, 0);
         }
         return(TRUE);
         break;

      case message_DATASAVEACK :
         event->data.message.data.datasaveack.estsize = 1024;
         event->data.message.header.yourref = event->data.message.header.myref;
         event->data.message.header.action = message_DATALOAD;
         Wimp_SendMessage(event_USERMESSAGERECORDED, &event->data.message, event->data.message.data.datasaveack.window, event->data.message.data.datasaveack.icon);
         Icon_SetText(saveframe_window, 7, event->data.message.data.datasaveack.filename);
         Wimp_CreateMenu((menu_ptr) -1, 0, 0); /* remove any menu from screen */
         save_frame();
         return(TRUE);
         break;
   }

   return(FALSE);
}



BOOL dataloader(event_pollblock *event, void *reference)
{
   /* Loads a file dragged to us. */
   char tmppath[256] = "";
   int  filetype = 0;
   int  reply = 0;

   if ( strlen(ttxpath) != 0 )
   {
      if ( changed )
      {
         Error_Report(1, "The frame you are currently editing has not yet been saved. Please save (or 'remove') it before loading in a new frame...");
         return TRUE;
      }
      // else... load it in.
   }

   /* Copy input file name. */
   strcpy(tmppath, ttxpath);
   strcpy(ttxpath, event->data.message.data.dataload.filename);
   /* And file type */
   filetype = event->data.message.data.dataload.filetype;

   switch (filetype)
   {
      case 0x112 : /* My !Teletext, Ground Control Teletext */
                   blank_frame();
                   if (load_frame() == FALSE)
                   {
                      strcpy(ttxpath, tmppath);
                      return TRUE;
                   }
                   break;

      case 0x80B : /* CC's teletext software */
                   blank_frame();
                   if (load_frame() == FALSE)
                   {
                      strcpy(ttxpath, tmppath);
                      return TRUE;
                   }
                   break;

      case 0xB12 : /* Octopus' Teletext+ */
                   blank_frame();
                   if (load_frame() == FALSE)
                   {
                      strcpy(ttxpath, tmppath);
                      return TRUE;
                   }
                   break;

      case 0x000 : /* Untyped file */
      case 0xBBC : /* Should be BBC ROM, but you never know... */
      case 0xFFD : /* Data (generic no-other-type) [we save MODE 7 data as...data] */
                   // Possibly MODE 7 data
                   blank_frame();
                   if (load_frame() == FALSE)
                   {
                      strcpy(ttxpath, tmppath);
                      return TRUE;
                   }
                   break;

      case 0xFF9 : /* sprite :-) */
                   reply = convert_from_sprite();

                   if (reply == FALSE)
                   {
                      // a failure we can't recover from...
                      Error_Report(1, "Sorry, unable to load type &%03X files...", filetype);
                      strcpy(ttxpath, tmppath);
                      return TRUE;
                      break;
                   }

                   if (reply == -2)
                   {
                      // we oughta be able to deal with this...
                      // try converting it down...
                      if (convert_invalid_sprite(FALSE) == TRUE)
                      {
                         // try a reload
                         if (convert_from_sprite() == TRUE)
                         {
                            strcpy(ttxpath, tmppath);
                            return TRUE; // all went okay, skip the error message...
                         }
                         // else it failed on reload, give up NOW
                      }
                      Error_Report(1, "Sorry, unable to load type &%03X files...", filetype);
                   }
                   strcpy(ttxpath, tmppath);
                   load_sprite_patchup();
                   return TRUE;
                   break;

      case 0xC85 : /* JPEG :-) */
                   // try converting it down...
                   if (convert_invalid_sprite(TRUE) == TRUE)
                   {
                      // try a reload
                      if (convert_from_sprite() == TRUE)
                      {
                         strcpy(ttxpath, tmppath);
                         return TRUE; // all went okay, skip the error message...
                      }
                      // else it failed on reload, give up NOW
                   }
                   Error_Report(1, "Sorry, unable to load type &%03X files...", filetype);
                   strcpy(ttxpath, tmppath);
                   load_sprite_patchup();
                   return TRUE;
                   break;

      default    : /* Ignore all other files */
                   Error_Report(1, "Sorry, unable to load type &%03X files...", filetype);
                   strcpy(ttxpath, tmppath);
                   return TRUE;
                   break;
   }

   editor_settitle(FALSE);

   if (drawmode > 4)
      drawmode -= 4;

   return TRUE;
}



BOOL iconbar_handler(event_pollblock *event, void *reference)
{
   reference = reference;
   int masterright;
   int mastertop;
   window_state windatum;
   caret_block carrot;

   if (event->data.mouse.button.data.select)
   {
      if ( strlen(ttxpath) == 0 )
      {
         blank_frame();
         changed = FALSE;
         strcpy(ttxpath, "<TTXeditor$Dir>.temp");
         Icon_SetText(saveframe_window, 7, ttxpath);
         editor_settitle(FALSE);
         cursor_x = 0;
         cursor_y = 0;
         if (drawmode > 4)
            drawmode -= 4;
         frame_refreshdisplay();
      }
      else
      {
         Window_Show(panel_window, open_WHEREVER); // panel MUST be opened first...
         open_viewer();
         Wimp_GetWindowState(viewer_window, &windatum);
         masterright = windatum.openblock.screenrect.max.x;
         mastertop = windatum.openblock.screenrect.max.y;
         panel_move(masterright, mastertop);
         viewer_is_open = TRUE;
         carrot.window = viewer_window;
         carrot.icon = -1;
         carrot.offset.x = -256; // don't show
         carrot.offset.y = 256;  // the carrot
         carrot.height   = -1;
         carrot.index    = -1;
         Wimp_SetCaretPosition(&carrot);
      }
   }

   if (event->data.mouse.button.data.menu)
      Menu_Show(main_menu, event->data.mouse.pos.x, -1);

   return TRUE;
}



BOOL frameinfo_clickhandler(event_pollblock *event, void *reference)
{
   /* Handles mouse clicks on the 'frameinfo' window */

   int icon = -1;

   reference = reference;

   icon = event->data.mouse.icon;

   if ((event->data.mouse.button.data.select) || (event->data.mouse.button.data.adjust))
   {
      icon = event->data.mouse.icon;

      switch(icon)
      {
         case 22 : // page language
                   Menu_Show(lang_menu, event->data.mouse.pos.x, event->data.mouse.pos.y);
                   lang_type = 22;
                   break;

         case 25 : // header language
                   Menu_Show(lang_menu, event->data.mouse.pos.x, event->data.mouse.pos.y);
                   lang_type = 25;
                   break;

         case 30 : // Okay
                   frameinfo_update();
                   break;

         case 31 : // Cancel
                   Window_Hide(frameinfo_window);
                   frameinfo_refresh();
                   break;
      }
   }

   return TRUE;
}



BOOL mosaicpanel_clickhandler(event_pollblock *event, void *reference)
{
   /* Handles mouse clicks on the 'mosaicpanel' window */

   int icon = -1;
   int masterright;
   int mastertop;
   window_state windatum;

   reference = reference;

   icon = event->data.mouse.icon;

   if ((event->data.mouse.button.data.select) || (event->data.mouse.button.data.adjust))
   {
      icon = event->data.mouse.icon;

      switch(icon)
      {
         case  0 : // Text options
                   drawmode = 0;
                   Window_Hide(panel_window);
                   panel_window = textpanel_window;
                   Window_Show(panel_window, open_WHEREVER);
                   Wimp_GetWindowState(viewer_window, &windatum);
                   masterright = windatum.openblock.screenrect.max.x;
                   mastertop = windatum.openblock.screenrect.max.y;
                   panel_move(masterright, mastertop);
                   mosaicmode = FALSE;
                   drawmode = 0;
                   break;

         case  1 : // mosaic options
                   // (do what - open ourselves again?)
                   break;

         case  2 : // Black (mosaic)
         case  3 : // Red (mosaic)
         case  4 : // Green (mosaic)
         case  5 : // Yellow (mosaic)
         case  6 : // Blue (mosaic)
         case  7 : // Magenta (mosaic)
         case  8 : // Cyan (mosaic)
         case  9 : // White (mosaic)
                   frame_writebyte( (icon + 14) );
                   last_colour = (icon - 2);
                   break;

         case 10 : // flash
         case 11 : // steady
                   frame_writebyte( (icon - 2) );
                   break;

         case 12 : // double on
                   frame_writebyte(13);
                   break;

         case 13 : // double off
                   frame_writebyte(12);
                   break;

         case 14 : // separated
                   frame_writebyte(26);
                   break;

         case 15 : // contiguous
                   frame_writebyte(25);
                   break;

         case 16 : // black bg.
         case 17 : // new bg.
         case 18 : // hold
         case 19 : // release
                   frame_writebyte( (icon + 12) );
                   break;

         case 20 : // show codes
                   showcodes = showcodes ^ TRUE;
                   Menu_SetFlags(viewer_menu, 0, showcodes, FALSE);
                   frame_refreshdisplay();
                   redraw_viewer();
                   Icon_SetSelect(mosaicpanel_window, 20, showcodes);
                   Icon_SetSelect(textpanel_window, 17, showcodes);
                   break;

         case 21 : // draw line
                   drawmode = 1;
                   mosaic_setvalidation();
                   break;

         case 22 : // draw rectangle
                   drawmode = 2;
                   mosaic_setvalidation();
                   break;

         case 23 : // draw circle
                   Error_Report(1, "Sorry, this facility has not yet been implemented.");
                   break;

         case 24 : // draw point
                   drawmode = 4;
                   mosaic_setvalidation();
                   break;

         case 25 : // show grid
                   showgrid = showgrid ^ TRUE;
                   Menu_SetFlags(viewer_menu, 1, showgrid, FALSE);
                   frame_refreshdisplay();
                   redraw_viewer();
                   Icon_SetSelect(mosaicpanel_window, 25, showgrid);
                   break;

         case 26 : // conceal
                   frame_writebyte(24);
                   break;

         default : // ignored
                   break;
      }
   }

   return TRUE;
}



BOOL remark_clickhandler(event_pollblock *event, void *reference)
{
   /* Handles mouse clicks on the 'remark' window */

   int icon = -1;

   reference = reference;

   icon = event->data.mouse.icon;

   if ((event->data.mouse.button.data.select) || (event->data.mouse.button.data.adjust))
   {
      icon = event->data.mouse.icon;

      switch(icon)
      {
         default : Error_Report(1, "Icon %d clicked.", icon);
                   break;
      }
   }

   return TRUE;
}



BOOL saveframe_clickhandler(event_pollblock *event, void *reference)
{
   /* Handles mouse clicks on the 'saveframe' window */

   int  icon = -1;
   char fileicon[16] = "file_xxx";

   reference = reference;

   icon = event->data.mouse.icon;

   if ((event->data.mouse.button.data.select) || (event->data.mouse.button.data.adjust))
   {
      icon = event->data.mouse.icon;

      switch(icon)
      {
         case  1 : // teletext
         case  2 : // sprite
         case  3 : // mode 7
                   export_format = (icon - 1);
                   Icon_SetSelect(saveframe_window, 1, FALSE);
                   Icon_SetSelect(saveframe_window, 2, FALSE);
                   Icon_SetSelect(saveframe_window, 3, FALSE);
                   Icon_SetSelect(saveframe_window, icon, TRUE);
                   switch (export_format)
                   {
                      case  0 : /* Teletext */
                                strcpy(fileicon, "file_112");
                                break;

                      case  1 : /* Sprite */
                                strcpy(fileicon, "file_ff9");
                                break;

                      case  2 : /* MODE 7 */
                                strcpy(fileicon, "file_ffd");
                                break;
                   }
                   saveframe_setupicon(fileicon);
                   break;

         case  5 : // Okay button
                   save_frame();
                   break;

         case  6 : // draggy...
                   switch (export_format)
                   {
                      case  0 : /* Teletext */
                                strcpy(fileicon, "file_112");
                                break;

                      case  1 : /* Sprite */
                                strcpy(fileicon, "file_ff9");
                                break;

                      case  2 : /* MODE 7 */
                                strcpy(fileicon, "file_ffd");
                                break;
                   }
                   export_verifyicon(fileicon);
                   begin_drag(saveframe_window, 6, fileicon);
                   break;
      }
   }

   return TRUE;
}



BOOL textpanel_clickhandler(event_pollblock *event, void *reference)
{
   /* Handles mouse clicks on the 'textpanel' window */

   int icon = -1;
   int masterright;
   int mastertop;
   window_state windatum;

   reference = reference;

   icon = event->data.mouse.icon;

   if ((event->data.mouse.button.data.select) || (event->data.mouse.button.data.adjust))
   {
      icon = event->data.mouse.icon;

      switch(icon)
      {
         case  0 : // text options
                   // (do what - open ourselves again?)
                   break;

         case  1 : // Mosaic options
                   drawmode = 0;
                   Window_Hide(panel_window);
                   panel_window = mosaicpanel_window;
                   Window_Show(panel_window, open_WHEREVER);
                   Wimp_GetWindowState(viewer_window, &windatum);
                   masterright = windatum.openblock.screenrect.max.x;
                   mastertop = windatum.openblock.screenrect.max.y;
                   panel_move(masterright, mastertop);

                   mosaicmode = TRUE;
                   drawmode = 4; // default to point plotter
                   mosaic_setvalidation();

                   break;

         case  2 : // Black (text)
         case  3 : // Red (text)
         case  4 : // Green (text)
         case  5 : // Yellow (text)
         case  6 : // Blue (text)
         case  7 : // Magenta (text)
         case  8 : // Cyan (text)
         case  9 : // White (text)
                   frame_writebyte( (icon - 2) );
                   break;

         case 10 : // flash
         case 11 : // steady
                   frame_writebyte( (icon - 2) );
                   break;

         case 12 : // double on
                   frame_writebyte(13);
                   break;

         case 13 : // double off
                   frame_writebyte(12);
                   break;

         case 14 : // black bg.
         case 15 : // new bg.
                   frame_writebyte( (icon + 14) );
                   break;

         case 16 : // conceal
                   frame_writebyte(24);
                   break;

         case 17 : // show codes
                   showcodes = showcodes ^ TRUE;
                   Menu_SetFlags(viewer_menu, 0, showcodes, FALSE);
                   frame_refreshdisplay();
                   redraw_viewer();
                   Icon_SetSelect(mosaicpanel_window, 20, showcodes);
                   Icon_SetSelect(textpanel_window, 17, showcodes);
                   break;

         default : // ignored
                   break;
      }
   }


   return TRUE;
}



BOOL viewer_clickhandler(event_pollblock *event, void *reference)
{
   /* Handles mouse clicks on the 'viewer' window */

   int xposraw = 0; // raw X,Y as offset co-ordinates
   int yposraw = 0;
   int xpostxt = 0; // X,Y as row/col co-ordinates
   int ypostxt = 0;
   int xpospix = 0; // X,Y as ttx-pixel co-ordinates (2x6 mosiac grid)
   int ypospix = 0;
   int x = event->data.mouse.pos.x;
   int y = event->data.mouse.pos.y;
   window_state wstate;
   caret_block carrot;
   int byte = 0;

   reference = reference;

   /* Work out our position */
   Wimp_GetWindowState(viewer_window, &wstate);
   xposraw = abs((wstate.openblock.screenrect.min.x - x));
   yposraw = (wstate.openblock.screenrect.max.y - y);
   xpostxt = ((xposraw / CHAR_WIDTH)  + 1); // 40 columns
   ypostxt = ((yposraw / CHAR_HEIGHT) + 1); // 25 rows
   xpospix = ((xposraw / 12) + 1); // 80 mosaic 'dots' horizontally
   ypospix = ((yposraw / 12) + 1); // 75 mosaic 'dots' vertically

   // just in case...
   if (xpostxt > 40) xpostxt = 40;
   if (ypostxt > 25) ypostxt = 25;
   if (xpospix > 80) xpospix = 80;
   if (ypospix > 75) ypospix = 75;

#ifdef DEBUG
   debug("VIEWER CLICK:\n");
   debug("  >RAW position %d, %d\n", xposraw, yposraw);
   debug("  Text position %d, %d\n", (xpostxt - 1), (ypostxt - 1));
   debug("  Grfx position %d, %d\n", (xpospix - 1), (ypospix - 1));
#endif

   if (event->data.mouse.button.data.select)
   {
      if (!mosaicmode)
      {
         // TEXT mode - set cursor
         cursor_x = (xpostxt - 1);
         cursor_y = (ypostxt - 1);
         carrot.window = viewer_window;
         carrot.icon = -1;
         carrot.offset.x = -256; // don't show
         carrot.offset.y = 256;  // the carrot
         carrot.height   = -1;
         carrot.index    = -1;
         Wimp_SetCaretPosition(&carrot);
         caret = TRUE;
         frame_refreshdisplay();
         redraw_viewer();
      }
      else
      {
         // MOSAIC mode
         switch (drawmode)
         {
            case 1 : // line begin point
                     gspoint_x = (xpospix - 1);
                     gspoint_y = (ypospix - 1);
                     drawmode = (1 + 4);
                     break;

            case 2 : // rectangle begin point
                     gspoint_x = (xpospix - 1);
                     gspoint_y = (ypospix - 1);
                     drawmode = (2 + 4);
                     break;

            case 3 : // circle begin point
                     break;

            case 4 : // point plot
                     graphics_plotpoint((xpospix - 1), (ypospix - 1), TRUE);
                     break;

            case 5 : // line end point
                     gfpoint_x = (xpospix - 1);
                     gfpoint_y = (ypospix - 1);
                     graphics_plotline(TRUE);
                     drawmode = 1;
                     break;

            case 6 : // rectangle end point
                     gfpoint_x = (xpospix - 1);
                     gfpoint_y = (ypospix - 1);
                     graphics_plotrectangle(TRUE);
                     drawmode = 2;
                     break;

            case 7 : // circle end point
                     break;

            case 8 : // point end - shouldn't ever happen!
                     break;
         }

         frame_refreshdisplay();
         redraw_viewer();
      }
   }

   if (event->data.mouse.button.data.menu)
      Menu_Show(viewer_menu, event->data.mouse.pos.x, event->data.mouse.pos.y);

   if (event->data.mouse.button.data.adjust)
   {
      if (!mosaicmode)
      {
         // TEXT mode - tell user what the character is...
         byte = frame.data.bytes[ (((ypostxt - 1) * 40) + (xpostxt - 1)) ];
         byte = ((byte | 0x80) - 0x80);
         switch (byte)
         {
            case  0 : Error_Report(1, "Alphanumeric black");
                      break;
            case  1 : Error_Report(1, "Alphanumeric red");
                      break;
            case  2 : Error_Report(1, "Alphanumeric green");
                      break;
            case  3 : Error_Report(1, "Alphanumeric yellow");
                      break;
            case  4 : Error_Report(1, "Alphanumeric blue");
                      break;
            case  5 : Error_Report(1, "Alphanumeric magenta");
                      break;
            case  6 : Error_Report(1, "Alphanumeric cyan");
                      break;
            case  7 : Error_Report(1, "Alphanumeric white");
                      break;
            case  8 : Error_Report(1, "Flash");
                      break;
            case  9 : Error_Report(1, "Steady");
                      break;
            case 10 : Error_Report(1, "End box");
                      break;
            case 11 : Error_Report(1, "Start box (boxing is used to insert text into a television picture, like the Newsflash frames)");
                      break;
            case 12 : Error_Report(1, "Normal height");
                      break;
            case 13 : Error_Report(1, "Double height");
                      break;
            case 14 : Error_Report(1, "No action (double width in Level 2)");
                      break;
            case 15 : Error_Report(1, "No action (double height and width in Level 2)");
                      break;
            case 16 : Error_Report(1, "Mosaic black");
                      break;
            case 17 : Error_Report(1, "Mosaic red");
                      break;
            case 18 : Error_Report(1, "Mosaic green");
                      break;
            case 19 : Error_Report(1, "Mosaic yellow");
                      break;
            case 20 : Error_Report(1, "Mosaic blue");
                      break;
            case 21 : Error_Report(1, "Mosaic magenta");
                      break;
            case 22 : Error_Report(1, "Mosaic cyan");
                      break;
            case 23 : Error_Report(1, "Mosaic white");
                      break;
            case 24 : Error_Report(1, "Conceal (display as spaces until user requests 'reveal')");
                      break;
            case 25 : Error_Report(1, "Contiguous graphics (mosaic 'pixels' adjoin one another)");
                      break;
            case 26 : Error_Report(1, "Separated graphics (each mosaic 'pixel' is surrounded by the current background colour)");
                      break;
            case 27 : Error_Report(1, "No action (Control Sequence Introducer in Level 2)");
                      break;
            case 28 : Error_Report(1, "Set background to black");
                      break;
            case 29 : Error_Report(1, "Set background to last colour defined (white by default)");
                      break;
            case 30 : Error_Report(1, "Hold (hold the last mosaic character defined across a control code space)");
                      break;
            case 31 : Error_Report(1, "Release (undoes effect of 'hold')");
                      break;
            case 127: Error_Report(1, "ASCII code %d (character 'Backspace')", byte);
                      break;
            default : Error_Report(1, "ASCII code %d (character '%c')", byte, byte);
                      break;
         } // end what-is-it switch
      } // end text mode stuff
      else
      {
         // MOSAIC mode : ADJUST 'unplots'
         switch (drawmode)
         {
            case 1 : // line begin point
                     gspoint_x = (xpospix - 1);
                     gspoint_y = (ypospix - 1);
                     drawmode = (1 + 4);
                     break;

            case 2 : // rectangle begin point
                     gspoint_x = (xpospix - 1);
                     gspoint_y = (ypospix - 1);
                     drawmode = (2 + 4);
                     break;

            case 3 : // circle begin point
                     break;

            case 4 : // point plot
                     graphics_plotpoint((xpospix - 1), (ypospix - 1), FALSE);
                     break;

            case 5 : // line end point
                     gfpoint_x = (xpospix - 1);
                     gfpoint_y = (ypospix - 1);
                     graphics_plotline(FALSE);
                     drawmode = 1;
                     break;

            case 6 : // rectangle end point
                     gfpoint_x = (xpospix - 1);
                     gfpoint_y = (ypospix - 1);
                     graphics_plotrectangle(FALSE);
                     drawmode = 2;
                     break;

            case 7 : // circle end point
                     break;

            case 8 : // point end - shouldn't ever happen!
                     break;
         }
      } // end mosaic mode stuff
   } // end ADJUST handler

   return TRUE;
}



BOOL saveframe_keyhandler(event_pollblock *event, void *reference)
{
   /* Handles key presses on the 'saveframe' window - TO BE IMPLEMENTED */

   int icon = -1;
   int key  = -1;

   reference = reference;

   icon = event->data.mouse.icon;
   key = event->data.key.code;

   Error_Report(1, "Keypress %d in icon %d.", key, icon);

   /* You should pass all keys NOT recognised by you to:
         Wimp_ProcessKey(key);
      So that mechanisms like 'F12 for CLI' still work whilst
      you have input focus. */

   return TRUE;
}



void begin_drag(window_handle window, icon_handle icon, char *iconname)
{
   /* Taken from FetchMail. */

   int solid;
   wimp_rect bound;
   int block[20];
   int winminx, winminy, winmaxx, winmaxy;
   drag_block draginfo;

   /* Solid drag? */
   OS_Byte(osbyte_READCMOSRAM, 28, 0, NULL, &solid);

   block[0] = window;
   r.r[1] = (int) block;
   _kernel_swi( SWI_XOS_Bit | 0x0400CB, &r, &r ); /* Wimp_GetWindowState */
   winminx = block[1];
   winminy = block[2];
   winmaxx = block[3];
   winmaxy = block[4];

   block[0] = window;
   block[1] = icon;
   r.r[1] = (int) block;
   _kernel_swi( SWI_XOS_Bit | 0x0400CE, &r, &r ); /* Wimp_GetIconState */

   bound.min.x = winminx + block[2];
   bound.max.x = winminx + block[4];
   bound.min.y = winminy + block[3] + (winmaxy - winminy);
   bound.max.y = winminy + block[5] + (winmaxy - winminy);

   if ((solid & 2) == 2)
   {
      DragASprite_Start(0xC5, (void *) 1, iconname, &bound, (wimp_rect *) 0);
   }
   else
   {
      draginfo.window            = 0;
      draginfo.type              = drag_FIXEDBOX;
      draginfo.screenrect        = bound;
      draginfo.parent.min.x      = 0;
      draginfo.parent.min.y      = 0;
      draginfo.parent.max.x      = 10000;
      draginfo.parent.max.y      = 10000;
      Wimp_DragBox( &draginfo);
   }

   return;
}



BOOL finish_drag(event_pollblock *event, void *reference)
{
   /* Tidies up the drag action. */
   int  solid;
   mouse_block ptr;
   char export_file[256];
   char filenm[12];

   reference = reference;

   OS_Byte(osbyte_READCMOSRAM, 28, 0, NULL, &solid);

   if ((solid & 2) == 2)
      DragASprite_Stop();
   else
      Wimp_DragBox((drag_block *) -1);

   Wimp_GetPointerInfo(&ptr);

   event->data.message.header.size            = 80;
   event->data.message.header.yourref         = 0;
   event->data.message.header.action          = message_DATASAVE;
   event->data.message.data.datasave.window   = ptr.window;
   event->data.message.data.datasave.icon     = ptr.icon;
   event->data.message.data.datasave.pos      = ptr.pos;
   event->data.message.data.datasave.estsize  = 1024;
   event->data.message.data.datasave.filetype = 0xfff;

   Icon_GetText(saveframe_window, 7, export_file);
   extract_filename(export_file, filenm);
   strcpy(event->data.message.data.datasave.leafname, filenm);

   Wimp_SendMessage(event_SENDWANTACK, &event->data.message, (message_destinee) ptr.window, ptr.icon);

  return(TRUE);
}



void extract_filename(char *in, char *out)
{
   /* Extract the filename from the path */
   int  len = 0;
   int  pos = 0;
   int  count = 0;

   pos = strlen(in);
   len = strlen(in);

   do                        /* Scan from end looking for period */
   {
      pos--;
   } while ((in[pos] != '.') &&  (pos > 0));

   if (pos == 0)             /* No period? Copy input as output and return */
   {
      strcpy(out, in);
      return;
   }

   pos++;                    /* Move up past the period */

   for (;pos <= len; pos++)  /* ...and copy as output */
   {
      out[count] = in[pos];
      count++;
   }
   out[count] = '\0';

   return;
}



void blank_frame(void)
{
   int  loop = 0;

   // Teletext 'frame' should be all spaces
   for (loop = 0; loop < 1040; loop++)
      frame.rawdata[loop] = ' ';
   // Control data blanked to null bytes
   for (loop = 1040; loop < 1080; loop++)
      frame.rawdata[loop] = 0;

   frame.data.link_red    = (0x100 << 16);
   frame.data.link_green  = (0x100 << 16);
   frame.data.link_yellow = (0x100 << 16);
   frame.data.link_cyan   = (0x100 << 16);
   frame.data.link_cat    = (0x100 << 16);
   frame.data.link_index  = (0x100 << 16);
   frame.data.link_status = (0x100 << 16);
   frame.data.crc = 0;
   frame.data.frame = 0;
   frame.data.lang_page = 0;   // english
   frame.data.lang_header = 0; // english
   frameinfo_refresh();

   return;
}



int  load_frame(void)
{
   FILE *fp = NULL;
   int length = 0;
   int masterright;
   int mastertop;
   window_state windatum;
   caret_block carrot;

   fp = fopen(ttxpath, "rb");

   // File should be 1024 bytes, or 1080 bytes...
   fseek(fp, 0, SEEK_END);
   length = (int)ftell(fp);
   fseek(fp, 0, SEEK_SET);

   switch (length)
   {
      case 1000 : /* A raw teletext frame (25 lines x 40 columns) */
      case 1024 : /* CC's teletext frames? Basically a 'chapter zero' size. */
      case 1080 : /* GC/my/Octopus teletext frame */
      case 1081 : /* A bugette in my old editor software, oops! */
                  break;

      case 2048 : /* Possibly a raw dump of chapers zero and four - we cannot handle
                     this directly, so we'll assume it's only 1024 bytes... */
                  length = 1024;

      default   : /* Anything else is BAD */
                  fclose(fp);
                  Error_Report(1, "Incorrect file length (should be 1024 or 1080 bytes), is this a valid teletext frame?");
                  strcpy(ttxpath, "");
                  return FALSE;
   }

   if (length == 1081)
      length = 1080;

   fread((void *)&frame.rawdata, length, 1, fp);
   // assume it worked

   fclose(fp);

   if (length != 1080)
   {
      // set up defaults
      frame.data.link_red    = (0x100 << 16);
      frame.data.link_green  = (0x100 << 16);
      frame.data.link_yellow = (0x100 << 16);
      frame.data.link_cyan   = (0x100 << 16);
      frame.data.link_cat    = (0x100 << 16);
      frame.data.link_index  = (0x100 << 16);
      frame.data.link_status = (0x100 << 16);
      frame.data.crc = 0;
      frame.data.frame = 0; // we don't know it and can't determine it from only-page-data
      frame.data.lang_page = 0;   // english
      frame.data.lang_header = 0; // english
   }

   frame_refreshdisplay();

   Window_Show(panel_window, open_WHEREVER); // panel must be opened first
   open_viewer();
   Wimp_GetWindowState(viewer_window, &windatum);
   masterright = windatum.openblock.screenrect.max.x;
   mastertop = windatum.openblock.screenrect.max.y;
   panel_move(masterright, mastertop);
   viewer_is_open = TRUE;

   redraw_viewer();

   frameinfo_refresh();

   carrot.window = viewer_window;
   carrot.icon = -1;
   carrot.offset.x = -256; // don't show
   carrot.offset.y = 256;  // the carrot
   carrot.height   = -1;
   carrot.index    = -1;
   Wimp_SetCaretPosition(&carrot);

   Icon_SetText(saveframe_window, 7, ttxpath);

   return TRUE;
}



void frame_refreshdisplay(void)
{
   // Refresh the sprite used to display this frame...

   int  address = 0;
   int  *value = NULL;
   unsigned char *savearea = NULL;
   int  return_0, return_1, return_2, return_3;
   mouse_block ptr;
   window_outline winoutl;

   // Get address of teletext frame sprite
   r.r[0] = (256 + 24);
   r.r[1] = (int)resource_sprites;
   r.r[2] = (int)"ttx_frame";
   _kernel_swi(SWI_OS_SpriteOp, &r, &r);

   // Work out where the (raw) sprite data begins
   address = r.r[2];
   value = (int *)address;
   address = address + value[9]; // address = address + offset to ACTUAL sprite data

   // Perform surgical operations
   r.r[0] = (int)&frame.rawdata;
   r.r[1] = (int)address;
   r.r[2] = 1;
   _kernel_swi(SWI_TTX_ConvertPage, &r, &r);

   // Redirect VDU output to sprite
   r.r[0] = (256 + 62);
   r.r[1] = (int)resource_sprites;
   r.r[2] = (int)"ttx_frame";
   _kernel_swi(SWI_OS_SpriteOp, &r, &r);
   savearea = calloc(1, r.r[3]);

   r.r[0] = (256 + 60);
   r.r[1] = (int)resource_sprites;
   r.r[2] = (int)"ttx_frame";
   r.r[3] = (int)savearea;
   _kernel_swi(SWI_OS_SpriteOp, &r, &r);
   return_0 = r.r[0];
   return_1 = r.r[1];
   return_2 = r.r[2];
   return_3 = r.r[3];

   // If codes, plot 'em.
   if (showcodes)
      frame_addcodes();

   // If grid, plot it.
   if (showgrid)
      frame_addgrid();

   // if draw mode, paste in the draw points
   if (mosaicmode)
   {
      winoutl.window = viewer_window;
      Wimp_GetWindowOutline(&winoutl);
      Wimp_GetPointerInfo(&ptr);
      switch (drawmode)
      {
         case 1 : // line start, nothing to plot
                  break;

         case 2 : // rectangle start, nothing to plot
                  break;

         case 3 : // circle start, nothing to plot
                  break;

         case 4 : // point, nothing to plot
                  break;

         case 5 : // line end, draw marker
                  GFX_GCOL(3, 14); // EOR orange
                  GFX_Move((gspoint_x * 12), (900 - (gspoint_y * 12)));
                  GFX_Draw((ptr.pos.x - winoutl.screenrect.min.x), \
                           (ptr.pos.y - winoutl.screenrect.min.y));
                  break;

         case 6 : // rectangle end, draw rect. marker
                  GFX_GCOL(3, 14); // EOR orange
                  GFX_RectangleFill((gspoint_x * 12), (900 - (gspoint_y * 12)), \
                                    ((ptr.pos.x-winoutl.screenrect.min.x) - (gspoint_x * 12)),\
                                    ((ptr.pos.y-winoutl.screenrect.min.y) - \
                                     (900-(gspoint_y * 12))) );
                  break;
      }
   }


   // If caret, plot it
   if (caret)
   {
      GFX_GCOL(0, 14); // orange
      GFX_RectangleFill( ((cursor_x * CHAR_WIDTH) + 1), (((24 - cursor_y) * CHAR_HEIGHT) + 1), \
                        (CHAR_WIDTH - 2), (CHAR_HEIGHT - 2));
      if (caret_ours)
         caret = FALSE;
   }
   else
   {
      caret = TRUE; // for next time...
   }

   // Undirect VDU output from sprite
   r.r[0] = return_0;
   r.r[1] = return_1;
   r.r[2] = return_2;
   r.r[3] = return_3;
   _kernel_swi(SWI_OS_SpriteOp, &r, &r);
   free(savearea);

   return;
}



void frame_addcodes(void)
{
   int  xpos = 0;
   int  ypos = 0;
   int  chrp = 0;
   int  xoff = 0;
   int  yoff = 0;
   int  byte = 0;
   char spri[16] = "";

   for (ypos = 0; ypos < 26; ypos++)
   {
      for (xpos = 0; xpos < 40; xpos++)
      {
         chrp = ((ypos * 40) + xpos);
         byte = frame.data.bytes[chrp];
         byte = ((byte | 0x80) - 0x80);

         strcpy(spri, "");
         switch (byte)
         {
            case  0 : // Alpha black
                      strcpy(spri, "text_black");
                      break;

            case  1 : // Alpha red
                      strcpy(spri, "text_red");
                      break;

            case  2 : // Alpha green
                      strcpy(spri, "text_green");
                      break;

            case  3 : // Alpha yellow
                      strcpy(spri, "text_yellow");
                      break;

            case  4 : // Alpha blue
                      strcpy(spri, "text_blue");
                      break;

            case  5 : // Alpha magenta
                      strcpy(spri, "text_magenta");
                      break;

            case  6 : // Alpha cyan
                      strcpy(spri, "text_cyan");
                      break;

            case  7 : // Alpha white
                      strcpy(spri, "text_white");
                      break;

            case  8 : // Flash
                      strcpy(spri, "flash_on");
                      break;

            case  9 : // Steady
                      strcpy(spri, "flash_off");
                      break;

            case 10 : // End box
                      strcpy(spri, "box_off");
                      break;

            case 11 : // Start box
                      strcpy(spri, "box_on");
                      break;

            case 12 : // Normal height
                      strcpy(spri, "double_off");
                      break;

            case 13 : // Double height
                      strcpy(spri, "double_on");
                      break;

            case 14 : // Undefined (Double width in Level 2)
                      strcpy(spri, "no_action");
                      break;

            case 15 : // Undefined (Double height and width in Level 2)
                      strcpy(spri, "no_action");
                      break;

            case 16 : // Mosaic black
                      strcpy(spri, "mosa_black");
                      break;

            case 17 : // Mosaic red
                      strcpy(spri, "mosa_red");
                      break;

            case 18 : // Mosaic green
                      strcpy(spri, "mosa_green");
                      break;

            case 19 : // Mosaic yellow
                      strcpy(spri, "mosa_yellow");
                      break;

            case 20 : // Mosaic blue
                      strcpy(spri, "mosa_blue");
                      break;

            case 21 : // Mosaic magenta
                      strcpy(spri, "mosa_magenta");
                      break;

            case 22 : // Mosaic cyan
                      strcpy(spri, "mosa_cyan");
                      break;

            case 23 : // Mosaic white
                      strcpy(spri, "mosa_white");
                      break;

            case 24 : // Conceal
                      strcpy(spri, "conceal_on");
                      break;

            case 25 : // Contiguous
                      strcpy(spri, "separate_off");
                      break;

            case 26 : // Separated
                      strcpy(spri, "separate_on");
                      break;

            case 27 : // Undefined (Control Sequence Introducer in Level 2)
                      strcpy(spri, "no_action");
                      break;

            case 28 : // Black background
                      strcpy(spri, "back_black");
                      break;

            case 29 : // New background
                      strcpy(spri, "back_new");
                      break;

            case 30 : // Hold
                      strcpy(spri, "hold_on");
                      break;

            case 31 : // Release
                      strcpy(spri, "hold_off");
                      break;
         }

         if ( strlen(spri) > 0 )
         {
            // We have something to plot...
            // GFX_GCOL(0, 2);
            // GFX_RectangleFill(xoff, yoff, CHAR_WIDTH, CHAR_HEIGHT);
            xoff = (xpos * CHAR_WIDTH);
            yoff = ((24 - ypos) * CHAR_HEIGHT); // text origin is top left; gfx is bottom left
            if (xoff <   0) xoff = 0;
            if (yoff <   0) yoff = 0;
            if (xoff > 936) xoff = 936;
            if (yoff > 864) yoff = 864;
            r.r[0] = (256 + 34);
            r.r[1] = (int)resource_sprites;
            r.r[2] = (int)spri;
            r.r[3] = xoff;
            r.r[4] = yoff;
            r.r[5] = 8;
            _kernel_swi(SWI_OS_SpriteOp, &r, &r);

         }
      } // xpos loop
   } // ypos loop

   return;
}



void frame_addgrid(void)
{
   // Draw a grid...

   // Set colour to mid-grey
   GFX_GCOL(0, 4);

   // Rows
   for (int row = 0; row <= 900; row += CHAR_HEIGHT)
   {
      GFX_Move(0, row);
      GFX_Draw(960, row);
   }

   // Columns
   for (int col = 0; col <= 960; col += CHAR_WIDTH)
   {
      GFX_Move(col, 0);
      GFX_Draw(col, 900);
   }

   return;
}



void open_viewer(void)
{
   // Open viewer window, centred, taking into account the toolpanel width.

   window_state  wstate;
   wimp_point    moveto = {0, 0};
   int           w, wb, h;

   Screen_CacheModeInfo();

   Wimp_GetWindowState(panel_window, &wstate);
   wb = wstate.openblock.screenrect.max.x - wstate.openblock.screenrect.min.x;

   Wimp_GetWindowState(viewer_window, &wstate);
   wstate.openblock.behind = -1; // on top

   if ( wstate.flags.data.open )
   {
      /* Already open, pop it to the top but DO NOT mess with it's position */
      Wimp_OpenWindow(&wstate.openblock);
      return;
   }
   else
   {
      /* Not open, open it centred */

      w = wstate.openblock.screenrect.max.x - wstate.openblock.screenrect.min.x;
      h = wstate.openblock.screenrect.max.y - wstate.openblock.screenrect.min.y;

      moveto.x = (screen_size.x - (w + wb)) / 2;
      moveto.y = (screen_size.y + h) / 2;

      wstate.openblock.screenrect.min.x = moveto.x;
      wstate.openblock.screenrect.max.y = moveto.y;
      wstate.openblock.screenrect.max.x = wstate.openblock.screenrect.min.x + w;
      wstate.openblock.screenrect.min.y = wstate.openblock.screenrect.max.y - h;

      Wimp_OpenWindow(&wstate.openblock);
   }

   return;
}



void frame_writebyte(int byte)
{
   int  newbyte = 0;
   int  chrp = 0;

   // Try to translate it
   newbyte = 0;

   switch (frame.data.lang_page) // we ONLY consider the page language, ATM...
   {
   case 1 : // French
            switch (byte)
            {
               case '' : newbyte =  35;
                          break;
               case '' : newbyte =  36;
                          break;
               case '' : newbyte =  64;
                          break;
               case '' : newbyte =  91;
                          break;
               case '' : newbyte =  92;
                          break;
               case '' : newbyte =  93;
                          break;
               case '' : newbyte =  94;
                          break;
               case '#' : newbyte =  95;
                          break;
               case '' : newbyte =  96;
                          break;
               case '' : newbyte = 123;
                          break;
               case '' : newbyte = 124;
                          break;
               case '' : newbyte = 125;
                          break;
               case '' : newbyte = 126;
                          break;
            }
            break;

   case 2 : // Swedish
            switch (byte)
            {
               case '#' : newbyte =  35;
                          break;
               case '' : newbyte =  36;
                          break;
               case '' : newbyte =  64;
                          break;
               case '' : newbyte =  91;
                          break;
               case '' : newbyte =  92;
                          break;
               case '' : newbyte =  93;
                          break;
               case '' : newbyte =  94;
                          break;
               case '_' : newbyte =  95;
                          break;
               case '' : newbyte =  96;
                          break;
               case '' : newbyte = 123;
                          break;
               case '' : newbyte = 124;
                          break;
               case '' : newbyte = 125;
                          break;
               case '' : newbyte = 126;
                          break;
            }
            break;

   case 4 : // German
            switch (byte)
            {
               case '#' : newbyte =  35;
                          break;
               case '$' : newbyte =  36;
                          break;
               case '' : newbyte =  64;
                          break;
               case '' : newbyte =  91;
                          break;
               case '' : newbyte =  92;
                          break;
               case '' : newbyte =  93;
                          break;
               case '^' : newbyte =  94;
                          break;
               case '_' : newbyte =  95;
                          break;
               case '' : newbyte =  96;
                          break;
               case '' : newbyte = 123;
                          break;
               case '' : newbyte = 124;
                          break;
               case '' : newbyte = 125;
                          break;
               case '' : newbyte = 126;
                          break;
            }
            break;

   case 5 : // Spanish
            switch (byte)
            {
               case '' : newbyte =  35;
                          break;
               case '$' : newbyte =  36;
                          break;
               case '' : newbyte =  64;
                          break;
               case '' : newbyte =  91;
                          break;
               case '' : newbyte =  92;
                          break;
               case '' : newbyte =  93;
                          break;
               case '' : newbyte =  94;
                          break;
               case '' : newbyte =  95;
                          break;
               case '' : newbyte =  96;
                          break;
               case '' : newbyte = 123;
                          break;
               case '' : newbyte = 124;
                          break;
               case '' : newbyte = 125;
                          break;
               case '' : newbyte = 126;
                          break;
            }
            break;

   case 6 : // Italian
            switch (byte)
            {
               case '' : newbyte =  35;
                          break;
               case '$' : newbyte =  36;
                          break;
               case '' : newbyte =  64;
                          break;
               case '' : newbyte =  91;
                          break;
               case '' : newbyte =  92;
                          break;
               //case '' : newbyte =  93;  right arrow
               //           break;
               //case '' : newbyte =  94;  up arrow
               //           break;
               case '#' : newbyte =  95;
                          break;
               case '' : newbyte =  96;
                          break;
               case '' : newbyte = 123;
                          break;
               case '' : newbyte = 124;
                          break;
               case '' : newbyte = 125;
                          break;
               case '' : newbyte = 126;
                          break;
            }
            break;

   default: // English (for everything else)
            switch (byte)
            {
               case '' : newbyte =  35;
                          break;
               case '$' : newbyte =  36;
                          break;
               case '@' : newbyte =  64;
                          break;
               //case '' : newbyte =  91;
               //           break;
               case '' : newbyte =  92;
                          break;
               //case '' : newbyte =  93;
               //           break;
               //case '' : newbyte =  94;
               //           break;
               case '#' : newbyte =  95;
                          break;
               case '' : // 'cos I'm lazy...
               case '' : // it's ALT '-', not just '-'... honest!
               case '' : newbyte =  96;
                          break;
               case '' : newbyte = 123;
                          break;
               case '|' : newbyte = 124;
                          break;
               case '' : newbyte = 125;
                          break;
               case '' : newbyte = 126;
                          break;
            }
            break;
   }

   // Give a message if high-bit-set and not understood...
   if ( (byte > 127) && (newbyte == 0) )
   {
      Error_Report(1, "Sorry, the character '%c' is not available for use. Please check you have configured the correct frame language for National Option Characters; and also consult the Teletext Character set.", byte);
      return;
   }

   // If National Option, swap it in...
   if (newbyte != 0)
      byte = newbyte;

   // Work out where...
   chrp = ((cursor_y * 40) + cursor_x);

   // ...and push it.
   frame.data.bytes[chrp] = byte;

   // Update cursor
   cursor_x++;
   if (cursor_x > 39)
   {
      cursor_x = 0;
      cursor_y++;
      if (cursor_y > 24)
         cursor_y = 24;
   }

   // Force a redraw
   caret = TRUE;
   frame_refreshdisplay();
   redraw_viewer();

   // Finally, flag a change...
   if (changed == FALSE)
      editor_settitle(TRUE);
   changed = TRUE;

   return;
}



void frameinfo_refresh(void)
{
   Icon_printf(frameinfo_window, 19, "%07X", frame.data.frame);
   Icon_printf(frameinfo_window, 29, "%04X", frame.data.crc);
   Icon_printf(frameinfo_window,  3, "%07X", frame.data.link_red);
   Icon_printf(frameinfo_window,  5, "%07X", frame.data.link_green);
   Icon_printf(frameinfo_window,  7, "%07X", frame.data.link_yellow);
   Icon_printf(frameinfo_window,  9, "%07X", frame.data.link_cyan);
   Icon_printf(frameinfo_window, 11, "%07X", frame.data.link_cat);
   Icon_printf(frameinfo_window, 13, "%07X", frame.data.link_index);
   Icon_printf(frameinfo_window, 15, "%07X", frame.data.link_status);

   lang_page = frame.data.lang_page;
   lang_header = frame.data.lang_header;

   frameinfo_refreshlanguage();

   return;
}



void frameinfo_refreshlanguage(void)
{
   switch (lang_page)
   {
      case 0 : Icon_printf(frameinfo_window, 21, "English");
               break;
      case 1 : Icon_printf(frameinfo_window, 21, "French");
               break;
      case 2 : Icon_printf(frameinfo_window, 21, "Swedish");
               break;
      case 3 : Icon_printf(frameinfo_window, 21, "-unassigned-");
               break;
      case 4 : Icon_printf(frameinfo_window, 21, "German");
               break;
      case 5 : Icon_printf(frameinfo_window, 21, "Spanish");
               break;
      case 6 : Icon_printf(frameinfo_window, 21, "Italian");
               break;
      case 7 : Icon_printf(frameinfo_window, 21, "-unassigned-");
               break;
      default: Icon_printf(frameinfo_window, 21, "INVALID");
               break;
   }

   switch (lang_header)
   {
      case 0 : Icon_printf(frameinfo_window, 24, "English");
               break;
      case 1 : Icon_printf(frameinfo_window, 24, "French");
               break;
      case 2 : Icon_printf(frameinfo_window, 24, "Swedish");
               break;
      case 3 : Icon_printf(frameinfo_window, 24, "-unassigned-");
               break;
      case 4 : Icon_printf(frameinfo_window, 24, "German");
               break;
      case 5 : Icon_printf(frameinfo_window, 24, "Spanish");
               break;
      case 6 : Icon_printf(frameinfo_window, 24, "Italian");
               break;
      case 7 : Icon_printf(frameinfo_window, 24, "-unassigned-");
               break;
      default: Icon_printf(frameinfo_window, 24, "INVALID");
               break;
   }

   return;
}



void frameinfo_update(void)
{
   char tmpbuf[16];
   int  frameval = 0;
   int  linkval[7] = {0, 0, 0, 0, 0, 0, 0};
   int  crcval = 0;
   caret_block carrot;

   Icon_GetText(frameinfo_window, 19, tmpbuf);
   sscanf(tmpbuf, "%x", &frameval);
   if ( (frameval != 0) && ( (frameval < 0x0010000) || (frameval > 0x7FF3F7F) || \
                             ((frameval & 0xFF) > 0x7F) ) )
   {
      Error_Report(1, "Frames MUST be numbered in the range &0010000 - &7FF3F7F or zero. The first three digits are the frame number as you normally see them, the latter four are the subframe; thus &1230001 is page 123.1. Note that frames 8xx are 0xx.");
      return;
   }

   Icon_GetText(frameinfo_window,  3, tmpbuf); // red
   sscanf(tmpbuf, "%x", &linkval[0]);
   if ( (linkval[0] > 0x7FF3F7F) || ((linkval[0] & 0xFF) > 0x7F) )
   {
      Error_Report(1, "The %s link is invalid - the highest frame that it can link to is &7FF3F7F. Note that this also means that &1230080 is an invalid frame, the subframe is too high.", "red");
      return;
   }

   Icon_GetText(frameinfo_window,  5, tmpbuf); // green
   sscanf(tmpbuf, "%x", &linkval[1]);
   if ( (linkval[1] > 0x7FF3F7F) || ((linkval[1] & 0xFF) > 0x7F) )
   {
      Error_Report(1, "The %s link is invalid - the highest frame that it can link to is &7FF3F7F. Note that this also means that &1230080 is an invalid frame, the subframe is too high.", "green");
      return;
   }

   Icon_GetText(frameinfo_window,  7, tmpbuf); // yellow
   sscanf(tmpbuf, "%x", &linkval[2]);
   if ( (linkval[2] > 0x7FF3F7F) || ((linkval[2] & 0xFF) > 0x7F) )
   {
      Error_Report(1, "The %s link is invalid - the highest frame that it can link to is &7FF3F7F. Note that this also means that &1230080 is an invalid frame, the subframe is too high.", "yellow");
      return;
   }

   Icon_GetText(frameinfo_window,  9, tmpbuf); // cyan
   sscanf(tmpbuf, "%x", &linkval[3]);
   if ( (linkval[3] > 0x7FF3F7F) || ((linkval[3] & 0xFF) > 0x7F) )
   {
      Error_Report(1, "The %s link is invalid - the highest frame that it can link to is &7FF3F7F. Note that this also means that &1230080 is an invalid frame, the subframe is too high.", "cyan");
      return;
   }

   Icon_GetText(frameinfo_window, 11, tmpbuf); // catalogue
   sscanf(tmpbuf, "%x", &linkval[4]);
   if ( (linkval[4] > 0x7FF3F7F) || ((linkval[4] & 0xFF) > 0x7F) )
   {
      Error_Report(1, "The %s link is invalid - the highest frame that it can link to is &7FF3F7F. Note that this also means that &1230080 is an invalid frame, the subframe is too high.", "catalogue");
      return;
   }

   Icon_GetText(frameinfo_window, 13, tmpbuf); // index
   sscanf(tmpbuf, "%x", &linkval[5]);
   if ( (linkval[5] > 0x7FF3F7F) || ((linkval[5] & 0xFF) > 0x7F) )
   {
      Error_Report(1, "The %s link is invalid - the highest frame that it can link to is &7FF3F7F. Note that this also means that &1230080 is an invalid frame, the subframe is too high.", "index");
      return;
   }

   Icon_GetText(frameinfo_window, 15, tmpbuf); // status
   sscanf(tmpbuf, "%x", &linkval[6]);
   // status, iirc, is a bunch of bits - so we won't bother validating them...

   Icon_GetText(frameinfo_window, 29, tmpbuf); // CRC
   sscanf(tmpbuf, "%x", &crcval);
   if (crcval > 0xFFFF)
   {
      Error_Report(1, "The CRC is a 16 bit value; &0000 - &FFFF please!");
      return;
   }

   if ( (crcval != frame.data.crc) && (crcval != 0) )
   {
      os_error error;
      int flags = 0;

      error.errnum = 1;
      sprintf(error.errmess, "You have specified the frame to be given the CRC &%04X when it used to be &%04X... are you sure you wish to alter the frame's CRC?", crcval, frame.data.crc);
      flags = Wimp_ReportErrorR(&error, 27, event_taskname);
      if (flags != 1)
         return; // user said NO
   }

   // Here? Then update it all...
   frame.data.frame       = frameval;
   frame.data.crc         = crcval;
   frame.data.link_red    = linkval[0];
   frame.data.link_green  = linkval[1];
   frame.data.link_yellow = linkval[2];
   frame.data.link_cyan   = linkval[3];
   frame.data.link_cat    = linkval[4];
   frame.data.link_index  = linkval[5];
   frame.data.link_status = linkval[6];
   frame.data.lang_page   = lang_page;
   frame.data.lang_header = lang_header;

   frame_refreshdisplay();
   redraw_viewer();

   Window_Hide(frameinfo_window);

   carrot.window = viewer_window;
   carrot.icon = -1;
   carrot.offset.x = -256; // don't show
   carrot.offset.y = 256;  // the carrot
   carrot.height   = -1;
   carrot.index    = -1;
   Wimp_SetCaretPosition(&carrot);

   return;
}



void export_verifyicon(char *icn)
{
   /* Ensures the icon about to be plotted exists.
      If it doesn't, the null icon will be used.
   */

   r.r[0] = 40;
   r.r[1] = 0;
   r.r[2] = (int)icn;
   if ( _kernel_swi(0x400E9, &r, &r) != NULL ) /* Wimp_SpriteOp */
      strcpy(icn, "file_xxx");

   return;
}


void saveframe_setupicon(char *icn)
{
  icon_createblock iconcreate;
  icon_handle handle;

   r.r[0] = 40;
   r.r[1] = 0;
   r.r[2] = (int)icn;
   if ( _kernel_swi(0x400E9, &r, &r) != NULL ) /* Wimp_SpriteOp */
      strcpy(icn, "file_xxx");

  Wimp_GetIconState(saveframe_window, 6, &iconcreate.icondata);
  Wimp_DeleteIcon(saveframe_window, 6);
  strcpy((char *) iconcreate.icondata.data.indirectsprite.name, icn);
  iconcreate.window = saveframe_window;
  Wimp_CreateIcon(&iconcreate, &handle);
  Wimp_SetIconState(saveframe_window, handle, 0, 0);

  if (handle != 6)
     Error_Report(1, "Save icon altered its handle during sprite change. Uh-oh!");

  return;
}


void save_frame(void)
{
   FILE *fp = NULL;
   char saveas[256] = "";

   Icon_GetText(saveframe_window, 7, saveas);

   fp = fopen(saveas, "wb");
   if (fp == NULL)
   {
      char *errmsg = NULL;
      errmsg = strerror(errno);
      Error_Report(1, "Unable to save \"%s\" because: %s", saveas, errmsg);
      return;
   }

   switch (export_format)
   {
      case 0 : // teletext
               fwrite((void *)&frame.rawdata, 1080, 1, fp);
               strncpy(ttxpath, saveas, 256);
               changed = FALSE;
               editor_settitle(FALSE);
               break;

      case 1 : // sprite
               export_sprite(fp);
               break;

      case 2 : // mode 7 (raw)
               for (int loop = 0; loop < 1000; loop++)
               {
                  int byte = frame.data.bytes[loop];

                  if (byte < 32)
                     fputc( (byte | 0x80), fp);  // control codes get 128 added
                  else
                     fputc(byte, fp);
               }
               break;
   }

   fclose(fp);

   switch (export_format)
   {
      case 0 : // teletext
               File_SetType(saveas, 0x112);
               break;

      case 1 : // sprite
               File_SetType(saveas, filetype_SPRITE);
               break;

      case 2 : // mode 7 (raw)
               File_SetType(saveas, filetype_DATA);
               break;
   }

   return;
}



void export_sprite(FILE *fp)
{
   int  ouraddress = 0;
   int  nxtaddress = 0;
   int  *value = NULL;
   int  copy_caret = 0;
   int  copy_codes = 0;
   int  copy_grid = 0;

   // Make a 'plain' version of the sprite for saveage
   // ("saveage" looks wrong, but "savage" is a whole different word!)
   copy_caret = caret;
   copy_codes = showcodes;
   copy_grid  = showgrid;
   caret = FALSE;
   showcodes = FALSE;
   showgrid = FALSE;
   frame_refreshdisplay();

   // Get address of teletext frame sprite
   r.r[0] = (256 + 24);
   r.r[1] = (int)resource_sprites;
   r.r[2] = (int)"ttx_frame";
   _kernel_swi(SWI_OS_SpriteOp, &r, &r);
   ouraddress = r.r[2];

   // Work out where the next sprite begins
   value = (int *)ouraddress;
   nxtaddress = value[0];

   fwriteword(fp, 1);                 // number of sprites
   fwriteword(fp, 16);                // offset to our sprite
   fwriteword(fp, (nxtaddress + 16)); // offset to first free word
   fwrite((void *)ouraddress, nxtaddress, 1, fp);

   // restore 'original' frame view
   caret = copy_caret;
   showcodes = copy_codes;
   showgrid = copy_grid;
   frame_refreshdisplay();

   return;
}


void fwriteword(FILE *fp, int value)
{
   fputc(  value        & 0xFF, fp);
   fputc( (value >>  8) & 0xFF, fp);
   fputc( (value >> 16) & 0xFF, fp);
   fputc( (value >> 24) & 0xFF, fp);

   return;
}



void goto_lastword(void)
{
   // scan backwards on the line for a previous word...
   int  chrp = 0;
   int  byte = 0;

   cursor_x--;
   if (cursor_x <= 0)
   {
      cursor_x = 0;
      return;
   }

   // find start of this word
#ifdef DEBUG
   Error_Report(1, "Looking for start of THIS word or space.");
#endif
   do
   {
      chrp = ((cursor_y * 40) + cursor_x);

      byte = frame.data.bytes[chrp];
      byte = ((byte | 0x80) - 0x80);

#ifdef DEBUG
      Error_Report(1, "Checking byte at +%d (%d, %d), it is %d (\'%c\')", chrp, cursor_x, cursor_y, byte, byte);
#endif

      if (byte > 32)
         cursor_x--;

      if (cursor_x < 0)
      {
#ifdef DEBUG
         Error_Report(1, "Start of line - dropping out...");
#endif
         cursor_x = 0;
         return;
      }
   } while (byte > 32);

   // find end of previous word
#ifdef DEBUG
   Error_Report(1, "Found space, looking now for PREVIOUS word tail.");
#endif
   do
   {
      chrp = ((cursor_y * 40) + cursor_x);

      byte = frame.data.bytes[chrp];
      byte = ((byte | 0x80) - 0x80);

#ifdef DEBUG
      Error_Report(1, "Checking byte at +%d (%d, %d), it is %d (\'%c\')", chrp, cursor_x, cursor_y, byte, byte);
#endif

      if (byte <= 32)
         cursor_x--;

      if (cursor_x < 0)
      {
#ifdef DEBUG
         Error_Report(1, "Start of line - dropping out...");
#endif
         cursor_x = 0;
         return;
      }
   } while (byte <= 32);

#ifdef DEBUG
   Error_Report(1, "Found PREVIOUS word tail at offset +%d (cursor_x = %d)", chrp, cursor_x);
#endif

   // adjust for correct positioning
   cursor_x++;
   // don't need to range-check, it CANNOT be at column 39 as word or spaces will carry us back.

   return;
}



void goto_nextword(void)
{
   // scan forewards on the line for the next word...

   int  chrp = 0;
   int  byte = 0;

   cursor_x++;
   if (cursor_x > 39)
   {
      cursor_x = 39;
      return;
   }
   // find end of this word
#ifdef DEBUG
   Error_Report(1, "Looking for end of THIS word or space.");
#endif
   do
   {
      chrp = ((cursor_y * 40) + cursor_x);

      byte = frame.data.bytes[chrp];
      byte = ((byte | 0x80) - 0x80);

#ifdef DEBUG
      Error_Report(1, "Checking byte at +%d (%d, %d), it is %d (\'%c\')", chrp, cursor_x, cursor_y, byte, byte);
#endif

      if (byte > 32)
         cursor_x++;

      if (cursor_x > 39)
      {
#ifdef DEBUG
         Error_Report(1, "End of line - dropping out...");
#endif
         cursor_x = 39;
         return;
      }
   } while (byte > 32);

   // find end of previous word
#ifdef DEBUG
   Error_Report(1, "Found space, looking now for NEXT word start.");
#endif
   do
   {
      chrp = ((cursor_y * 40) + cursor_x);

      byte = frame.data.bytes[chrp];
      byte = ((byte | 0x80) - 0x80);

#ifdef DEBUG
      Error_Report(1, "Checking byte at +%d (%d, %d), it is %d (\'%c\')", chrp, cursor_x, cursor_y, byte, byte);
#endif

      if (byte <= 32)
         cursor_x++;

      if (cursor_x > 39)
      {
#ifdef DEBUG
         Error_Report(1, "End of line - trying to be clever!");
#endif
         do
         {
            chrp = ((cursor_y * 40) + cursor_x);

            byte = frame.data.bytes[chrp];
            byte = ((byte | 0x80) - 0x80);

#ifdef DEBUG
            Error_Report(1, "Checking byte at +%d (%d, %d), it is %d (\'%c\')", chrp, cursor_x, cursor_y, byte, byte);
#endif

            if (byte <= 32)
               cursor_x--;

            if (cursor_x < 0)
            {
#ifdef DEBUG
               Error_Report(1, "Start of line - dropping out...");
#endif
               cursor_x = 39; // so user doesn't do ShRight on an empty line to see the
               return;        // cursor remain stationary (-ery?) in the leftmost column!
            }
         } while (byte <= 32);

         cursor_x = cursor_x + 2; // position ready for 'next' word
         if (cursor_x > 39)
            cursor_x = 39;

         return;
      } // end of drop-out-for-EOL routine
   } while (byte <= 32);

#ifdef DEBUG
   Error_Report(1, "Found NEXT word start at offset +%d (cursor_x = %d)", chrp, cursor_x);
#endif

   // don't adjust for correct positioning, we're at the word start...

   return;
}



void goto_nexttab(void)
{
   // work out our position, work out next 4 and go there...

   int  div = 0;

   div = (int)cursor_x / 4;
   div++;
   cursor_x = div * 4;

   if (cursor_x > 39)
      cursor_x = 39;

   return;
}



void goto_lasttab(void)
{
   // work out our position, work out previous 4 and go there...

   int  div = 0;

   div = (int)cursor_x / 4;
   div--;
   if (div < 0)
      div = 0;
   cursor_x = div * 4;

   if (cursor_x < 0)
      cursor_x = 0;

   return;
}



void insert_col(void)
{
   // From the CURRENT cursor position, insert a space shifting everything else to the right.
   // Stuff shifted out of column 39 is **DISCARDED**.
   int chrp = 0;

   if (cursor_x == 39)
   {
      // don't be silly!
      chrp = ((cursor_y * 40) + cursor_x);
      frame.data.bytes[chrp] = ' ';
      return;
   }

   for (int loop = 38; loop >= cursor_x; loop--)
   {
      chrp = ((cursor_y * 40) + loop);

      frame.data.bytes[(chrp + 1)] = frame.data.bytes[chrp];
   }
   frame.data.bytes[ ((cursor_y * 40) + cursor_x) ] = ' ';

   return;
}



void delete_col(void)
{
   // From the CURRENT cursor position, remove the current letter, shifting everything else left.
   int chrp = 0;

   if (cursor_x == 0)
   {
      // don't be silly!
      chrp = ((cursor_y * 40) + cursor_x);
      frame.data.bytes[chrp] = ' ';
      return;
   }

   for (int loop = cursor_x; loop < 39; loop++)
   {
      chrp = ((cursor_y * 40) + loop);

      frame.data.bytes[chrp] = frame.data.bytes[(chrp + 1)];
   }
   frame.data.bytes[ ((cursor_y * 40) + 39) ] = ' ';

   return;
}



void editor_settitle(int chngd)
{
   // Set title to "IDEFS::Willow.$...myframe", append '*' if changed...

   char  title[64] = "";

   if ( strlen(ttxpath) < 60 )
   {
      strcpy(title, ttxpath); // it's short, just copy it over
   }
   else
   {
      // long...
      char tmp[256];
      char fnm[256];
      char *posn;
      int  len;

      strncpy(tmp, ttxpath, 256);
      posn = strchr(tmp, '$');
      if (posn == NULL)
      {
         // no '$' found in path! (?!?)
         tmp[56] = '\0';
         strcpy(title, tmp);
         strcat(title, "[...]"); // make it "blahblahblah[...]"
      }
      else
      {
         // copy up to '$'
         posn++;
         posn[0] = '\0';
         strcpy(title, tmp);
         strcat(title, " ... ");
         // look for filename
         posn++;
         extract_filename(posn, fnm);
         // make sure it'll fit
         len = strlen(title) + strlen(fnm);
         if (len > 60)
         {
            // STILL too long!
            fnm[ (56 - strlen(title)) ] = '\0';
            strcat(fnm, "[...]");
         }
         strcat(title, fnm);
      }
   }

   // changed? if so, append " *"
   if (chngd)
      strcat(title, " *");

   Window_SetTitle(viewer_window, title);

   return;
}



void graphics_plotpoint(int xpos, int ypos, int doplot)
{
   // plots a point into the teletext frame
   int  xchr = 0;
   int  ychr = 0;
   int  chrp = 0;
   int  byte = 0;
   int  xpix = 0;
   int  ypix = 0;
   int  pixl = 0;
   int  loop = 0;
   int  abort = 0;
   int  foundchar = 0;

   // DEVELOPMENT

   // Work out which 'character' this corresponds to
   xchr = (xpos / 2);
   ychr = (ypos / 3);

   // we can't use column zero
   if (xchr == 0)
   {
      Error_Report(1, "Sorry, you cannot plot a mosaic pixel in the leftmost column as you need to include a change-to-graphics control code...");
      return;
   }

#ifdef DEBUG
   debug("\n  graphics_plotpoint()\nPlotting to character position %d, %d (%d, %d)\n", \
         xchr, ychr, xpos, ypos);
#endif

   // Scan backwards along this line looking for a mosaic colour-set code

#ifdef DEBUG
   debug("\nLooking for mosaic colour codes...\n");
#endif
   abort = FALSE;
   foundchar = FALSE;
   loop = xchr;
   do
   {
      // we abort if we find a mosaic colour code, or an alpha colour code.
      loop--;

      if (loop >= 0)
      {
         chrp = ((ychr * 40) + loop);
         byte = frame.data.bytes[chrp];
         byte = ((byte | 0x80) - 0x80);
      }

#ifdef DEBUG
      debug("Byte at offset +%d is %d.\n", loop, byte);
#endif

      if (byte < 8)
         abort = TRUE; // ALPHA

      if ( (byte > 15) && (byte < 24) )
      {
         abort = TRUE; // MOSAIC
         foundchar = FALSE; // assume characters following ARE mosaic characters
      }

      if (byte > 32)
         foundchar = TRUE; // text character

      if (loop < 0)
         abort = TRUE; // no more data

   } while (!abort);

   // now let's see what we have...
   if ((loop < 0) && (!foundchar))
   {
      // we found nothing, so push the last-set-colour to the lefthand column
#ifdef DEBUG
      debug("Pushing mosaic colour %d in left column.\n", last_colour);
#endif
      frame.data.bytes[ (ychr * 40) ] = last_colour + 16; // +16 for mosaic colour
   }
   else
   {
      if ( (byte < 8) || foundchar )
      {
         // it was an alphabetic character or code; so we must RESCAN until we find
         // a non-space character - assuming that will be the end of the text...
         abort = FALSE;
         loop = xchr;
         do
         {
            // we abort if we find an alpha colour code or text
            loop--;
            chrp = ((ychr * 40) + loop);
            byte = frame.data.bytes[chrp];
            byte = ((byte | 0x80) - 0x80);

#ifdef DEBUG
            debug("Byte at offset +%d is %d.\n", loop, byte);
#endif

            if (byte < 8)
               abort = TRUE; // ALPHA

            if (byte > 32)
               abort = TRUE; // TEXT

         } while (!abort);

         if (byte < 8)
         {
            // all that is here is an alpha code, REPLACE it
#ifdef DEBUG
            debug("Replacing ALPHA code at +%d with mosaic colour %d.\n", loop, last_colour);
#endif
            frame.data.bytes[ ((ychr * 40) + loop) ] = last_colour + 16;
         }
         else
         {
            // found text, advance one place
            loop++;
            if (loop == xchr)
               loop--; // if the advance brings us to oursevles, back up and kill the character

#ifdef DEBUG
            debug("Writing colour code %d at +%d.\n", last_colour, loop);
#endif
            frame.data.bytes[ ((ychr * 40) + loop) ] = last_colour + 16;
         }
      }
      // else... it was a mosaic colour set code, so just 'use' that...
   }


   // Work out the graphics character to use:
   //   32 + ...
   //           .-----------.
   //           | 1   | 2   |
   //           |     |     |
   //           |-----+-----|
   //           | 4   | 8   |
   //           |     |     |
   //           |-----+-----|
   //           | 16  | 64  |
   //           |     |     |
   //           '-----------'

   chrp = ((ychr * 40) + xchr);
   byte = frame.data.bytes[chrp];

   if ((byte & 32) != 32)
   {
      // This isn't a mosaic character, so blank it...
      byte = 32;
   }

   // Which pixel are we trying to set?
   xpix = (xchr * 2);
   if (xpix == xpos)
      xpix = 1; // left column
   else
      xpix = 2; // right column

#ifdef DEBUG
   debug("  Xpix = %d; left would be %d and actual is %d\n", xpix, ((xchr * 2)-1), xpos);
#endif

   ypix = (ychr * 3);

#ifdef DEBUG
   debug("  Ypix = %d; top would be %d and actual is %d\n", ypix, (ychr * 3), ypos);
#endif

   // top row?
   if (ypix == ypos)
   {
      if (xpix == 1)
         pixl = 1; // top left
      else
         pixl = 2; // top right
#ifdef DEBUG
      debug("  Ypix %d is top row, pixl is %d\n", ypix, pixl);
#endif
   }

   // middle row?
   if ((ypix + 1) == ypos)
   {
      if (xpix == 1)
         pixl = 4; // middle left
      else
         pixl = 8; // middle right
#ifdef DEBUG
      debug("  Ypix %d is middle row, pixl is %d\n", ypix, pixl);
#endif
   }

   // bottom row?
   if ((ypix + 2) == ypos)
   {
      if (xpix == 1)
         pixl = 16; // bottom left
      else
         pixl = 64; // bottom right
#ifdef DEBUG
      debug("  Ypix %d is bottom row, pixl is %d\n", ypix, pixl);
#endif
   }

#ifdef DEBUG
   debug("  Plotting pixel value %d at %d,%d (%d,%d)\n", pixl, xpix, ypix, xpos, ypos);
#endif

   // Merge it into the pixel
   byte |= pixl;

   // Now, if we are UNSETTING, clear this pixel
   if (doplot == FALSE)
      byte ^= pixl;
                                                                                                    // And write it back
   frame.data.bytes[chrp] = byte;

   // flag changed
   if (changed == FALSE)
      editor_settitle(TRUE);
   changed = TRUE;

   return;
}



void graphics_plotpointfast(int xpos, int ypos, int doplot)
{
   // plots a point into the teletext frame
   int  xchr = 0;
   int  ychr = 0;
   int  chrp = 0;
   int  byte = 0;
   int  xpix = 0;
   int  ypix = 0;
   int  pixl = 0;

   // Work out which 'character' this corresponds to
   xchr = (xpos / 2);
   ychr = (ypos / 3);

   // we can't use column zero
   if (xchr == 0)
      return;

   chrp = ((ychr * 40) + xchr);
   byte = frame.data.bytes[chrp];

   if ((byte & 32) != 32)
      byte = 32;

   // Which pixel are we trying to set?
   xpix = (xchr * 2);
   if (xpix == xpos)
      xpix = 1; // left column
   else
      xpix = 2; // right column

   ypix = (ychr * 3);

   // top row?
   if (ypix == ypos)
   {
      if (xpix == 1)
         pixl = 1; // top left
      else
         pixl = 2; // top right
   }

   // middle row?
   if ((ypix + 1) == ypos)
   {
      if (xpix == 1)
         pixl = 4; // middle left
      else
         pixl = 8; // middle right
   }

   // bottom row?
   if ((ypix + 2) == ypos)
   {
      if (xpix == 1)
         pixl = 16; // bottom left
      else
         pixl = 64; // bottom right
   }

   // Merge it into the pixel
   byte |= pixl;

   // Now, if we are UNSETTING, clear this pixel
   if (doplot == FALSE)
      byte ^= pixl;
                                                                                                    // And write it back
   frame.data.bytes[chrp] = byte;

   return;
}



void graphics_plotline(int doplot)
{
   // Plots a line into the teletext frame
   int  xdif = 0;
   int  ydif = 0;
   int  xpos = 0;
   int  ypos = 0;
   int  ylst = 0;
   float shift = 0;
   float yposfl = 0;

   // WE ASSUME START AND END POINTS DEFINED PRIOR TO THIS CALL.
   // WE ASSUME ALSO THAT POINTS DO NOT EXTEND BEYOND THE SCREEN.


   // if same, plot point
   if ((gspoint_x == gfpoint_x) && (gspoint_y == gfpoint_y))
   {
      graphics_plotpoint(gfpoint_x, gfpoint_y, doplot);
   }

   // if X=X then call fast-plot-vertical
   if (gspoint_x == gfpoint_x)
   {
      graphics_plotline_fastvertical(gspoint_x, gspoint_y, gfpoint_y, doplot);
      return;
   }

   // if Y=Y then call fast-plot horizontal
   if (gspoint_y == gfpoint_y)
   {
      graphics_plotline_fasthorizontal(gspoint_y, gspoint_x, gfpoint_x, doplot);
      return;
   }

   // Work out the difference between the two points to plot
   xdif = (gfpoint_x - gspoint_x);
   ydif = (gfpoint_y - gspoint_y);

   if (xdif < 0)
   {
      // we CANNOT handle going backwards, so re-arrange the points to something we can cope with
      int  tmpx = gspoint_x;
      int  tmpy = gspoint_y;
      gspoint_x = gfpoint_x;
      gspoint_y = gfpoint_y;
      gfpoint_x = tmpx;
      gfpoint_y = tmpy;
      // recalculate offsets
      xdif = (gfpoint_x - gspoint_x);
      ydif = (gfpoint_y - gspoint_y);
   }

   // We now know that we must move 'xdif' pixels horizontally;
   // and in that movement, we also go 'ydif' pixels vertically.

   // Let's work out how many vertical steps this would be...
   shift = ( (float)ydif / (float)xdif); // this LOOKS backwards, it is correct!

   // let's report on what we have thus far
#ifdef DEBUG
   debug("Line from %d,%d to %d,%d; we move %d across and %d down; for every pixel across, we must go %.2f down.\n", gspoint_x, gspoint_y, gfpoint_x, gfpoint_y, xdif, ydif, shift);
#endif

   // plot start point
   graphics_plotpoint(gspoint_x, gspoint_y, doplot);
   // plot in-between points
   yposfl = (float)gspoint_y;
   ylst = (int)yposfl;
   for (xpos = (gspoint_x + 1); xpos <= gfpoint_x; xpos++)
   {
      // if we have skipped any previously, fill them in...
      int yadj = 0;

      if (ypos < 0)
         yadj = 0;
      else
         yadj = 1;

#ifdef DEBUG
      debug("\n  Checking ylst (%d) + yadj (%d) [= %d] against (int)YposFL = %d\n", ylst, yadj, (ylst + yadj), (int)yposfl);
#endif

      if ((ylst + yadj) != (int)((int)yposfl + (float)shift) )
      {
         int  starty = ylst;
         int  endy = (int)((int)yposfl + (float)shift);
#ifdef DEBUG
         debug("  **PATCHUP** Ylst = %d (int)YposFL = %d  [%d -> %d]\n", ylst, (int)yposfl, starty, endy);
#endif

         if (endy < starty)
         {
            endy += 2;
#ifdef DEBUG
            debug("Reverse patchup: %d -> %d\n", starty, endy);
#endif
            for (int loop = endy; loop < starty; loop++)
            {
#ifdef DEBUG
               debug("  > %d, %d\n", (xpos-1), loop);
#endif
               if ( (xpos - 1) >= 2 )
                  graphics_plotpoint((xpos - 1), loop, doplot);
            }
            ylst = (endy);
         }
         else
         {
            starty++;
            endy--;
#ifdef DEBUG
            debug("Forward patchup: %d -> %d\n", starty, endy);
#endif
            for (int loop = starty; loop < endy; loop++)
            {
#ifdef DEBUG
               debug("  > %d, %d\n", (xpos-1), loop);
#endif
               if ( (xpos - 1) >= 2 )
                  graphics_plotpoint((xpos - 1), loop, doplot);
            }
            ylst = (int)yposfl;
         }
      }
      else
      {
#ifdef DEBUG
         debug("  Point is okay as %d (YposFL = %.2f)\n", ylst, yposfl);
#endif
         ylst = (int)yposfl;
      }


      // work out the next position
      yposfl += shift;

      // plot this point
#ifdef DEBUG
      debug("  YposFL for Xpos %d is %.2f\n", xpos, yposfl);
#endif
      if ( xpos >= 2 )
         graphics_plotpoint(xpos, (int)yposfl, doplot);
   }
   // plot final point
   if ( gfpoint_x >= 2 )
      graphics_plotpoint(gfpoint_x, gfpoint_y, doplot);

   return;
}



void graphics_plotline_fastvertical(int xpos, int ystart, int yend, int doplot)
{
   // fast vertical line plotter

#ifdef DEBUG
   debug("graphics_plotline_fastvertical(xpos=%d, ystart=%d, yend=%d)\n", xpos, ystart, yend);
#endif

   if (xpos < 2)
      return; // we can't do this in xpos 0 or 1...

   if (ystart > yend)
   {
      int ytmp = yend;
      yend = ystart;
      ystart = ytmp;
#ifdef DEBUG
      debug("  start was after end, swapped; ystart=%d, yend=%d\n", ystart, yend);
#endif
   }
   else
   {
      yend++; // plot end point INCLUSIVE
   }

   for (int loop = ystart; loop < yend; loop++)
   {
#ifdef DEBUG
      debug("  plotting point %d, %d\n", xpos, loop);
#endif
      graphics_plotpoint(xpos, loop, doplot);
   }

   return;
}



void graphics_plotline_fasthorizontal(int ypos, int xstart, int xend, int doplot)
{
   // fast horizontal line plotter - WE CAN ONLY GO LEFT-TO-RIGHT

#ifdef DEBUG
   debug("graphics_plotline_fasthorizontal(ypos=%d, xstart=%d, xend=%d)\n", ypos, xstart, xend);
#endif

   if (xstart > xend)
   {
      int xtmp = xend;
      xend = xstart;
      xstart = xtmp;
#ifdef DEBUG
      debug("  start was after end, swapped; xstart=%d, xend=%d\n", xstart, xend);
#endif
   }
   else
   {
      xend++; // plot end point INCLUSIVE
   }

   for (int loop = xstart; loop < xend; loop++)
   {
#ifdef DEBUG
      debug("  plotting point %d, %d\n", loop, ypos);
#endif
      graphics_plotpoint(loop, ypos, doplot);
   }

   return;
}



void graphics_plotrectangle(int doplot)
{
   // plots a rectangle...

   int  start_x = gspoint_x;
   int  start_y = gspoint_y;
   int  end_x   = gfpoint_x;
   int  end_y   = gfpoint_y;
   int  tmp = 0;

   if (start_x > end_x)
   {
      tmp = end_x;
      end_x = start_x;
      start_x = tmp;
   }

   if (start_y > end_y)
   {
      tmp = end_y;
      end_y = start_y;
      start_y = tmp;
   }

   if (start_x < 2)
      start_x = 2; // can't use leftmost place

   if (start_x >= end_x)
      return; // trap thin verticals

   for (int rowloop = start_y; rowloop <= end_y; rowloop++)
      for (int colloop = start_x; colloop <= end_x; colloop++)
         if (colloop == start_x)
            graphics_plotpoint(colloop, rowloop, doplot);
         else
            graphics_plotpointfast(colloop, rowloop, doplot); // don't bother the scanning...

   return;
}



int  convert_from_sprite(void)
{
   // load sprite
   // ensure it is < 75 x 75 and 1bpp
   //
   // scan across sprite, matching pixels set to teletext pixels set

   // IF ANYTHING GOES WRONG - WE FAIL *SILENTLY*

   FILE *fp = NULL;
   int  length = 0;
   char *sprite = NULL;
   int  xsize = 0;
   int  ysize = 0;
   int  mode = 0;
   int  xpos = 0;
   int  ypos = 0;
   int  yposrev = 0;

   // we are passed the filename of the sprite in "ttxpath"
   fp = fopen(ttxpath, "rb");
   if (fp == NULL)
      return FALSE;

   fseek(fp, 0, SEEK_END);
   length = (int)ftell(fp);
   fseek(fp, 0, SEEK_SET);
   fclose(fp);

   length = length + 16;
   sprite = calloc(length, sizeof(char));
   if (sprite == NULL)
      return FALSE;

   sprite[0] =  (length        & 0xFF);
   sprite[1] = ((length >>  8) & 0xFF);
   sprite[2] = ((length >> 16) & 0xFF);
   sprite[3] = ((length >> 24) & 0xFF);
   sprite[4] = 0;
   sprite[8] = 16;
   sprite[16]= 16;

   r.r[0] = (9 + 256);
   r.r[1] = (int)sprite;
   _kernel_swi(SWI_OS_SpriteOp, &r, &r); // initialise

   r.r[0] = (10 + 256);
   r.r[1] = (int)sprite;
   r.r[2] = (int)ttxpath;
   _kernel_swi(SWI_OS_SpriteOp, &r, &r); // load sprite

   r.r[0] = (512 + 40);
   r.r[1] = (int)sprite;
   r.r[2] = (int)sprite + 16;
   _kernel_swi(SWI_OS_SpriteOp, &r, &r); // read sprite info

   xsize = r.r[3];
   ysize = r.r[4];
   mode = r.r[6];

   // check the size
   if ((xsize > 76) || (ysize > 75))
   {
      free(sprite);
      return -2;
   }

   // check the mode
   r.r[0] = mode;
   r.r[1] = 3;
   _kernel_swi(SWI_OS_ReadModeVariable, &r, &r);
   if (r.r[2] != 1)
   {
      free(sprite);
      return -2;
   }

   blank_frame();

   // okay then...
   for (ypos = 0; ypos < ysize; ypos++)
   {
      yposrev = (75 - ypos);

      for (xpos = 0; xpos < xsize; xpos++)
      {
         // read pixel value
         r.r[0] = (41 + 512);
         r.r[1] = (int)sprite;
         r.r[2] = (int)sprite + 16;
         r.r[3] = xpos;
         r.r[4] = ypos;
         _kernel_swi(SWI_OS_SpriteOp, &r, &r); // read pixel colour

         if (r.r[5] == 1)
            graphics_plotpoint((xpos + 4), yposrev, TRUE);
      }
   }

   // done!
   free(sprite);

   return TRUE;
}



int  convert_invalid_sprite(int lockaspect)
{
   char wrkspc[256] = "";

   // does ChangeFSI exist?
   r.r[0] = 17;
   r.r[1] = (int)"<ChangeFSI$Dir>.ChangeFSI";
   _kernel_swi(SWI_OS_File, &r, &r);
   if (r.r[0] != 1)
   {
      Error_Report(1, "no cfsi");
      return FALSE; // not there
   }

   // set stuff up
   if (lockaspect == TRUE)
      sprintf(wrkspc, "<ChangeFSI$Dir>.ChangeFSI %s <Wimp$Scrap> 25 75: 75: -range -lock -nosize -noscale -nomode", ttxpath);
   else
      sprintf(wrkspc, "<ChangeFSI$Dir>.ChangeFSI %s <Wimp$Scrap> 25 75: 75: -range -nomode", ttxpath);

   // do the actual conversion
   r.r[0] = (int)wrkspc;
   if ( Wimp_StartTask(wrkspc) != NULL )
   {
      Error_Report(1, "cfsi cmd failed - \"%s\"", wrkspc);
      return FALSE; // it failed (oh bugger)
   }

   // push this path for impending reload
   strcpy(ttxpath, "<Wimp$Scrap>");

   return TRUE;
}



void mosaic_setvalidation(void)
{
    Icon_AlterValidation(mosaicpanel_window, 21, "R1;Ssel_line");
    Icon_AlterValidation(mosaicpanel_window, 22, "R1;Ssel_rect");
    Icon_AlterValidation(mosaicpanel_window, 23, "R1;Ssel_circ");
    Icon_AlterValidation(mosaicpanel_window, 24, "R1;Ssel_poin");

    switch (drawmode)
    {
       case 1 : Icon_AlterValidation(mosaicpanel_window, 21, "R2;Ssel_line");
                break;
       case 2 : Icon_AlterValidation(mosaicpanel_window, 22, "R2;Ssel_rect");
                break;
       case 3 : Icon_AlterValidation(mosaicpanel_window, 23, "R2;Ssel_circ");
                break;
       case 4 : Icon_AlterValidation(mosaicpanel_window, 24, "R2;Ssel_poin");
                break;
    }

    return;
}



void load_sprite_patchup(void)
{
   window_state windatum;
   caret_block carrot;
   int masterright;
   int mastertop;

   frame.data.link_red    = (0x100 << 16);
   frame.data.link_green  = (0x100 << 16);
   frame.data.link_yellow = (0x100 << 16);
   frame.data.link_cyan   = (0x100 << 16);
   frame.data.link_cat    = (0x100 << 16);
   frame.data.link_index  = (0x100 << 16);
   frame.data.link_status = (0x100 << 16);
   frame.data.crc = 0;
   frame.data.frame = 0;
   frame.data.lang_page = 0;   // english
   frame.data.lang_header = 0; // english

   frame_refreshdisplay();

   Window_Show(panel_window, open_WHEREVER); // panel must be opened first
   open_viewer();
   Wimp_GetWindowState(viewer_window, &windatum);
   masterright = windatum.openblock.screenrect.max.x;
   mastertop = windatum.openblock.screenrect.max.y;
   panel_move(masterright, mastertop);
   viewer_is_open = TRUE;

   redraw_viewer();

   frameinfo_refresh();

   carrot.window = viewer_window;
   carrot.icon = -1;
   carrot.offset.x = -256; // don't show
   carrot.offset.y = 256;  // the carrot
   carrot.height   = -1;
   carrot.index    = -1;
   Wimp_SetCaretPosition(&carrot);

   Icon_SetText(saveframe_window, 7, ttxpath);

   return;
}



int  check_numerical(void)
{
   // returns TRUE if numeric keyboard with ALT held
   int  found = FALSE;
   char *ptr = NULL;
   int  offset = 0;

   r.r[0] = 129;
   r.r[1] = 110;
   r.r[2] = 255;
   _kernel_swi(SWI_OS_Byte, &r, &r);

   switch (r.r[1])
   {
      case  26 : // keypad 6
                 found = 6;
                 break;

      case  27 : // keypad 7
                 found = 7;
                 break;

      case  42 : // keypad 8
                 found = 8;
                 break;

      case  43 : // keypad 9
                 found = 9;
                 break;

      case 106 : // keypad 0
                 found = 10;
                 break;

      case 107 : // keypad 1
                 found = 1;
                 break;

      case 108 : // keypad 3
                 found = 3;
                 break;

      case 122 : // keypad 4
                 found = 4;
                 break;

      case 123 : // keypad 5
                 found = 5;
                 break;

      case 124 : // keypad 2
                 found = 2;
                 break;

      default : // anything else...
                found = FALSE;
                break;
   }

   if (found == FALSE)
      return FALSE;

   // Was Shift held?
   r.r[0] = 129;
   r.r[1] = 255; // alt
   r.r[2] = 255;
   _kernel_swi(SWI_OS_Byte, &r, &r);
   if (r.r[1] != 255)
      return FALSE;

   // and Ctrl held?
   r.r[0] = 129;
   r.r[1] = 254; // ctrl
   r.r[2] = 255;
   _kernel_swi(SWI_OS_Byte, &r, &r);
   if (r.r[1] != 255)
      return FALSE;

   if (changed)
   {
      Error_Report(1, "Unsaved data - please save or blank the current frame, then try this again...");
      return TRUE;
   }

   // let's load it up...
   if (found == 10)
      found = 0;

   ptr = embed_base_address + (found * 1024);

   for (offset = 0; offset < 1024; offset++)
      frame.data.bytes[offset] = (ptr[offset] ^ 0x16);

   load_sprite_patchup();

   switch (found)
   {
      case 0 : // French flag
      case 1 : // J'en ai marre (1)
      case 2 : // J'en ai marre (2)
      case 3 : // J'en ai marre (3)
      case 4 : // Alize piccy
               offset = 1; // french
               break;

      case 5 : // MTV logo
      case 6 : // CNBC logo
               offset = 0; // english
               break;

      case 7 : // TVE logo
               offset = 5; // spanish
               break;

      case 8 : // Alyson
      case 9 : // Union Jack
               offset = 0; // english
               break;
   }

   frame.data.lang_page = offset;
   frame.data.lang_header = offset;

   frame_refreshdisplay();

   Icon_SetText(saveframe_window, 7, "Why did the chicken cross the road...?");

   return TRUE;
}

