File indexing completed on 2024-04-21 15:08:11

0001 /***************************************************************************
0002                           ctrigger.cpp  -  trigger
0003     This file is a part of KMuddy distribution.
0004                              -------------------
0005     begin                : Ne okt 13 2002
0006     copyright            : (C) 2002-2008 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 #include "ctrigger.h"
0020 
0021 #include "csoundplayer.h"
0022 
0023 #include "cactionmanager.h"
0024 #include "cansiparser.h"
0025 #include "ccmdqueue.h"
0026 #include "cexpresolver.h"
0027 #include "coutput.h"
0028 #include "cpattern.h"
0029 #include "cscripteval.h"
0030 #include "ctextchunk.h"
0031 #include "ctextprocessor.h"
0032 #include "ctriggerlist.h"
0033 #include "cvariablelist.h"
0034 
0035 #include <list>
0036 
0037 // this struct holds some trigger settings for faster access
0038 struct cTrigger::Private {
0039   bool global;
0040   bool dontsend;
0041   bool promptDetect;
0042   QString condition;
0043   cList::TraverseAction ifMatch, ifNotMatch;
0044   arith_exp *exp;
0045   cPattern p;
0046   cExpResolver *resolver;
0047   QStringList commands;   // commands that this trigger wants to execute
0048 
0049   /** Helper for recolorize() */
0050   QColor getColor (int c);
0051 };
0052 
0053 cTrigger::cTrigger (cList *list) : cListObject (list)
0054 {
0055   d = new Private;
0056 
0057   // these defaults must match the default property values set in cTriggerList
0058 
0059   d->p.setMatching (cPattern::substring);
0060   d->global = false;
0061   d->dontsend = false;
0062   d->promptDetect = false;
0063   d->exp = nullptr;
0064   d->resolver = nullptr;
0065   d->ifMatch = cList::Stop;
0066   d->ifNotMatch = cList::Continue;
0067 }
0068 
0069 cTrigger::~cTrigger ()
0070 {
0071   delete d->exp;
0072   delete d->resolver;
0073   delete d;
0074 }
0075 
0076 void cTrigger::updateVisibleName ()
0077 {
0078   QString pattern = strVal ("pattern");
0079   if (pattern.isEmpty())
0080     cListObject::updateVisibleName();
0081   else
0082     setVisibleName (pattern);
0083 }
0084 
0085 void cTrigger::attribChanged (const QString &name)
0086 {
0087   if (name == "pattern") {
0088     d->p.setPattern (strVal ("pattern"));
0089     updateVisibleName();
0090     return;
0091   }
0092   if (name == "matching") {
0093     int m = intVal ("matching");
0094     cPattern::PatternType pt;
0095     switch (m) {
0096       case 0: pt = cPattern::exact; break;
0097       case 1: pt = cPattern::substring; break;
0098       case 2: pt = cPattern::begin; break;
0099       case 3: pt = cPattern::end; break;
0100       case 4: pt = cPattern::regexp; break;
0101       default: pt = cPattern::begin;
0102     }
0103     d->p.setMatching (pt);
0104     return;
0105   }
0106   if (name == "cs") {
0107     d->p.setCaseSensitive (boolVal ("cs"));
0108     return;
0109   }
0110   if (name == "whole-words") {
0111     d->p.setWholeWords (boolVal ("whole-words"));
0112     return;
0113   }
0114   if (name == "global") {
0115     d->global = boolVal ("global");
0116     return;
0117   }
0118   if (name == "prompt") {
0119     d->promptDetect = boolVal ("prompt");
0120     return;
0121   }
0122   if ((name == "action-matched") || (name == "action-not-matched")) {
0123     int m = intVal (name);
0124     cList::TraverseAction act;
0125     switch (m) {
0126       case 0: act = cList::Continue; break;
0127       case 1: act = cList::Stop; break;
0128       case 2: act = cList::LeaveGroup; break;
0129       default: act = cList::Continue;
0130     }
0131     if (name == "action-matched")
0132       d->ifMatch = act;
0133     else
0134       d->ifNotMatch = act;
0135     return;
0136   }
0137   if (name == "condition") {
0138     d->condition = strVal ("condition");
0139     // TODO: this is duplicated for every place with conditions
0140     // find out if we could create a common class for this
0141     delete d->exp;
0142     d->exp = nullptr;
0143   
0144     // no expression ? nothing to do !
0145     if (d->condition.trimmed().isEmpty()) return;
0146 
0147     // parse the condition
0148     arith_exp *exp = new arith_exp;
0149     bool ok = exp->compile (d->condition);
0150     if (ok)
0151       d->exp = exp;
0152     else
0153       // cannot parse condition - no conditional matching ...
0154       delete exp;
0155     return;
0156   }
0157   // the other special trigger values would not be set/used so frequently, so
0158   // we will take them directly from the attributes when needed
0159 }
0160 
0161 cList::TraverseAction cTrigger::traverse (int traversalType)
0162 {
0163   if (traversalType == TRIGGER_MATCH)
0164     return doMatch ();
0165   return cList::Stop;  // unknown action
0166 }
0167 
0168 cList::TraverseAction cTrigger::doMatch ()
0169 {
0170   cTriggerList *tl = (cTriggerList *) list();
0171 
0172   // prompt-detect mode doesn't fit - do nothing
0173   bool detecting = tl->detectingPrompt();
0174   if (detecting != d->promptDetect)
0175     return cList::Continue;
0176 
0177   // fetch the string from the trigger list
0178   QString string = tl->stringToMatch();
0179   int mpos = 0;  // reset the index where matching will start
0180   //match against trigger
0181   bool everMatched = false;
0182   while (true) {
0183     if (!d->p.match (string, mpos)) break;  // match the string
0184 
0185     // matched
0186     bool cond = testCondition ();   // match the condition
0187 
0188     // also matched - execute this trigger !
0189     // but don't break the loop if the condition didn't match, as global matching
0190     // relies on that
0191     if (cond) {
0192       everMatched = true;
0193       executeTrigger ();
0194     }
0195 
0196     if (!d->global) break;  // only continue if it's global matching
0197 
0198     if (boolVal ("rewrite")) break;
0199     
0200     // global - update matching position, or terminate if no more matching should occur
0201     // if last length is 0, we must advance by 1 to avoid an endless loop ...
0202     int shift = (d->p.getLastLength() == 0) ? 1 : d->p.getLastLength();
0203     mpos = d->p.getLastPos() + shift;
0204     if (mpos >= string.length())
0205       break;
0206   }
0207 
0208   return everMatched ? d->ifMatch : d->ifNotMatch;
0209 }
0210 
0211 // TODO: this is duplicated for every place with conditions
0212 // find out if we could create a common class for this
0213 bool cTrigger::testCondition ()
0214 {
0215   // no condition -> always matches ...
0216   if (!d->exp) return true;
0217 
0218   if (!d->resolver) d->resolver = new cExpResolver (list()->session());
0219 
0220   // set up pseudo-variable expansion
0221   cCmdQueue *queue = new cCmdQueue (list()->session());
0222   d->resolver->setQueue (queue);
0223   queue->fillFromPattern (&d->p);
0224   cValue val = d->exp->evaluate (d->resolver);
0225   delete queue;
0226   d->resolver->setQueue(nullptr);
0227 
0228   // test passes if the evaluator returns non-zero ...
0229   return (val.asInteger() != 0);
0230 }
0231 
0232 void cTrigger::executeTrigger ()
0233 {
0234   cActionManager *am = cActionManager::self();
0235   int sess = list()->session();
0236   cTextProcessor *textproc = dynamic_cast<cTextProcessor *>(am->object ("textproc", sess));
0237   cOutput *output = dynamic_cast<cOutput *>(am->object ("output", sess));
0238   cANSIParser *ansiparser = dynamic_cast<cANSIParser *>(am->object ("ansiparser", sess));
0239   
0240   d->commands.clear ();
0241 
0242   bool colorize = boolVal ("colorize");
0243   bool gag = boolVal ("gag");
0244   bool wantRewrite = boolVal ("rewrite");
0245   bool notifytrigger = boolVal ("notify");
0246 
0247   // colorize if needed
0248   if (colorize && ((!gag) || d->promptDetect))
0249     recolorize ();
0250 
0251   //rewrite the line if this is a rewrite trigger
0252   if (wantRewrite)
0253     rewrite ();
0254 
0255   // prompt detection - tell the processor that we have a prompt and end
0256   if (d->promptDetect)
0257   {
0258     textproc->setHavePrompt ();
0259     return;
0260   }
0261 
0262   // gag the line if needed
0263   if (gag)
0264     textproc->gagLine ();
0265 
0266   // notify the user if needed
0267   if (notifytrigger)
0268     am->invokeEvent ("notify-request", sess);
0269 
0270   // redirection
0271   if (boolVal ("output-window")) {
0272     textproc->setOutputWindow (strVal ("output-window-name"));
0273     if (boolVal ("output-gag-in-main"))
0274       textproc->gagLineInPrimary ();
0275   }
0276 
0277   //attempt to play the sound if needed
0278   if (boolVal ("sound") && (!(strVal ("sound-file").isEmpty())))
0279   {
0280     cSoundPlayer *player = dynamic_cast<cSoundPlayer *>(am->object ("soundplayer", 0));
0281     if (player)
0282     {
0283       player->setFileName (strVal ("sound-file"));
0284       player->setPriority (50);
0285       player->setRepeatsCount (1);
0286       player->setVolume (50);
0287       player->play ();
0288     }
0289   }
0290 
0291   int commands = strListCount ("newtext");
0292   // if we have only one empty command, we do nothing
0293   // prevents gag/notify/... triggers from sending empty lines to the server
0294   if ((commands > 1) || (!strListValue ("newtext", 1).isEmpty()))
0295   {
0296 
0297     // add commands to be sent
0298     for (int i = 1; i <= commands; ++i) {
0299       QString cmd = strListValue ("newtext", i);
0300       d->p.expandPseudoVariables (cmd);
0301       d->commands << cmd;
0302     }
0303 
0304     // we want to display, not send ?
0305     if (boolVal ("dont-send"))        
0306     {
0307       for (QStringList::iterator it = d->commands.begin(); it != d->commands.end(); ++it)
0308       {
0309         cTextChunk *chunk = cTextChunk::makeLine (*it, ansiparser->defaultTextColor(),
0310             ansiparser->defaultBkColor(), output->console());
0311         am->invokeEvent ("display-line", sess, chunk);
0312         delete chunk;
0313       }
0314       
0315       d->commands.clear ();    //don't send anything!
0316       return;
0317     }
0318 
0319     // we are done - send the commands
0320     cTriggerList *tl = (cTriggerList *) list();
0321     tl->processCommands (d->commands);
0322     d->commands.clear ();
0323   }
0324 
0325   // execute the script, if any
0326   QString script = strVal ("script");
0327   if (!script.isEmpty()) {
0328     cScriptEval *eval = dynamic_cast<cScriptEval *>(am->object ("scripteval", sess));
0329     if (eval) eval->eval (script, d->p.scriptVariables());
0330   }
0331 }
0332 
0333 // helper for colorize
0334 QColor cTrigger::Private::getColor (int c)
0335 {
0336   int r, g, b;
0337   b = c % 256;
0338   c /= 256;
0339   g = c % 256;
0340   c /= 256;
0341   r = c % 256;
0342   return QColor (r, g, b);
0343 }
0344 
0345 void cTrigger::recolorize ()
0346 {
0347   cANSIParser *ansiparser = dynamic_cast<cANSIParser *>(cActionManager::self()->object ("ansiparser", list()->session()));
0348   cTextProcessor *textproc = dynamic_cast<cTextProcessor *>(cActionManager::self()->object ("textproc", list()->session()));
0349   
0350   std::list<colorChange> colors;
0351   
0352   // now go entry by entry
0353   int colorizationCount = intVal ("colorize-count");
0354   for (int clr = 1; clr <= colorizationCount; clr++)
0355   {
0356     QString sclr = QString::number (clr);
0357     QString varname = strVal ("colorize-variable-"+sclr).trimmed();
0358     //trim leading $ from varname
0359     if (varname[0] == '$') varname = varname.mid (1);    
0360     int fgc = intVal ("colorize-fg-"+sclr);
0361     int bgc = intVal ("colorize-bg-"+sclr);
0362     // sanity check
0363     if ((fgc < -16) || (fgc > 256*256*256)) fgc = 0;
0364     if ((bgc < -16) || (bgc > 256*256*256)) bgc = 0;
0365 
0366     if ((bgc == 0) && (fgc == 0)) continue;   // no change
0367 
0368     // get new colors
0369     QColor fg, bg;
0370     if (fgc > 0) {
0371       fg = d->getColor (fgc - 1);  // RGB value
0372     } else if (fgc < 0) {
0373       fg = ansiparser->color (fgc + 16);  // ANSI value
0374     }
0375     if (bgc > 0) {
0376       bg = d->getColor (bgc - 1);  // RGB value
0377     } else if (bgc < 0) {
0378       bg = ansiparser->color (bgc + 16);  // ANSI value
0379     }
0380 
0381     // positions
0382     int fromIndex, length;
0383     d->p.variablePosition (varname, &fromIndex, &length);
0384     if (fromIndex == -1)  //failed
0385       continue;
0386     
0387     //if we are here, colorization can occur!
0388     colorChange chg;
0389     chg.fg = fg;
0390     chg.bg = bg;
0391     chg.keepfg = (fgc == 0);
0392     chg.keepbg = (bgc == 0);
0393     chg.start = fromIndex;
0394     chg.len = length;
0395     colors.push_back (chg);
0396   }
0397   
0398   //okay, recolorize!
0399   textproc->recolorize (colors);
0400 }
0401 
0402 void cTrigger::rewrite ()
0403 {
0404   QString varname = strVal ("rewrite-var");
0405   //trim leading $
0406   if (varname[0] == '$')
0407     varname = varname.mid (1);
0408   int fromIndex, length;
0409   d->p.variablePosition (varname, &fromIndex, &length);
0410   if (fromIndex == -1)
0411     return;
0412   
0413   QString rt = strVal ("rewrite-text");
0414   //expand pseudo-variables in the replacement text
0415   d->p.expandPseudoVariables (rt);
0416   //also expand real variables, if any
0417   cVariableList *vl = dynamic_cast<cVariableList *>(cActionManager::self()->object ("variables", list()->session()));
0418   rt = vl->expandVariables (rt, true);
0419   cTriggerList *tl = (cTriggerList *) list();
0420   tl->rewriteText (fromIndex, length, rt);
0421 }
0422 
0423