/* 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; }