File indexing completed on 2025-01-12 06:47:30
0001 // 0002 // C++ Implementation: ctextchunk 0003 // 0004 // Description: 0005 // 0006 /* 0007 Copyright 2004-2011 Tomas Mecir <kmuddy@kmuddy.com> 0008 0009 This program is free software; you can redistribute it and/or 0010 modify it under the terms of the GNU General Public License as 0011 published by the Free Software Foundation; either version 2 of 0012 the License, or (at your option) any later version. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 0023 #include "ctextchunk.h" 0024 0025 #include "cansiparser.h" 0026 #include "cconsole.h" 0027 0028 #include <qcolor.h> 0029 #include <qfont.h> 0030 #include <qpainter.h> 0031 #include <qregexp.h> 0032 0033 #include <stdlib.h> 0034 0035 /** state variables needed to paint a row */ 0036 /* 0037 struct paintStatus { 0038 int length, selstart, sellen; 0039 0040 int charWidth, charHeight; 0041 int cellHeight; 0042 0043 bool blinking, negative, invisible; 0044 0045 //paint() won't display text if this is on - used to display blinking text 0046 bool dontShowText; 0047 QFont defaultFont, paintFont; 0048 QColor paintColor, fillColor; 0049 QColor defbkcolor; 0050 }; 0051 */ 0052 0053 cTextChunk::cTextChunk (cConsole *_console) 0054 { 0055 init (_console); 0056 } 0057 0058 cTextChunk::cTextChunk (cConsole *_console, const QString &text) 0059 { 0060 init (_console); 0061 chunkText *cht = new chunkText; 0062 cht->setText (text); 0063 appendEntry (cht); 0064 } 0065 0066 void cTextChunk::init (cConsole *_console) 0067 { 0068 startattr.startpos = 0; 0069 startattr.attrib = 0; 0070 // pstatus = new paintStatus; 0071 console = _console; 0072 //update the timestamp 0073 timestamp = QDateTime::currentDateTime(); 0074 } 0075 0076 cTextChunk::~cTextChunk () 0077 { 0078 //delete all chunks... 0079 list<chunkItem *>::iterator it; 0080 for (it = _entries.begin(); it != _entries.end(); ++it) 0081 delete *it; 0082 _entries.clear(); 0083 // delete pstatus; 0084 } 0085 0086 void cTextChunk::appendEntry (chunkItem *entry) 0087 { 0088 if (!entry) return; 0089 //compute starting position 0090 int basepos = startattr.startpos; 0091 if (!_entries.empty()) 0092 basepos = _entries.back()->startPos() + _entries.back()->length(); 0093 //add the new entry 0094 _entries.push_back (entry); 0095 //update starting position 0096 entry->setStartPos (basepos); 0097 entry->_chunk = this; 0098 //update the timestamp 0099 timestamp = QDateTime::currentDateTime(); 0100 } 0101 0102 void cTextChunk::append (cTextChunk *chunk2) 0103 { 0104 if (_entries.empty()) 0105 // this chunk empty - copy starting attributes from the other chunk 0106 setStartAttr (chunk2->startAttr()); 0107 else { 0108 // this chunk not empty - copy start attributes as regular entries .., 0109 QColor fg = chunk2->startAttr().fg; 0110 QColor bg = chunk2->startAttr().bg; 0111 int attrib = chunk2->startAttr().attrib; 0112 0113 chunkFg *ch1 = new chunkFg; 0114 ch1->setFg (fg); 0115 chunkBg *ch2 = new chunkBg; 0116 ch2->setBg (bg); 0117 chunkAttrib *ch3 = new chunkAttrib; 0118 ch3->setAttrib (attrib); 0119 0120 _entries.push_back (ch1); 0121 _entries.push_back (ch2); 0122 _entries.push_back (ch3); 0123 } 0124 //add entries... 0125 list<chunkItem *>::iterator it; 0126 for (it = chunk2->_entries.begin(); it != chunk2->_entries.end(); ++it) 0127 //add entry 0128 _entries.push_back (*it); 0129 0130 fixupStartPositions (); 0131 0132 //update the timestamp 0133 timestamp = QDateTime::currentDateTime(); 0134 0135 //finally, delete chunk2 - but clear its list first to prevent destructor from deleting items 0136 chunk2->_entries.clear(); 0137 delete chunk2; 0138 } 0139 0140 chunkItem *cTextChunk::itemAt (int pos) 0141 { 0142 list<chunkItem *>::iterator it; 0143 for (it = _entries.begin(); it != _entries.end(); ++it) 0144 { 0145 int spos = (*it)->startPos (); 0146 int len = (*it)->length (); 0147 if ((len > 0) && (pos >= spos) && (pos < spos + len)) 0148 break; 0149 } 0150 if (it != _entries.end()) 0151 return *it; 0152 return nullptr; 0153 } 0154 0155 cTextChunk *cTextChunk::splitLine (int idx, bool wordwrap, int indent, 0156 bool trimSpaces) 0157 { 0158 //look if we need to split the line 0159 if (length() <= idx) 0160 return nullptr; 0161 0162 //OKay, we need to split... 0163 QString text = plainText (); 0164 int startpos = startattr.startpos; 0165 0166 //find position, where to split... 0167 int splitpos = idx - 1; 0168 if (wordwrap) 0169 { 0170 if (text[splitpos + 1] != ' ') //okay if next char is not a space 0171 { 0172 while (splitpos >= startpos) 0173 if (text[splitpos] == ' ') //wrap here 0174 break; 0175 else 0176 splitpos--; 0177 if (splitpos < startpos) //no suitable wrap-position found 0178 splitpos = idx - 1; 0179 } 0180 } 0181 //splitpos now contains the last letter index that will be on line 1 0182 0183 //now go, find the item that this splitpos belongs to, and split there! 0184 bool moving = false; 0185 cTextChunk *ch2 = nullptr; 0186 chunkStart newstartattr = startattr; 0187 newstartattr.startpos = indent; //start at indentation value 0188 list<chunkItem *>::iterator it, moveit; 0189 for (it = _entries.begin(); it != _entries.end(); ++it) 0190 { 0191 char type = (*it)->type(); 0192 0193 //update newstartattr 0194 switch (type) 0195 { 0196 case CHUNK_FG: newstartattr.fg = ((chunkFg*) (*it))->fg(); 0197 break; 0198 case CHUNK_BG: newstartattr.bg = ((chunkBg *) (*it))->bg(); 0199 break; 0200 case CHUNK_ATTRIB: newstartattr.attrib = ((chunkAttrib *) (*it))->attrib(); 0201 break; 0202 } 0203 0204 if ((*it)->length() == 0) 0205 continue; //this chunk has no length - OK 0206 int last = (*it)->startPos() + (*it)->length() - 1; 0207 0208 if (last < splitpos) //don't split here yet... 0209 continue; 0210 0211 moving = true; 0212 //OKay, this chunk is the one to split! 0213 if (last == splitpos) 0214 { 0215 ++it; 0216 moveit = it; 0217 } 0218 else 0219 { 0220 //split the text in two 0221 chunkItem *chunk; 0222 chunk = (*it)->split (splitpos - (*it)->startPos()); 0223 if (chunk) 0224 { 0225 ++it; 0226 it = _entries.insert (it, chunk); 0227 moveit = it; 0228 } 0229 } 0230 break; 0231 } 0232 0233 bool mustTrim = trimSpaces; 0234 if (moving) //now move remaining entries... 0235 { 0236 ch2 = new cTextChunk (console); 0237 ch2->setStartAttr (newstartattr); 0238 while (moveit != _entries.end()) 0239 { 0240 if (mustTrim && (*moveit)->length() > 0) 0241 { 0242 (*moveit)->trimLeft (); 0243 if ((*moveit)->length()) 0244 { 0245 mustTrim = false; 0246 //add to second list 0247 ch2->appendEntry (*moveit); 0248 } 0249 else 0250 delete *moveit; //chunk not needed anymore 0251 } 0252 else 0253 //just add to second list 0254 ch2->appendEntry (*moveit); 0255 //remove from first list 0256 moveit = _entries.erase (moveit); 0257 } 0258 } 0259 0260 //update the timestamp 0261 timestamp = QDateTime::currentDateTime(); 0262 0263 //return the result 0264 return ch2; 0265 } 0266 0267 0268 struct letterColor { 0269 QColor fg, bg; 0270 }; 0271 0272 void cTextChunk::applyColorChanges (list<colorChange> &changes) 0273 { 0274 // *** this is more complex than the solution in 0.5-0.6.1, and may also be slower *** 0275 // *** on the other hand, it allows much better functionality, so it's worth it... *** 0276 0277 if (changes.empty()) 0278 return; 0279 0280 //generate an array of current colors 0281 QString plain = plainText (); 0282 int len = plain.length(); 0283 if (len == 0) return; 0284 letterColor *colors = new letterColor[len]; 0285 letterColor *newcolors = new letterColor[len]; 0286 list<chunkItem *>::iterator ii; 0287 QColor curfg = startattr.fg; 0288 QColor curbg = startattr.bg; 0289 for (ii = _entries.begin(); ii != _entries.end(); ++ii) 0290 { 0291 if ((*ii)->type() == CHUNK_FG) 0292 curfg = ((chunkFg *) *ii)->fg(); 0293 else if ((*ii)->type() == CHUNK_BG) 0294 curbg = ((chunkBg *) *ii)->bg(); 0295 0296 if ((*ii)->length() == 0) 0297 continue; 0298 for (int i = (*ii)->startPos(); i < (*ii)->startPos() + (*ii)->length(); i++) 0299 { 0300 colors[i].fg = curfg; 0301 colors[i].bg = curbg; 0302 } 0303 } 0304 //generate an array of new colors, and replace color settings with new ones if needed 0305 for (int i = 0; i < len; i++) 0306 { 0307 newcolors[i].fg = colors[i].fg; 0308 newcolors[i].bg = colors[i].bg; 0309 } 0310 0311 list<colorChange>::iterator it; 0312 for (it = changes.begin(); it != changes.end(); ++it) 0313 { 0314 for (int i = (*it).start; i < (*it).start + (*it).len; i++) 0315 if (i < len) 0316 { 0317 if ((*it).keepfg) 0318 newcolors[i].fg = colors[i].fg; 0319 else 0320 newcolors[i].fg = (*it).fg; 0321 if ((*it).keepbg) 0322 newcolors[i].bg = colors[i].bg; 0323 else 0324 newcolors[i].bg = (*it).bg; 0325 } 0326 } 0327 // OK. Now we have a list of colors of all letters to play with :D 0328 0329 //delete all color changes from the old string 0330 ii = _entries.begin(); 0331 while (ii != _entries.end()) 0332 { 0333 //erase FG/BG, keep the rest 0334 if (((*ii)->type() == CHUNK_FG) || ((*ii)->type() == CHUNK_BG)) 0335 { 0336 delete *ii; 0337 ii = _entries.erase (ii); 0338 } 0339 else 0340 ++ii; 0341 } 0342 //update initial color settings 0343 startattr.fg = newcolors[0].fg; 0344 startattr.bg = newcolors[0].bg; 0345 0346 //generate list of color-change item chunks 0347 list<chunkItem *> colorchanges; 0348 curfg = startattr.fg; 0349 curbg = startattr.bg; 0350 for (int i = 1; i < len; i++) 0351 { 0352 if (newcolors[i].fg != curfg) 0353 { 0354 curfg = newcolors[i].fg; 0355 chunkFg *chfg = new chunkFg; 0356 chfg->setFg (curfg); 0357 chfg->setStartPos (startattr.startpos + i); 0358 colorchanges.push_back (chfg); 0359 } 0360 if (newcolors[i].bg != curbg) 0361 { 0362 curbg = newcolors[i].bg; 0363 chunkBg *chbg = new chunkBg; 0364 chbg->setBg (curbg); 0365 chbg->setStartPos (startattr.startpos + i); 0366 colorchanges.push_back (chbg); 0367 } 0368 } 0369 0370 //fill in the generated color changes 0371 list<chunkItem *>::iterator ii2 = colorchanges.begin (); 0372 //only if there is at least one such color change (initial color is not counted into this) 0373 if (ii2 != colorchanges.end()) 0374 { 0375 ii = _entries.begin(); 0376 while ((ii != _entries.end()) && (ii2 != colorchanges.end())) 0377 { 0378 int pos = (*ii)->startPos (); 0379 int len = (*ii)->length (); 0380 int chpos = (*ii2)->startPos(); 0381 if (len == 0) //zero-length items are not interesting here, so we just skip them 0382 { 0383 ++ii; 0384 continue; 0385 } 0386 if (chpos >= pos + len) //this item won't be affected... 0387 { 0388 ++ii; 0389 continue; 0390 } 0391 //if we are here, then this item WILL be affected 0392 if (chpos > pos) //we need to split the text if this one is true 0393 { 0394 chunkItem *item = (*ii)->split (chpos - pos - 1); 0395 if (item) 0396 { 0397 ++ii; 0398 _entries.insert (ii, item); //insert BEFORE iterator position 0399 --ii; //okay, now we are between the two splitted chunk items 0400 } 0401 } 0402 0403 //now we insert the colorization commands and then we can move on to the next one 0404 pos = (*ii)->startPos (); 0405 while ((ii2 != colorchanges.end()) && ((*ii2)->startPos() == pos)) 0406 { 0407 chunkItem *item = *ii2; 0408 _entries.insert (ii, item); //insert colorization command before our chunk item... 0409 ++ii2; 0410 } 0411 } 0412 } 0413 0414 //simplify the result 0415 simplify (); 0416 0417 //free up memory 0418 delete[] colors; 0419 delete[] newcolors; 0420 0421 //update the timestamp 0422 timestamp = QDateTime::currentDateTime(); 0423 } 0424 0425 void cTextChunk::replace (int pos, int len, const QString newtext) 0426 { 0427 list<chunkItem *>::iterator startit, endit; 0428 int spos, l; 0429 0430 //step 1: prepare replacement chunk 0431 chunkText *chunk = nullptr; 0432 if (!newtext.isEmpty()) 0433 { 0434 chunk = new chunkText; 0435 chunk->setText (newtext); 0436 chunk->setStartPos (pos); 0437 } 0438 0439 //step 2: find position for the new chunk 0440 list<chunkItem *>::iterator it; 0441 for (it = _entries.begin(); it != _entries.end(); ++it) 0442 { 0443 spos = (*it)->startPos (); 0444 l = (*it)->length (); 0445 if (l && (pos >= spos) && (pos <= spos+l)) //chunk of non-zero length containing pos 0446 { 0447 //alright, this is the starting position! 0448 startit = it; 0449 break; 0450 } 0451 } 0452 if (it == _entries.end()) //too far - appending to the very end 0453 { 0454 appendEntry (chunk); 0455 return; 0456 } 0457 0458 //step 3: find final position 0459 for (; it != _entries.end(); ++it) 0460 { 0461 spos = (*it)->startPos (); 0462 l = (*it)->length (); 0463 if (pos+len <= spos+l) 0464 { 0465 //alright, this is the ending position! 0466 endit = it; 0467 break; 0468 } 0469 } 0470 if (it == _entries.end()) 0471 endit = (_entries.end())--; //can do that - would stop at step 2 if the list were empty 0472 0473 //step 4: handle (quite common) situation, where starting and ending chunks are the same 0474 if (startit == endit) 0475 { 0476 (*startit)->replace (pos - (*startit)->startPos(), len, newtext); 0477 delete chunk; // not needed after all :) 0478 0479 fixupStartPositions (); 0480 return; 0481 } 0482 0483 //step 5: split margin chunks if needed 0484 //starting... 0485 spos = (*startit)->startPos (); 0486 if (pos > spos) 0487 { 0488 chunkItem *ch = (*startit)->split (pos - spos - 1); 0489 startit++; 0490 if (ch) 0491 delete ch; //this one is a part of the replaced text, hence we don't need it anymore 0492 } 0493 spos = (*endit)->startPos (); 0494 l = (*endit)->length (); 0495 if (pos+len < spos+l) 0496 { 0497 chunkItem *ch = (*endit)->split (pos+len - spos - 1); 0498 endit++; 0499 endit = _entries.insert (endit, ch); 0500 endit--; 0501 } 0502 0503 //step 6: put replacement chunk to place 0504 if (chunk) _entries.insert (startit, chunk); 0505 0506 //step 7: delete old chunks 0507 endit++; 0508 it = startit; 0509 while (it != endit) 0510 { 0511 delete *it; 0512 it = _entries.erase (it); 0513 } 0514 0515 fixupStartPositions (); 0516 } 0517 0518 bool cTextChunk::expireNamedLinks (const QString &name) 0519 { 0520 //did any link expire? 0521 bool expired = false; 0522 0523 list<chunkItem *>::iterator it; 0524 for (it = _entries.begin(); it != _entries.end(); ++it) 0525 if ((*it)->type() == CHUNK_LINK) 0526 { 0527 chunkLink *chl = (chunkLink *) *it; 0528 if (chl->name().isEmpty()) 0529 continue; //nothing is the link has no name 0530 0531 if ((name == chl->name()) || (name == QString())) 0532 { 0533 //this link should expire 0534 chunkText *cht = new chunkText; //create a static text 0535 cht->setText (chl->text()); 0536 cht->setStartPos (chl->startPos ()); 0537 delete *it; //delete the link 0538 it = _entries.erase (it); //remove the link from the list 0539 it = _entries.insert (it, cht); //add static text to its place, make iterator point to it 0540 } 0541 } 0542 0543 return expired; 0544 } 0545 0546 QString cTextChunk::plainText () 0547 { 0548 QString s; 0549 //start with some spaces 0550 if (startattr.startpos) 0551 s.fill (' ', startattr.startpos); 0552 //then add all the texts... 0553 list<chunkItem *>::iterator it; 0554 for (it = _entries.begin(); it != _entries.end(); ++it) 0555 { 0556 if ((*it)->type() == CHUNK_TEXT) 0557 s += ((chunkText *) *it)->text(); 0558 if ((*it)->type() == CHUNK_LINK) 0559 s += ((chunkLink *) *it)->text(); 0560 } 0561 return s; 0562 } 0563 0564 QStringList cTextChunk::words (int minLength) 0565 { 0566 QString t = plainText(); 0567 //create the list of words 0568 // Regexp splitting of words to remove special characters. Added by Magnus Lundborg 051005 0569 QStringList res = t.split (QRegExp("[\\s\\.\\,\\(\\)\\[\\]\\?\\!\\:\\;\"\']")); 0570 //remove words that are too short 0571 QStringList::iterator it = res.begin (); 0572 while (it != res.end()) 0573 if ((*it).length() < minLength) 0574 it = res.erase (it); 0575 else 0576 ++it; 0577 return res; 0578 } 0579 0580 int cTextChunk::length () 0581 { 0582 int len = 0; 0583 list<chunkItem *>::iterator it; 0584 for (it = _entries.begin(); it != _entries.end(); ++it) 0585 len += (*it)->length(); 0586 return len; 0587 } 0588 0589 void cTextChunk::simplify () 0590 { 0591 //This is just an optimalization method - i.e., everything will work without it, only that 0592 //it might be slower... 0593 0594 //NOTHING HERE YET 0595 0596 } 0597 0598 cTextChunk *cTextChunk::duplicate () 0599 { 0600 cTextChunk *chunk = new cTextChunk (console); 0601 chunk->setStartAttr (startattr); 0602 list<chunkItem *>::iterator it; 0603 for (it = _entries.begin(); it != _entries.end(); ++it) 0604 { 0605 chunkItem *item = (*it)->duplicate (); 0606 chunk->_entries.push_back (item); 0607 } 0608 chunk->fixupStartPositions (); 0609 return chunk; 0610 } 0611 0612 // TODO - we don't need this any more 0613 /* 0614 void cTextChunk::paint (int length, int selstart, int sellen, 0615 int charWidth, int charHeight, 0616 QPainter *painter, QPainter *blinkpainter) 0617 { 0618 //prepare the paintStruct structure 0619 pstatus->length = length; 0620 pstatus->selstart = selstart; 0621 pstatus->sellen = sellen; 0622 pstatus->charWidth = charWidth; 0623 pstatus->charHeight = charHeight; 0624 pstatus->cellHeight = console->cellHeight(); 0625 pstatus->dontShowText = false; 0626 pstatus->blinking = false; 0627 pstatus->negative = false; 0628 pstatus->invisible = false; 0629 0630 //fill in initial attributes 0631 pstatus->paintColor = startattr.fg; 0632 pstatus->fillColor = startattr.bg; 0633 pstatus->defbkcolor = console->defaultBkColor(); 0634 pstatus->paintFont = console->font(); 0635 pstatus->defaultFont = console->font(); 0636 0637 if (startattr.attrib & ATTRIB_BOLD) 0638 pstatus->paintFont.setBold (true); 0639 if (startattr.attrib & ATTRIB_ITALIC) 0640 pstatus->paintFont.setItalic (true); 0641 if (startattr.attrib & ATTRIB_UNDERLINE) 0642 pstatus->paintFont.setUnderline (true); 0643 if (startattr.attrib & ATTRIB_STRIKEOUT) 0644 pstatus->paintFont.setStrikeOut (true); 0645 if (startattr.attrib & ATTRIB_BLINK) 0646 pstatus->blinking = true; 0647 if (startattr.attrib & ATTRIB_NEGATIVE) 0648 pstatus->negative = true; 0649 if (startattr.attrib & ATTRIB_INVISIBLE) 0650 pstatus->invisible = true; 0651 0652 //go and paint! (space 0..startpos is NOT painted as of now!) 0653 list<chunkItem *>::iterator it; 0654 for (it = _entries.begin(); it != _entries.end(); ++it) 0655 { 0656 pstatus->dontShowText = false; 0657 painter->setBackground (pstatus->defbkcolor); 0658 (*it)->paint (painter, pstatus); 0659 if (blinkpainter) 0660 { 0661 if (pstatus->blinking) 0662 pstatus->dontShowText = true; 0663 blinkpainter->setBackground (pstatus->defbkcolor); 0664 (*it)->paint (blinkpainter, pstatus); 0665 } 0666 } 0667 } 0668 */ 0669 0670 QString cTextChunk::toText () 0671 { 0672 QString s; 0673 //start with some spaces 0674 if (startattr.startpos) 0675 s.fill (' ', startattr.startpos); 0676 //then add all the texts... 0677 list<chunkItem *>::iterator it; 0678 for (it = _entries.begin(); it != _entries.end(); ++it) 0679 s += (*it)->toText(); 0680 return s; 0681 } 0682 0683 QString cTextChunk::toAnsi (cANSIParser *ap) 0684 { 0685 QString s; 0686 0687 //starting attributes 0688 s += "\x1b[0m"; 0689 s += chunkFg::constructAnsi (startattr.fg, ap); 0690 s += chunkBg::constructAnsi (startattr.bg, ap); 0691 s += chunkAttrib::constructAnsi (startattr.attrib); 0692 0693 //some spaces if needed 0694 if (startattr.startpos) 0695 s.fill (' ', startattr.startpos); 0696 //then add all the texts... 0697 list<chunkItem *>::iterator it; 0698 for (it = _entries.begin(); it != _entries.end(); ++it) 0699 s += (*it)->toAnsi (ap); 0700 return s; 0701 } 0702 0703 QString cTextChunk::toHTML () 0704 { 0705 QString s, suffix; 0706 0707 //starting attributes 0708 s += chunkFg::constructHTML (startattr.fg, suffix); 0709 s += chunkBg::constructHTML (startattr.bg, suffix); 0710 0711 //some spaces if needed 0712 if (startattr.startpos) 0713 s.fill (' ', startattr.startpos); 0714 //then add all the texts... 0715 list<chunkItem *>::iterator it; 0716 for (it = _entries.begin(); it != _entries.end(); ++it) 0717 s += (*it)->toHTML (suffix); 0718 s += suffix; 0719 return "<p>" + s + "</p>"; 0720 } 0721 0722 void cTextChunk::insertToDocument (QTextCursor &cursor) 0723 { 0724 QTextCharFormat format; 0725 chunkFg::setFormat (format, startattr.fg); 0726 chunkBg::setFormat (format, startattr.bg); 0727 chunkAttrib::setFormat (format, startattr.attrib); 0728 0729 /* I think this isn't needed anymore ... 0730 if (startattr.startpos) 0731 { 0732 QString s; 0733 s.fill (' ', startattr.startpos); 0734 cursor.insertText (s, format); 0735 } 0736 */ 0737 0738 for (chunkItem *item : _entries) 0739 item->insertToDocument (cursor, format); 0740 } 0741 0742 0743 cTextChunk *cTextChunk::makeLine (const QString &text, QColor fg, QColor bg, cConsole *console) 0744 { 0745 cTextChunk *chunk = new cTextChunk (console); 0746 chunkStart startattr; 0747 startattr.attrib = 0; 0748 startattr.startpos = 0; 0749 startattr.fg = fg; 0750 startattr.bg = bg; 0751 0752 chunk->setStartAttr (startattr); 0753 chunkFg *chfg = new chunkFg; 0754 chfg->setFg (fg); 0755 chunkBg *chbg = new chunkBg; 0756 chbg->setBg (bg); 0757 chunkText *cht = new chunkText; 0758 cht->setText (text); 0759 0760 chfg->setStartPos (0); 0761 chbg->setStartPos (0); 0762 cht->setStartPos (0); 0763 chunk->appendEntry (chfg); 0764 chunk->appendEntry (chbg); 0765 chunk->appendEntry (cht); 0766 return chunk; 0767 } 0768 0769 QDateTime cTextChunk::getTimeStamp () 0770 { 0771 return timestamp; 0772 } 0773 0774 void cTextChunk::fixupStartPositions () 0775 { 0776 int pos = startattr.startpos; 0777 list<chunkItem *>::iterator it; 0778 for (it = _entries.begin(); it != _entries.end(); ++it) 0779 { 0780 (*it)->setStartPos (pos); 0781 pos += (*it)->length (); 0782 } 0783 } 0784 0785 // --- chunkItem-derived stuff starts here --- 0786 0787 QString chunkText::toHTML (QString &) 0788 { 0789 QString res; 0790 for (int i = 0; i < _text.length(); ++i) { 0791 if (_text[i] == '<') 0792 res += "<"; 0793 else if (_text[i] == '>') 0794 res += ">"; 0795 else if (_text[i] == '&') 0796 res += "&"; 0797 else 0798 res += _text[i]; 0799 } 0800 return res; 0801 } 0802 0803 chunkItem *chunkText::split (int pos) 0804 { 0805 if ((pos < 0) || (pos >= length()-1)) 0806 return nullptr; //do NOTHING 0807 chunkText *cht = new chunkText; 0808 int l1 = pos + 1; 0809 int l2 = _text.length() - l1; 0810 cht->setText (_text.right (l2)); 0811 setText (_text.left (l1)); 0812 cht->setStartPos (startPos() + l1); 0813 return cht; 0814 } 0815 0816 chunkItem *chunkLink::split (int pos) 0817 { 0818 if ((pos < 0) || (pos >= length()-1)) 0819 return nullptr; //do NOTHING 0820 chunkLink *chl = (chunkLink *) duplicate (); 0821 int l1 = pos + 1; 0822 int l2 = _text.length() - l1; 0823 chl->setText (_text.right (l2)); 0824 setText (_text.left (l1)); 0825 chl->setStartPos (startPos() + l1); 0826 0827 return chl; 0828 } 0829 0830 chunkItem *chunkText::duplicate() 0831 { 0832 chunkText *item = new chunkText; 0833 item->startpos = startpos; 0834 item->_text = _text; 0835 return item; 0836 } 0837 0838 chunkItem *chunkFg::duplicate() 0839 { 0840 chunkFg *item = new chunkFg; 0841 item->startpos = startpos; 0842 item->_fg = _fg; 0843 return item; 0844 } 0845 0846 chunkItem *chunkBg::duplicate() 0847 { 0848 chunkBg *item = new chunkBg; 0849 item->startpos = startpos; 0850 item->_bg = _bg; 0851 return item; 0852 } 0853 0854 chunkItem *chunkAttrib::duplicate() 0855 { 0856 chunkAttrib *item = new chunkAttrib; 0857 item->startpos = startpos; 0858 item->_attrib = _attrib; 0859 return item; 0860 } 0861 0862 chunkItem *chunkLink::duplicate() 0863 { 0864 chunkLink *item = new chunkLink; 0865 item->startpos = startpos; 0866 item->_name = _name; 0867 item->_target = _target; 0868 item->_text = _text; 0869 item->_hint = _hint; 0870 item->_iscommand = _iscommand; 0871 item->_toprompt = _toprompt; 0872 item->_ismenu = _ismenu; 0873 return item; 0874 } 0875 0876 void chunkText::trimLeft () 0877 { 0878 int len; 0879 int tlen = _text.length(); 0880 for (len = 0; len < tlen; ++len) 0881 if (!_text[len].isSpace()) 0882 break; 0883 if (len) 0884 _text.remove (0, len); 0885 } 0886 0887 void chunkLink::trimLeft () 0888 { 0889 int len; 0890 int tlen = _text.length(); 0891 for (len = 0; len < tlen; ++len) 0892 if (!_text[len].isSpace()) 0893 break; 0894 if (len) 0895 _text.remove (0, len); 0896 } 0897 0898 void chunkText::replace (int pos, int len, const QString &newtext) 0899 { 0900 _text = _text.replace (pos, len, newtext); 0901 } 0902 0903 void chunkLink::replace (int pos, int len, const QString &newtext) 0904 { 0905 _text = _text.replace (pos, len, newtext); 0906 } 0907 0908 void chunkLink::parseMenu () 0909 { 0910 _menu.clear(); 0911 if (!isMenu()) 0912 return; 0913 0914 QStringList targets = _target.split ('|', Qt::KeepEmptyParts); 0915 QStringList hints = _hint.split ('|', Qt::KeepEmptyParts); 0916 //first hint is the main hint, what follows (if anything) are hints for individual commands 0917 if (!hints.empty()) 0918 { 0919 _hint = hints.first(); 0920 hints.pop_front (); 0921 } 0922 0923 //now fill up the relevant structures 0924 QStringList::iterator it1, it2; 0925 it2 = hints.begin(); 0926 for (it1 = targets.begin(); it1 != targets.end(); ++it1) 0927 { 0928 menuItem mi; 0929 mi.command = *it1; 0930 if (it2 != hints.end()) 0931 mi.caption = *it2; 0932 else 0933 //no hint given - command used 0934 mi.caption = mi.command; 0935 0936 _menu.push_back (mi); 0937 if (it2 != hints.end()) ++it2; 0938 } 0939 } 0940 0941 /* 0942 void chunkItem::paintText (const QString &text, QPainter *painter, QFont font, 0943 QColor fg, QColor bg, paintStatus *ps) 0944 { 0945 //draw the text, including selection if needed... 0946 int endpos = startpos + text.length() - 1; 0947 int endsel = ps->selstart + ps->sellen - 1; 0948 QString t = text; 0949 if (endpos + 1 > ps->length) 0950 //text won't fit into console - trim it! 0951 t.truncate (ps->length - startpos); 0952 0953 if ((ps->selstart == -1) || (ps->sellen == 0) || 0954 (endpos < ps->selstart) || (startpos > endsel)) 0955 { 0956 //simple case - no selection in this chunk... 0957 painter->setPen (fg); 0958 painter->setBackground (bg); 0959 painter->setFont (font); 0960 if (bg != ps->defbkcolor) 0961 //only paint if non-default background color is to be used 0962 painter->fillRect (ps->charWidth * startpos, 0, ps->charWidth * t.length(), 0963 ps->cellHeight, bg); 0964 if (((!ps->blinking) || (!ps->dontShowText)) && (!ps->invisible)) 0965 painter->drawText (ps->charWidth * startpos, ps->charHeight, t); 0966 } 0967 else 0968 { 0969 //some of this text is selected 0970 QString t1, t2, t3; //t1 = before, t2 = selected, t3 = after 0971 int len1, len3; 0972 if (ps->selstart <= startpos) 0973 len1 = 0; 0974 else 0975 len1 = ps->selstart - startpos; 0976 if (endsel >= endpos) 0977 len3 = 0; 0978 else 0979 len3 = endpos - endsel; 0980 if (len1) t1 = t.left (len1); 0981 if (len3) t3 = t.right (len3); 0982 t2 = t.mid (len1, t.length() - len1 - len3); 0983 0984 //good, now draw each part of the text... 0985 if (len1) 0986 { 0987 painter->setPen (fg); 0988 painter->setBackground (bg); 0989 painter->setFont (font); 0990 if (bg != ps->defbkcolor) 0991 //only paint if non-default background color is to be used 0992 painter->fillRect (ps->charWidth * startpos, 0, ps->charWidth * len1, 0993 ps->cellHeight, bg); 0994 if (((!ps->blinking) || (!ps->dontShowText)) && (!ps->invisible)) 0995 painter->drawText (ps->charWidth * startpos, ps->charHeight, t1); 0996 } 0997 0998 //we KNOW that t2 is not empty :D 0999 //this is the selection, painted in reversed colors... 1000 painter->setPen (bg); //REVERSE colors 1001 painter->setBackground (fg); //REVERSE colors 1002 painter->setFont (font); 1003 if (fg != ps->defbkcolor) 1004 //only paint if non-default background color is to be used 1005 // (this is the selection, so the condition will usually be true, but hey...) 1006 painter->fillRect (ps->charWidth * (startpos + len1), 0, ps->charWidth * t2.length(), 1007 ps->cellHeight, fg); 1008 if (((!ps->blinking) || (!ps->dontShowText)) && (!ps->invisible)) 1009 painter->drawText (ps->charWidth * (startpos + len1), ps->charHeight, t2); 1010 1011 if (len3) 1012 { 1013 painter->setPen (fg); 1014 painter->setBackground (bg); 1015 painter->setFont (font); 1016 if (bg != ps->defbkcolor) 1017 //only paint if non-default background color is to be used 1018 painter->fillRect (ps->charWidth * (startpos+len1+t2.length()), 0, ps->charWidth * len3, 1019 ps->cellHeight, bg); 1020 if (((!ps->blinking) || (!ps->dontShowText)) && (!ps->invisible)) 1021 painter->drawText (ps->charWidth * (startpos + len1 + t2.length()), ps->charHeight, t3); 1022 } 1023 } 1024 } 1025 1026 void chunkText::paint (QPainter *painter, paintStatus *ps) 1027 { 1028 if (startpos >= ps->length) //we're past console width 1029 return; 1030 QColor paintColor = ps->paintColor; 1031 QColor fillColor = ps->fillColor; 1032 if (ps->negative) //negative image - invert colors 1033 { 1034 paintColor = ps->fillColor; 1035 fillColor = ps->paintColor; 1036 } 1037 1038 //okay, paint the text! 1039 paintText (_text, painter, ps->paintFont, paintColor, fillColor, ps); 1040 } 1041 1042 void chunkFg::paint (QPainter *, paintStatus *ps) 1043 { 1044 if (startpos >= ps->length) //we're past console width 1045 return; 1046 1047 //set text color 1048 ps->paintColor = _fg; 1049 } 1050 1051 void chunkBg::paint (QPainter *, paintStatus *ps) 1052 { 1053 if (startpos >= ps->length) //we're past console width 1054 return; 1055 1056 //set text background 1057 ps->fillColor = _bg; 1058 } 1059 1060 void chunkAttrib::paint (QPainter *, paintStatus *ps) 1061 { 1062 if (startpos >= ps->length) //we're past console width 1063 return; 1064 1065 //set font back to default one... 1066 ps->paintFont = ps->defaultFont; 1067 ps->blinking = false; 1068 ps->negative = false; 1069 ps->invisible = false; 1070 1071 //set some font attributes and stuff 1072 if (_attrib & ATTRIB_BOLD) 1073 ps->paintFont.setBold (true); 1074 if (_attrib & ATTRIB_ITALIC) 1075 ps->paintFont.setItalic (true); 1076 if (_attrib & ATTRIB_UNDERLINE) 1077 ps->paintFont.setUnderline (true); 1078 if (_attrib & ATTRIB_STRIKEOUT) 1079 ps->paintFont.setStrikeOut (true); 1080 if (_attrib & ATTRIB_BLINK) 1081 ps->blinking = true; 1082 if (_attrib & ATTRIB_NEGATIVE) 1083 ps->negative = true; 1084 if (_attrib & ATTRIB_INVISIBLE) 1085 ps->invisible = true; 1086 } 1087 1088 void chunkLink::paint (QPainter *painter, paintStatus *ps) 1089 { 1090 if (startpos >= ps->length) //we're past console width 1091 return; 1092 QColor paintColor = linkColor; 1093 QColor fillColor = ps->fillColor; 1094 if (ps->negative) //negative image - invert colors 1095 { 1096 paintColor = ps->fillColor; 1097 fillColor = ps->paintColor; 1098 } 1099 1100 QFont font = ps->paintFont; 1101 font.setUnderline (true); 1102 1103 //okay, paint the text! 1104 paintText (_text, painter, font, paintColor, fillColor, ps); 1105 } 1106 */ 1107 1108 QString chunkFg::constructAnsi (QColor color, cANSIParser *ap) 1109 { 1110 QColor colors[16]; 1111 for (int i = 0; i < 16; i++) 1112 { 1113 colors[i] = ap->color (i); 1114 if (colors[i] == color) //exactly this color 1115 { 1116 QString s = "\x1b["; 1117 if (i >= 8) 1118 s += "1;"; 1119 s += QString::number (30 + i % 8); 1120 s += "m"; 1121 return s; 1122 } 1123 } 1124 1125 //no exact color match - this color did not originate as an ANSI sequence... 1126 //we need to find the best substitute for the color... 1127 int subst = 0; 1128 int bestmatch = abs (colors[0].red() - color.red()) + abs (colors[0].green() - color.green()) + 1129 abs (colors[0].blue() - color.blue()); 1130 for (int i = 1; i < 16; i++) 1131 { 1132 int match = abs (colors[i].red() - color.red()) + abs (colors[i].green() - color.green()) + 1133 abs (colors[i].blue() - color.blue()); 1134 if (match < bestmatch) 1135 { 1136 subst = i; 1137 match = bestmatch; 1138 } 1139 } 1140 1141 QString s = "\x1b["; 1142 if (subst >= 8) 1143 s += "1;"; 1144 s += QString::number (30 + subst % 8); 1145 s += "m"; 1146 return s; 1147 } 1148 1149 QString chunkBg::constructAnsi (QColor color, cANSIParser *ap) 1150 { 1151 //similar to chunkFg::constructANSI, only that it only uses the first 8 colors... 1152 1153 QColor colors[8]; 1154 for (int i = 0; i < 8; i++) 1155 { 1156 colors[i] = ap->color (i); 1157 if (colors[i] == color) //exactly this color 1158 { 1159 QString s = "\x1b["; 1160 s += QString::number (40 + i); 1161 s += "m"; 1162 return s; 1163 } 1164 } 1165 1166 //no exact color match - this color did not originate as an ANSI sequence... 1167 //we need to find the best substitute for the color... 1168 int subst = 0; 1169 int bestmatch = abs (colors[0].red() - color.red()) + abs (colors[0].green() - color.green()) + 1170 abs (colors[0].blue() - color.blue()); 1171 for (int i = 1; i < 8; i++) 1172 { 1173 int match = abs (colors[i].red() - color.red()) + abs (colors[i].green() - color.green()) + 1174 abs (colors[i].blue() - color.blue()); 1175 if (match < bestmatch) 1176 { 1177 subst = i; 1178 match = bestmatch; 1179 } 1180 } 1181 1182 QString s = "\x1b["; 1183 s += QString::number (40 + subst); 1184 s += "m"; 1185 return s; 1186 } 1187 1188 QString chunkAttrib::constructAnsi (unsigned char attrib) 1189 { 1190 //not very effective, eh? :D 1191 QString s; 1192 if (attrib & ATTRIB_BOLD) 1193 s += "\x1b[1m"; //bold 1194 else 1195 s += "\x1b[22m"; //bold off 1196 /* 1197 // All these attributes are now DISABLED 1198 // The reason is that they add quite a lot of unnecessary stuff into 1199 // transcript files - might be re-enabled if needed, but some output 1200 // optimization might be necessary (things like don't disable italics 1201 // if we know that it's off, and so) 1202 1203 if (attrib & ATTRIB_ITALIC) 1204 s += "\x1b[3m"; //italic 1205 else 1206 s += "\x1b[23m"; //italic off 1207 if (attrib & ATTRIB_UNDERLINE) 1208 s += "\x1b[4m"; //underline 1209 else 1210 s += "\x1b[24m"; //underline off 1211 if (attrib & ATTRIB_STRIKEOUT) 1212 s += "\x1b[9m"; //strikeout 1213 else 1214 s += "\x1b[29m"; //strikeout off 1215 if (attrib & ATTRIB_BLINK) 1216 s += "\x1b[5m"; //blink 1217 else 1218 s += "\x1b[25m"; //blink off 1219 if (attrib & ATTRIB_NEGATIVE) 1220 s += "\x1b[5m"; //negative 1221 else 1222 s += "\x1b[25m"; //negative off 1223 if (attrib & ATTRIB_INVISIBLE) 1224 s += "\x1b[7m"; //invisible 1225 else 1226 s += "\x1b[27m"; //invisible off 1227 */ 1228 return s; 1229 } 1230 1231 QString chunkFg::toAnsi (cANSIParser *ap) 1232 { 1233 return chunkFg::constructAnsi (_fg, ap); 1234 } 1235 1236 QString chunkBg::toAnsi (cANSIParser *ap) 1237 { 1238 return chunkBg::constructAnsi (_bg, ap); 1239 } 1240 1241 QString chunkAttrib::toAnsi (cANSIParser *) 1242 { 1243 return chunkAttrib::constructAnsi (_attrib); 1244 } 1245 1246 QString chunkLink::toAnsi (cANSIParser *ap) 1247 { 1248 QString s = chunkFg::constructAnsi (linkColor, ap); 1249 s += _text; 1250 return s; 1251 } 1252 1253 QString chunkFg::constructHTML (QColor color, QString &suffix) 1254 { 1255 suffix = "</font>" + suffix; 1256 return "<font style=\"color: " + color.name() + "\">"; 1257 } 1258 1259 QString chunkBg::constructHTML (QColor, QString &) 1260 { 1261 //NOTHING HERE AS OF NOW! 1262 return QString(); 1263 } 1264 1265 QString chunkFg::toHTML (QString &suffix) 1266 { 1267 return chunkFg::constructHTML (_fg, suffix); 1268 } 1269 1270 QString chunkBg::toHTML (QString &suffix) 1271 { 1272 return chunkBg::constructHTML (_bg, suffix); 1273 } 1274 1275 QColor chunkLink::linkColor = Qt::blue; 1276 QString chunkLink::toHTML (QString &) 1277 { 1278 QString rel = _iscommand ? "command" : "link"; 1279 QString href = rel + " " + (_toprompt ? "prompt" : "send") + " " + _target; 1280 return "<a rel=\"" + rel + "\" href=\"" + href + "\">" + _text + "</a>"; 1281 } 1282 1283 void chunkLink::insertToDocument (QTextCursor &cursor, QTextCharFormat &format) { 1284 QTextCharFormat linkformat = format; 1285 linkformat.setAnchor (true); 1286 QString href = QString(_iscommand ? "command" : "link") + " " + (_toprompt ? "prompt" : "send") + " " + _target; 1287 linkformat.setAnchorHref (href); 1288 if (linkformat.foreground().color() == _chunk->startAttr().fg) 1289 linkformat.setForeground (linkColor); 1290 cursor.insertText (_text, linkformat); 1291 } 1292 1293 void chunkFg::setFormat (QTextCharFormat &format, QColor color) 1294 { 1295 format.setForeground (color); 1296 } 1297 1298 void chunkBg::setFormat (QTextCharFormat &format, QColor color) 1299 { 1300 format.setBackground (color); 1301 } 1302 1303 void chunkAttrib::setFormat (QTextCharFormat &format, int attrib) 1304 { 1305 format.setFontWeight ((attrib & ATTRIB_BOLD) ? QFont::Bold : QFont::Normal); 1306 format.setFontItalic (attrib & ATTRIB_ITALIC); 1307 format.setFontUnderline (attrib & ATTRIB_UNDERLINE); 1308 format.setFontStrikeOut (attrib & ATTRIB_STRIKEOUT); 1309 // blink, negative, and invisible are currently not supported 1310 } 1311 1312