From 214c774fc6a4ee5ad1dff1dab4cb331023aa1340 Mon Sep 17 00:00:00 2001 From: Srivats P Date: Wed, 3 Nov 2021 21:08:26 +0530 Subject: [PATCH] Prepare to extract streamsWidget code from portsWindow --- client/streamswidget.cpp | 1096 ++++++++++++++++++++++++++++++++++++++ client/streamswidget.h | 111 ++++ 2 files changed, 1207 insertions(+) create mode 100644 client/streamswidget.cpp create mode 100644 client/streamswidget.h diff --git a/client/streamswidget.cpp b/client/streamswidget.cpp new file mode 100644 index 0000000..307a379 --- /dev/null +++ b/client/streamswidget.cpp @@ -0,0 +1,1096 @@ +/* +Copyright (C) 2010 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 "portswindow.h" + +#include "applymsg.h" +#include "clipboardhelper.h" +#include "deviceswidget.h" +#include "portconfigdialog.h" +#include "settings.h" +#include "streamconfigdialog.h" +#include "streamfileformat.h" +#include "streamlistdelegate.h" + +#include "fileformat.pb.h" + +#include "xqlocale.h" + +#include +#include +#include +#include +#include +#include + +extern ClipboardHelper *clipboardHelper; +extern QMainWindow *mainWindow; + +PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) + : QWidget(parent), proxyPortModel(NULL) +{ + QAction *sep; + + delegate = new StreamListDelegate; + proxyPortModel = new QSortFilterProxyModel(this); + + //slm = new StreamListModel(); + //plm = new PortGroupList(); + plm = pgl; + + Ui::PortsWindow::setupUi(this); + Ui::StreamsWidget::setupUi(streamsWidget); + applyMsg_ = new ApplyMessage(); + devicesWidget->setPortGroupList(plm); + + tvPortList->header()->hide(); + + tvStreamList->setItemDelegate(delegate); + + tvStreamList->verticalHeader()->setDefaultSectionSize( + tvStreamList->verticalHeader()->minimumSectionSize()); + + // Populate PortList Context Menu Actions + tvPortList->addAction(actionNew_Port_Group); + tvPortList->addAction(actionDelete_Port_Group); + tvPortList->addAction(actionConnect_Port_Group); + tvPortList->addAction(actionDisconnect_Port_Group); + + tvPortList->addAction(actionExclusive_Control); + tvPortList->addAction(actionPort_Configuration); + + // Populate StreamList Context Menu Actions + tvStreamList->addAction(actionNew_Stream); + tvStreamList->addAction(actionEdit_Stream); + tvStreamList->addAction(actionDuplicate_Stream); + tvStreamList->addAction(actionDelete_Stream); + + QAction *sep2 = new QAction(this); + sep2->setSeparator(true); + tvStreamList->addAction(sep2); + + tvStreamList->addAction(actionOpen_Streams); + tvStreamList->addAction(actionSave_Streams); + + // PortList, StreamList, DeviceWidget actions combined + // make this window's actions + addActions(tvPortList->actions()); + sep = new QAction(this); + sep->setSeparator(true); + addAction(sep); + addActions(tvStreamList->actions()); + sep = new QAction(this); + sep->setSeparator(true); + addAction(sep); + addActions(devicesWidget->actions()); + + // Add the clipboard actions to the context menu of streamList + // but not to PortsWindow's actions since they are already available + // in the global Edit Menu + sep = new QAction("Clipboard", this); + sep->setSeparator(true); + tvStreamList->insertAction(sep2, sep); + tvStreamList->insertActions(sep2, clipboardHelper->actions()); + + tvStreamList->setModel(plm->getStreamModel()); + + // 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 + // used by the source model and the proxy model are different + // i.e. the row, column, internalId/internalPtr used by both + // will be different. Since our domain objects - PortGroupList, + // PortGroup, Port etc. use these attributes, we need to map the + // proxy's index to the source's index before invoking any domain + // object methods + // TODO: research if we can skip the mapping when the domain + // objects' design is reviewed + if (proxyPortModel) { + proxyPortModel->setSourceModel(plm->getPortModel()); + tvPortList->setModel(proxyPortModel); + } + else + tvPortList->setModel(plm->getPortModel()); + + connect( plm->getPortModel(), + SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(when_portModel_dataChanged(const QModelIndex&, + const QModelIndex&))); + + connect(plm->getPortModel(), SIGNAL(modelReset()), + SLOT(when_portModel_reset())); + + connect( tvPortList->selectionModel(), + 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())); + connect(plm->getStreamModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)), + SLOT(updateStreamViewActions())); + + connect(tvStreamList->selectionModel(), + SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), + SLOT(updateStreamViewActions())); + connect(tvStreamList->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + SLOT(updateStreamViewActions())); + + tvStreamList->resizeColumnToContents(StreamModel::StreamIcon); + tvStreamList->resizeColumnToContents(StreamModel::StreamStatus); + + // Initially we don't have any ports/streams/devices + // - so send signal triggers + when_portView_currentChanged(QModelIndex(), QModelIndex()); + updateStreamViewActions(); + + connect(plm->getStreamModel(), + SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(streamModelDataChanged())); + connect(plm->getStreamModel(), + SIGNAL(modelReset()), + this, SLOT(streamModelDataChanged())); +} + +void PortsWindow::streamModelDataChanged() +{ + QModelIndex current = tvPortList->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + if (plm->isPort(current)) + plm->port(current).recalculateAverageRates(); +} + +PortsWindow::~PortsWindow() +{ + delete delegate; + delete proxyPortModel; + delete applyMsg_; +} + +int PortsWindow::portGroupCount() +{ + return plm->numPortGroups(); +} + +int PortsWindow::reservedPortCount() +{ + int count = 0; + int n = portGroupCount(); + + for (int i = 0; i < n; i++) + count += plm->portGroupByIndex(i).numReservedPorts(); + + return count; +} + +//! Always return true +bool PortsWindow::openSession( + const OstProto::SessionContent *session, + QString & /*error*/) +{ + QProgressDialog progress("Opening Session", NULL, + 0, session->port_groups_size(), mainWindow); + progress.show(); + progress.setEnabled(true); // since parent (mainWindow) is disabled + + plm->removeAllPortGroups(); + + for (int i = 0; i < session->port_groups_size(); i++) { + const OstProto::PortGroupContent &pgc = session->port_groups(i); + PortGroup *pg = new PortGroup(QString::fromStdString( + pgc.server_name()), + quint16(pgc.server_port())); + pg->setConfigAtConnect(&pgc); + plm->addPortGroup(*pg); + progress.setValue(i+1); + } + + return true; +} + +/*! + * Prepare content to be saved for a session + * + * If port reservation is in use, saves only 'my' reserved ports + * + * Returns false, if user cancels op; true, otherwise + */ +bool PortsWindow::saveSession( + OstProto::SessionContent *session, // OUT param + QString & /*error*/, + QProgressDialog *progress) +{ + int n = portGroupCount(); + QString myself; + + if (progress) { + progress->setLabelText("Preparing Ports and PortGroups ..."); + progress->setRange(0, n); + } + + if (reservedPortCount()) + myself = appSettings->value(kUserKey, kUserDefaultValue).toString(); + + for (int i = 0; i < n; i++) + { + PortGroup &pg = plm->portGroupByIndex(i); + OstProto::PortGroupContent *pgc = session->add_port_groups(); + + pgc->set_server_name(pg.serverName().toStdString()); + pgc->set_server_port(pg.serverPort()); + + for (int j = 0; j < pg.numPorts(); j++) + { + if (myself != pg.mPorts.at(j)->userName()) + continue; + + OstProto::PortContent *pc = pgc->add_ports(); + OstProto::Port *p = pc->mutable_port_config(); + + // XXX: We save the entire OstProto::Port even though some + // fields may be ephemeral; while opening we use only relevant + // fields + pg.mPorts.at(j)->protoDataCopyInto(p); + + for (int k = 0; k < pg.mPorts.at(j)->numStreams(); k++) + { + OstProto::Stream *s = pc->add_streams(); + pg.mPorts.at(j)->streamByIndex(k)->protoDataCopyInto(*s); + } + + for (int k = 0; k < pg.mPorts.at(j)->numDeviceGroups(); k++) + { + OstProto::DeviceGroup *dg = pc->add_device_groups(); + dg->CopyFrom(*(pg.mPorts.at(j)->deviceGroupByIndex(k))); + } + } + + if (progress) { + if (progress->wasCanceled()) + return false; + progress->setValue(i); + } + if (i % 2 == 0) + qApp->processEvents(); + } + + return true; +} + +void PortsWindow::clearCurrentSelection() +{ + tvPortList->selectionModel()->clearCurrentIndex(); + tvPortList->clearSelection(); +} + +void PortsWindow::showMyReservedPortsOnly(bool enabled) +{ + if (!proxyPortModel) + return; + + if (enabled) { + QString rx = "Port Group|\\[" + + QRegExp::escape(appSettings->value(kUserKey, + kUserDefaultValue).toString()) + + "\\]"; + qDebug("%s: regexp: <%s>", __FUNCTION__, qPrintable(rx)); + proxyPortModel->setFilterRegExp(QRegExp(rx)); + } + else + proxyPortModel->setFilterRegExp(QRegExp("")); +} + +void PortsWindow::on_tvStreamList_activated(const QModelIndex & index) +{ + if (!index.isValid()) + { + qDebug("%s: invalid index", __FUNCTION__); + return; + } + + qDebug("stream list activated\n"); + + Port &curPort = plm->port(proxyPortModel ? + proxyPortModel->mapToSource(tvPortList->currentIndex()) : + tvPortList->currentIndex()); + + QList streams; + streams.append(curPort.mutableStreamByIndex(index.row(), false)); + + StreamConfigDialog scd(streams, curPort, this); + if (scd.exec() == QDialog::Accepted) { + curPort.recalculateAverageRates(); + curPort.setLocalConfigChanged(true); + } +} + +void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, + const QModelIndex& previousIndex) +{ + QModelIndex current = currentIndex; + QModelIndex previous = previousIndex; + + if (proxyPortModel) { + current = proxyPortModel->mapToSource(current); + previous = proxyPortModel->mapToSource(previous); + } + + plm->getStreamModel()->setCurrentPortIndex(current); + updatePortViewActions(currentIndex); + updateStreamViewActions(); + + qDebug("In %s", __FUNCTION__); + + if (previous.isValid() && plm->isPort(previous)) + { + disconnect(&(plm->port(previous)), SIGNAL(portRateChanged(int, int)), + this, SLOT(updatePortRates())); + disconnect(&(plm->port(previous)), + SIGNAL(localConfigChanged(int, int, bool)), + this, + SLOT(updateApplyHint(int, int, bool))); + } + + if (!current.isValid()) + { + qDebug("setting stacked widget to welcome page"); + swDetail->setCurrentIndex(0); // welcome page + } + else + { + if (plm->isPortGroup(current)) + { + swDetail->setCurrentIndex(1); // portGroup detail page + } + else if (plm->isPort(current)) + { + swDetail->setCurrentIndex(2); // port detail page + updatePortRates(); + connect(&(plm->port(current)), SIGNAL(portRateChanged(int, int)), + SLOT(updatePortRates())); + connect(&(plm->port(current)), + SIGNAL(localConfigChanged(int, int, bool)), + SLOT(updateApplyHint(int, int, bool))); + if (plm->port(current).isDirty()) + updateApplyHint(plm->port(current).portGroupId(), + plm->port(current).id(), true); + else if (plm->port(current).numStreams()) + applyHint->setText("Click " + "to transmit packets"); + else + applyHint->setText(""); + } + } + + emit currentPortChanged(current, previous); +} + +void PortsWindow::when_portModel_dataChanged(const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + qDebug("In %s %d:(%d, %d) - %d:(%d, %d)", __FUNCTION__, + topLeft.parent().isValid(), topLeft.row(), topLeft.column(), + bottomRight.parent().isValid(), bottomRight.row(), bottomRight.column()); + + if (!topLeft.isValid() || !bottomRight.isValid()) + return; + + if (topLeft.parent() != bottomRight.parent()) + return; + + // If a port has changed, expand the port group + if (topLeft.parent().isValid()) + tvPortList->expand(proxyPortModel ? + proxyPortModel->mapFromSource(topLeft.parent()) : + topLeft.parent()); + +#if 0 // not sure why the >= <= operators are not overloaded in QModelIndex + if ((tvPortList->currentIndex() >= topLeft) && + (tvPortList->currentIndex() <= bottomRight)) +#endif + if (((topLeft < tvPortList->currentIndex()) || + (topLeft == tvPortList->currentIndex())) && + (((tvPortList->currentIndex() < bottomRight)) || + (tvPortList->currentIndex() == bottomRight))) + { + // Update UI to reflect potential change in exclusive mode, + // transmit mode et al + when_portView_currentChanged(tvPortList->currentIndex(), + tvPortList->currentIndex()); + } +} + +void PortsWindow::when_portModel_reset() +{ + when_portView_currentChanged(QModelIndex(), tvPortList->currentIndex()); +} + +void PortsWindow::on_startTx_clicked() +{ + QModelIndex current = tvPortList->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + Q_ASSERT(plm->isPort(current)); + + QModelIndex curPortGroup = plm->getPortModel()->parent(current); + Q_ASSERT(curPortGroup.isValid()); + Q_ASSERT(plm->isPortGroup(curPortGroup)); + + QList portList({plm->port(current).id()}); + plm->portGroup(curPortGroup).startTx(&portList); +} + +void PortsWindow::on_stopTx_clicked() +{ + QModelIndex current = tvPortList->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + Q_ASSERT(plm->isPort(current)); + + QModelIndex curPortGroup = plm->getPortModel()->parent(current); + Q_ASSERT(curPortGroup.isValid()); + Q_ASSERT(plm->isPortGroup(curPortGroup)); + + QList portList({plm->port(current).id()}); + plm->portGroup(curPortGroup).stopTx(&portList); +} + +void PortsWindow::on_averagePacketsPerSec_editingFinished() +{ + QModelIndex current = tvPortList->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + Q_ASSERT(plm->isPort(current)); + + bool isOk; + double pps = XLocale().toDouble(averagePacketsPerSec->text(), &isOk); + + plm->port(current).setAveragePacketRate(pps); +} + +void PortsWindow::on_averageBitsPerSec_editingFinished() +{ + QModelIndex current = tvPortList->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + Q_ASSERT(plm->isPort(current)); + + bool isOk; + double bps = XLocale().toDouble(averageBitsPerSec->text(), &isOk); + + plm->port(current).setAverageBitRate(bps); +} + +void PortsWindow::updatePortRates() +{ + QModelIndex current = tvPortList->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + if (!current.isValid()) + return; + + if (!plm->isPort(current)) + return; + + averagePacketsPerSec->setText(QString("%L1") + .arg(plm->port(current).averagePacketRate(), 0, 'f', 4)); + averageBitsPerSec->setText(QString("%L1") + .arg(plm->port(current).averageBitRate(), 0, 'f', 0)); +} + +void PortsWindow::updateStreamViewActions() +{ + QModelIndex current = tvPortList->currentIndex(); + + 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 (tvStreamList->selectionModel()->hasSelection() && + (tvStreamList->selectionModel()->selection().size() > 0)) + { + qDebug("Has selection %d", + tvStreamList->selectionModel()->selection().size()); + + // If more than one non-contiguous ranges selected, + // disable "New" and "Edit" + if (tvStreamList->selectionModel()->selection().size() > 1) + { + actionNew_Stream->setDisabled(true); + actionEdit_Stream->setDisabled(true); + } + else + { + actionNew_Stream->setEnabled(true); + actionEdit_Stream->setEnabled(true); + } + + // Duplicate/Delete are always enabled as long as we have a selection + actionDuplicate_Stream->setEnabled(true); + actionDelete_Stream->setEnabled(true); + } + else + { + qDebug("No selection"); + if (plm->isPort(current)) + actionNew_Stream->setEnabled(true); + else + actionNew_Stream->setDisabled(true); + actionEdit_Stream->setDisabled(true); + actionDuplicate_Stream->setDisabled(true); + actionDelete_Stream->setDisabled(true); + } + actionOpen_Streams->setEnabled(plm->isPort(current)); + actionSave_Streams->setEnabled(tvStreamList->model()->rowCount() > 0); + + startTx->setEnabled(tvStreamList->model()->rowCount() > 0); + stopTx->setEnabled(tvStreamList->model()->rowCount() > 0); +} + +void PortsWindow::updateApplyHint(int /*portGroupId*/, int /*portId*/, + bool configChanged) +{ + if (configChanged) + applyHint->setText("Configuration has changed - " + "click Apply " + "to activate the changes"); + else if (tvStreamList->model()->rowCount() > 0) + applyHint->setText("Configuration activated - " + "click " + "to transmit packets"); + else + applyHint->setText("Configuration activated"); +} + +void PortsWindow::updatePortViewActions(const QModelIndex& currentIndex) +{ + QModelIndex current = currentIndex; + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + if (!current.isValid()) + { + qDebug("current is now invalid"); + actionDelete_Port_Group->setDisabled(true); + actionConnect_Port_Group->setDisabled(true); + actionDisconnect_Port_Group->setDisabled(true); + + actionExclusive_Control->setDisabled(true); + actionPort_Configuration->setDisabled(true); + + goto _EXIT; + } + + qDebug("currentChanged %p", (void*)current.internalId()); + + if (plm->isPortGroup(current)) + { + actionDelete_Port_Group->setEnabled(true); + + actionExclusive_Control->setDisabled(true); + actionPort_Configuration->setDisabled(true); + + switch(plm->portGroup(current).state()) + { + case QAbstractSocket::UnconnectedState: + case QAbstractSocket::ClosingState: + qDebug("state = unconnected|closing"); + actionConnect_Port_Group->setEnabled(true); + actionDisconnect_Port_Group->setDisabled(true); + break; + + case QAbstractSocket::HostLookupState: + case QAbstractSocket::ConnectingState: + case QAbstractSocket::ConnectedState: + qDebug("state = lookup|connecting|connected"); + actionConnect_Port_Group->setDisabled(true); + actionDisconnect_Port_Group->setEnabled(true); + break; + + + case QAbstractSocket::BoundState: + case QAbstractSocket::ListeningState: + default: + // FIXME(LOW): indicate error + qDebug("unexpected state"); + break; + } + } + else if (plm->isPort(current)) + { + actionDelete_Port_Group->setDisabled(true); + actionConnect_Port_Group->setDisabled(true); + actionDisconnect_Port_Group->setDisabled(true); + + actionExclusive_Control->setEnabled(true); + if (plm->port(current).hasExclusiveControl()) + actionExclusive_Control->setChecked(true); + else + actionExclusive_Control->setChecked(false); + actionPort_Configuration->setEnabled(true); + } + +_EXIT: + return; +} + +void PortsWindow::on_pbApply_clicked() +{ + QModelIndex curPort; + QModelIndex curPortGroup; + + curPort = tvPortList->selectionModel()->currentIndex(); + if (proxyPortModel) + curPort = proxyPortModel->mapToSource(curPort); + if (!curPort.isValid()) + { + qDebug("%s: curPort is invalid", __FUNCTION__); + goto _exit; + } + + if (!plm->isPort(curPort)) + { + qDebug("%s: curPort is not a port", __FUNCTION__); + goto _exit; + } + + if (plm->port(curPort).getStats().state().is_transmit_on()) + { + QMessageBox::information(0, "Configuration Change", + "Please stop transmit on the port before applying any changes"); + goto _exit; + } + + curPortGroup = plm->getPortModel()->parent(curPort); + if (!curPortGroup.isValid()) + { + qDebug("%s: curPortGroup is invalid", __FUNCTION__); + goto _exit; + } + if (!plm->isPortGroup(curPortGroup)) + { + qDebug("%s: curPortGroup is not a portGroup", __FUNCTION__); + goto _exit; + } + + disconnect(applyMsg_); + connect(&(plm->portGroup(curPortGroup)), SIGNAL(applyFinished()), + applyMsg_, SLOT(hide())); + applyMsg_->show(); + + // FIXME(HI): shd this be a signal? + //portGroup.when_configApply(port); + // FIXME(MED): mixing port id and index!!! + plm->portGroup(curPortGroup).when_configApply(plm->port(curPort).id()); + +_exit: + return; + +#if 0 + // TODO (LOW): This block is for testing only + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (current.isValid()) + qDebug("current = %llx", current.internalId()); + else + qDebug("current is invalid"); +#endif +} + +void PortsWindow::on_actionNew_Port_Group_triggered() +{ + bool ok; + QString text = QInputDialog::getText(this, + "Add Port Group", "Port Group Address (HostName[:Port])", + QLineEdit::Normal, lastNewPortGroup, &ok); + + if (ok) + { + QStringList addr = text.split(":"); + quint16 port = DEFAULT_SERVER_PORT; + + if (addr.size() > 2) { // IPv6 Address + // IPv6 addresses with port number SHOULD be specified as + // [2001:db8::1]:80 (RFC5952 Sec6) to avoid ambiguity due to ':' + addr = text.split("]:"); + if (addr.size() > 1) + port = addr[1].toUShort(); + } + else if (addr.size() == 2) // Hostname/IPv4 + Port specified + port = addr[1].toUShort(); + + // Play nice and remove square brackets irrespective of addr type + addr[0].remove(QChar('[')); + addr[0].remove(QChar(']')); + + PortGroup *pg = new PortGroup(addr[0], port); + plm->addPortGroup(*pg); + lastNewPortGroup = text; + } +} + +void PortsWindow::on_actionDelete_Port_Group_triggered() +{ + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + if (current.isValid()) + plm->removePortGroup(plm->portGroup(current)); +} + +void PortsWindow::on_actionConnect_Port_Group_triggered() +{ + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + if (current.isValid()) + plm->portGroup(current).connectToHost(); +} + +void PortsWindow::on_actionDisconnect_Port_Group_triggered() +{ + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + if (current.isValid()) + plm->portGroup(current).disconnectFromHost(); +} + +void PortsWindow::on_actionExclusive_Control_triggered(bool checked) +{ + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + if (plm->isPort(current)) + { + OstProto::Port config; + + config.set_is_exclusive_control(checked); + plm->portGroup(current.parent()).modifyPort(current.row(), config); + } +} + +void PortsWindow::on_actionPort_Configuration_triggered() +{ + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + if (!plm->isPort(current)) + return; + + Port &port = plm->port(current); + OstProto::Port config; + // XXX: we don't call Port::protoDataCopyInto() to get config b'coz + // we want only the modifiable fields populated to send to Drone + // TODO: extend Port::protoDataCopyInto() to accept an optional param + // which says copy only modifiable fields + //plm->port(current).protoDataCopyInto(&config); + config.set_transmit_mode(port.transmitMode()); + config.set_is_tracking_stream_stats(port.trackStreamStats()); + config.set_is_exclusive_control(port.hasExclusiveControl()); + config.set_user_name(port.userName().toStdString()); + + PortConfigDialog dialog(config, port.getStats().state(), this); + + if (dialog.exec() == QDialog::Accepted) + plm->portGroup(current.parent()).modifyPort(current.row(), config); +} + +void PortsWindow::on_actionNew_Stream_triggered() +{ + qDebug("New Stream Action"); + + QItemSelectionModel* selectionModel = tvStreamList->selectionModel(); + if (selectionModel->selection().size() > 1) { + qDebug("%s: Unexpected selection size %d, can't add", __FUNCTION__, + selectionModel->selection().size()); + return; + } + + // In case nothing is selected, insert 1 row at the end + StreamModel *streamModel = plm->getStreamModel(); + int row = streamModel->rowCount(), count = 1; + + // 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 (selectionModel->selection().size() == 1) + { + row = selectionModel->selection().at(0).top(); + count = selectionModel->selection().at(0).height(); + } + + Port &curPort = plm->port(proxyPortModel ? + proxyPortModel->mapToSource(tvPortList->currentIndex()) : + tvPortList->currentIndex()); + + QList streams; + for (int i = 0; i < count; i++) + streams.append(new Stream); + + StreamConfigDialog scd(streams, curPort, this); + scd.setWindowTitle(tr("Add Stream")); + if (scd.exec() == QDialog::Accepted) + streamModel->insert(row, streams); +} + +void PortsWindow::on_actionEdit_Stream_triggered() +{ + qDebug("Edit Stream Action"); + + QItemSelectionModel* streamModel = tvStreamList->selectionModel(); + if (!streamModel->hasSelection()) + return; + + Port &curPort = plm->port(proxyPortModel ? + proxyPortModel->mapToSource(tvPortList->currentIndex()) : + tvPortList->currentIndex()); + + QList streams; + foreach(QModelIndex index, streamModel->selectedRows()) + streams.append(curPort.mutableStreamByIndex(index.row(), false)); + + StreamConfigDialog scd(streams, curPort, this); + if (scd.exec() == QDialog::Accepted) { + curPort.recalculateAverageRates(); + curPort.setLocalConfigChanged(true); + } +} + +void PortsWindow::on_actionDuplicate_Stream_triggered() +{ + QItemSelectionModel* model = tvStreamList->selectionModel(); + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + qDebug("Duplicate Stream Action"); + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + if (model->hasSelection()) + { + bool isOk; + int count = QInputDialog::getInt(this, "Duplicate Streams", + "Count", 1, 1, 9999, 1, &isOk); + + if (!isOk) + return; + + QList list; + foreach(QModelIndex index, model->selectedRows()) + list.append(index.row()); + plm->port(current).duplicateStreams(list, count); + } + else + qDebug("No selection"); +} + +void PortsWindow::on_actionDelete_Stream_triggered() +{ + qDebug("Delete Stream Action"); + + QModelIndex index; + + if (tvStreamList->selectionModel()->hasSelection()) + { + qDebug("SelectedIndexes %d", + tvStreamList->selectionModel()->selectedRows().size()); + while(tvStreamList->selectionModel()->selectedRows().size()) + { + index = tvStreamList->selectionModel()->selectedRows().at(0); + plm->getStreamModel()->removeRows(index.row(), 1); + } + } + else + qDebug("No selection"); +} + +void PortsWindow::on_actionOpen_Streams_triggered() +{ + qDebug("Open Streams Action"); + + QStringList fileTypes = StreamFileFormat::supportedFileTypes( + StreamFileFormat::kOpenFile); + QString fileType; + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + static QString dirName; + QString fileName; + QString errorStr; + bool append = true; + bool ret; + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + Q_ASSERT(plm->isPort(current)); + + if (fileTypes.size()) + fileType = fileTypes.at(0); + + fileName = QFileDialog::getOpenFileName(this, tr("Open Streams"), + dirName, fileTypes.join(";;"), &fileType); + if (fileName.isEmpty()) + goto _exit; + + if (tvStreamList->model()->rowCount()) + { + QMessageBox msgBox(QMessageBox::Question, qApp->applicationName(), + tr("Append to existing streams? Or overwrite?"), + QMessageBox::NoButton, this); + QPushButton *appendBtn = msgBox.addButton(tr("Append"), + QMessageBox::ActionRole); + QPushButton *overwriteBtn = msgBox.addButton(tr("Overwrite"), + QMessageBox::ActionRole); + QPushButton *cancelBtn = msgBox.addButton(QMessageBox::Cancel); + + msgBox.exec(); + + if (msgBox.clickedButton() == cancelBtn) + goto _exit; + else if (msgBox.clickedButton() == appendBtn) + append = true; + else if (msgBox.clickedButton() == overwriteBtn) + append = false; + else + Q_ASSERT(false); + } + + ret = plm->port(current).openStreams(fileName, append, errorStr); + if (!ret || !errorStr.isEmpty()) + { + QMessageBox msgBox(this); + QStringList str = errorStr.split("\n\n\n\n"); + + msgBox.setIcon(ret ? QMessageBox::Warning : QMessageBox::Critical); + msgBox.setWindowTitle(qApp->applicationName()); + msgBox.setText(str.at(0)); + if (str.size() > 1) + msgBox.setDetailedText(str.at(1)); + msgBox.setStandardButtons(QMessageBox::Ok); + + msgBox.exec(); + } + dirName = QFileInfo(fileName).absolutePath(); + +_exit: + return; +} + +void PortsWindow::on_actionSave_Streams_triggered() +{ + qDebug("Save Streams Action"); + + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + static QString fileName; + QStringList fileTypes = StreamFileFormat::supportedFileTypes( + StreamFileFormat::kSaveFile); + QString fileType; + QString errorStr; + QFileDialog::Options options; + + if (proxyPortModel) + current = proxyPortModel->mapToSource(current); + + // On Mac OS with Native Dialog, getSaveFileName() ignores fileType + // which we need +#if defined(Q_OS_MAC) + options |= QFileDialog::DontUseNativeDialog; +#endif + + if (fileTypes.size()) + fileType = fileTypes.at(0); + + Q_ASSERT(plm->isPort(current)); + +_retry: + fileName = QFileDialog::getSaveFileName(this, tr("Save Streams"), + fileName, fileTypes.join(";;"), &fileType, options); + if (fileName.isEmpty()) + goto _exit; + + if (QFileInfo(fileName).suffix().isEmpty()) { + QString fileExt = fileType.section(QRegExp("[\\*\\)]"), 1, 1); + qDebug("Adding extension '%s' to '%s'", + qPrintable(fileExt), qPrintable(fileName)); + fileName.append(fileExt); + if (QFileInfo(fileName).exists()) { + if (QMessageBox::warning(this, tr("Overwrite File?"), + QString("The file \"%1\" already exists.\n\n" + "Do you wish to overwrite it?") + .arg(QFileInfo(fileName).fileName()), + QMessageBox::Yes|QMessageBox::No, + QMessageBox::No) != QMessageBox::Yes) + goto _retry; + } + } + + fileType = fileType.remove(QRegExp("\\(.*\\)")).trimmed(); + if (!fileType.startsWith("Ostinato") + && !fileType.startsWith("Python")) + { + if (QMessageBox::warning(this, tr("Ostinato"), + QString("You have chosen to save in %1 format. All stream " + "attributes may not be saved in this format.\n\n" + "It is recommended to save in native Ostinato format.\n\n" + "Continue to save in %2 format?").arg(fileType).arg(fileType), + QMessageBox::Yes|QMessageBox::No, + QMessageBox::No) != QMessageBox::Yes) + goto _retry; + } + + // TODO: all or selected? + + if (!plm->port(current).saveStreams(fileName, fileType, errorStr)) + QMessageBox::critical(this, qApp->applicationName(), errorStr); + else if (!errorStr.isEmpty()) + QMessageBox::warning(this, qApp->applicationName(), errorStr); + + fileName = QFileInfo(fileName).absolutePath(); +_exit: + return; +} + + diff --git a/client/streamswidget.h b/client/streamswidget.h new file mode 100644 index 0000000..92aa85c --- /dev/null +++ b/client/streamswidget.h @@ -0,0 +1,111 @@ +/* +Copyright (C) 2010 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 _PORTS_WINDOW_H +#define _PORTS_WINDOW_H + +#include +#include +#include "ui_portswindow.h" +#include "ui_streamswidget.h" +#include "portgrouplist.h" + +class ApplyMessage; +class QAbstractItemDelegate; +class QProgressDialog; +class QSortFilterProxyModel; + +namespace OstProto { + class SessionContent; +} + +class PortsWindow : public QWidget, private Ui::PortsWindow, private Ui::StreamsWidget +{ + Q_OBJECT + + //QAbstractItemModel *slm; // stream list model + PortGroupList *plm; + +public: + PortsWindow(PortGroupList *pgl, QWidget *parent = 0); + ~PortsWindow(); + + int portGroupCount(); + int reservedPortCount(); + + bool openSession(const OstProto::SessionContent *session, + QString &error); + bool saveSession(OstProto::SessionContent *session, + QString &error, + QProgressDialog *progress = NULL); + +signals: + void currentPortChanged(const QModelIndex ¤t, + const QModelIndex &previous); + +private: + QString lastNewPortGroup; + QAbstractItemDelegate *delegate; + QSortFilterProxyModel *proxyPortModel; + ApplyMessage *applyMsg_; + +public slots: + void clearCurrentSelection(); + void showMyReservedPortsOnly(bool enabled); + +private slots: + void updateApplyHint(int portGroupId, int portId, bool configChanged); + void updatePortViewActions(const QModelIndex& currentIndex); + void updateStreamViewActions(); + + void on_startTx_clicked(); + void on_stopTx_clicked(); + void on_averagePacketsPerSec_editingFinished(); + void on_averageBitsPerSec_editingFinished(); + void updatePortRates(); + void on_tvStreamList_activated(const QModelIndex & index); + void when_portView_currentChanged(const QModelIndex& currentIndex, + const QModelIndex& previousIndex); + void when_portModel_dataChanged(const QModelIndex& topLeft, + const QModelIndex& bottomRight); + void when_portModel_reset(); + + void on_pbApply_clicked(); + + void on_actionNew_Port_Group_triggered(); + void on_actionDelete_Port_Group_triggered(); + void on_actionConnect_Port_Group_triggered(); + void on_actionDisconnect_Port_Group_triggered(); + + void on_actionExclusive_Control_triggered(bool checked); + void on_actionPort_Configuration_triggered(); + + void on_actionNew_Stream_triggered(); + void on_actionEdit_Stream_triggered(); + void on_actionDuplicate_Stream_triggered(); + void on_actionDelete_Stream_triggered(); + + void on_actionOpen_Streams_triggered(); + void on_actionSave_Streams_triggered(); + + void streamModelDataChanged(); +}; + +#endif +