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"