1 // Copyright (C) 2007 by Robert Knight <robertknight@gmail.com>
3 // Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008
4 // Forked for Baulk - Copyright (C) 2008-2009 - Jacob Alexander <haata at users.sf.net>
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // any later version, including version 3 of the License.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #include <QtGui/QAction>
27 #include <QtGui/QApplication>
28 #include <QtGui/QClipboard>
29 #include <QtCore/QString>
31 #include <QtCore/QSharedData>
39 #include "TerminalCharacterDecoder.h"
41 using namespace Konsole
;
43 FilterChain::~FilterChain()
45 QMutableListIterator
<Filter
*> iter(*this);
47 while ( iter
.hasNext() )
49 Filter
* filter
= iter
.next();
55 void FilterChain::addFilter(Filter
* filter
)
59 void FilterChain::removeFilter(Filter
* filter
)
63 bool FilterChain::containsFilter(Filter
* filter
)
65 return contains(filter
);
67 void FilterChain::reset()
69 QListIterator
<Filter
*> iter(*this);
70 while (iter
.hasNext())
73 void FilterChain::setBuffer(const QString
* buffer
, const QList
<int>* linePositions
)
75 QListIterator
<Filter
*> iter(*this);
76 while (iter
.hasNext())
77 iter
.next()->setBuffer(buffer
,linePositions
);
79 void FilterChain::process()
81 QListIterator
<Filter
*> iter(*this);
82 while (iter
.hasNext())
83 iter
.next()->process();
85 void FilterChain::clear()
87 QList
<Filter
*>::clear();
89 Filter::HotSpot
* FilterChain::hotSpotAt(int line
, int column
) const
91 QListIterator
<Filter
*> iter(*this);
92 while (iter
.hasNext())
94 Filter
* filter
= iter
.next();
95 Filter::HotSpot
* spot
= filter
->hotSpotAt(line
,column
);
105 QList
<Filter::HotSpot
*> FilterChain::hotSpots() const
107 QList
<Filter::HotSpot
*> list
;
108 QListIterator
<Filter
*> iter(*this);
109 while (iter
.hasNext())
111 Filter
* filter
= iter
.next();
112 list
<< filter
->hotSpots();
116 //QList<Filter::HotSpot*> FilterChain::hotSpotsAtLine(int line) const;
118 TerminalImageFilterChain::TerminalImageFilterChain()
124 TerminalImageFilterChain::~TerminalImageFilterChain()
127 delete _linePositions
;
130 void TerminalImageFilterChain::setImage(const Character
* const image
, int lines
, int columns
, const QVector
<LineProperty
>& lineProperties
)
132 //qDebug("%s %d", __FILE__, __LINE__);
135 //qDebug("%s %d", __FILE__, __LINE__);
137 // reset all filters and hotspots
139 //qDebug("%s %d", __FILE__, __LINE__);
141 PlainTextDecoder decoder
;
142 decoder
.setTrailingWhitespace(false);
144 //qDebug("%s %d", __FILE__, __LINE__);
145 // setup new shared buffers for the filters to process on
146 QString
* newBuffer
= new QString();
147 QList
<int>* newLinePositions
= new QList
<int>();
148 setBuffer( newBuffer
, newLinePositions
);
150 // free the old buffers
152 delete _linePositions
;
155 _linePositions
= newLinePositions
;
157 QTextStream
lineStream(_buffer
);
158 decoder
.begin(&lineStream
);
160 for (int i
=0 ; i
< lines
; i
++)
162 _linePositions
->append(_buffer
->length());
163 decoder
.decodeLine(image
+ i
*columns
,columns
,LINE_DEFAULT
);
165 // pretend that each line ends with a newline character.
166 // this prevents a link that occurs at the end of one line
167 // being treated as part of a link that occurs at the start of the next line
169 // the downside is that links which are spread over more than one line are not
172 // TODO - Use the "line wrapped" attribute associated with lines in a
173 // terminal image to avoid adding this imaginary character for wrapped
175 if ( !(lineProperties
.value(i
,LINE_DEFAULT
) & LINE_WRAPPED
) )
176 lineStream
<< QChar('\n');
179 // qDebug("%s %d", __FILE__, __LINE__);
190 QListIterator
<HotSpot
*> iter(_hotspotList
);
191 while (iter
.hasNext())
199 _hotspotList
.clear();
202 void Filter::setBuffer(const QString
* buffer
, const QList
<int>* linePositions
)
205 _linePositions
= linePositions
;
208 void Filter::getLineColumn(int position
, int& startLine
, int& startColumn
)
210 Q_ASSERT( _linePositions
);
214 for (int i
= 0 ; i
< _linePositions
->count() ; i
++)
216 //kDebug() << "line position at " << i << " = " << _linePositions[i];
219 if ( i
== _linePositions
->count()-1 )
221 nextLine
= _buffer
->length() + 1;
225 nextLine
= _linePositions
->value(i
+1);
228 // kDebug() << "pos - " << position << " line pos(" << i<< ") " << _linePositions->value(i) <<
229 // " next = " << nextLine << " buffer len = " << _buffer->length();
231 if ( _linePositions
->value(i
) <= position
&& position
< nextLine
)
234 startColumn
= position
- _linePositions
->value(i
);
241 /*void Filter::addLine(const QString& text)
243 _linePositions << _buffer.length();
244 _buffer.append(text);
247 const QString
* Filter::buffer()
251 Filter::HotSpot::~HotSpot()
254 void Filter::addHotSpot(HotSpot
* spot
)
256 _hotspotList
<< spot
;
258 for (int line
= spot
->startLine() ; line
<= spot
->endLine() ; line
++)
260 _hotspots
.insert(line
,spot
);
263 QList
<Filter::HotSpot
*> Filter::hotSpots() const
267 QList
<Filter::HotSpot
*> Filter::hotSpotsAtLine(int line
) const
269 return _hotspots
.values(line
);
272 Filter::HotSpot
* Filter::hotSpotAt(int line
, int column
) const
274 QListIterator
<HotSpot
*> spotIter(_hotspots
.values(line
));
276 while (spotIter
.hasNext())
278 HotSpot
* spot
= spotIter
.next();
280 if ( spot
->startLine() == line
&& spot
->startColumn() > column
)
282 if ( spot
->endLine() == line
&& spot
->endColumn() < column
)
291 Filter::HotSpot::HotSpot(int startLine
, int startColumn
, int endLine
, int endColumn
)
292 : _startLine(startLine
)
293 , _startColumn(startColumn
)
295 , _endColumn(endColumn
)
296 , _type(NotSpecified
)
299 QString
Filter::HotSpot::tooltip() const
303 QList
<QAction
*> Filter::HotSpot::actions()
305 return QList
<QAction
*>();
307 int Filter::HotSpot::startLine() const
311 int Filter::HotSpot::endLine() const
315 int Filter::HotSpot::startColumn() const
319 int Filter::HotSpot::endColumn() const
323 Filter::HotSpot::Type
Filter::HotSpot::type() const
327 void Filter::HotSpot::setType(Type type
)
332 RegExpFilter::RegExpFilter()
336 RegExpFilter::HotSpot::HotSpot(int startLine
,int startColumn
,int endLine
,int endColumn
)
337 : Filter::HotSpot(startLine
,startColumn
,endLine
,endColumn
)
342 void RegExpFilter::HotSpot::activate(QObject
*)
346 void RegExpFilter::HotSpot::setCapturedTexts(const QStringList
& texts
)
348 _capturedTexts
= texts
;
350 QStringList
RegExpFilter::HotSpot::capturedTexts() const
352 return _capturedTexts
;
355 void RegExpFilter::setRegExp(const QRegExp
& regExp
)
357 _searchText
= regExp
;
359 QRegExp
RegExpFilter::regExp() const
363 /*void RegExpFilter::reset(int)
367 void RegExpFilter::process()
370 const QString
* text
= buffer();
374 // ignore any regular expressions which match an empty string.
375 // otherwise the while loop below will run indefinitely
376 static const QString
emptyString("");
377 if ( _searchText
.exactMatch(emptyString
) )
382 pos
= _searchText
.indexIn(*text
,pos
);
393 //kDebug() << "pos from " << pos << " to " << pos + _searchText.matchedLength();
395 getLineColumn(pos
,startLine
,startColumn
);
396 getLineColumn(pos
+ _searchText
.matchedLength(),endLine
,endColumn
);
398 //kDebug() << "start " << startLine << " / " << startColumn;
399 //kDebug() << "end " << endLine << " / " << endColumn;
401 RegExpFilter::HotSpot
* spot
= newHotSpot(startLine
,startColumn
,
403 spot
->setCapturedTexts(_searchText
.capturedTexts());
406 pos
+= _searchText
.matchedLength();
408 // if matchedLength == 0, the program will get stuck in an infinite loop
409 Q_ASSERT( _searchText
.matchedLength() > 0 );
414 RegExpFilter::HotSpot
* RegExpFilter::newHotSpot(int startLine
,int startColumn
,
415 int endLine
,int endColumn
)
417 return new RegExpFilter::HotSpot(startLine
,startColumn
,
420 RegExpFilter::HotSpot
* UrlFilter::newHotSpot(int startLine
,int startColumn
,int endLine
,
423 return new UrlFilter::HotSpot(startLine
,startColumn
,
426 UrlFilter::HotSpot::HotSpot(int startLine
,int startColumn
,int endLine
,int endColumn
)
427 : RegExpFilter::HotSpot(startLine
,startColumn
,endLine
,endColumn
)
428 , _urlObject(new FilterObject(this))
432 QString
UrlFilter::HotSpot::tooltip() const
434 QString url
= capturedTexts().first();
436 const UrlType kind
= urlType();
438 if ( kind
== StandardUrl
)
440 else if ( kind
== Email
)
445 UrlFilter::HotSpot::UrlType
UrlFilter::HotSpot::urlType() const
447 QString url
= capturedTexts().first();
449 if ( FullUrlRegExp
.exactMatch(url
) )
451 else if ( EmailAddressRegExp
.exactMatch(url
) )
457 void UrlFilter::HotSpot::activate(QObject
* object
)
459 QString url
= capturedTexts().first();
461 const UrlType kind
= urlType();
463 const QString
& actionName
= object
? object
->objectName() : QString();
465 if ( actionName
== "copy-action" )
467 //kDebug() << "Copying url to clipboard:" << url;
469 QApplication::clipboard()->setText(url
);
473 if ( !object
|| actionName
== "open-action" )
475 if ( kind
== StandardUrl
)
477 // if the URL path does not include the protocol ( eg. "www.kde.org" ) then
478 // prepend http:// ( eg. "www.kde.org" --> "http://www.kde.org" )
479 if (!url
.contains("://"))
481 url
.prepend("http://");
484 else if ( kind
== Email
)
486 url
.prepend("mailto:");
489 // new KRun(url,QApplication::activeWindow());
493 // Note: Altering these regular expressions can have a major effect on the performance of the filters
494 // used for finding URLs in the text, especially if they are very general and could match very long
496 // Please be careful when altering them.
500 // protocolname:// or www. followed by anything other than whitespaces, <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, comma and dot
501 const QRegExp
UrlFilter::FullUrlRegExp("(www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]]");
503 // [word chars, dots or dashes]@[word chars, dots or dashes].[word chars]
504 const QRegExp
UrlFilter::EmailAddressRegExp("\\b(\\w|\\.|-)+@(\\w|\\.|-)+\\.\\w+\\b");
506 // matches full url or email address
507 const QRegExp
UrlFilter::CompleteUrlRegExp('('+FullUrlRegExp
.pattern()+'|'+
508 EmailAddressRegExp
.pattern()+')');
510 UrlFilter::UrlFilter()
512 setRegExp( CompleteUrlRegExp
);
514 UrlFilter::HotSpot::~HotSpot()
518 void FilterObject::activated()
520 _filter
->activate(sender());
522 QList
<QAction
*> UrlFilter::HotSpot::actions()
524 QList
<QAction
*> list
;
526 const UrlType kind
= urlType();
528 QAction
* openAction
= new QAction(_urlObject
);
529 QAction
* copyAction
= new QAction(_urlObject
);;
531 Q_ASSERT( kind
== StandardUrl
|| kind
== Email
);
533 if ( kind
== StandardUrl
)
535 openAction
->setText(("Open Link"));
536 copyAction
->setText(("Copy Link Address"));
538 else if ( kind
== Email
)
540 openAction
->setText(("Send Email To..."));
541 copyAction
->setText(("Copy Email Address"));
544 // object names are set here so that the hotspot performs the
545 // correct action when activated() is called with the triggered
546 // action passed as a parameter.
547 openAction
->setObjectName("open-action");
548 copyAction
->setObjectName("copy-action");
550 QObject::connect( openAction
, SIGNAL(triggered()) , _urlObject
, SLOT(activated()) );
551 QObject::connect( copyAction
, SIGNAL(triggered()) , _urlObject
, SLOT(activated()) );
559 //#include "moc_Filter.cpp"