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

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 += "&lt;";
0793     else if (_text[i] == '>')
0794       res += "&gt;";
0795     else if (_text[i] == '&')
0796       res += "&amp;";
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