File indexing completed on 2024-04-28 15:35:15

0001 /***************************************************************************
0002  *   Copyright (C) 2004 by Tomas Mecir                                     *
0003  *   kmuddy@kmuddy.org                                                     *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU Library General Public License as       *
0007  *   published by the Free Software Foundation; either version 2 of the    *
0008  *   License, or (at your option) any later version.                       *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU Library General Public License for more details.                  *
0014  ***************************************************************************/
0015 #include "cmxpstate.h"
0016 
0017 #include "centitymanager.h"
0018 #include "cmxpcolors.h"
0019 #include "cresulthandler.h"
0020 
0021 #include "rgbops.h"
0022 #include "stringops.h"
0023 
0024 #include <config.h>
0025 
0026 #include <algorithm>
0027 #include <stdlib.h>
0028 #include <string.h>
0029 
0030 cMXPState::cMXPState (cResultHandler *resh, cElementManager *elm, cEntityManager *enm)
0031 {
0032   results = resh;
0033   elements = elm;
0034   entities = enm;
0035 
0036   //currently implemented MXP version
0037   mxpVersion = "1.0";
0038 
0039   //starting MXP mode is LOCKED, to prevent problems with non-MXP MUDs).
0040   //This goes against the MXP protocol, therefore there's a setting that will keep the OPEN
0041   //mode, if desired - see public API.
0042   mode = lockedMode;
0043   defaultmode = lockedMode;
0044   initiallyLocked = true;
0045   tempMode = false;
0046   wasSecureMode = false;
0047   
0048   //some default values...
0049   cMXPColors *colors = cMXPColors::self();
0050   defaultfg = colors->color ("gray");
0051   defaultbg = colors->color ("black");
0052   defaultfont = "Courier";
0053   defaultsize = 12;
0054   defaultattribs = 0;
0055   //by default, all headers are written in the same font (Courier), they are bold and they
0056   //differ in sizes...
0057   for (int i = 0; i < 6; i++)
0058   {
0059     Hfont[i] = "Courier";
0060     Hfg[i] = defaultfg;
0061     Hbg[i] = defaultbg;
0062     Hattribs[i] = Bold;
0063   }
0064   Hsize[0] = 32;
0065   Hsize[1] = 24;
0066   Hsize[2] = 20;
0067   Hsize[3] = 16;
0068   Hsize[4] = 14;
0069   Hsize[5] = 12;
0070   ttFont = "Courier";
0071   setDefaultGaugeColor (colors->color ("white"));
0072   //PACKAGE and VERSION are defined in config.h
0073   clientName = PACKAGE;
0074   clientVersion = VERSION;
0075   //some default screen and font attributes...
0076   fX = 16;
0077   fY = 8;
0078   sX = 800;
0079   sY = 600;
0080   
0081   suplink = supgauge = supstatus = supframe = supimage = suprelocate = false;
0082   
0083   //params
0084   reset ();
0085 }
0086 
0087 
0088 cMXPState::~cMXPState ()
0089 {
0090   //delete mxpResult structures in closing tags list
0091   list<closingTag *>::iterator it;
0092   for (it = closingTags.begin(); it != closingTags.end(); ++it)
0093   {
0094     if ((*it)->closingresult)
0095       delete (*it)->closingresult;
0096     list<mxpResult *> *rlist = (*it)->closingresults;
0097     if (rlist)
0098     {
0099       list<mxpResult *>::iterator it2;
0100       for (it2 = rlist->begin(); it2 != rlist->end(); ++it2)
0101         delete *it2;
0102       delete rlist;
0103     }
0104   }
0105   closingTags.clear ();
0106 }
0107 
0108 //some user-adjustable parameters
0109 
0110 void cMXPState::setDefaultText (const string &font, int size, bool _bold, bool _italic,
0111     bool _underline, bool _strikeout, RGB fg, RGB bg)
0112 {
0113   if (curfont == defaultfont) curfont = font;
0114   defaultfont = font;
0115 
0116   if (cursize == defaultsize) cursize = size;
0117   defaultsize = size;
0118 
0119   char curattrib = (bold?1:0) * Bold + (italic?1:0) * Italic +
0120       (underline?1:0) * Underline + (strikeout?1:0) * Strikeout;
0121   char newattribs = (_bold?1:0) * Bold + (_italic?1:0) * Italic +
0122       (_underline?1:0) * Underline + (_strikeout?1:0) * Strikeout;
0123   if (curattrib == defaultattribs)
0124   {
0125     bold = _bold;
0126     italic = _italic;
0127     underline = _underline;
0128     strikeout = _strikeout;
0129   }
0130   defaultattribs = newattribs;
0131 
0132   if (fgcolor == defaultfg) fgcolor = fg;
0133   defaultfg = fg;
0134   if (bgcolor == defaultbg) bgcolor = bg;
0135   defaultbg = bg;
0136 }
0137 
0138 void cMXPState::setHeaderParams (int which, const string &font, int size, bool _bold, bool _italic,
0139     bool _underline, bool _strikeout, RGB fg, RGB bg)
0140 {
0141   //invalid H-num?
0142   if ((which < 1) || (which > 6))
0143     return;
0144 
0145   Hfont[which - 1] = font;
0146 
0147   Hsize[which - 1] = size;
0148 
0149   char newattribs = (_bold?1:0) * Bold + (_italic?1:0) * Italic +
0150       (_underline?1:0) * Underline + (_strikeout?1:0) * Strikeout;
0151   Hattribs[which - 1] = newattribs;
0152 
0153   Hfg[which - 1] = fg;
0154   Hbg[which - 1] = bg;
0155 }
0156 
0157 void cMXPState::setDefaultGaugeColor (RGB color)
0158 {
0159   gaugeColor = color;
0160 }
0161 
0162 void cMXPState::setNonProportFont (string font)
0163 {
0164   ttFont = font;
0165 }
0166 
0167 void cMXPState::setClient (string name, string version)
0168 {
0169   clientName = name;
0170   clientVersion = version;
0171 }
0172 
0173 void cMXPState::supportsLink (bool supports)
0174 {
0175   suplink = supports;
0176 }
0177 
0178 void cMXPState::supportsGauge (bool supports)
0179 {
0180   supgauge = supports;
0181 }
0182 
0183 void cMXPState::supportsStatus (bool supports)
0184 {
0185   supstatus = supports;
0186 }
0187 
0188 void cMXPState::supportsSound (bool supports)
0189 {
0190   supsound = supports;
0191 }
0192 
0193 void cMXPState::supportsFrame (bool supports)
0194 {
0195   supframe = supports;
0196 }
0197 
0198 void cMXPState::supportsImage (bool supports)
0199 {
0200   supimage = supports;
0201 }
0202 
0203 void cMXPState::supportsRelocate (bool supports)
0204 {
0205   suprelocate = supports;
0206 }
0207 
0208 void cMXPState::switchToOpen ()
0209 {
0210   mode = openMode;
0211   defaultmode = openMode;
0212   initiallyLocked = false;
0213   //not we conform to MXP spec... use with care - only affects non-MXP MUDs, where it allows
0214   //open tags - MUDs supporting MXP are NOT affected
0215 }
0216 
0217 void cMXPState::reset ()
0218 {
0219   bold = defaultattribs & Bold;
0220   italic = defaultattribs & Italic;
0221   underline = defaultattribs & Underline;
0222   strikeout = defaultattribs & Strikeout;
0223   fgcolor = defaultfg;
0224   bgcolor = defaultbg;
0225   curfont = defaultfont;
0226   cursize = defaultsize;
0227   inVar = false;
0228   varValue = "";
0229   inParagraph = false;
0230   ignoreNextNewLine = false;
0231   inLink = false;
0232   isALink = false;
0233   linkText = "";
0234   gotmap = false;
0235   curWindow = "";
0236   prevWindow = "";
0237   
0238 }
0239 
0240 //modes, mode switching
0241 
0242 mxpMode cMXPState::getMXPMode ()
0243 {
0244   return mode;
0245 }
0246 
0247 void cMXPState::setMXPMode (mxpMode m)
0248 {
0249   mode = m;
0250   tempMode = false;
0251   wasSecureMode = false;
0252   
0253   //if we start in LOCKED mode and mode change occurs, we set default mode
0254   //to OPEN, so that we are compatible with the spec...
0255   if (initiallyLocked)
0256   {
0257     initiallyLocked = false;
0258     defaultmode = openMode;
0259   }
0260 }
0261 
0262 void cMXPState::gotLineTag (int number)
0263 {
0264   //got a line tag - close outstanding entities, if any (unless we're in LOCKED mode)
0265   if (mode != lockedMode)
0266   {
0267     string t = entities->expandEntities ("", true);
0268     if (!t.empty())
0269       gotText (t, false);
0270   }
0271 
0272   //leaving secure mode
0273   if (wasSecureMode && (number != 1))
0274     closeAllTags ();
0275   wasSecureMode = false;
0276 
0277   if (number < 0) return;
0278   if (number > 99) return;
0279   if (number >= 10)
0280     results->addToList (results->createLineTag (number));
0281   else
0282   {
0283     switch (number) {
0284       case 0:
0285         setMXPMode (openMode);
0286         break;
0287       case 1:
0288         setMXPMode (secureMode);
0289         break;
0290       case 2:
0291         setMXPMode (lockedMode);
0292         break;
0293       case 3:
0294         closeAllTags ();
0295         //default mode remains the same...
0296         setMXPMode (openMode);
0297         reset ();
0298         break;
0299       case 4:
0300         setMXPMode (secureMode);
0301         tempMode = true;
0302         break;
0303       case 5:
0304         setMXPMode (openMode);
0305         defaultmode = openMode;
0306         break;
0307       case 6:
0308         setMXPMode (secureMode);
0309         defaultmode = secureMode;
0310         break;
0311       case 7:
0312         setMXPMode (lockedMode);
0313         defaultmode = lockedMode;
0314         break;
0315       default:
0316         results->addToList (results->createWarning ("Received unrecognized line tag."));
0317         break;
0318     };
0319   }
0320 }
0321 
0322 void cMXPState::closeAllTags ()
0323 {
0324   if (closingTags.empty())
0325     return;
0326 
0327   //process open tags one by one...
0328   while (!closingTags.empty())
0329   {
0330     //closingTags is a FIFO queue, tho technically it's a list
0331     closingTag *tag = closingTags.back ();
0332     closingTags.pop_back ();
0333 
0334     results->addToList (results->createWarning ("Had to auto-close tag " + tag->name + "."));
0335     
0336     closeTag (tag);
0337   }
0338 }
0339 
0340 void cMXPState::commonTagHandler ()
0341 {
0342   //got a new tag - close outstanding entities, if any (unless we're in LOCKED mode)
0343   if (mode != lockedMode)
0344   {
0345     string t = entities->expandEntities ("", true);
0346     if (!(t.empty()))
0347       gotText (t, false);
0348   }
0349 
0350   //outstanding tags are closed, if we're going out of secure mode, unless a change back to secure
0351   //mode occurs
0352   if (wasSecureMode)
0353   {
0354     closeAllTags ();
0355     wasSecureMode = false;
0356   }
0357 
0358   //error is reported, if we're inside VAR...
0359   if (inVar)
0360     results->addToList (results->createError ("Got a tag inside a variable!"));
0361 }
0362 
0363 void cMXPState::commonAfterTagHandler ()
0364 {
0365   //secure mode for one tag?
0366   if (tempMode)
0367   {
0368     tempMode = false;
0369     //set mode back to default mode
0370     mode = defaultmode;
0371   }
0372 }
0373 
0374 //regular text
0375 
0376 void cMXPState::gotText (const string &text, bool expandentities)
0377 {
0378   if (text.length() == 0)
0379     return;
0380   //temp-secure mode -> ERROR!
0381   if (tempMode)
0382   {
0383     tempMode = false;
0384     mode = defaultmode;
0385     results->addToList (results->createError ("Temp-secure line tag not followed by a tag!"));
0386   }
0387 
0388   //outstanding tags are closed, if we're going out of secure mode, unless a change back to secure
0389   //mode occurs
0390   if (wasSecureMode)
0391   {
0392     closeAllTags ();
0393     wasSecureMode = false;
0394   }
0395 
0396   //expand entities, if needed
0397   string t;
0398   if (expandentities && (mode != lockedMode))
0399     t = entities->expandEntities (text, false);
0400   else
0401     t = text;
0402 
0403   //special handling if we're in a variable or a link
0404   if (inVar)
0405     varValue.append (t);
0406   if (inLink)
0407     linkText.append (t);
0408 
0409   //text can be sent is it's not a part of a link or of a variable
0410   if (!(inVar || inLink))
0411     //add text to the list of things to send
0412     results->addToList (results->createText (t));
0413 }
0414 
0415 void cMXPState::gotNewLine ()
0416 {
0417   //got a newline char - close outstanding entities, if any (unless we're in LOCKED mode)
0418   if (mode != lockedMode)
0419   {
0420     string t = entities->expandEntities ("", true);
0421     if (!t.empty())
0422       gotText (t, false);
0423   }
0424 
0425   //was temp-secure mode?
0426   if (tempMode)
0427   {
0428     tempMode = false;
0429     mode = defaultmode;
0430     results->addToList (results->createError ("Temp-secure line tag followed by a newline!"));
0431   }
0432 
0433   //leaving secure mode?
0434   wasSecureMode = false;
0435   if ((mode == secureMode) && (defaultmode != secureMode))
0436     wasSecureMode = true;
0437 
0438   //ending line in OPEN mode - close all tags!
0439   if (mode == openMode)
0440     closeAllTags ();
0441 
0442   //is we're in SECURE mode, some tags may need to be closed...
0443 
0444   //line ended inside a link
0445   if (inLink)
0446   {
0447     inLink = false;
0448     isALink = false;
0449     linkText = "";
0450     results->addToList (results->createError ("Received an unterminated link!"));
0451   }
0452 
0453   if (inVar)
0454   {
0455     inVar = false;
0456     results->addToList (results->createError ("Received an unterminated VAR tag!"));
0457     varValue = "";
0458   }
0459 
0460   //should next newline be ignored?
0461   if (ignoreNextNewLine)
0462   {
0463     ignoreNextNewLine = false;
0464     return;
0465   }
0466 
0467   //if we're in a paragraph, don't report the new-line either
0468   if (inParagraph)
0469     return;
0470 
0471   //set mode back to default mode
0472   mode = defaultmode;
0473   
0474   //neither NOBR nor P - report newline
0475   results->addToList (results->createText ("\r\n"));
0476 }
0477 
0478 //flags
0479 
0480 //we treat flag as another tag - this is needed to allow correct flag closing even if the //appropriate closing tag wasn't sent by the MUD (auto-closing of flag)
0481 void cMXPState::gotFlag (bool begin, string flag)
0482 {
0483   bool setFlag = false;  //is this a set-variable flag?
0484   string f = lcase (flag);
0485   if ((f[0] == 's') && (f[1] == 'e') && (f[2] == 't') && (f[3] == ' '))
0486     setFlag = true;
0487 
0488   //disable inVar and remember old value, if this is a set-flag
0489   //this is needed to prevent error report in commonTagHandler()
0490   bool oldInVar = inVar;
0491   if (setFlag) inVar = false;
0492 
0493   commonTagHandler();
0494   
0495   //restore inVar value
0496   inVar = oldInVar;
0497   
0498   //no -> inform about the flag
0499   if (begin)
0500   {
0501     mxpResult *res = results->createFlag (true, flag);
0502     mxpResult *res2 = createClosingResult (res);
0503     results->addToList (res);
0504     addClosingTag ("flag", res2);
0505     
0506     //"set xxx" type of flag?
0507     if (setFlag)
0508     {
0509       if (inVar)  //in variable already
0510       {
0511         results->addToList (results->createError
0512             ("Got a set-flag, but I'm already in a variable definition!"));
0513         return;
0514       }
0515       //we are now in a variable
0516       inVar = true;
0517       varName = f.substr (f.rfind (' ') + 1);  //last word
0518       varValue = "";
0519     }
0520   }
0521   else
0522   {
0523     //closing set-flag...
0524     if (inVar && setFlag)
0525     {
0526       results->addToList (results->createVariable (varName, varValue));
0527       //send variable value, but no varname as in </var>
0528       results->addToList (results->createText (varValue));
0529       entities->addEntity (varName, varValue);
0530       inVar = false;
0531       varName = "";
0532       varValue = "";
0533     }
0534     gotClosingTag ("flag");
0535   }
0536   
0537   //no commonAfterTagHandler() here - this ain't no real tag :D
0538 }
0539 
0540 
0541 //tags:
0542 
0543 //variables
0544 
0545 void cMXPState::gotVariable (const string &name, const string &value, bool erase)
0546 {
0547   commonTagHandler();
0548 
0549   //send the variable value
0550   results->addToList (results->createVariable (name, value, erase));
0551 
0552   commonAfterTagHandler();
0553 }
0554 
0555 void cMXPState::gotVAR (const string &name)
0556 {
0557   commonTagHandler();
0558 
0559   if (inVar)
0560   {
0561     results->addToList (results->createError ("Nested VAR tags are not allowed!"));
0562     commonAfterTagHandler();
0563     return;
0564   }
0565 
0566   //we are now in a variable
0567   inVar = true;
0568   varName = name;
0569   varValue = "";
0570 
0571   //create a closing result; the variable name shall be updated when the tag will be closed
0572   addClosingTag ("var");
0573 
0574   commonAfterTagHandler();
0575 }
0576 
0577 
0578 //text formatting (OPEN tags)
0579 
0580 void cMXPState::gotBOLD ()
0581 {
0582   commonTagHandler();
0583 
0584   mxpResult *res = results->createFormatting (USE_BOLD, Bold, cMXPColors::noColor(),
0585       cMXPColors::noColor(), "", 0);
0586   mxpResult *res2 = createClosingResult (res);
0587   applyResult (res);
0588   results->addToList (res);
0589   addClosingTag ("b", res2);
0590 
0591   commonAfterTagHandler();
0592 }
0593 
0594 void cMXPState::gotITALIC ()
0595 {
0596   commonTagHandler();
0597 
0598   mxpResult *res = results->createFormatting (USE_ITALICS, Italic, cMXPColors::noColor(),
0599       cMXPColors::noColor(), "", 0);
0600   mxpResult *res2 = createClosingResult (res);
0601   applyResult (res);
0602   results->addToList (res);
0603   addClosingTag ("i", res2);
0604 
0605   commonAfterTagHandler();
0606 }
0607 
0608 void cMXPState::gotUNDERLINE ()
0609 {
0610   commonTagHandler();
0611 
0612   mxpResult *res = results->createFormatting (USE_UNDERLINE, Underline, cMXPColors::noColor(), cMXPColors::noColor(), "", 0);
0613   mxpResult *res2 = createClosingResult (res);
0614   applyResult (res);
0615   results->addToList (res);
0616   addClosingTag ("u", res2);
0617 
0618   commonAfterTagHandler();
0619 }
0620 
0621 void cMXPState::gotSTRIKEOUT ()
0622 {
0623   commonTagHandler();
0624 
0625   mxpResult *res = results->createFormatting (USE_STRIKEOUT, Strikeout, cMXPColors::noColor(),
0626       cMXPColors::noColor(), "", 0);
0627   mxpResult *res2 = createClosingResult (res);
0628   applyResult (res);
0629   results->addToList (res);
0630   addClosingTag ("s", res2);
0631 
0632   commonAfterTagHandler();
0633 }
0634 
0635 void cMXPState::gotCOLOR (RGB fg, RGB bg)
0636 {
0637   commonTagHandler();
0638 
0639   mxpResult *res = results->createFormatting (USE_FG | USE_BG, 0, fg, bg, "", 0);
0640   mxpResult *res2 = createClosingResult (res);
0641   applyResult (res);
0642   results->addToList (res);
0643   addClosingTag ("c", res2);
0644 
0645   commonAfterTagHandler();
0646 }
0647 
0648 void cMXPState::gotHIGH ()
0649 {
0650   commonTagHandler();
0651 
0652   RGB color = fgcolor;
0653   //High color is computed by adding 128 to each attribute...
0654   //This is a very primitive way of doing it, and it's probably insufficient. We'll see.
0655   color.r = (color.r < 128) ? (color.r + 128) : 255;
0656   color.g = (color.g < 128) ? (color.g + 128) : 255;
0657   color.b = (color.b < 128) ? (color.b + 128) : 255;
0658 
0659   mxpResult *res = results->createFormatting (USE_FG, 0, color, cMXPColors::noColor(), "", 0);
0660   mxpResult *res2 = createClosingResult (res);
0661   applyResult (res);
0662   results->addToList (res);
0663   addClosingTag ("h", res2);
0664 
0665   commonAfterTagHandler();
0666 }
0667 
0668 void cMXPState::gotFONT (const string &face, int size, RGB fg, RGB bg)
0669 {
0670   commonTagHandler();
0671 
0672   mxpResult *res = results->createFormatting (USE_FG | USE_BG | USE_FONT | USE_SIZE, 0, fg, bg,
0673       face, size);
0674   mxpResult *res2 = createClosingResult (res);
0675   applyResult (res);
0676   results->addToList (res);
0677   addClosingTag ("font", res2);
0678 
0679   commonAfterTagHandler();
0680 }
0681 
0682 //line spacing
0683 
0684 void cMXPState::gotNOBR ()
0685 {
0686   commonTagHandler();
0687 
0688   //next new-line is to be ignored
0689   ignoreNextNewLine = true;
0690 
0691   //no reporting to client
0692 
0693   commonAfterTagHandler();
0694 }
0695 
0696 void cMXPState::gotP ()
0697 {
0698   commonTagHandler();
0699 
0700   //we're now in a paragraph
0701   inParagraph = true;
0702 
0703   addClosingTag ("p");
0704   
0705   //no reporting to the client
0706 
0707   commonAfterTagHandler();
0708 }
0709 
0710 void cMXPState::gotBR ()
0711 {
0712   commonTagHandler();
0713 
0714   //inform the client that we got a newline (but no mode changes shall occur)
0715   results->addToList (results->createText ("\r\n"));
0716 
0717   commonAfterTagHandler();
0718 }
0719 
0720 void cMXPState::gotSBR ()
0721 {
0722   commonTagHandler();
0723 
0724   //soft-break is represented as 0x1F
0725   results->addToList (results->createText ("\x1f"));
0726 
0727   commonAfterTagHandler();
0728 }
0729 
0730 //links
0731 
0732 void cMXPState::gotA (const string &href, const string &hint, const string &expire)
0733 {
0734   commonTagHandler();
0735 
0736   inLink = true;
0737   isALink = true;
0738   linkText = "";
0739   mxpResult *res = results->createLink (expire, href, "", hint);
0740 
0741   addClosingTag ("a", res);
0742 
0743   commonAfterTagHandler();
0744 }
0745 
0746 string stripANSI (const string &s)
0747 {
0748   // first of all, find out whether there are any ANSI sequences
0749   bool ansi = false;
0750   for (int i = 0; i < s.length(); ++i)
0751     if (s[i] == 27) ansi = true;
0752   if (!ansi) return s;
0753 
0754   // there are ANSI sequences - have to get rid of them
0755   string res;
0756   ansi = false;
0757   for (int i = 0; i < s.length(); ++i) {
0758     if (!ansi) {
0759       if (s[i] == 27)
0760         ansi = true;
0761       else
0762         res += s[i];
0763     } else {
0764       // ANSI seq is ended by a-z,A-Z
0765       if (isalpha(s[i]))
0766         ansi = false;
0767     }
0768   }
0769   return res;
0770 }
0771 
0772 void cMXPState::gotSEND (const string &command, const string &hint, bool prompt, const string &expire)
0773 {
0774   commonTagHandler();
0775 
0776   inLink = true;
0777   isALink = false;
0778   linkText = "";
0779   gotmap = false;
0780   string cmd = stripANSI (command);
0781   lastcmd = cmd;
0782   mxpResult *res = results->createSendLink (expire, cmd, "", hint, prompt,
0783       (command.find ("|") == string::npos) ? false : true);
0784 
0785   addClosingTag ("send", res);
0786 
0787   commonAfterTagHandler();
0788 }
0789 
0790 void cMXPState::gotEXPIRE (const string &name)
0791 {
0792   commonTagHandler();
0793 
0794   results->addToList (results->createExpire (name));
0795 
0796   commonAfterTagHandler();
0797 }
0798 
0799 //version control
0800 
0801 void cMXPState::gotVERSION ()
0802 {
0803   commonTagHandler();
0804 
0805   //this is to be sent...
0806   results->addToList (results->createSendThis ("\x1b[1z<VERSION MXP=" + mxpVersion + " CLIENT=" +
0807       clientName + " VERSION=" + clientVersion + ">\r\n"));
0808 
0809   commonAfterTagHandler();
0810 }
0811 
0812 void cMXPState::gotSUPPORT (list<string> params)
0813 {
0814   commonTagHandler();
0815 
0816   if (!params.empty())  //some parameters - this is not supported at the moment
0817     results->addToList (results->createWarning (
0818         "Received <support> with parameters, but this isn't supported yet..."));
0819     
0820   string res;
0821   res = "\x1b[1z<SUPPORTS +!element +!attlist +!entity +var +b +i +u +s +c +h +font";
0822   res += " +nobr +p +br +sbr +version +support +h1 +h2 +h3 +h4 +h5 +h6 +hr +small +tt";
0823   if (suplink)
0824     res += " +a +send +expire";
0825   if (supgauge)
0826     res += " +gauge";
0827   if (supstatus)
0828     res += " +status";
0829   if (supsound)
0830     res += " +sound +music";
0831   if (supframe)
0832     res += " +frame +dest";
0833   if (supimage)
0834     res += " +image";
0835   if (suprelocate)
0836     res += " +relocate +user +password";
0837   res += ">\r\n";
0838   results->addToList (results->createSendThis (res));
0839   
0840   commonAfterTagHandler();
0841 }
0842 
0843 //optional tags go next
0844 
0845 //other HTML tags
0846 
0847 void cMXPState::gotHtag (int which)
0848 {
0849   if ((which < 1) || (which > 6)) //BUG!!!
0850   {
0851     commonAfterTagHandler();
0852     return;
0853   }
0854 
0855   commonTagHandler();
0856 
0857   int idx = which - 1;
0858   mxpResult *res = results->createFormatting (USE_ALL, Hattribs[idx], Hfg[idx], Hbg[idx],
0859       Hfont[idx], Hsize[idx]);
0860   mxpResult *res2 = createClosingResult (res);
0861   applyResult (res);
0862   results->addToList (res);
0863   char ct[3];
0864   ct[0] = 'h';
0865   ct[1] = '1' + idx;
0866   ct[2] = '\0';
0867   addClosingTag (ct, res2);
0868 
0869   commonAfterTagHandler();
0870 }
0871 
0872 void cMXPState::gotHR ()
0873 {
0874   commonTagHandler();
0875 
0876   results->addToList (results->createHorizLine ());
0877 
0878   commonAfterTagHandler();
0879 }
0880 
0881 void cMXPState::gotSMALL ()
0882 {
0883   commonTagHandler();
0884 
0885   //SMALL means 3/4 of standard size :)
0886   mxpResult *res = results->createFormatting (USE_SIZE, 0, cMXPColors::noColor(),
0887     cMXPColors::noColor(), "", defaultsize * 3/4);
0888   mxpResult *res2 = createClosingResult (res);
0889   applyResult (res);
0890   results->addToList (res);
0891   addClosingTag ("small", res2);
0892 
0893   commonAfterTagHandler();
0894 }
0895 
0896 void cMXPState::gotTT ()
0897 {
0898   commonTagHandler();
0899 
0900   mxpResult *res = results->createFormatting (USE_FONT, 0, cMXPColors::noColor(), cMXPColors::noColor(), ttFont, 0);
0901   mxpResult *res2 = createClosingResult (res);
0902   applyResult (res);
0903   results->addToList (res);
0904   addClosingTag ("tt", res2);
0905 
0906   commonAfterTagHandler();
0907 }
0908 
0909 //MSP compatibility
0910 
0911 void cMXPState::gotSOUND (const string &fname, int vol, int count, int priority,
0912     const string &type, const string &url)
0913 {
0914   commonTagHandler();
0915 
0916   results->addToList (results->createSound (true, fname, vol, count, priority, false, type, url));
0917 
0918   commonAfterTagHandler();
0919 }
0920 
0921 void cMXPState::gotMUSIC (const string &fname, int vol, int count, bool contifrereq,
0922     const string &type, const string &url)
0923 {
0924   commonTagHandler();
0925 
0926   results->addToList (results->createSound (false, fname, vol, count, 0, contifrereq, type, url));
0927 
0928   commonAfterTagHandler();
0929 }
0930 
0931 //gauges / status bars
0932 
0933 void cMXPState::gotGAUGE (const string &entity, const string &maxentity, const string &caption,
0934     RGB color)
0935 {
0936   commonTagHandler();
0937 
0938   results->addToList (results->createGauge (entity, maxentity, caption, color));
0939 
0940   commonAfterTagHandler();
0941 }
0942 
0943 void cMXPState::gotSTAT (const string &entity, const string &maxentity, const string &caption)
0944 {
0945   commonTagHandler();
0946 
0947   results->addToList (results->createStat (entity, maxentity, caption));
0948 
0949   commonAfterTagHandler();
0950 }
0951 
0952 //frames and cursor control
0953 
0954 void cMXPState::gotFRAME (const string &name, const string &action, const string &title,
0955     bool internal, const string &align, int left, int top, int width, int height,
0956     bool scrolling, bool floating)
0957 {
0958   commonTagHandler();
0959 
0960   if (name.empty())
0961   {
0962     results->addToList (results->createError ("Got FRAME tag without frame name!"));
0963     commonAfterTagHandler();
0964     return;
0965   }
0966 
0967   string nm = lcase (name);
0968   string act = lcase (action);
0969   string alg = lcase (align);
0970 
0971   string tt = title;
0972   //name is the default title
0973   if (tt.empty())
0974     tt = name;
0975    
0976   //align
0977   alignType at = Top;
0978   if (!align.empty())
0979   {
0980     bool alignok = false;
0981     if (align == "left") { at = Left; alignok = true; }
0982     if (align == "right") { at = Right; alignok = true; }
0983     if (align == "top") { at = Top; alignok = true; }
0984     if (align == "bottom") { at = Bottom; alignok = true; }
0985     if (!alignok)
0986       results->addToList (results->createError ("Received FRAME tag with unknown ALIGN option!"));
0987   }
0988 
0989   //does the list of frames contain frame with name nm?
0990   bool nmExists = (frames.count (nm) != 0);
0991   
0992   if (act == "open")
0993   {
0994     if (nmExists)
0995     {
0996       results->addToList (results->createError ("Received request to create an existing frame!"));
0997       commonAfterTagHandler();
0998       return;
0999     }
1000     //cannot create _top or _previous
1001     if ((nm == "_top") || (nm == "_previous"))
1002     {
1003       results->addToList (results->createError ("Received request to create a frame with name " +
1004           nm + ", which is invalid!"));
1005       commonAfterTagHandler();
1006       return;
1007     }
1008     if (internal)
1009     {
1010       //false for internal windows... value not used as of now, but it may be used later...
1011       frames[nm] = false;
1012       results->addToList (results->createInternalWindow (nm, tt, at, scrolling));
1013     }
1014     else
1015     {
1016       //true for normal windows... value not used as of now, but it may be used later...
1017       frames[nm] = true;
1018       results->addToList (results->createWindow (nm, tt, left, top, width, height,
1019           scrolling, floating));
1020     }
1021   }
1022   if (act == "close")
1023   {
1024     if (nmExists)
1025     {
1026       frames.erase (nm);
1027       results->addToList (results->createCloseWindow (nm));
1028     }
1029     else
1030       results->addToList (results->createError
1031           ("Received request to close a non-existing frame!"));
1032   }
1033   if (act == "redirect")
1034   {
1035     //if the frame exists, or if the name is either _top or _previous, we redirect to that window
1036     if ((nm == "_top") || (nm == "_previous") || nmExists)
1037       redirectTo (nm);
1038 
1039     else
1040     {
1041       //create that window
1042       if (internal)
1043       {
1044         //false for internal windows... value not used as of now, but it may be used later...
1045         frames[nm] = false;
1046         results->addToList (results->createInternalWindow (nm, tt, at, scrolling));
1047       }
1048       else
1049       {
1050         //true for normal windows... value not used as of now, but it may be used later...
1051         frames[nm] = true;
1052         results->addToList (results->createWindow (nm, tt, left, top, width, height,
1053             scrolling, floating));
1054       }
1055       //then redirect to it
1056       redirectTo (nm);
1057     }
1058   }
1059 
1060   commonAfterTagHandler();
1061 }
1062 
1063 void cMXPState::redirectTo (const string &name)
1064 {
1065   string nm = lcase (name);
1066   
1067   string emptystring;
1068   mxpResult *res = 0;
1069   if (nm == "_top")
1070     res = results->createSetWindow (emptystring);
1071   else
1072   if (nm == "_previous")
1073     res = results->createSetWindow (prevWindow);
1074   else
1075     if (frames.count (nm))
1076       res = results->createSetWindow (nm);
1077     else
1078       res = results->createError ("Received request to redirect to non-existing window " + nm);
1079   //apply result - will update info about previous window and so...
1080   applyResult (res);
1081   results->addToList (res);
1082 }
1083 
1084 void cMXPState::gotDEST (const string &name, int x, int y, bool eol, bool eof)
1085 {
1086   commonTagHandler();
1087 
1088   string nm = lcase (name);
1089   bool nmExists = (frames.count (nm) != 0);
1090   
1091   if (!nmExists)
1092   {
1093     results->addToList (results->createError ("Received a request to redirect to non-existing window " + nm));
1094     return;
1095   }
1096   
1097   mxpResult *res = results->createSetWindow (name);
1098   mxpResult *res2 = createClosingResult (res);
1099   applyResult (res);
1100   results->addToList (res);
1101 
1102   int _x = x;
1103   int _y = y;
1104   if ((y >= 0) && (x < 0)) _x = 0;
1105   if ((_x >= 0) && (_y >= 0))
1106     results->addToList (results->createMoveCursor (_x, _y));
1107 
1108   list<mxpResult *> *ls = 0;
1109   //erase AFTER displaying text
1110   if (eol || eof)
1111   {
1112     ls = new list<mxpResult *>;
1113     ls->push_back (res2);
1114     res2 = results->createEraseText (eof);
1115   }
1116 
1117   //closing tag...
1118   addClosingTag ("dest", res2, ls);
1119 
1120   commonAfterTagHandler();
1121 }
1122 
1123 //crosslinking servers
1124 
1125 void cMXPState::gotRELOCATE (const string &hostname, int port)
1126 {
1127   commonTagHandler();
1128 
1129   results->addToList (results->createRelocate (hostname, port));
1130 
1131   commonAfterTagHandler();
1132 }
1133 
1134 void cMXPState::gotUSER ()
1135 {
1136   commonTagHandler();
1137 
1138   results->addToList (results->createSendLogin (true));
1139 
1140   commonAfterTagHandler();
1141 }
1142 
1143 void cMXPState::gotPASSWORD ()
1144 {
1145   commonTagHandler();
1146 
1147   results->addToList (results->createSendLogin (false));
1148 
1149   commonAfterTagHandler();
1150 }
1151 
1152 //images
1153 
1154 void cMXPState::gotIMAGE (const string &fname, const string &url, const string &type, int height,
1155     int width, int hspace, int vspace, const string &align, bool ismap)
1156 {
1157   commonTagHandler();
1158 
1159   //align
1160   string alg = lcase (align);
1161   alignType at = Top;
1162   if (!align.empty())
1163   {
1164     bool alignok = false;
1165     if (align == "left") { at = Left; alignok = true; }
1166     if (align == "right") { at = Right; alignok = true; }
1167     if (align == "top") { at = Top; alignok = true; }
1168     if (align == "bottom") { at = Bottom; alignok = true; }
1169     if (align == "middle") { at = Middle; alignok = true; }
1170     if (!alignok)
1171       results->addToList (results->createError ("Received IMAGE tag with unknown ALIGN option!"));
1172   }
1173 
1174   if (gotmap)
1175     results->addToList (results->createError ("Received multiple image maps in one SEND tag!"));
1176 
1177   if (ismap)
1178   {
1179     if (inLink && (!isALink))
1180     {
1181       results->addToList (results->createImageMap (lastcmd));
1182       lastcmd = "";
1183       gotmap = true;
1184     }
1185     else
1186       results->addToList (results->createError ("Received an image map with no SEND tag!"));
1187   }
1188   results->addToList (results->createImage (fname, url, type, height, width, hspace, vspace, at));
1189 
1190   commonAfterTagHandler();
1191 }
1192 
1193 
1194 //closing tags
1195 
1196 void cMXPState::gotClosingTag (const string &name)
1197 {
1198   string nm = lcase (name);
1199   //hack, to prevent an error from being reported when </var> or end-of-flag comes
1200   //we cannot simply test for </var> and friends and disable it then, because
1201   //we could have the var tag inside some element
1202   bool oldInVar = inVar;
1203   inVar = false;
1204   
1205   commonTagHandler();
1206   
1207   //restore the inVar variable...
1208   inVar = oldInVar;
1209 
1210   bool okay = false;
1211   while (!okay)
1212   {
1213     if (closingTags.empty())
1214       break;  //last one closed...
1215     //closingTags is a FIFO queue, tho technically it's a list
1216     closingTag *tag = closingTags.back ();
1217     closingTags.pop_back ();
1218 
1219     if (tag->name == nm)
1220       okay = true;  //good
1221     else
1222       results->addToList (results->createWarning ("Had to auto-close tag " + tag->name +
1223           ", because closing tag </" + name + "> was received."));
1224     
1225     closeTag (tag);
1226   }
1227 
1228   if (!okay)
1229     results->addToList (results->createError ("Received unpaired closing tag </" + name + ">."));
1230 
1231   commonAfterTagHandler();
1232 }
1233 
1234 void cMXPState::closeTag (closingTag *tag)
1235 {
1236   //some tags need special handling...
1237   if (tag->name == "p")
1238   {
1239     inParagraph = false;
1240     ignoreNextNewLine = false;
1241     //also send a newline after end of paragraph... MXP docs say nothing about this :(
1242     results->addToList (results->createText ("\r\n"));
1243   }
1244   if (tag->name == "var")
1245   {
1246     tag->closingresult = 0;
1247     tag->closingresults = 0;
1248     results->addToList (results->createVariable (varName, varValue));
1249     results->addToList (results->createText (varName + ": " + varValue));
1250     entities->addEntity (varName, varValue);
1251     inVar = false;
1252     varName = "";
1253     varValue = "";
1254   }
1255   if (tag->name == "a")
1256   {
1257     if (inLink && isALink)
1258     {
1259       // !!! SOME LOW-LEVEL MANIPULATIONS HERE !!!
1260       
1261       linkStruct *ls = (linkStruct *) tag->closingresult->data;
1262       //assign text, using URL if no text given
1263       string lt = linkText.empty() ? (ls->url ? ls->url : "") : linkText;
1264       lt = stripANSI (lt);
1265       ls->text = new char[lt.length() + 1];
1266       ls->text[0] = '\0';
1267       if (lt.length())
1268         strcpy (ls->text, lt.c_str());
1269     }
1270     else
1271       //this should never happen
1272       results->addToList (results->createError ("Received </A> tag, but I'm not in a link!"));
1273     linkText = "";
1274     inLink = false;
1275     isALink = false;
1276   }
1277   if (tag->name == "send")
1278   {
1279     if (gotmap)
1280     {
1281       //don't send this closing result
1282       results->deleteResult (tag->closingresult);
1283       tag->closingresult = 0;
1284       
1285       if (!linkText.empty())
1286         results->addToList (results->createError
1287             ("Received image map and a command in one SEND tag!"));
1288     }
1289     else if (inLink && (!isALink))
1290     {
1291       // !!! SOME LOW-LEVEL MANIPULATIONS HERE !!!
1292       
1293       sendStruct *ss = (sendStruct *) tag->closingresult->data;
1294       //assign text, also assign to command if none given
1295       
1296       //assign linkText to ss->text
1297       linkText = stripANSI (linkText);
1298       delete[] ss->text;
1299       ss->text = new char[linkText.length() + 1];
1300       strcpy (ss->text, linkText.c_str());
1301       
1302       if (ss->hint)
1303       {
1304         //expand &text; in hint
1305         string hint = ss->hint;
1306 
1307         bool found = true, havematch = false;
1308         while (found)
1309         {
1310           int p = hint.find ("&text;");
1311           if (p < hint.length())   //found it
1312           {
1313             //replace it...
1314             hint.replace (p, 6, linkText);
1315             havematch = true;
1316           }
1317           else
1318             found = false;  //no more matches
1319         }
1320         if (havematch)  //apply changes if needed
1321         {
1322           //assign hint to ss->hint
1323           delete[] ss->hint;
1324           ss->hint = new char[hint.length() + 1];
1325           strcpy (ss->hint, hint.c_str());
1326         }
1327       }
1328       if (ss->command)
1329       {
1330         string cmd = ss->command;
1331         //also expand &text; in href
1332         
1333         bool found = true, havematch = false;
1334         while (found)
1335         {
1336           int p = cmd.find ("&text;");
1337           if (p < cmd.length())   //found it
1338           {
1339             //replace it...
1340             cmd.replace (p, 6, linkText);
1341             havematch = true;
1342           }
1343           else
1344             found = false;  //no more matches
1345         }
1346         if (havematch)  //apply changes if needed
1347         {
1348           //assign cmd to ss->command
1349           delete[] ss->command;
1350           ss->command = new char[cmd.length() + 1];
1351           strcpy (ss->command, cmd.c_str());
1352         }
1353       }
1354       else if (!linkText.empty())
1355       {
1356         //assign linkText to ss->command
1357         ss->command = new char[linkText.length() + 1];
1358         strcpy (ss->command, linkText.c_str());
1359       }
1360     }
1361     else
1362       //this should never happen
1363       results->addToList (results->createError ("Received </SEND> tag, but I'm not in a link!"));
1364 
1365     linkText = "";
1366     inLink = false;
1367     isALink = false;
1368     gotmap = false;
1369   }
1370 
1371   //handle applying/sending of closing results, is any
1372   if (tag->closingresult)
1373   {
1374     //apply result, reverting changes made by opening tag
1375     applyResult (tag->closingresult);
1376     //and send the changes to the client app
1377     results->addToList (tag->closingresult);
1378   }
1379   if (tag->closingresults)
1380   {
1381     //the same for remaining closing tags...
1382     list<mxpResult *>::iterator it;
1383     for (it = tag->closingresults->begin(); it != tag->closingresults->end(); ++it)
1384     {
1385       applyResult (*it);
1386       results->addToList (*it);
1387     }
1388   }
1389   //finally, the closing tag gets deleted
1390   //note that this won't delete the results themselves - they will be deleted after
1391   //they are processed by the client app
1392   delete tag->closingresults;
1393   tag->closingresults = 0;
1394   delete tag;
1395 }
1396 
1397 
1398 //mxpResult handling
1399 
1400 mxpResult *cMXPState::createClosingResult (mxpResult *what)
1401 {
1402   mxpResult *res = 0;
1403   switch (what->type) {
1404     case 3: {
1405       flagStruct *fs = (flagStruct *) what->data;
1406       res = results->createFlag (false, fs->name);
1407       break;
1408     }
1409     case 5: {
1410       formatStruct *fs = (formatStruct *) what->data;
1411       //usemask is the most relevant thing here - things not enabled there won't be applied,
1412       //so we can place anything there
1413       int usemask = fs->usemask;
1414       char curattrib = (bold?1:0) * Bold + (italic?1:0) * Italic +
1415           (underline?1:0) * Underline + (strikeout?1:0) * Strikeout;
1416       string font;
1417       if (usemask & USE_FONT)
1418         font = curfont;
1419       res = results->createFormatting (usemask, curattrib, fgcolor, bgcolor, font, cursize);
1420       break;
1421     }
1422     case 15: {
1423       res = results->createSetWindow (curWindow);
1424       break;
1425     }
1426   };
1427   return res;
1428 }
1429 
1430 void cMXPState::applyResult (mxpResult *what)
1431 {
1432   switch (what->type) {
1433     case 5: {
1434       formatStruct *fs = (formatStruct *) what->data;
1435       int usemask = fs->usemask;
1436       if (usemask & USE_BOLD)
1437         bold = fs->attributes & Bold;
1438       if (usemask & USE_ITALICS)
1439         italic = fs->attributes & Italic;
1440       if (usemask & USE_UNDERLINE)
1441         underline = fs->attributes & Underline;
1442       if (usemask & USE_STRIKEOUT)
1443         strikeout = fs->attributes & Strikeout;
1444       if (usemask & USE_FG)
1445         fgcolor = fs->fg;
1446       if (usemask & USE_BG)
1447         bgcolor = fs->bg;
1448       if (usemask & USE_FONT)
1449         curfont = fs->font;
1450       if (usemask & USE_SIZE)
1451         cursize = fs->size;
1452       break;
1453     }
1454     case 15: {
1455       prevWindow = curWindow;
1456       if (what->data)
1457         curWindow = (char *) what->data;
1458       else
1459         curWindow = "";
1460       break;
1461     };
1462 
1463   };
1464 }
1465 
1466 void cMXPState::addClosingTag (const string &name, mxpResult *res, list<mxpResult *> *res2)
1467 {
1468   closingTag *ctag = new closingTag;
1469   ctag->name = name;
1470   ctag->closingresult = res;
1471   ctag->closingresults = res2;
1472   closingTags.push_back (ctag);
1473 }
1474 
1475 void cMXPState::setScreenProps (int sx, int sy, int wx, int wy, int fx, int fy)
1476 {
1477   sX = sx;
1478   sY = sy;
1479   wX = wx;
1480   wY = wy;
1481   fX = fx;
1482   fY = fy;
1483 }
1484 
1485 int cMXPState::computeCoord (const string &coord, bool isX, bool inWindow)
1486 {
1487   int retval = atoi (coord.c_str());
1488   int len = coord.length();
1489   char ch = coord[len - 1];
1490   if (ch == 'c') retval *= (isX ? fX : fY);
1491   if (ch == '%') retval = retval * (inWindow ? (isX ? wX : wY) : (isX ? sX : sY)) / 100;
1492   return retval;
1493 }
1494