/*
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 "devicegroupmodel.h"
#include "port.h"
#include "emulproto.pb.h"
#include "uint128.h"
#include
#include
const QLatin1String kDeviceGroupsMimeType(
"application/vnd.ostinato.devicegroups");
enum {
kName,
kVlanCount,
kDeviceCount, // Across all vlans
kIp,
kIp4Address,
kIp6Address,
kFieldCount
};
static QStringList columns_ = QStringList()
<< "Name"
<< "Vlans"
<< "Devices"
<< "IP Stack"
<< "IPv4 Address"
<< "IPv6 Address";
DeviceGroupModel::DeviceGroupModel(QObject *parent)
: QAbstractTableModel(parent)
{
port_ = NULL;
}
int DeviceGroupModel::rowCount(const QModelIndex &parent) const
{
if (!port_ || parent.isValid())
return 0;
return port_->numDeviceGroups();
}
int DeviceGroupModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return columns_.size();
}
QVariant DeviceGroupModel::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 DeviceGroupModel::data(const QModelIndex &index, int role) const
{
if (!port_ || !index.isValid())
return QVariant();
int dgIdx = index.row();
int field = index.column();
Q_ASSERT(dgIdx < port_->numDeviceGroups());
Q_ASSERT(field < kFieldCount);
const OstProto::DeviceGroup *devGrp = port_->deviceGroupByIndex(dgIdx);
Q_ASSERT(devGrp);
switch (field) {
case kName:
switch (role) {
case Qt::DisplayRole:
return QString::fromStdString(devGrp->core().name());
default:
break;
}
return QVariant();
case kVlanCount:
switch (role) {
case Qt::DisplayRole:
if (int v = vlanCount(devGrp))
return v;
return QString("None");
case Qt::TextAlignmentRole:
return static_cast(Qt::AlignRight|Qt::AlignVCenter);
default:
break;
}
return QVariant();
case kDeviceCount:
switch (role) {
case Qt::DisplayRole:
return qMax(vlanCount(devGrp), 1)*devGrp->device_count();
case Qt::TextAlignmentRole:
return static_cast(Qt::AlignRight|Qt::AlignVCenter);
default:
break;
}
return QVariant();
case kIp:
switch (role) {
case Qt::DisplayRole:
if (devGrp->HasExtension(OstEmul::ip4))
if (devGrp->HasExtension(OstEmul::ip6))
return QString("Dual Stack");
else
return QString("IPv4");
else if (devGrp->HasExtension(OstEmul::ip6))
return QString("IPv6");
else
return QString("None");
default:
break;
}
return QVariant();
case kIp4Address:
switch (role) {
case Qt::DisplayRole:
if (devGrp->HasExtension(OstEmul::ip4))
return QHostAddress(
devGrp->GetExtension(OstEmul::ip4)
.address()).toString();
else
return QString("--");
default:
break;
}
return QVariant();
case kIp6Address:
switch (role) {
case Qt::DisplayRole:
if (devGrp->HasExtension(OstEmul::ip6)) {
OstEmul::Ip6Address ip = devGrp->GetExtension(
OstEmul::ip6).address();
return QHostAddress(
UInt128(ip.hi(), ip.lo()).toArray())
.toString();
}
else
return QString("--");
default:
break;
}
return QVariant();
default:
Q_ASSERT(false); // unreachable!
break;
}
qWarning("%s: Unsupported field #%d", __FUNCTION__, field);
return QVariant();
}
bool DeviceGroupModel::setData(
const QModelIndex & /*index*/,
const QVariant & /*value*/,
int /*role*/)
{
if (!port_)
return false;
// TODO; when implementing also implement flags() to
// return ItemIsEditable
return false;
}
QStringList DeviceGroupModel::mimeTypes() const
{
return QStringList() << kDeviceGroupsMimeType;
}
QMimeData* DeviceGroupModel::mimeData(const QModelIndexList &indexes) const
{
using ::google::protobuf::uint8;
if (indexes.isEmpty())
return nullptr;
// indexes may include multiple columns for a row - but we are only
// interested in rows 'coz we have a single data for all columns
// XXX: use QMap instead of QSet to keep rows in sorted order
QMap rows;
foreach(QModelIndex index, indexes)
rows.insert(index.row(), index.row());
OstProto::DeviceGroupConfigList dgList;
dgList.mutable_port_id()->set_id(port_->id());
foreach(int row, rows) {
OstProto::DeviceGroup *devGrp = dgList.add_device_group();
devGrp->CopyFrom(*port_->deviceGroupByIndex(row));
}
QByteArray data;
data.resize(dgList.ByteSize());
dgList.SerializeWithCachedSizesToArray((uint8*)data.data());
//qDebug("copy %s", dgList.DebugString().c_str());
//TODO: copy DebugString as text/plain?
QMimeData *mimeData = new QMimeData();
mimeData->setData(kDeviceGroupsMimeType, data);
return mimeData; // XXX: caller is expected to take ownership and free!
}
bool DeviceGroupModel::dropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int /*column*/,
const QModelIndex &parent)
{
if (!data)
return false;
if (!data->hasFormat(kDeviceGroupsMimeType))
return false;
if (action != Qt::CopyAction)
return false;
OstProto::DeviceGroupConfigList dgList;
QByteArray ba(data->data(kDeviceGroupsMimeType));
dgList.ParseFromArray((void*)ba.constData(), ba.size());
//qDebug("paste %s", dgList.DebugString().c_str());
if ((row < 0) || (row > rowCount(parent)))
row = rowCount(parent);
// Delete rows that we are going to overwrite
int c = 0, count = dgList.device_group_size();
if (row < rowCount(parent))
removeRows(row, qMin(rowCount() - row, count));
beginInsertRows(parent, row, row+count-1);
for (int i = 0; i < count; i++) {
if (port_->newDeviceGroupAt(row+i, &dgList.device_group(i)))
c++;
}
endInsertRows();
if (c != count) {
qWarning("failed to copy rows in DeviceGroupModel at row %d; "
"requested = %d, actual = %d", row, count, c);
return false;
}
return true;
}
bool DeviceGroupModel::insertRows(
int row,
int count,
const QModelIndex &parent)
{
int c = 0;
Q_ASSERT(!parent.isValid());
beginInsertRows(parent, row, row+count-1);
for (int i = 0; i < count; i++) {
if (port_->newDeviceGroupAt(row))
c++;
}
endInsertRows();
if (c != count) {
qWarning("failed to insert rows in DeviceGroupModel at row %d; "
"requested = %d, actual = %d", row, count, c);
return false;
}
return true;
}
bool DeviceGroupModel::removeRows(
int row,
int count,
const QModelIndex &parent)
{
int c = 0;
Q_ASSERT(!parent.isValid());
beginRemoveRows(parent, row, row+count-1);
for (int i = 0; i < count; i++) {
if (port_->deleteDeviceGroupAt(row))
c++;
}
endRemoveRows();
if (c != count) {
qWarning("failed to delete rows in DeviceGroupModel at row %d; "
"requested = %d, actual = %d", row, count, c);
return false;
}
return true;
}
void DeviceGroupModel::setPort(Port *port)
{
beginResetModel();
port_ = port;
endResetModel();
}
//
// ---------------------- Private Methods -----------------------
//
int DeviceGroupModel::vlanCount(const OstProto::DeviceGroup *deviceGroup) const
{
if (!deviceGroup->has_encap()
|| !deviceGroup->encap().HasExtension(OstEmul::vlan))
return 0;
OstEmul::VlanEmulation vlan = deviceGroup->encap()
.GetExtension(OstEmul::vlan);
int numTags = vlan.stack_size();
int count = 1;
for (int i = 0; i < numTags; i++)
count *= vlan.stack(i).count();
return count;
}