kde-extraapps/kdevplatform/outputview/outputmodel.cpp
2015-07-26 14:23:17 +03:00

410 lines
12 KiB
C++

/***************************************************************************
* This file is part of KDevelop *
* Copyright 2007 Andreas Pakulat <apaku@gmx.de> *
* Copyright 2010 Aleix Pol Gonzalez <aleixpol@kde.org> *
* Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library 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 Library 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. *
***************************************************************************/
#include "outputmodel.h"
#include "filtereditem.h"
#include "outputfilteringstrategies.h"
#include <interfaces/icore.h>
#include <interfaces/idocumentcontroller.h>
#include <QtCore/QStringList>
#include <QtCore/QTimer>
#include <QThread>
#include <QFont>
#include <QApplication>
#include <KDebug>
#include <KGlobalSettings>
#include <set>
Q_DECLARE_METATYPE(QVector<KDevelop::FilteredItem>)
Q_DECLARE_METATYPE(KDevelop::IFilterStrategy*)
namespace KDevelop
{
/**
* Number of lines that are processed in one go before we notify the GUI thread
* about the result. It is generally faster to add multiple items to a model
* in one go compared to adding each item independently.
*/
static const int BATCH_SIZE = 50;
/**
* Time in ms that we wait in the parse worker for new incoming lines before
* actually processing them. If we already have enough for one batch though
* we process immediately.
*/
static const int BATCH_AGGREGATE_TIME_DELAY = 50;
class ParseWorker : public QObject
{
Q_OBJECT
public:
ParseWorker()
: QObject(0)
, m_filter( new NoFilterStrategy )
, m_timer(new QTimer(this))
{
m_timer->setInterval(BATCH_AGGREGATE_TIME_DELAY);
m_timer->setSingleShot(true);
connect(m_timer, SIGNAL(timeout()), SLOT(process()));
}
public slots:
void changeFilterStrategy( KDevelop::IFilterStrategy* newFilterStrategy )
{
m_filter = QSharedPointer<IFilterStrategy>( newFilterStrategy );
}
void addLines( const QStringList& lines )
{
m_cachedLines << lines;
if (m_cachedLines.size() >= BATCH_SIZE) {
// if enough lines were added, process immediately
m_timer->stop();
process();
} else if (!m_timer->isActive()) {
m_timer->start();
}
}
void flushBuffers()
{
m_timer->stop();
process();
emit allDone();
}
signals:
void parsedBatch(const QVector<KDevelop::FilteredItem>& filteredItems);
void allDone();
private slots:
/**
* Process *all* cached lines, emit parsedBatch for each batch
*/
void process()
{
QVector<KDevelop::FilteredItem> filteredItems;
filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size()));
foreach(const QString& line, m_cachedLines) {
FilteredItem item = m_filter->errorInLine(line);
if( item.type == FilteredItem::InvalidItem ) {
item = m_filter->actionInLine(line);
}
filteredItems << item;
if( filteredItems.size() == BATCH_SIZE ) {
emit parsedBatch(filteredItems);
filteredItems.clear();
filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size()));
}
}
// Make sure to emit the rest as well
if( !filteredItems.isEmpty() ) {
emit parsedBatch(filteredItems);
}
m_cachedLines.clear();
}
private:
QSharedPointer<IFilterStrategy> m_filter;
QStringList m_cachedLines;
QTimer* m_timer;
};
class ParsingThread
{
public:
ParsingThread()
{
m_thread.setObjectName("OutputFilterThread");
}
virtual ~ParsingThread()
{
if (m_thread.isRunning()) {
m_thread.quit();
m_thread.wait();
}
}
void addWorker(ParseWorker* worker)
{
if (!m_thread.isRunning()) {
m_thread.start();
}
worker->moveToThread(&m_thread);
}
private:
QThread m_thread;
};
K_GLOBAL_STATIC(ParsingThread, s_parsingThread);
struct OutputModelPrivate
{
OutputModelPrivate( OutputModel* model, const KUrl& builddir = KUrl() );
~OutputModelPrivate();
bool isValidIndex( const QModelIndex&, int currentRowCount ) const;
OutputModel* model;
ParseWorker* worker;
QVector<FilteredItem> m_filteredItems;
// We use std::set because that is ordered
std::set<int> m_errorItems; // Indices of all items that we want to move to using previous and next
KUrl m_buildDir;
void linesParsed(const QVector<KDevelop::FilteredItem>& items)
{
model->beginInsertRows( QModelIndex(), model->rowCount(), model->rowCount() + items.size() - 1);
foreach( const FilteredItem& item, items ) {
if( item.type == FilteredItem::ErrorItem ) {
m_errorItems.insert(m_filteredItems.size());
}
m_filteredItems << item;
}
model->endInsertRows();
}
};
OutputModelPrivate::OutputModelPrivate( OutputModel* model_, const KUrl& builddir)
: model(model_)
, worker(new ParseWorker )
, m_buildDir( builddir )
{
qRegisterMetaType<QVector<KDevelop::FilteredItem> >();
qRegisterMetaType<KDevelop::IFilterStrategy*>();
s_parsingThread->addWorker(worker);
model->connect(worker, SIGNAL(parsedBatch(QVector<KDevelop::FilteredItem>)),
model, SLOT(linesParsed(QVector<KDevelop::FilteredItem>)));
model->connect(worker, SIGNAL(allDone()), model, SIGNAL(allDone()));
}
bool OutputModelPrivate::isValidIndex( const QModelIndex& idx, int currentRowCount ) const
{
return ( idx.isValid() && idx.row() >= 0 && idx.row() < currentRowCount && idx.column() == 0 );
}
OutputModelPrivate::~OutputModelPrivate()
{
worker->deleteLater();
}
OutputModel::OutputModel( const KUrl& builddir, QObject* parent )
: QAbstractListModel(parent)
, d( new OutputModelPrivate( this, builddir ) )
{
}
OutputModel::OutputModel( QObject* parent )
: QAbstractListModel(parent)
, d( new OutputModelPrivate( this ) )
{
}
OutputModel::~OutputModel()
{
delete d;
}
QVariant OutputModel::data(const QModelIndex& idx , int role ) const
{
if( d->isValidIndex(idx, rowCount()) )
{
switch( role )
{
case Qt::DisplayRole:
return d->m_filteredItems.at( idx.row() ).shortenedText;
break;
case OutputModel::OutputItemTypeRole:
return static_cast<int>(d->m_filteredItems.at( idx.row() ).type);
break;
case Qt::FontRole:
return KGlobalSettings::fixedFont();
break;
default:
break;
}
}
return QVariant();
}
int OutputModel::rowCount( const QModelIndex& parent ) const
{
if( !parent.isValid() )
return d->m_filteredItems.count();
return 0;
}
QVariant OutputModel::headerData( int, Qt::Orientation, int ) const
{
return QVariant();
}
void OutputModel::activate( const QModelIndex& index )
{
if( index.model() != this || !d->isValidIndex(index, rowCount()) )
{
return;
}
kDebug() << "Model activated" << index.row();
FilteredItem item = d->m_filteredItems.at( index.row() );
if( item.isActivatable )
{
kDebug() << "activating:" << item.lineNo << item.url;
KTextEditor::Cursor range( item.lineNo, item.columnNo );
KDevelop::IDocumentController *docCtrl = KDevelop::ICore::self()->documentController();
KUrl url = item.url;
if(url.isRelative()) {
url = KUrl(d->m_buildDir, url.path());
}
docCtrl->openDocument( url, range );
} else {
kDebug() << "not an activateable item";
}
}
QModelIndex OutputModel::nextHighlightIndex( const QModelIndex &currentIdx )
{
int startrow = d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() + 1 : 0;
if( !d->m_errorItems.empty() )
{
kDebug() << "searching next error";
// Jump to the next error item
std::set< int >::const_iterator next = d->m_errorItems.lower_bound( startrow );
if( next == d->m_errorItems.end() )
next = d->m_errorItems.begin();
return index( *next, 0, QModelIndex() );
}
for( int row = 0; row < rowCount(); ++row )
{
int currow = (startrow + row) % rowCount();
if( d->m_filteredItems.at( currow ).isActivatable )
{
return index( currow, 0, QModelIndex() );
}
}
return QModelIndex();
}
QModelIndex OutputModel::previousHighlightIndex( const QModelIndex &currentIdx )
{
//We have to ensure that startrow is >= rowCount - 1 to get a positive value from the % operation.
int startrow = rowCount() + (d->isValidIndex(currentIdx, rowCount()) ? currentIdx.row() : rowCount()) - 1;
if(!d->m_errorItems.empty())
{
kDebug() << "searching previous error";
// Jump to the previous error item
std::set< int >::const_iterator previous = d->m_errorItems.lower_bound( currentIdx.row() );
if( previous == d->m_errorItems.begin() )
previous = d->m_errorItems.end();
--previous;
return index( *previous, 0, QModelIndex() );
}
for ( int row = 0; row < rowCount(); ++row )
{
int currow = (startrow - row) % rowCount();
if( d->m_filteredItems.at( currow ).isActivatable )
{
return index( currow, 0, QModelIndex() );
}
}
return QModelIndex();
}
void OutputModel::setFilteringStrategy(const OutputFilterStrategy& currentStrategy)
{
// TODO: Turn into factory, decouple from OutputModel
IFilterStrategy* filter = 0;
switch( currentStrategy )
{
case NoFilter:
filter = new NoFilterStrategy;
break;
case CompilerFilter:
filter = new CompilerFilterStrategy( d->m_buildDir );
break;
case ScriptErrorFilter:
filter = new ScriptErrorFilterStrategy;
break;
case NativeAppErrorFilter:
filter = new NativeAppErrorFilterStrategy;
break;
case StaticAnalysisFilter:
filter = new StaticAnalysisFilterStrategy;
break;
default:
// assert(false);
filter = new NoFilterStrategy;
break;
}
Q_ASSERT(filter);
QMetaObject::invokeMethod(d->worker, "changeFilterStrategy",
Q_ARG(KDevelop::IFilterStrategy*, filter));
}
void OutputModel::appendLines( const QStringList& lines )
{
if( lines.isEmpty() )
return;
QMetaObject::invokeMethod(d->worker, "addLines",
Q_ARG(QStringList, lines));
}
void OutputModel::appendLine( const QString& l )
{
appendLines( QStringList() << l );
}
void OutputModel::ensureAllDone()
{
QMetaObject::invokeMethod(d->worker, "flushBuffers");
}
}
#include "outputmodel.moc"
#include "moc_outputmodel.cpp"