diff --git a/src/Filter.cpp b/src/Filter.cpp index bf25bfce..f0a2dc1f 100644 --- a/src/Filter.cpp +++ b/src/Filter.cpp @@ -1,598 +1,590 @@ /* Copyright 2007-2008 by Robert Knight 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. */ // Own #include "Filter.h" #include "konsoledebug.h" // Qt #include #include #include #include #include #include #include #include // KDE #include #include // Konsole #include "Session.h" #include "TerminalCharacterDecoder.h" using namespace Konsole; FilterChain::~FilterChain() { qDeleteAll(_filters); } void FilterChain::addFilter(Filter *filter) { _filters.append(filter); } void FilterChain::removeFilter(Filter *filter) { _filters.removeAll(filter); } void FilterChain::reset() { for(auto *filter : _filters) { filter->reset(); } } void FilterChain::setBuffer(const QString *buffer, const QList *linePositions) { for(auto *filter : _filters) { filter->setBuffer(buffer, linePositions); } } void FilterChain::process() { for( auto *filter : _filters) { filter->process(); } } void FilterChain::clear() { _filters.clear(); } QSharedPointer FilterChain::hotSpotAt(int line, int column) const { for(auto *filter : _filters) { QSharedPointer spot = filter->hotSpotAt(line, column); if (spot != nullptr) { return spot; } } return nullptr; } QList> FilterChain::hotSpots() const { QList> list; for (auto *filter : _filters) { list.append(filter->hotSpots()); > } return list; } TerminalImageFilterChain::TerminalImageFilterChain() : _buffer(nullptr), _linePositions(nullptr) { } TerminalImageFilterChain::~TerminalImageFilterChain() { - delete _buffer; - delete _linePositions; } void TerminalImageFilterChain::setImage(const Character * const image, int lines, int columns, const QVector &lineProperties) { if (_filters.empty()) { return; } // reset all filters and hotspots reset(); PlainTextDecoder decoder; decoder.setLeadingWhitespace(true); decoder.setTrailingWhitespace(true); // setup new shared buffers for the filters to process on - auto newBuffer = new QString(); - auto newLinePositions = new QList(); - setBuffer(newBuffer, newLinePositions); + _buffer.reset(new QString()); + _linePositions.reset(new QList()); - // free the old buffers - delete _buffer; - delete _linePositions; + setBuffer(_buffer.get(), _linePositions.get()); - _buffer = newBuffer; - _linePositions = newLinePositions; - - QTextStream lineStream(_buffer); + QTextStream lineStream(_buffer.get()); decoder.begin(&lineStream); for (int i = 0; i < lines; i++) { _linePositions->append(_buffer->length()); decoder.decodeLine(image + i * columns, columns, LINE_DEFAULT); // pretend that each line ends with a newline character. // this prevents a link that occurs at the end of one line // being treated as part of a link that occurs at the start of the next line // // the downside is that links which are spread over more than one line are not // highlighted. // // TODO - Use the "line wrapped" attribute associated with lines in a // terminal image to avoid adding this imaginary character for wrapped // lines if ((lineProperties.value(i, LINE_DEFAULT) & LINE_WRAPPED) == 0) { lineStream << QLatin1Char('\n'); } } decoder.end(); } Filter::Filter() : _linePositions(nullptr), _buffer(nullptr) { } Filter::~Filter() { reset(); } void Filter::reset() { _hotspots.clear(); _hotspotList.clear(); } void Filter::setBuffer(const QString *buffer, const QList *linePositions) { _buffer = buffer; _linePositions = linePositions; } std::pair Filter::getLineColumn(int position) { Q_ASSERT(_linePositions); Q_ASSERT(_buffer); for (int i = 0; i < _linePositions->count(); i++) { const int nextLine = i == _linePositions->count() - 1 ? _buffer->length() + 1 : _linePositions->value(i + 1); if (_linePositions->value(i) <= position && position < nextLine) { return std::make_pair(i, Character::stringWidth(buffer()->mid(_linePositions->value(i), position - _linePositions->value(i)))); } } return std::make_pair(-1, -1); } const QString *Filter::buffer() { return _buffer; } Filter::HotSpot::~HotSpot() = default; void Filter::addHotSpot(QSharedPointer spot) { _hotspotList << spot; for (int line = spot->startLine(); line <= spot->endLine(); line++) { _hotspots.insert(line, spot); } } QList> Filter::hotSpots() const { return _hotspotList; } QSharedPointer Filter::hotSpotAt(int line, int column) const { const auto hotspots = _hotspots.values(line); for (auto &spot : hotspots) { if (spot->startLine() == line && spot->startColumn() > column) { continue; } if (spot->endLine() == line && spot->endColumn() < column) { continue; } return spot; } return nullptr; } Filter::HotSpot::HotSpot(int startLine, int startColumn, int endLine, int endColumn) : _startLine(startLine), _startColumn(startColumn), _endLine(endLine), _endColumn(endColumn), _type(NotSpecified) { } QList Filter::HotSpot::actions() const { return {}; } 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() : _searchText(QRegularExpression()) { } RegExpFilter::HotSpot::HotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts) : Filter::HotSpot(startLine, startColumn, endLine, endColumn), _capturedTexts(capturedTexts) { setType(Marker); } void RegExpFilter::HotSpot::activate(QObject *) { } QStringList RegExpFilter::HotSpot::capturedTexts() const { return _capturedTexts; } void RegExpFilter::setRegExp(const QRegularExpression ®Exp) { _searchText = regExp; _searchText.optimize(); } QRegularExpression RegExpFilter::regExp() const { return _searchText; } void RegExpFilter::process() { const QString *text = buffer(); Q_ASSERT(text); if (!_searchText.isValid() || _searchText.pattern().isEmpty()) { return; } QRegularExpressionMatchIterator iterator(_searchText.globalMatch(*text)); while (iterator.hasNext()) { QRegularExpressionMatch match(iterator.next()); auto [startLine, startColumn] = getLineColumn(match.capturedStart()); auto [endLine, endColumn] = getLineColumn(match.capturedEnd()); QSharedPointer spot(newHotSpot(startLine, startColumn, endLine, endColumn, match.capturedTexts())); if (spot == nullptr) { continue; } addHotSpot(spot); } } QSharedPointer RegExpFilter::newHotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts) { return QSharedPointer(new RegExpFilter::HotSpot(startLine, startColumn, endLine, endColumn, capturedTexts)); } QSharedPointer UrlFilter::newHotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts) { return QSharedPointer(new UrlFilter::HotSpot(startLine, startColumn, endLine, endColumn, capturedTexts)); } UrlFilter::HotSpot::HotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts) : RegExpFilter::HotSpot(startLine, startColumn, endLine, endColumn, capturedTexts) { setType(Link); } UrlFilter::HotSpot::UrlType UrlFilter::HotSpot::urlType() const { const QString url = capturedTexts().at(0); return FullUrlRegExp.match(url).hasMatch() ? StandardUrl : EmailAddressRegExp.match(url).hasMatch() ? Email : Unknown; } void UrlFilter::HotSpot::activate(QObject *object) { QString url = capturedTexts().at(0); const UrlType kind = urlType(); const QString &actionName = object != nullptr ? object->objectName() : QString(); if (actionName == QLatin1String("copy-action")) { QApplication::clipboard()->setText(url); return; } if ((object == nullptr) || actionName == QLatin1String("open-action")) { if (kind == StandardUrl) { // if the URL path does not include the protocol ( eg. "www.kde.org" ) then // prepend https:// ( eg. "www.kde.org" --> "https://www.kde.org" ) if (!url.contains(QLatin1String("://"))) { url.prepend(QLatin1String("https://")); } } else if (kind == Email) { url.prepend(QLatin1String("mailto:")); } new KRun(QUrl(url), QApplication::activeWindow()); } } // Note: Altering these regular expressions can have a major effect on the performance of the filters // used for finding URLs in the text, especially if they are very general and could match very long // pieces of text. // Please be careful when altering them. //regexp matches: // full url: // protocolname:// or www. followed by anything other than whitespaces, <, >, ' or ", and ends before whitespaces, <, >, ', ", ], !, ), :, comma and dot const QRegularExpression UrlFilter::FullUrlRegExp(QStringLiteral("(www\\.(?!\\.)|[a-z][a-z0-9+.-]*://)[^\\s<>'\"]+[^!,\\.\\s<>'\"\\]\\)\\:]"), QRegularExpression::OptimizeOnFirstUsageOption); // email address: // [word chars, dots or dashes]@[word chars, dots or dashes].[word chars] const QRegularExpression UrlFilter::EmailAddressRegExp(QStringLiteral("\\b(\\w|\\.|-|\\+)+@(\\w|\\.|-)+\\.\\w+\\b"), QRegularExpression::OptimizeOnFirstUsageOption); // matches full url or email address const QRegularExpression UrlFilter::CompleteUrlRegExp(QLatin1Char('(') + FullUrlRegExp.pattern() + QLatin1Char('|') + EmailAddressRegExp.pattern() + QLatin1Char(')'), QRegularExpression::OptimizeOnFirstUsageOption); UrlFilter::UrlFilter() { setRegExp(CompleteUrlRegExp); } UrlFilter::HotSpot::~HotSpot() { } QList UrlFilter::HotSpot::actions() const { auto openAction = new QAction(this); auto copyAction = new QAction(this); const UrlType kind = urlType(); Q_ASSERT(kind == StandardUrl || kind == Email); if (kind == StandardUrl) { openAction->setText(i18n("Open Link")); openAction->setIcon(QIcon::fromTheme(QStringLiteral("internet-services"))); copyAction->setText(i18n("Copy Link Address")); copyAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy-url"))); } else if (kind == Email) { openAction->setText(i18n("Send Email To...")); openAction->setIcon(QIcon::fromTheme(QStringLiteral("mail-send"))); copyAction->setText(i18n("Copy Email Address")); copyAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy-mail"))); } // object names are set here so that the hotspot performs the // correct action when activated() is called with the triggered // action passed as a parameter. openAction->setObjectName(QStringLiteral("open-action")); copyAction->setObjectName(QStringLiteral("copy-action")); QObject::connect(openAction, &QAction::triggered, this, [this, openAction]{ activate(openAction); }); QObject::connect(copyAction, &QAction::triggered, this, [this, copyAction]{ activate(copyAction); }); return {openAction, copyAction}; } /** * File Filter - Construct a filter that works on local file paths using the * posix portable filename character set combined with KDE's mimetype filename * extension blob patterns. * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_267 */ QSharedPointer FileFilter::newHotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts) { if (_session.isNull()) { qCDebug(KonsoleDebug) << "Trying to create new hot spot without session!"; return nullptr; } QString filename = capturedTexts.first(); if (filename.startsWith(QLatin1Char('\'')) && filename.endsWith(QLatin1Char('\''))) { filename.remove(0, 1); filename.chop(1); } // Return nullptr if it's not: // /filename // /childDir/filename bool isChild = false; for (const QString &s : _currentDirContents) { if (filename.startsWith(s)) { isChild = true; break; } } if (!isChild) { return nullptr; } return QSharedPointer(new FileFilter::HotSpot(startLine, startColumn, endLine, endColumn, capturedTexts, _dirPath + filename)); } void FileFilter::process() { const QDir dir(_session->currentWorkingDirectory()); _dirPath = dir.canonicalPath() + QLatin1Char('/'); _currentDirContents = dir.entryList(QDir::Dirs | QDir::Files); RegExpFilter::process(); } FileFilter::HotSpot::HotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts, const QString &filePath) : RegExpFilter::HotSpot(startLine, startColumn, endLine, endColumn, capturedTexts), _filePath(filePath) { setType(Link); } void FileFilter::HotSpot::activate(QObject *) { new KRun(QUrl::fromLocalFile(_filePath), QApplication::activeWindow()); } QString createFileRegex(const QStringList &patterns, const QString &filePattern, const QString &pathPattern) { QStringList suffixes = patterns.filter(QRegularExpression(QStringLiteral("^\\*") + filePattern + QStringLiteral("$"))); QStringList prefixes = patterns.filter(QRegularExpression(QStringLiteral("^") + filePattern + QStringLiteral("+\\*$"))); const QStringList fullNames = patterns.filter(QRegularExpression(QStringLiteral("^") + filePattern + QStringLiteral("$"))); suffixes.replaceInStrings(QStringLiteral("*"), QString()); suffixes.replaceInStrings(QStringLiteral("."), QStringLiteral("\\.")); prefixes.replaceInStrings(QStringLiteral("*"), QString()); prefixes.replaceInStrings(QStringLiteral("."), QStringLiteral("\\.")); return QString( // Optional path in front pathPattern + QLatin1Char('?') + QLatin1Char('(') // Files with known suffixes, e.g. "[A-Za-z0-9\\._\\-]+(txt|cpp|h|xml)" + filePattern + QLatin1Char('(') + suffixes.join(QLatin1Char('|')) + QLatin1Char(')') + QLatin1Char('|') // Files with known prefixes, e.g. "(Makefile\\.)[A-Za-z0-9\\._\\-]+" to match "Makefile.am" + QLatin1Char('(') + prefixes.join(QLatin1Char('|')) + QLatin1Char(')') + filePattern + QLatin1Char('|') // Files with known full names, e.g. "ChangeLog|COPYING" + fullNames.join(QLatin1Char('|')) + QLatin1Char(')') ); } FileFilter::FileFilter(Session *session) : _session(session) , _dirPath(QString()) , _currentDirContents(QStringList()) { static QRegularExpression re = QRegularExpression(QString(), QRegularExpression::DontCaptureOption); if (re.pattern().isEmpty()) { QStringList patterns; QMimeDatabase mimeDatabase; const QList allMimeTypes = mimeDatabase.allMimeTypes(); for (const QMimeType &mimeType : allMimeTypes) { patterns.append(mimeType.globPatterns()); } patterns.removeDuplicates(); const QString fileRegex = createFileRegex(patterns, QStringLiteral("[A-Za-z0-9\\._\\-]+"), // filenames regex QStringLiteral("([A-Za-z0-9\\._\\-/]+/)") // path regex ); const QString regex = QLatin1String("(\\b") + fileRegex + QLatin1String("\\b)") // file names with no spaces + QLatin1Char('|') + QLatin1String("('") + fileRegex + QLatin1String("')"); // file names with spaces re.setPattern(regex); } setRegExp(re); } FileFilter::HotSpot::~HotSpot() { } QList FileFilter::HotSpot::actions() const { auto openAction = new QAction(this); openAction->setText(i18n("Open File")); QObject::connect(openAction, &QAction::triggered, this, [this ]{ activate(); }); return {openAction}; } diff --git a/src/Filter.h b/src/Filter.h index e02722d2..1024d5b4 100644 --- a/src/Filter.h +++ b/src/Filter.h @@ -1,401 +1,405 @@ /* Copyright 2007-2008 by Robert Knight 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 #include #include #include #include #include #include +#include + // Konsole #include "Character.h" // std #include class QAction; namespace Konsole { class Session; /** * A filter processes blocks of text looking for certain patterns (such as URLs or keywords from a list) * and marks the areas which match the filter's patterns as 'hotspots'. * * Each hotspot has a type identifier associated with it ( such as a link or a highlighted section ), * and an action. When the user performs some activity such as a mouse-click in a hotspot area ( the exact * action will depend on what is displaying the block of text which the filter is processing ), the hotspot's * activate() method should be called. Depending on the type of hotspot this will trigger a suitable response. * * For example, if a hotspot represents a URL then a suitable action would be opening that URL in a web browser. * Hotspots may have more than one action, in which case the list of actions can be obtained using the * actions() method. * * Different subclasses of filter will return different types of hotspot. * Subclasses must reimplement the process() method to examine a block of text and identify sections of interest. * When processing the text they should create instances of Filter::HotSpot subclasses for sections of interest * and add them to the filter's list of hotspots using addHotSpot() */ class Filter { public: /** * Represents an area of text which matched the pattern a particular filter has been looking for. * * Each hotspot has a type identifier associated with it ( such as a link or a highlighted section ), * and an action. When the user performs some activity such as a mouse-click in a hotspot area ( the exact * action will depend on what is displaying the block of text which the filter is processing ), the hotspot's * activate() method should be called. Depending on the type of hotspot this will trigger a suitable response. * * For example, if a hotspot represents a URL then a suitable action would be opening that URL in a web browser. * Hotspots may have more than one action, in which case the list of actions can be obtained using the * actions() method. These actions may then be displayed in a popup menu or toolbar for example. */ class HotSpot : public QObject { // krazy suggest using Q_OBJECT here but moc can not handle // nested classes // QObject derived classes should use the Q_OBJECT macro public: /** * Constructs a new hotspot which covers the area from (@p startLine,@p startColumn) to (@p endLine,@p endColumn) * in a block of text. */ HotSpot(int startLine, int startColumn, int endLine, int endColumn); virtual ~HotSpot(); enum Type { // the type of the hotspot is not specified NotSpecified, // this hotspot represents a clickable link Link, // this hotspot represents a marker Marker }; /** Returns the line when the hotspot area starts */ int startLine() const; /** Returns the line where the hotspot area ends */ int endLine() const; /** Returns the column on startLine() where the hotspot area starts */ int startColumn() const; /** Returns the column on endLine() where the hotspot area ends */ int endColumn() const; /** * Returns the type of the hotspot. This is usually used as a hint for views on how to represent * the hotspot graphically. eg. Link hotspots are typically underlined when the user mouses over them */ Type type() const; /** * Causes the action associated with a hotspot to be triggered. * * @param object The object which caused the hotspot to be triggered. This is * typically null ( in which case the default action should be performed ) or * one of the objects from the actions() list. In which case the associated * action should be performed. */ virtual void activate(QObject *object = nullptr) = 0; /** * Returns a list of actions associated with the hotspot which can be used in a * menu or toolbar */ virtual QList actions() const; protected: /** Sets the type of a hotspot. This should only be set once */ void setType(Type type); private: int _startLine; int _startColumn; int _endLine; int _endColumn; Type _type; }; /** Constructs a new filter. */ Filter(); virtual ~Filter(); /** Causes the filter to process the block of text currently in its internal buffer */ virtual void process() = 0; /** * Empties the filters internal buffer and resets the line count back to 0. * All hotspots are deleted. */ void reset(); /** Returns the hotspot which covers the given @p line and @p column, or 0 if no hotspot covers that area */ QSharedPointer hotSpotAt(int line, int column) const; /** Returns the list of hotspots identified by the filter */ QList> hotSpots() const; /** Returns the list of hotspots identified by the filter which occur on a given line */ /** * TODO: Document me */ void setBuffer(const QString *buffer, const QList *linePositions); protected: /** Adds a new hotspot to the list */ void addHotSpot(QSharedPointer spot); /** Returns the internal buffer */ const QString *buffer(); /** Converts a character position within buffer() to a line and column */ std::pair getLineColumn(int position); private: Q_DISABLE_COPY(Filter) QMultiHash> _hotspots; QList> _hotspotList; const QList *_linePositions; const QString *_buffer; }; /** * A filter which searches for sections of text matching a regular expression and creates a new RegExpFilter::HotSpot * instance for them. * * Subclasses can reimplement newHotSpot() to return custom hotspot types when matches for the regular expression * are found. */ class RegExpFilter : public Filter { public: /** * Type of hotspot created by RegExpFilter. The capturedTexts() method can be used to find the text * matched by the filter's regular expression. */ class HotSpot : public Filter::HotSpot { public: HotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts); void activate(QObject *object = nullptr) override; /** Returns the texts found by the filter when matching the filter's regular expression */ QStringList capturedTexts() const; private: QStringList _capturedTexts; }; /** Constructs a new regular expression filter */ RegExpFilter(); /** * Sets the regular expression which the filter searches for in blocks of text. * * Regular expressions which match the empty string are treated as not matching * anything. */ void setRegExp(const QRegularExpression ®Exp); /** Returns the regular expression which the filter searches for in blocks of text */ QRegularExpression regExp() const; /** * Reimplemented to search the filter's text buffer for text matching regExp() * * If regexp matches the empty string, then process() will return immediately * without finding results. */ void process() override; protected: /** * Called when a match for the regular expression is encountered. Subclasses should reimplement this * to return custom hotspot types */ virtual QSharedPointer newHotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts); private: QRegularExpression _searchText; }; /** A filter which matches URLs in blocks of text */ class UrlFilter : public RegExpFilter { public: /** * Hotspot type created by UrlFilter instances. The activate() method opens a web browser * at the given URL when called. */ class HotSpot : public RegExpFilter::HotSpot { public: HotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts); ~HotSpot() override; QList actions() const override; /** * Open a web browser at the current URL. The url itself can be determined using * the capturedTexts() method. */ void activate(QObject *object = nullptr) override; private: enum UrlType { StandardUrl, Email, Unknown }; UrlType urlType() const; }; UrlFilter(); protected: QSharedPointer newHotSpot(int, int, int, int, const QStringList &) override; private: static const QRegularExpression FullUrlRegExp; static const QRegularExpression EmailAddressRegExp; // combined OR of FullUrlRegExp and EmailAddressRegExp static const QRegularExpression CompleteUrlRegExp; }; /** * A filter which matches files according to POSIX Portable Filename Character Set * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_267 */ class FileFilter : public RegExpFilter { public: /** * Hotspot type created by FileFilter instances. */ class HotSpot : public RegExpFilter::HotSpot { public: HotSpot(int startLine, int startColumn, int endLine, int endColumn, const QStringList &capturedTexts, const QString &filePath); ~HotSpot() override; QList actions() const override; /** * Opens kate for editing the file. */ void activate(QObject *object = nullptr) override; private: QString _filePath; }; explicit FileFilter(Session *session); void process() override; protected: QSharedPointer newHotSpot(int, int, int, int, const QStringList &) override; private: QPointer _session; QString _dirPath; QStringList _currentDirContents; }; /** * A chain which allows a group of filters to be processed as one. * The chain owns the filters added to it and deletes them when the chain itself is destroyed. * * Use addFilter() to add a new filter to the chain. * When new text to be filtered arrives, use addLine() to add each additional * line of text which needs to be processed and then after adding the last line, use * process() to cause each filter in the chain to process the text. * * After processing a block of text, the reset() method can be used to set the filter chain's * internal cursor back to the first line. * * The hotSpotAt() method will return the first hotspot which covers a given position. * * The hotSpots() method return all of the hotspots in the text and on * a given line respectively. */ class FilterChain { public: virtual ~FilterChain(); /** Adds a new filter to the chain. The chain will delete this filter when it is destroyed */ void addFilter(Filter *filter); /** Removes a filter from the chain. The chain will no longer delete the filter when destroyed */ void removeFilter(Filter *filter); /** Removes all filters from the chain */ void clear(); /** Resets each filter in the chain */ void reset(); /** * Processes each filter in the chain */ void process(); /** Sets the buffer for each filter in the chain to process. */ void setBuffer(const QString *buffer, const QList *linePositions); /** Returns the first hotspot which occurs at @p line, @p column or 0 if no hotspot was found */ QSharedPointer hotSpotAt(int line, int column) const; /** Returns a list of all the hotspots in all the chain's filters */ QList> hotSpots() const; protected: QList _filters; }; /** A filter chain which processes character images from terminal displays */ class TerminalImageFilterChain : public FilterChain { public: TerminalImageFilterChain(); ~TerminalImageFilterChain() override; /** * Set the current terminal image to @p image. * * @param image The terminal image * @param lines The number of lines in the terminal image * @param columns The number of columns in the terminal image * @param lineProperties The line properties to set for image */ void setImage(const Character * const image, int lines, int columns, const QVector &lineProperties); private: Q_DISABLE_COPY(TerminalImageFilterChain) - QString *_buffer; - QList *_linePositions; +/* usually QStrings and QLists are not supposed to be in the heap, here we have a problem: + we need a shared memory space between many filter objeccts, defined by this TerminalImage. */ + std::unique_ptr _buffer; + std::unique_ptr> _linePositions; }; } #endif //FILTER_H