source: ogBrowser-Git/qtermwidget/lib/Filter.cpp @ c0cec9d

jenkinsmain
Last change on this file since c0cec9d was 64efc22, checked in by Vadim Troshchinskiy <vtroshchinskiy@…>, 19 months ago

Update qtermwidget to modern version

  • Property mode set to 100644
File size: 13.7 KB
Line 
1/*
2    Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17    02110-1301  USA.
18*/
19
20// Own
21#include "Filter.h"
22
23// System
24#include <iostream>
25
26// Qt
27#include <QAction>
28#include <QApplication>
29#include <QtAlgorithms>
30#include <QClipboard>
31#include <QString>
32#include <QTextStream>
33#include <QSharedData>
34#include <QFile>
35#include <QDesktopServices>
36#include <QUrl>
37
38// KDE
39//#include <KLocale>
40//#include <KRun>
41
42// Konsole
43#include "TerminalCharacterDecoder.h"
44#include "konsole_wcwidth.h"
45
46using namespace Konsole;
47
48FilterChain::~FilterChain()
49{
50    QMutableListIterator<Filter*> iter(*this);
51
52    while ( iter.hasNext() )
53    {
54        Filter* filter = iter.next();
55        iter.remove();
56        delete filter;
57    }
58}
59
60void FilterChain::addFilter(Filter* filter)
61{
62    append(filter);
63}
64void FilterChain::removeFilter(Filter* filter)
65{
66    removeAll(filter);
67}
68bool FilterChain::containsFilter(Filter* filter)
69{
70    return contains(filter);
71}
72void FilterChain::reset()
73{
74    QListIterator<Filter*> iter(*this);
75    while (iter.hasNext())
76        iter.next()->reset();
77}
78void FilterChain::setBuffer(const QString* buffer , const QList<int>* linePositions)
79{
80    QListIterator<Filter*> iter(*this);
81    while (iter.hasNext())
82        iter.next()->setBuffer(buffer,linePositions);
83}
84void FilterChain::process()
85{
86    QListIterator<Filter*> iter(*this);
87    while (iter.hasNext())
88        iter.next()->process();
89}
90void FilterChain::clear()
91{
92    QList<Filter*>::clear();
93}
94Filter::HotSpot* FilterChain::hotSpotAt(int line , int column) const
95{
96    QListIterator<Filter*> iter(*this);
97    while (iter.hasNext())
98    {
99        Filter* filter = iter.next();
100        Filter::HotSpot* spot = filter->hotSpotAt(line,column);
101        if ( spot != nullptr )
102        {
103            return spot;
104        }
105    }
106
107    return nullptr;
108}
109
110QList<Filter::HotSpot*> FilterChain::hotSpots() const
111{
112    QList<Filter::HotSpot*> list;
113    QListIterator<Filter*> iter(*this);
114    while (iter.hasNext())
115    {
116        Filter* filter = iter.next();
117        list << filter->hotSpots();
118    }
119    return list;
120}
121//QList<Filter::HotSpot*> FilterChain::hotSpotsAtLine(int line) const;
122
123TerminalImageFilterChain::TerminalImageFilterChain()
124: _buffer(nullptr)
125, _linePositions(nullptr)
126{
127}
128
129TerminalImageFilterChain::~TerminalImageFilterChain()
130{
131    delete _buffer;
132    delete _linePositions;
133}
134
135void TerminalImageFilterChain::setImage(const Character* const image , int lines , int columns, const QVector<LineProperty>& lineProperties)
136{
137    if (empty())
138        return;
139
140    // reset all filters and hotspots
141    reset();
142
143    PlainTextDecoder decoder;
144    decoder.setTrailingWhitespace(false);
145
146    // setup new shared buffers for the filters to process on
147    QString* newBuffer = new QString();
148    QList<int>* newLinePositions = new QList<int>();
149    setBuffer( newBuffer , newLinePositions );
150
151    // free the old buffers
152    delete _buffer;
153    delete _linePositions;
154
155    _buffer = newBuffer;
156    _linePositions = newLinePositions;
157
158    QTextStream lineStream(_buffer);
159    decoder.begin(&lineStream);
160
161    for (int i=0 ; i < lines ; i++)
162    {
163        _linePositions->append(_buffer->length());
164        decoder.decodeLine(image + i*columns,columns,LINE_DEFAULT);
165
166        // pretend that each line ends with a newline character.
167        // this prevents a link that occurs at the end of one line
168        // being treated as part of a link that occurs at the start of the next line
169        //
170        // the downside is that links which are spread over more than one line are not
171        // highlighted.
172        //
173        // TODO - Use the "line wrapped" attribute associated with lines in a
174        // terminal image to avoid adding this imaginary character for wrapped
175        // lines
176        if ( !(lineProperties.value(i,LINE_DEFAULT) & LINE_WRAPPED) )
177            lineStream << QLatin1Char('\n');
178    }
179    decoder.end();
180}
181
182Filter::Filter() :
183_linePositions(nullptr),
184_buffer(nullptr)
185{
186}
187
188Filter::~Filter()
189{
190    qDeleteAll(_hotspotList);
191    _hotspotList.clear();
192}
193void Filter::reset()
194{
195    qDeleteAll(_hotspotList);
196    _hotspots.clear();
197    _hotspotList.clear();
198}
199
200void Filter::setBuffer(const QString* buffer , const QList<int>* linePositions)
201{
202    _buffer = buffer;
203    _linePositions = linePositions;
204}
205
206void Filter::getLineColumn(int position , int& startLine , int& startColumn)
207{
208    Q_ASSERT( _linePositions );
209    Q_ASSERT( _buffer );
210
211
212    for (int i = 0 ; i < _linePositions->count() ; i++)
213    {
214        int nextLine = 0;
215
216        if ( i == _linePositions->count()-1 )
217            nextLine = _buffer->length() + 1;
218        else
219            nextLine = _linePositions->value(i+1);
220
221        if ( _linePositions->value(i) <= position && position < nextLine )
222        {
223            startLine = i;
224            startColumn = string_width(buffer()->mid(_linePositions->value(i),position - _linePositions->value(i)).toStdWString());
225            return;
226        }
227    }
228}
229
230
231/*void Filter::addLine(const QString& text)
232{
233    _linePositions << _buffer.length();
234    _buffer.append(text);
235}*/
236
237const QString* Filter::buffer()
238{
239    return _buffer;
240}
241Filter::HotSpot::~HotSpot()
242{
243}
244void Filter::addHotSpot(HotSpot* spot)
245{
246    _hotspotList << spot;
247
248    for (int line = spot->startLine() ; line <= spot->endLine() ; line++)
249    {
250        _hotspots.insert(line,spot);
251    }
252}
253QList<Filter::HotSpot*> Filter::hotSpots() const
254{
255    return _hotspotList;
256}
257QList<Filter::HotSpot*> Filter::hotSpotsAtLine(int line) const
258{
259    return _hotspots.values(line);
260}
261
262Filter::HotSpot* Filter::hotSpotAt(int line , int column) const
263{
264    QListIterator<HotSpot*> spotIter(_hotspots.values(line));
265
266    while (spotIter.hasNext())
267    {
268        HotSpot* spot = spotIter.next();
269
270        if ( spot->startLine() == line && spot->startColumn() > column )
271            continue;
272        if ( spot->endLine() == line && spot->endColumn() < column )
273            continue;
274
275        return spot;
276    }
277
278    return nullptr;
279}
280
281Filter::HotSpot::HotSpot(int startLine , int startColumn , int endLine , int endColumn)
282    : _startLine(startLine)
283    , _startColumn(startColumn)
284    , _endLine(endLine)
285    , _endColumn(endColumn)
286    , _type(NotSpecified)
287{
288}
289QList<QAction*> Filter::HotSpot::actions()
290{
291    return QList<QAction*>();
292}
293int Filter::HotSpot::startLine() const
294{
295    return _startLine;
296}
297int Filter::HotSpot::endLine() const
298{
299    return _endLine;
300}
301int Filter::HotSpot::startColumn() const
302{
303    return _startColumn;
304}
305int Filter::HotSpot::endColumn() const
306{
307    return _endColumn;
308}
309Filter::HotSpot::Type Filter::HotSpot::type() const
310{
311    return _type;
312}
313void Filter::HotSpot::setType(Type type)
314{
315    _type = type;
316}
317
318RegExpFilter::RegExpFilter()
319{
320}
321
322RegExpFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn)
323    : Filter::HotSpot(startLine,startColumn,endLine,endColumn)
324{
325    setType(Marker);
326}
327
328void RegExpFilter::HotSpot::activate(const QString&)
329{
330}
331
332void RegExpFilter::HotSpot::setCapturedTexts(const QStringList& texts)
333{
334    _capturedTexts = texts;
335}
336QStringList RegExpFilter::HotSpot::capturedTexts() const
337{
338    return _capturedTexts;
339}
340
341void RegExpFilter::setRegExp(const QRegExp& regExp)
342{
343    _searchText = regExp;
344}
345QRegExp RegExpFilter::regExp() const
346{
347    return _searchText;
348}
349/*void RegExpFilter::reset(int)
350{
351    _buffer = QString();
352}*/
353void RegExpFilter::process()
354{
355    int pos = 0;
356    const QString* text = buffer();
357
358    Q_ASSERT( text );
359
360    // ignore any regular expressions which match an empty string.
361    // otherwise the while loop below will run indefinitely
362    static const QString emptyString;
363    if ( _searchText.exactMatch(emptyString) )
364        return;
365
366    while(pos >= 0)
367    {
368        pos = _searchText.indexIn(*text,pos);
369
370        if ( pos >= 0 )
371        {
372            int startLine = 0;
373            int endLine = 0;
374            int startColumn = 0;
375            int endColumn = 0;
376
377            getLineColumn(pos,startLine,startColumn);
378            getLineColumn(pos + _searchText.matchedLength(),endLine,endColumn);
379
380            RegExpFilter::HotSpot* spot = newHotSpot(startLine,startColumn,
381                                           endLine,endColumn);
382            spot->setCapturedTexts(_searchText.capturedTexts());
383
384            addHotSpot( spot );
385            pos += _searchText.matchedLength();
386
387            // if matchedLength == 0, the program will get stuck in an infinite loop
388            if ( _searchText.matchedLength() == 0 )
389                pos = -1;
390        }
391    }
392}
393
394RegExpFilter::HotSpot* RegExpFilter::newHotSpot(int startLine,int startColumn,
395                                                int endLine,int endColumn)
396{
397    return new RegExpFilter::HotSpot(startLine,startColumn,
398                                                  endLine,endColumn);
399}
400RegExpFilter::HotSpot* UrlFilter::newHotSpot(int startLine,int startColumn,int endLine,
401                                                    int endColumn)
402{
403    HotSpot *spot = new UrlFilter::HotSpot(startLine,startColumn,
404                                               endLine,endColumn);
405    connect(spot->getUrlObject(), &FilterObject::activated, this, &UrlFilter::activated);
406    return spot;
407}
408
409UrlFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn)
410: RegExpFilter::HotSpot(startLine,startColumn,endLine,endColumn)
411, _urlObject(new FilterObject(this))
412{
413    setType(Link);
414}
415
416UrlFilter::HotSpot::UrlType UrlFilter::HotSpot::urlType() const
417{
418    QString url = capturedTexts().constFirst();
419
420    if ( FullUrlRegExp.exactMatch(url) )
421        return StandardUrl;
422    else if ( EmailAddressRegExp.exactMatch(url) )
423        return Email;
424    else
425        return Unknown;
426}
427
428void UrlFilter::HotSpot::activate(const QString& actionName)
429{
430    QString url = capturedTexts().constFirst();
431
432    const UrlType kind = urlType();
433
434    if ( actionName == QLatin1String("copy-action") )
435    {
436        QApplication::clipboard()->setText(url);
437        return;
438    }
439
440    if ( actionName.isEmpty() || actionName == QLatin1String("open-action") || actionName == QLatin1String("click-action") )
441    {
442        if ( kind == StandardUrl )
443        {
444            // if the URL path does not include the protocol ( eg. "www.kde.org" ) then
445            // prepend http:// ( eg. "www.kde.org" --> "http://www.kde.org" )
446            if (!url.contains(QLatin1String("://")))
447            {
448                url.prepend(QLatin1String("http://"));
449            }
450        }
451        else if ( kind == Email )
452        {
453            url.prepend(QLatin1String("mailto:"));
454        }
455
456        _urlObject->emitActivated(QUrl(url, QUrl::StrictMode), actionName != QLatin1String("click-action"));
457    }
458}
459
460// Note:  Altering these regular expressions can have a major effect on the performance of the filters
461// used for finding URLs in the text, especially if they are very general and could match very long
462// pieces of text.
463// Please be careful when altering them.
464
465//regexp matches:
466// full url:
467// protocolname:// or www. followed by anything other than whitespaces, <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, comma and dot
468const QRegExp UrlFilter::FullUrlRegExp(QLatin1String("(www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]]"));
469// email address:
470// [word chars, dots or dashes]@[word chars, dots or dashes].[word chars]
471const QRegExp UrlFilter::EmailAddressRegExp(QLatin1String("\\b(\\w|\\.|-)+@(\\w|\\.|-)+\\.\\w+\\b"));
472
473// matches full url or email address
474const QRegExp UrlFilter::CompleteUrlRegExp(QLatin1Char('(')+FullUrlRegExp.pattern()+QLatin1Char('|')+
475                                            EmailAddressRegExp.pattern()+QLatin1Char(')'));
476
477UrlFilter::UrlFilter()
478{
479    setRegExp( CompleteUrlRegExp );
480}
481
482UrlFilter::HotSpot::~HotSpot()
483{
484    delete _urlObject;
485}
486
487void FilterObject::emitActivated(const QUrl& url, bool fromContextMenu)
488{
489    emit activated(url, fromContextMenu);
490}
491
492void FilterObject::activate()
493{
494    _filter->activate(sender()->objectName());
495}
496
497FilterObject* UrlFilter::HotSpot::getUrlObject() const
498{
499    return _urlObject;
500}
501
502QList<QAction*> UrlFilter::HotSpot::actions()
503{
504    QList<QAction*> list;
505
506    const UrlType kind = urlType();
507
508    QAction* openAction = new QAction(_urlObject);
509    QAction* copyAction = new QAction(_urlObject);;
510
511    Q_ASSERT( kind == StandardUrl || kind == Email );
512
513    if ( kind == StandardUrl )
514    {
515        openAction->setText(QObject::tr("Open Link"));
516        copyAction->setText(QObject::tr("Copy Link Address"));
517    }
518    else if ( kind == Email )
519    {
520        openAction->setText(QObject::tr("Send Email To..."));
521        copyAction->setText(QObject::tr("Copy Email Address"));
522    }
523
524    // object names are set here so that the hotspot performs the
525    // correct action when activated() is called with the triggered
526    // action passed as a parameter.
527    openAction->setObjectName( QLatin1String("open-action" ));
528    copyAction->setObjectName( QLatin1String("copy-action" ));
529
530    QObject::connect( openAction , &QAction::triggered , _urlObject , &FilterObject::activate );
531    QObject::connect( copyAction , &QAction::triggered , _urlObject , &FilterObject::activate );
532
533    list << openAction;
534    list << copyAction;
535
536    return list;
537}
538
539//#include "Filter.moc"
Note: See TracBrowser for help on using the repository browser.