kde-workspace/ksysguard/gui/SensorBrowser.cpp

653 lines
23 KiB
C++

/*
KSysGuard, the KDE System Guard
Copyright (c) 1999, 2000 Chris Schlaeger <cs@kde.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License version 2 or at your option version 3 as published by
the Free Software Foundation.
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.
*/
#include <QtGui/QDrag>
#include <QtGui/QLabel>
#include <QtGui/qevent.h>
#include <QtGui/QPainter>
#include <QtGui/QPixmap>
#include <QtGui/QVBoxLayout>
#include <QMimeData>
#include <kdebug.h>
#include <kicon.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <ksgrd/SensorManager.h>
#include <kfilterproxysearchline.h>
#include "SensorBrowser.h"
//#define SENSOR_MODEL_DO_TEST
//uncomment the above to test the model
#ifdef SENSOR_MODEL_DO_TEST
#include "modeltest.h"
#endif
SensorBrowserModel::SensorBrowserModel()
{
#ifdef SENSOR_MODEL_DO_TEST
new ModelTest(this);
#endif
mIdCount=1;
}
SensorBrowserModel::~SensorBrowserModel()
{
qDeleteAll( mHostInfoMap );
mHostInfoMap.clear();
qDeleteAll( mSensorInfoMap );
mSensorInfoMap.clear();
}
int SensorBrowserModel::columnCount( const QModelIndex &) const { //virtual
return 1;
}
QVariant SensorBrowserModel::data( const QModelIndex & index, int role) const { //virtual
if(!index.isValid()) return QVariant();
switch(role) {
case Qt::DisplayRole: {
if(index.column()==0) {
uint id = index.internalId();
if(mSensorInfoMap.contains(id)) {
Q_ASSERT(mSensorInfoMap.value(id));
SensorInfo *sensorInfo = mSensorInfoMap.value(id);
return QString(sensorInfo->description() + " (" + KSGRD::SensorMgr->translateSensorType(sensorInfo->type()) + ')' );
}
if(mTreeNodeNames.contains(id)) return mTreeNodeNames.value(id);
if(mHostInfoMap.contains(id)) {
Q_ASSERT(mHostInfoMap.value(id));
return mHostInfoMap.value(id)->hostName();
}
}
return QString();
}
case Qt::DecorationRole: {
if(index.column() == 0) {
HostInfo *host = getHostInfo(index.internalId());
KSGRD::SensorAgent *agent;
if(host != NULL && (agent = host->sensorAgent())) {
if(agent->daemonOnLine())
return KIcon("computer");
else
return KIcon("dialog-warning");
} else
return QIcon();
} else
return QIcon();
break;
}
case Qt::ToolTipRole: {
if(index.column() == 0) {
HostInfo *host = getHostInfo(index.internalId());
KSGRD::SensorAgent *agent;
if(host != NULL && (agent = host->sensorAgent())) {
if(agent->daemonOnLine())
return agent->hostName();
else
return agent->reasonForOffline();
}
}
break;
}
} //switch
return QVariant();
}
QVariant SensorBrowserModel::headerData ( int section, Qt::Orientation , int role) const { //virtual
if(role != Qt::DisplayRole) return QVariant();
if(section==0) return i18n("Sensor Browser");
return QVariant();
}
void SensorBrowserModel::retranslate() {
emit headerDataChanged(Qt::Horizontal, 0,0);
}
QModelIndex SensorBrowserModel::index ( int row, int column, const QModelIndex & parent) const { //virtual
if(column != 0) return QModelIndex();
QList<int> ids;
if(!parent.isValid()) {
ids = mHostInfoMap.keys();
}
else {
ids = mTreeMap.value(parent.internalId());
}
if( row >= ids.size() || row< 0) {
return QModelIndex();
}
QModelIndex index = createIndex(row, column, ids[row]);
Q_ASSERT(index.isValid());
return index;
}
QStringList SensorBrowserModel::listHosts() const
{
QStringList hostList;
QMapIterator<int, HostInfo*> it( mHostInfoMap );
while ( it.hasNext() ) {
it.next();
Q_ASSERT(it.value());
hostList.append( it.value()->hostName() );
}
return hostList;
}
QStringList SensorBrowserModel::listSensors( const QString &hostName ) const
{
QMapIterator<int, HostInfo*> it( mHostInfoMap );
while ( it.hasNext() ) {
it.next();
Q_ASSERT(it.value());
if ( it.value()->hostName() == hostName ) {
Q_ASSERT(mSensorInfoMap.contains(it.key()));
return listSensors( it.key() );
}
}
return QStringList();
}
QStringList SensorBrowserModel::listSensors( int parentId) const
{
SensorInfo *sensor=mSensorInfoMap.value(parentId);
if(sensor) return QStringList(sensor->name());
QStringList childSensors;
QList<int> children = mTreeMap.value(parentId);
for(int i=0; i < children.size(); i++) {
childSensors+= listSensors(children[i]);
}
return childSensors;
}
SensorInfo *SensorBrowserModel::getSensorInfo(QModelIndex index) const
{
if(!index.isValid()) return NULL;
return mSensorInfoMap.value(index.internalId());
}
int SensorBrowserModel::makeSensor(HostInfo *hostInfo, int parentId, const QString &sensorName, const QString &name, const QString &sensorType) {
//sensorName is the full version. e.g. mem/free
//name is the short version. e.g. free
//sensortype is e.g. Integer
QList<int> children = mTreeMap.value(parentId);
for(int i=0; i<children.size(); i++)
if(mSensorInfoMap.contains(children[i])) {
Q_ASSERT(mSensorInfoMap.value(children[i]));
if(mSensorInfoMap.value(children[i])->name() == sensorName)
return children[i];
}
QModelIndex parentModelIndex;
if(hostInfo->id() == parentId) {
parentModelIndex = createIndex(mHostInfoMap.keys().indexOf(parentId), 0 , parentId);
} else {
int parentsParentId = mParentsTreeMap.value(parentId);
parentModelIndex = createIndex(mTreeMap.value(parentsParentId).indexOf(parentId), 0, parentId);
}
Q_ASSERT(parentModelIndex.isValid());
QList<int> &parentTreemap = mTreeMap[parentId];
SensorInfo *sensorInfo = new SensorInfo(hostInfo, sensorName, name, sensorType);
beginInsertRows( parentModelIndex , parentTreemap.size(), parentTreemap.size() );
parentTreemap << mIdCount;
mParentsTreeMap.insert( mIdCount, parentId );
mSensorInfoMap.insert(mIdCount, sensorInfo);
mHostSensorsMap[hostInfo->id()].insert(sensorName, true);
mIdCount++;
endInsertRows();
return mIdCount-1; //NOTE mIdCount is next available number. Se we use it, then increment it, but return the number of the one that we use
}
void SensorBrowserModel::removeSensor(HostInfo *hostInfo, int parentId, const QString &sensorName) {
//sensorName is the full version. e.g. mem/free
QList<int> children = mTreeMap.value(parentId);
int idCount = -1;
int index;
for(index=0; index<children.size(); index++)
if(mSensorInfoMap.contains(children[index])) {
Q_ASSERT(mSensorInfoMap.value(children[index]));
if(mSensorInfoMap.value(children[index])->name() == sensorName) {
idCount = children[index];
break;
}
}
if(idCount == -1) {
kDebug(1215) << "removeSensor called for sensor that doesn't exist in the tree: " << sensorName ;
return;
}
QModelIndex parentModelIndex;
int parentsParentId = -1;
if(hostInfo->id() == parentId) {
parentModelIndex = createIndex(mHostInfoMap.keys().indexOf(parentId), 0 , parentId);
} else {
parentsParentId = mParentsTreeMap.value(parentId);
parentModelIndex = createIndex(mTreeMap.value(parentsParentId).indexOf(parentId), 0, parentId);
}
Q_ASSERT(parentModelIndex.isValid());
QList<int> &parentTreemap = mTreeMap[parentId];
beginRemoveRows( parentModelIndex, index, index );
parentTreemap.removeAll(idCount);
mParentsTreeMap.remove(idCount);
SensorInfo *sensorInfo = mSensorInfoMap.take(idCount);
delete sensorInfo;
mHostSensorsMap[hostInfo->id()].remove(sensorName);
endRemoveRows();
if(parentsParentId != -1)
removeEmptyParentTreeBranches(hostInfo->id(), parentId, parentsParentId);
}
void SensorBrowserModel::removeEmptyParentTreeBranches(int hostId, int id, int parentId) {
if(hostId == id)
return; //We don't want to remove hosts
if(!mTreeMap.value(id).isEmpty()) return; // We should have no children
QModelIndex parentModelIndex;
int parentsParentId = -1;
if(hostId == parentId) {
parentModelIndex = createIndex(mHostInfoMap.keys().indexOf(parentId), 0 , parentId);
} else {
parentsParentId = mParentsTreeMap.value(parentId);
parentModelIndex = createIndex(mTreeMap.value(parentsParentId).indexOf(parentId), 0, parentId);
}
int index = mTreeMap.value(parentId).indexOf(id);
int idCount = mTreeMap.value(parentId).at(index);
QList<int> &parentTreemap = mTreeMap[parentId];
beginRemoveRows( parentModelIndex, index, index );
parentTreemap.removeAll(idCount);
mParentsTreeMap.remove(idCount);
mTreeMap.remove(idCount);
mTreeNodeNames.remove(idCount);
endRemoveRows();
if(parentsParentId != -1)
removeEmptyParentTreeBranches(hostId, parentId, parentsParentId);
}
int SensorBrowserModel::makeTreeBranch(int parentId, const QString &name) {
QList<int> children = mTreeMap.value(parentId);
for(int i=0; i<children.size(); i++)
if(mTreeNodeNames.value(children[i]) == name) return children[i];
QModelIndex parentModelIndex;
if(mHostInfoMap.contains(parentId)) {
parentModelIndex = createIndex(mHostInfoMap.keys().indexOf(parentId), 0 , parentId);
} else {
int parentsParentId = mParentsTreeMap.value(parentId);
parentModelIndex = createIndex(mTreeMap.value(parentsParentId).indexOf(parentId), 0, parentId);
}
Q_ASSERT(parentModelIndex.isValid());
QList<int> &parentTreemap = mTreeMap[parentId];
beginInsertRows( parentModelIndex , parentTreemap.size(), parentTreemap.size() );
parentTreemap << mIdCount;
mParentsTreeMap.insert( mIdCount, parentId );
mTreeMap[mIdCount]; //create with empty qlist
mTreeNodeNames.insert(mIdCount, name);
mIdCount++;
endInsertRows();
return mIdCount-1;
}
void SensorBrowserModel::answerReceived( int hostId, const QList<QByteArray>&answer )
{
/* An answer has the following example format:
cpu/system/idle integer
cpu/system/sys integer
cpu/system/nice integer
cpu/system/user integer
ps table
*/
HostInfo *hostInfo = getHostInfo(hostId);
if(!hostInfo) {
kDebug(1215) << "Invalid hostId " << hostId ;
return;
}
/* We keep a copy of the previous sensor names so that we can detect what sensors have been removed */
QHash<QString,bool> oldSensorNames = mHostSensorsMap.value(hostId);
for ( int i = 0; i < answer.count(); ++i ) {
if ( answer[ i ].isEmpty() )
continue;
QList<QByteArray> words = answer[ i ].split('\t');
if(words.size() != 2) {
kDebug(1215) << "Invalid data " << answer[i];
continue; /* Something wrong with this line of data */
}
QString sensorName = QString::fromUtf8(words[ 0 ]);
QString sensorType = QString::fromUtf8(words[ 1 ]);
oldSensorNames.remove(sensorName); /* This sensor has not been removed */
if ( hasSensor(hostId, sensorName)) {
continue;
}
if(sensorName.isEmpty()) continue;
if(sensorType == "string") continue;
/* The sensor browser can display sensors in a hierarchical order.
* Sensors can be grouped into nodes by seperating the hierarchical
* nodes through slashes in the sensor name. E. g. cpu/system/user is
* the sensor user in the cpu node. There is no limit for the
* depth of nodes. */
int currentNodeId = hostId; //Start from the host branch and work our way down the tree
QStringList absolutePath = sensorName.split( '/' );
for ( int j = 0; j < absolutePath.count()-1; ++j ) {
// Localize the sensor name part by part.
QString name = KSGRD::SensorMgr->translateSensorPath( absolutePath[ j ] );
currentNodeId = makeTreeBranch(currentNodeId, name);
}
QString name = KSGRD::SensorMgr->translateSensorPath( absolutePath[ absolutePath.size()-1] );
makeSensor(hostInfo, currentNodeId, sensorName, name, sensorType);
}
/* Now we have to remove sensors that were not found */
QHashIterator<QString, bool> it( oldSensorNames );
while ( it.hasNext() ) {
it.next();
int currentNodeId = hostId; //Start from the host branch and work our way down the tree
QStringList absolutePath = it.key().split( '/' );
for ( int j = 0; j < absolutePath.count()-1; ++j ) {
// Localize the sensor name part by part.
QString name = KSGRD::SensorMgr->translateSensorPath( absolutePath[ j ] );
currentNodeId = makeTreeBranch(currentNodeId, name);
}
removeSensor(hostInfo, currentNodeId, it.key());
}
emit sensorsAddedToHost( createIndex( mHostInfoMap.keys().indexOf(hostId), 0, hostId ) );
}
//virtual
QModelIndex SensorBrowserModel::parent ( const QModelIndex & index ) const {
if(!index.isValid() || index.column() != 0)
return QModelIndex();
if(mHostInfoMap.contains(index.internalId())) return QModelIndex();
if(!mParentsTreeMap.contains(index.internalId())) {
kDebug(1215) << "Something is wrong with the model. Doesn't contain " << index.internalId();
return QModelIndex();
}
int parentId = mParentsTreeMap.value(index.internalId());
QModelIndex parentModelIndex;
if(mHostInfoMap.contains(parentId)) {
parentModelIndex = createIndex(mHostInfoMap.keys().indexOf(parentId), 0 , parentId);
} else {
int parentsParentId = mParentsTreeMap.value(parentId);
parentModelIndex = createIndex(mTreeMap.value(parentsParentId).indexOf(parentId), 0, parentId);
}
Q_ASSERT(parentModelIndex.isValid());
return parentModelIndex;
}
//virtual
int SensorBrowserModel::rowCount ( const QModelIndex & parent ) const {
if(!parent.isValid()) return mHostInfoMap.size();
if(parent.column() != 0) return 0;
return mTreeMap.value(parent.internalId()).size();
}
//virtual
Qt::ItemFlags SensorBrowserModel::flags ( const QModelIndex & index ) const {
if(!index.isValid()) return 0;
if(mSensorInfoMap.contains(index.internalId())) return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
else return Qt::ItemIsEnabled;
}
SensorBrowserWidget::SensorBrowserWidget( QWidget* parent, KSGRD::SensorManager* sm ) : QWidget( parent )
{
QVBoxLayout *layout = new QVBoxLayout;
m_treeWidget = new SensorBrowserTreeWidget(this, sm);
KFilterProxySearchLine * search_line = new KFilterProxySearchLine(this);
search_line->setProxy(&m_treeWidget->model());
layout->addWidget(search_line);
layout->addWidget(m_treeWidget);
setLayout(layout);
}
SensorBrowserWidget::~SensorBrowserWidget()
{
}
SensorBrowserTreeWidget::SensorBrowserTreeWidget( QWidget* parent, KSGRD::SensorManager* sm ) : QTreeView( parent ), mSensorManager( sm )
{
mSortFilterProxyModel.setSourceModel(&mSensorBrowserModel);
mSortFilterProxyModel.setShowAllChildren(true);
setModel(&mSortFilterProxyModel);
connect( mSensorManager, SIGNAL(update()), &mSensorBrowserModel, SLOT(update()) );
connect( mSensorManager, SIGNAL(hostAdded(KSGRD::SensorAgent*,QString)), &mSensorBrowserModel, SLOT(hostAdded(KSGRD::SensorAgent*,QString)) );
connect( mSensorManager, SIGNAL(hostConnectionLost(QString)), &mSensorBrowserModel, SLOT(hostRemoved(QString)) );
// connect( mSensorManager, SIGNAL(hostAdded(KSGRD::SensorAgent*,QString)), SLOT(updateView()) );
// connect( mSensorManager, SIGNAL(hostConnectionLost(QString)), SLOT(updateView()) );
connect( &mSortFilterProxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(updateView()) );
setDragDropMode(QAbstractItemView::DragOnly);
setUniformRowHeights(true);
//setMinimumWidth( 1 );
retranslateUi();
connect( &mSensorBrowserModel, SIGNAL(sensorsAddedToHost(QModelIndex)), this, SLOT(expandItem(QModelIndex)));
KSGRD::SensorManagerIterator it( mSensorManager );
while ( it.hasNext() ) {
KSGRD::SensorAgent* sensorAgent = it.next().value();
QString hostName = mSensorManager->hostName( sensorAgent );
mSensorBrowserModel.addHost(sensorAgent, hostName);
}
updateView();
}
SensorBrowserTreeWidget::~SensorBrowserTreeWidget()
{
}
void SensorBrowserTreeWidget::updateView()
{
if(mSensorManager->count() == 1) {
setRootIsDecorated( false );
//expand the top level
for(int i = 0; i < mSortFilterProxyModel.rowCount(); i++)
expand(mSortFilterProxyModel.index(i,0));
} else
setRootIsDecorated( true );
}
void SensorBrowserTreeWidget::expandItem(const QModelIndex &model_index)
{
expand(mSortFilterProxyModel.mapFromSource(model_index));
}
void SensorBrowserTreeWidget::retranslateUi() {
this->setToolTip( i18n( "Drag sensors to empty cells of a worksheet "));
this->setWhatsThis( i18n( "The sensor browser lists the connected hosts and the sensors "
"that they provide. Click and drag sensors into drop zones "
"of a worksheet. A display will appear "
"that visualizes the "
"values provided by the sensor. Some sensor displays can "
"display values of multiple sensors. Simply drag other "
"sensors on to the display to add more sensors." ) );
}
void SensorBrowserTreeWidget::changeEvent( QEvent * event )
{
if (event->type() == QEvent::LanguageChange) {
retranslateUi();
mSensorBrowserModel.retranslate();
mSensorBrowserModel.update();
}
QWidget::changeEvent(event);
}
void SensorBrowserTreeWidget::disconnect()
{
QModelIndexList indexlist = selectionModel()->selectedRows();
for(int i=0; i < indexlist.size(); i++)
{
mSensorBrowserModel.disconnectHost(indexlist.value(i).internalId());
}
}
void SensorBrowserTreeWidget::hostReconfigured( const QString& )
{
// TODO: not yet implemented.
}
void SensorBrowserModel::clear() {
qDeleteAll(mHostInfoMap);
mHostInfoMap.clear();
}
void SensorBrowserModel::disconnectHost(uint id)
{
disconnectHost(mHostInfoMap.value(id));
}
void SensorBrowserModel::disconnectHost(const HostInfo *hostInfo)
{
KSGRD::SensorMgr->disengage( hostInfo->sensorAgent() );
}
void SensorBrowserModel::disconnectHost(const QString &hostname)
{
HostInfo* toDelete = findHostInfoByHostName(hostname);
if (toDelete != NULL)
disconnectHost(toDelete);
}
HostInfo* SensorBrowserModel::findHostInfoByHostName(const QString &hostName) const {
HostInfo* toReturn = NULL;
QMapIterator<int, HostInfo*> it( mHostInfoMap );
while (it.hasNext() && toReturn == NULL) {
it.next();
if (it.value()->hostName() == hostName) {
toReturn = it.value();
}
}
return toReturn;
}
void SensorBrowserModel::hostAdded(KSGRD::SensorAgent *sensorAgent, const QString &hostName) {
addHost(sensorAgent,hostName);
update();
}
void SensorBrowserModel::hostRemoved(const QString &hostName) {
HostInfo* toRemove = findHostInfoByHostName(hostName);
if (toRemove != NULL) {
beginResetModel();
int hostId = toRemove->id();
removeAllSensorUnderBranch(toRemove,hostId);
removeEmptyParentTreeBranches(hostId,hostId,hostId);
delete mHostInfoMap.take(hostId);
mTreeMap.take(hostId);
mHostSensorsMap.take(hostId);
endResetModel();
}
update();
}
void SensorBrowserModel::removeAllSensorUnderBranch(HostInfo* hostInfo, int parentId) {
QList<int> children = mTreeMap.value(parentId);
for (int i = 0; i < children.size(); i++) {
if (mTreeMap.contains(children[i])) {
//well our children is not a sensor so remove what is under him
removeAllSensorUnderBranch(hostInfo,children[i]);
} else {
//well this should be a sensor so remove it
if (mSensorInfoMap.contains(children[i])) {
SensorInfo* sensorToRemove = mSensorInfoMap.value(children[i]);
Q_ASSERT(sensorToRemove);
removeSensor(hostInfo, parentId, sensorToRemove->name());
}
}
}
}
void SensorBrowserModel::addHost(KSGRD::SensorAgent *sensorAgent, const QString &hostName)
{
beginInsertRows( QModelIndex() , mHostInfoMap.size(), mHostInfoMap.size() );
HostInfo* hostInfo = new HostInfo( mIdCount, sensorAgent, hostName);
mHostInfoMap.insert(mIdCount, hostInfo);
mTreeMap.insert(mIdCount, QList<int>());
mHostSensorsMap.insert(mIdCount, QHash<QString, bool>());
mIdCount++;
endInsertRows();
hostInfo->sensorAgent()->sendRequest( "monitors", this, mIdCount-1 );
}
void SensorBrowserModel::update()
{
QMapIterator<int, HostInfo*> it( mHostInfoMap );
while ( it.hasNext() ) {
it.next();
KSGRD::SensorAgent* sensorAgent = it.value()->sensorAgent();
int id = it.key();
sensorAgent->sendRequest( "monitors", this, id );
}
}
QMimeData * SensorBrowserModel::mimeData ( const QModelIndexList & indexes ) const { //virtual
QMimeData *mimeData = new QMimeData();
if(indexes.size() != 1) return mimeData;
SensorInfo *sensor = getSensorInfo(indexes[0]);
if(!sensor) return mimeData;
// Create text drag object as
// "<hostname> <sensorname> <sensortype> <sensordescription>".
// Only the description may contain blanks.
Q_ASSERT(sensor);
Q_ASSERT(sensor->hostInfo());
QString mDragText = sensor->hostInfo()->hostName() + ' ' +
sensor->name() + ' ' +
sensor->type()+ ' ' +
sensor->description();
mimeData->setData( "application/x-ksysguard", mDragText.toUtf8() );
return mimeData;
}
SensorInfo::SensorInfo( HostInfo *hostInfo, const QString &name,
const QString &desc, const QString &type )
: mName( name ), mDesc( desc ), mType( type ), mHostInfo( hostInfo )
{
Q_ASSERT(mHostInfo);
}
QString SensorInfo::name() const
{
return mName;
}
QString SensorInfo::type() const
{
return mType;
}
QString SensorInfo::description() const
{
return mDesc;
}
HostInfo *SensorInfo::hostInfo() const
{
return mHostInfo;
}
#include "moc_SensorBrowser.cpp"