Commit 04fb8415 authored by Robert Knight's avatar Robert Knight
Browse files

Added filters classes. These scan blocks of text and define a set of 'hot...

Added filters classes.  These scan blocks of text and define a set of 'hot spots' which can have various actions actions associated with them.

svn path=/branches/work/konsole-split-view/; revision=619579
parent 867801b9
......@@ -73,6 +73,7 @@ set(konsole_KDEINIT_SRCS
BlockArray.cpp
TerminalCharacterDecoder.cpp
IncrementalSearchBar.cpp
Filter.cpp
schema.cpp
konsole_wcwidth.cpp
)
......
/*
Copyright (C) 2007 by Robert Knight <robertknight@gmail.com>
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.
*/
// System
#include <iostream>
// Qt
#include <QAction>
#include <QApplication>
#include <QString>
#include <QtDebug>
#include <QSharedData>
#include <QFile>
// KDE
#include <KLocale>
#include <KRun>
// Konsole
#include "Filter.h"
#include "TerminalCharacterDecoder.h"
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<Filter*> iter(*this);
while (iter.hasNext())
iter.next()->reset();
}
void FilterChain::process()
{
QListIterator<Filter*> iter(*this);
while (iter.hasNext())
iter.next()->process();
}
void FilterChain::addLine(const QString& line)
{
QListIterator<Filter*> iter(*this);
while (iter.hasNext())
iter.next()->addLine(line);
}
void FilterChain::clear()
{
QList<Filter*>::clear();
}
Filter::HotSpot* FilterChain::hotSpotAt(int line , int column) const
{
QListIterator<Filter*> iter(*this);
while (iter.hasNext())
{
Filter* filter = iter.next();
Filter::HotSpot* spot = filter->hotSpotAt(line,column);
if ( spot != 0 )
{
return spot;
}
}
return 0;
}
//QList<Filter::HotSpot*> FilterChain::hotSpots() const;
//QList<Filter::HotSpot*> FilterChain::hotSpotsAtLine(int line) const;
void TerminalImageFilterChain::addImage(const ca* const image , int lines , int columns)
{
if (empty())
return;
PlainTextDecoder decoder;
decoder.setTrailingWhitespace(false);
QString line;
QTextStream lineStream(&line);
for (int i=0 ; i < lines ; i++)
{
decoder.decodeLine(image + i*columns,columns,0,&lineStream);
addLine(line);
line.clear();
}
}
Filter::~Filter()
{
QListIterator<HotSpot*> iter(_hotspotList);
while (iter.hasNext())
{
delete iter.next();
}
}
void Filter::reset()
{
_hotspots.clear();
_hotspotList.clear();
_linePositions.clear();
_buffer.clear();
}
void Filter::getLineColumn(int position , int& startLine , int& startColumn)
{
for (int i = 0 ; i < _linePositions.count() ; i++)
{
int nextLine = 0;
if ( i == _linePositions.count()-1 )
{
nextLine = _buffer.length();
}
else
{
nextLine = _linePositions[i+1];
}
if ( _linePositions[i] <= position && position <= nextLine )
{
startLine = i;
startColumn = position - _linePositions[i];
return;
}
}
}
void Filter::addLine(const QString& text)
{
_linePositions << _buffer.length();
_buffer.append(text);
}
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::HotSpot*> Filter::hotSpots() const
{
return _hotspotList;
}
QList<Filter::HotSpot*> Filter::hotSpotsAtLine(int line) const
{
return _hotspots.values(line);
}
Filter::HotSpot* Filter::hotSpotAt(int line , int column) const
{
QListIterator<HotSpot*> 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(NoType)
{
}
QList<QAction*> Filter::HotSpot::actions()
{
return QList<QAction*>();
}
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)
{}
void RegExpFilter::HotSpot::activate()
{
qDebug() << "regexp hotspot activated";
}
void RegExpFilter::HotSpot::setCapturedTexts(const QStringList& texts)
{
_capturedTexts = texts;
}
QStringList RegExpFilter::HotSpot::capturedTexts() const
{
return _capturedTexts;
}
void RegExpFilter::setRegExp(const QRegExp& regExp)
{
_searchText = QRegExp(regExp);
}
QRegExp RegExpFilter::regExp() const
{
return _searchText;
}
/*void RegExpFilter::reset(int)
{
_buffer = QString::null;
}*/
void RegExpFilter::process()
{
int pos = 0;
QString& text = buffer();
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();
}
}
}
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)
{
return new UrlFilter::HotSpot(startLine,startColumn,
endLine,endColumn);
}
UrlFilter::HotSpot::HotSpot(int startLine,int startColumn,int endLine,int endColumn)
: RegExpFilter::HotSpot(startLine,startColumn,endLine,endColumn)
, _urlObject(new FilterObject(this))
{
setType(LinkType);
}
void UrlFilter::HotSpot::activate()
{
const QStringList& texts = capturedTexts();
QString url = texts.first();
// 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://");
}
if ( texts.count() > 0 )
{
new KRun(url,QApplication::activeWindow());
}
}
UrlFilter::UrlFilter()
{
//regexp matches:
// protocolname:// or www. followed by numbers, letters dots and dashes
setRegExp(QRegExp("([a-z]+://|www\\.)[a-zA-Z0-9\\-\\./]+"));
}
UrlFilter::HotSpot::~HotSpot()
{
delete _urlObject;
}
void FilterObject::activated()
{
_filter->activate();
}
QList<QAction*> UrlFilter::HotSpot::actions()
{
QList<QAction*> list;
QAction* openAction = new QAction(i18n("Open Link"),_urlObject);
QObject::connect( openAction , SIGNAL(triggered()) , _urlObject , SLOT(activated()) );
list << openAction;
return list;
}
#include "Filter.moc"
/*
Copyright (C) 2007 by Robert Knight <robertknight@gmail.com>
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.
*/
#ifndef FILTER_H
#define FILTER_H
// Qt
#include <QList>
#include <QObject>
#include <QStringList>
#include <QMultiHash>
#include <QRegExp>
class ca;
class Filter
{
public:
class HotSpot
{
public:
HotSpot(int startLine , int startColumn , int endLine , int endColumn);
virtual ~HotSpot();
enum Type
{
// the type of the hotspot is not specified
NoType,
// this hotspot represents a clickable link
LinkType,
// this hotspot represents a marker
MarkerType
};
int startLine() const;
int endLine() const;
int startColumn() const;
int endColumn() const;
Type type() const;
virtual void activate() = 0;
virtual QList<QAction*> actions();
protected:
void setType(Type type);
private:
int _startLine;
int _startColumn;
int _endLine;
int _endColumn;
Type _type;
};
virtual ~Filter();
virtual void process() = 0;
void reset();
void addLine(const QString& string);
HotSpot* hotSpotAt(int line , int column) const;
QList<HotSpot*> hotSpots() const;
QList<HotSpot*> hotSpotsAtLine(int line) const;
protected:
void addHotSpot(HotSpot*);
QString& buffer();
void getLineColumn(int position , int& startLine , int& startColumn);
private:
QMultiHash<int,HotSpot*> _hotspots;
QList<HotSpot*> _hotspotList;
QList<int> _linePositions;
QString _buffer;
};
class RegExpFilter : public Filter
{
public:
class HotSpot : public Filter::HotSpot
{
public:
HotSpot(int startLine, int startColumn, int endLine , int endColumn);
virtual void activate();
void setCapturedTexts(const QStringList& texts);
QStringList capturedTexts() const;
private:
QStringList _capturedTexts;
};
RegExpFilter();
void setRegExp(const QRegExp& text);
QRegExp regExp() const;
virtual void process();
protected:
virtual RegExpFilter::HotSpot* newHotSpot(int startLine,int startColumn,
int endLine,int endColumn);
private:
QRegExp _searchText;
};
class FilterObject;
class UrlFilter : public RegExpFilter
{
public:
class HotSpot : public RegExpFilter::HotSpot
{
public:
HotSpot(int startLine,int startColumn,int endLine,int endColumn);
virtual ~HotSpot();
virtual QList<QAction*> actions();
virtual void activate();
private:
FilterObject* _urlObject;
};
UrlFilter();
protected:
virtual RegExpFilter::HotSpot* newHotSpot(int,int,int,int);
};
class FilterObject : public QObject
{
Q_OBJECT
public:
FilterObject(Filter::HotSpot* filter) : _filter(filter) {};
private slots:
void activated();
private:
Filter::HotSpot* _filter;
};
class FilterChain : protected QList<Filter*>
{
public:
void addFilter(Filter* filter);
void removeFilter(Filter* filter);
bool containsFilter(Filter* filter);
void clear();
void reset();
void addLine(const QString& line);
void process();
Filter::HotSpot* hotSpotAt(int line , int column) const;
QList<Filter::HotSpot*> hotSpots() const;
QList<Filter::HotSpot> hotSpotsAtLine(int line) const;
};
class TerminalImageFilterChain : public FilterChain
{
public:
void addImage(const ca* const image , int lines , int columns);
};
#endif //FILTER_H
......@@ -88,6 +88,7 @@
// Konsole
#include "config.h"
#include "Filter.h"
#include "TEWidget.h"
#include "konsole_wcwidth.h"
......@@ -386,7 +387,10 @@ TEWidget::TEWidget(QWidget *parent)
,m_cursorCol(0)
,m_isIMEdit(false)
,blend_color(qRgba(0,0,0,0xff))
,_filterChain(new TerminalImageFilterChain())
{
_filterChain->addFilter( new UrlFilter() );
// The offsets are not yet calculated.
// Do not calculate these too often to be more smoothly when resizing
// konsole in opaque mode.
......@@ -442,6 +446,7 @@ TEWidget::~TEWidget()
delete gridLayout;
delete outputSuspendedLabel;
delete _filterChain;
}
/* ------------------------------------------------------------------------- */
......@@ -935,6 +940,13 @@ void TEWidget::setImage(const ca* const newimg, int lines, int columns)
if (!image)
updateImageSize(); // Create image
//TEMPORARY: This is for testing only, this could potentially be expensive,
//so a mechanism must be added to only process filters as needed
#warning "Temporary addition to test filters. Don't do this in the final code"
_filterChain->reset();
_filterChain->addImage(newimg,lines,columns);
_filterChain->process();
assert( this->usedLines <= this->lines );
assert( this->usedColumns <= this->columns );
......@@ -1827,6 +1839,12 @@ void TEWidget::mouseReleaseEvent(QMouseEvent* ev)
int charColumn;
characterPosition(ev->pos(),charLine,charColumn);
Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
if ( spot )
{
spot->activate();
}
if ( ev->button() == Qt::LeftButton)
{
emit isBusySelecting(false);
......
......@@ -57,6 +57,7 @@ class QFrame;
class QGridLayout;
class QShowEvent;
class QHideEvent;
class TerminalImageFilterChain;
/**
* A widget which displays output from a terminal emulation and sends input keypresses and mouse activity
......@@ -412,6 +413,8 @@ private:
QAction* m_mvAction;
QAction* m_lnAction;
TerminalImageFilterChain* _filterChain;
//the delay in milliseconds between redrawing blinking text
static const int BLINK_DELAY = 750;
......
......@@ -25,7 +25,21 @@
#include "TerminalCharacterDecoder.h"
void PlainTextDecoder::decodeLine(ca* const characters, int count, LineProperty /*properties*/,
PlainTextDecoder::PlainTextDecoder()
: _includeTrailingWhitespace(true)
{
}
void PlainTextDecoder::setTrailingWhitespace(bool enable)