/* Copyright 2007-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "Filter.h" // System #include // Qt #include #include #include #include #include #include #include #include #include #include // KDE //#include //#include // Konsole #include "TerminalCharacterDecoder.h" #include "konsole_wcwidth.h" using namespace Konsole; FilterChain::~FilterChain() { QMutableListIterator iter(*this); while ( iter.hasNext() ) { Filter* filter = iter.next(); iter.remove(); delete filter; } } void FilterChain::addFilter(Filter* filter) { append(filter); } void FilterChain::removeFilter(Filter* filter) { removeAll(filter); } bool FilterChain::containsFilter(Filter* filter) { return contains(filter); } void FilterChain::reset() { QListIterator iter(*this); while (iter.hasNext()) iter.next()->reset(); } void FilterChain::setBuffer(const QString* buffer , const QList* linePositions) { QListIterator iter(*this); while (iter.hasNext()) iter.next()->setBuffer(buffer,linePositions); } void FilterChain::process() { QListIterator iter(*this); while (iter.hasNext()) iter.next()->process(); } void FilterChain::clear() { QList::clear(); } Filter::HotSpot* FilterChain::hotSpotAt(int line , int column) const { QListIterator iter(*this); while (iter.hasNext()) { Filter* filter = iter.next(); Filter::HotSpot* spot = filter->hotSpotAt(line,column); if ( spot != 0 ) { return spot; } } return 0; } QList FilterChain::hotSpots() const { QList list; QListIterator iter(*this); while (iter.hasNext()) { Filter* filter = iter.next(); list << filter->hotSpots(); } return list; } //QList FilterChain::hotSpotsAtLine(int line) const; TerminalImageFilterChain::TerminalImageFilterChain() : _buffer(0) , _linePositions(0) { } TerminalImageFilterChain::~TerminalImageFilterChain() { delete _buffer; delete _linePositions; } void TerminalImageFilterChain::setImage(const Character* const image , int lines , int columns, const QVector& lineProperties) { if (empty()) return; // reset all filters and hotspots reset(); PlainTextDecoder decoder; decoder.setTrailingWhitespace(false); // setup new shared buffers for the filters to process on QString* newBuffer = new QString(); QList* newLinePositions = new QList(); setBuffer( newBuffer , newLinePositions ); // free the old buffers delete _buffer; delete _linePositions; _buffer = newBuffer; _linePositions = newLinePositions; QTextStream lineStream(_buffer); decoder.begin(&lineStream); for (int i=0 ; i < lines ; i++) { _linePositions->append(_buffer->length()); decoder.decodeLine(image + i*columns,columns,LINE_DEFAULT); // pretend that each line ends with a newline character. // this prevents a link that occurs at the end of one line // being treated as part of a link that occurs at the start of the next line // // the downside is that links which are spread over more than one line are not // highlighted. // // TODO - Use the "line wrapped" attribute associated with lines in a // terminal image to avoid adding this imaginary character for wrapped // lines if ( !(lineProperties.value(i,LINE_DEFAULT) & LINE_WRAPPED) ) lineStream << QChar('\n'); } decoder.end(); } Filter::Filter() : _linePositions(0), _buffer(0) { } Filter::~Filter() { QListIterator iter(_hotspotList); while (iter.hasNext()) { delete iter.next(); } } void Filter::reset() { qDeleteAll(_hotspotList); _hotspots.clear(); _hotspotList.clear(); } void Filter::setBuffer(const QString* buffer , const QList* linePositions) { _buffer = buffer; _linePositions = linePositions; } void Filter::getLineColumn(int position , int& startLine , int& startColumn) { Q_ASSERT( _linePositions ); Q_ASSERT( _buffer ); for (int i = 0 ; i < _linePositions->count() ; i++) { int nextLine = 0; if ( i == _linePositions->count()-1 ) nextLine = _buffer->length() + 1; else nextLine = _linePositions->value(i+1); if ( _linePositions->value(i) <= position && position < nextLine ) { startLine = i; startColumn = string_width(buffer()->mid(_linePositions->value(i),position - _linePositions->value(i))); return; } } } /*void Filter::addLine(const QString& text) { _linePositions << _buffer.length(); _buffer.append(text); }*/ const QString* Filter::buffer() { return _buffer; } Filter::HotSpot::~HotSpot() { } void Filter::addHotSpot(HotSpot* spot) { _hotspotList << spot; for (int line = spot->startLine() ; line <= spot->endLine() ; line++) { _hotspots.insert(line,spot); } } QList Filter::hotSpots() const { return _hotspotList; } QList Filter::hotSpotsAtLine(int line) const { return _hotspots.values(line); } Filter::HotSpot* Filter::hotSpotAt(int line , int column) const { QListIterator spotIter(_hotspots.values(line)); while (spotIter.hasNext()) { HotSpot* spot = spotIter.next(); if ( spot->startLine() == line && spot->startColumn() > column ) continue; if ( spot->endLine() == line && spot->endColumn() < column ) continue; return spot; } return 0; } Filter::HotSpot::HotSpot(int startLine , int startColumn , int endLine , int endColumn) : _startLine(startLine) , _startColumn(startColumn) , _endLine(endLine) , _endColumn(endColumn) , _type(NotSpecified) { } QList Filter::HotSpot::actions() { return QList(); } int Filter::HotSpot::startLine() const { return _startLine; } int Filter::HotSpot::endLine() const { return _endLine; } int Filter::HotSpot::startColumn() const { return _startColumn; } int Filter::HotSpot::endColumn() const { return _endColumn; } Filter::HotSpot::Type Filter::HotSpot::type() const { return _type; } void Filter::HotSpot::setType(Type type) { _type = type; } RegExpFilter::RegExpFilter() { } RegExpFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn) : Filter::HotSpot(startLine,startColumn,endLine,endColumn) { setType(Marker); } void RegExpFilter::HotSpot::activate(const QString&) { } void RegExpFilter::HotSpot::setCapturedTexts(const QStringList& texts) { _capturedTexts = texts; } QStringList RegExpFilter::HotSpot::capturedTexts() const { return _capturedTexts; } void RegExpFilter::setRegExp(const QRegExp& regExp) { _searchText = regExp; } QRegExp RegExpFilter::regExp() const { return _searchText; } /*void RegExpFilter::reset(int) { _buffer = QString(); }*/ void RegExpFilter::process() { int pos = 0; const QString* text = buffer(); Q_ASSERT( text ); // ignore any regular expressions which match an empty string. // otherwise the while loop below will run indefinitely static const QString emptyString(""); if ( _searchText.exactMatch(emptyString) ) return; while(pos >= 0) { pos = _searchText.indexIn(*text,pos); if ( pos >= 0 ) { int startLine = 0; int endLine = 0; int startColumn = 0; int endColumn = 0; getLineColumn(pos,startLine,startColumn); getLineColumn(pos + _searchText.matchedLength(),endLine,endColumn); RegExpFilter::HotSpot* spot = newHotSpot(startLine,startColumn, endLine,endColumn); spot->setCapturedTexts(_searchText.capturedTexts()); addHotSpot( spot ); pos += _searchText.matchedLength(); // if matchedLength == 0, the program will get stuck in an infinite loop if ( _searchText.matchedLength() == 0 ) pos = -1; } } } RegExpFilter::HotSpot* RegExpFilter::newHotSpot(int startLine,int startColumn, int endLine,int endColumn) { return new RegExpFilter::HotSpot(startLine,startColumn, endLine,endColumn); } RegExpFilter::HotSpot* UrlFilter::newHotSpot(int startLine,int startColumn,int endLine, int endColumn) { HotSpot *spot = new UrlFilter::HotSpot(startLine,startColumn, endLine,endColumn); connect(spot->getUrlObject(), &FilterObject::activated, this, &UrlFilter::activated); return spot; } UrlFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn) : RegExpFilter::HotSpot(startLine,startColumn,endLine,endColumn) , _urlObject(new FilterObject(this)) { setType(Link); } UrlFilter::HotSpot::UrlType UrlFilter::HotSpot::urlType() const { QString url = capturedTexts().constFirst(); if ( FullUrlRegExp.exactMatch(url) ) return StandardUrl; else if ( EmailAddressRegExp.exactMatch(url) ) return Email; else return Unknown; } void UrlFilter::HotSpot::activate(const QString& actionName) { QString url = capturedTexts().constFirst(); const UrlType kind = urlType(); if ( actionName == "copy-action" ) { QApplication::clipboard()->setText(url); return; } if ( actionName.isEmpty() || actionName == "open-action" || actionName == "click-action" ) { if ( kind == StandardUrl ) { // if the URL path does not include the protocol ( eg. "www.kde.org" ) then // prepend http:// ( eg. "www.kde.org" --> "http://www.kde.org" ) if (!url.contains("://")) { url.prepend("http://"); } } else if ( kind == Email ) { url.prepend("mailto:"); } _urlObject->emitActivated(url, actionName != "click-action"); } } // Note: Altering these regular expressions can have a major effect on the performance of the filters // used for finding URLs in the text, especially if they are very general and could match very long // pieces of text. // Please be careful when altering them. //regexp matches: // full url: // protocolname:// or www. followed by anything other than whitespaces, <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, comma and dot const QRegExp UrlFilter::FullUrlRegExp("(www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]]"); // email address: // [word chars, dots or dashes]@[word chars, dots or dashes].[word chars] const QRegExp UrlFilter::EmailAddressRegExp("\\b(\\w|\\.|-)+@(\\w|\\.|-)+\\.\\w+\\b"); // matches full url or email address const QRegExp UrlFilter::CompleteUrlRegExp('('+FullUrlRegExp.pattern()+'|'+ EmailAddressRegExp.pattern()+')'); UrlFilter::UrlFilter() { setRegExp( CompleteUrlRegExp ); } UrlFilter::HotSpot::~HotSpot() { delete _urlObject; } void FilterObject::emitActivated(const QUrl& url, bool fromContextMenu) { emit activated(url, fromContextMenu); } void FilterObject::activate() { _filter->activate(sender()->objectName()); } FilterObject* UrlFilter::HotSpot::getUrlObject() const { return _urlObject; } QList UrlFilter::HotSpot::actions() { QList list; const UrlType kind = urlType(); QAction* openAction = new QAction(_urlObject); QAction* copyAction = new QAction(_urlObject);; Q_ASSERT( kind == StandardUrl || kind == Email ); if ( kind == StandardUrl ) { openAction->setText(QObject::tr("Open Link")); copyAction->setText(QObject::tr("Copy Link Address")); } else if ( kind == Email ) { openAction->setText(QObject::tr("Send Email To...")); copyAction->setText(QObject::tr("Copy Email Address")); } // object names are set here so that the hotspot performs the // correct action when activated() is called with the triggered // action passed as a parameter. openAction->setObjectName( QLatin1String("open-action" )); copyAction->setObjectName( QLatin1String("copy-action" )); QObject::connect( openAction , &QAction::triggered , _urlObject , &FilterObject::activate ); QObject::connect( copyAction , &QAction::triggered , _urlObject , &FilterObject::activate ); list << openAction; list << copyAction; return list; } //#include "Filter.moc"