Fix crash if key bindings specified in profile cannot be found. Improve
[personal-kdebase.git] / apps / konsole / src / Filter.cpp
blobfbad1f01176d329e55b63db31cd8a261ea936595
1 /*
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
17 02110-1301 USA.
20 // Own
21 #include "Filter.h"
23 // System
24 #include <iostream>
26 // Qt
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>
35 // KDE
36 #include <KLocale>
37 #include <KRun>
39 // Konsole
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();
52 iter.remove();
53 delete filter;
57 void FilterChain::addFilter(Filter* filter)
59 append(filter);
61 void FilterChain::removeFilter(Filter* filter)
63 removeAll(filter);
65 bool FilterChain::containsFilter(Filter* filter)
67 return contains(filter);
69 void FilterChain::reset()
71 QListIterator<Filter*> iter(*this);
72 while (iter.hasNext())
73 iter.next()->reset();
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);
98 if ( spot != 0 )
100 return spot;
104 return 0;
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();
116 return list;
118 //QList<Filter::HotSpot*> FilterChain::hotSpotsAtLine(int line) const;
120 TerminalImageFilterChain::TerminalImageFilterChain()
121 : _buffer(0)
122 , _linePositions(0)
126 TerminalImageFilterChain::~TerminalImageFilterChain()
128 delete _buffer;
129 delete _linePositions;
132 void TerminalImageFilterChain::setImage(const Character* const image , int lines , int columns, const QVector<LineProperty>& lineProperties)
134 if (empty())
135 return;
137 // reset all filters and hotspots
138 reset();
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
149 delete _buffer;
150 delete _linePositions;
152 _buffer = newBuffer;
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
168 // highlighted.
170 // TODO - Use the "line wrapped" attribute associated with lines in a
171 // terminal image to avoid adding this imaginary character for wrapped
172 // lines
173 if ( !(lineProperties.value(i,LINE_DEFAULT) & LINE_WRAPPED) )
174 lineStream << QChar('\n');
176 decoder.end();
179 Filter::Filter() :
180 _linePositions(0),
181 _buffer(0)
185 Filter::~Filter()
187 QListIterator<HotSpot*> iter(_hotspotList);
188 while (iter.hasNext())
190 delete iter.next();
193 void Filter::reset()
195 _hotspots.clear();
196 _hotspotList.clear();
199 void Filter::setBuffer(const QString* buffer , const QList<int>* linePositions)
201 _buffer = buffer;
202 _linePositions = linePositions;
205 void Filter::getLineColumn(int position , int& startLine , int& startColumn)
207 Q_ASSERT( _linePositions );
208 Q_ASSERT( _buffer );
211 for (int i = 0 ; i < _linePositions->count() ; i++)
213 int nextLine = 0;
215 if ( i == _linePositions->count()-1 )
216 nextLine = _buffer->length() + 1;
217 else
218 nextLine = _linePositions->value(i+1);
220 if ( _linePositions->value(i) <= position && position < nextLine )
222 startLine = i;
223 startColumn = string_width(buffer()->mid(_linePositions->value(i),position - _linePositions->value(i)));
224 return;
230 /*void Filter::addLine(const QString& text)
232 _linePositions << _buffer.length();
233 _buffer.append(text);
236 const QString* Filter::buffer()
238 return _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
254 return _hotspotList;
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 )
270 continue;
271 if ( spot->endLine() == line && spot->endColumn() < column )
272 continue;
274 return spot;
277 return 0;
280 Filter::HotSpot::HotSpot(int startLine , int startColumn , int endLine , int endColumn)
281 : _startLine(startLine)
282 , _startColumn(startColumn)
283 , _endLine(endLine)
284 , _endColumn(endColumn)
285 , _type(NotSpecified)
288 QString Filter::HotSpot::tooltip() const
290 return QString();
292 QList<QAction*> Filter::HotSpot::actions()
294 return QList<QAction*>();
296 int Filter::HotSpot::startLine() const
298 return _startLine;
300 int Filter::HotSpot::endLine() const
302 return _endLine;
304 int Filter::HotSpot::startColumn() const
306 return _startColumn;
308 int Filter::HotSpot::endColumn() const
310 return _endColumn;
312 Filter::HotSpot::Type Filter::HotSpot::type() const
314 return _type;
316 void Filter::HotSpot::setType(Type type)
318 _type = type;
321 RegExpFilter::RegExpFilter()
325 RegExpFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn)
326 : Filter::HotSpot(startLine,startColumn,endLine,endColumn)
328 setType(Marker);
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
350 return _searchText;
352 /*void RegExpFilter::reset(int)
354 _buffer = QString();
356 void RegExpFilter::process()
358 int pos = 0;
359 const QString* text = buffer();
361 Q_ASSERT( text );
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) )
367 return;
369 while(pos >= 0)
371 pos = _searchText.indexIn(*text,pos);
373 if ( pos >= 0 )
375 int startLine = 0;
376 int endLine = 0;
377 int startColumn = 0;
378 int endColumn = 0;
380 getLineColumn(pos,startLine,startColumn);
381 getLineColumn(pos + _searchText.matchedLength(),endLine,endColumn);
383 RegExpFilter::HotSpot* spot = newHotSpot(startLine,startColumn,
384 endLine,endColumn);
385 spot->setCapturedTexts(_searchText.capturedTexts());
387 addHotSpot( spot );
388 pos += _searchText.matchedLength();
390 // if matchedLength == 0, the program will get stuck in an infinite loop
391 if ( _searchText.matchedLength() == 0 )
392 pos = -1;
397 RegExpFilter::HotSpot* RegExpFilter::newHotSpot(int startLine,int startColumn,
398 int endLine,int endColumn)
400 return new RegExpFilter::HotSpot(startLine,startColumn,
401 endLine,endColumn);
403 RegExpFilter::HotSpot* UrlFilter::newHotSpot(int startLine,int startColumn,int endLine,
404 int endColumn)
406 return new UrlFilter::HotSpot(startLine,startColumn,
407 endLine,endColumn);
409 UrlFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn)
410 : RegExpFilter::HotSpot(startLine,startColumn,endLine,endColumn)
411 , _urlObject(new FilterObject(this))
413 setType(Link);
415 QString UrlFilter::HotSpot::tooltip() const
417 QString url = capturedTexts().first();
419 const UrlType kind = urlType();
421 if ( kind == StandardUrl )
422 return QString();
423 else if ( kind == Email )
424 return QString();
425 else
426 return QString();
428 UrlFilter::HotSpot::UrlType UrlFilter::HotSpot::urlType() const
430 QString url = capturedTexts().first();
432 if ( FullUrlRegExp.exactMatch(url) )
433 return StandardUrl;
434 else if ( EmailAddressRegExp.exactMatch(url) )
435 return Email;
436 else
437 return Unknown;
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);
451 return;
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
476 // pieces of text.
477 // Please be careful when altering them.
479 //regexp matches:
480 // full url:
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<>'\"\\]]");
483 // email address:
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()
497 delete _urlObject;
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()) );
534 list << openAction;
535 list << copyAction;
537 return list;
540 #include "Filter.moc"