Skip to content

Commit

Permalink
iio-widgets/MultiDataStrategy: Implement container for multiple Data …
Browse files Browse the repository at this point in the history
…Strategies

The MultiDataStrategy is a Data Strategy that can be added to an
IIOWidget. In this strategy, the user can add multiple DSs that
will be aggregated in the MultiDataStrategy.

Signed-off-by: Andrei-Fabian-Pop <Andreifabian.Pop@analog.com>
  • Loading branch information
Andrei-Fabian-Pop committed Jul 18, 2024
1 parent d7c2e6b commit a215340
Show file tree
Hide file tree
Showing 4 changed files with 310 additions and 48 deletions.
103 changes: 103 additions & 0 deletions iio-widgets/include/iio-widgets/datastrategy/multidatastrategy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2024 Analog Devices Inc.
*
* This file is part of Scopy
* (see https://www.github.com/analogdevicesinc/scopy).
*
* This program 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 <https://www.gnu.org/licenses/>.
*/

#ifndef SCOPY_MULTIDATASTRATEGY_H
#define SCOPY_MULTIDATASTRATEGY_H

#include <QWidget>
#include <QList>
#include <QSet>
#include <iio.h>
#include "datastrategy/datastrategyinterface.h"
#include "scopy-iio-widgets_export.h"

namespace scopy {
class SCOPY_IIO_WIDGETS_EXPORT MultiDataStrategy : public QWidget, public DataStrategyInterface
{
Q_OBJECT
Q_INTERFACES(scopy::DataStrategyInterface)
public:
/**
* @brief MultiDataStrategy This is a class for incorporating multiple Data Strategies in the
* same IIOWidget. All results that are used but the IIOWidget class are aggregated, the ones
* that are related to the status of the operations are not.
* @param strategies A QList of pointers to DataStrategyInterfaces. More Data Strategies can be
* added after creation.
* @param parent QWidget*
*/
explicit MultiDataStrategy(QList<DataStrategyInterface *> strategies, QWidget *parent = nullptr);

/**
* @brief addDataStrategy Add and connect a new Data Strategy.
* @param ds
*/
void addDataStrategy(DataStrategyInterface *ds);

/**
* @brief removeDataStrategy Remove and disconnect a Data Strategy.
* @param ds
*/
void removeDataStrategy(DataStrategyInterface *ds);

/**
* @brief removeDataStrategyByIndex Remove and disconnect a Data Strategy based on its index.
* The index can be found from the dataStrategies() function.
* @param index
*/
void removeDataStrategyByIndex(int index);

/**
* @brief dataStrategies Return a list of all Data Strategies, useful when wanting to find a
* specific index.
* @return QList of DataStrategyInterface pointers.
*/
QList<DataStrategyInterface *> dataStrategies();

QString data() override;
QString optionalData() override;

public Q_SLOTS:
int write(QString data) override;
QPair<QString, QString> read() override;

void writeAsync(QString data) override;
void readAsync() override;

void receiveSingleRead(QString data, QString optionalData);

Q_SIGNALS:
void sendData(QString data, QString dataOptions) override;
void aboutToWrite(QString oldData, QString newData) override;
void emitStatus(QDateTime timestamp, QString oldData, QString newData, int returnCode, bool isReadOp) override;

private:
QList<DataStrategyInterface *> m_dataStrategies;
QList<QPair<QString, QString>> m_receivedData;

QSet<QObject *> m_expectedSignals;
QSet<QObject *> m_receivedSignals;

QString m_data;
QString m_optionalData;
int m_returnCode;
};
} // namespace scopy

#endif // SCOPY_MULTIDATASTRATEGY_H
142 changes: 142 additions & 0 deletions iio-widgets/src/datastrategy/multidatastrategy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#include "datastrategy/multidatastrategy.h"

Q_LOGGING_CATEGORY(CAT_MULTI_DATA_STRATEGY, "MultiDataStrategy")
using namespace scopy;

MultiDataStrategy::MultiDataStrategy(QList<DataStrategyInterface *> strategies, QWidget *parent)
: QWidget(parent)
, m_dataStrategies(strategies)
, m_data("")
, m_optionalData("")
, m_returnCode(0)
{
for(DataStrategyInterface *ds : m_dataStrategies) {
QWidget *widgetDS = dynamic_cast<QWidget *>(ds);
if(!widgetDS) {
qWarning(CAT_MULTI_DATA_STRATEGY) << "Data strategy not valid.";
continue;
}

m_expectedSignals.insert(widgetDS);
connect(widgetDS, SIGNAL(sendData(QString, QString)), this, SLOT(receiveSingleRead(QString, QString)));

// Forward signals from all DS. Caution: these signals are not aggregated.
connect(widgetDS, SIGNAL(emitStatus(QDateTime, QString, QString, int, bool)), this,
SIGNAL(emitStatus(QDateTime, QString, QString, int, bool)));
connect(widgetDS, SIGNAL(aboutToWrite(QString, QString)), this, SIGNAL(aboutToWrite(QString, QString)));
}
}

void MultiDataStrategy::addDataStrategy(DataStrategyInterface *ds)
{
m_dataStrategies.append(ds);
m_expectedSignals.insert(dynamic_cast<QObject *>(ds));

connect(dynamic_cast<QWidget *>(ds), SIGNAL(sendData(QString, QString)), this,
SLOT(receiveSingleRead(QString, QString)));

// Forward signals from all DS. Caution: these signals are not aggregated.
connect(dynamic_cast<QWidget *>(ds), SIGNAL(emitStatus(QDateTime, QString, QString, int, bool)), this,
SIGNAL(emitStatus(QDateTime, QString, QString, int, bool)));
connect(dynamic_cast<QWidget *>(ds), SIGNAL(aboutToWrite(QString, QString)), this,
SIGNAL(aboutToWrite(QString, QString)));
}

void MultiDataStrategy::removeDataStrategy(DataStrategyInterface *ds)
{
m_dataStrategies.removeOne(ds);
m_expectedSignals.remove(dynamic_cast<QObject *>(ds));
disconnect(dynamic_cast<QWidget *>(ds), SIGNAL(sendData(QString, QString)), this,
SLOT(receiveSingleRead(QString, QString)));
disconnect(dynamic_cast<QWidget *>(ds), SIGNAL(emitStatus(QDateTime, QString, QString, int, bool)), this,
SIGNAL(emitStatus(QDateTime, QString, QString, int, bool)));
disconnect(dynamic_cast<QWidget *>(ds), SIGNAL(aboutToWrite(QString, QString)), this,
SIGNAL(aboutToWrite(QString, QString)));
}

void MultiDataStrategy::removeDataStrategyByIndex(int index)
{
QWidget *ds = dynamic_cast<QWidget *>(m_dataStrategies.at(index));
m_expectedSignals.remove(dynamic_cast<QObject *>(m_dataStrategies.at(index)));
m_dataStrategies.removeAt(index);
disconnect(ds, SIGNAL(sendData(QString, QString)), this, SLOT(receiveSingleRead(QString, QString)));
disconnect(ds, SIGNAL(emitStatus(QDateTime, QString, QString, int, bool)), this,
SIGNAL(emitStatus(QDateTime, QString, QString, int, bool)));
disconnect(ds, SIGNAL(aboutToWrite(QString, QString)), this, SIGNAL(aboutToWrite(QString, QString)));
}

QList<DataStrategyInterface *> MultiDataStrategy::dataStrategies() { return m_dataStrategies; }

QString MultiDataStrategy::data() { return m_data; }

QString MultiDataStrategy::optionalData() { return m_optionalData; }

int MultiDataStrategy::write(QString data)
{
int res = 0;
for(DataStrategyInterface *ds : m_dataStrategies) {
int currentRet = ds->write(data);
if(currentRet < 0) {
res = currentRet;
}
}

return res;
}

QPair<QString, QString> MultiDataStrategy::read()
{
QPair<QString, QString> initialRead;
for(int i = 0; i < m_dataStrategies.size(); ++i) {
if(i == 0) {
initialRead = m_dataStrategies[i]->read();
} else {
if(initialRead != m_dataStrategies[i]->read()) {
initialRead = {"DIFFERENT RESULTS", ""};
}
}
}

return initialRead;
}

void MultiDataStrategy::writeAsync(QString data)
{
for(DataStrategyInterface *ds : m_dataStrategies) {
ds->writeAsync(data);
}
}

void MultiDataStrategy::readAsync()
{
for(DataStrategyInterface *ds : m_dataStrategies) {
ds->readAsync();
}
}

void MultiDataStrategy::receiveSingleRead(QString data, QString optionalData)
{
QObject *sender = QObject::sender();
m_receivedSignals.insert(sender);
m_receivedData.append({data, optionalData});

if(m_receivedSignals == m_expectedSignals) {
qDebug(CAT_MULTI_DATA_STRATEGY) << "Received all signals!";
bool ok = true;
for(QPair<QString, QString> ss : m_receivedData) {
if(ss.first != data || ss.second != optionalData) {
ok = false;
Q_EMIT sendData("DIFFERENT RESULTS", "");
}
}
if(ok) {
m_data = data;
m_optionalData = optionalData;
Q_EMIT sendData(data, optionalData);
}
m_receivedData.clear();
m_receivedSignals.clear();
}
}

#include "moc_multidatastrategy.cpp"
3 changes: 0 additions & 3 deletions iio-widgets/src/iiowidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,6 @@ IIOWidget::IIOWidget(GuiStrategyInterface *uiStrategy, DataStrategyInterface *da
// intercept the sendData from dataStrategy to collect information
connect(dataStrategyWidget, SIGNAL(sendData(QString, QString)), this, SLOT(storeReadInfo(QString, QString)));

connect(dynamic_cast<QWidget *>(m_dataStrategy), SIGNAL(sendData(QString, QString)), this,
SLOT(storeReadInfo(QString, QString)));

m_dataStrategy->readAsync();
}

Expand Down
110 changes: 65 additions & 45 deletions plugins/test/src/testtool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
#include "plotaxis.h"
#include "plotwidget.h"
#include "spinbox_a.hpp"

#include <iio-widgets/iiowidgetbuilder.h>
#include <iio-widgets/guistrategy/editableguistrategy.h>
#include <iio-widgets/guistrategy/comboguistrategy.h>
#include <iio-widgets/datastrategy/channelattrdatastrategy.h>
#include <iio-widgets/datastrategy/multidatastrategy.h>

#include <QButtonGroup>
#include <QDebug>
Expand Down Expand Up @@ -169,8 +174,8 @@ TestTool::TestTool(QWidget *parent)
QWidget *wch0 = createMenu(tool);
QLabel *wch1 = new QLabel("Channel1Label");

// auto *wch2 = iioWidgetsSettingsHelper();
auto *wch2 = new QLabel("Channel2Label");
auto *wch2 = iioWidgetsSettingsHelper();
// auto *wch2 = new QLabel("Channel2Label");
// wch2->layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Preferred, QSizePolicy::MinimumExpanding));

tool->rightStack()->add("ch0", wch0);
Expand Down Expand Up @@ -312,21 +317,35 @@ QWidget *TestTool::iioWidgetsSettingsHelper()
// struct iio_context *context = iio_create_context_from_uri("ip:127.0.0.1");
// struct iio_device *device = iio_context_find_device(context, "ad74413r");
// struct iio_channel *attrChannel = iio_device_find_channel(device, "voltage7", false);
auto *wch2Scroll = new QScrollArea(this);

wch2Scroll->setWidgetResizable(true);
wch2Scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
wch2Scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
auto wch2 = new QWidget(this);
auto channelAttrMenu =
new MenuCollapseSection("Channel attributes", scopy::MenuCollapseSection::MHCW_ARROW, wch2);
channelAttrMenu->contentLayout()->setContentsMargins(0, 0, 0, 0);
channelAttrMenu->contentLayout()->setSpacing(10);
channelAttrMenu->setObjectName("this object");
wch2Scroll->setWidget(wch2);
wch2->setLayout(new QVBoxLayout(wch2));
wch2->layout()->setContentsMargins(0, 0, 0, 0);
// auto *wch2Scroll = new QScrollArea(this);

// wch2Scroll->setWidgetResizable(true);
// wch2Scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// wch2Scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// auto wch2 = new QWidget(this);
// auto channelAttrMenu = new QWidget(this);
// channelAttrMenu->setLayout(new QVBoxLayout(channelAttrMenu));
// channelAttrMenu->setObjectName("this object");
// wch2Scroll->setWidget(wch2);
// wch2->setLayout(new QVBoxLayout(wch2));
// wch2->layout()->setContentsMargins(0, 0, 0, 0);
// wch2->layout()->setSpacing(0);
QList<IIOWidget *> attrWidgets = {}; // IIOWidgetFactory::buildAllAttrsForChannel(attrChannel);
// auto uis = new ComboAttrUi({.channel = attrChannel, .data = "sampling_frequency", .iioDataOptions =
// "sampling_frequency_available"}, this); auto ds1 = new ChannelAttrDataStrategy({.channel = attrChannel, .data
// = "sampling_frequency", .iioDataOptions = "sampling_frequency_available"}, this); ds1->setObjectName("ds1");
// auto ds2 = new ChannelAttrDataStrategy({.channel = iio_device_find_channel(device, "voltage6", false), .data
// = "sampling_frequency", .iioDataOptions = "sampling_frequency_available"}, this); ds2->setObjectName("ds2");
// auto ds3 = new ChannelAttrDataStrategy({.channel = iio_device_find_channel(device, "voltage5", false), .data
// = "sampling_frequency", .iioDataOptions = "sampling_frequency_available"}, this); ds3->setObjectName("ds3");
// auto mds = new MultiDataStrategy({ds1, ds2}, this);
// mds->addDataStrategy(ds3);

// auto iiow = new IIOWidget(uis, mds, this);
// attrWidgets.append(iiow);

// auto mw = new IIOMultiWidget(uis, {ds1, ds2, ds3}, this);
// attrWidgets.append(mw);

// attrWidgets.append(attrFactory->buildSingle(
// AttrFactory::AFH::ExternalSave | AttrFactory::AFH::SwitchUi | AttrFactory::AFH::AttrData,
Expand All @@ -347,37 +366,38 @@ QWidget *TestTool::iioWidgetsSettingsHelper()
//| IIOWidgetFactory::FileDemoData,
// {.data = "The Office Cast"}));

StyleHelper::IIOWidgetElement(channelAttrMenu, "IIOWidget");
// StyleHelper::IIOWidgetElement(channelAttrMenu, "IIOWidget");
for(auto item : attrWidgets) {
if(item) {
auto container = new QFrame(channelAttrMenu);
auto header = new QWidget(channelAttrMenu);
auto title = new QLabel(item->getRecipe().data.replace("_", " ").toUpper(), header);
auto errorBox = new ErrorBox(header);
header->setLayout(new QHBoxLayout(header));
header->layout()->setContentsMargins(0, 0, 0, 0);
header->layout()->addWidget(title);
header->layout()->addWidget(errorBox);
errorBox->changeColor(ErrorBox::AvailableColors::Green);
connect(item, &IIOWidget::currentStateChanged, this,
[errorBox](IIOWidget::State state, QString explanation) {
if(state == IIOWidget::Busy) {
errorBox->changeColor(ErrorBox::Yellow);
} else if(state == IIOWidget::Correct) {
errorBox->changeColor(ErrorBox::Green);
} else if(state == IIOWidget::Error) {
errorBox->changeColor(ErrorBox::Red);
}
errorBox->setToolTip(explanation);
});

container->setLayout(new QVBoxLayout(container));
container->layout()->setContentsMargins(0, 0, 0, 0);
container->layout()->addWidget(header);
container->layout()->addWidget(item);

StyleHelper::IIOWidget(container, "iioWidgetElement");
channelAttrMenu->contentLayout()->addWidget(container);
// auto container = new QFrame(channelAttrMenu);
// auto header = new QWidget(channelAttrMenu);
// auto title = new QLabel(item->getRecipe().data.replace("_", " ").toUpper(), header);
// auto title = new QLabel("okokk");
// auto errorBox = new ErrorBox(header);
// header->setLayout(new QHBoxLayout(header));
// header->layout()->setContentsMargins(0, 0, 0, 0);
// header->layout()->addWidget(title);
// header->layout()->addWidget(errorBox);
// errorBox->changeColor(ErrorBox::AvailableColors::Green);
// connect(item, &IIOWidget::currentStateChanged, this,
// [errorBox](IIOWidget::State state, QString explanation) {
// if(state == IIOWidget::Busy) {
// errorBox->changeColor(ErrorBox::Yellow);
// } else if(state == IIOWidget::Correct) {
// errorBox->changeColor(ErrorBox::Green);
// } else if(state == IIOWidget::Error) {
// errorBox->changeColor(ErrorBox::Red);
// }
// errorBox->setToolTip(explanation);
// });

// container->setLayout(new QVBoxLayout(container));
// container->layout()->setContentsMargins(0, 0, 0, 0);
// container->layout()->addWidget(header);
// container->layout()->addWidget(item);

// StyleHelper::IIOWidget(container, "iioWidgetElement");
channelAttrMenu->layout()->addWidget(item);
}
}
wch2->layout()->addWidget(channelAttrMenu);
Expand Down

0 comments on commit a215340

Please sign in to comment.