2 Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
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.
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.
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
27 #include <QtGui/QAction>
28 #include <QtGui/QApplication>
29 #include <QtGui/QClipboard>
30 #include <QtCore/QString>
31 #include <QtCore/QTextStream>
32 #include <QtCore/QSharedData>
33 #include <QtCore/QFile>
40 #include "TerminalCharacterDecoder.h"
41 #include "konsole_wcwidth.h"
43 using namespace Konsole
;
45 FilterChain::~FilterChain()
47 QMutableListIterator
<Filter
*> iter(*this);
49 while ( iter
.hasNext() )
51 Filter
* filter
= iter
.next();
57 void FilterChain::addFilter(Filter
* filter
)
61 void FilterChain::removeFilter(Filter
* filter
)
65 bool FilterChain::containsFilter(Filter
* filter
)
67 return contains(filter
);
69 void FilterChain::reset()
71 QListIterator
<Filter
*> iter(*this);
72 while (iter
.hasNext())
75 void FilterChain::setBuffer(const QString
* buffer
, const QList
<int>* linePositions
)
77 QListIterator
<Filter
*> iter(*this);
78 while (iter
.hasNext())
79 iter
.next()->setBuffer(buffer
,linePositions
);
81 void FilterChain::process()
83 QListIterator
<Filter
*> iter(*this);
84 while (iter
.hasNext())
85 iter
.next()->process();
87 void FilterChain::clear()
89 QList
<Filter
*>::clear();
91 Filter::HotSpot
* FilterChain::hotSpotAt(int line
, int column
) const
93 QListIterator
<Filter
*> iter(*this);
94 while (iter
.hasNext())
96 Filter
* filter
= iter
.next();
97 Filter::HotSpot
* spot
= filter
->hotSpotAt(line
,column
);
107 QList
<Filter::HotSpot
*> FilterChain::hotSpots() const
109 QList
<Filter::HotSpot
*> list
;
110 QListIterator
<Filter
*> iter(*this);
111 while (iter
.hasNext())
113 Filter
* filter
= iter
.next();
114 list
<< filter
->hotSpots();
118 //QList<Filter::HotSpot*> FilterChain::hotSpotsAtLine(int line) const;
120 TerminalImageFilterChain::TerminalImageFilterChain()
126 TerminalImageFilterChain::~TerminalImageFilterChain()
129 delete _linePositions
;
132 void TerminalImageFilterChain::setImage(const Character
* const image
, int lines
, int columns
, const QVector
<LineProperty
>& lineProperties
)
137 // reset all filters and hotspots
140 PlainTextDecoder decoder
;
141 decoder
.setTrailingWhitespace(false);
143 // setup new shared buffers for the filters to process on
144 QString
* newBuffer
= new QString();
145 QList
<int>* newLinePositions
= new QList
<int>();
146 setBuffer( newBuffer
, newLinePositions
);
148 // free the old buffers
150 delete _linePositions
;
153 _linePositions
= newLinePositions
;
155 QTextStream
lineStream(_buffer
);
156 decoder
.begin(&lineStream
);
158 for (int i
=0 ; i
< lines
; i
++)
160 _linePositions
->append(_buffer
->length());
161 decoder
.decodeLine(image
+ i
*columns
,columns
,LINE_DEFAULT
);
163 // pretend that each line ends with a newline character.
164 // this prevents a link that occurs at the end of one line
165 // being treated as part of a link that occurs at the start of the next line
167 // the downside is that links which are spread over more than one line are not
170 // TODO - Use the "line wrapped" attribute associated with lines in a
171 // terminal image to avoid adding this imaginary character for wrapped
173 if ( !(lineProperties
.value(i
,LINE_DEFAULT
) & LINE_WRAPPED
) )
174 lineStream
<< QChar('\n');
187 QListIterator
<HotSpot
*> iter(_hotspotList
);
188 while (iter
.hasNext())
196 _hotspotList
.clear();
199 void Filter::setBuffer(const QString
* buffer
, const QList
<int>* linePositions
)
202 _linePositions
= linePositions
;
205 void Filter::getLineColumn(int position
, int& startLine
, int& startColumn
)
207 Q_ASSERT( _linePositions
);
211 for (int i
= 0 ; i
< _linePositions
->count() ; i
++)
215 if ( i
== _linePositions
->count()-1 )
216 nextLine
= _buffer
->length() + 1;
218 nextLine
= _linePositions
->value(i
+1);
220 if ( _linePositions
->value(i
) <= position
&& position
< nextLine
)
223 startColumn
= string_width(buffer()->mid(_linePositions
->value(i
),position
- _linePositions
->value(i
)));
230 /*void Filter::addLine(const QString& text)
232 _linePositions << _buffer.length();
233 _buffer.append(text);
236 const QString
* Filter::buffer()
240 Filter::HotSpot::~HotSpot()
243 void Filter::addHotSpot(HotSpot
* spot
)
245 _hotspotList
<< spot
;
247 for (int line
= spot
->startLine() ; line
<= spot
->endLine() ; line
++)
249 _hotspots
.insert(line
,spot
);
252 QList
<Filter::HotSpot
*> Filter::hotSpots() const
256 QList
<Filter::HotSpot
*> Filter::hotSpotsAtLine(int line
) const
258 return _hotspots
.values(line
);
261 Filter::HotSpot
* Filter::hotSpotAt(int line
, int column
) const
263 QListIterator
<HotSpot
*> spotIter(_hotspots
.values(line
));
265 while (spotIter
.hasNext())
267 HotSpot
* spot
= spotIter
.next();
269 if ( spot
->startLine() == line
&& spot
->startColumn() > column
)
271 if ( spot
->endLine() == line
&& spot
->endColumn() < column
)
280 Filter::HotSpot::HotSpot(int startLine
, int startColumn
, int endLine
, int endColumn
)
281 : _startLine(startLine
)
282 , _startColumn(startColumn
)
284 , _endColumn(endColumn
)
285 , _type(NotSpecified
)
288 QString
Filter::HotSpot::tooltip() const
292 QList
<QAction
*> Filter::HotSpot::actions()
294 return QList
<QAction
*>();
296 int Filter::HotSpot::startLine() const
300 int Filter::HotSpot::endLine() const
304 int Filter::HotSpot::startColumn() const
308 int Filter::HotSpot::endColumn() const
312 Filter::HotSpot::Type
Filter::HotSpot::type() const
316 void Filter::HotSpot::setType(Type type
)
321 RegExpFilter::RegExpFilter()
325 RegExpFilter::HotSpot::HotSpot(int startLine
,int startColumn
,int endLine
,int endColumn
)
326 : Filter::HotSpot(startLine
,startColumn
,endLine
,endColumn
)
331 void RegExpFilter::HotSpot::activate(QObject
*)
335 void RegExpFilter::HotSpot::setCapturedTexts(const QStringList
& texts
)
337 _capturedTexts
= texts
;
339 QStringList
RegExpFilter::HotSpot::capturedTexts() const
341 return _capturedTexts
;
344 void RegExpFilter::setRegExp(const QRegExp
& regExp
)
346 _searchText
= regExp
;
348 QRegExp
RegExpFilter::regExp() const
352 /*void RegExpFilter::reset(int)
356 void RegExpFilter::process()
359 const QString
* text
= buffer();
363 // ignore any regular expressions which match an empty string.
364 // otherwise the while loop below will run indefinitely
365 static const QString
emptyString("");
366 if ( _searchText
.exactMatch(emptyString
) )
371 pos
= _searchText
.indexIn(*text
,pos
);
380 getLineColumn(pos
,startLine
,startColumn
);
381 getLineColumn(pos
+ _searchText
.matchedLength(),endLine
,endColumn
);
383 RegExpFilter::HotSpot
* spot
= newHotSpot(startLine
,startColumn
,
385 spot
->setCapturedTexts(_searchText
.capturedTexts());
388 pos
+= _searchText
.matchedLength();
390 // if matchedLength == 0, the program will get stuck in an infinite loop
391 if ( _searchText
.matchedLength() == 0 )
397 RegExpFilter::HotSpot
* RegExpFilter::newHotSpot(int startLine
,int startColumn
,
398 int endLine
,int endColumn
)
400 return new RegExpFilter::HotSpot(startLine
,startColumn
,
403 RegExpFilter::HotSpot
* UrlFilter::newHotSpot(int startLine
,int startColumn
,int endLine
,
406 return new UrlFilter::HotSpot(startLine
,startColumn
,
409 UrlFilter::HotSpot::HotSpot(int startLine
,int startColumn
,int endLine
,int endColumn
)
410 : RegExpFilter::HotSpot(startLine
,startColumn
,endLine
,endColumn
)
411 , _urlObject(new FilterObject(this))
415 QString
UrlFilter::HotSpot::tooltip() const
417 QString url
= capturedTexts().first();
419 const UrlType kind
= urlType();
421 if ( kind
== StandardUrl
)
423 else if ( kind
== Email
)
428 UrlFilter::HotSpot::UrlType
UrlFilter::HotSpot::urlType() const
430 QString url
= capturedTexts().first();
432 if ( FullUrlRegExp
.exactMatch(url
) )
434 else if ( EmailAddressRegExp
.exactMatch(url
) )
440 void UrlFilter::HotSpot::activate(QObject
* object
)
442 QString url
= capturedTexts().first();
444 const UrlType kind
= urlType();
446 const QString
& actionName
= object
? object
->objectName() : QString();
448 if ( actionName
== "copy-action" )
450 QApplication::clipboard()->setText(url
);
454 if ( !object
|| actionName
== "open-action" )
456 if ( kind
== StandardUrl
)
458 // if the URL path does not include the protocol ( eg. "www.kde.org" ) then
459 // prepend http:// ( eg. "www.kde.org" --> "http://www.kde.org" )
460 if (!url
.contains("://"))
462 url
.prepend("http://");
465 else if ( kind
== Email
)
467 url
.prepend("mailto:");
470 new KRun(url
,QApplication::activeWindow());
474 // Note: Altering these regular expressions can have a major effect on the performance of the filters
475 // used for finding URLs in the text, especially if they are very general and could match very long
477 // Please be careful when altering them.
481 // protocolname:// or www. followed by anything other than whitespaces, <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, comma and dot
482 const QRegExp
UrlFilter::FullUrlRegExp("(www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]]");
484 // [word chars, dots or dashes]@[word chars, dots or dashes].[word chars]
485 const QRegExp
UrlFilter::EmailAddressRegExp("\\b(\\w|\\.|-)+@(\\w|\\.|-)+\\.\\w+\\b");
487 // matches full url or email address
488 const QRegExp
UrlFilter::CompleteUrlRegExp('('+FullUrlRegExp
.pattern()+'|'+
489 EmailAddressRegExp
.pattern()+')');
491 UrlFilter::UrlFilter()
493 setRegExp( CompleteUrlRegExp
);
495 UrlFilter::HotSpot::~HotSpot()
499 void FilterObject::activated()
501 _filter
->activate(sender());
503 QList
<QAction
*> UrlFilter::HotSpot::actions()
505 QList
<QAction
*> list
;
507 const UrlType kind
= urlType();
509 QAction
* openAction
= new QAction(_urlObject
);
510 QAction
* copyAction
= new QAction(_urlObject
);;
512 Q_ASSERT( kind
== StandardUrl
|| kind
== Email
);
514 if ( kind
== StandardUrl
)
516 openAction
->setText(i18n("Open Link"));
517 copyAction
->setText(i18n("Copy Link Address"));
519 else if ( kind
== Email
)
521 openAction
->setText(i18n("Send Email To..."));
522 copyAction
->setText(i18n("Copy Email Address"));
525 // object names are set here so that the hotspot performs the
526 // correct action when activated() is called with the triggered
527 // action passed as a parameter.
528 openAction
->setObjectName("open-action");
529 copyAction
->setObjectName("copy-action");
531 QObject::connect( openAction
, SIGNAL(triggered()) , _urlObject
, SLOT(activated()) );
532 QObject::connect( copyAction
, SIGNAL(triggered()) , _urlObject
, SLOT(activated()) );
540 #include "Filter.moc"