long CMUSHclientDoc::SetTriggerOption(LPCTSTR TriggerName, LPCTSTR OptionName, LPCTSTR Value) 
{
CString strTriggerName = TriggerName;
CString strValue = Value;
CTrigger * trigger_item;

  // trim spaces from name, make lower-case
  CheckObjectName (strTriggerName, false);

  if (!GetTriggerMap ().Lookup (strTriggerName, trigger_item))
    return eTriggerNotFound;

CString strOptionName = OptionName;

  strOptionName.MakeLower();
  strOptionName.TrimLeft ();
  strOptionName.TrimRight ();

int iItem;
int iResult = FindBaseOption (strOptionName, TriggerOptionsTable, iItem);

bool bChanged;

  if (iResult == eOK)
    {
    // this is a numeric option
    
    // for boolean options, accept "y" or "n"
    if (TriggerOptionsTable [iItem].iMinimum == 0 &&
      TriggerOptionsTable [iItem].iMaximum == 0)
      {
      if (strValue == "Y" || strValue == "y")
        Value = "1";
      else if (strValue == "N" || strValue == "n")
        Value = "0";
      }

    if (!IsNumber (Value, true))
       return eOptionOutOfRange;

    long iValue = atol (Value);

    if (m_CurrentPlugin &&
        (TriggerOptionsTable [iItem].iFlags & OPT_PLUGIN_CANNOT_WRITE))
    	return ePluginCannotSetOption;  // not available to plugin

    if (TriggerOptionsTable [iItem].iFlags & OPT_CANNOT_WRITE)
    	return ePluginCannotSetOption;  // not available for writing at all    

    iResult = SetBaseOptionItem (iItem,
                        TriggerOptionsTable,
                        NUMITEMS (TriggerOptionsTable),
                        (char *) trigger_item, 
                        iValue,
                        bChanged);

    if (bChanged)
      {
      if (!m_CurrentPlugin) // plugin mods don't really count
        SetModifiedFlag (TRUE);   // document has changed
      trigger_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks
      }

    if (strOptionName == "sequence")
      SortTriggers ();

    return iResult;

    }  // end of found
  else
    { // not numeric option, try alpha
    int iResult = FindBaseAlphaOption (strOptionName, TriggerAlphaOptionsTable, iItem);
    if (iResult == eOK)
      {

      // alpha option

      if (m_CurrentPlugin &&
          (TriggerAlphaOptionsTable [iItem].iFlags & OPT_PLUGIN_CANNOT_WRITE))
    	  return ePluginCannotSetOption;  // not available to plugin

      if (TriggerAlphaOptionsTable [iItem].iFlags & OPT_CANNOT_WRITE)
    	  return ePluginCannotSetOption;  // not available for writing at all    

      // ------ preliminary validation before setting the option

      // cannot have null match text
      if (strOptionName == "match" || 
          strOptionName == "ignore_case" ||
          strOptionName == "multi_line")
        {
        if (strValue.IsEmpty ())
          return eTriggerCannotBeEmpty;

        t_regexp * regexp = NULL;

        CString strRegexp; 

        if (trigger_item->bRegexp)
          strRegexp = strValue;
        else
          strRegexp = ConvertToRegularExpression (strValue);

        // compile regular expression
        try 
          {
          regexp = regcomp (strRegexp, (trigger_item->ignore_case ? PCRE_CASELESS : 0) |
                                       (trigger_item->bMultiLine  ? PCRE_MULTILINE : 0) |
                                       (m_bUTF_8 ? PCRE_UTF8 : 0)
                                       );
          }   // end of try
        catch(CException* e)
          {
          e->Delete ();
          return eBadRegularExpression;
          } // end of catch
      
        delete trigger_item->regexp;    // get rid of old one
        trigger_item->regexp = regexp;

        } // end of option "match"  
      else if (strOptionName == "script")
        {
  
        // get trigger dispatch ID

        if (GetScriptEngine () && !strValue.IsEmpty ())
          {
          DISPID dispid = DISPID_UNKNOWN;
          CString strMessage;
          dispid = GetProcedureDispid (strValue, "trigger", TriggerName, strMessage);
          if (dispid == DISPID_UNKNOWN)
            return eScriptNameNotLocated;
          trigger_item->dispid  = dispid;   // update dispatch ID
          }
        } // end of option "script"


      // set the option now

      iResult = SetBaseAlphaOptionItem (iItem,
                        TriggerAlphaOptionsTable,
                        NUMITEMS (TriggerAlphaOptionsTable),
                        (char *) trigger_item,  
                        strValue,
                        bChanged);

      if (bChanged)
        {
        if (!m_CurrentPlugin) // plugin mods don't really count
          SetModifiedFlag (TRUE);   // document has changed
        trigger_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks
        }

      return iResult;
      }  // end of found alpha option
    }  // end of not numeric option

 return eUnknownOption;
}   // end of SetTriggerOption
long CMUSHclientDoc::SetAlphaOptionItem (const int iItem, 
                                        LPCTSTR sValue, 
                                        const bool bDoSpecial,
                                        const bool bInclude)
  {


  CString strValue = sValue;

  if (AlphaOptionsTable [iItem].iFlags & OPT_COMMAND_STACK)
    {

    if (strValue.GetLength () > 1)
      return eOptionOutOfRange;

      if (strValue.IsEmpty ())
        {
        m_enable_command_stack = false;
        return eOptionOutOfRange;
        }

      if (!isprint (strValue [0]) || isspace (strValue [0]))
        {
        m_enable_command_stack = false;
        return eOptionOutOfRange;
        }

    } // end of command stack character

  if (AlphaOptionsTable [iItem].iFlags & OPT_WORLD_ID)
    {

    if (!strValue.IsEmpty ())
      if (strValue.GetLength () != PLUGIN_UNIQUE_ID_LENGTH)
        return eOptionOutOfRange;

    // make sure hex characters
    const char * p = strValue;
    for ( ; *p ; p++)
      if (!isxdigit (*p))
      return eOptionOutOfRange;

    strValue.MakeLower ();

    } // end of world id
  
bool bChanged;
long iResult = SetBaseAlphaOptionItem (iItem,
                        AlphaOptionsTable,
                        NUMITEMS (AlphaOptionsTable),
                        (char *) this,
                        strValue,
                        bChanged);

  if (iResult != eOK)
    return iResult;

  if (bChanged)
    SetModifiedFlag ();

  if (bInclude)
    {
    m_AlphaConfiguration [iItem]->bInclude = true;
    m_AlphaConfiguration [iItem]->sValue = strValue;
    }

  // specials wanted?
  if (!bDoSpecial)
    return eOK;


// any special tests here ...

  if (AlphaOptionsTable [iItem].iFlags & OPT_UPDATE_INPUT_FONT)
    ChangeInputFont (m_input_font_height, 
                     m_input_font_name, 
                     m_input_font_weight, 
                     m_input_font_charset,
                     m_input_font_italic);

  if (AlphaOptionsTable [iItem].iFlags & OPT_UPDATE_OUTPUT_FONT)
    ChangeFont (m_font_height, 
                m_font_name, 
                m_font_weight, 
                m_font_charset,
                m_bShowBold,
                m_bShowItalic,
                m_bShowUnderline,
                m_iLineSpacing);

  if (AlphaOptionsTable [iItem].iFlags & OPT_UPDATE_VIEWS)
    UpdateAllViews (NULL);  

  return eOK;

  }  // end of CMUSHclientDoc:SetAlphaOptionItem 
long CMUSHclientDoc::SetTimerOption(LPCTSTR TimerName, LPCTSTR OptionName, LPCTSTR Value)
{
    CString strTimerName = TimerName;
    CString strValue = Value;
    CTimer * Timer_item;

    // trim spaces from name, make lower-case
    CheckObjectName (strTimerName, false);

    if (!GetTimerMap ().Lookup (strTimerName, Timer_item))
        return eTimerNotFound;

    CString strOptionName = OptionName;

    strOptionName.MakeLower();
    strOptionName.TrimLeft ();
    strOptionName.TrimRight ();

    int iItem;
    int iResult = FindBaseOption (strOptionName, TimerOptionsTable, iItem);

    bool bChanged;

    if (iResult == eOK)
    {
        // this is a numeric option

        // for boolean options, accept "y" or "n"
        if (TimerOptionsTable [iItem].iMinimum == 0 &&
                TimerOptionsTable [iItem].iMaximum == 0)
        {
            if (strValue == "Y" || strValue == "y")
                Value = "1";
            else if (strValue == "N" || strValue == "n")
                Value = "0";
        }

        long iValue = 0;
        double fValue = 0;

        if (strOptionName == "second")
            fValue = atof (Value);
        else
        {
            if (!IsNumber (Value, true))
                return eOptionOutOfRange;

            iValue = atol (Value);
        }

        if (m_CurrentPlugin &&
                (TimerOptionsTable [iItem].iFlags & OPT_PLUGIN_CANNOT_WRITE))
            return ePluginCannotSetOption;  // not available to plugin

        if (TimerOptionsTable [iItem].iFlags & OPT_CANNOT_WRITE)
            return ePluginCannotSetOption;  // not available for writing at all

        iResult = SetBaseOptionItem (iItem,
                                     TimerOptionsTable,
                                     NUMITEMS (TimerOptionsTable),
                                     (char *) Timer_item,
                                     iValue,
                                     bChanged);

        if (bChanged)
        {
            if (!m_CurrentPlugin) // plugin mods don't really count
                SetModifiedFlag (TRUE);   // document has changed
            Timer_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks
        }

        if (iResult == eOK && Timer_item->iType == CTimer::eInterval)
        {   // need to set "every" time, not "at" time
            if (strOptionName == "hour")
            {
                Timer_item->iEveryHour = iValue;
                ResetOneTimer (Timer_item);
            } // end of option "hour"
            else if (strOptionName == "minute")
            {
                Timer_item->iEveryMinute = iValue;
                ResetOneTimer (Timer_item);
            } // end of option "minute"
            else if (strOptionName == "second")
            {
                Timer_item->fEverySecond = fValue;
                ResetOneTimer (Timer_item);
            } // end of option "second"

        } // end of need to fiddle with hour/minute/second

        // need to reset if we are changing this
        if (strOptionName == "at_time" && bChanged)
        {
            // copy from at to every or vice-versa
            if (Timer_item->iType == CTimer::eInterval)
            {
                Timer_item->iEveryHour    = Timer_item->iAtHour;
                Timer_item->iEveryMinute  = Timer_item->iAtHour;
                Timer_item->fEverySecond  = Timer_item->fAtSecond;
            }
            else
            {
                Timer_item->iAtHour    = Timer_item->iEveryHour;
                Timer_item->iAtMinute  = Timer_item->iEveryHour;
                Timer_item->fAtSecond  = Timer_item->fEverySecond;
            }
            ResetOneTimer (Timer_item);
        }

        return iResult;

    }  // end of found
    else
    {   // not numeric option, try alpha
        int iResult = FindBaseAlphaOption (strOptionName, TimerAlphaOptionsTable, iItem);
        if (iResult == eOK)
        {

            // alpha option

            if (m_CurrentPlugin &&
                    (TimerAlphaOptionsTable [iItem].iFlags & OPT_PLUGIN_CANNOT_WRITE))
                return ePluginCannotSetOption;  // not available to plugin

            if (TimerAlphaOptionsTable [iItem].iFlags & OPT_CANNOT_WRITE)
                return ePluginCannotSetOption;  // not available for writing at all

            // ------ preliminary validation before setting the option

            if (strOptionName == "script")
            {

                // get Timer dispatch ID

                if (GetScriptEngine () && !strValue.IsEmpty ())
                {
                    DISPID dispid = DISPID_UNKNOWN;
                    CString strMessage;
                    dispid = GetProcedureDispid (strValue, "Timer", TimerName, strMessage);
                    if (dispid == DISPID_UNKNOWN)
                        return eScriptNameNotLocated;
                    Timer_item->dispid  = dispid;   // update dispatch ID
                }
            } // end of option "script"


            // set the option now

            iResult = SetBaseAlphaOptionItem (iItem,
                                              TimerAlphaOptionsTable,
                                              NUMITEMS (TimerAlphaOptionsTable),
                                              (char *) Timer_item,
                                              strValue,
                                              bChanged);

            if (bChanged)
            {
                if (!m_CurrentPlugin) // plugin mods don't really count
                    SetModifiedFlag (TRUE);   // document has changed
                Timer_item->nUpdateNumber    = App.GetUniqueNumber ();   // for concurrency checks
            }

            return iResult;
        }  // end of found alpha option
    }  // end of not numeric option

    return eUnknownOption;
} // end of SetTimerOption