From 853802b99728465b57f4389c624cfa9499fabe27 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Mon, 14 Mar 2016 20:11:40 +0530 Subject: [PATCH] Device Emulation (contd.): Implemented display of ARP Cache Device Detail; styled "drillable" fields in DeviceModel suitably --- client/arpstatusmodel.cpp | 151 ++++++++++++++++++++++++++++++++++++++ client/arpstatusmodel.h | 55 ++++++++++++++ client/devicemodel.cpp | 55 +++++++++++++- client/devicemodel.h | 6 ++ client/deviceswidget.cpp | 20 +++++ client/deviceswidget.h | 2 + client/deviceswidget.ui | 19 ++++- client/ostinato.pro | 2 + 8 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 client/arpstatusmodel.cpp create mode 100644 client/arpstatusmodel.h diff --git a/client/arpstatusmodel.cpp b/client/arpstatusmodel.cpp new file mode 100644 index 0000000..cd6af5d --- /dev/null +++ b/client/arpstatusmodel.cpp @@ -0,0 +1,151 @@ +/* +Copyright (C) 2016 Srivats P. + +This file is part of "Ostinato" + +This 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 3 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, see +*/ + +#include "arpstatusmodel.h" + +#include "port.h" + +#include "emulproto.pb.h" + +#include + +enum { + kIp4Address, + kMacAddress, + kStatus, + kFieldCount +}; + +static QStringList columns_ = QStringList() + << "IPv4 Address" + << "Mac Address" + << "Status"; + +ArpStatusModel::ArpStatusModel(QObject *parent) + : QAbstractTableModel(parent) +{ + port_ = NULL; + deviceIndex_ = -1; + neighbors_ = NULL; +} + +int ArpStatusModel::rowCount(const QModelIndex &parent) const +{ + if (!port_ || deviceIndex_ < 0 || parent.isValid()) + return 0; + + return port_->numArp(deviceIndex_); +} + +int ArpStatusModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return columns_.size(); +} + +QVariant ArpStatusModel::headerData( + int section, + Qt::Orientation orientation, + int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + switch (orientation) { + case Qt::Horizontal: + return columns_[section]; + case Qt::Vertical: + return QString("%1").arg(section + 1); + default: + Q_ASSERT(false); // Unreachable + } + + return QVariant(); +} + +QVariant ArpStatusModel::data(const QModelIndex &index, int role) const +{ + QString str; + + if (!port_ || deviceIndex_ < 0 || !index.isValid()) + return QVariant(); + + int arpIdx = index.row(); + int field = index.column(); + + Q_ASSERT(arpIdx < port_->numArp(deviceIndex_)); + Q_ASSERT(field < kFieldCount); + + const OstEmul::ArpEntry &arp = neighbors_->arp(arpIdx); + + switch (field) { + case kIp4Address: + switch (role) { + case Qt::DisplayRole: + return QHostAddress(arp.ip4()).toString(); + default: + break; + } + return QVariant(); + + case kMacAddress: + switch (role) { + case Qt::DisplayRole: + return QString("%1").arg(arp.mac(), 6*2, 16, QChar('0')) + .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:") + .toUpper(); + default: + break; + } + return QVariant(); + + case kStatus: + switch (role) { + case Qt::DisplayRole: + return arp.mac() ? + QString("Resolved") : QString("Failed"); + default: + break; + } + return QVariant(); + + default: + Q_ASSERT(false); // unreachable! + break; + } + + qWarning("%s: Unsupported field #%d", __FUNCTION__, field); + return QVariant(); +} + +void ArpStatusModel::setDeviceIndex(Port *port, int deviceIndex) +{ + port_ = port; + deviceIndex_ = deviceIndex; + if (port_) + neighbors_ = port_->deviceNeighbors(deviceIndex); + reset(); +} + +void ArpStatusModel::updateArpStatus() +{ + reset(); +} diff --git a/client/arpstatusmodel.h b/client/arpstatusmodel.h new file mode 100644 index 0000000..64debaa --- /dev/null +++ b/client/arpstatusmodel.h @@ -0,0 +1,55 @@ +/* +Copyright (C) 2016 Srivats P. + +This file is part of "Ostinato" + +This 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 3 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, see +*/ + +#ifndef _ARP_STATUS_MODEL_H +#define _ARP_STATUS_MODEL_H + +#include + +class Port; +namespace OstEmul { + class DeviceNeighborList; +} + +class ArpStatusModel: public QAbstractTableModel +{ + Q_OBJECT +public: + ArpStatusModel(QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const; + + void setDeviceIndex(Port *port, int deviceIndex); + +public slots: + void updateArpStatus(); + +private: + Port *port_; + int deviceIndex_; + const OstEmul::DeviceNeighborList *neighbors_; +}; + +#endif + diff --git a/client/devicemodel.cpp b/client/devicemodel.cpp index c99faf7..a261b4a 100644 --- a/client/devicemodel.cpp +++ b/client/devicemodel.cpp @@ -19,11 +19,15 @@ along with this program. If not, see #include "devicemodel.h" +#include "arpstatusmodel.h" #include "port.h" #include "emulproto.pb.h" #include "uint128.h" +#include +#include +#include #include enum { @@ -52,6 +56,7 @@ DeviceModel::DeviceModel(QObject *parent) : QAbstractTableModel(parent) { port_ = NULL; + arpStatusModel_ = new ArpStatusModel(this); } int DeviceModel::rowCount(const QModelIndex &parent) const @@ -195,10 +200,15 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const case kArpInfo: switch (role) { case Qt::DisplayRole: - return QString("%1/%2") + if (dev->has_ip4_prefix_length()) + return QString("%1/%2") .arg(port_->numArpResolved(devIdx)) .arg(port_->numArp(devIdx)); + else + return QString("--"); default: + if (dev->has_ip4_prefix_length()) + return drillableStyle(role); break; } return QVariant(); @@ -206,10 +216,15 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const case kNdpInfo: switch (role) { case Qt::DisplayRole: - return QString("%1/%2") + if (dev->has_ip6_prefix_length()) + return QString("%1/%2") .arg(port_->numNdpResolved(devIdx)) .arg(port_->numNdp(devIdx)); + else + return QString("--"); default: + if (dev->has_ip6_prefix_length()) + return drillableStyle(role); break; } return QVariant(); @@ -220,6 +235,8 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const } qWarning("%s: Unsupported field #%d", __FUNCTION__, field); + +_exit: return QVariant(); } @@ -231,7 +248,41 @@ void DeviceModel::setPort(Port *port) reset(); } +QAbstractItemModel* DeviceModel::detailModel(const QModelIndex &index) +{ + if (!index.isValid()) + return NULL; + + switch(index.column()) { + case kArpInfo: + arpStatusModel_->setDeviceIndex(port_, index.row()); + return arpStatusModel_; + case kNdpInfo: + default: + return NULL; + } +} + void DeviceModel::updateDeviceList() { reset(); } + +// Style roles for drillable fields +QVariant DeviceModel::drillableStyle(int role) const +{ + QFont f; + switch (role) { + case Qt::ToolTipRole: + return QString("Click for details ..."); + case Qt::ForegroundRole: + return QBrush(QColor(Qt::blue)); + case Qt::FontRole: + f.setUnderline(true); + return f; + default: + break; + } + return QVariant(); +} + diff --git a/client/devicemodel.h b/client/devicemodel.h index 5e161ff..78637d5 100644 --- a/client/devicemodel.h +++ b/client/devicemodel.h @@ -22,7 +22,9 @@ along with this program. If not, see #include +class ArpStatusModel; class Port; + class DeviceModel: public QAbstractTableModel { Q_OBJECT @@ -37,12 +39,16 @@ public: QVariant data(const QModelIndex &index, int role) const; void setPort(Port *port); + QAbstractItemModel* detailModel(const QModelIndex &index); public slots: void updateDeviceList(); private: + QVariant drillableStyle(int role) const; + Port *port_; + ArpStatusModel *arpStatusModel_; }; #endif diff --git a/client/deviceswidget.cpp b/client/deviceswidget.cpp index 9aa5bf5..789a150 100644 --- a/client/deviceswidget.cpp +++ b/client/deviceswidget.cpp @@ -32,11 +32,14 @@ DevicesWidget::DevicesWidget(QWidget *parent) deviceGroupList->setVisible(deviceConfig->isChecked()); deviceList->setVisible(deviceInfo->isChecked()); refresh->setVisible(deviceInfo->isChecked()); + deviceDetail->hide(); deviceGroupList->verticalHeader()->setDefaultSectionSize( deviceGroupList->verticalHeader()->minimumSectionSize()); deviceList->verticalHeader()->setDefaultSectionSize( deviceList->verticalHeader()->minimumSectionSize()); + deviceDetail->verticalHeader()->setDefaultSectionSize( + deviceDetail->verticalHeader()->minimumSectionSize()); // Populate DeviceGroup Context Menu Actions deviceGroupList->addAction(actionNewDeviceGroup); @@ -68,6 +71,10 @@ void DevicesWidget::setPortGroupList(PortGroupList *portGroups) SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(updateDeviceViewActions())); + connect(deviceList->selectionModel(), + SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), + SLOT(when_deviceList_currentChanged(const QModelIndex&))); + // FIXME: hardcoding deviceGroupList->resizeColumnToContents(1); // Vlan Count deviceGroupList->resizeColumnToContents(2); // Device Count @@ -147,6 +154,7 @@ void DevicesWidget::on_deviceInfo_toggled(bool checked) refresh->setVisible(checked); deviceGroupList->setHidden(checked); deviceList->setVisible(checked); + deviceDetail->hide(); } void DevicesWidget::on_actionNewDeviceGroup_triggered() @@ -215,3 +223,15 @@ void DevicesWidget::on_refresh_clicked() portGroups_->portGroup(curPortGroup) .getDeviceInfo(portGroups_->port(currentPortIndex_).id()); } + +void DevicesWidget::when_deviceList_currentChanged(const QModelIndex &index) +{ + if (!index.isValid() || !portGroups_) + return; + + QAbstractItemModel *detailModel = portGroups_->getDeviceModel() + ->detailModel(index); + + deviceDetail->setModel(detailModel); + deviceDetail->setVisible(detailModel != NULL); +} diff --git a/client/deviceswidget.h b/client/deviceswidget.h index fb15ef6..8c7626e 100644 --- a/client/deviceswidget.h +++ b/client/deviceswidget.h @@ -48,6 +48,8 @@ private slots: void on_refresh_clicked(); + void when_deviceList_currentChanged(const QModelIndex &index); + private: PortGroupList *portGroups_; QModelIndex currentPortIndex_; diff --git a/client/deviceswidget.ui b/client/deviceswidget.ui index 7753e8f..6ac8adf 100644 --- a/client/deviceswidget.ui +++ b/client/deviceswidget.ui @@ -86,7 +86,24 @@ - + + + + 0 + 1 + + + + QAbstractItemView::SelectRows + + + + + + + QAbstractItemView::SingleSelection + + diff --git a/client/ostinato.pro b/client/ostinato.pro index 7c20029..75f0604 100644 --- a/client/ostinato.pro +++ b/client/ostinato.pro @@ -33,6 +33,7 @@ LIBS += -lprotobuf LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2 RESOURCES += ostinato.qrc HEADERS += \ + arpstatusmodel.h \ devicegroupdialog.h \ devicegroupmodel.h \ devicemodel.h \ @@ -73,6 +74,7 @@ FORMS += \ variablefieldswidget.ui SOURCES += \ + arpstatusmodel.cpp \ devicegroupdialog.cpp \ devicegroupmodel.cpp \ devicemodel.cpp \