File indexing completed on 2024-04-14 14:32:09

0001 /***************************************************************************
0002                           ctextprocessor.cpp  -  text processor
0003     This file is a part of KMuddy distribution.
0004                              -------------------
0005     begin                : Po oct 14 2002
0006     copyright            : (C) 2002-2003 by Tomas Mecir
0007     email                : kmuddy@kmuddy.com
0008  ***************************************************************************/
0009 
0010 /***************************************************************************
0011  *                                                                         *
0012  *   This program is free software; you can redistribute it and/or modify  *
0013  *   it under the terms of the GNU General Public License as published by  *
0014  *   the Free Software Foundation; either version 2 of the License, or     *
0015  *   (at your option) any later version.                                   *
0016  *                                                                         *
0017  ***************************************************************************/
0018 
0019 #ifndef QT_CLEAN_NAMESPACE
0020 #define QT_CLEAN_NAMESPACE
0021 #endif
0022 
0023 #include <config-mxp.h>
0024 
0025 #include "ctextprocessor.h"
0026 
0027 #include "cansiparser.h"
0028 #include "cconsole.h"
0029 #include "clistmanager.h"
0030 #include "coutput.h"
0031 #include "ctextchunk.h"
0032 #include "ctriggerlist.h"
0033 #include "cvariablelist.h"
0034 #include "cwindowlist.h"
0035 
0036 #ifdef HAVE_MXP
0037 #include "cmxpmanager.h"
0038 #endif //HAVE_MXP
0039 
0040 #include <QApplication>
0041 #include <qtimer.h>
0042 
0043 cTextProcessor::cTextProcessor (int sess) : cActionBase ("textproc", sess)
0044 {
0045   chunk = nullptr;
0046 
0047   flush ();
0048   gotprompt = false;
0049 
0050   //some defaults...
0051   fg = Qt::gray;
0052   bg = Qt::black;
0053   attrib = 0;
0054 
0055   elapsedticks = 0;
0056   pdtimer = new QTimer;
0057   connect (pdtimer, &QTimer::timeout, this, &cTextProcessor::timeout);
0058   pdtimer->start (250);
0059   
0060   // some events ahd stuff ...
0061   addEventHandler ("data-received", 50, PT_STRING);
0062   addEventHandler ("received-ga", 50, PT_NOTHING);
0063   addEventHandler ("connected", 100, PT_NOTHING);
0064   addEventHandler ("command-sent", 50, PT_STRING);
0065   
0066   // Both ANSI parser and MXP parser must already exist when this is being created
0067   // That is due to the following code ...
0068 
0069   cANSIParser *ap = dynamic_cast<cANSIParser *>(object ("ansiparser", sess));
0070 #ifdef HAVE_MXP
0071   cMXPManager *mxpmanager = dynamic_cast<cMXPManager *>(object ("mxpmanager", sess));
0072   connect (mxpmanager, &cMXPManager::gotNewText, ap, &cANSIParser::parseText);
0073   connect (mxpmanager, &cMXPManager::gotNewLine, this, &cTextProcessor::gotNewLine );
0074   connect (mxpmanager, &cMXPManager::gotFgColor, this, &cTextProcessor::gotFgColor);
0075   connect (mxpmanager, &cMXPManager::gotBgColor, this, &cTextProcessor::gotBgColor);
0076   connect (mxpmanager, &cMXPManager::gotAttrib, this, &cTextProcessor::gotAttrib);
0077   connect (mxpmanager, &cMXPManager::gotALink, this, &cTextProcessor::gotALink);
0078   connect (mxpmanager, &cMXPManager::gotSENDLink, this, &cTextProcessor::gotSENDLink);
0079   connect (mxpmanager, &cMXPManager::gotExpire, this, &cTextProcessor::gotExpire);
0080   
0081 #endif
0082   connect (this, &cTextProcessor::plainText, ap, &cANSIParser::parseText);
0083   connect (ap, &cANSIParser::fgColor, this, &cTextProcessor::gotFgColor);
0084   connect (ap, &cANSIParser::bgColor, this, &cTextProcessor::gotBgColor);
0085   connect (ap, &cANSIParser::attrib, this, &cTextProcessor::gotAttrib);
0086   connect (ap, &cANSIParser::plainText, this, &cTextProcessor::gotNewText);
0087 }
0088 
0089 cTextProcessor::~cTextProcessor ()
0090 {
0091   removeEventHandler ("data-received");
0092   removeEventHandler ("received-ga");
0093   removeEventHandler ("connected");
0094   removeEventHandler ("command-sent");
0095 
0096   pdtimer->stop ();
0097   delete pdtimer;
0098 }
0099 
0100 void cTextProcessor::eventStringHandler (QString event, int, QString &par1,
0101     const QString &)
0102 {
0103   if (event == "data-received")
0104     parseText (par1);
0105   else if (event == "command-sent")
0106     sendingCommand ();
0107 }
0108 
0109 void cTextProcessor::eventNothingHandler (QString event, int)
0110 {
0111   if (event == "received-ga")
0112     receivedGA ();
0113   else if (event == "connected")
0114     flush ();
0115   else if (event == "text-here")
0116     moreTextHere ();
0117 }
0118 
0119 void cTextProcessor::parseText (const QString &text)
0120 {
0121   // TODO: Both MXP and ANSI parsing should be done via events, not like this ...
0122   // This is so that we can easily add new protocols and so on ...
0123 #ifdef HAVE_MXP
0124   //if MXP is on, pass the text to MXP parser...
0125   cMXPManager *mxpmanager = dynamic_cast<cMXPManager *>(object ("mxpmanager", sess()));
0126   if (mxpmanager->isMXPActive ())
0127   {
0128     mxpmanager->processText (text);
0129     return;
0130   }
0131 #endif
0132   //this will only be run if MXP is off...
0133   QString t;
0134   int l = text.length ();
0135   for (int i = 0; i < l; i++)
0136   {
0137     QChar ch = text[i];
0138     if (ch == '\n')
0139     {
0140       if (t.length())
0141         emit plainText (t);
0142       gotNewLine ();
0143       t = "";
0144     }
0145     else
0146       t += text[i];
0147   }
0148 
0149   if (t.length())
0150     emit plainText (t);
0151 }
0152 
0153 void cTextProcessor::sendingCommand ()
0154 {
0155   //we can receive a prompt again, even if no newline arrives
0156   //this is to support things like 'Name:', then 'Password:', but
0157   //no newline between them (as that comes from the user)
0158   gotprompt = false;
0159 }
0160 
0161 void cTextProcessor::gotNewText (const QString &text)
0162 {
0163   //if we already have a prompt, then this will go to a new line
0164   //this is to prevent multiple prompts on one line and similar things
0165   // *** CODE DISABLED FOR NOW ***
0166   //if (gotprompt)
0167   //  gotNewLine ();
0168 
0169   if (chunk == nullptr)
0170     createChunk ();
0171 
0172   //reset tick-counter
0173   elapsedticks = 0;
0174 
0175   int len = text.length();
0176   QString txt;
0177   for (int i = 0; i < len; i++)
0178   {
0179     if (text[i] == '\t')  //TAB character
0180       //add 8 spaces - not perfect, but hey ;)
0181       for (int i = 0; i < 8; i++)
0182         txt += QChar (' ');
0183     else if (text[i] == '\a')  //beep
0184       QApplication::beep();
0185     else
0186       txt += text[i];
0187   }
0188 
0189   //create the text chunk an'all :)
0190   chunkText *cht = new chunkText;
0191   cht->setText (txt);
0192   chunk->appendEntry (cht);
0193 
0194   //prompt detection (only if no prompt has been recvd since end of line)
0195   if ((!txt.isEmpty()) && (!gotprompt))
0196   {
0197     haveprompt = false;
0198 
0199     cList *tl = cListManager::self()->getList (sess(), "triggers");
0200     cTriggerList *triggers = tl ? dynamic_cast<cTriggerList *>(tl) : nullptr;
0201     if (triggers)
0202     {
0203       //only prompt-detecting triggers will be executed here
0204       triggers->setDetectingPrompt (true);
0205       triggers->matchString (chunk);
0206     }
0207     if (haveprompt)
0208       //if triggers say that this is a prompt, then we behave as if
0209       //telnet-GA was received
0210       receivedGA ();
0211   }
0212 }
0213 
0214 void cTextProcessor::gotFgColor (QColor color)
0215 {
0216   if (chunk == nullptr)
0217     createChunk ();
0218 
0219   chunkFg *chfg = new chunkFg;
0220   chfg->setFg (color);
0221   chunk->appendEntry (chfg);
0222 
0223   //remember the color...
0224   fg = color;
0225 }
0226 
0227 void cTextProcessor::gotBgColor (QColor color)
0228 {
0229   if (chunk == nullptr)
0230     createChunk ();
0231 
0232   chunkBg *chbg = new chunkBg;
0233   chbg->setBg (color);
0234   chunk->appendEntry (chbg);
0235 
0236   //remember the color...
0237   bg = color;
0238 }
0239 
0240 void cTextProcessor::gotAttrib (int a)
0241 {
0242   if (chunk == nullptr)
0243     createChunk ();
0244 
0245   chunkAttrib *chatt = new chunkAttrib;
0246   chatt->setAttrib (a);
0247   chunk->appendEntry (chatt);
0248 
0249   //remember the attribute...
0250   attrib = a;
0251 }
0252 
0253 void cTextProcessor::gotALink (const QString &name, const QString &url, const QString &text,
0254     const QString &hint)
0255 {
0256   if (chunk == nullptr)
0257     createChunk ();
0258 
0259   chunkLink *chl = new chunkLink;
0260   chl->setIsCommand (false);
0261   chl->setIsMenu (false);
0262   chl->setName (name);
0263   chl->setTarget (url);
0264   chl->setText (text);
0265   chl->setHint (hint);
0266   
0267   chunk->appendEntry (chl);
0268 }
0269 
0270 void cTextProcessor::gotSENDLink (const QString &name, const QString &command,
0271     const QString &text, const QString &hint, bool toprompt, bool ismenu)
0272 {
0273   if (chunk == nullptr)
0274     createChunk ();
0275 
0276   chunkLink *chl = new chunkLink;
0277   chl->setIsCommand (true);
0278   chl->setName (name);
0279   chl->setTarget (command);
0280   chl->setText (text);
0281   chl->setHint (hint);
0282   chl->setToPrompt (toprompt);
0283   chl->setIsMenu (ismenu);
0284   
0285   //parse menu information of needed
0286   if (ismenu)
0287     chl->parseMenu();
0288 
0289   chunk->appendEntry (chl);
0290 }
0291 
0292 void cTextProcessor::gotExpire (const QString &name)
0293 {
0294   cConsole *console = (dynamic_cast<cOutput *>(object ("output")))->console();
0295   console->expireNamedLinks (name);
0296 }
0297 
0298 void cTextProcessor::createChunk ()
0299 {
0300   chunk = new cTextChunk ((dynamic_cast<cOutput *>(object ("output")))->console());
0301   chunkStart startattr;
0302   startattr.startpos = 0;
0303   startattr.fg = fg;
0304   startattr.bg = bg;
0305   startattr.attrib = attrib;
0306   chunk->setStartAttr (startattr);
0307 }
0308 
0309 void cTextProcessor::gotNewLine ()
0310 {
0311   if (!chunk)  //empty line - create an empty one
0312     createChunk ();
0313 
0314   //inform plug-ins - phase 1 (before triggers)
0315   invokeEvent ("got-line", sess(), chunk);
0316 
0317   //is a gagging trigger gets activated, it will call gagLine(), which will
0318   //set this flag
0319   gag = false;
0320   //the same for primary gag, which is set by window-redirection triggers
0321   primarygag = false;
0322   //also clear window name
0323   wname = QString();
0324 
0325   //first call trigger comparison/execution...
0326 
0327   cList *tl = cListManager::self()->getList (sess(), "triggers");
0328   cTriggerList *triggers = tl ? dynamic_cast<cTriggerList *>(tl) : nullptr;
0329   if (triggers)
0330   {
0331     triggers->setDetectingPrompt (false);
0332     triggers->matchString (chunk);
0333   }
0334 
0335   //inform plug-ins - phase 2 (after triggers)
0336   if (gag) invokeEvent ("will-gag", sess());
0337   invokeEvent ("got-line-after-triggers", sess(), chunk);
0338 
0339   QString plainText = chunk->plainText();
0340 
0341   //now emit some events, if the line shouldn't be gagged...
0342   //the cOutput class is connected here, amongst others
0343   if (!gag)
0344   {
0345     if (!wname.isEmpty())
0346     {
0347       cWindowList *wlist = dynamic_cast<cWindowList *>(object ("windowlist"));
0348       if (wlist) {
0349         //send the text to the output window, if it exists
0350         if (wlist->exists (wname))
0351           wlist->textToWindow (wname, chunk);
0352         else
0353           //if the window doesn't exist, display the text in the main window, even if it would
0354           //normally be gagged
0355           //this is to prevent loss of information in case that the user removes some window,
0356           //but keeps triggers that use that window
0357           primarygag = false;
0358       }
0359     }
0360     if (!primarygag)
0361       invokeEvent ("display-line", sess(), chunk);
0362   }
0363   //got-line is emitted even if line is gagged - this means that scripts will receive the line
0364   //even if it is gagged...
0365   invokeEvent ("got-line", sess(), plainText);
0366 
0367   delete chunk;
0368   chunk = nullptr;
0369 
0370   //increase line counter (even if line was gagged), and set $line variable in variableList
0371   lines++;
0372 
0373   cVariableList *vl = dynamic_cast<cVariableList *>(object ("variables"));
0374   if (vl) {
0375     QString str_lines;
0376     str_lines.setNum(lines);
0377     vl->set (QString("line"), str_lines);
0378   }
0379 
0380   //we can receive prompt again
0381   gotprompt = false;
0382 }
0383 
0384 void cTextProcessor::flush ()
0385 {
0386   lines = 0;
0387 
0388   delete chunk;
0389   chunk = nullptr;
0390 
0391   gag = primarygag = false;
0392   haveprompt = gotprompt = false;
0393 }
0394 
0395 void cTextProcessor::receivedGA ()
0396 {
0397   if (!gotprompt)  //avoid 2+ prompts on one line
0398   {
0399     if (chunk)
0400     {
0401       invokeEvent ("got-prompt", sess(), chunk->plainText());
0402       invokeEvent ("got-prompt", sess(), chunk);
0403 
0404       cList *tl = cListManager::self()->getList (sess(), "triggers");
0405       cTriggerList *triggers = tl ? dynamic_cast<cTriggerList *>(tl) : nullptr;
0406       if (triggers)
0407       {
0408         triggers->setDetectingPrompt (false);
0409         triggers->matchString (chunk);
0410         invokeEvent ("got-prompt-after-triggers", sess(), chunk);
0411       }
0412       invokeEvent ("display-prompt", sess(), chunk);
0413     
0414       delete chunk;
0415       chunk = nullptr;
0416     }
0417 
0418     gotprompt = true;
0419 
0420     //increase line counter (even if line was gagged), and set $line variable in variableList
0421     lines++;
0422 
0423     cVariableList *vl = dynamic_cast<cVariableList *>(object ("variables"));
0424     if (vl)
0425     {
0426       QString str_lines;
0427       str_lines.setNum(lines);
0428       vl->set (QString("line"), str_lines);
0429     }
0430   }
0431 }
0432 
0433 void cTextProcessor::moreTextHere ()
0434 {
0435   if (chunk && !chunk->entries().empty())
0436    invokeEvent ("partial-line", sess(), chunk->plainText());
0437 }
0438 
0439 void cTextProcessor::recolorize (list<colorChange> &changes)
0440 {
0441   if (chunk)
0442     chunk->applyColorChanges (changes);
0443 }
0444 
0445 void cTextProcessor::timeout ()
0446 {
0447   //If some part of the line is waiting, and no prompt has been received
0448   //so far, we increase our counter. If the counter reaches 4 and no more
0449   //text arrives, we assume that the waiting text is a prompt and we
0450   //treat it as such.
0451 
0452   if ((!gotprompt) && chunk && (!chunk->plainText().isEmpty()))
0453     elapsedticks++;
0454   if (elapsedticks == 4)  //it's probably a prompt
0455     receivedGA ();
0456 }
0457 
0458 #include "moc_ctextprocessor.cpp"