// GlobalOptions.cpp : load/save global options
//

#include "stdafx.h"
#include "MUSHclient.h"
#include "TextDocument.h"
#include "TextView.h"
#include "GlobalConfigurationDlg.h"

#pragma warning( disable : 4800 )   // never mind those "bool" warnings

static CMUSHclientApp * pApp = NULL;  // purely for #define below

#define O(arg) offsetof (CMUSHclientApp, arg), sizeof (pApp->arg)


//-----config name ----------------  default ---variable name ---- min, max, flags ---
tConfigurationNumericOption GlobalOptionsTable [] = {


{ "activity_button_bar_style",          false,           O(m_iActivityButtonBarStyle), 0, 5},
{ "activity_window_refresh_interval",   15,              O(m_nActivityWindowRefreshInterval), 1, 300},
{ "activity_window_refresh_type",       CMUSHclientApp::eRefreshBoth,    
                                                         O(m_nActivityWindowRefreshType), 0, 2},
{ "all_typing_to_command_window",       true,            O(m_bAllTypingToCommandWindow)},
{ "allow_loading_dlls",                 false,           O(m_bEnablePackageLibrary)},
{ "always_on_top",                      false,           O(m_bAlwaysOnTop)},
{ "append_to_log_files",                false,           O(m_bAppendToLogFiles)},
{ "ascii_art_layout",                   0,               O(m_iAsciiArtLayout), 0, 4},
{ "auto_connect_worlds",                true,            O(m_bAutoConnectWorlds)},
{ "auto_expand_config",                 false,           O(m_bAutoExpand)},
{ "flat_toolbars",                      false,           O(m_bFlatToolbars)},
{ "auto_log_world",                     false,           O(m_bAutoLogWorld)},
{ "bleed_background",                   false,           O(m_bBleedBackground)},
{ "colour_gradient_config",             true,            O(m_bColourGradient)},
{ "confirm_before_closing_client",      true,            O(m_bConfirmBeforeClosingMushclient)},
{ "confirm_before_closing_mxp_debug",   false,           O(m_bConfirmBeforeClosingMXPdebug)},
{ "confirm_before_closing_world",       true,            O(m_bConfirmBeforeClosingWorld)},
{ "confirm_before_saving_variables",    true,            O(m_bConfirmBeforeSavingVariables)},
{ "confirm_log_file_close",             true,            O(m_bConfirmLogFileClose)},
{ "default_input_font_height",          9,               O(m_iDefaultInputFontHeight), 1, 1000,},
{ "default_input_font_charset",         DEFAULT_CHARSET, O(m_iDefaultInputFontCharset), 0, 65536,},
{ "default_input_font_italic",          false,           O(m_iDefaultInputFontItalic)},
{ "default_input_font_weight",          FW_NORMAL,       O(m_iDefaultInputFontWeight), 1, 1000},
{ "default_output_font_height",         9,               O(m_iDefaultOutputFontHeight), 1, 1000,},
{ "default_output_font_charset",        DEFAULT_CHARSET, O(m_iDefaultOutputFontCharset), 0, 65536,},
{ "disable_keyboard_menu_activation",   false,           O(m_bDisableKeyboardMenuActivation)},
{ "enable_spell_check",                 false,           O(m_bEnableSpellCheck)},
{ "fixed_font_for_editing",             true,            O(m_bFixedFontForEditing)},
{ "fixed_pitch_font_size",              9,               O(m_iFixedPitchFontSize)},

{ "icon_placement",                     ICON_PLACEMENT_TASKBAR,            O(m_iIconPlacement), ICON_PLACEMENT_TASKBAR, ICON_PLACEMENT_BOTH},
{ "notepad_back_colour",                0,               O(m_cNotepadBackColour), 0, 0xFFFFFF, OPT_RGB_COLOUR},
{ "notepad_text_colour",                0,               O(m_cNotepadTextColour), 0, 0xFFFFFF, OPT_RGB_COLOUR},
{ "notepad_word_wrap",                  true,            O(m_bNotepadWordWrap)},
{ "notify_if_cannot_connect",           true,            O(m_bNotifyIfCannotConnect)},
{ "error_notification_to_output_window",false,           O(m_bErrorNotificationToOutputWindow)},
{ "notify_on_disconnect",               true,            O(m_bNotifyOnDisconnect)},
{ "open_activity_window",               false,           O(m_bOpenActivityWindow)},
{ "open_worlds_maximised",              false,           O(m_bOpenWorldsMaximised)},
{ "paren_match_flags",                 
    PAREN_NEST_BRACES | PAREN_BACKSLASH_ESCAPES | PAREN_PERCENT_ESCAPES,
                                                         O(m_nParenMatchFlags), 0, 0xFFFF},
{ "printer_font_size",                  10,              O(m_nPrinterFontSize), 1, 1000,},
{ "printer_left_margin",                15,              O(m_nPrinterLeftMargin), 0, 100},
{ "printer_lines_per_page",             60,              O(m_nPrinterLinesPerPage), 10, 500},
{ "printer_top_margin",                 15,              O(m_nPrinterTopMargin), 0, 100},
{ "reconnect_on_link_failure",          false,           O(m_bReconnectOnLinkFailure)},
{ "regexp_match_empty",                 true,            O(m_bRegexpMatchEmpty)},
{ "show_grid_lines_in_list_views",      true,            O(m_bShowGridLinesInListViews)},
{ "check_before_removing",              true,            O(m_bTriggerRemoveCheck)},
{ "f1_is_macro",                        false,           O(m_bF1macro)},
{ "smooth_scrolling",                   false,           O(m_bSmoothScrolling)},
{ "smoother_scrolling",                 false,           O(m_bSmootherScrolling)},
{ "timer_interval",                     0,               O(m_nTimerInterval), 0, 120},
{ "tray_icon",                          0,               O(m_iTrayIcon), 0, 9},
{ "window_tabs_style",                  0,               O(m_iWindowTabsStyle), 0, 2},

{NULL}   // end of table marker            

  };  // end of GlobalOptionsTable 

#define A(arg) offsetof (CMUSHclientApp, arg)
                    
// first  optional argument: multiline flag
// second optional argument: preserve spaces flag (defaults to true for multiline)

tConfigurationAlphaOption AlphaGlobalOptionsTable  [] =
{

{ "ascii_art_font",                 "fonts\\standard.flf",   A(m_strAsciiArtFont)},
{ "default_aliases_file",           "",                      A(m_strDefaultAliasesFile)},
{ "default_colours_file",           "",                      A(m_strDefaultColoursFile)},
{ "default_input_font",             MUSHCLIENT_DEFAULT_FONT, A(m_strDefaultInputFont)},
{ "default_log_file_directory",     ".\\logs\\",             A(m_strDefaultLogFileDirectory)},
{ "default_macros_file",            "",                      A(m_strDefaultMacrosFile)},
{ "default_output_font",            MUSHCLIENT_DEFAULT_FONT, A(m_strDefaultOutputFont)},
{ "default_timers_file",            "",                      A(m_strDefaultTimersFile)},
{ "default_triggers_file",          "",                      A(m_strDefaultTriggersFile)},
{ "default_world_file_directory",   ".\\worlds\\",           A(m_strDefaultWorldFileDirectory)},
{ "fixed_pitch_font",               "FixedSys",              A(m_strFixedPitchFont)},
{ "notepad_quote_string",           "> ",                    A(m_strNotepadQuoteString)},
{ "plugins_directory",              ".\\worlds\\plugins\\",  A(m_strPluginsDirectory)},
{ "printer_font",                   "Courier",               A(m_strPrinterFont)},
{ "word_delimiters",                ".,()[]\"\'",            A(m_strWordDelimiters)},
{ "word_delimiters_dbl_click",      ".,()[]\"\'",            A(m_strWordDelimitersDblClick)},
{ "trayiconfilename",               "",                      A(m_strTrayIconFileName)},
{ "lua_script",                     "",                      A(m_strLuaScript)},
{ "locale",                         "EN",                    A(m_strLocale)},

{NULL}  // end of table marker
};         // end of AlphaGlobalOptionsTable


static CString strFileName;
extern UINT iLineLastItemFound;
static UINT iErrorCount;

void CMUSHclientApp::ReadGlobalPrefs (void)
  {
bool bOK;

CFile * f = NULL;
CArchive * ar = NULL;

  do
    {
  try
      {
      f = new CFile (m_GlobalConfigurationFileName, CFile::modeRead | CFile::shareDenyWrite);

      ar = new CArchive(f, CArchive::load);

      ImportArchive (*ar);

      bOK = true;

      } // end of try block

    catch (CException * e)
      {
      e->Delete ();
      bOK = false;
      } // end of catching a file exception


    delete ar;      // delete archive
    ar = NULL;

    delete f;       // delete file
    f = NULL;

    if (!bOK)
      {
      CGlobalConfigurationDlg dlg;

      if (dlg.DoModal () != IDOK)
        {
        GetGlobalPrefsFromRegistry ();
        return;   // give up, take default prefs
        }

      // otherwise go around loop, try loading again

      } // end of not loading OK

    } while (!bOK);


  }   // end of CMUSHclientApp::ReadGlobalPrefs


void CMUSHclientApp::ImportArchive (CArchive & ar)
  {
CXMLparser parser;
CXMLelement * pMuClientElement = NULL;

  try
    {

    if (IsArchiveXML (ar))
      {

      strFileName = ar.GetFile ()->GetFilePath ();

      iLineLastItemFound = 0;
      parser.BuildStructure (ar.GetFile ());

      // find <?xml> tag so we don't get a warning
      FindNode (parser.m_xmlRoot, "?xml");

      // if we have a <muclient> tag, use contents of that
      pMuClientElement = FindNode (parser.m_xmlRoot, "muclient");
      if (pMuClientElement)
        CheckUsed (parser.m_xmlRoot);   // check no tags outside <muclient>
      else
        pMuClientElement = &parser.m_xmlRoot;

      Load_Global_XML    (*pMuClientElement);

      CheckUsed (*pMuClientElement);   // check we used all attributes

      }
    else
      ::TMessageBox ("Not in XML format");

     } // end of try block
  catch (CException* e) 
    {
    if (iLineLastItemFound == 0)
      iLineLastItemFound = parser.m_xmlLine;
    HandleLoadException ("problem in this file", e);
    AfxThrowArchiveException (CArchiveException::badSchema);
    }

  }   // end of CMUSHclientApp::ImportArchive


void CMUSHclientApp::CheckUsed (CXMLelement & node)
  {
  POSITION pos;
  CString strName;
  CAttribute * attribute;

/*
  Each tag can have comments as a child
*/

  Load_Comments_XML (node);

  // check attributes of this tag
  for (pos = node.AttributeMap.GetStartPosition(); pos; )
    {
    node.AttributeMap.GetNextAssoc (pos, strName, attribute);
    if (attribute->bUsed)
      continue;

    LoadError (node.strName, 
      TFormat ("Attribute not used: %s=\"%s\"",
                (LPCTSTR) strName,
                (LPCTSTR) attribute->strValue),
                attribute->iLine);

    }      // end of checking each attribute

  // look for unused children

  for (pos = node.ChildrenList.GetHeadPosition (); pos; )
    {
    CXMLelement * pElement = node.ChildrenList.GetNext (pos);
    if (pElement->bUsed)
      continue;

    LoadError (node.strName, 
      TFormat ("Tag not used: <%s>",
                (LPCTSTR) pElement->strName),
                pElement->iLine);

    } // end of checking each tag

  } // end of CMUSHclientApp::CheckUsed 


void CMUSHclientApp::HandleLoadException (const char * sComment, CException* e)
  {
  char sMessage [1000];
  e->GetErrorMessage (sMessage, sizeof sMessage);
  e->Delete ();
  LoadError (sComment, sMessage);
  }

void CMUSHclientApp::LoadError (const char * sType, const char * sMessage, UINT iLine)
  {

  CString strTitle = "XML import warnings - ";
  strTitle += strFileName;

  // line defaults to last attribute line
  if (iLine == 0)
    iLine = iLineLastItemFound;

  CString str = TFormat ("Line %4i: %s (%s)%s",
                      iLine,  // which line
                      sMessage,     // what message
                      sType,  // type of thing (eg, trigger)
                      ENDLINE);

  AppendToTheNotepad (strTitle, 
                      str,                 // start new line
                      false,   // append
                      eNotepadWorldLoadError);

  // make sure they see it
  ActivateNotepad (strTitle);

  iErrorCount++;
  }   // end of CMUSHclientApp::LoadError

#define GET_VERSION_AND_DEFAULTS(node) \
  CString strVersion = GetDateAndVersion (*node); \
  bool bUseDefault = true;  \
  Get_XML_boolean (*node, DEFAULTS_LIT, bUseDefault, true); \

// get date and version to avoid "not used" warnings

CString GetDateAndVersion (CXMLelement & node)
  {
CString strVersion = MUSHCLIENT_VERSION, 
        strDate;
  
  Get_XML_string (node, "muclient_version", strVersion, true);
  Get_XML_string (node, "date_saved", strDate, true);

  return strVersion;
  } // end of GetDateAndVersion

void CMUSHclientApp::Load_Comments_XML (CXMLelement & parent)
  {
CString strComment;
int iFlags = 0; // for use by GET_VERSION_AND_DEFAULTS macro

  LOAD_LOOP (parent, "comment", pComments);

    GET_VERSION_AND_DEFAULTS (pComments);
    
    strComment += pComments->strContent;

  END_LOAD_LOOP;

  strComment.TrimLeft ();
  strComment.TrimRight ();

  if (strComment.IsEmpty ())
    return;   // no comment

  strComment += ENDLINE;    // better finish on a new line

  CString strTitle = "XML import notes - ";
  strTitle += strFileName;

  AppendToTheNotepad (strTitle, 
                      strComment,                 // start new line
                      false,   // append
                      eNotepadXMLcomments);

  ActivateNotepad (strTitle);

  } // end of CMUSHclientApp::Load_Comments_XML


CTextDocument * CMUSHclientApp::FindNotepad (const CString strTitle)
  {
CTextDocument * pTextDoc = NULL;

  for (POSITION docPos = App.m_pNormalDocTemplate->GetFirstDocPosition();
      docPos != NULL; )
    {
    pTextDoc = (CTextDocument *) App.m_pWorldDocTemplate->GetNextDoc(docPos);

    // ignore related worlds
    if (pTextDoc->m_pRelatedWorld == NULL &&
       pTextDoc->m_strTitle.CompareNoCase (strTitle) == 0)
      return pTextDoc;      // right title, not attached to a world

    } // end of doing each document


  return NULL;    // not found
  }


bool CMUSHclientApp::AppendToTheNotepad (const CString strTitle,
                                      const CString strText,
                                      const bool bReplace,
                                      const int  iNotepadType)
  {
CTextDocument * pTextDoc = FindNotepad (strTitle);

  if (pTextDoc)
    {
    // append to the view
    POSITION pos=pTextDoc->GetFirstViewPosition();

    if (pos)
      {
      CView* pView = pTextDoc->GetNextView(pos);

      if (pView->IsKindOf(RUNTIME_CLASS(CTextView)))
        {
        CTextView* pmyView = (CTextView*)pView;

        // find actual window length for appending [#422]

        int iLength = pmyView->GetWindowTextLength ();

        if (bReplace)
          pmyView->GetEditCtrl ().SetSel (0, -1, FALSE);
        else
          pmyView->GetEditCtrl ().SetSel (iLength, iLength, FALSE);
        pmyView->GetEditCtrl ().ReplaceSel (strText);
        return true;
        } // end of having the right type of view
      }   // end of having a view
    return false;
    } // end of having an existing notepad document

  BOOL bOK = CreateTextWindow (strText,     // contents
                      strTitle,     // title
                      NULL,   // document
                      0,      // document number
                      App.m_strDefaultInputFont,
                      App.m_iDefaultInputFontHeight,
                      App.m_iDefaultInputFontWeight,
                      RGB (0, 0, 0),
                      RGB (255, 255, 255),
                      "",     // search string
                      "",       // line preamble
                      false,
                      false,
                      false,
                      false,  
                      false,
                      false,
                      iNotepadType
                      );

  return bOK;
  }   // end of CMUSHclientApp::AppendToTheNotepad

BOOL CMUSHclientApp::ActivateNotepad(LPCTSTR Title) 
{
CTextDocument * pTextDoc = FindNotepad (Title);

  if (pTextDoc)
    {
    // activate the view
    POSITION pos=pTextDoc->GetFirstViewPosition();

    if (pos)
      {
      CView* pView = pTextDoc->GetNextView(pos);

      if (pView->IsKindOf(RUNTIME_CLASS(CTextView)))
        {
        CTextView* pmyView = (CTextView*)pView;
        pmyView->GetParentFrame ()->ActivateFrame ();
        pmyView->GetParentFrame ()->SetActiveView(pmyView);
        return true;
        } // end of having the right type of view
      }   // end of having a view
    } // end of having an existing notepad document
 return false;
}


void CMUSHclientApp::Load_Global_XML (CXMLelement & parent)
  {
int iFlags = 0; // for use by GET_VERSION_AND_DEFAULTS macro
  // see if we have "global" tag(s)

  LOAD_LOOP (parent, "global", pWorldElement);

    GET_VERSION_AND_DEFAULTS (pWorldElement);

    // worlds consist of:
    // 1. single-line alpha attributes, eg.  script_language="VBscript"
    // 2. numeric attributes, eg. wrap_column="80"
    // 3. multi-line alpha children, eg. <notes>blah</notes>
    
    // get numeric ones first, so we know if various options are enabled
    Load_Global_Numeric_Options_XML (*pWorldElement, bUseDefault);
    Load_Global_Single_Line_Alpha_Options_XML (*pWorldElement, bUseDefault);
//    Load_World_Multi_Line_Alpha_Options_XML (*pWorldElement, bUseDefault);

    Load_Startup_Worlds_XML (*pWorldElement);

    Load_Keymaps_XML (*pWorldElement);

    CheckUsed (*pWorldElement);


  END_LOAD_LOOP;

  } // end of CMUSHclientDoc::Load_General_XML

void CMUSHclientApp::Load_Global_Numeric_Options_XML (CXMLelement & parent,
                                                    bool bUseDefault)
  {
int iFlags = 0; // for use by GET_VERSION_AND_DEFAULTS macro

  for (int i = 0; GlobalOptionsTable [i].pName; i++)
    {
    try
      {
      COLORREF cValue;
      if (GlobalOptionsTable [i].iFlags & OPT_RGB_COLOUR)
        {
        if (Get_XML_colour (parent, 
                            GlobalOptionsTable [i].pName, 
                            cValue,
                            bUseDefault || m_NumericConfiguration [i]->bInclude))
          SetOptionItem (i, cValue);
        else // not found, use default?
          if (!(bUseDefault || m_NumericConfiguration [i]->bInclude))
             SetOptionItem (i, 0);       // then take zero
        }   // end of RGB colour
      else
      if (GlobalOptionsTable [i].iMinimum == 0 &&
          GlobalOptionsTable [i].iMaximum == 0)
        {
        bool bValue;
        if (Get_XML_boolean (parent, 
                            GlobalOptionsTable [i].pName, 
                            bValue,
                            bUseDefault || m_NumericConfiguration [i]->bInclude))
          SetOptionItem (i, bValue);
        else // not found, use default?
          if (!(bUseDefault || m_NumericConfiguration [i]->bInclude))
            SetOptionItem (i, 0);        // then take zero
  
        }   // end of boolean 
      else
        {   // not RGB colour or boolean
        long iValue;
        if (Get_XML_number (parent, 
                            GlobalOptionsTable [i].pName, 
                            iValue,
                            bUseDefault || m_NumericConfiguration [i]->bInclude,
                            GlobalOptionsTable [i].iMinimum,
                            GlobalOptionsTable [i].iMaximum))
          SetOptionItem (i, iValue);
        else // not found, use default?
          if (!(bUseDefault || m_NumericConfiguration [i]->bInclude))
            {             // then take zero, if we can
            if (0 < GlobalOptionsTable [i].iMinimum ||
                0 > GlobalOptionsTable [i].iMaximum)
               ThrowErrorException ("Value of zero not acceptable here");

            SetOptionItem (i, 0);

            }  // end of taking zero for unspecified variables
        } // end of not RGB colour or boolean
      }
    catch (CException* e)
      {
      HandleLoadException (TFormat ("option '%s' not set", (LPCTSTR) GlobalOptionsTable [i].pName), e);
      } // end of catch
    }

  } // end of CMUSHclientApp::Load_Global_Numeric_Options_XML

void CMUSHclientApp::Load_Global_Single_Line_Alpha_Options_XML (CXMLelement & parent,
                                                              bool bUseDefault)
  {
int iFlags = 0; // for use by GET_VERSION_AND_DEFAULTS macro

  CString strValue;
  for (int i = 0; AlphaGlobalOptionsTable [i].pName; i++)
    if (!(AlphaGlobalOptionsTable [i].iFlags & OPT_MULTLINE))
      {
      try
        {
        if (Get_XML_string (parent, 
                            AlphaGlobalOptionsTable [i].pName, 
                            strValue, 
                            bUseDefault || m_AlphaConfiguration [i]->bInclude,
                            !(AlphaGlobalOptionsTable [i].iFlags & OPT_KEEP_SPACES)))
          {

      // found it - set value
          SetAlphaOptionItem (i, strValue);
          } // end of found
        else
          if (!(bUseDefault || m_AlphaConfiguration [i]->bInclude))
            if ((AlphaGlobalOptionsTable [i].iFlags & OPT_WORLD_ID))
              SetAlphaOptionItem (i, GetUniqueID ());   // get world ID, not a blank one
            else
              SetAlphaOptionItem (i, "");

        } // end of try block
      catch (CException* e)
        {
        HandleLoadException (TFormat ("option '%s' not set", (LPCTSTR) AlphaGlobalOptionsTable [i].pName), e);
        } // end of catch
      }  // end of single-line options

  } // end of CMUSHclientApp::Load_Global_Single_Line_Alpha_Options_XML 


void CMUSHclientApp::AllocateConfigurationArrays (void)
  {
int i; 

  m_NumericConfiguration.SetSize (NUMITEMS (GlobalOptionsTable));

  for (i = 0; i < NUMITEMS (GlobalOptionsTable); i++)
     m_NumericConfiguration.SetAt (i, new CNumericConfiguration);


  m_AlphaConfiguration.SetSize (NUMITEMS (AlphaGlobalOptionsTable));

  for (i = 0; i < NUMITEMS (AlphaGlobalOptionsTable); i++)
     m_AlphaConfiguration.SetAt (i, new CAlphaConfiguration);

  }

void CMUSHclientApp::ResetConfigurationArrays (void)
  {
int i; 

  for (i = 0; i < NUMITEMS (GlobalOptionsTable); i++)
     m_NumericConfiguration [i]->Reset ();


  for (i = 0; i < NUMITEMS (AlphaGlobalOptionsTable); i++)
     m_AlphaConfiguration [i]->Reset ();

  }

void CMUSHclientApp::DeleteConfigurationArrays (void)
  {
int i; 

  for (i = 0; i < NUMITEMS (GlobalOptionsTable); i++)
     delete m_NumericConfiguration  [i];

  m_NumericConfiguration.RemoveAll ();

  for (i = 0; i < NUMITEMS (AlphaGlobalOptionsTable); i++)
     delete m_AlphaConfiguration  [i];

  m_AlphaConfiguration.RemoveAll ();

  }

long CMUSHclientApp::GetOptionIndex (LPCTSTR OptionName) 
  {

int iItem;
int iResult = FindBaseOption (OptionName, GlobalOptionsTable, iItem);

  if (iResult != 0)
    return -1;
  
  return iItem;
  
  }   // end of CMUSHclientApp::GetOptionIndex 

long CMUSHclientApp::SetOptionItem (const int iItem, 
                                    long Value) 
  {
bool bChanged;

return SetBaseOptionItem (iItem, 
                                  GlobalOptionsTable, 
                                  NUMITEMS (GlobalOptionsTable),
                                  (char *) this,
                                  Value,
                                  bChanged);  

  } // end of CMUSHclientApp::SetOptionItem

long CMUSHclientApp::SetAlphaOptionItem (const int iItem, 
                                        LPCTSTR sValue)
  {
  
bool bChanged;
CString strValue = sValue;
return SetBaseAlphaOptionItem (iItem,
                        AlphaGlobalOptionsTable,
                        NUMITEMS (AlphaGlobalOptionsTable),
                        (char *) this,
                        strValue,
                        bChanged);


  }  // end of CMUSHclientApp:SetAlphaOptionItem 

#define NL "\r\n"

void CMUSHclientApp::WriteGlobalPrefs (void)
  {
bool bOK;
CFile * f = NULL;
CArchive * ar = NULL;

  do
    {
    try
      {
      f = new CFile (m_GlobalConfigurationFileName, 
                      CFile::modeCreate | CFile::modeReadWrite);

      ar = new CArchive(f, CArchive::store);

      // better get encoding right :)
      CString strEncoding = "iso-8859-1";

      if (m_bUTF_8)
         strEncoding = "UTF-8";

      // xml prolog
      ar->WriteString (
          CFormat ("<?xml version=\"1.0\" encoding=\"%s\"?>" NL,
          strEncoding));

      // document type is world
      ar->WriteString ("<!DOCTYPE muclient>" NL);

      // when did we save it?
      ar->WriteString (CFormat ("<!-- Saved on %s -->" NL, 
            FixHTMLString (CTime::GetCurrentTime().Format 
            (TranslateTime ("%A, %B %d, %Y, %#I:%M %p")))));

      // which version was it?
      ar->WriteString (CFormat 
            ("<!-- MuClient version %s -->" NL, MUSHCLIENT_VERSION));

      ar->WriteString ("<!-- Written by Nick Gammon -->" NL);
      ar->WriteString ("<!-- Home Page: http://www.mushclient.com/ -->" NL);

      // ok - ready to go
      ar->WriteString ("<muclient>" NL);

      Save_Global_XML (*ar);

      // last line in XML file
      ar->WriteString ("</muclient>" NL);

      bOK = true;

      } // end of try block

    catch (CFileException * e)
      {
      ::TMessageBox ("Unable to create the requested file", MB_ICONEXCLAMATION);
      e->Delete ();
      bOK = false;
      } // end of catching a file exception

    catch (CMemoryException * e)
      {
      ::TMessageBox ("Insufficient memory to do this operation", MB_ICONEXCLAMATION);
      e->Delete ();
      bOK = false;
      } // end of catching a memory exception

    catch (CArchiveException * e)
      {
      ::TMessageBox ("There was a problem in the data format", MB_ICONEXCLAMATION);
      e->Delete ();
      bOK = false;
      } // end of catching an archive exception


    delete ar;      // delete archive
    ar = NULL;
    delete f;       // delete file
    f = NULL;

    if (!bOK)
      {
      CGlobalConfigurationDlg dlg;

      if (dlg.DoModal () != IDOK)
        {
        GetGlobalPrefsFromRegistry ();
        return;   // give up
        }

      // otherwise go around loop, try loading again

      } // end of not loading OK

    } while (!bOK);

  }

void CMUSHclientApp::GetGlobalPrefsFromRegistry (void)
  {

  char localeBuf [10];

  // find 2-character country ID
  GetLocaleInfo (GetUserDefaultLCID (), LOCALE_SABBREVLANGNAME, localeBuf, sizeof (localeBuf));
  localeBuf [2] = 0;  // truncate to 2 characters

  // read global prefs from the Registry

  m_bAutoConnectWorlds              = GetProfileInt     ("Global prefs", "AutoConnectWorlds",             1);
  m_bReconnectOnLinkFailure         = GetProfileInt     ("Global prefs", "ReconnectOnLinkFailure",        0);
  m_bOpenWorldsMaximised            = GetProfileInt     ("Global prefs", "OpenWorldsMaximised",           0);
  m_iWindowTabsStyle                = GetProfileInt     ("Global prefs", "WindowTabsStyle",               0);
  m_bConfirmBeforeClosingWorld      = GetProfileInt     ("Global prefs", "ConfirmBeforeClosingWorld",     1);
  m_bConfirmBeforeClosingMushclient = GetProfileInt     ("Global prefs", "ConfirmBeforeClosingMushclient",1);
  m_bConfirmBeforeClosingMXPdebug   = GetProfileInt     ("Global prefs", "ConfirmBeforeClosingMXPdebug",  0);
  m_bConfirmBeforeSavingVariables   = GetProfileInt     ("Global prefs", "ConfirmBeforeSavingVariables",  1);
  m_bNotifyOnDisconnect             = GetProfileInt     ("Global prefs", "NotifyOnDisconnect",            1);
  m_bNotifyIfCannotConnect          = GetProfileInt     ("Global prefs", "NotifyIfCannotConnect",         1);
  m_bErrorNotificationToOutputWindow= GetProfileInt     ("Global prefs", "ErrorNotificationToOutputWindow", 0);
  m_bConfirmLogFileClose            = GetProfileInt     ("Global prefs", "ConfirmLogFileClose",           1);
  m_nPrinterFontSize                = GetProfileInt     ("Global prefs", "PrinterFontSize",               10);
  m_nPrinterLeftMargin              = GetProfileInt     ("Global prefs", "PrinterLeftMargin",             15);
  m_nPrinterTopMargin               = GetProfileInt     ("Global prefs", "PrinterTopMargin",              15);
  m_nPrinterLinesPerPage            = GetProfileInt     ("Global prefs", "PrinterLinesPerPage",           60);
  m_bAutoLogWorld                   = GetProfileInt     ("Global prefs", "AutoLogWorld",                  0);
  m_bAppendToLogFiles               = GetProfileInt     ("Global prefs", "AppendToLogFiles",              0);
  m_nTimerInterval                  = GetProfileInt     ("Global prefs", "TimerInterval",                 0);
  m_strPrinterFont                  = GetProfileString  ("Global prefs", "PrinterFont",                "Courier");
  m_strDefaultLogFileDirectory      = GetProfileString  ("Global prefs", "DefaultLogFileDirectory",    ".\\logs\\");
  m_strDefaultWorldFileDirectory    = GetProfileString  ("Global prefs", "DefaultWorldFileDirectory",    ".\\worlds\\");
  m_strPluginsDirectory             = GetProfileString  ("Global prefs", "PluginsDirectory",  m_strDefaultWorldFileDirectory + "plugins\\");
  m_strLuaScript                    = GetProfileString  ("Global prefs", "LuaScript",  lalala);

  CString strWorldList              = GetProfileString  ("Global prefs", "WorldList",                  "");
  StringToList (strWorldList, "*", m_WorldList);

  CString strPluginList              = GetProfileString  ("Global prefs", "PluginList",                  "");
  StringToList (strPluginList, "*", m_PluginList);

  m_bOpenActivityWindow             = GetProfileInt     ("Global prefs", "OpenActivityWindow",            0);
  m_nActivityWindowRefreshInterval  = GetProfileInt     ("Global prefs", "ActivityWindowRefreshInterval", 15);
  m_nActivityWindowRefreshType      = GetProfileInt     ("Global prefs", "ActivityWindowRefreshType", eRefreshBoth);
  m_bAlwaysOnTop                    = GetProfileInt     ("Global prefs", "AlwaysOnTop", 0);
  m_bDisableKeyboardMenuActivation  = GetProfileInt     ("Global prefs", "DisableKeyboardMenuActivation", 0);
  m_bEnableSpellCheck               = GetProfileInt     ("Global prefs", "EnableSpellCheck", 0);
  m_bEnablePackageLibrary           = GetProfileInt     ("Global prefs", "AllowLoadingDlls", 0);
  m_strWordDelimiters               = GetProfileString  ("Global prefs", "WordDelimiters",         ".,()[]\"\'");
  m_strWordDelimitersDblClick       = GetProfileString  ("Global prefs", "WordDelimitersDblClick", ".,()[]\"\'");
  m_bAutoExpand                     = GetProfileInt     ("Global prefs", "AutoExpandConfig",  0);
  m_bFlatToolbars                   = GetProfileInt     ("Global prefs", "FlatToolbars",  0);
  m_bColourGradient                 = GetProfileInt     ("Global prefs", "ColourGradientConfig",  1);
  m_bBleedBackground                = GetProfileInt     ("Global prefs", "BleedBackground",  0);
  m_bFixedFontForEditing            = GetProfileInt     ("Global prefs", "FixedFontForEditing",  1);
  m_bRegexpMatchEmpty               = GetProfileInt     ("Global prefs", "RegexpMatchEmpty",  1);
  m_bSmootherScrolling              = GetProfileInt     ("Global prefs", "SmootherScrolling",  0);
  m_bSmoothScrolling                = GetProfileInt     ("Global prefs", "SmoothScrolling",  0);
  m_bShowGridLinesInListViews       = GetProfileInt     ("Global prefs", "ShowGridLinesInListViews",  1);
  m_bTriggerRemoveCheck             = GetProfileInt     ("Global prefs", "TriggerRemoveCheck",  1);
  m_bF1macro                        = GetProfileInt     ("Global prefs", "F1macro",  1);
  m_bAllTypingToCommandWindow       = GetProfileInt     ("Global prefs", "AllTypingToCommandWindow",  1);
  m_strLocale                       = GetProfileString  ("Global prefs", "Locale", localeBuf);
  m_strFixedPitchFont               = GetProfileString  ("Global prefs", "FixedPitchFont", "FixedSys");
  m_iFixedPitchFontSize             = GetProfileInt     ("Global prefs", "FixedPitchFontSize", 9);

  // defaults
  m_strDefaultColoursFile        = GetProfileString  ("Global prefs", "DefaultColoursFile",   "");      
  m_strDefaultTriggersFile       = GetProfileString  ("Global prefs", "DefaultTriggersFile",  "");          
  m_strDefaultAliasesFile        = GetProfileString  ("Global prefs", "DefaultAliasesFile",   "");             
  m_strDefaultMacrosFile         = GetProfileString  ("Global prefs", "DefaultMacrosFile",    "");      
  m_strDefaultTimersFile         = GetProfileString  ("Global prefs", "DefaultTimersFile",   "");          
  m_strDefaultOutputFont         = GetProfileString  ("Global prefs", "DefaultOutputFont",  MUSHCLIENT_DEFAULT_FONT);             
  m_strDefaultInputFont          = GetProfileString  ("Global prefs", "DefaultInputFont",   MUSHCLIENT_DEFAULT_FONT);                    
                                                                                                                            
  m_iDefaultInputFontHeight      = GetProfileInt     ("Global prefs", "DefaultInputFontHeight",            9);            
  m_iDefaultInputFontWeight      = GetProfileInt     ("Global prefs", "DefaultInputFontWeight", FW_NORMAL);           
  m_iDefaultInputFontItalic      = GetProfileInt     ("Global prefs", "DefaultInputFontItalic", 0);     
  m_iDefaultOutputFontHeight     = GetProfileInt     ("Global prefs", "DefaultOutputFontHeight", 9);                              

  m_bNotepadWordWrap             =  GetProfileInt     ("Global prefs", "NotepadWordWrap", 1); 
  m_cNotepadTextColour           =  GetProfileInt     ("Global prefs", "NotepadTextColour", 0); 
  m_cNotepadBackColour           =  GetProfileInt     ("Global prefs", "NotepadBackColour", 0); 
  m_nParenMatchFlags             =  GetProfileInt     ("Global prefs", "ParenMatchFlags", 
                        PAREN_NEST_BRACES | PAREN_BACKSLASH_ESCAPES | PAREN_PERCENT_ESCAPES); 
  m_strNotepadQuoteString        = GetProfileString  ("Global prefs", "NotepadQuoteString",     "> ");                    

  m_iAsciiArtLayout              = GetProfileInt     ("Global prefs", "AsciiArtLayout", 0);                              
  m_strAsciiArtFont              = GetProfileString  ("Global prefs", "AsciiArtFont",     "fonts\\standard.flf");                    
  
  m_iActivityButtonBarStyle      = GetProfileInt     ("Global prefs", "ActivityButtonBarStyle", 0);                              
  m_strTrayIconFileName          = GetProfileString  ("Global prefs", "TrayIconFileName",     "");                    
  }

void CMUSHclientApp::Save_Version_And_Date_XML (CArchive& ar)
  {

  Save_XML_string (ar, "muclient_version", MUSHCLIENT_VERSION);
  Save_XML_date   (ar, "date_saved", CTime::GetCurrentTime());

  } // end of CMUSHclientApp::Save_Version_And_Date_XML

void CMUSHclientApp::Save_Global_XML (CArchive& ar)
  {

  ar.WriteString ("<global" NL);

// identifying stuff to help us later on

  Save_Version_And_Date_XML (ar);

  // blank line to separate above stuff from actual world data
  ar.WriteString (NL);

  // first do the fixed attributes for the whole world

  // single-line alpha
  Save_Global_Single_Line_Alpha_Options_XML (ar);

  // blank line to separate alpha from booleans :)
  ar.WriteString (NL);

  // numbers, booleans
  Save_Global_Numeric_Options_XML (ar);

  ar.WriteString ("   > <!-- end of global attributes -->" NL);    // end of global attributes

  // multi-line alpha
//  Save_World_Multi_Line_Alpha_Options_XML (ar);

  ar.WriteString (NL "</global>" NL);

  }

void CMUSHclientApp::Save_Global_Numeric_Options_XML (CArchive& ar)
  {
  for (long i = 0; GlobalOptionsTable [i].pName; i++)
    {
    long iValue = GetOptionItem (i);

    // skip included values that have not been changed
    if (m_NumericConfiguration [i]->bInclude &&
        iValue == m_NumericConfiguration [i]->iValue)
        continue;          

    // do booleans differently so they come out as "y" or "n"
    if (GlobalOptionsTable [i].iMinimum == 0 &&
        GlobalOptionsTable [i].iMaximum == 0)
      Save_XML_boolean (ar, GlobalOptionsTable [i].pName, iValue, false, true);
    else
      // RGB colours are different, also
      if (GlobalOptionsTable [i].iFlags & OPT_RGB_COLOUR)
        Save_XML_colour (ar, GlobalOptionsTable [i].pName, iValue, false, true);
      else
        Save_XML_number (ar, GlobalOptionsTable [i].pName, iValue, false, true);

    } // end of looping through each option

  } // end of CMUSHclientApp::Save_World_Numeric_Options_XML

void CMUSHclientApp::Save_Global_Single_Line_Alpha_Options_XML (CArchive& ar)
  {

  for (int i = 0; AlphaGlobalOptionsTable [i].pName; i++)
    if (!(AlphaGlobalOptionsTable [i].iFlags & OPT_MULTLINE))
      {
      CString strValue =  GetAlphaOptionItem (i);

      // skip included values that have not been changed
      if (m_AlphaConfiguration [i]->bInclude &&
          strValue == m_AlphaConfiguration [i]->sValue)
          continue;          
      
      if ((AlphaGlobalOptionsTable [i].iFlags & OPT_PASSWORD) && !strValue.IsEmpty ())
        {
        strValue = encodeBase64 (strValue, false);  // base 64 encoding
        Save_XML_boolean (ar, 
                          CFormat ("%s_base64", (LPCTSTR) AlphaGlobalOptionsTable [i].pName), 
                          true, false, true);
        }
      Save_XML_string (ar, AlphaGlobalOptionsTable [i].pName, strValue, false, true);
      }    // end of looping through each option

  } // end of CMUSHclientApp::Save_World_Single_Line_Alpha_Options_XML 


long CMUSHclientApp::GetOptionItem (const int iItem)
  {
  return GetBaseOptionItem (iItem, 
                            GlobalOptionsTable, 
                            NUMITEMS (GlobalOptionsTable),
                            (char *) this);  
  }

CString CMUSHclientApp::GetAlphaOptionItem (const int iItem)
  {

  return GetBaseAlphaOptionItem (iItem, 
                                 AlphaGlobalOptionsTable,
                                 NUMITEMS (AlphaGlobalOptionsTable),
                                 (char *) this);

  }  // end of CMUSHclientApp::GetAlphaOptionItem


void CMUSHclientApp::SetDefaults (void)
  {
  for (int i = 0; GlobalOptionsTable [i].pName; i++)
      SetOptionItem (i, GlobalOptionsTable [i].iDefault);
  }   // end of CMUSHclientDoc::SetDefaults


void CMUSHclientApp::SetAlphaDefaults (void)
  {
  for (int i = 0; AlphaGlobalOptionsTable [i].pName; i++)
      SetAlphaOptionItem (i, AlphaGlobalOptionsTable [i].sDefault);
  }   // end of CMUSHclientDoc::SetAlphaDefaults


UINT CMUSHclientApp::Load_Startup_Worlds_XML (CXMLelement & parent)
  {
UINT count = 0;

  LOAD_LOOP (parent, "startup_worlds", pStartupWorlds);

    GET_VERSION_AND_DEFAULTS (pStartupWorlds);

    LOAD_LOOP (*pStartupWorlds, "startup_world", pElement);

    try
      {
      Load_One_Startup_World_XML (*pElement, strVersion, bUseDefault);
      count++;
      }
    catch (CException* e)
      {
      HandleLoadException ("startup world not loaded", e);
      } // end of catch

    END_LOAD_LOOP;

    CheckUsed (*pStartupWorlds);   // check we used all attributes

  END_LOAD_LOOP;

  return count;
  }   // end of CMUSHclientApp::Load_Startup_Worlds_XML 


void CMUSHclientApp::Load_One_Startup_World_XML (CXMLelement & node, 
                                         const CString strVersion, 
                                         bool bUseDefault)
  {
CString strWorldName;

  try
    {
    Get_XML_boolean (node, DEFAULTS_LIT, bUseDefault, true); // see if they want defaults

    GET_STRING_TRIM  ("name",           strWorldName);

    } // end of try

  catch(CException*)
    {
    throw;
    }

  // now add to our internal timer map


  m_WorldList.AddTail (strWorldName);

  CheckUsed (node);   // check we used all attributes

  } // end of CMUSHclientApp::Load_One_Startup_World_XML

UINT CMUSHclientApp::Load_Keymaps_XML (CXMLelement & parent)
  {
UINT count = 0;

  LOAD_LOOP (parent, "keymaps", pKeymaps);

    GET_VERSION_AND_DEFAULTS (pKeymaps);

    LOAD_LOOP (*pKeymaps, "keymap", pElement);

    try
      {
      Load_One_Keymap_XML (*pElement, strVersion, bUseDefault);
      count++;
      }
    catch (CException* e)
      {
      HandleLoadException ("keymap not loaded", e);
      } // end of catch

    END_LOAD_LOOP;

    CheckUsed (*pKeymaps);   // check we used all attributes

  END_LOAD_LOOP;      

  return count;
  }   // end of CMUSHclientApp::Load_Keymaps_XML


extern tCommandIDMapping CommandIDs [];
extern tVirtualKeyMapping VirtualKeys [];

void CMUSHclientApp::Load_One_Keymap_XML (CXMLelement & node, 
                                         const CString strVersion, 
                                         bool bUseDefault)
  {
CString strKey,
        strFunction,
        strSend,
        strProcedure,
        strType;

CKeymap * k = new CKeymap;

  try
    {

		Get_XML_boolean (node, DEFAULTS_LIT, bUseDefault, true); // see if they want defaults

		GET_STRING_TRIM   ("key", strKey);
		GET_STRING_TRIM   ("function", strFunction);
		GET_STRING_TRIM   ("script", strProcedure);
		GET_STRING_TRIM   ("type", strType);

    if (strKey.IsEmpty ())
			ThrowErrorException ("No keystroke specified");

		// find <send> text
	  FindNodeContents (node, "send", strSend, true);

		k->cmd = StringToCommandID (strFunction);

		if (k->cmd == 0 && !strFunction.IsEmpty ())
			ThrowErrorException ("Command named \"%s\" not recognised", (LPCTSTR) strFunction);


    if (!strProcedure.IsEmpty ())
      ThrowErrorException ("Key %s calls script %s however global keys cannot call script functions",
                          (LPCTSTR) strKey, (LPCTSTR) strProcedure);

		if (strFunction.IsEmpty () && strSend.IsEmpty () && strProcedure.IsEmpty ())
			ThrowErrorException ("No action specified for key %s",
                            (LPCTSTR) strKey);

    StringToKeyCode (strKey, k->fVirt, k->key);

    k->strSend = strSend;
    k->strProcedure = strProcedure;

    // see if they try to slip in a command, script and send text all at once
    int iCount = 0;
    if (!strFunction.IsEmpty ())
      iCount++;
    if (!strSend.IsEmpty ())
      iCount++;
    if (!strProcedure.IsEmpty ())
      iCount++;

    if (iCount > 1)
       ThrowErrorException ("Can only have one action for a macro");

    unsigned short iType = SEND_NOW;   // default to send now

    if (strType == "replace")
      iType = REPLACE_COMMAND;
    else  if (strType == "send_now")
      iType = SEND_NOW;
    else  if (strType == "insert")
      iType = ADD_TO_COMMAND;
    else 
      if (!strType.IsEmpty ()) 
        ThrowErrorException ("Macro type \"%s\" not recognised", (LPCTSTR) strType);

    k->iType = iType;    // remember type

    } // end of try

  catch(CException*)
    {
    delete k; // get rid of keymap
    throw;
    }

  // now add to our internal keymap list

  k->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks
  m_KeymapList.AddTail (k);

  CheckUsed (node);   // check we used all attributes

  } // end of CMUSHclientApp::Load_One_Keymap_XML

