mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-24 19:02:53 +00:00
410 lines
12 KiB
C++
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 ¤tIdx )
|
|
{
|
|
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 ¤tIdx )
|
|
{
|
|
//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"
|