areaeditor/
/*
** AreaEditor - a program for editing SMAUG and ROM area files.
** Author: Nick Gammon
** http://www.gammon.com.au/
** See Copyright Notice at the end of AreaEditor.h
*/


#include "stdafx.h"

#include "AreaEditor.h"
#include "defaults.h"


#define SPACES " \t\r\n"

class CMUDitem;


inline string trim_right (const string & s, const string & t = SPACES)
  { 
  string d (s); 
  string::size_type i (d.find_last_not_of (t));
  if (i == string::npos)
    return "";
  else
   return d.erase (d.find_last_not_of (t) + 1) ; 
  }  // end of trim_right

inline string trim_left (const string & s, const string & t = SPACES) 
  { 
  string d (s); 
  return d.erase (0, s.find_first_not_of (t)) ; 
  }  // end of trim_left

inline string trim (const string & s, const string & t = SPACES)
  { 
  string d (s); 
  return trim_left (trim_right (d, t), t) ; 
  }  // end of trim

// string find-and-replace
inline string FindAndReplace
  (const string& source, const string target, const string replacement)
  {
  string str = source;
  string::size_type pos = 0,   // where we are now
                    found;     // where the found data is
  while ((found = str.find (target, pos)) != string::npos)
    {
    str.replace (found, target.size (), replacement);
    pos = found + replacement.size ();
    }
  return str;
  };   // end of FindAndReplace

string GetWord (string & s, 
                const string delim = " ",
                const bool trim_spaces = true);

void StringToVector (const string s, 
                     vector<string> & v,
                     const string delim = " ", 
                     const bool trim_spaces = true);

string VectorToString (const vector<string> & v, 
                       const string delim = " ");


bool FixWrap (CEdit & ctlEdit, const bool bEndline)
  {
  CString strOldText, strNewText;
  vector<string> v;
  string s;

  // don't start putting in trailing ENDLINEs if no text in the window to start with
  if (ctlEdit.GetWindowTextLength () == 0)
    return false;

  ctlEdit.GetWindowText (strOldText);

  StringToVector ((LPCTSTR) strOldText, v, ENDLINE);

  // we need to fix up the text with linefeeds at the end of each line

  for (int i = 0; i < ctlEdit.GetLineCount (); i++)
    {
    int iStart = ctlEdit.LineIndex (i),
        iEnd = ctlEdit.LineIndex (i + 1);
    
    if (iEnd == -1) // past end, take end of string
      iEnd = strOldText.GetLength ();

    CString strLine = strOldText.Mid (iStart, iEnd - iStart);
    strLine.Replace (ENDLINE, "");

    if (strLine.GetLength () > MAXDESCRIPTIONWIDTH)
      {
      ::AfxMessageBox (CFormat ("The contents of line %i exceeds the maximum permitted line width.\n"
                       "Please use the \"Edit\" button to edit the text.", i + 1),
                      MB_ICONEXCLAMATION);
      return true;
      }

    if (!strNewText.IsEmpty ())   // add a newline to all lines except the first
      strNewText += ENDLINE;

    strNewText += strLine;
    }

   if (bEndline)
     {
     // make sure it has a trailing ENDLINE
     if (strNewText.Right (strlen (ENDLINE)) != ENDLINE)
        strNewText += ENDLINE;
     }
   else
     {
     // make sure it has does NOT have a trailing ENDLINE
     if (strNewText.Right (strlen (ENDLINE)) == ENDLINE)
       strNewText = strNewText.Left (strNewText.GetLength () - 2);
     }

    // put new data back into control with endlines in place
    ctlEdit.SetWindowText (strNewText);
//    ::AfxMessageBox (strNewText);   //NJG
    return false;   // no errors

  }

// this is called to ensure we don't have tilde's in descriptions and names
void CheckForTilde (CDataExchange* pDX, int nIDC, CString& value)
  {
    if (value.Find ('~') != -1)
      {
      DDX_Text(pDX, nIDC, value);
      ::AfxMessageBox ("This field is not permitted to have a tilde (~) character in it", 
                       MB_ICONEXCLAMATION);
      pDX->Fail();
      }

  } // end of CheckForTilde


// Helper routine for setting the font in certain description windows to a
// fixed-pitch courier, to make editing easier.

void FixFont (ptrCFont & pFont, CEdit & editctrl)
  {

   delete pFont;         // get rid of old font

   pFont = new CFont;    // create new font

   if (pFont)
    {

    int iSize = App.GetProfileInt (sProfilePreferences, sProfileScreenFontSize, 9);
    CString strName = App.GetProfileString (sProfilePreferences, sProfileScreenFontName, "FixedSys");
    
    CDC dc;

    dc.CreateCompatibleDC (NULL);

     int lfHeight = -MulDiv(iSize, 
                    dc.GetDeviceCaps(LOGPIXELSY), 72);

     pFont->CreateFont(lfHeight, // int nHeight, 
				    0, // int nWidth, 
				    0, // int nEscapement, 
				    0, // int nOrientation, 
				    FW_NORMAL, // int nWeight, 
				    0, // BYTE bItalic, 
				    0, // BYTE bUnderline, 
            0, // BYTE cStrikeOut, 
            0, // BYTE nCharSet, 
            0, // BYTE nOutPrecision, 
            0, // BYTE nClipPrecision, 
            0, // BYTE nQuality, 
            FIXED_PITCH, // BYTE nPitchAndFamily,  
            strName);// LPCTSTR lpszFacename );

      // Get the metrics of the font.

      dc.SelectObject(pFont);

      if (pFont)
        editctrl.SendMessage (WM_SETFONT,
                                   (WPARAM) pFont->m_hObject,
                                   MAKELPARAM (TRUE, 0));
     }  // end of having a font to select


  }   // end of FixFont


// we load up quite a few combo boxes from pointer arrays
// centralise it all here

void LoadComboBox (CComboBox & ctlComboBox, 
                   const tpNames pNames, 
                   const int iMaxItems,
                   const int iSelected)
  {
  int i,
      sel;

  // empty combo box

  ctlComboBox.ResetContent ();

  // load combo box from pointer array

  for (i = 0; i < iMaxItems; i++)
    {
    int nItem = ctlComboBox.AddString (pNames [i]);
    if (nItem != CB_ERR && nItem != CB_ERRSPACE)
      ctlComboBox.SetItemData (nItem, i); // for recalling later
    }

  // if selected (ie. current) item is in range, select it

  if (iSelected >= 0 && iSelected < iMaxItems)
    {
    sel = ctlComboBox.FindStringExact(-1, pNames [iSelected]);
    if (sel != CB_ERR)
      ctlComboBox.SetCurSel(sel);
    }
  else
    ctlComboBox.SetCurSel(-1);


  }   // end of LoadComboBox

// and here if the source is a list

void LoadComboBoxList (CComboBox & ctlComboBox, 
                       const CConfigList & WhichList, 
                       const int iSelected)
  {

  tList selectedItem;
  bool  bFoundIt = false;

  // empty combo box

  ctlComboBox.ResetContent ();

    // load combo box from list

  for (POSITION pos = WhichList.list.GetHeadPosition (); pos; )
    {
    tList listItem = WhichList.list.GetNext (pos);

    int nItem = ctlComboBox.AddString (listItem.strName);
    if (nItem != CB_ERR && nItem != CB_ERRSPACE)
      {
      ctlComboBox.SetItemData (nItem, listItem.iValue); // for recalling later
      if (listItem.iValue == iSelected)
        {
        bFoundIt = true;
        selectedItem = listItem;
        }
      }
    } // end of loop

  // if selected (ie. current) item is in range, select it
  // we do it here in case the list is sorted

  if (bFoundIt)
    {
    int sel = ctlComboBox.FindStringExact(-1, selectedItem.strName);
    if (sel != CB_ERR)
      ctlComboBox.SetCurSel(sel);
    }
  else
    ctlComboBox.SetCurSel(-1);

  }   // end of LoadComboBoxList

// find which item in combo box, if any, was selected (returns true if found)

bool UnloadComboBox (CComboBox & ctlComboBox, 
                     int & iSelected)
  {
  int sel = ctlComboBox.GetCurSel ();
  if (sel == CB_ERR)
    return false;   // nothing selected

  iSelected = ctlComboBox.GetItemData (sel);
  return true;

  }   // end of LoadComboBox

void FixupVnum (int& iVnum,
                const int * const pOldVnum, 
                const int * const pNewVnum, 
                const int iMax)
 {

  // search array for match, if found return corresponding new vnum
  for (int i = 0; i < iMax; i++)
    if (iVnum == pOldVnum [i])
      {
      iVnum = pNewVnum [i];
      return;
      }

  // no match, vnum stays the same
  return;
  }   // end of FixupVnum

// for lookup up a char * table to find a match on a given name

int TableLookup (const char * pWhichName,
                 const tpNames pNames, 
                 const int iMaxItems)
  {
int i;

  for (i = 0; i < iMaxItems; i++)
    if (!str_prefix (pWhichName, pNames [i]))
      return i;

  return -1;
  } // end of TableLookup

// for converting a ROM flag into an string (eg. ACGT)

CString ConvertROMFlag (const EXT_BV iFlag)
  {
long iBit = 1;
char c = 'A';
int i;
CString strResult;

  if (iFlag == 0)
    return "0";

  // do upper-case letters
  for (i = 0; i < 26; i++)
    {
    if (iBit & iFlag)
      strResult += c;
    c++;
    iBit <<= 1;
    }

  c = 'a';

    // do lower-case letters
  for (i = 0; i < 6; i++)
    {
    if (iBit & iFlag)
      strResult += c;
    c++;
    iBit <<= 1;
    }

  return strResult;

  } // end of ConvertFlag


// quotes a string for writing to the database, if necessary

CString Quote (const CString & strName)
  {
  // if it isn't empty and doesn't contains spaces, it is OK as is
  if (strName.Find (' ') == -1 && !strName.IsEmpty ())
    return strName;

  CString strNew = '\'';
  strNew += strName;
  strNew += '\'';

  return strNew;

  }; // end of Quote


// split a line into the first word, and rest-of-the-line
string GetWord (string & s, 
                const string delim,
                const bool trim_spaces)
  {
    
  // find delimiter  
  string::size_type i (s.find (delim));

  // split into before and after delimiter
  string w (s.substr (0, i));

  // if no delimiter, remainder is empty
  if (i == string::npos)
    s.erase ();
  else
    // erase up to the delimiter
    s.erase (0, i + delim.size ());

  // trim spaces if required
  if (trim_spaces)
    {
    w = trim (w);
    s = trim (s);
    }

  // return first word in line
  return w;
  
  } // end of GetWord	

// To be symmetric, we assume an empty string (after trimming spaces)
// will give an empty vector.
// However, a non-empty string (with no delimiter) will give one item
// After that, you get an item per delimiter, plus 1.
// eg.  ""      => empty
//      "a"     => 1 item
//      "a,b"   => 2 items
//      "a,b,"  => 3 items (last one empty)

void StringToVector (const string s, 
                     vector<string> & v,
                     const string delim, 
                     const bool trim_spaces)
  {

  // start with initial string, trimmed of leading/trailing spaces if required
  string s1 (trim_spaces ? trim (s) : s);

  v.clear (); // ensure vector empty

  // no string? no elements
  if (s1.empty ())
    return;

  // add to vector while we have a delimiter
  while (!s1.empty () && s1.find (delim) != string::npos)
    v.push_back (GetWord (s1, delim, trim_spaces));

  // add final element
  v.push_back (s1);
  } // end of StringToVector 

// Takes a vector of strings and converts it to a string 
// like "apples,peaches,pears" 
// Should be symmetric with StringToVector (excepting any spaces that might have
//  been trimmed).

string VectorToString (const vector<string> & v, 
                       const string delim)
  {
  // vector empty gives empty string
  if (v.empty ())
    return "";

  // for copying results into
  ostringstream os;

  // copy all but last one, with delimiter after each one
  copy (v.begin (), v.end () - 1, 
        ostream_iterator<string> (os, delim.c_str ()));

  // return string with final element appended
  return os.str () + *(v.end () - 1);

  } // end of VectorToString