Device Emulation (contd.): PortsWindow - refactored all device related widgets into a separate DevicesWidget for better modularity.

This commit is contained in:
Srivats P 2016-03-13 19:51:32 +05:30
parent 941d522451
commit 24a93a5025
7 changed files with 411 additions and 309 deletions

217
client/deviceswidget.cpp Normal file
View File

@ -0,0 +1,217 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#include "deviceswidget.h"
#include "devicegroupdialog.h"
#include "port.h"
#include "portgrouplist.h"
#include <QHeaderView>
DevicesWidget::DevicesWidget(QWidget *parent)
: QWidget(parent), portGroups_(NULL)
{
setupUi(this);
deviceGroupList->setVisible(deviceConfig->isChecked());
deviceList->setVisible(deviceInfo->isChecked());
refresh->setVisible(deviceInfo->isChecked());
deviceGroupList->verticalHeader()->setDefaultSectionSize(
deviceGroupList->verticalHeader()->minimumSectionSize());
deviceList->verticalHeader()->setDefaultSectionSize(
deviceList->verticalHeader()->minimumSectionSize());
// Populate DeviceGroup Context Menu Actions
deviceGroupList->addAction(actionNewDeviceGroup);
deviceGroupList->addAction(actionEditDeviceGroup);
deviceGroupList->addAction(actionDeleteDeviceGroup);
// DevicesWidget's actions is an aggegate of all sub-widget's actions
addActions(deviceGroupList->actions());
}
void DevicesWidget::setPortGroupList(PortGroupList *portGroups)
{
portGroups_ = portGroups;
deviceGroupList->setModel(portGroups_->getDeviceGroupModel());
deviceList->setModel(portGroups_->getDeviceModel());
connect(portGroups_->getDeviceGroupModel(),
SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(updateDeviceViewActions()));
connect(portGroups_->getDeviceGroupModel(),
SIGNAL(rowsRemoved(QModelIndex, int, int)),
SLOT(updateDeviceViewActions()));
connect(deviceGroupList->selectionModel(),
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
SLOT(updateDeviceViewActions()));
connect(deviceGroupList->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
SLOT(updateDeviceViewActions()));
// FIXME: hardcoding
deviceGroupList->resizeColumnToContents(1); // Vlan Count
deviceGroupList->resizeColumnToContents(2); // Device Count
// FIXME: hardcoding
deviceList->resizeColumnToContents(1); // Vlan Id(s)
deviceList->resizeColumnToContents(6); // ARP Info
deviceList->resizeColumnToContents(7); // NDP Info
updateDeviceViewActions();
}
void DevicesWidget::setCurrentPortIndex(const QModelIndex &portIndex)
{
Port *port = NULL;
if (!portGroups_)
return;
// XXX: We assume portIndex corresponds to sourceModel, not proxyModel
if (portGroups_->isPort(portIndex))
port = &portGroups_->port(portIndex);
portGroups_->getDeviceGroupModel()->setPort(port);
portGroups_->getDeviceModel()->setPort(port);
currentPortIndex_ = portIndex;
updateDeviceViewActions();
}
void DevicesWidget::updateDeviceViewActions()
{
QItemSelectionModel *devSel = deviceGroupList->selectionModel();
if (!portGroups_)
return;
// For some reason hasSelection() returns true even if selection size is 0
// so additional check for size introduced
if (devSel->hasSelection() && (devSel->selection().size() > 0)) {
// If more than one non-contiguous ranges selected,
// disable "New" and "Edit"
if (devSel->selection().size() > 1) {
actionNewDeviceGroup->setDisabled(true);
actionEditDeviceGroup->setDisabled(true);
}
else {
actionNewDeviceGroup->setEnabled(true);
// Enable "Edit" only if the single range has a single row
if (devSel->selection().at(0).height() > 1)
actionEditDeviceGroup->setDisabled(true);
else
actionEditDeviceGroup->setEnabled(true);
}
// Delete is always enabled as long as we have a selection
actionDeleteDeviceGroup->setEnabled(true);
}
else {
qDebug("No device selection");
if (portGroups_->isPort(currentPortIndex_))
actionNewDeviceGroup->setEnabled(true);
else
actionNewDeviceGroup->setDisabled(true);
actionEditDeviceGroup->setDisabled(true);
actionDeleteDeviceGroup->setDisabled(true);
}
}
//
// DeviceGroup slots
//
void DevicesWidget::on_deviceInfo_toggled(bool checked)
{
refresh->setVisible(checked);
deviceGroupList->setHidden(checked);
deviceList->setVisible(checked);
}
void DevicesWidget::on_actionNewDeviceGroup_triggered()
{
// In case nothing is selected, insert 1 row at the top
int row = 0, count = 1;
QItemSelection selection = deviceGroupList->selectionModel()->selection();
if (!portGroups_)
return;
// In case we have a single range selected; insert as many rows as
// in the singe selected range before the top of the selected range
if (selection.size() == 1) {
row = selection.at(0).top();
count = selection.at(0).height();
}
portGroups_->getDeviceGroupModel()->insertRows(row, count);
}
void DevicesWidget::on_actionDeleteDeviceGroup_triggered()
{
QModelIndex index;
if (!portGroups_)
return;
if (deviceGroupList->selectionModel()->hasSelection()) {
while(deviceGroupList->selectionModel()->selectedRows().size()) {
index = deviceGroupList->selectionModel()->selectedRows().at(0);
portGroups_->getDeviceGroupModel()->removeRows(index.row(), 1);
}
}
}
void DevicesWidget::on_actionEditDeviceGroup_triggered()
{
QItemSelection selection = deviceGroupList->selectionModel()->selection();
// Ensure we have only one range selected which contains only one row
if ((selection.size() == 1) && (selection.at(0).height() == 1))
on_deviceGroupList_activated(selection.at(0).topLeft());
}
void DevicesWidget::on_deviceGroupList_activated(const QModelIndex &index)
{
if (!index.isValid() || !portGroups_)
return;
DeviceGroupDialog dgd(&portGroups_->port(currentPortIndex_), index.row());
dgd.exec();
}
void DevicesWidget::on_refresh_clicked()
{
if (!portGroups_)
return;
Q_ASSERT(portGroups_->isPort(currentPortIndex_));
QModelIndex curPortGroup = portGroups_->getPortModel()
->parent(currentPortIndex_);
Q_ASSERT(curPortGroup.isValid());
Q_ASSERT(portGroups_->isPortGroup(curPortGroup));
portGroups_->portGroup(curPortGroup)
.getDeviceInfo(portGroups_->port(currentPortIndex_).id());
}

57
client/deviceswidget.h Normal file
View File

@ -0,0 +1,57 @@
/*
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 <http://www.gnu.org/licenses/>
*/
#ifndef _DEVICES_WIDGET_H
#define _DEVICES_WIDGET_H
#include "ui_deviceswidget.h"
#include <QWidget>
class PortGroupList;
class DevicesWidget: public QWidget, private Ui::DevicesWidget
{
Q_OBJECT
public:
DevicesWidget(QWidget *parent = NULL);
void setPortGroupList(PortGroupList *portGroups);
public slots:
void setCurrentPortIndex(const QModelIndex &portIndex);
private slots:
void updateDeviceViewActions();
void on_deviceInfo_toggled(bool checked);
void on_actionNewDeviceGroup_triggered();
void on_actionDeleteDeviceGroup_triggered();
void on_actionEditDeviceGroup_triggered();
void on_deviceGroupList_activated(const QModelIndex &index);
void on_refresh_clicked();
private:
PortGroupList *portGroups_;
QModelIndex currentPortIndex_;
};
#endif

110
client/deviceswidget.ui Normal file
View File

@ -0,0 +1,110 @@
<ui version="4.0" >
<class>DevicesWidget</class>
<widget class="QWidget" name="DevicesWidget" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>675</width>
<height>328</height>
</rect>
</property>
<property name="windowTitle" >
<string>Form</string>
</property>
<layout class="QVBoxLayout" >
<property name="leftMargin" >
<number>0</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>0</number>
</property>
<property name="bottomMargin" >
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QRadioButton" name="deviceConfig" >
<property name="text" >
<string>Configuration</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="deviceInfo" >
<property name="text" >
<string>Information</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>131</width>
<height>23</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="refresh" >
<property name="text" >
<string>Refresh</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="deviceGroupList" >
<property name="contextMenuPolicy" >
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
</property>
<property name="lineWidth" >
<number>1</number>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="deviceList" />
</item>
</layout>
<action name="actionNewDeviceGroup" >
<property name="text" >
<string>New Device Group</string>
</property>
</action>
<action name="actionDeleteDeviceGroup" >
<property name="text" >
<string>Delete Device Group</string>
</property>
</action>
<action name="actionEditDeviceGroup" >
<property name="text" >
<string>Edit Device Group</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -36,6 +36,7 @@ HEADERS += \
devicegroupdialog.h \
devicegroupmodel.h \
devicemodel.h \
deviceswidget.h \
dumpview.h \
hexlineedit.h \
mainwindow.h \
@ -61,6 +62,7 @@ HEADERS += \
FORMS += \
about.ui \
devicegroupdialog.ui \
deviceswidget.ui \
mainwindow.ui \
portconfigdialog.ui \
portstatsfilter.ui \
@ -74,6 +76,7 @@ SOURCES += \
devicegroupdialog.cpp \
devicegroupmodel.cpp \
devicemodel.cpp \
deviceswidget.cpp \
dumpview.cpp \
stream.cpp \
hexlineedit.cpp \

View File

@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portswindow.h"
#include "abstractfileformat.h"
#include "devicegroupdialog.h"
#include "deviceswidget.h"
#include "portconfigdialog.h"
#include "settings.h"
#include "streamconfigdialog.h"
@ -45,7 +45,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
plm = pgl;
setupUi(this);
refresh->setVisible(deviceInfo->isChecked());
devicesWidget->setPortGroupList(plm);
tvPortList->header()->hide();
@ -53,10 +53,6 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvStreamList->verticalHeader()->setDefaultSectionSize(
tvStreamList->verticalHeader()->minimumSectionSize());
deviceGroupList->verticalHeader()->setDefaultSectionSize(
deviceGroupList->verticalHeader()->minimumSectionSize());
deviceList->verticalHeader()->setDefaultSectionSize(
deviceList->verticalHeader()->minimumSectionSize());
// Populate PortList Context Menu Actions
tvPortList->addAction(actionNew_Port_Group);
@ -67,7 +63,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvPortList->addAction(actionExclusive_Control);
tvPortList->addAction(actionPort_Configuration);
// Populate StramList Context Menu Actions
// Populate StreamList Context Menu Actions
tvStreamList->addAction(actionNew_Stream);
tvStreamList->addAction(actionEdit_Stream);
tvStreamList->addAction(actionDuplicate_Stream);
@ -80,12 +76,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvStreamList->addAction(actionOpen_Streams);
tvStreamList->addAction(actionSave_Streams);
// Populate DeviceGroup Context Menu Actions
deviceGroupList->addAction(actionNewDeviceGroup);
deviceGroupList->addAction(actionEditDeviceGroup);
deviceGroupList->addAction(actionDeleteDeviceGroup);
// PortList, StreamList, DeviceGroupList actions combined
// PortList, StreamList, DeviceWidget actions combined
// make this window's actions
addActions(tvPortList->actions());
sep = new QAction(this);
@ -95,11 +86,9 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
sep = new QAction(this);
sep->setSeparator(true);
addAction(sep);
addActions(deviceGroupList->actions());
addActions(devicesWidget->actions());
tvStreamList->setModel(plm->getStreamModel());
deviceGroupList->setModel(plm->getDeviceGroupModel());
deviceList->setModel(plm->getDeviceModel());
// XXX: It would be ideal if we only needed to do the below to
// get the proxy model to do its magic. However, the QModelIndex
@ -130,6 +119,9 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(when_portView_currentChanged(const QModelIndex&,
const QModelIndex&)));
connect(this,
SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)),
devicesWidget, SLOT(setCurrentPortIndex(const QModelIndex&)));
connect(plm->getStreamModel(), SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(updateStreamViewActions()));
@ -146,34 +138,10 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvStreamList->resizeColumnToContents(StreamModel::StreamIcon);
tvStreamList->resizeColumnToContents(StreamModel::StreamStatus);
connect(plm->getDeviceGroupModel(),
SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(updateDeviceViewActions()));
connect(plm->getDeviceGroupModel(),
SIGNAL(rowsRemoved(QModelIndex, int, int)),
SLOT(updateDeviceViewActions()));
connect(deviceGroupList->selectionModel(),
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
SLOT(updateDeviceViewActions()));
connect(deviceGroupList->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
SLOT(updateDeviceViewActions()));
// FIXME: hardcoding
deviceGroupList->resizeColumnToContents(1); // Vlan Count
deviceGroupList->resizeColumnToContents(2); // Device Count
// FIXME: hardcoding
deviceList->resizeColumnToContents(1); // Vlan Id(s)
deviceList->resizeColumnToContents(6); // ARP Info
deviceList->resizeColumnToContents(7); // NDP Info
// Initially we don't have any ports/streams/devices
// - so send signal triggers
when_portView_currentChanged(QModelIndex(), QModelIndex());
updateStreamViewActions();
updateDeviceViewActions();
connect(plm->getStreamModel(),
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
@ -246,7 +214,6 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
{
QModelIndex current = currentIndex;
QModelIndex previous = previousIndex;
Port *port = NULL;
if (proxyPortModel) {
current = proxyPortModel->mapToSource(current);
@ -256,15 +223,9 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
plm->getStreamModel()->setCurrentPortIndex(current);
updatePortViewActions(currentIndex);
updateStreamViewActions();
updateDeviceViewActions();
qDebug("In %s", __FUNCTION__);
if (plm->isPort(current))
port = &(plm->port(current));
plm->getDeviceGroupModel()->setPort(port);
plm->getDeviceModel()->setPort(port);
if (previous.isValid() && plm->isPort(previous))
{
disconnect(&(plm->port(previous)), SIGNAL(portRateChanged(int, int)),
@ -290,6 +251,8 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
SLOT(updatePortRates()));
}
}
emit currentPortChanged(current, previous);
}
void PortsWindow::when_portModel_dataChanged(const QModelIndex& topLeft,
@ -418,51 +381,6 @@ void PortsWindow::updateStreamViewActions()
actionSave_Streams->setEnabled(tvStreamList->model()->rowCount() > 0);
}
void PortsWindow::updateDeviceViewActions()
{
QModelIndex current = tvPortList->currentIndex();
QItemSelectionModel *devSel = deviceGroupList->selectionModel();
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
// For some reason hasSelection() returns true even if selection size is 0
// so additional check for size introduced
if (devSel->hasSelection() && (devSel->selection().size() > 0))
{
// If more than one non-contiguous ranges selected,
// disable "New" and "Edit"
if (devSel->selection().size() > 1)
{
actionNewDeviceGroup->setDisabled(true);
actionEditDeviceGroup->setDisabled(true);
}
else
{
actionNewDeviceGroup->setEnabled(true);
// Enable "Edit" only if the single range has a single row
if (devSel->selection().at(0).height() > 1)
actionEditDeviceGroup->setDisabled(true);
else
actionEditDeviceGroup->setEnabled(true);
}
// Delete is always enabled as long as we have a selection
actionDeleteDeviceGroup->setEnabled(true);
}
else
{
qDebug("No device selection");
if (plm->isPort(current))
actionNewDeviceGroup->setEnabled(true);
else
actionNewDeviceGroup->setDisabled(true);
actionEditDeviceGroup->setDisabled(true);
actionDeleteDeviceGroup->setDisabled(true);
}
}
void PortsWindow::updatePortViewActions(const QModelIndex& currentIndex)
{
QModelIndex current = currentIndex;
@ -897,83 +815,4 @@ _exit:
return;
}
//
// DeviceGroup slots
//
void PortsWindow::on_deviceInfo_toggled(bool checked)
{
refresh->setVisible(checked);
devicesWidget->setCurrentIndex(checked ? 1 : 0);
}
void PortsWindow::on_actionNewDeviceGroup_triggered()
{
// In case nothing is selected, insert 1 row at the top
int row = 0, count = 1;
QItemSelection selection = deviceGroupList->selectionModel()->selection();
// In case we have a single range selected; insert as many rows as
// in the singe selected range before the top of the selected range
if (selection.size() == 1)
{
row = selection.at(0).top();
count = selection.at(0).height();
}
plm->getDeviceGroupModel()->insertRows(row, count);
}
void PortsWindow::on_actionDeleteDeviceGroup_triggered()
{
QModelIndex index;
if (deviceGroupList->selectionModel()->hasSelection())
{
while(deviceGroupList->selectionModel()->selectedRows().size())
{
index = deviceGroupList->selectionModel()->selectedRows().at(0);
plm->getDeviceGroupModel()->removeRows(index.row(), 1);
}
}
}
void PortsWindow::on_actionEditDeviceGroup_triggered()
{
QItemSelection selection = deviceGroupList->selectionModel()->selection();
// Ensure we have only one range selected which contains only one row
if ((selection.size() == 1) && (selection.at(0).height() == 1))
on_deviceGroupList_activated(selection.at(0).topLeft());
}
void PortsWindow::on_deviceGroupList_activated(const QModelIndex &index)
{
if (!index.isValid())
return;
QModelIndex currentPort = tvPortList->currentIndex();
if (proxyPortModel)
currentPort = proxyPortModel->mapToSource(currentPort);
DeviceGroupDialog dgd(&plm->port(currentPort), index.row(), this);
dgd.exec();
}
void PortsWindow::on_refresh_clicked()
{
QModelIndex curPort;
QModelIndex curPortGroup;
curPort = tvPortList->selectionModel()->currentIndex();
if (proxyPortModel)
curPort = proxyPortModel->mapToSource(curPort);
Q_ASSERT(curPort.isValid());
Q_ASSERT(plm->isPort(curPort));
curPortGroup = plm->getPortModel()->parent(curPort);
Q_ASSERT(curPortGroup.isValid());
Q_ASSERT(plm->isPortGroup(curPortGroup));
plm->portGroup(curPortGroup).getDeviceInfo(plm->port(curPort).id());
}

View File

@ -39,6 +39,10 @@ public:
PortsWindow(PortGroupList *pgl, QWidget *parent = 0);
~PortsWindow();
signals:
void currentPortChanged(const QModelIndex &current,
const QModelIndex &previous);
private:
QString lastNewPortGroup;
QAbstractItemDelegate *delegate;
@ -50,7 +54,6 @@ public slots:
private slots:
void updatePortViewActions(const QModelIndex& currentIndex);
void updateStreamViewActions();
void updateDeviceViewActions();
void on_averagePacketsPerSec_editingFinished();
void on_averageBitsPerSec_editingFinished();
@ -81,15 +84,6 @@ private slots:
void on_actionSave_Streams_triggered();
void streamModelDataChanged();
void on_deviceInfo_toggled(bool checked);
void on_actionNewDeviceGroup_triggered();
void on_actionDeleteDeviceGroup_triggered();
void on_actionEditDeviceGroup_triggered();
void on_deviceGroupList_activated(const QModelIndex &index);
void on_refresh_clicked();
};
#endif

View File

@ -5,7 +5,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>710</width>
<width>663</width>
<height>352</height>
</rect>
</property>
@ -178,118 +178,7 @@
</attribute>
<layout class="QVBoxLayout" >
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QRadioButton" name="deviceConfig" >
<property name="text" >
<string>Configuration</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="deviceInfo" >
<property name="text" >
<string>Information</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>131</width>
<height>23</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="refresh" >
<property name="text" >
<string>Refresh</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="devicesWidget" >
<property name="currentIndex" >
<number>0</number>
</property>
<widget class="QWidget" name="deviceConfigPage" >
<layout class="QHBoxLayout" >
<property name="spacing" >
<number>0</number>
</property>
<property name="leftMargin" >
<number>0</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>0</number>
</property>
<property name="bottomMargin" >
<number>0</number>
</property>
<item>
<widget class="QTableView" name="deviceGroupList" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy" >
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
</property>
<property name="lineWidth" >
<number>1</number>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="deviceInfoPage" >
<layout class="QHBoxLayout" >
<property name="spacing" >
<number>0</number>
</property>
<property name="leftMargin" >
<number>0</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>0</number>
</property>
<property name="bottomMargin" >
<number>0</number>
</property>
<item>
<widget class="QTableView" name="deviceList" />
</item>
</layout>
</widget>
</widget>
<widget class="DevicesWidget" native="1" name="devicesWidget" />
</item>
</layout>
</widget>
@ -403,22 +292,15 @@
<string>Duplicate Stream</string>
</property>
</action>
<action name="actionNewDeviceGroup" >
<property name="text" >
<string>New Device Group</string>
</property>
</action>
<action name="actionDeleteDeviceGroup" >
<property name="text" >
<string>Delete Device Group</string>
</property>
</action>
<action name="actionEditDeviceGroup" >
<property name="text" >
<string>Edit Device Group</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>DevicesWidget</class>
<extends>QWidget</extends>
<header>deviceswidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="ostinato.qrc" />
</resources>