File indexing completed on 2025-01-19 04:23:25

0001 /*
0002     Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
0003 
0004     This program is free software; you can redistribute it and/or modify
0005     it under the terms of the GNU General Public License as published by
0006     the Free Software Foundation; either version 2 of the License, or
0007     (at your option) any later version.
0008 
0009     This program is distributed in the hope that it will be useful,
0010     but WITHOUT ANY WARRANTY; without even the implied warranty of
0011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012     GNU General Public License for more details.
0013 
0014     You should have received a copy of the GNU General Public License
0015     along with this program; if not, write to the Free Software
0016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0017     02110-1301  USA.
0018 */
0019 
0020 // Own
0021 #include "Filter.h"
0022 
0023 // System
0024 #include <iostream>
0025 #include <memory>
0026 
0027 // Qt
0028 #include <QAction>
0029 #include <QApplication>
0030 #include <QtAlgorithms>
0031 #include <QClipboard>
0032 #include <QString>
0033 #include <QTextStream>
0034 #include <QSharedData>
0035 #include <QFile>
0036 #include <QDesktopServices>
0037 #include <QUrl>
0038 
0039 // KDE
0040 //#include <KLocale>
0041 //#include <KRun>
0042 
0043 // Konsole
0044 #include "TerminalCharacterDecoder.h"
0045 #include "konsole_wcwidth.h"
0046 
0047 using namespace Konsole;
0048 
0049 FilterChain::~FilterChain()
0050 {
0051     QMutableListIterator<Filter*> iter(*this);
0052 
0053     while ( iter.hasNext() )
0054     {
0055         Filter* filter = iter.next();
0056         iter.remove();
0057         delete filter;
0058     }
0059 }
0060 
0061 void FilterChain::addFilter(Filter* filter)
0062 {
0063     append(filter);
0064 }
0065 void FilterChain::removeFilter(Filter* filter)
0066 {
0067     removeAll(filter);
0068 }
0069 bool FilterChain::containsFilter(Filter* filter)
0070 {
0071     return contains(filter);
0072 }
0073 void FilterChain::reset()
0074 {
0075     QListIterator<Filter*> iter(*this);
0076     while (iter.hasNext())
0077         iter.next()->reset();
0078 }
0079 void FilterChain::setBuffer(const QString* buffer , const QList<int>* linePositions)
0080 {
0081     QListIterator<Filter*> iter(*this);
0082     while (iter.hasNext())
0083         iter.next()->setBuffer(buffer,linePositions);
0084 }
0085 void FilterChain::process()
0086 {
0087     QListIterator<Filter*> iter(*this);
0088     while (iter.hasNext())
0089         iter.next()->process();
0090 }
0091 void FilterChain::clear()
0092 {
0093     QList<Filter*>::clear();
0094 }
0095 Filter::HotSpot* FilterChain::hotSpotAt(int line , int column) const
0096 {
0097     QListIterator<Filter*> iter(*this);
0098     while (iter.hasNext())
0099     {
0100         Filter* filter = iter.next();
0101         Filter::HotSpot* spot = filter->hotSpotAt(line,column);
0102         if ( spot != 0 )
0103         {
0104             return spot;
0105         }
0106     }
0107 
0108     return 0;
0109 }
0110 
0111 QList<Filter::HotSpot*> FilterChain::hotSpots() const
0112 {
0113     QList<Filter::HotSpot*> list;
0114     QListIterator<Filter*> iter(*this);
0115     while (iter.hasNext())
0116     {
0117         Filter* filter = iter.next();
0118         list << filter->hotSpots();
0119     }
0120     return list;
0121 }
0122 //QList<Filter::HotSpot*> FilterChain::hotSpotsAtLine(int line) const;
0123 
0124 TerminalImageFilterChain::TerminalImageFilterChain()
0125 : _buffer(0)
0126 , _linePositions(0)
0127 {
0128 }
0129 
0130 TerminalImageFilterChain::~TerminalImageFilterChain()
0131 {
0132     delete _buffer;
0133     delete _linePositions;
0134 }
0135 
0136 void TerminalImageFilterChain::setImage(const Character* const image , int lines , int columns, const QVector<LineProperty>& lineProperties)
0137 {
0138     if (empty())
0139         return;
0140 
0141     // reset all filters and hotspots
0142     reset();
0143 
0144     PlainTextDecoder decoder;
0145     decoder.setTrailingWhitespace(false);
0146 
0147     // setup new shared buffers for the filters to process on
0148     QString* newBuffer = new QString();
0149     QList<int>* newLinePositions = new QList<int>();
0150     setBuffer( newBuffer , newLinePositions );
0151 
0152     // free the old buffers
0153     delete _buffer;
0154     delete _linePositions;
0155 
0156     _buffer = newBuffer;
0157     _linePositions = newLinePositions;
0158 
0159     QTextStream lineStream(_buffer);
0160     decoder.begin(&lineStream);
0161 
0162     for (int i=0 ; i < lines ; i++)
0163     {
0164         _linePositions->append(_buffer->length());
0165         decoder.decodeLine(image + i*columns,columns,LINE_DEFAULT);
0166 
0167         // pretend that each line ends with a newline character.
0168         // this prevents a link that occurs at the end of one line
0169         // being treated as part of a link that occurs at the start of the next line
0170         //
0171         // the downside is that links which are spread over more than one line are not
0172         // highlighted.
0173         //
0174         // TODO - Use the "line wrapped" attribute associated with lines in a
0175         // terminal image to avoid adding this imaginary character for wrapped
0176         // lines
0177         if ( !(lineProperties.value(i,LINE_DEFAULT) & LINE_WRAPPED) )
0178             lineStream << QLatin1Char('\n');
0179     }
0180     decoder.end();
0181 }
0182 
0183 Filter::Filter() :
0184 _linePositions(0),
0185 _buffer(0)
0186 {
0187 }
0188 
0189 Filter::~Filter()
0190 {
0191     QListIterator<HotSpot*> iter(_hotspotList);
0192     while (iter.hasNext())
0193     {
0194         delete iter.next();
0195     }
0196 }
0197 void Filter::reset()
0198 {
0199     QListIterator<HotSpot*> iter(_hotspotList);
0200     while (iter.hasNext())
0201     {
0202         HotSpot* currentHotSpot = iter.next();
0203         if (currentHotSpot->hasAnotherParent()) {
0204             continue;
0205         }
0206         delete currentHotSpot;
0207     }
0208     _hotspots.clear();
0209     _hotspotList.clear();
0210 }
0211 
0212 void Filter::setBuffer(const QString* buffer , const QList<int>* linePositions)
0213 {
0214     _buffer = buffer;
0215     _linePositions = linePositions;
0216 }
0217 
0218 void Filter::getLineColumn(int position , int& startLine , int& startColumn)
0219 {
0220     Q_ASSERT( _linePositions );
0221     Q_ASSERT( _buffer );
0222 
0223 
0224     for (int i = 0 ; i < _linePositions->count() ; i++)
0225     {
0226         int nextLine = 0;
0227 
0228         if ( i == _linePositions->count()-1 )
0229             nextLine = _buffer->length() + 1;
0230         else
0231             nextLine = _linePositions->value(i+1);
0232 
0233         if ( _linePositions->value(i) <= position && position < nextLine )
0234         {
0235             startLine = i;
0236             startColumn = string_width(buffer()->mid(_linePositions->value(i),position - _linePositions->value(i)).toStdWString());
0237             return;
0238         }
0239     }
0240 }
0241 
0242 
0243 /*void Filter::addLine(const QString& text)
0244 {
0245     _linePositions << _buffer.length();
0246     _buffer.append(text);
0247 }*/
0248 
0249 const QString* Filter::buffer()
0250 {
0251     return _buffer;
0252 }
0253 Filter::HotSpot::~HotSpot()
0254 {
0255 }
0256 void Filter::addHotSpot(HotSpot* spot)
0257 {
0258     _hotspotList << spot;
0259 
0260     for (int line = spot->startLine() ; line <= spot->endLine() ; line++)
0261     {
0262         _hotspots.insert(line,spot);
0263     }
0264 }
0265 QList<Filter::HotSpot*> Filter::hotSpots() const
0266 {
0267     return _hotspotList;
0268 }
0269 QList<Filter::HotSpot*> Filter::hotSpotsAtLine(int line) const
0270 {
0271     return _hotspots.values(line);
0272 }
0273 
0274 Filter::HotSpot* Filter::hotSpotAt(int line , int column) const
0275 {
0276     QListIterator<HotSpot*> spotIter(_hotspots.values(line));
0277 
0278     while (spotIter.hasNext())
0279     {
0280         HotSpot* spot = spotIter.next();
0281 
0282         if ( spot->startLine() == line && spot->startColumn() > column )
0283             continue;
0284         if ( spot->endLine() == line && spot->endColumn() < column )
0285             continue;
0286 
0287         return spot;
0288     }
0289 
0290     return 0;
0291 }
0292 
0293 Filter::HotSpot::HotSpot(int startLine , int startColumn , int endLine , int endColumn)
0294     : _hasAnotherParent(false)
0295     , _startLine(startLine)
0296     , _startColumn(startColumn)
0297     , _endLine(endLine)
0298     , _endColumn(endColumn)
0299     , _type(NotSpecified)
0300 {
0301 }
0302 QList<QAction*> Filter::HotSpot::actions(QWidget* parent)
0303 {
0304     Q_UNUSED(parent);
0305 
0306     return QList<QAction*>();
0307 }
0308 int Filter::HotSpot::startLine() const
0309 {
0310     return _startLine;
0311 }
0312 int Filter::HotSpot::endLine() const
0313 {
0314     return _endLine;
0315 }
0316 int Filter::HotSpot::startColumn() const
0317 {
0318     return _startColumn;
0319 }
0320 int Filter::HotSpot::endColumn() const
0321 {
0322     return _endColumn;
0323 }
0324 Filter::HotSpot::Type Filter::HotSpot::type() const
0325 {
0326     return _type;
0327 }
0328 void Filter::HotSpot::setType(Type type)
0329 {
0330     _type = type;
0331 }
0332 
0333 RegExpFilter::RegExpFilter()
0334 {
0335 }
0336 
0337 RegExpFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn)
0338     : Filter::HotSpot(startLine,startColumn,endLine,endColumn)
0339 {
0340     setType(Marker);
0341 }
0342 
0343 void RegExpFilter::HotSpot::activate(const QString&)
0344 {
0345 }
0346 
0347 void RegExpFilter::HotSpot::setCapturedTexts(const QStringList& texts)
0348 {
0349     _capturedTexts = texts;
0350 }
0351 QStringList RegExpFilter::HotSpot::capturedTexts() const
0352 {
0353     return _capturedTexts;
0354 }
0355 
0356 void RegExpFilter::setRegExp(const QRegExp& regExp)
0357 {
0358     _searchText = regExp;
0359 }
0360 QRegExp RegExpFilter::regExp() const
0361 {
0362     return _searchText;
0363 }
0364 /*void RegExpFilter::reset(int)
0365 {
0366     _buffer = QString();
0367 }*/
0368 void RegExpFilter::process()
0369 {
0370     int pos = 0;
0371     const QString* text = buffer();
0372 
0373     Q_ASSERT( text );
0374 
0375     // ignore any regular expressions which match an empty string.
0376     // otherwise the while loop below will run indefinitely
0377     static const QString emptyString;
0378     if ( _searchText.exactMatch(emptyString) )
0379         return;
0380 
0381     while(pos >= 0)
0382     {
0383         pos = _searchText.indexIn(*text,pos);
0384 
0385         if ( pos >= 0 )
0386         {
0387             int startLine = 0;
0388             int endLine = 0;
0389             int startColumn = 0;
0390             int endColumn = 0;
0391 
0392             getLineColumn(pos,startLine,startColumn);
0393             getLineColumn(pos + _searchText.matchedLength(),endLine,endColumn);
0394 
0395             RegExpFilter::HotSpot* spot = newHotSpot(startLine,startColumn,
0396                                            endLine,endColumn);
0397             spot->setCapturedTexts(_searchText.capturedTexts());
0398 
0399             addHotSpot( spot );
0400             pos += _searchText.matchedLength();
0401 
0402             // if matchedLength == 0, the program will get stuck in an infinite loop
0403             if ( _searchText.matchedLength() == 0 )
0404                 pos = -1;
0405         }
0406     }
0407 }
0408 
0409 RegExpFilter::HotSpot* RegExpFilter::newHotSpot(int startLine,int startColumn,
0410                                                 int endLine,int endColumn)
0411 {
0412     return new RegExpFilter::HotSpot(startLine,startColumn,
0413                                                   endLine,endColumn);
0414 }
0415 RegExpFilter::HotSpot* UrlFilter::newHotSpot(int startLine,int startColumn,int endLine,
0416                                                     int endColumn)
0417 {
0418     HotSpot *spot = new UrlFilter::HotSpot(startLine,startColumn,
0419                                                endLine,endColumn);
0420     connect(spot->getUrlObject(), &FilterObject::activated, this, &UrlFilter::activated);
0421     return spot;
0422 }
0423 
0424 UrlFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn)
0425 : RegExpFilter::HotSpot(startLine,startColumn,endLine,endColumn)
0426 , _urlObject(new FilterObject(this))
0427 {
0428     setType(Link);
0429 }
0430 
0431 UrlFilter::HotSpot::UrlType UrlFilter::HotSpot::urlType() const
0432 {
0433     QString url = capturedTexts().constFirst();
0434 
0435     if ( FullUrlRegExp.exactMatch(url) )
0436         return StandardUrl;
0437     else if ( EmailAddressRegExp.exactMatch(url) )
0438         return Email;
0439     else
0440         return Unknown;
0441 }
0442 
0443 void UrlFilter::HotSpot::activate(const QString& actionName)
0444 {
0445     QString url = capturedTexts().constFirst();
0446 
0447     const UrlType kind = urlType();
0448 
0449     if ( actionName == QLatin1String("copy-action") )
0450     {
0451         QApplication::clipboard()->setText(url);
0452         return;
0453     }
0454 
0455     if ( actionName.isEmpty() || actionName == QLatin1String("open-action") || actionName == QLatin1String("click-action") )
0456     {
0457         if ( kind == StandardUrl )
0458         {
0459             // if the URL path does not include the protocol ( eg. "www.kde.org" ) then
0460             // prepend http:// ( eg. "www.kde.org" --> "http://www.kde.org" )
0461             if (!url.contains(QLatin1String("://")))
0462             {
0463                 url.prepend(QLatin1String("http://"));
0464             }
0465         }
0466         else if ( kind == Email )
0467         {
0468             url.prepend(QLatin1String("mailto:"));
0469         }
0470 
0471         _urlObject->emitActivated(QUrl(url, QUrl::StrictMode), actionName != QLatin1String("click-action"));
0472     }
0473 }
0474 
0475 // Note:  Altering these regular expressions can have a major effect on the performance of the filters
0476 // used for finding URLs in the text, especially if they are very general and could match very long
0477 // pieces of text.
0478 // Please be careful when altering them.
0479 
0480 //regexp matches:
0481 // full url:
0482 // protocolname:// or www. followed by anything other than whitespaces, <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, comma and dot
0483 const QRegExp UrlFilter::FullUrlRegExp(QLatin1String("(www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]]"));
0484 // email address:
0485 // [word chars, dots or dashes]@[word chars, dots or dashes].[word chars]
0486 const QRegExp UrlFilter::EmailAddressRegExp(QLatin1String("\\b(\\w|\\.|-)+@(\\w|\\.|-)+\\.\\w+\\b"));
0487 
0488 // matches full url or email address
0489 const QRegExp UrlFilter::CompleteUrlRegExp(QLatin1Char('(')+FullUrlRegExp.pattern()+QLatin1Char('|')+
0490                                             EmailAddressRegExp.pattern()+QLatin1Char(')'));
0491 
0492 UrlFilter::UrlFilter()
0493 {
0494     setRegExp( CompleteUrlRegExp );
0495 }
0496 
0497 UrlFilter::HotSpot::~HotSpot()
0498 {
0499     delete _urlObject;
0500 }
0501 
0502 void FilterObject::emitActivated(const QUrl& url, bool fromContextMenu)
0503 {
0504     Q_EMIT activated(url, fromContextMenu);
0505 }
0506 
0507 void FilterObject::activate()
0508 {
0509     _filter->activate(sender()->objectName());
0510 }
0511 
0512 FilterObject* UrlFilter::HotSpot::getUrlObject() const
0513 {
0514     return _urlObject;
0515 }
0516 
0517 class UrlAction : public QAction {
0518 public:
0519     UrlAction(QWidget* parent, std::shared_ptr<UrlFilter::HotSpot> hotspotPtr)
0520         : QAction(parent)
0521         , _hotspotPtr(hotspotPtr)
0522     {
0523     }
0524 
0525 private:
0526     std::shared_ptr<UrlFilter::HotSpot> _hotspotPtr;
0527 };
0528 
0529 QList<QAction*> UrlFilter::HotSpot::actions(QWidget* parent)
0530 {
0531     this->_hasAnotherParent = true;
0532     QList<QAction*> list;
0533 
0534     const UrlType kind = urlType();
0535 
0536     std::shared_ptr<UrlFilter::HotSpot> hotspotPtr(this);
0537     UrlAction* openAction = new UrlAction(parent, hotspotPtr);
0538     UrlAction* copyAction = new UrlAction(parent, hotspotPtr);
0539 
0540     Q_ASSERT( kind == StandardUrl || kind == Email );
0541 
0542     if ( kind == StandardUrl )
0543     {
0544         openAction->setText(QObject::tr("Open Link"));
0545         copyAction->setText(QObject::tr("Copy Link Address"));
0546     }
0547     else if ( kind == Email )
0548     {
0549         openAction->setText(QObject::tr("Send Email To..."));
0550         copyAction->setText(QObject::tr("Copy Email Address"));
0551     }
0552 
0553     // object names are set here so that the hotspot performs the
0554     // correct action when activated() is called with the triggered
0555     // action passed as a parameter.
0556     openAction->setObjectName( QLatin1String("open-action" ));
0557     copyAction->setObjectName( QLatin1String("copy-action" ));
0558 
0559     QObject::connect( openAction , &QAction::triggered , _urlObject , &FilterObject::activate );
0560     QObject::connect( copyAction , &QAction::triggered , _urlObject , &FilterObject::activate );
0561 
0562     list << openAction;
0563     list << copyAction;
0564 
0565     return list;
0566 }
0567 
0568 //#include "Filter.moc"