File indexing completed on 2024-04-21 04:02:46
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