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