commit 8b1eb2e880d3987d97a5ccac5cbcfde87cfaa5b4 Author: mstanaev Date: Sat Jul 13 16:38:28 2024 +0300 feat: добавить скрипт CI/CD diff --git a/.gitea/workflows/test_sdk.yaml b/.gitea/workflows/test_sdk.yaml new file mode 100644 index 0000000..5d1e440 --- /dev/null +++ b/.gitea/workflows/test_sdk.yaml @@ -0,0 +1,13 @@ +name: Build Example +on: + push: + branches: + - main + +jobs: + build: + runs-on: stateoftheartio/qt6:6.6-mingw-aqt + name: Build + steps: + - name: Build core + run: qmake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1717df7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +build-*/ +.output/ +*.user +*debug/ +*release/ +*DSPFilters/ +*.qtc_clangd/ +*Makefile* +*.qmake.stash +*.gtl +log_* diff --git a/README.md b/README.md new file mode 100644 index 0000000..bf01f81 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# gtl \ No newline at end of file diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/core/core.pro b/core/core.pro new file mode 100644 index 0000000..224b7f8 --- /dev/null +++ b/core/core.pro @@ -0,0 +1,47 @@ +QT -= gui +QT += xml qml + +TEMPLATE = lib +DEFINES += CORE_LIBRARY +TARGET = gtl_core + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + gtl_analog_data.cpp \ + gtl_core.cpp \ + gtl_data_model.cpp \ + gtl_data_model_node.cpp \ + gtl_device.cpp \ + gtl_selection_data_model.cpp \ + gtl_logger.cpp + +HEADERS += \ + core_global.h \ + gtl.h \ + gtl_analog_data.h \ + gtl_core.h \ + gtl_data_model.h \ + gtl_data_model_node.h \ + gtl_device.h \ + gtl_selection_data_model.h \ + gtl_logger.h + +# Default rules for deployment. +unix { + target.path = /usr/lib +} +!isEmpty(target.path): INSTALLS += target + +win32:CONFIG(release, debug|release): DESTDIR = ../.output/release +else:win32:CONFIG(debug, debug|release): DESTDIR = ../.output/debug +else:unix: DESTDIR = ../../.ouput + +INCLUDEPATH += $$PWD/.. +DEPENDPATH += $$PWD/.. + + diff --git a/core/core_global.h b/core/core_global.h new file mode 100644 index 0000000..80d2ea8 --- /dev/null +++ b/core/core_global.h @@ -0,0 +1,12 @@ +#ifndef CORE_GLOBAL_H +#define CORE_GLOBAL_H + +#include + +#if defined(CORE_LIBRARY) +# define CORE_EXPORT Q_DECL_EXPORT +#else +# define CORE_EXPORT Q_DECL_IMPORT +#endif + +#endif // CORE_GLOBAL_H diff --git a/core/gtl.h b/core/gtl.h new file mode 100644 index 0000000..a390e7f --- /dev/null +++ b/core/gtl.h @@ -0,0 +1,13 @@ +#ifndef GTL_H +#define GTL_H + +namespace gtl +{ + enum units + { + unit, + db + }; +} + +#endif // GTL_H \ No newline at end of file diff --git a/core/gtl_analog_data.cpp b/core/gtl_analog_data.cpp new file mode 100644 index 0000000..5870be0 --- /dev/null +++ b/core/gtl_analog_data.cpp @@ -0,0 +1,216 @@ +#include "gtl_analog_data.h" + +#include "gtl_device.h" + +namespace gtl +{ + analog_data::analog_data(data_model_node *parent, bool is_hidden) + :data_model_node(types::analog, parent, is_hidden) + , _color(Qt::black) + { + + } + + analog_data::analog_data(analog_data *parent, bool is_hidden) + :data_model_node(types::analog, parent, is_hidden) + { + if(parent) + _rate = parent->get_rate(); + else + _rate = 100000; + + connect(parent, &analog_data::reference_changed, this, &analog_data::reference_changed); + } + + analog_data::analog_data(qreal rate) + :data_model_node(types::analog, nullptr, true) + { + _rate = rate; + } + + + + void analog_data::get_data(std::back_insert_iterator > data) + { + std::copy(begin(), end(), data); + } + + void analog_data::set_data(iterator begin, iterator end) + { + + if(!_history.empty()) + { + int size = (int)std::distance(begin, end); + + int idx = std::max(0, size - (int)_history.size()); + while(idx != size) + { + int copy_size = std::min(size - idx, (int)_history.size() - _history_ptr); + + std::copy(begin + idx, begin + idx + copy_size, _history.begin() + _history_ptr); + + _history_ptr = (_history_ptr + copy_size) % _history.size(); + idx += copy_size; + } + } + + emit data_changed(); + + for(std::vector::iterator iter_node = _children.begin(); iter_node != _children.end(); iter_node++) + { + if((*iter_node)->type() != data_model_node::analog) + continue; + + static_cast(*iter_node)->set_data(this->begin(), this->end()); + } + + for(auto iter_node : _hidden_children) + { + if(iter_node->type() != data_model_node::analog) + continue; + + static_cast(iter_node)->set_data(this->begin(), this->end()); + } + } + + void analog_data::set_rate(qreal value) + { + if(value != _rate) + { + + foreach(data_model_node* child, _children) + { + if(child->type() == data_model_node::analog) + { + static_cast(child)->set_rate(value); + } + } + _rate = value; + } + } + + void analog_data::set_color(int value) + { + if(value != _color) + { + _color = value; + emit color_changed(); + } + } + + qreal analog_data::get_rate() const + { + return _rate; + } + + void analog_data::lock_device() + { + data_model_node* root_node = root(); + if(root_node == NULL) + return; + if(root_node->type() != data_model_node::device) + return; + + static_cast(root_node)->lock_ai(); + } + + void analog_data::unlock_device() + { + data_model_node* root_node = root(); + if(root_node == NULL) + return; + if(root_node->type() != data_model_node::device) + return; + + static_cast(root_node)->unlock_ai(); + } + + int analog_data::color() const + { + return _color; + } + + qreal analog_data::reference() const + { + if(parent_node()) + { + if(parent_node()->type() == analog) + { + return static_cast(parent_node())->reference(); + } + + } + + return 0; + } + + void analog_data::save(QDomElement &root_element) + { + data_model_node::save(root_element); + + root_element.setAttribute("color", _color); + } + + void analog_data::load(const QDomElement &root_element) + { + data_model_node::load(root_element); + + _color = root_element.attribute("color", "0").toInt(); + } + + void analog_data::get_state(QJsonObject &root) + { + root["color"] = _color; + data_model_node::get_state(root); + } + + analog_data* analog_data::analog_data_root() + { + data_model_node* node = this; + while(node->parent_node()) + { + if(node->parent_node()->type() != analog) + break; + + node = node->parent_node(); + } + + if(node->type() != analog) + return nullptr; + + return static_cast(node); + } + + qreal analog_data::history() const + { + if(_rate == 0) + return 0; + + return _history.size() / _rate; + } + + void analog_data::set_history(qreal value) + { + if(_rate == 0) + return; + + _history.resize(qRound(value*_rate)); + std::fill(_history.begin(), _history.end(), 0); + _history_ptr = 0; + + } + + QJsonArray analog_data::getHistoryArray() + { + QJsonArray array; + for(int i = 0; i < _history.size(); i++) + array.append(_history[(_history_ptr + i)%_history.size()]); + + return array; + } + + bool analog_data::is_checkable() const + { + return true; + } +} diff --git a/core/gtl_analog_data.h b/core/gtl_analog_data.h new file mode 100644 index 0000000..5a49486 --- /dev/null +++ b/core/gtl_analog_data.h @@ -0,0 +1,74 @@ +#ifndef ANALOG_DATA_H +#define ANALOG_DATA_H + +#include +#include + +#include "core_global.h" + +#include "core/gtl_data_model_node.h" + +namespace gtl +{ + class CORE_EXPORT analog_data : public data_model_node, public std::vector + { + Q_OBJECT + Q_PROPERTY(qreal rate READ get_rate WRITE set_rate NOTIFY rate_changed) + Q_PROPERTY(int color READ color WRITE set_color NOTIFY color_changed) + Q_PROPERTY(qreal reference READ reference NOTIFY reference_changed) + Q_PROPERTY(qreal history READ history WRITE set_history) + + public: + analog_data(data_model_node *parent, bool is_hidden = false); + analog_data(analog_data *parent, bool is_hidden = false); + analog_data(qreal rate); + + void get_data(std::back_insert_iterator> data); + virtual void set_data(std::vector::iterator begin, std::vector::iterator end); + + qreal get_rate() const; + + void lock_device(); + void unlock_device(); + + int color() const; + + virtual qreal reference() const; + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + virtual void get_state(QJsonObject& root); + + analog_data* analog_data_root(); + + protected: + qreal _rate; + int _color; + + private: + std::vector _history; + int _history_ptr; + + private: + qreal history() const; + void set_history(qreal value); + + protected: + virtual bool is_checkable() const; + + public slots: + virtual void set_rate(qreal value); + void set_color(int value); + QJsonArray getHistoryArray(); + + signals: + void data_changed(); + void rate_changed(); + void color_changed(); + void reference_changed(); + + }; + +} + +#endif // ANALOG_DATA_H diff --git a/core/gtl_core.cpp b/core/gtl_core.cpp new file mode 100644 index 0000000..5a868c6 --- /dev/null +++ b/core/gtl_core.cpp @@ -0,0 +1,5 @@ +#include "gtl_core.h" + +core::core() +{ +} diff --git a/core/gtl_core.h b/core/gtl_core.h new file mode 100644 index 0000000..12cfd14 --- /dev/null +++ b/core/gtl_core.h @@ -0,0 +1,12 @@ +#ifndef CORE_H +#define CORE_H + +#include "core_global.h" + +class CORE_EXPORT core +{ +public: + core(); +}; + +#endif // CORE_H diff --git a/core/gtl_data_model.cpp b/core/gtl_data_model.cpp new file mode 100644 index 0000000..156b94f --- /dev/null +++ b/core/gtl_data_model.cpp @@ -0,0 +1,296 @@ +#include "gtl_data_model.h" + +namespace gtl +{ + data_model::data_model(QObject *parent) + : QAbstractItemModel(parent) + { + } + + QVariant data_model::headerData(int section, Qt::Orientation orientation, int role) const + { + // FIXME: Implement me! + + return QVariant(); + } + + QModelIndex data_model::index(int row, int column, const QModelIndex &parent) const + { + // FIXME: Implement me! + + if(row >= rowCount(parent)) + return QModelIndex(); + + if(parent.isValid()) + { + return createIndex(row, column, static_cast(parent.internalPointer())->child(row)); + } + + return createIndex(row, column, _devices[row]); + } + + QModelIndex data_model::parent(const QModelIndex &index) const + { + // FIXME: Implement me! + if(!index.isValid()) + return QModelIndex(); + + gtl::data_model_node* node = static_cast(index.internalPointer()); + + if(node->parent_node() == NULL) + { + return QModelIndex(); + } + else if(node->parent_node()->parent_node() == NULL) + { + int row = std::find(_devices.begin(), _devices.end(), node->parent_node()) - _devices.begin(); + return createIndex(row, 0, node->parent_node()); + } + else + { + + return createIndex(index_of(node->parent_node()), 0, node->parent_node()); + } + } + + QModelIndex data_model::index(data_model_node *node) + { + return index(index_of(node), 0, parent(node)); + } + + QModelIndex gtl::data_model::parent(data_model_node* node) + { + QModelIndex parent; + if(node->parent_node()) + { + int row_parent = node->parent_node()->index_of(); + if(row_parent < 0) + row_parent = index_of(node->parent_node()); + + parent = createIndex(row_parent, 0, node->parent_node()); + } + + return parent; + } + + int data_model::rowCount(const QModelIndex &parent) const + { + if (parent.isValid()) + return static_cast(parent.internalPointer())->count(); + + // FIXME: Implement me! + + return (int)_devices.size(); + } + + int data_model::columnCount(const QModelIndex &parent) const + { + // FIXME: Implement me! + + return 1; + } + + QVariant data_model::data(const QModelIndex &index, int role) const + { + if (!index.isValid()) + return QVariant(); + + // FIXME: Implement me! + + if(role == Qt::DisplayRole) + { + return static_cast(index.internalPointer())->name(); + } + + return QVariant(); + } + + bool data_model::setData(const QModelIndex &index, const QVariant &value, int role) + { + data_model_node* node = static_cast(index.internalPointer()); + if(node) + { + if(role == Qt::EditRole && !value.toString().isEmpty()) + { + node->set_name(value.toString()); + return true; + } + } + + return false; + + } + + Qt::ItemFlags data_model::flags(const QModelIndex &index) const + { + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + + data_model_node* node = static_cast(index.internalPointer()); + + if(node->type() != data_model_node::device) + flags |= Qt::ItemFlag::ItemIsEditable; + + while(node->type() == data_model_node::analog) + { + if(!node->is_enabled()) + { + flags &= ~Qt::ItemFlag::ItemIsEnabled; + break; + } + + node = node->parent_node(); + if(node == nullptr) + break; + } + + return flags; + } + + void data_model::clear() + { + beginResetModel(); + + _devices.clear(); + + endResetModel(); + } + + void data_model::add_device(device *d) + { + if(std::find(_devices.begin(), _devices.end(), d) == _devices.end()) + { + beginInsertRows(QModelIndex(), (int)_devices.size(), (int)_devices.size()); + + _devices.push_back(d); + + endInsertRows(); + + connect(d, &data_model_node::create_node, this, &data_model::create_node); + connect(d, &data_model_node::deleting, this, &data_model::deleting_device); + connect(d, &data_model_node::node_name_changed, this, &data_model::node_name_changed); + + connect(d, &data_model_node::begin_insert_children, this, &data_model::begin_add_children); + connect(d, &data_model_node::end_insert_children, this, &data_model::end_add_children); + + connect(d, &data_model_node::begin_remove_children, this, &data_model::begin_remove_children); + connect(d, &data_model_node::end_remove_children, this, &data_model::end_remove_children); + } + } + + void data_model::remove(data_model_node *node) + { + if(node->parent_node()) + node->parent_node()->remove(node); + else + { + int row = index_of(node); + beginRemoveRows(QModelIndex(), row, row); + _devices.erase(_devices.begin() + row); + endRemoveRows(); + } + } + + data_model_node *data_model::node(QString path) const + { + + QStringList tokens = path.split("/"); + if(tokens.empty()) + return NULL; + + auto iter_device = std::find_if(_devices.cbegin(), _devices.cend(), [=](const gtl::device* d){return d->name() == tokens.first();}); + + if(iter_device == _devices.end()) + return NULL; + + data_model_node* node = *iter_device; + for(int i = 1; i < tokens.size(); i++) + { + node = node->child(tokens[i].toInt()); + if(node == NULL) + break; + } + + return node; + + } + + int data_model::index_of(data_model_node *node) const + { + if(node->type() != data_model_node::device) + return node->index_of(); + + auto iter = std::find(_devices.cbegin(), _devices.cend(), static_cast(node)); + if(iter == _devices.cend()) + return -1; + + return std::distance(_devices.cbegin(), iter); + } + + data_model_node *data_model::node(const QModelIndex& index) const + { + return static_cast(index.internalPointer()); + } + + void data_model::save(QDomElement &root_element) + { + for(auto device_iter: _devices) + { + QDomElement device_element = root_element.ownerDocument().createElement("device"); + root_element.appendChild(device_element); + + device_iter->save(device_element); + } + } + + void data_model::load(const QDomElement &root_element) + { + QDomElement device_element = root_element.firstChildElement("device"); + while(!device_element.isNull()) + { + gtl::data_model_node* device = NULL; + device = emit create_node(NULL, device_element); + if(device) + { + add_device(static_cast(device)); + device->load(device_element); + } + + device_element = device_element.nextSiblingElement("device"); + } + + } + + void data_model::begin_add_children(data_model_node* parent, int begin, int end) + { + QModelIndex parent_index = data_model::index(parent); + beginInsertRows(parent_index, begin, end); + + } + + void data_model::end_add_children() + { + endInsertRows(); + } + + void data_model::begin_remove_children(data_model_node *parent, int begin, int end) + { + QModelIndex parent_index = data_model::index(parent); + beginRemoveRows(parent_index, begin, end); + } + + void data_model::end_remove_children() + { + endRemoveRows(); + } + + void data_model::deleting_device() + { + remove(static_cast(sender())); + } + + void data_model::node_name_changed(data_model_node *node) + { + QModelIndex node_index = index(node); + emit dataChanged(node_index, node_index, QList() << Qt::DisplayRole); + } +} diff --git a/core/gtl_data_model.h b/core/gtl_data_model.h new file mode 100644 index 0000000..0a4f709 --- /dev/null +++ b/core/gtl_data_model.h @@ -0,0 +1,74 @@ +#ifndef DATA_MODEL_H +#define DATA_MODEL_H + +#include +#include + +#include "core_global.h" + +#include "core/gtl_device.h" + +namespace gtl +{ + class CORE_EXPORT data_model : public QAbstractItemModel + { + Q_OBJECT + + public: + explicit data_model(QObject *parent = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + + QModelIndex index(data_model_node* node); + QModelIndex parent(data_model_node* node); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + + void clear(); + + void add_device(device *d); + + + gtl::data_model_node* node(QString path) const; + + int index_of(data_model_node* node) const; + gtl::data_model_node* node(const QModelIndex& index) const; + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + std::vector _devices; + + public slots: + void remove(gtl::data_model_node* node); + + private slots: + void begin_add_children(gtl::data_model_node* parent, int begin, int end); + void end_add_children(); + + void begin_remove_children(gtl::data_model_node* parent, int begin, int end); + void end_remove_children(); + + void deleting_device(); + void node_name_changed(data_model_node *node); + + + + signals: + gtl::data_model_node* create_node(gtl::data_model_node* parent, QDomElement& node_element); + }; +} + +#endif // DATA_MODEL_H diff --git a/core/gtl_data_model_node.cpp b/core/gtl_data_model_node.cpp new file mode 100644 index 0000000..f56fa0a --- /dev/null +++ b/core/gtl_data_model_node.cpp @@ -0,0 +1,264 @@ +#include "gtl_data_model_node.h" + +#include + +#include "gtl_device.h" + +namespace gtl +{ + data_model_node::data_model_node(types type, data_model_node *parent, bool is_hidden) + : QObject(parent) + , _is_enabled(true) + , _type(type) + { + if(parent) + { + if(is_hidden) + { + parent->add_hidden_child(this); + } + else + { + parent->add_child(this); + + connect(this, &data_model_node::create_node, parent, &data_model_node::create_node); + connect(this, &data_model_node::deleting, parent, &data_model_node::deleting_child); + connect(this, &data_model_node::node_name_changed, parent, &data_model_node::node_name_changed); + + connect(this, &data_model_node::begin_insert_children, parent, &data_model_node::begin_insert_children); + connect(this, &data_model_node::end_insert_children, parent, &data_model_node::end_insert_children); + + connect(this, &data_model_node::begin_remove_children, parent, &data_model_node::begin_remove_children); + connect(this, &data_model_node::end_remove_children, parent, &data_model_node::end_remove_children); + } + } + } + + data_model_node::~data_model_node() + { + //for(auto it: _children) + while(_children.size() != 0) + delete _children[0]; + + emit deleting(); + } + + data_model_node *data_model_node::parent_node() const + { + return static_cast(parent()); + } + + void data_model_node::set_name(QString name) + { + if(_name != name) + { + _name = name; + emit name_changed(); + emit node_name_changed(this); + } + } + + void data_model_node::deleting_hidden_child() + { + _hidden_children.erase(std::find(_hidden_children.begin(), _hidden_children.end(), sender())); + } + + void data_model_node::deleting_child() + { + remove(static_cast(sender())); + } + + QString data_model_node::name() const + { + return _name; + } + + int data_model_node::count() + { + return (int)_children.size(); + } + + int data_model_node::index_of(const data_model_node *child) const + { + int idx = std::find(_children.begin(), _children.end(), child) - _children.begin(); + if(idx == _children.size()) + idx = -1; + + return idx; + } + + int data_model_node::index_of() const + { + data_model_node* parent = parent_node(); + if(parent) + return parent->index_of(this); + + return -1; + + } + + data_model_node *data_model_node::child(int idx) + { + if(idx < 0 || idx >= (int)_children.size()) + return NULL; + + return _children[idx]; + } + + void data_model_node::add_child(data_model_node *child) + { + emit begin_insert_children(this, _children.size(), _children.size()); + _children.push_back(child); + emit end_insert_children(); + } + + void data_model_node::add_hidden_child(data_model_node *child) + { + _hidden_children.push_back(child); + connect(child, >l::data_model_node::deleting, this, >l::data_model_node::deleting_hidden_child); + } + + data_model_node::types data_model_node::type() const + { + return _type; + } + + QString data_model_node::path() const + { + std::vector indices; + const data_model_node* node = this; + while(node->parent_node()) + { + indices.push_back(node->parent_node()->index_of(node)); + node = node->parent_node(); + + } + + QString path = node->name(); + for(auto it = indices.rbegin(); it != indices.rend(); it++) + path += "/" + QString::number(*it); + + return path; + } + + void data_model_node::save(QDomElement &root_element) + { + root_element.setAttribute("node", metaObject()->className()); + root_element.setAttribute("name", _name); + save_childs(root_element); + } + + void data_model_node::save_childs(QDomElement &root_element) + { + for(auto it: _children) + { + QDomElement child_element = root_element.ownerDocument().createElement("child"); + root_element.appendChild(child_element); + + (*it).save(child_element); + } + } + + void data_model_node::load(const QDomElement &root_element) + { + _name = root_element.attribute("name", _name); + load_childs(root_element); + } + + void data_model_node::load_childs(const QDomElement &root_element) + { + QDomElement child_element = root_element.firstChildElement("child"); + int idx = 0; + while(!child_element.isNull()) + { + if(idx < count()) + { + child(idx)->load(child_element); + } + else + { + data_model_node* node = emit create_node(this, child_element); + if(node) + { + node->load(child_element); + } + } + + child_element = child_element.nextSiblingElement("child"); + idx++; + } + } + + void data_model_node::get_state(QJsonObject &root) + { + root["name"] = _name; + root["node"] = metaObject()->className(); + } + + bool data_model_node::remove(data_model_node *child) + { + auto iter = std::find(_children.begin(), _children.end(), child); + if(iter == _children.end()) + return false; + + int idx = std::distance(_children.begin(), iter); + + + gtl::data_model_node* root = child->root(); + if(root) + if(root->type() == device) + static_cast(root)->lock_ai(); + + + emit begin_remove_children(this, idx, idx); + + _children.erase(iter); + + emit end_remove_children(); + + if(root) + if(root->type() == device) + static_cast(root)->unlock_ai(); + + return true; + } + + void data_model_node::remove_children() + { + if(_children.empty()) + return; + + emit begin_remove_children(this, 0, (int)_children.size() - 1); + + _children.clear(); + + emit end_remove_children(); + } + + bool data_model_node::is_child_name_exist(QString name) + { + return std::find_if(_children.begin(), _children.end(), [=](data_model_node* child){return child->name() == name;}) != _children.end(); + } + + void data_model_node::set_enabled(bool value) + { + _is_enabled = value; + } + + bool data_model_node::is_enabled() const + { + return _is_enabled; + } + + data_model_node *data_model_node::root() + { + data_model_node* node = this; + while(node->parent_node()) + node = node->parent_node(); + + if(node->type() != device) + return nullptr; + + return node; + } +} diff --git a/core/gtl_data_model_node.h b/core/gtl_data_model_node.h new file mode 100644 index 0000000..a4b858f --- /dev/null +++ b/core/gtl_data_model_node.h @@ -0,0 +1,95 @@ +#ifndef DATA_MODEL_NODE_H +#define DATA_MODEL_NODE_H + +#include +#include +#include + +#include "core_global.h" + +namespace gtl +{ + class CORE_EXPORT data_model_node : public QObject + { + Q_OBJECT + Q_PROPERTY(QString name READ name WRITE set_name NOTIFY name_changed) + + public: + enum types + { + unknown, + device, + analog + }; + + public: + data_model_node(types type, data_model_node *parent = nullptr, bool is_hidden = false); + ~data_model_node(); + data_model_node* parent_node() const; + + + QString name() const; + + int count(); + int index_of(const data_model_node* child) const; + int index_of() const; + data_model_node* child(int idx); + + virtual bool is_checkable() const = 0; + + types type() const; + + QString path() const; + + virtual void save(QDomElement& root_element); + virtual void save_childs(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + virtual void load_childs(const QDomElement& root_element); + virtual void get_state(QJsonObject& root); + + bool remove(data_model_node* child); + void remove_children(); + + data_model_node* root(); + + bool is_child_name_exist(QString name); + + void set_enabled(bool value); + bool is_enabled() const; + + protected: + QString _name; + std::vector _children; + std::vector _hidden_children; + bool _is_enabled; + + private: + types _type; + + private: + void add_child(data_model_node* child); + void add_hidden_child(data_model_node* child); + + public slots: + void set_name(QString name); + void deleting_hidden_child(); + + private slots: + void deleting_child(); + + signals: + void name_changed(); + void node_name_changed(data_model_node*); + void deleting(); + gtl::data_model_node* create_node(gtl::data_model_node* parent, QDomElement& node_element); + + void begin_insert_children(gtl::data_model_node* parent, int begin, int end); + void end_insert_children(); + + void begin_remove_children(gtl::data_model_node* parent, int begin, int end); + void end_remove_children(); + + }; +} + +#endif // DATA_MODEL_NODE_H diff --git a/core/gtl_device.cpp b/core/gtl_device.cpp new file mode 100644 index 0000000..d004bb2 --- /dev/null +++ b/core/gtl_device.cpp @@ -0,0 +1,122 @@ +#include "gtl_device.h" + +namespace gtl +{ + device::device(data_model_node *parent) + : data_model_node(types::device, parent) + { + + } + + device::~device() + { +// lock_ai(); + } + + gtl::analog_data *device::ai(int idx) + { + if(idx < 0 || idx >= _ai.size()) + return NULL; + + return _ai[idx]; + } + + void device::clear() + { + for(int i = 0; i < _ai.size(); i++) + { +// _children.erase(std::find(_children.begin(), _children.end(), _ai[i])); + delete _ai[i]; + } + + _ai.clear(); + } + + int device::count_ai() const + { + return (int)_ai.size(); + } + + void device::add_ai(analog_data *ai) + { + _ai.push_back(ai); + } + + void device::lock_ai() + { + _ai_mutex.lock(); + } + + void device::unlock_ai() + { + _ai_mutex.unlock(); + } + + void device::save(QDomElement &root_element) + { + emit save_device(root_element); + data_model_node::save(root_element); + } + + void device::load(const QDomElement &root_element) + { + bool is_running = root_element.attribute("is_running", "0").toInt(); + _id = root_element.attribute("id", ""); + _rate = root_element.attribute("rate", "0").toDouble(); + + emit load_device(root_element); + + emit start(_id, _rate); + + data_model_node::load(root_element); + + if(is_running) + emit restart(); + else + emit stop(); + + } + + QString device::id() const + { + return _id; + } + + void device::set_id(QString id) + { + _id = id; + } + + qreal device::rate() const + { + return _rate; + } + + void device::set_rate(qreal rate) + { + _rate = rate; + } + + QString device::device_type() const + { + return emit get_type(); + } + + QVariant device::get_parameter(int idx) + { + QVariant v; + emit get_device_parameter(idx, v); + + return v; + } + + void device::set_parameter(int idx, QVariant value) + { + emit set_device_parameter(idx, value); + } + + bool device::is_checkable() const + { + return false; + } +} diff --git a/core/gtl_device.h b/core/gtl_device.h new file mode 100644 index 0000000..f089c1c --- /dev/null +++ b/core/gtl_device.h @@ -0,0 +1,74 @@ +#ifndef DEVICE_H +#define DEVICE_H + +#include "core_global.h" + +#include +#include +#include + +#include "core/gtl_analog_data.h" + +namespace gtl +{ + class CORE_EXPORT device : public data_model_node + { + Q_OBJECT + public: + device(data_model_node *parent = nullptr); + virtual ~device(); + + analog_data* ai(int idx); + + void clear(); + int count_ai() const; + + void add_ai(analog_data* ai); + + void lock_ai(); + void unlock_ai(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + QString id() const; + void set_id(QString id); + + qreal rate() const; + void set_rate(qreal rate); + + QString device_type() const; + + QVariant get_parameter(int idx); + void set_parameter(int idx, QVariant value); + + protected: + virtual bool is_checkable() const; + + private: + std::vector _ai; + QRecursiveMutex _ai_mutex; + + QString _id; + qreal _rate; + + + signals: + void save_device(QDomElement& root_element); + void load_device(const QDomElement& root_element); + void start(QString id, qreal rate); + void restart(); + void stop(); + void recieved_data(); + QString get_type() const; + + void get_device_parameter(int idx, QVariant &value); + void set_device_parameter(int idx, const QVariant &value); + + void started(); + void stopped(); + + }; +} + +#endif // DEVICE_H diff --git a/core/gtl_logger.cpp b/core/gtl_logger.cpp new file mode 100644 index 0000000..229975c --- /dev/null +++ b/core/gtl_logger.cpp @@ -0,0 +1,183 @@ +#include "gtl_logger.h" + +#include +#include +#include + +namespace gtl +{ + QFile logger::_log_file; + QString logger::_path; + QString logger::_file_name; + QThread logger::_thread; + uint32_t logger::_max_file_size; + uint32_t logger::_max_index; + + logger::logger(QObject *parent) + : QObject{parent} + { + _is_continue = false; + open_file(); + } + + logger::~logger() + { + if(_log_file.isOpen()) + _log_file.close(); + _thread.wait(); + } + + logger* logger::getInstance() + { + static logger instance; + return &instance; + } + + void logger::init(const QString &path, const uint32_t max_file_size, const file_size_unit unit) + { + _path = path; + _max_file_size = max_file_size << get_file_size_unit(unit); + _file_name = find_last_file(); + + connect(getInstance(), &logger::sgn_info, getInstance(), &logger::slt_info); + connect(getInstance(), &logger::sgn_warning, getInstance(), &logger::slt_warning); + connect(getInstance(), &logger::sgn_error, getInstance(), &logger::slt_error); + connect(getInstance(), &logger::sgn_debug, getInstance(), &logger::slt_debug); + + getInstance()->moveToThread(&_thread); + _thread.start(); + } + + void logger::open_file() + { + if(_log_file.isOpen()) + _log_file.close(); + + _log_file.setFileName(_file_name ); + + _log_file.open( QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text ); + + if( !_log_file.isOpen() ) + throw std::iostream::failure("Cannot open file: " + _file_name.toStdString()); + } + + QString logger::find_last_file() + { + QString file_name = _path + QDir::separator() + "log_0000"; + QRegularExpression reNameLogFile("^log_(\\d{4})$"); + + QDir currentDir = QDir(_path); + + if(false == currentDir.exists()) + currentDir.mkdir(_path); + + QStringList filters; + filters << "log_*"; + QStringList filesList = currentDir.entryList(filters, QDir::Files | QDir::NoSymLinks); + + if(!filesList.isEmpty()) + { + _max_index = 0; + uint32_t current_index = 0; + bool flagFindCorrectName = false; + + while(!filesList.isEmpty()) + { + QString fName = filesList.at(0); + filesList.removeFirst(); + + QRegularExpressionMatch match = reNameLogFile.match(fName); + if(!match.hasMatch()) + continue; + + flagFindCorrectName = true; + fName.replace("log_", ""); + current_index = fName.toUInt(); + + _max_index = ((_max_index < current_index) ? current_index : _max_index); + } + + if(flagFindCorrectName) + { + file_name = _path + QDir::separator() + "log_" + QString::number(_max_index).rightJustified(4, '0'); + if(false == check_file_size(file_name)) + { + _max_index += 1; + file_name = _path + QDir::separator() + "log_" + QString::number(_max_index).rightJustified(4, '0'); + } + } + } + + return file_name; + } + + bool logger::check_file_size(const QString& path) + { + QFileInfo info1(path); + return (info1.size() >= _max_file_size) ? false : true; + } + + void logger::info(const QString &tag, const QString &str) + { + if(getInstance() != nullptr) + emit getInstance()->sgn_info(tag, str); + } + + void logger::warning(const QString &tag, const QString &str) + { + if(getInstance() != nullptr) + emit getInstance()->sgn_warning(tag, str); + } + + void logger::error(const QString &tag, const QString &str) + { + if(getInstance() != nullptr) + emit getInstance()->sgn_error(tag, str); + } + + void logger::debug(const QString &tag, const QString &str) + { + if(getInstance() != nullptr) + emit getInstance()->sgn_debug(tag, str); + } + + uint32_t logger::get_file_size_unit(const file_size_unit unit) + { + uint32_t temp; + switch(unit) { + case file_size_unit::b: temp = 0; break; + case file_size_unit::kb: temp = 10; break; + case file_size_unit::mb: temp = 20; break; + case file_size_unit::gb: temp = 30; break; + } + return temp; + } + + QString logger::get_level_string(const log_level level) + { + QString temp; + switch(level) { + case log_level::info: temp = "INFO"; break; + case log_level::warning: temp = "WARNING"; break; + case log_level::debug: temp = "DEBUG"; break; + case log_level::error: temp = "ERROR"; break; + } + return temp; + } + + QString logger::get_time_string() + { + //** for debug ** + //QThread::sleep(5); + //** for debug ** + + QDateTime m_dateTime = QDateTime::currentDateTime(); + return m_dateTime.toString("hh:mm:ss.zzz"); + } + + QString logger::get_date_string() + { + QDateTime m_dateTime = QDateTime::currentDateTime(); + return m_dateTime.toString("dd.MM.yyyy"); + } +} diff --git a/core/gtl_logger.h b/core/gtl_logger.h new file mode 100644 index 0000000..6a7db77 --- /dev/null +++ b/core/gtl_logger.h @@ -0,0 +1,108 @@ +#ifndef GTL_LOGGER_H +#define GTL_LOGGER_H + +#include +#include +#include +#include +#include +#include "core_global.h" + +namespace gtl +{ + + enum class log_level { + info, + warning, + error, + debug + }; + + + + class CORE_EXPORT logger : public QObject + { + Q_OBJECT + + public: + enum file_size_unit { + b, + kb, + mb, + gb + }; + + static logger *getInstance(void); + static void init(const QString &path, const uint32_t max_file_size, const file_size_unit unit); + static void info(const QString &tag, const QString &text); + static void warning(const QString &tag, const QString &text); + static void error(const QString &tag, const QString &text); + static void debug(const QString &tag, const QString &text); + + private: + static QFile _log_file; + static QString _path; + static QString _file_name; + static QThread _thread; + static uint32_t _max_file_size; + static uint32_t _max_index; + + static void open_file(void); + static QString find_last_file(void); + static bool check_file_size(const QString& path); + + protected: + logger(QObject *parent = nullptr); + ~logger(); + + bool _is_continue; + static uint32_t get_file_size_unit(const file_size_unit unit); + QString get_level_string(const log_level level); + QString get_time_string(void); + QString get_date_string(void); + + + + signals: + void sgn_info(const QString &tag, const QString &text); + void sgn_warning(const QString &tag, const QString &text); + void sgn_error(const QString &tag, const QString &text); + void sgn_debug(const QString &tag, const QString &text); + void sgn_send_to_viewer(const QString &level, const QString &date, const QString &time, const QString &tag, const QString &text); + + + public slots: + void slt_info(const QString &tag, const QString &text) { log(log_level::info, tag, text); } + void slt_warning(const QString &tag, const QString &text){ log(log_level::warning, tag, text); } + void slt_error(const QString &tag, const QString &text) { log(log_level::error, tag, text); } + void slt_debug(const QString &tag, const QString &text) { log(log_level::debug, tag, text); } + + void log(log_level level = log_level::info, const QString &tag = "FLUSH", const QString &text = "flush") + { + QString s_level = get_level_string(level); + QString s_date = get_date_string(); + QString s_time = get_time_string(); + + QString str_new = "[" + s_level + "]::[" +\ + s_date + "]::[" +\ + s_time + "]::[" +\ + tag + "]::" +\ + text + "\n"; + + QTextStream textStream(&_log_file); + textStream << str_new.toStdString().c_str(); + + if(false == check_file_size(_file_name)) + { + _max_index += 1; + _file_name = _path + QDir::separator() + "log_" + QString::number(_max_index).rightJustified(4, '0'); + + open_file(); + } + + emit sgn_send_to_viewer(s_level, s_date, s_time, tag, text); + } + }; +} + +#endif // GTL_LOGGER_H diff --git a/core/gtl_selection_data_model.cpp b/core/gtl_selection_data_model.cpp new file mode 100644 index 0000000..83ad86d --- /dev/null +++ b/core/gtl_selection_data_model.cpp @@ -0,0 +1,202 @@ +#include "gtl_selection_data_model.h" + +#include "core/gtl_data_model.h" + +namespace gtl +{ + selection_data_model::selection_data_model(QObject *source, int count) + : QIdentityProxyModel{source} + , _count(count) + { + // setSourceModel(source); + _selection = new selection_list(this); + } + + void selection_data_model::save(QDomElement &root_element) + { + for(auto it = _selection->begin(); it != _selection->end(); it++) + { + QDomElement item_element = root_element.ownerDocument().createElement("item"); + root_element.appendChild(item_element); + item_element.setAttribute("path", (*it)->path()); + } + root_element.setAttribute("count", _count); + } + + void gtl::selection_data_model::add_selection(gtl::data_model_node* node) + { + + if(std::find(_selection->begin(), _selection->end(), node) != _selection->end()) + return; + + + _selection->add(node); + connect(node, >l::data_model_node::deleting, this, &selection_data_model::deleting_node); + emit selected(static_cast(node)); + + while(_selection->size() > _count && _count > 0) + deselect(_selection->front()); +} + + void selection_data_model::load(const QDomElement &root_element) + { + clear(); + + gtl::data_model* model = static_cast(sourceModel()); + + _count = root_element.attribute("count", "0").toInt(); + + QDomElement item_element = root_element.firstChildElement(/*"item"*/); + while(!item_element.isNull()) + { + gtl::data_model_node* node = model->node(item_element.attribute("path", "")); + if(node) + { + add_selection(node); + QModelIndex index = createIndex(node->index_of(), 0, node); + emit dataChanged(index, index, QList() << Qt::CheckStateRole); + } + + item_element = item_element.nextSiblingElement("item"); + } + } + + void selection_data_model::clear() + { + for(auto it = _selection->begin(); it != _selection->end(); it++) + emit deselected(static_cast(*it)); + + _selection->remove_all(); + } + + QAbstractListModel *selection_data_model::selection() + { + return _selection; + } + + Qt::ItemFlags selection_data_model::flags(const QModelIndex &index) const + { + return QIdentityProxyModel::flags(index) | Qt::ItemIsUserCheckable; + } + + QVariant selection_data_model::data(const QModelIndex &index, int role) const + { + if(role == Qt::CheckStateRole) + { + if(parent(index).isValid()) + { + data_model_node* node = static_cast(index.internalPointer()); + if(std::find(_selection->begin(), _selection->end(), node) == _selection->end()) + return Qt::Unchecked; + else + return Qt::Checked; + } + } + + return QIdentityProxyModel::data(index, role); + } + + bool selection_data_model::setData(const QModelIndex &index, const QVariant &value, int role) + { + if(role == Qt::CheckStateRole) + { + data_model_node* node = static_cast(index.internalPointer()); + if(node->is_checkable()) + { + if(value.toBool()) + { + add_selection(node); + } + else + { + if(_selection->size() > _count) + { + _selection->remove(node); + emit deselected(static_cast(node)); + } + } + } + + return true; + } + + return QIdentityProxyModel::setData(index, value, role); + } + + void selection_data_model::get_selections(std::back_insert_iterator > ad) + { + std::copy(_selection->begin(), _selection->end(), ad); + } + + void selection_data_model::select(analog_data *node) + { + QModelIndex index = mapFromSource(static_cast(sourceModel())->index(node)); + setData(index, true, Qt::CheckStateRole); + emit dataChanged(index, index, {Qt::CheckStateRole}); + } + + void selection_data_model::deselect(analog_data *node) + { + QModelIndex index = mapFromSource(static_cast(sourceModel())->index(node)); + setData(index, false, Qt::CheckStateRole); + emit dataChanged(index, index, {Qt::CheckStateRole}); + } + + void selection_data_model::deleting_node() + { + auto iter_node = std::find(_selection->begin(), _selection->end(), sender()); + if(iter_node != _selection->end()) + _selection->remove(*iter_node); + } + + selection_list::selection_list(QObject *parent) + : QAbstractListModel(parent) + { + + } + + int selection_list::rowCount(const QModelIndex&) const + { + return (int)size(); + } + + QVariant selection_list::data(const QModelIndex &index, int role) const + { + if(role == Qt::DisplayRole) + return at(index.row())->name(); + + return QVariant(); + } + + + void selection_list::add(data_model_node *node) + { + beginInsertRows(QModelIndex(), (int)size(), (int)size()); + + push_back(static_cast(node)); + + endInsertRows(); + } + + void selection_list::remove(data_model_node *node) + { + auto it = std::find(begin(), end(), node); + if(it == end()) + return; + + int idx = std::distance(begin(), it); + beginRemoveRows(QModelIndex(), idx, idx); + + erase(it); + + endRemoveRows(); + } + + void selection_list::remove_all() + { + beginResetModel(); + clear(); + endResetModel(); + } + +} diff --git a/core/gtl_selection_data_model.h b/core/gtl_selection_data_model.h new file mode 100644 index 0000000..d480b31 --- /dev/null +++ b/core/gtl_selection_data_model.h @@ -0,0 +1,71 @@ +#ifndef GTL_SELELCTION_DATA_MODEL_H +#define GTL_SELELCTION_DATA_MODEL_H + +#include + +#include +#include + +#include "core/core_global.h" + +#include "core/gtl_analog_data.h" + +namespace gtl +{ + class selection_list : public QAbstractListModel, public std::deque + { + private: + + public: + selection_list(QObject* parent = NULL); + + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + void add(data_model_node* node); + void remove(data_model_node* node); + + void remove_all(); + + }; + + + class CORE_EXPORT selection_data_model : public QIdentityProxyModel + { + Q_OBJECT + public: + explicit selection_data_model(QObject *source, int count = 0); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + void clear(); + + QAbstractListModel* selection(); + + void get_selections(std::back_insert_iterator> ad); + + void select(gtl::analog_data* node); + void deselect(gtl::analog_data* node); + + protected: + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + virtual QVariant data(const QModelIndex &index, int role/* = Qt::DisplayRole*/) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + private: + selection_list* _selection; + int _count; + + private: + void add_selection(gtl::data_model_node* node); + + private slots: + void deleting_node(); + + signals: + void selected(gtl::analog_data*); + void deselected(gtl::analog_data*); + }; +} +#endif // GTL_SELELCTION_DATA_MODEL_H diff --git a/gtl_sdk.iss b/gtl_sdk.iss new file mode 100644 index 0000000..7de3ac4 --- /dev/null +++ b/gtl_sdk.iss @@ -0,0 +1,41 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "gtl_sdk" +#define MyAppVersion "0.38" +#define MyAppPublisher "GTLab" +#define MyAppURL "www.gtlab.pro" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. +; Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{F66666BA-CAFC-4712-8325-BDE3CAC56477} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={pf}\{#MyAppName} +DefaultGroupName={#MyAppName} +OutputBaseFilename={#MyAppName}-{#MyAppVersion}-setup +OutputDir=e:\setups\gt\gtl_sdk +Compression=lzma +SolidCompression=yes + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" + +[Files] +Source: ".\.output\*"; DestDir: "{app}\lib"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.exp, *.ilk, *.pdb" +Source: ".\core\*.h"; DestDir: "{app}\include\core"; Flags: ignoreversion +Source: ".\gui\*.h"; DestDir: "{app}\include\gui"; Flags: ignoreversion recursesubdirs +Source: ".\hw\*.h"; DestDir: "{app}\include\hw"; Flags: ignoreversion; Excludes: "adlink.h" +Source: ".\math\*.h"; DestDir: "{app}\include\math"; Flags: ignoreversion +Source: ".\script\*.h"; DestDir: "{app}\include\script"; Flags: ignoreversion +Source: ".\test_gtl\version.h"; DestDir: "{app}\include"; Flags: ignoreversion +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + diff --git a/gui/.gitignore b/gui/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/gui/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/gui/config/gtl_gui_config_filter_response_chart.cpp b/gui/config/gtl_gui_config_filter_response_chart.cpp new file mode 100644 index 0000000..1fcec7e --- /dev/null +++ b/gui/config/gtl_gui_config_filter_response_chart.cpp @@ -0,0 +1,38 @@ +#include "gtl_gui_config_filter_response_chart.h" + +#include "gui/config/gtl_gui_config_filter_response_chart_series.h" +#include "gui/config/gtl_gui_config_filter_response_chart_series_axis_y.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + filter_response_chart::filter_response_chart(QWidget* parent) + : gtl::gui::chart(parent, new chart_axis_x(), new filter_response_chart_series_axis_y()) + { + _axis_x->set_scale(::chart::axis::logarithmic); + } + + void filter_response_chart::set_filter(math::filter_iir *filter) + { + remove_series(); + add_ad(filter); + } + + chart_series *filter_response_chart::create_series(analog_data *ai) + { + return new filter_response_chart_series(static_cast(ai), _axis_x, _axis_y); + } + + void filter_response_chart::resizeEvent(QResizeEvent *event) + { + gtl::gui::chart::resizeEvent(event); + + if(!_series.empty()) + static_cast(_series.front())->update_data(); + } + } + } +} diff --git a/gui/config/gtl_gui_config_filter_response_chart.h b/gui/config/gtl_gui_config_filter_response_chart.h new file mode 100644 index 0000000..7f3eb24 --- /dev/null +++ b/gui/config/gtl_gui_config_filter_response_chart.h @@ -0,0 +1,28 @@ +#ifndef FILTER_RESPONSE_CHART_H +#define FILTER_RESPONSE_CHART_H + +#include "gui/gtl_gui_chart.h" +#include "math/gtl_math_filter_iir.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + class filter_response_chart : public gtl::gui::chart + { + Q_OBJECT + public: + filter_response_chart(QWidget* parent = NULL); + void set_filter(gtl::math::filter_iir* filter); + + private: + virtual chart_series* create_series(gtl::analog_data* ai) override; + void resizeEvent(QResizeEvent *event) override; + }; + } + } +} + +#endif // FILTER_RESPONSE_CHART_H diff --git a/gui/config/gtl_gui_config_filter_response_chart_series.cpp b/gui/config/gtl_gui_config_filter_response_chart_series.cpp new file mode 100644 index 0000000..9e2e472 --- /dev/null +++ b/gui/config/gtl_gui_config_filter_response_chart_series.cpp @@ -0,0 +1,87 @@ +#include "gtl_gui_config_filter_response_chart_series.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + filter_response_chart_series::filter_response_chart_series(gtl::math::filter_iir* filter, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y) + : gtl::gui::chart_series(filter, axis_x, axis_y) + , _y_min(-100) + , _y_max(0) + { + + connect(filter, >l::math::filter_iir::changed, this, &filter_response_chart_series::update_data); + + connect(axis_x, &::chart::axis_horz::signal_range_changed, this, &filter_response_chart_series::update_data); + + update_data(); + } + + void filter_response_chart_series::update_data() + { + prepareGeometryChange(); + + gtl::math::filter_iir* filter = static_cast(_ad); + qreal rate = filter->get_rate(); + std::vector data; + + data.push_back(QPointF(1, 20 * log10(std::max(1e-5, filter->response(1/rate))))); + + _y_min = 0; + _y_max = -100; + + for(int i = 0; i < (_axis_horz->boundingRect().width() + 1)*10; i++) + { + qreal x = _axis_horz->map_from_widget(i/10.0); + qreal y = 1; + y = filter->response(x / rate); + + if (qIsNaN(y) || y == qInf() || y == -qInf()) + continue; + + if (y < 1e-5) + y = 1e-5; + + y = 20 * log10(y/* / 0.000001*/); + + if(y < _y_min) + _y_min = y; + if(y > _y_max) + _y_max = y; + + data.push_back(QPointF(x, y)); + } + + data.push_back(QPointF(rate/2, 20 * log10(std::max(filter->response(0.5), 1e-5)))); + + set_data(data.begin(), data.end()); + + qreal range = _y_max - _y_min; + _axis_vert->set_range(_y_min - 0.05*range, _y_max + 0.05*range); + + } + + bool filter_response_chart_series::get_ranges(qreal &x_min, qreal &x_max, qreal &y_min, qreal &y_max) + { + x_min = 0; + x_max = static_cast(_ad)->get_rate()/2; + + return true; + } + + bool filter_response_chart_series::get_range_y(qreal x_min, qreal x_max, qreal &y_min, qreal &y_max) + { +// y_min = _y_min; +// y_max = _y_max; +// qDebug() << _y_min << _y_max; + +// return true; + + return gtl::gui::chart_series::get_range_y(x_min, x_max, y_min, y_max); + } + + } + } +} diff --git a/gui/config/gtl_gui_config_filter_response_chart_series.h b/gui/config/gtl_gui_config_filter_response_chart_series.h new file mode 100644 index 0000000..9590a85 --- /dev/null +++ b/gui/config/gtl_gui_config_filter_response_chart_series.h @@ -0,0 +1,33 @@ +#ifndef FILTER_RESPONSE_CHART_SERIES_H +#define FILTER_RESPONSE_CHART_SERIES_H + +#include "gui/gtl_gui_chart_series.h" +#include "math/gtl_math_filter_iir.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + class filter_response_chart_series : public gtl::gui::chart_series + { + public: + filter_response_chart_series(gtl::math::filter_iir* filter, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y); + + private: + virtual bool get_ranges(qreal &x_min, qreal &x_max, qreal &y_min, qreal &y_max) override; + virtual bool get_range_y(qreal x_min, qreal x_max, qreal &y_min, qreal &y_max) override; + + private: + qreal _y_min; + qreal _y_max; + + public slots: + void update_data(); + }; + } + } +} + +#endif // FILTER_RESPONSE_CHART_SERIES_H diff --git a/gui/config/gtl_gui_config_filter_response_chart_series_axis_y.cpp b/gui/config/gtl_gui_config_filter_response_chart_series_axis_y.cpp new file mode 100644 index 0000000..fd242bf --- /dev/null +++ b/gui/config/gtl_gui_config_filter_response_chart_series_axis_y.cpp @@ -0,0 +1,20 @@ +#include "gtl_gui_config_filter_response_chart_series_axis_y.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + filter_response_chart_series_axis_y::filter_response_chart_series_axis_y() + { + _is_autoscale = true; + } + + void filter_response_chart_series_axis_y::emit_fit() + { + emit fit(); + } + } + } +} diff --git a/gui/config/gtl_gui_config_filter_response_chart_series_axis_y.h b/gui/config/gtl_gui_config_filter_response_chart_series_axis_y.h new file mode 100644 index 0000000..d1d1aa1 --- /dev/null +++ b/gui/config/gtl_gui_config_filter_response_chart_series_axis_y.h @@ -0,0 +1,23 @@ +#ifndef FILTER_RESPONSE_CHART_SERIES_AXIS_Y_H +#define FILTER_RESPONSE_CHART_SERIES_AXIS_Y_H + +#include "gui/gtl_gui_chart_axis_y.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + class filter_response_chart_series_axis_y : public chart_axis_y + { + Q_OBJECT + public: + filter_response_chart_series_axis_y(); + void emit_fit(); + }; + } + } +} + +#endif // FILTER_RESPONSE_CHART_SERIES_AXIS_Y_H diff --git a/gui/config/gtl_gui_config_hardware_dialog.cpp b/gui/config/gtl_gui_config_hardware_dialog.cpp new file mode 100644 index 0000000..8f325ea --- /dev/null +++ b/gui/config/gtl_gui_config_hardware_dialog.cpp @@ -0,0 +1,88 @@ +#include "gtl_gui_config_hardware_dialog.h" +#include "ui_gtl_gui_config_hardware_dialog.h" + +#include "gui/config/gtl_gui_config_hw_widget_audio.h" +#include "gui/config/gtl_gui_config_hw_widget_dac.h" +#include "gui/config/gtl_gui_config_hw_widget_generator.h" + + +namespace gtl +{ + namespace gui + { + namespace config + { + hardware_dialog::hardware_dialog(QString plugins_path, QWidget *parent) + : QDialog(parent) + , _hw(plugins_path) + + { + _ui.setupUi(this); + + _ui.stacked_widget->addWidget(new hw_widget_dac(plugins_path, this)); + _ui.stacked_widget->addWidget(new hw_widget_generator(this)); + _ui.stacked_widget->addWidget(new hw_widget_audio(this)); + } + + hardware_dialog::~hardware_dialog() + { + + } + + QString hardware_dialog::id() const + { + + return static_cast(_ui.stacked_widget->currentWidget())->id(); + } + + qreal hardware_dialog::rate() const + { + return static_cast(_ui.stacked_widget->currentWidget())->rate(); + } + + QString hardware_dialog::type() const + { + + if(_ui.stacked_widget->currentIndex() == 1) + return "generator"; + else if(_ui.stacked_widget->currentIndex() == 2) + return "audio"; + else if(_ui.stacked_widget->currentIndex() == 3) + return "player"; + + return static_cast(_ui.stacked_widget->widget(0))->type(); + } + + hw::device* hardware_dialog::create_device() + { + return static_cast(_ui.stacked_widget->currentWidget())->create_device(); + } + + hw::device *hardware_dialog::create_device(QString type) + { + return static_cast(_ui.stacked_widget->widget(get_widget_idex(type)))->create_device(); + } + + int hardware_dialog::exec(QString type) + { + _ui.stacked_widget->setCurrentIndex(get_widget_idex(type)); + + return QDialog::exec(); + } + + int hardware_dialog::get_widget_idex(QString type) + { + if(type == "generator") + return 1; + else if(type == "audio") + return 2; + else if(type == "player") + return 3; + + static_cast(_ui.stacked_widget->widget(0))->set_type(type); + + return 0; + } + } + } +} diff --git a/gui/config/gtl_gui_config_hardware_dialog.h b/gui/config/gtl_gui_config_hardware_dialog.h new file mode 100644 index 0000000..c9b506b --- /dev/null +++ b/gui/config/gtl_gui_config_hardware_dialog.h @@ -0,0 +1,49 @@ +#ifndef GTL_GUI_CONFIG_HARDWARE_DIALOG_H +#define GTL_GUI_CONFIG_HARDWARE_DIALOG_H + +#include + +#include "hw/gtl_hw.h" + +#include "gui/ui_gtl_gui_config_hardware_dialog.h" +#include "gui/gui_global.h" + +#include "gui/config/gtl_gui_config_hw_widget.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + class GTL_GUI_EXPORT hardware_dialog : public QDialog + { + Q_OBJECT + + public: + explicit hardware_dialog(QString plugins_path, QWidget *parent = nullptr); + ~hardware_dialog(); + + QString id() const; + qreal rate() const; + QString type() const; + + hw::device* create_device(); + hw::device* create_device(QString type); + + int exec(QString type = ""); + + private: + Ui::config_hardware_dialog _ui; + + gtl::hw::hw _hw; + + private: + int get_widget_idex(QString type); + + }; + } + } +} + +#endif // GTL_GUI_CONFIG_HARDWARE_DIALOG_H diff --git a/gui/config/gtl_gui_config_hardware_dialog.ui b/gui/config/gtl_gui_config_hardware_dialog.ui new file mode 100644 index 0000000..0fd7b4f --- /dev/null +++ b/gui/config/gtl_gui_config_hardware_dialog.ui @@ -0,0 +1,150 @@ + + + config_hardware_dialog + + + true + + + + 0 + 0 + 434 + 132 + + + + + 0 + 0 + + + + Add hardware + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 6 + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + 0 + 0 + + + + + 0 + 100 + + + + -1 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + OK + + + + + + + Cancel + + + + + + + + + + + ok_button + clicked() + config_hardware_dialog + accept() + + + 181 + 87 + + + 100 + 80 + + + + + pushButton_2 + clicked() + config_hardware_dialog + reject() + + + 235 + 92 + + + 33 + 71 + + + + + diff --git a/gui/config/gtl_gui_config_hw_widget.cpp b/gui/config/gtl_gui_config_hw_widget.cpp new file mode 100644 index 0000000..bb3f914 --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget.cpp @@ -0,0 +1,16 @@ +#include "gtl_gui_config_hw_widget.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + hw_widget::hw_widget(QWidget *parent) + : QWidget{parent} + { + + } + } + } +} diff --git a/gui/config/gtl_gui_config_hw_widget.h b/gui/config/gtl_gui_config_hw_widget.h new file mode 100644 index 0000000..f3af8d8 --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget.h @@ -0,0 +1,30 @@ +#ifndef CONFIG_HW_WIDGET_H +#define CONFIG_HW_WIDGET_H + +#include + +#include "hw/gtl_hw.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + class hw_widget : public QWidget + { + Q_OBJECT + public: + explicit hw_widget(QWidget *parent = nullptr); + virtual hw::device* create_device() = 0; + virtual QString id() = 0; + virtual qreal rate() = 0; + + signals: + + }; + } + } +} + +#endif // CONFIG_HW_WIDGET_H diff --git a/gui/config/gtl_gui_config_hw_widget_audio.cpp b/gui/config/gtl_gui_config_hw_widget_audio.cpp new file mode 100644 index 0000000..b227dd7 --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget_audio.cpp @@ -0,0 +1,91 @@ +#include "gtl_gui_config_hw_widget_audio.h" +#include "ui_gtl_gui_config_hw_widget_audio.h" + +#include "hw/gtl_hw_audio.h" + + + +namespace gtl +{ + namespace gui + { + namespace config + { + hw_widget_audio::hw_widget_audio(QWidget *parent) + : hw_widget(parent) + , ui(new Ui::config_hw_widget_audio) + { + ui->setupUi(this); + + _devices = new QMediaDevices(this); + + connect(ui->devices, &QComboBox::currentIndexChanged, this, &hw_widget_audio::device_changed); + + const auto devices = _devices->audioInputs(); + for (auto &device_info: devices) + ui->devices->addItem(device_info.description(), QVariant::fromValue(device_info)); + } + + hw_widget_audio::~hw_widget_audio() + { + delete ui; + } + + hw::device *hw_widget_audio::create_device() + { + return new gtl::hw::audio(ui->channels->currentData().toInt(), (QAudioFormat::SampleFormat)ui->sample_format->currentData().toInt()); + } + + QString hw_widget_audio::id() + { + return ui->devices->currentData().value().id(); + } + + qreal hw_widget_audio::rate() + { + return ui->rate->value(); + } + + void hw_widget_audio::device_changed(int idx) + { + QAudioDevice device_info = ui->devices->itemData(idx).value(); + + + ui->rate->setMinimum(device_info.minimumSampleRate()); + ui->rate->setMaximum(device_info.maximumSampleRate()); + int sampleValue = qBound(device_info.minimumSampleRate(), 48000, + device_info.maximumSampleRate()); + ui->rate->setValue(sampleValue); + + ui->freq_label->setText( + tr("Rate") + + " (" + QString::number(device_info.minimumSampleRate()) + " - " + QString::number(device_info.maximumSampleRate()) + ") " + + ":" + ); + + ui->channels->clear(); + for(int i = device_info.minimumChannelCount(); i <= device_info.maximumChannelCount(); i++) + ui->channels->addItem(QString::number(i), i); + + ui->sample_format->clear(); + auto sample_formats = device_info.supportedSampleFormats(); + for(auto sample_format: sample_formats) + { + QString sample_format_string = tr("Unknown"); + if(sample_format == QAudioFormat::UInt8) + sample_format_string = tr("Unsigned 8 bit"); + else if(sample_format == QAudioFormat::Int16) + sample_format_string = tr("Signed 16 bit"); + else if(sample_format == QAudioFormat::Int32) + sample_format_string = tr("Signed 32 bit"); + else if(sample_format == QAudioFormat::Float) + sample_format_string = tr("Float"); + + ui->sample_format->addItem(sample_format_string, sample_format); + } + + + } + } + } +} diff --git a/gui/config/gtl_gui_config_hw_widget_audio.h b/gui/config/gtl_gui_config_hw_widget_audio.h new file mode 100644 index 0000000..ce1fd6a --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget_audio.h @@ -0,0 +1,43 @@ +#ifndef GTL_GUI_CONFIG_HW_WIDGET_AUDIO_H +#define GTL_GUI_CONFIG_HW_WIDGET_AUDIO_H + +#include "gtl_gui_config_hw_widget.h" + +#include +#include + +namespace Ui { +class config_hw_widget_audio; +} + +namespace gtl +{ + namespace gui + { + namespace config + { + class hw_widget_audio : public hw_widget + { + Q_OBJECT + + public: + explicit hw_widget_audio(QWidget *parent = nullptr); + ~hw_widget_audio(); + + protected: + virtual hw::device* create_device() override; + virtual QString id() override; + virtual qreal rate() override; + + private: + Ui::config_hw_widget_audio *ui; + QMediaDevices *_devices; + + private slots: + void device_changed(int); + }; + } + } +} + +#endif // GTL_GUI_CONFIG_HW_WIDGET_AUDIO_H diff --git a/gui/config/gtl_gui_config_hw_widget_audio.ui b/gui/config/gtl_gui_config_hw_widget_audio.ui new file mode 100644 index 0000000..c6b8956 --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget_audio.ui @@ -0,0 +1,88 @@ + + + config_hw_widget_audio + + + + 0 + 0 + 400 + 98 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + Rate: + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + QAbstractSpinBox::NoButtons + + + 0 + + + + + + + Channels: + + + + + + + + + + Sample Format: + + + + + + + + + + + diff --git a/gui/config/gtl_gui_config_hw_widget_dac.cpp b/gui/config/gtl_gui_config_hw_widget_dac.cpp new file mode 100644 index 0000000..4289b2d --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget_dac.cpp @@ -0,0 +1,52 @@ +#include "gtl_gui_config_hw_widget_dac.h" +#include "ui_gtl_gui_config_hw_widget_dac.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + hw_widget_dac::hw_widget_dac(QString plugins_path, QWidget *parent) + : hw_widget(parent) + , ui(new Ui::config_hw_widget_dac) + , _hw(plugins_path) + + { + ui->setupUi(this); + + ui->type->addItems(_hw.devices()); + } + + hw_widget_dac::~hw_widget_dac() + { + delete ui; + } + + void hw_widget_dac::set_type(QString type) + { + ui->type->setCurrentText(type); + } + + QString hw_widget_dac::type() const + { + return ui->type->currentText(); + } + + hw::device *hw_widget_dac::create_device() + { + return _hw.create_device(ui->type->currentText()); + } + + QString hw_widget_dac::id() + { + return ui->id->text(); + } + + qreal hw_widget_dac::rate() + { + return ui->rate->value(); + } + } + } +} diff --git a/gui/config/gtl_gui_config_hw_widget_dac.h b/gui/config/gtl_gui_config_hw_widget_dac.h new file mode 100644 index 0000000..45b0163 --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget_dac.h @@ -0,0 +1,42 @@ +#ifndef GTL_GUI_CONFIG_HW_WIDGET_DAC_H +#define GTL_GUI_CONFIG_HW_WIDGET_DAC_H + +#include "gtl_gui_config_hw_widget.h" + +#include "hw/gtl_hw.h" + +namespace Ui { +class config_hw_widget_dac; +} + +namespace gtl +{ + namespace gui + { + namespace config + { + class hw_widget_dac : public hw_widget + { + Q_OBJECT + + public: + explicit hw_widget_dac(QString plugins_path, QWidget *parent = nullptr); + ~hw_widget_dac(); + + void set_type(QString type); + QString type() const; + + protected: + virtual hw::device* create_device() override; + virtual QString id() override; + virtual qreal rate() override; + + private: + Ui::config_hw_widget_dac *ui; + gtl::hw::hw _hw; + }; + } + } +} + +#endif // GTL_GUI_CONFIG_HW_WIDGET_DAC_H diff --git a/gui/config/gtl_gui_config_hw_widget_dac.ui b/gui/config/gtl_gui_config_hw_widget_dac.ui new file mode 100644 index 0000000..839d344 --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget_dac.ui @@ -0,0 +1,82 @@ + + + config_hw_widget_dac + + + + 0 + 0 + 400 + 72 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Type + + + + + + + + + + ID + + + + + + + + + + Rate + + + + + + + QAbstractSpinBox::NoButtons + + + 0 + + + 1.000000000000000 + + + 1000000.000000000000000 + + + 100000.000000000000000 + + + + + + + + diff --git a/gui/config/gtl_gui_config_hw_widget_generator.cpp b/gui/config/gtl_gui_config_hw_widget_generator.cpp new file mode 100644 index 0000000..8dd1d67 --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget_generator.cpp @@ -0,0 +1,40 @@ +#include "gtl_gui_config_hw_widget_generator.h" +#include "ui_gtl_gui_config_hw_widget_generator.h" + +#include "hw/gtl_hw_generator.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + hw_widget_generator::hw_widget_generator(QWidget *parent) + : hw_widget(parent) + , ui(new Ui::config_hw_widget_generator) + { + ui->setupUi(this); + } + + hw_widget_generator::~hw_widget_generator() + { + delete ui; + } + + hw::device *hw_widget_generator::create_device() + { + return new gtl::hw::generator(); + } + + QString hw_widget_generator::id() + { + return ui->id->text(); + } + + qreal hw_widget_generator::rate() + { + return ui->rate->value(); + } + } + } +} diff --git a/gui/config/gtl_gui_config_hw_widget_generator.h b/gui/config/gtl_gui_config_hw_widget_generator.h new file mode 100644 index 0000000..7e2d479 --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget_generator.h @@ -0,0 +1,36 @@ +#ifndef GTL_GUI_CONFIG_HW_WIDGET_GENERATOR_H +#define GTL_GUI_CONFIG_HW_WIDGET_GENERATOR_H + +#include "gtl_gui_config_hw_widget.h" + +namespace Ui { +class config_hw_widget_generator; +} + +namespace gtl +{ + namespace gui + { + namespace config + { + class hw_widget_generator : public hw_widget + { + Q_OBJECT + + public: + explicit hw_widget_generator(QWidget *parent = nullptr); + ~hw_widget_generator(); + + protected: + virtual hw::device* create_device() override; + virtual QString id() override; + virtual qreal rate() override; + + private: + Ui::config_hw_widget_generator *ui; + }; + } + } +} + +#endif // GTL_GUI_CONFIG_HW_WIDGET_GENERATOR_H diff --git a/gui/config/gtl_gui_config_hw_widget_generator.ui b/gui/config/gtl_gui_config_hw_widget_generator.ui new file mode 100644 index 0000000..9aa8f99 --- /dev/null +++ b/gui/config/gtl_gui_config_hw_widget_generator.ui @@ -0,0 +1,72 @@ + + + config_hw_widget_generator + + + + 0 + 0 + 400 + 48 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + ID + + + + + + + + + + Rate + + + + + + + QAbstractSpinBox::NoButtons + + + 0 + + + 1.000000000000000 + + + 1000000.000000000000000 + + + 100000.000000000000000 + + + + + + + + diff --git a/gui/config/gtl_gui_config_widget.cpp b/gui/config/gtl_gui_config_widget.cpp new file mode 100644 index 0000000..9f728e9 --- /dev/null +++ b/gui/config/gtl_gui_config_widget.cpp @@ -0,0 +1,320 @@ +#include "gtl_gui_config_widget.h" + +#include "gtl_gui_config_widget_device.h" +#include "gtl_gui_config_widget_analog.h" + +#include "hw/gtl_hw_player.h" + +#include "math/gtl_math_filter_iir.h" +#include "math/gtl_math_intg.h" +#include "math/gtl_math_diff.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + widget::widget(gtl::data_model* model, QString hwplugings_path, QWidget *parent) + : QWidget{parent} + , _model(model) + , _hw_dialog(hwplugings_path) + , _popup_node(NULL) + { + _ui.setupUi(this); + + _ui.view->setModel(_model); + _ui.view->setContextMenuPolicy(Qt::CustomContextMenu); + connect(_ui.view, &QWidget::customContextMenuRequested, this, &widget::menu_reques); + + QItemSelectionModel* selection = new QItemSelectionModel(model); + _ui.view->setSelectionModel(selection); + connect(selection, &QItemSelectionModel::currentChanged, this, &widget::selection_changed); + + + _add_hardware_action = new QAction(tr("Add hardware"), this); + connect(_add_hardware_action, &QAction::triggered, this, &widget::add_hardware); + + _add_generator_action = new QAction(tr("Add virtual generator"), this); + connect(_add_generator_action, &QAction::triggered, this, &widget::add_generator); + + _add_audio_action = new QAction(tr("Add audio input device"), this); + connect(_add_audio_action, &QAction::triggered, this, &widget::add_audio); + + + _add_iir_filter_action = new QAction(tr("Add IIR filter"), this); + connect(_add_iir_filter_action, &QAction::triggered, this, &widget::add_iir_filter); + + _add_integrator_action = new QAction(tr("Add integrator"), this); + connect(_add_integrator_action, &QAction::triggered, this, &widget::add_integrator); + + _add_differentioator_action = new QAction(tr("Add differentiator"), this); + connect(_add_differentioator_action, &QAction::triggered, this, &widget::add_differentiator); + + _add_player_action = new QAction(tr("Add player"), this); + connect(_add_player_action, &QAction::triggered, this, &widget::add_player); + + _remove_action = new QAction(tr("Remove"), this); + connect(_remove_action, &QAction::triggered, this, &widget::remove); + + _set_config_action = new QAction(tr("Set config"), this); + connect(_set_config_action, &QAction::triggered, this, &widget::set_config); + + _get_config_action = new QAction(tr("Get config"), this); + connect(_get_config_action, &QAction::triggered, this, &widget::get_config); + + connect(_model, >l::data_model::create_node, this, &widget::create_node); + + + _ui.stackedWidget->addWidget(new widget_device(this)); + _ui.stackedWidget->addWidget(new widget_analog(this)); + + + } + + void widget::save(QDomElement &root_element) + { + QDomElement devices_element = root_element.ownerDocument().createElement("devices"); + root_element.appendChild(devices_element); + + QDomElement splitter_element = root_element.ownerDocument().createElement("splitter"); + root_element.appendChild(splitter_element); + splitter_element.setAttribute("state", QString(_ui.splitter->saveState().toBase64())); + + _model->save(devices_element); + } + + void widget::load(const QDomElement &root_element) + { + clear(); + + QDomElement devices_element = root_element.firstChildElement("devices"); + + QDomElement splitter_element = root_element.firstChildElement("splitter"); + _ui.splitter->restoreState(QByteArray::fromBase64(splitter_element.attribute("state", "").toUtf8())); + + _model->load(devices_element); + } + + void widget::clear() + { + _model->clear(); + for(auto it = _devices.begin(); it != _devices.end(); it++) + delete *it; + + _devices.clear(); + } + + hw::device* widget::add(hw::device *device) + { + _model->add_device(device->gtl_device()); + _devices.push_back(device); + + return device; + } + + void widget::menu_reques(const QPoint &pos) + { + QMenu menu; + + QModelIndex index = _ui.view->indexAt(pos); + _popup_node = static_cast(index.internalPointer()); + if(_popup_node == NULL) + { + menu.addAction(_add_hardware_action); + menu.addAction(_add_generator_action); + menu.addAction(_add_audio_action); + menu.addAction(_add_player_action); + } + else + { + if(_popup_node->type() == gtl::data_model_node::device) + { +/* + menu.addAction(_set_config_action); + menu.addAction(_get_config_action); + menu.addSeparator(); +*/ + menu.addAction(_remove_action); + } + else if(_popup_node->type() == gtl::data_model_node::analog) + { + menu.addAction(_add_iir_filter_action); + menu.addAction(_add_integrator_action); + menu.addAction(_add_differentioator_action); + + if(_popup_node->parent_node()->type() != gtl::data_model_node::device) + { + menu.addSeparator(); + menu.addAction(_remove_action); + } + } + } + + menu.exec(_ui.view->viewport()->mapToGlobal(pos)); + } + + void widget::add_hardware() + { + _hw_dialog.setWindowTitle(tr("Add hardware")); + if(_hw_dialog.exec() == QDialog::Accepted) + { + if(std::find_if(_devices.begin(), _devices.end(), [=](gtl::hw::device* d){return d->gtl_device()->device_type() == _hw_dialog.type() && d->gtl_device()->id() == _hw_dialog.id();}) == _devices.end()) + { + add(_hw_dialog.create_device())->start(_hw_dialog.id(), _hw_dialog.rate()); + } + else + { + QMessageBox(QMessageBox::Icon::Warning, tr("Add device"), tr("Such device already exists.")).exec(); + } + } + } + + void widget::add_generator() + { + _hw_dialog.setWindowTitle(tr("Add generator")); + if(_hw_dialog.exec("generator") == QDialog::Accepted) + { + add(_hw_dialog.create_device("generator"))->start(_hw_dialog.id(), _hw_dialog.rate()); + } + } + + void widget::add_audio() + { + _hw_dialog.setWindowTitle(tr("Add audio input device")); + if(_hw_dialog.exec("audio") == QDialog::Accepted) + { + add(_hw_dialog.create_device("audio"))->start(_hw_dialog.id(), _hw_dialog.rate()); + } + } + + void widget::add_iir_filter() + { + new gtl::math::filter_iir(static_cast(_popup_node)); + } + + void widget::add_integrator() + { + new gtl::math::intg(static_cast(_popup_node)); + } + + void widget::add_differentiator() + { + new gtl::math::diff(static_cast(_popup_node)); + } + + void widget::add_player() + { + /* + _hw_dialog.setWindowTitle(tr("Add player")); + if(_hw_dialog.exec("player") == QDialog::Accepted) + { + add(_hw_dialog.create_device("player"))->start("", 0); + } + */ + + add(new gtl::hw::player())->start("", 0); + } + + void widget::remove() + { + //_model->remove(_popup_node); + + + auto iter_device = std::find_if(_devices.begin(), _devices.end(), [=](const gtl::hw::device* d){return d->gtl_device() == _popup_node;}); + if(iter_device != _devices.end()) + { + delete *iter_device; + _devices.erase(iter_device); + } + else + delete _popup_node; + + + } + + void widget::set_config() + { + auto iter_device = std::find_if(_devices.begin(), _devices.end(), [=](const gtl::hw::device* d){return d->gtl_device() == _popup_node;}); + + if(iter_device == _devices.end()) + return; + + QJsonDocument config = QJsonDocument::fromJson(QApplication::clipboard()->text().toUtf8()); + + if(config.isObject()) + (*iter_device)->set_config(config.object()); + else if(config.isArray()) + (*iter_device)->set_config(config.array()); + } + + void widget::get_config() + { + auto iter_device = std::find_if(_devices.begin(), _devices.end(), [=](const gtl::hw::device* d){return d->gtl_device() == _popup_node;}); + + if(iter_device == _devices.end()) + return; + + QByteArray data; + if((*iter_device)->config().isObject()) + data = QJsonDocument((*iter_device)->config().toObject()).toJson(); + else + data = QJsonDocument((*iter_device)->config().toArray()).toJson(); + + QApplication::clipboard()->setText(QString(data)); + + } + + gtl::data_model_node* widget::create_node(gtl::data_model_node* parent, QDomElement &node_element) + { + gtl::data_model_node* node = NULL; + + if(node_element.attribute("node", "") == "gtl::device") + { + QString type = node_element.attribute("type", ""); + + gtl::hw::device* d = NULL; + if(type == "player") + d = new gtl::hw::player(); + else + d = _hw_dialog.create_device(type); + + if(d) + { + node = d->gtl_device(); + _devices.push_back(d); + } + } + else if(node_element.attribute("node", "") == "gtl::math::filter_iir") + { + node = new gtl::math::filter_iir(static_cast(parent)); + } + else if(node_element.attribute("node", "") == "gtl::math::intg") + { + node = new gtl::math::intg(static_cast(parent)); + } + else if(node_element.attribute("node", "") == "gtl::math::diff") + { + node = new gtl::math::diff(static_cast(parent)); + } + + return node; + } + + void widget::selection_changed(const QModelIndex ¤t, const QModelIndex&) + { + if(!current.isValid()) + { + _ui.stackedWidget->setCurrentIndex(0); + return; + } + + + gtl::data_model_node* node = static_cast(current.internalPointer()); + + static_cast(_ui.stackedWidget->widget(node->type()))->set_node(node); + _ui.stackedWidget->setCurrentIndex(node->type()); + } + } + } +} diff --git a/gui/config/gtl_gui_config_widget.h b/gui/config/gtl_gui_config_widget.h new file mode 100644 index 0000000..c6ec60c --- /dev/null +++ b/gui/config/gtl_gui_config_widget.h @@ -0,0 +1,85 @@ +#ifndef CONFIG_WIDGET_H +#define CONFIG_WIDGET_H + +#include +#include +#include +#include +#include + +#include "core/gtl_data_model.h" +#include "hw/gtl_hw_device.h" + +#include "gui/config/gtl_gui_config_hardware_dialog.h" +#include "gui/ui_gtl_gui_config_widget.h" +#include "gui/gui_global.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + class GTL_GUI_EXPORT widget : public QWidget + { + Q_OBJECT + public: + explicit widget(gtl::data_model* model, QString hwplugings_path, QWidget *parent = nullptr); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + void clear(); + + private: + Ui::config_widget _ui; + + gtl::data_model* _model; + + QAction* _add_hardware_action; + QAction* _add_generator_action; + QAction* _add_player_action; + QAction* _add_audio_action; + + QAction* _add_iir_filter_action; + QAction* _add_integrator_action; + QAction* _add_differentioator_action; + + QAction* _remove_action; + QAction* _set_config_action; + QAction* _get_config_action; + + + QList _devices; + + gtl::gui::config::hardware_dialog _hw_dialog; + + gtl::data_model_node* _popup_node; + + private: + hw::device* add(gtl::hw::device* device); + + signals: + + private slots: + void menu_reques(const QPoint &pos); + void add_hardware(); + void add_generator(); + void add_audio(); + void add_iir_filter(); + void add_integrator(); + void add_differentiator(); + void add_player(); + void remove(); + void set_config(); + void get_config(); + + gtl::data_model_node* create_node(gtl::data_model_node* parent, QDomElement& device_element); + void selection_changed(const QModelIndex ¤t, const QModelIndex &prev); + + }; + } + } +} + +#endif // CONFIG_WIDGET_H diff --git a/gui/config/gtl_gui_config_widget.ui b/gui/config/gtl_gui_config_widget.ui new file mode 100644 index 0000000..7e553a0 --- /dev/null +++ b/gui/config/gtl_gui_config_widget.ui @@ -0,0 +1,51 @@ + + + config_widget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + + + + + + + + + + diff --git a/gui/config/gtl_gui_config_widget_analog.cpp b/gui/config/gtl_gui_config_widget_analog.cpp new file mode 100644 index 0000000..718a107 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_analog.cpp @@ -0,0 +1,297 @@ + +#include +#include + +#include "gtl_gui_config_widget_analog.h" +#include "ui_gtl_gui_config_widget_analog.h" + +#include "hw/gtl_analog_input.h" +#include "hw/gtl_hw_generator_analog_input.h" +#include "hw/gtl_hw_player_analog_input.h" +#include "math/gtl_math_intg.h" +#include "math/gtl_math_diff.h" +#include "gui/gtl_gui_chart_widget.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + widget_analog::widget_analog(QWidget *parent) : + widget_node(parent), + ui(new Ui::config_widget_analog), + _filter_info(new gtl::math::filter_iir_info()) + { + ui->setupUi(this); + _color = new color_box(this); + _color->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); + ui->color_layout->setWidget(0, QFormLayout::ItemRole::FieldRole, _color); + + _filter_response_chart = new config::filter_response_chart(this); + ui->page_3->layout()->addWidget(new gtl::gui::chart_widget(_filter_response_chart)); + } + + widget_analog::~widget_analog() + { + delete ui; + delete _filter_info; + } + + void widget_analog::set_node(data_model_node *node) + { + _color->disconnect(); + _color->set_color(static_cast(node)->color()); + connect(_color, &color_box::icolor_changed, static_cast(node), >l::analog_data::set_color); + + QString class_name(node->metaObject()->className()); + if(class_name == "gtl::hw::analog_input") + { + gtl::hw::analog_input* input = static_cast(node); + ui->stackedWidget->setCurrentIndex(0); + + ui->sensitivity->disconnect(); + ui->sensitivity->setValue(input->sensitivity()); + connect(ui->sensitivity, &QDoubleSpinBox::valueChanged, input, >l::hw::analog_input::set_sensitivity); + + ui->gain->disconnect(); + ui->gain->setValue(input->gain()); + connect(ui->gain, &QDoubleSpinBox::valueChanged, input, >l::hw::analog_input::set_gain); + + ui->offset->disconnect(); + ui->offset->setValue(input->offset()); + connect(ui->offset, &QDoubleSpinBox::valueChanged, input, >l::hw::analog_input::set_offset); + + ui->reference->disconnect(); + ui->reference->setValue(input->reference()); + connect(ui->reference, &QDoubleSpinBox::valueChanged, input, >l::hw::analog_input::set_reference); + + ui->iepe->disconnect(); + ui->iepe->setChecked(input->is_iepe()); + connect(ui->iepe, &QCheckBox::stateChanged, input, >l::hw::analog_input::set_iepe); + + ui->coupling->disconnect(); + ui->coupling->setCurrentIndex(input->is_coupling()); + connect(ui->coupling, &QComboBox::currentIndexChanged, input, >l::hw::analog_input::set_coupling); + + ui->inverse->disconnect(); + ui->inverse->setChecked(input->is_inverting()); + connect(ui->inverse, &QCheckBox::stateChanged, input, >l::hw::analog_input::set_inverting); + } + else if(class_name == "gtl::hw::generator_analog_input") + { + gtl::hw::generator_analog_input* input = static_cast(node); + ui->stackedWidget->setCurrentIndex(1); + + ui->freq->disconnect(); + ui->freq->setValue(input->freq()); + connect(ui->freq, &QDoubleSpinBox::valueChanged, input, >l::hw::generator_analog_input::set_freq); + + ui->phase->disconnect(); + ui->phase->setValue(input->phase()); + connect(ui->phase, &QDoubleSpinBox::valueChanged, input, >l::hw::generator_analog_input::set_phase); + + } + else if(class_name == "gtl::math::filter_iir") + { + _filter = static_cast(node); + + ui->stackedWidget->setCurrentIndex(2); + + ui->filter_frequency->disconnect(); + ui->filter_frequency->setValue(_filter->frequency()); + connect(ui->filter_frequency, &QDoubleSpinBox::valueChanged, _filter, >l::math::filter_iir::set_frequency); + + ui->filter_order->disconnect(); + ui->filter_order->setValue(_filter->order()); + connect(ui->filter_order, &QSpinBox::valueChanged, _filter, >l::math::filter_iir::set_order); + + ui->filter_gain->disconnect(); + ui->filter_gain->setValue(_filter->gain()); + connect(ui->filter_gain, &QDoubleSpinBox::valueChanged, _filter, >l::math::filter_iir::set_gain); + + ui->filter_width->disconnect(); + ui->filter_width->setValue(_filter->width()); + connect(ui->filter_width, &QDoubleSpinBox::valueChanged, _filter, >l::math::filter_iir::set_width); + + ui->filter_slope->disconnect(); + ui->filter_slope->setValue(_filter->slope()); + connect(ui->filter_slope, &QDoubleSpinBox::valueChanged, _filter, >l::math::filter_iir::set_slope); + + ui->filter_q->disconnect(); + ui->filter_q->setValue(_filter->q()); + connect(ui->filter_q, &QDoubleSpinBox::valueChanged, _filter, >l::math::filter_iir::set_q); + + ui->filter_ripple->disconnect(); + ui->filter_ripple->setValue(_filter->ripple()); + connect(ui->filter_ripple, &QDoubleSpinBox::valueChanged, _filter, >l::math::filter_iir::set_ripple); + + ui->filter_stop->disconnect(); + ui->filter_stop->setValue(_filter->stop()); + connect(ui->filter_stop, &QDoubleSpinBox::valueChanged, _filter, >l::math::filter_iir::set_stop); + + /// set kind /// + ui->filter_kind->disconnect(); + ui->filter_kind->clear(); + + for (QDomNode dom = _filter_info->info().firstChild(); + !dom.isNull(); + dom = dom.nextSibling()) + { + if (dom.isElement()) + { + ui->filter_kind->addItem(dom.nodeName()); + if( _filter->kind() == _filter_info->kind(dom.nodeName())) + ui->filter_kind->setCurrentIndex(ui->filter_kind->count() - 1); + } + + } + + connect(ui->filter_kind, &QComboBox::currentIndexChanged, this, &widget_analog::filter_kind_changed); + filter_kind_changed(); + + + _filter_response_chart->set_filter(_filter); + /////////////// + } + else if(class_name == "gtl::math::intg") + { + gtl::math::intg* intg = static_cast(node); + ui->stackedWidget->setCurrentIndex(3); + + ui->intg_taps->disconnect(); + ui->intg_taps->setValue(intg->taps()); + connect(ui->intg_taps, &QSpinBox::valueChanged, intg, >l::math::intg::set_taps); + + } + else if(class_name == "gtl::math::diff") + { + gtl::math::diff* diff = static_cast(node); + ui->stackedWidget->setCurrentIndex(4); + + ui->diff_taps->disconnect(); + ui->diff_taps->setValue(diff->taps()); + connect(ui->diff_taps, &QSpinBox::valueChanged, diff, >l::math::diff::set_taps); + } + else if(class_name == "gtl::hw::player_analog_input") + { + ui->player_analog_input_info->setPlainText(QJsonDocument(static_cast(node)->info()).toJson()); + ui->stackedWidget->setCurrentIndex(5); + } + + } + + void widget_analog::set_color_visible(bool value) + { + ui->color_label->setVisible(value); + _color->setVisible(value); + } + + void widget_analog::filter_kind_changed() + { + if(_filter) + { + QString name = ui->filter_kind->currentText(); + + ui->filter_type->disconnect(); + ui->filter_type->clear(); + + QDomNode kind = _filter_info->info().namedItem(name); + for (QDomNode type = kind.firstChild(); + !type.isNull(); + type = type.nextSibling()) + { + if (type.isElement()) + { + ui->filter_type->addItem(type.nodeName()); + if(_filter_info->type(type.nodeName()) == _filter->type()) + ui->filter_type->setCurrentIndex(ui->filter_type->count() - 1); + } + } + _filter->set_kind(_filter_info->kind(name)); + } + + connect(ui->filter_type, &QComboBox::currentIndexChanged, this, &widget_analog::filter_type_changed); + filter_type_changed(); + + } + + + void widget_analog::filter_type_changed() + { + if(_filter) + { + filter_show_property("none"); + QDomNode kind = _filter_info->info().namedItem(ui->filter_kind->currentText()); + QDomElement type = kind.namedItem(ui->filter_type->currentText()).toElement(); + + if(type.hasAttributes()) + for(int i = 0; i < 6; i++) + { + QString prop = type.attribute(QString::number(i)); + if(prop != "") + filter_show_property(prop); + else + break; + } + _filter->set_type(_filter_info->type(ui->filter_type->currentText())); + } + } + + void widget_analog::filter_show_property(QString property) + { + if(property == QString("gain")) + { + ui->filter_gain_label->setVisible(true); + ui->filter_gain->setVisible(true); + ui->filter_gain->setValue(_filter->gain()); + } + else if(property == QString("q")) + { + ui->filter_q_label->setVisible(true); + ui->filter_q->setVisible(true); + ui->filter_q->setValue(_filter->q()); + } + else if(property == QString("ripple")) + { + ui->filter_ripple_label->setVisible(true); + ui->filter_ripple->setVisible(true); + ui->filter_ripple->setValue(_filter->ripple()); + } + else if(property == QString("slope")) + { + ui->filter_slope_label->setVisible(true); + ui->filter_slope->setVisible(true); + ui->filter_slope->setValue(_filter->slope()); + } + else if(property == QString("stop")) + { + ui->filter_stop_label->setVisible(true); + ui->filter_stop->setVisible(true); + ui->filter_stop->setValue(_filter->stop()); + } + else if(property == QString("width")) + { + ui->filter_width_label->setVisible(true); + ui->filter_width->setVisible(true); + ui->filter_width->setValue(_filter->width()); + } + else + { + ui->filter_gain_label->setVisible(false); + ui->filter_gain->setVisible(false); + ui->filter_q_label->setVisible(false); + ui->filter_q->setVisible(false); + ui->filter_ripple_label->setVisible(false); + ui->filter_ripple->setVisible(false); + ui->filter_slope_label->setVisible(false); + ui->filter_slope->setVisible(false); + ui->filter_stop_label->setVisible(false); + ui->filter_stop->setVisible(false); + ui->filter_width_label->setVisible(false); + ui->filter_width->setVisible(false); + } + } + } + } +} diff --git a/gui/config/gtl_gui_config_widget_analog.h b/gui/config/gtl_gui_config_widget_analog.h new file mode 100644 index 0000000..3fc5fb4 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_analog.h @@ -0,0 +1,53 @@ +#ifndef GTL_GUI_CONFIG_WIDGET_ANALOG_H +#define GTL_GUI_CONFIG_WIDGET_ANALOG_H + +#include + +#include "gui/gtl_gui_color_box.h" +#include "gui/config/gtl_gui_config_filter_response_chart.h" +#include "gtl_gui_config_widget_node.h" +#include "math/gtl_math_filter_iir.h" + +#include "gui/gui_global.h" + +namespace Ui { +class config_widget_analog; +} + +namespace gtl +{ + namespace gui + { + namespace config + { + class GTL_GUI_EXPORT widget_analog : public widget_node + { + Q_OBJECT + + public: + explicit widget_analog(QWidget *parent = nullptr); + ~widget_analog(); + + virtual void set_node(gtl::data_model_node* node) override; + + void set_color_visible(bool value); + + private slots: + void filter_kind_changed(); + void filter_type_changed(); + // + + private: + Ui::config_widget_analog *ui; + color_box* _color; + config::filter_response_chart* _filter_response_chart; + + private: + gtl::math::filter_iir *_filter; + gtl::math::filter_iir_info *_filter_info; + void filter_show_property(QString property); + }; + } + } +} +#endif // GTL_GUI_CONFIG_WIDGET_ANALOG_H diff --git a/gui/config/gtl_gui_config_widget_analog.ui b/gui/config/gtl_gui_config_widget_analog.ui new file mode 100644 index 0000000..da348b9 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_analog.ui @@ -0,0 +1,723 @@ + + + config_widget_analog + + + + 0 + 0 + 400 + 488 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + QLayout::SetDefaultConstraint + + + QFormLayout::ExpandingFieldsGrow + + + QFormLayout::DontWrapRows + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Color + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 2 + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 6 + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Sensitivity + + + + + + + QAbstractSpinBox::NoButtons + + + 6 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + Gain + + + + + + + QAbstractSpinBox::NoButtons + + + 6 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + Offset + + + + + + + QAbstractSpinBox::NoButtons + + + 6 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + Reference + + + + + + + QAbstractSpinBox::NoButtons + + + 6 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + Inverse + + + + + + + + 0 + 22 + + + + + + + + + + + IEPE + + + + + + + + 0 + 22 + + + + + + + + + + + Coupling + + + + + + + + AC/DC + + + + + AC + + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Signal + + + + + + + + Sin + + + + + + + + QAbstractSpinBox::CorrectToNearestValue + + + 3 + + + 0.001000000000000 + + + 1000000.000000000000000 + + + + + + + Frequency + + + + + + + Phase + + + + + + + QAbstractSpinBox::CorrectToNearestValue + + + 1 + + + 0.000000000000000 + + + 360.000000000000000 + + + + + + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + QFormLayout::FieldsStayAtSizeHint + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 0 + + + + + Kind + + + + + + + + + + Type + + + + + + + + + + Order + + + + + + + 1 + + + 100 + + + + + + + Frequency + + + + + + + 3 + + + 0.001000000000000 + + + 1000000.000000000000000 + + + + + + + Gain + + + + + + + -100.000000000000000 + + + 100.000000000000000 + + + + + + + Width + + + + + + + 3 + + + 0.001000000000000 + + + 10000.000000000000000 + + + 0.001000000000000 + + + + + + + Ripple + + + + + + + 3 + + + 0.001000000000000 + + + 100.000000000000000 + + + 1.000000000000000 + + + + + + + Stop + + + + + + + 3 + + + 0.001000000000000 + + + 1000000.000000000000000 + + + + + + + Q + + + + + + + -100.000000000000000 + + + 100.000000000000000 + + + + + + + Slope + + + + + + + -100.000000000000000 + + + 100.000000000000000 + + + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Taps + + + + + + + 1 + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Taps + + + + + + + 1 + + + + + + + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + Courier New + + + + QPlainTextEdit::NoWrap + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + + + + + diff --git a/gui/config/gtl_gui_config_widget_device.cpp b/gui/config/gtl_gui_config_widget_device.cpp new file mode 100644 index 0000000..a918653 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_device.cpp @@ -0,0 +1,49 @@ +#include "gtl_gui_config_widget_device.h" +#include "ui_gtl_gui_config_widget_device.h" + + +#include "core/gtl_device.h" +#include "gui/config/gtl_gui_config_widget_player.h" + + +namespace gtl +{ + namespace gui + { + namespace config + { + widget_device::widget_device(QWidget *parent) : + widget_node(parent), + ui(new Ui::config_widget_device) + { + ui->setupUi(this); + _indices.insert(std::map::value_type("player", ui->stackedWidget->addWidget(new widget_player(this)))); + } + + widget_device::~widget_device() + { + delete ui; + } + + void widget_device::set_node(data_model_node *node) + { + if(QString(node->metaObject()->className()) != "gtl::device") + { + ui->stackedWidget->setCurrentIndex(0); + } + if(static_cast(node)->device_type() == "player") + { + static_cast(ui->stackedWidget->widget(_indices["player"]))->set_player(static_cast(node)); + ui->stackedWidget->setCurrentIndex(_indices["player"]); + + } + else + { + ui->stackedWidget->setCurrentIndex(1); + ui->id->setText(static_cast(node)->id()); + ui->rate->setValue(static_cast(node)->rate()); + } + } + } + } +} diff --git a/gui/config/gtl_gui_config_widget_device.h b/gui/config/gtl_gui_config_widget_device.h new file mode 100644 index 0000000..fa738c3 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_device.h @@ -0,0 +1,34 @@ +#ifndef GTL_GUI_CONFIG_WIDGET_DEVICE_H +#define GTL_GUI_CONFIG_WIDGET_DEVICE_H + +#include "gtl_gui_config_widget_node.h" + +namespace Ui { +class config_widget_device; +} + +namespace gtl +{ + namespace gui + { + namespace config + { + class widget_device : public widget_node + { + Q_OBJECT + + public: + explicit widget_device(QWidget *parent = nullptr); + ~widget_device(); + + virtual void set_node(gtl::data_model_node* node) override; + + private: + Ui::config_widget_device *ui; + std::map _indices; + }; + } + } +} + +#endif // GTL_GUI_CONFIG_WIDGET_DEVICE_H diff --git a/gui/config/gtl_gui_config_widget_device.ui b/gui/config/gtl_gui_config_widget_device.ui new file mode 100644 index 0000000..95afb2b --- /dev/null +++ b/gui/config/gtl_gui_config_widget_device.ui @@ -0,0 +1,150 @@ + + + config_widget_device + + + + 0 + 0 + 389 + 300 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + + QLayout::SetDefaultConstraint + + + 1 + + + 1 + + + 1 + + + 1 + + + + + QFormLayout::FieldsStayAtSizeHint + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 6 + + + 2 + + + + + + 0 + 0 + + + + ID + + + + + + + false + + + + 0 + 0 + + + + + + + + false + + + + 0 + 0 + + + + QAbstractSpinBox::NoButtons + + + 0 + + + 10000000.000000000000000 + + + + + + + Rate + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + diff --git a/gui/config/gtl_gui_config_widget_node.cpp b/gui/config/gtl_gui_config_widget_node.cpp new file mode 100644 index 0000000..1f9bc94 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_node.cpp @@ -0,0 +1,16 @@ +#include "gtl_gui_config_widget_node.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + widget_node::widget_node(QWidget *parent) + : QWidget{parent} + { + + } + } + } +} diff --git a/gui/config/gtl_gui_config_widget_node.h b/gui/config/gtl_gui_config_widget_node.h new file mode 100644 index 0000000..7026a3c --- /dev/null +++ b/gui/config/gtl_gui_config_widget_node.h @@ -0,0 +1,29 @@ +#ifndef CONFIG_WIDGET_NODE_H +#define CONFIG_WIDGET_NODE_H + +#include + +#include "core/gtl_data_model_node.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + class widget_node : public QWidget + { + Q_OBJECT + public: + explicit widget_node(QWidget *parent = nullptr); + + virtual void set_node(gtl::data_model_node* node) = 0; + + signals: + + }; + } + } +} + +#endif // CONFIG_WIDGET_NODE_H diff --git a/gui/config/gtl_gui_config_widget_player.cpp b/gui/config/gtl_gui_config_widget_player.cpp new file mode 100644 index 0000000..14c8f26 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_player.cpp @@ -0,0 +1,247 @@ +#include "gtl_gui_config_widget_player.h" +#include "ui_gtl_gui_config_widget_player.h" + +#include "gui/gtl_gui_chart_widget.h" +#include "gui/player/gtl_gui_player_resampling_dialog.h" + +#include +#include + +namespace gtl +{ + namespace gui + { + namespace config + { + widget_player::widget_player(QWidget *parent) + : QWidget(parent) + , ui(new Ui::config_widget_player) + , _files(this) + , _selection(&_files) + , _player(NULL) + + { + ui->setupUi(this); + + ui->play->setEnabled(false); + + QHBoxLayout* layout = new QHBoxLayout(ui->progress); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(ui->time); + ui->progress->setLayout(layout); + + + _files.setNameFilterDisables(false); + _files.setNameFilters(QStringList() << "*.gtr" << "*.wav"); + _files.setFilter(QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot); + ui->files->setModel(&_files); + ui->files->setSelectionModel(&_selection); + + connect(&_selection, &QItemSelectionModel::currentChanged, this, &widget_player::selection_changed); + + ui->files->setRootIndex(_files.setRootPath("")); + ui->files->setSortingEnabled(true); + ui->files->header()->setSectionsClickable(true); + ui->files->header()->setSortIndicatorShown(true); + ui->files->header()->setSortIndicator(0, Qt::SortOrder::AscendingOrder); + + connect(ui->play, &QAbstractButton::clicked, this, &widget_player::play); + connect(ui->cyclic, &QAbstractButton::clicked, this, &widget_player::set_cyclic); + + _chart = new player::chart(this); + chart_widget *chart_widget_ = new chart_widget(_chart, this); + ui->verticalLayout->insertWidget(0, chart_widget_); + + connect(&_timer, &QTimer::timeout, this, &widget_player::update_state); + + _timer.start(100); + + connect(ui->save_as_button, &QAbstractButton::clicked, this, &widget_player::save_as); + + ui->path->setClearButtonEnabled(true); + ui->path->setPlaceholderText("Root dir ..."); + + connect(ui->select_path_button, &QAbstractButton::clicked, this, &widget_player::select_path); + connect(ui->path, &QLineEdit::textChanged, this, &widget_player::set_path); + + QSettings settings; + ui->splitter->restoreState(settings.value("player/splitter").toByteArray()); + QList columns_width = settings.value("player/view/columns").toList(); + for(int i = 0; i < qMin(columns_width.count(), _files.columnCount()); i++) + ui->files->setColumnWidth(i, columns_width[i].toInt()); + + } + + widget_player::~widget_player() + { + QSettings settings; + + settings.setValue("player/splitter", QVariant(ui->splitter->saveState())); + QList columns_width; + for(int i = 0; i < _files.columnCount(); i++) + columns_width.push_back(ui->files->columnWidth(i)); + settings.setValue("player/view/columns", QVariant(columns_width)); + + + delete ui; + } + + void widget_player::set_player(gtl::device *node) + { + + if(_player) + { + _to_save = false; + save_player_state(); + disconnect(_player, NULL, this, NULL); + } + + _to_save = true; + + _player = node; + + ui->cyclic->setChecked(_player->get_parameter(2).toBool()); + + connect(_player, >l::device::stopped, this, &widget_player::player_stopped); + + ui->files->clearSelection(); + + QModelIndex index; + if(_player) + index = _files.index(node->id()); + + _selection.setCurrentIndex(index, QItemSelectionModel::Select); + + ui->path->setText(QSettings().value("player/root_path/" + get_player_uuid()).toString()); + + } + + void widget_player::save_player_state() + { + if(_player) + { + QDomDocument state_doc; + QDomElement player_element = state_doc.createElement("root"); + state_doc.appendChild(player_element); + _player->save_childs(player_element); + QFile file(_player->id() + ".conf"); + if(file.open(QFile::WriteOnly)) + { + file.write(state_doc.toByteArray()); + file.close(); + } + } + } + + QString widget_player::get_player_uuid() const + { + if(_player == nullptr) + return ""; + + return _player->get_parameter(7).toString(); + } + + void widget_player::selection_changed(const QModelIndex ¤t, const QModelIndex &previous) + { + QString path = _files.filePath(current); + + if(_player) + { + if(_to_save) + save_player_state(); + + + _player->start(path, -1); + qreal total_time = _player->get_parameter(0).toDouble(); + + ui->play->setEnabled(total_time != 0); + ui->cyclic->setEnabled(total_time != 0); + ui->progress->setMaximum(qRound(total_time*1000)); + + QFile file(path + ".conf"); + if(file.open(QFile::ReadOnly)) + { + QDomDocument state_doc; + state_doc.setContent(&file); + file.close(); + + _player->load_childs(state_doc.firstChildElement("root")); + } + } + + + + _chart->set_file(path); + + ui->play->setChecked(false); + } + + void widget_player::play(bool value) + { + if(value) + { + _chart->save_ranges(); + _player->start(_files.filePath(ui->files->currentIndex()), 0); + ui->progress->setMaximum(qRound(_player->get_parameter(0).toDouble()*1000)); + } + else + _player->stop(); + } + + void widget_player::set_cyclic(bool value) + { + _player->set_parameter(2, value); + } + + void widget_player::save_as() + { + gtl::gui::player::resampling_dialog* save_as_dialog = new gtl::gui::player::resampling_dialog(_player->id(), this); + + save_as_dialog->exec(); + + delete save_as_dialog; + } + + void widget_player::player_stopped() + { + qDebug() << "player stopped"; + ui->play->setChecked(false); + } + + void widget_player::update_state() + { + if(_player) + { + qreal time = _player->get_parameter(1).toDouble(); + ui->time->setText(QString::number(time, 'f', 3) + "/" + QString::number(ui->progress->maximum()/1000.0, 'f', 3)); + ui->progress->setValue(qRound(time*1000)); + _chart->set_position(_player->get_parameter(6).toDouble()); + } + else + { + ui->time->setText(""); + } + } + + void widget_player::select_path() + { + QString path = QFileDialog::getExistingDirectory(this, tr("Select root path"), ui->path->text()); + if(path.isEmpty()) + return; + + ui->path->setText(path); + } + + void widget_player::set_path(QString path) + { + QString current_path = _files.filePath(ui->files->currentIndex()).toLower(); + ui->files->setRootIndex(_files.index(path)); + QSettings().setValue("player/root_path/" + get_player_uuid(), path); + if(!current_path.contains(path.toLower())) + { + ui->files->setCurrentIndex(ui->files->rootIndex()); + } + } + } + } +} diff --git a/gui/config/gtl_gui_config_widget_player.h b/gui/config/gtl_gui_config_widget_player.h new file mode 100644 index 0000000..fd6c129 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_player.h @@ -0,0 +1,67 @@ +#ifndef GTL_GUI_CONFIG_WIDGET_PLAYER_H +#define GTL_GUI_CONFIG_WIDGET_PLAYER_H + +#include +#include +#include + +#include "core/gtl_device.h" +#include "gui/config/gtl_gui_config_widget_player_files.h" +#include "gui/player/gtl_gui_player_chart.h" + +namespace Ui { +class config_widget_player; +} + +namespace gtl +{ + namespace gui + { + namespace config + { + class widget_player : public QWidget + { + Q_OBJECT + + public: + explicit widget_player(QWidget *parent = nullptr); + ~widget_player(); + + void set_player(gtl::device* node); + + private: + Ui::config_widget_player *ui; + + widget_player_files _files; + QItemSelectionModel _selection; + + gtl::device* _player; + player::chart *_chart; + + QTimer _timer; + + bool _to_save; + + private: + void save_player_state(); + QString get_player_uuid() const; + + + private slots: + void selection_changed(const QModelIndex ¤t, const QModelIndex &previous); + void play(bool value); + void set_cyclic(bool value); + void save_as(); + + void player_stopped(); + + void update_state(); + + void select_path(); + void set_path(QString path); + }; + } + } +} + +#endif // GTL_GUI_CONFIG_WIDGET_PLAYER_H diff --git a/gui/config/gtl_gui_config_widget_player.ui b/gui/config/gtl_gui_config_widget_player.ui new file mode 100644 index 0000000..3f52d80 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_player.ui @@ -0,0 +1,192 @@ + + + config_widget_player + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + + 2 + + + + + + + false + + + + + + + ... + + + + + + + + + + + + + + + + + + Save as + + + ... + + + + :/player/resources/save_32.png:/player/resources/save_32.png + + + + 24 + 24 + + + + false + + + QToolButton::DelayedPopup + + + true + + + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + 24 + + + false + + + + + + + Stop + + + ... + + + + :/player/resources/REFRESH.png:/player/resources/REFRESH.png + + + + 24 + 24 + + + + true + + + QToolButton::DelayedPopup + + + true + + + + + + + Play/Pause + + + ... + + + + :/player/play + :/player/resources/stop.png:/player/play + + + + 24 + 24 + + + + true + + + false + + + QToolButton::DelayedPopup + + + true + + + + + + + + + + + + + + + + diff --git a/gui/config/gtl_gui_config_widget_player_files.cpp b/gui/config/gtl_gui_config_widget_player_files.cpp new file mode 100644 index 0000000..eeaf501 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_player_files.cpp @@ -0,0 +1,92 @@ +#include "gtl_gui_config_widget_player_files.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + widget_player_files::widget_player_files(QObject *parent) + : QFileSystemModel(parent) + { + + } + + widget_player_files::~widget_player_files() + { + + } + + int widget_player_files::columnCount(const QModelIndex &parent /* = QModelIndex() */) const + { + return /*QFileSystemModel::columnCount(parent) + 1*/3; + } + + QVariant widget_player_files::data(const QModelIndex &index, int role /*= Qt::DisplayRole*/) const + { + QString value; + + if (role == Qt::DisplayRole) + { + if (index.column() == 2) + { + QFileInfo info = fileInfo(index); + if (info.isFile()) + return QVariant(info.lastModified()); + else + return ""; + } + + } + else if (role == Qt::ToolTipRole) + { + //QString str_info; + //QVariant value; + QFileInfo info = fileInfo(index); + if (info.suffix().toLower() == "gtr") + { + gtl::hw::player_file_gtr file(NULL, filePath(index)); + value = file.info(); + } + else if (info.suffix().toLower() == "wav") + { + gtl::hw::player_file_wav file(NULL, filePath(index)); + value = file.info(); + } + //value = "1234"; + + //return value; + return value; + + } + + //return data; + + return QFileSystemModel::data(index, role); + } + + QVariant widget_player_files::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const + { + + if (role == Qt::DisplayRole) + { + if (orientation == Qt::Orientation::Horizontal) + { + if (section == 0) + return tr("name"); + else if (section == 1) + return tr("size"); + else if (section == 2) + return tr("date modified"); + else if (section == 3) + return tr("info"); + // else + // return ""; + } + } + + return QFileSystemModel::headerData(section, orientation, role); + } + } + } +} diff --git a/gui/config/gtl_gui_config_widget_player_files.h b/gui/config/gtl_gui_config_widget_player_files.h new file mode 100644 index 0000000..cb1b1c2 --- /dev/null +++ b/gui/config/gtl_gui_config_widget_player_files.h @@ -0,0 +1,35 @@ +#ifndef GTL_GUI_CONFIG_WIDGET_PLAYER_FILES_H +#define GTL_GUI_CONFIG_WIDGET_PLAYER_FILES_H + +#include +#include + +#include "hw/gtl_hw_player_file_gtr.h" +#include "hw/gtl_hw_player_file_wav.h" + +namespace gtl +{ + namespace gui + { + namespace config + { + class widget_player_files : public QFileSystemModel + { + Q_OBJECT + + public: + widget_player_files(QObject *parent); + ~widget_player_files(); + + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + private: + + }; + } + } +} + +#endif // GTL_GUI_CONFIG_WIDGET_PLAYER_FILES_H diff --git a/gui/gtl_gui.cpp b/gui/gtl_gui.cpp new file mode 100644 index 0000000..c4c13f9 --- /dev/null +++ b/gui/gtl_gui.cpp @@ -0,0 +1,5 @@ +#include "gtl_gui.h" + +Gui::Gui() +{ +} diff --git a/gui/gtl_gui.h b/gui/gtl_gui.h new file mode 100644 index 0000000..1083849 --- /dev/null +++ b/gui/gtl_gui.h @@ -0,0 +1,12 @@ +#ifndef GUI_H +#define GUI_H + +#include "gui_global.h" + +class GTL_GUI_EXPORT Gui +{ +public: + Gui(); +}; + +#endif // GUI_H diff --git a/gui/gtl_gui_apfc_chart.cpp b/gui/gtl_gui_apfc_chart.cpp new file mode 100644 index 0000000..e09b9aa --- /dev/null +++ b/gui/gtl_gui_apfc_chart.cpp @@ -0,0 +1,244 @@ +#include "gtl_gui_apfc_chart.h" + +namespace gtl { + namespace gui { + + apfc_chart::apfc_chart(QWidget* parent) + : chart(parent) + , _afc(0) + , _pfc(0) + , _frequency(200) + , _resolution(10) + , _window(5) + , _lines(20) + , _average(1) + , _overlap(0) + , _threshold(50) + { + set_axis_y_mode(true); + } + + apfc_chart::~apfc_chart() + { + if(_apfc) + delete _apfc; + } + + void apfc_chart::set_x_log(bool value) + { + + } + + chart_series *apfc_chart::create_series(analog_data *ai) + { + + if(!_series.size()) + _ref = ai; + + if(_series.size() == 1 && static_cast(_series[0])->ad() != _ref) + _ref = _data; + + _data = ai; + + while(_series.size()) + remove_series(static_cast(_series.back())); + + + if(!_apfc) + { + _apfc = new gtl::math::apfc(_ref, _data); + connect(_apfc, >l::math::apfc::afc_changed, this, >l::gui::apfc_chart::afc_changed); + connect(_apfc, >l::math::apfc::pfc_changed, this, >l::gui::apfc_chart::pfc_changed); + connect(_apfc, >l::math::apfc::frequency_changed, this, >l::gui::apfc_chart::frequency_changed); + connect(_apfc, >l::math::apfc::resolution_changed, this, >l::gui::apfc_chart::resolution_changed); + connect(_apfc, >l::math::apfc::window_changed, this, >l::gui::apfc_chart::window_changed); + connect(_apfc, >l::math::apfc::lines_changed, this, >l::gui::apfc_chart::lines_changed); + connect(_apfc, >l::math::apfc::average_changed, this, >l::gui::apfc_chart::average_changed); + connect(_apfc, >l::math::apfc::overlap_changed, this, >l::gui::apfc_chart::overlap_changed); + connect(_apfc, >l::math::apfc::threshold_changed, this, >l::gui::apfc_chart::threshold_changed); + + _apfc->set_average(_average); + _apfc->set_overlap(_overlap); + _apfc->set_frequency(_frequency); + _apfc->set_lines(_lines); + _apfc->set_window(static_cast(_window)); + _apfc->set_afc(static_cast(_afc)); + _apfc->set_pfc(static_cast(_pfc)); + _apfc->set_threshold(_threshold); + } + _apfc->set_ref(_ref); + _apfc->set_data(_data); + + _signals.clear(); + _signals.push_back(_ref->name()); + _signals.push_back(_data->name()); + emit signals_changed(&_signals); + + + apfc_series *seriesA = new apfc_series(is_updating(), true, _apfc, _ref, _axis_x, _axis_y); + seriesA->set_name(tr("AFC")); + add_series(seriesA); + + apfc_series *series = new apfc_series(is_updating(), false, _apfc, _data, _axis_x, _axis_y); + series->set_name(tr("PFC")); + series->update(); + + return series; + } + + qreal apfc_chart::threshold() const + { + if(_apfc) + return _apfc->threshold(); + return _threshold; + } + + void apfc_chart::set_threshold(qreal newThreshold) + { + if (qFuzzyCompare(_threshold, newThreshold)) + return; + _threshold = newThreshold; + if(_apfc && _ref && _data) + _apfc->set_threshold(_threshold); + } + + int apfc_chart::afc() const + { + if(_apfc) + return static_cast(_apfc->afc()); + return _afc; + } + + void apfc_chart::set_afc(int newAfc) + { + if (_afc == newAfc) + return; + _afc = newAfc; + if(_apfc && _ref && _data) + _apfc->set_afc(static_cast(_afc)); + } + + int apfc_chart::pfc() const + { + if(_apfc) + return static_cast(_apfc->pfc()); + return _pfc; + } + + void apfc_chart::set_pfc(int newPfc) + { + if (_pfc == newPfc) + return; + _pfc = newPfc; + if(_apfc && _ref && _data) + _apfc->set_pfc(static_cast(_pfc)); + } + + qreal apfc_chart::frequency() const + { + if(_apfc) + return _apfc->frequency(); + return _frequency; + } + + void apfc_chart::set_frequency(qreal newFrequency) + { + if (qFuzzyCompare(_frequency, newFrequency)) + return; + _frequency = newFrequency; + if(_apfc && _ref && _data) + _apfc->set_frequency(_frequency); + } + + qreal apfc_chart::resolution() const + { + if(_apfc) + return _apfc->resolution(); + return _resolution; + } + + void apfc_chart::set_resolution(qreal newResolution) + { + if (qFuzzyCompare(_resolution, newResolution)) + return; + _resolution = newResolution; + if(_apfc && _ref && _data) + _apfc->set_resolution(_resolution); + } + + int apfc_chart::window() const + { + if(_apfc) + return static_cast(_apfc->window()); + return _window; + } + + void apfc_chart::set_window(int newWindow) + { + if (_window == newWindow) + return; + _window = newWindow; + if(_apfc && _ref && _data) + _apfc->set_window(static_cast(_window)); + } + + int apfc_chart::lines() const + { + if(_apfc) + return _apfc->lines(); + return _lines; + } + + void apfc_chart::set_lines(int newLines) + { + if (_lines == newLines) + return; + _lines = newLines; + if(_apfc && _ref && _data) + _apfc->set_lines(_lines); + } + + int apfc_chart::average() const + { + if(_apfc) + return _apfc->average(); + return _average; + } + + void apfc_chart::set_average(int newAverage) + { + if (_average == newAverage) + return; + _average = newAverage; + if(_apfc && _ref && _data) + _apfc->set_average(_average); + } + + qreal apfc_chart::overlap() const + { + if(_apfc) + return _apfc->overlap(); + return _overlap; + } + + void apfc_chart::set_overlap(qreal newOverlap) + { + if (qFuzzyCompare(_overlap, newOverlap)) + return; + _overlap = newOverlap; + if(_apfc && _ref && _data) + _apfc->set_overlap(_overlap); + } + + void apfc_chart::set_ref(const QString &name) + { + if(_apfc && name != _ref->name()) + { + std::swap(_ref, _data); + _apfc->set_ref(_ref); + _apfc->set_data(_data); + } + } + + } // namespace gui +} // namespace gtl diff --git a/gui/gtl_gui_apfc_chart.h b/gui/gtl_gui_apfc_chart.h new file mode 100644 index 0000000..1bf8377 --- /dev/null +++ b/gui/gtl_gui_apfc_chart.h @@ -0,0 +1,88 @@ +#ifndef GTL_GUI_APFC_CHART_H +#define GTL_GUI_APFC_CHART_H + +#include "gtl_gui_chart.h" +#include "gui/gtl_gui_apfc_series.h" + +#include "gui_global.h" + +namespace gtl { + namespace gui { + + class GTL_GUI_EXPORT apfc_chart : public gtl::gui::chart + { + Q_OBJECT + public: + apfc_chart(QWidget* parent = NULL); + ~apfc_chart(); + + int afc() const; + void set_afc(int newAfc); + int pfc() const; + void set_pfc(int newPfc); + qreal frequency() const; + void set_frequency(qreal newFrequency); + qreal resolution() const; + void set_resolution(qreal newResolution); + int window() const; + void set_window(int newWindow); + int lines() const; + void set_lines(int newLines); + int average() const; + void set_average(int newAverage); + qreal overlap() const; + void set_overlap(qreal newOverlap); + qreal threshold() const; + void set_threshold(qreal newThreshold); + + public slots: + void set_x_log(bool value); + void set_ref(const QString &name); + + signals: + + void afc_changed(); + void pfc_changed(); + void frequency_changed(); + void resolution_changed(); + void window_changed(); + void lines_changed(); + void average_changed(); + void overlap_changed(); + void signals_changed(QList *sigs); + void threshold_changed(); + + protected: + virtual chart_series* create_series(gtl::analog_data* ai); + + private: + gtl::math::apfc *_apfc = nullptr; + analog_data *_ref = nullptr; + analog_data *_data = nullptr; + + int _afc; + int _pfc; + qreal _frequency; + qreal _resolution; + int _window; + int _lines; + int _average; + qreal _overlap; + qreal _threshold; + QList _signals; + + Q_PROPERTY(int afc READ afc WRITE set_afc NOTIFY afc_changed) + Q_PROPERTY(int pfc READ pfc WRITE set_pfc NOTIFY pfc_changed) + Q_PROPERTY(qreal frequency READ frequency WRITE set_frequency NOTIFY frequency_changed) + Q_PROPERTY(qreal resolution READ resolution WRITE set_resolution NOTIFY resolution_changed) + Q_PROPERTY(int window READ window WRITE set_window NOTIFY window_changed) + Q_PROPERTY(int lines READ lines WRITE set_lines NOTIFY lines_changed) + Q_PROPERTY(int average READ average WRITE set_average NOTIFY average_changed) + Q_PROPERTY(qreal overlap READ overlap WRITE set_overlap NOTIFY overlap_changed) + Q_PROPERTY(qreal threshold READ threshold WRITE set_threshold NOTIFY threshold_changed) + }; + + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_APFC_CHART_H diff --git a/gui/gtl_gui_apfc_series.cpp b/gui/gtl_gui_apfc_series.cpp new file mode 100644 index 0000000..731e668 --- /dev/null +++ b/gui/gtl_gui_apfc_series.cpp @@ -0,0 +1,63 @@ +#include "gtl_gui_apfc_series.h" + +namespace gtl { + namespace gui { + + apfc_series::apfc_series(bool is_updating, bool is_amplitude, gtl::math::apfc* apfc, gtl::analog_data* ai, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y) + : chart_series(ai, axis_x, axis_y) + , _apfc(apfc) + , _is_ampl(is_amplitude) + { + _is_updating = is_updating; + + connect(_apfc, >l::math::apfc::changed, this, &apfc_series::update); + connect(_apfc, >l::math::apfc::initialized, this, &apfc_series::update); + } + + apfc_series::~apfc_series() + { + + } + + void apfc_series::set_apfc(math::apfc *apfc) + { + disconnect(_apfc, >l::math::apfc::changed, this, &apfc_series::update); + disconnect(_apfc, >l::math::apfc::initialized, this, &apfc_series::update); + _apfc = apfc; + connect(_apfc, >l::math::apfc::changed, this, &apfc_series::update); + connect(_apfc, >l::math::apfc::initialized, this, &apfc_series::update); + } + + QString apfc_series::name() const + { + return _name; + } + + void apfc_series::set_name(const QString &newName) + { + _name = newName; + } + + void apfc_series::update() + { + clear(); + + if(_apfc) + { + axis_x()->set_boundaries(0, _apfc->frequency()); + qreal res = _apfc->resolution(); + + for(int i = 0; i < _apfc->size(); i++) + { + if(_is_ampl) + add(QPointF(i*res, _apfc->at(i).first)); + else + add(QPointF(i*res, _apfc->at(i).second)); + } + + emit data_changed(); + } + } + + } // namespace gui +} // namespace gtl diff --git a/gui/gtl_gui_apfc_series.h b/gui/gtl_gui_apfc_series.h new file mode 100644 index 0000000..b860a4c --- /dev/null +++ b/gui/gtl_gui_apfc_series.h @@ -0,0 +1,35 @@ +#ifndef GTL_GUI_APFC_SERIES_H +#define GTL_GUI_APFC_SERIES_H + +#include "math/gtl_math_apfc.h" + +#include "gtl_gui_chart_series.h" + +namespace gtl { + namespace gui { + + class apfc_series : public gtl::gui::chart_series + { + Q_OBJECT + public: + apfc_series(bool is_updating, bool is_amplitude, gtl::math::apfc* apfc, gtl::analog_data* ai, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y); + ~apfc_series(); + + void set_apfc(gtl::math::apfc *apfc); + + QString name() const; + void set_name(const QString &newName); + + private: + gtl::math::apfc *_apfc = nullptr; + bool _is_ampl = true; + QString _name = ""; + + public slots: + void update(); + }; + + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_APFC_SERIES_H diff --git a/gui/gtl_gui_apfc_widget.cpp b/gui/gtl_gui_apfc_widget.cpp new file mode 100644 index 0000000..7e52679 --- /dev/null +++ b/gui/gtl_gui_apfc_widget.cpp @@ -0,0 +1,142 @@ +#include "gtl_gui_apfc_widget.h" +#include "gui/ui_gtl_gui_apfc_widget.h" + +#include "gui/gtl_gui_chart_widget.h" +namespace gtl { + namespace gui { + + apfc_widget::apfc_widget(QWidget *parent, gtl::data_model* model) : + QWidget(parent), + ui(new Ui::apfc_widget) + { + ui->setupUi(this); + + _selection_data_model = new gtl::selection_data_model(this); + _selection_data_model->setSourceModel(model); + + ui->signals_->setModel(_selection_data_model); + ui->signals_->expandAll(); + + _chart = new gtl::gui::apfc_chart(this); + gtl::gui::chart_widget *chart_widget = new gtl::gui::chart_widget(_chart, this); + ui->splitter->insertWidget(0, chart_widget); + + connect(_selection_data_model, >l::selection_data_model::selected, _chart, >l::gui::chart::add_ad); + connect(_selection_data_model, >l::selection_data_model::deselected, _chart, >l::gui::chart::remove_ad); + + QMetaEnum meta_enum = QMetaEnum::fromType(); + ui->afc->disconnect(); + ui->afc->clear(); + for(int i = 0; i < meta_enum.keyCount(); i++) + { + ui->afc->addItem(meta_enum.valueToKey(i)); + } + ui->afc->setCurrentIndex(_chart->afc()); + connect(ui->afc, &QComboBox::currentIndexChanged, _chart, >l::gui::apfc_chart::set_afc); + + meta_enum = QMetaEnum::fromType(); + ui->pfc->disconnect(); + ui->pfc->clear(); + for(int i = 0; i < meta_enum.keyCount(); i++) + { + ui->pfc->addItem(meta_enum.valueToKey(i)); + } + ui->pfc->setCurrentIndex(_chart->pfc()); + connect(ui->pfc, &QComboBox::currentIndexChanged, _chart, >l::gui::apfc_chart::set_pfc); + + meta_enum = QMetaEnum::fromType(); + ui->window->disconnect(); + ui->window->clear(); + for(int i = 0; i < meta_enum.keyCount(); i++) + { + ui->window->addItem(meta_enum.valueToKey(i)); + } + ui->window->setCurrentIndex(_chart->window()); + connect(ui->window, &QComboBox::currentIndexChanged, _chart, >l::gui::apfc_chart::set_window); + + ui->frequency->disconnect(); + ui->frequency->setMaximum(100000); + ui->frequency->setValue(_chart->frequency()); + connect(ui->frequency, &QDoubleSpinBox::valueChanged, _chart, >l::gui::apfc_chart::set_frequency); + + ui->resolution->disconnect(); + ui->resolution->setMaximum(100000); + ui->resolution->setValue(_chart->resolution()); + connect(ui->resolution, &QDoubleSpinBox::valueChanged, _chart, >l::gui::apfc_chart::set_resolution); + + ui->lines->disconnect(); + ui->lines->setMaximum(100000); + ui->lines->setValue(_chart->lines()); + connect(ui->lines, &QDoubleSpinBox::valueChanged, _chart, >l::gui::apfc_chart::set_lines); + + ui->average->disconnect(); + ui->average->setMaximum(100); + ui->average->setValue(_chart->average()); + connect(ui->average, &QDoubleSpinBox::valueChanged, _chart, >l::gui::apfc_chart::set_average); + + ui->overlap->disconnect(); + ui->overlap->setMaximum(99); + ui->overlap->setValue(_chart->overlap()); + connect(ui->overlap, &QDoubleSpinBox::valueChanged, _chart, >l::gui::apfc_chart::set_overlap); + + ui->threshold->disconnect(); + ui->threshold->setMaximum(99); + ui->threshold->setValue(_chart->threshold()); + connect(ui->threshold, &QDoubleSpinBox::valueChanged, _chart, >l::gui::apfc_chart::set_threshold); + + connect(_chart, >l::gui::apfc_chart::frequency_changed, this, >l::gui::apfc_widget::update_parameters); + connect(_chart, >l::gui::apfc_chart::resolution_changed, this, >l::gui::apfc_widget::update_parameters); + connect(_chart, >l::gui::apfc_chart::lines_changed, this, >l::gui::apfc_widget::update_parameters); + + ui->x_log->setVisible(false); + connect(ui->x_log, &QCheckBox::toggled, _chart, >l::gui::apfc_chart::set_x_log); + + connect(_chart, >l::gui::apfc_chart::signals_changed, this, >l::gui::apfc_widget::handle_chart_signals_changed); + } + + apfc_widget::~apfc_widget() + { + delete ui; + } + + void apfc_widget::save(QDomElement &root_element) + { + + } + + void apfc_widget::load(const QDomElement &root_element) + { + + } + + void apfc_widget::update_parameters() + { + ui->frequency->disconnect(); + ui->resolution->disconnect(); + ui->lines->disconnect(); + + if( ui->frequency->value() != _chart->frequency() ) + ui->frequency->setValue(_chart->frequency()); + + if( ui->resolution->value() != _chart->resolution() ) + ui->resolution->setValue(_chart->resolution()); + + if( ui->lines->value() != (int) _chart->lines() ) + ui->lines->setValue(_chart->lines()); + + connect(ui->frequency, &QDoubleSpinBox::valueChanged, _chart, >l::gui::apfc_chart::set_frequency); + connect(ui->resolution, &QDoubleSpinBox::valueChanged, _chart, >l::gui::apfc_chart::set_resolution); + connect(ui->lines, &QDoubleSpinBox::valueChanged, _chart, >l::gui::apfc_chart::set_lines); + } + + void apfc_widget::handle_chart_signals_changed(QList *sigs) + { + disconnect(ui->reference, &QComboBox::currentTextChanged, _chart, >l::gui::apfc_chart::set_ref); + ui->reference->clear(); + foreach (QString sig, *sigs) + ui->reference->addItem(sig); + connect(ui->reference, &QComboBox::currentTextChanged, _chart, >l::gui::apfc_chart::set_ref); + } + + } // namespace gui +} // namespace gtl diff --git a/gui/gtl_gui_apfc_widget.h b/gui/gtl_gui_apfc_widget.h new file mode 100644 index 0000000..e6cddb9 --- /dev/null +++ b/gui/gtl_gui_apfc_widget.h @@ -0,0 +1,45 @@ +#ifndef GTL_GUI_GTL_GUI_APFC_WIDGET_H +#define GTL_GUI_GTL_GUI_APFC_WIDGET_H + +#include + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" +#include "gui/gtl_gui_apfc_chart.h" +#include "gui_global.h" + +namespace Ui { +class apfc_widget; +} + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT apfc_widget : public QWidget + { + Q_OBJECT + + public: + explicit apfc_widget(QWidget *parent = nullptr, gtl::data_model* model = nullptr); + ~apfc_widget(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + Ui::apfc_widget *ui; + gtl::selection_data_model* _selection_data_model; + gtl::gui::apfc_chart* _chart; + + private: + void update_parameters(); + + private slots: + void handle_chart_signals_changed(QList *sigs); + }; + + + } // namespace gui +} // namespace gtl +#endif // GTL_GUI_GTL_GUI_APFC_WIDGET_H diff --git a/gui/gtl_gui_apfc_widget.ui b/gui/gtl_gui_apfc_widget.ui new file mode 100644 index 0000000..db616a7 --- /dev/null +++ b/gui/gtl_gui_apfc_widget.ui @@ -0,0 +1,241 @@ + + + apfc_widget + + + + 0 + 0 + 624 + 586 + + + + Form + + + + + + Qt::Horizontal + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Reference + + + + + + + + + + Amplitude + + + + + + + + + + Phase + + + + + + + + + + Threshold + + + + + + + 100.000000000000000 + + + + + + + + + Qt::Horizontal + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Frequency + + + + + + + + + + Resolution + + + + + + + + + + Lines + + + + + + + + + + Window + + + + + + + + + + Average + + + + + + + 1.000000000000000 + + + 100.000000000000000 + + + + + + + Overlap + + + + + + + 100.000000000000000 + + + + + + + + + log x + + + + + + + + + + + + diff --git a/gui/gtl_gui_chart.cpp b/gui/gtl_gui_chart.cpp new file mode 100644 index 0000000..21becbc --- /dev/null +++ b/gui/gtl_gui_chart.cpp @@ -0,0 +1,637 @@ +#include "gtl_gui_chart.h" + +#include "core/gtl_device.h" + +namespace gtl +{ + namespace gui + { + chart::chart(QWidget* parent, chart_axis_x *axis_x, chart_axis_y *axis_y) + : ::chart::widget(parent, axis_x == nullptr ? new chart_axis_x() : axis_x, axis_y == nullptr ? new chart_axis_y() : axis_y) + , _is_multi_y(false) + , _is_updating(true) + , _is_cyclic(true) + + { + setBackgroundBrush(QBrush(Qt::white)); + + connect(static_cast(_axis_y), &chart_axis_y::get_series, this, &chart::get_axis_series); + + setContextMenuPolicy(Qt::DefaultContextMenu); + + _marker_series = new ::chart::series::xy::line(_axis_x, _axis_y); + scene()->addItem(_marker_series); + + _menu = new QMenu(this); + _markers_menu = _menu->addMenu(tr("Markers")); + + _marker_action_add = _markers_menu->addAction(tr("Add marker")); + connect(_marker_action_add, &QAction::triggered, this, &chart::add_marker); + + _marker_action_remove = _markers_menu->addAction(tr("Remove marker")); + connect(_marker_action_remove, &QAction::triggered, this, &chart::delete_marker); + + _marker_action_clear = _markers_menu->addAction(tr("Clear marker")); + connect(_marker_action_clear, &QAction::triggered, this, &chart::clear_markers); + + _menu->addSeparator(); + _set_theme_action = _menu->addAction("Set dark theme"); + _set_theme_action->setCheckable(true); + connect(_set_theme_action, &QAction::toggled, this, &chart::set_theme); + + + + + _markers = new chart_markers(this); + _single_markers = new chart_single_markers(this); + + } + + chart::~chart() + { + delete _marker_series; + } + + void chart::set_axis_y_mode(bool is_multi_y) + { + if(is_multi_y != _is_multi_y) + { + _is_multi_y = is_multi_y; + set_axes_y(); + + emit axis_y_mode_changed(_is_multi_y); + } + } + + void chart::add_marker() + { + chart_marker* marker = new chart_marker(_marker_series, _set_theme_action->isChecked()); + add(marker); + + connect(marker, &chart_marker::get_nearest_x, this, &chart::get_neares_series_x); + connect(marker, &chart_marker::get_series_data, this, &chart::get_series_data); + connect(marker, &chart_marker::get_series_values, this, &chart::get_series_values); + connect(marker, &chart_marker::deleting, this, &chart::remove_marker); + + marker->set_pos(_mouse_pos_release); + + _markers->add_marker(marker); + _single_markers->add(marker); + + } + + void chart::remove_marker() + { + chart_marker* marker = static_cast(sender()); + _markers->remove_marker(marker); +// _single_markers->remove(marker); + remove(marker); + } + + void chart::clear_markers() + { + while(_markers->count_markers() != 0) + { + chart_marker* marker = _markers->marker(0); + _markers->remove_marker(marker); +// _single_markers->remove(marker); + remove(marker); + delete marker; + } + } + + void chart::get_neares_series_x(qreal &x, chart_line::pos_behaviors behavior) + { + if(behavior == chart_line::magnetic_to_sample) + { + qreal neares_x = x; + qreal delta_min = qInf(); + foreach(::chart::series::series *s, _series) + { + ::chart::series::xy::xy *s_xy = static_cast<::chart::series::xy::xy*>(s); + if(s_xy->size() < 2) + continue; + + auto it = std::lower_bound(s_xy->begin_data(), s_xy->end_data(), QPointF(x, 0), []( + const QPointF &p0, const QPointF &p1) + { + return p0.x() < p1.x(); + } + ); + + int idx0 = std::min(std::distance(s_xy->begin_data(), it), (int)s_xy->size() - 1); + int idx1 = std::max(idx0 - 1, 0); + + qreal delta = qAbs(x - s_xy->at_x(idx0)); + if(delta < delta_min) + { + delta_min = delta; + neares_x = s_xy->at_x(idx0); + } + + delta = qAbs(x - s_xy->at_x(idx1)); + if(delta < delta_min) + { + delta_min = delta; + neares_x = s_xy->at_x(idx1); + } + } + + x = neares_x; + } + else{ + qreal min = qInf(), max = -qInf(); + + for(auto s : _series) + { + ::chart::series::xy::xy *s_xy = static_cast<::chart::series::xy::xy*>(s); + if(s_xy->empty()) + continue; + + if(s_xy->front_x() < min) + min = s_xy->front_x(); + if(s_xy->back_x() > max) + max = s_xy->back_x(); + } + + if(min == qInf() || max == -qInf()) + { + min = _axis_x->min_boundary(); + max = _axis_x->max_boundary(); + } + + if(x < min) + x = min; + else if(x > max) + x = max; + + } + } + + void chart::get_series_data(qreal x, bool is_widget_pos, QVariantList &data) + { + foreach(::chart::series::series *s, _series) + { + ::chart::series::xy::xy *s_xy = static_cast<::chart::series::xy::xy*>(s); + + if(s_xy->size() < 2) + { + data.push_back(QVariant(qQNaN())); + data.push_back(QVariant(QColor(Qt::black))); + } + + auto it = std::lower_bound(s_xy->begin_data(), s_xy->end_data(), QPointF(x, 0), []( + const QPointF &p0, const QPointF &p1) + { + return p0.x() < p1.x(); + } + ); + + if(it == s_xy->end_data()) + { + data.push_back(QVariant(qQNaN())); + data.push_back(QVariant(QColor(Qt::black))); + } + else if(it->x() == x) + { + if(is_widget_pos) + data.push_back(QVariant(s_xy->axis_y()->map_to_widget(it->y()))); + else + data.push_back(QVariant(it->y())); + data.push_back(QVariant(s_xy->color())); + } + else + { + auto it_prev = std::prev(it); + qreal k = (it_prev->y() - it->y())/(it_prev->x() - it->x()); + qreal y = k*x + it_prev->y() - k*it_prev->x(); + + if(is_widget_pos) + data.push_back(QVariant(s_xy->axis_y()->map_to_widget(y))); + else + data.push_back(QVariant(y)); + data.push_back(QVariant(s_xy->color())); + + /* + data.push_back(QVariant(qQNaN())); + data.push_back(QVariant(QColor(Qt::black))); + */ + } + } + } + + void chart::get_series_values(qreal x_min, qreal x_max, int series_idx, QVariantList &data) + { + ::chart::series::xy::xy *s_xy = static_cast<::chart::series::xy::xy*>(_series[series_idx]); + + auto it_min = std::lower_bound(s_xy->begin_data(), s_xy->end_data(), QPointF(x_min, 0), []( + const QPointF &p0, const QPointF &p1) + { + return p0.x() < p1.x(); + } + ); + + auto it_max = std::upper_bound(s_xy->begin_data(), s_xy->end_data(), QPointF(x_max, 0), []( + const QPointF &p0, const QPointF &p1) + { + return p0.x() < p1.x(); + } + ); + + for(auto it = it_min; it != it_max; it++) + data.push_back(it->y()); + + } + + void chart::delete_marker() + { + delete _marker_popup; + } + + void chart::deleting_ad() + { + remove_ad(static_cast(sender())); + } + + void chart::get_axis_series(QList &series) + { + for(auto s: _series) + { + if(s->axis_y() == sender()) + series.push_back(static_cast(s)); + } + } + + void chart::set_theme(bool is_dack) + { + if(is_dack) + { + setBackgroundBrush(QBrush(Qt::black)); + _markers->set_bgnd_color(Qt::black); + _markers->set_color(Qt::white); + } + else + { + setBackgroundBrush(QBrush(Qt::white)); + _markers->set_bgnd_color(Qt::white); + _markers->set_color(Qt::black); + } + + static_cast(_axis_x)->set_theme(is_dack); + static_cast(_axis_y)->set_theme(is_dack); + foreach(::chart::series::series *s, _series) + static_cast(s->axis_y())->set_theme(is_dack); + } + + void chart::device_recieved_data() + { + + } + + bool chart::is_axis_y_multi() const + { + return _is_multi_y; + } + + bool chart::is_updating() const + { + return _is_updating; + } + + bool chart::is_cyclic() const + { + return _is_cyclic; + } + + void chart::set_cyclic(bool value) + { + if(_is_cyclic != value) + { + qDebug() << value; + _is_cyclic = value; + emit cyclic_changed(value); + } + } + + chart_markers *chart::chart_markers_model() const + { + return _markers; + } + + void chart::save(QDomElement &root_element) + { + root_element.setAttribute("is_updating", _is_updating); + root_element.setAttribute("is_cyclic", _is_cyclic); + root_element.setAttribute("is_multi_y", _is_multi_y); + + QDomElement axes_element = root_element.ownerDocument().createElement("axes"); + root_element.appendChild(axes_element); + + if(_is_multi_y) + { + for(int i = 0; i < (int)_series.size(); i++) + { + QDomElement axis_element = root_element.ownerDocument().createElement("axis"); + axes_element.appendChild(axis_element); + + static_cast(_series[i]->axis_y())->save(axis_element); + } + } + else + { + QDomElement axis_element = root_element.ownerDocument().createElement("axis"); + axes_element.appendChild(axis_element); + + static_cast(_axis_y)->save(axis_element); + } + } + + void chart::load(const QDomElement &root_element) + { + set_updating(root_element.attribute("is_updating", "1").toInt() == 1); + set_cyclic(root_element.attribute("is_cyclic", "1").toInt() == 1); + set_axis_y_mode(root_element.attribute("is_multi_y", "0").toInt() == 1); + + QDomElement axes_element = root_element.firstChildElement("axes"); + QDomElement axis_element = axes_element.firstChildElement("axis"); + + if(_is_multi_y) + { + for(int i = 0; i < (int)_series.size(); i++) + { + static_cast(_series[i]->axis_y())->load(axis_element); + + axis_element = axis_element.nextSiblingElement("axis"); + } + + } + else + { + static_cast(_axis_y)->load(axis_element); + } + } + + bool chart::is_multi_y() const + { + return _is_multi_y; + } + + void chart::set_background(QColor color) + { + setBackgroundBrush(QBrush(color)); + _markers->set_bgnd_color(color); + } + + chart_single_markers *chart::single_markers() const + { + return _single_markers; + } + + void chart::dragEnterEvent(QDragEnterEvent *event) + { + event->ignore(); + } + + void gtl::gui::chart::add_series(chart_series *series) + { + add(series); + + _markers->add_series(series); + + set_axes_y(); + + set_bounds_x(); + + if(_series.size() == 1) + _axis_x->set_range(); + } + + void chart::add_ad(analog_data *ad) + { + chart_series *series = create_series(ad); + + gtl::data_model_node* device = ad->root(); + + if(_devices.find(device) == _devices.end()) + connect(static_cast(device), >l::device::recieved_data, this, &chart::device_recieved_data); + + _devices[device].push_back(series); + + connect(ad, >l::data_model_node::deleting, this, &chart::deleting_ad); + + add_series(series); + } + + void gtl::gui::chart::remove_series(chart_series *series) + { + + if(_is_multi_y) + { + ::chart::axis* axis = series->axis_y(); + series->set_axis_y(_axis_y); + remove(axis); + delete axis; + } + + _markers->remove_series(static_cast(series)); + + remove(series); + + set_axes_y(); + + set_bounds_x(); + } + + void chart::remove_series() + { + while(!_series.empty()) + { + gtl::gui::chart_series* series = static_cast(_series.front()); + remove_series(series); + + delete series; + } + + _series.clear(); + } + + void chart::remove_ad(analog_data *ad) + { + for(int i = 0; i < (int)_series.size(); i++) + { + if(static_cast(_series[i])->ad() == ad) + { + gtl::data_model_node* device = ad->root(); + _devices[device].remove(static_cast(_series[i])); + if(_devices[device].empty()) + { + disconnect(static_cast(device), >l::device::recieved_data, this, &chart::device_recieved_data); + _devices.erase(device); + } + + chart_series *series = static_cast(_series[i]); + remove_series(series); + delete series; + + break; + } + } + } + + void chart::set_updating(bool value) + { + if(_is_updating != value) + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + static_cast(*iter_series)->set_updating(value); + + _is_updating = value; + + emit updating_changed(value); + } + } + + void chart::set_bounds_x() + { + qreal min = qInf(), max = -qInf(); + for(int i = 0; i < _series.size(); i++) + { + ::chart::series::xy::xy* s = static_cast<::chart::series::xy::xy*>(_series[i]); + if(s->empty()) + continue; + + min = std::min(min, s->front_x()); + max = std::max(max, s->back_x()); + } + + if((min == qInf() && max == -qInf()) || (min == max)) + _axis_x->set_boundaries(0, 100); + else + _axis_x->set_boundaries(min, max); + } + + void chart::contextMenuEvent(QContextMenuEvent *event) + { + if(_mouse_pos_press != _mouse_pos_release) + return; + + _marker_popup = _markers->marker_at(event->pos()); + _marker_action_remove->setVisible(_marker_popup != NULL); + + _marker_action_clear->setVisible(_markers->count_markers() != 0); + + _set_theme_action->setText(_set_theme_action->isChecked() ? tr("Set light theme") : tr("Set dark theme")); + _set_theme_action->setIcon(_set_theme_action->isChecked() ? QIcon(":chart/light_theme") : QIcon(":chart/dark_theme")); + + _menu->popup(event->globalPos()); + } + + void chart::set_axes_y() + { + if(!_is_multi_y) + { + for(int i = 0; i < _series.size(); i++) + { + ::chart::axis* axis = _series[i]->axis_y(); + if(axis != _axis_y) + { + _series[i]->set_axis_y(_axis_y); + remove(axis); + delete axis; + } + } + + _axis_y->setVisible(true); + fit_axis(_axis_y); + } + else + { + for(int i = 0; i < _series.size(); i++) + { + ::chart::axis* axis = _series[i]->axis_y(); + if(axis == _axis_y) + { + chart_axis_y* axis = create_axis_y(); + connect(axis, &chart_axis_y::get_series, this, &chart::get_axis_series); + _series[i]->set_axis_y(axis); + add(axis); + fit_axis(axis); + } + + _series[i]->axis_y()->set_pos(i / (qreal)_series.size(), (i + 1) / (qreal)_series.size()); + + + } + + _axis_y->setVisible(false); + } + } + + void chart::mousePressEvent(QMouseEvent *event) + { + _mouse_pos_press = event->pos(); + + chart::widget::mousePressEvent(event); + } + + void chart::mouseReleaseEvent(QMouseEvent *event) + { + _mouse_pos_release = event->pos(); + + chart::widget::mouseReleaseEvent(event); + } + + void chart::mouseDoubleClickEvent(QMouseEvent *event) + { + if(event->button() == Qt::LeftButton) + { + QPointF pos = mapToScene(event->pos()); + + for(std::vector<::chart::axis*>::iterator iter_axis = _axes.begin(); iter_axis != _axes.end(); iter_axis++) + { + QRectF rect_axis = (*iter_axis)->boundingRect(); + if(rect_axis.contains(pos)) + { + if(_is_multi_y) + { + if((*iter_axis)->orient() == ::chart::axis::vert) + { + fit_axis(*iter_axis); + + auto series_iterator = std::find_if(_series.begin(), _series.end(), [=](::chart::series::series* series){return series->axis_y() == *iter_axis;}); + if(series_iterator != _series.end()) + { + (*series_iterator)->axis_x()->set_range(static_cast((*series_iterator))->left(), static_cast((*series_iterator))->right()); + } + } + + } + else + { + fit_axis(*iter_axis); + } + } + } + } + } + + void chart::mouseMoveEvent(QMouseEvent *event) + { + if(!_tool_zooming->isVisible()) + { + for(auto it : _series) + static_cast(it)->set_tool_tip(event->pos()); + } + + chart::widget::mouseMoveEvent(event); + } + + chart_axis_y *chart::create_axis_y() const + { + chart_axis_y* axis = new chart_axis_y(); + axis->set_theme(_set_theme_action->isChecked()); + + return axis; + } + } +} diff --git a/gui/gtl_gui_chart.h b/gui/gtl_gui_chart.h new file mode 100644 index 0000000..977907e --- /dev/null +++ b/gui/gtl_gui_chart.h @@ -0,0 +1,130 @@ +#ifndef CHART_H +#define CHART_H + +#include + +#include "chart/widget_chart.h" + +#include "gui_global.h" + +#include "gtl_gui_chart_axis_x.h" +#include "gtl_gui_chart_axis_y.h" +#include "gtl_gui_chart_series.h" + +#include "core/gtl_analog_data.h" + +#include "gtl_gui_chart_marker.h" +#include "gtl_gui_chart_single_markers.h" + +#include "gtl_gui_chart_markers.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT chart : public ::chart::widget + { + Q_OBJECT + public: + chart(QWidget* parent = NULL, chart_axis_x *axis_x = NULL, chart_axis_y *axis_y = NULL); + virtual ~chart(); + + bool is_axis_y_multi() const; + bool is_updating() const; + bool is_cyclic() const; + + chart_markers* chart_markers_model() const; + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + bool is_multi_y() const; + + void set_background(QColor color); + + chart_single_markers* single_markers() const; + void dragEnterEvent(QDragEnterEvent *event) override; + + private: + bool _is_multi_y; + + QPoint _mouse_pos_press; + chart_marker* _marker_popup; + + protected: + ::chart::series::series* _marker_series; + chart_markers* _markers; + chart_single_markers* _single_markers; + QPoint _mouse_pos_release; + + private: + void set_axes_y(); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + + protected: + virtual chart_axis_y* create_axis_y() const; + + + private: + bool _is_updating; + bool _is_cyclic; + + protected: + QMenu *_menu; + QMenu* _markers_menu; + + QAction* _marker_action_add; + QAction* _marker_action_remove; + QAction* _marker_action_clear; + QAction* _set_theme_action; + + std::map> _devices; + + + protected: + virtual chart_series* create_series(gtl::analog_data* ai) = 0; + virtual void contextMenuEvent(QContextMenuEvent *event) override; + + void add_series(chart_series *series); + void remove_series(chart_series *series); + void remove_series(); + + signals: + void updating_changed(bool); + void cyclic_changed(bool); + void axis_y_mode_changed(bool); + + public slots: + virtual void add_ad(gtl::analog_data* ad); + virtual void remove_ad(gtl::analog_data* ad); + + void set_updating(bool value); + void set_cyclic(bool value); + + void set_axis_y_mode(bool is_multi_y); + + + private slots: + void add_marker(); + void delete_marker(); + void clear_markers(); + void deleting_ad(); + void get_axis_series(QList &series); + void set_theme(bool); + + protected slots: + virtual void device_recieved_data(); + void set_bounds_x(); + void get_neares_series_x(qreal &x, chart_line::pos_behaviors); + void get_series_data(qreal x, bool is_widget_pos, QVariantList &data); + void get_series_values(qreal x_min, qreal x_max, int series_idx, QVariantList &data); + void remove_marker(); + + }; + } +} + +#endif // CHART_H diff --git a/gui/gtl_gui_chart_axis_x.cpp b/gui/gtl_gui_chart_axis_x.cpp new file mode 100644 index 0000000..cd28c6e --- /dev/null +++ b/gui/gtl_gui_chart_axis_x.cpp @@ -0,0 +1,21 @@ +#include "gtl_gui_chart_axis_x.h" + +namespace gtl +{ + namespace gui + { + chart_axis_x::chart_axis_x() + { + + } + + void chart_axis_x::set_theme(bool is_dack) + { + prepareGeometryChange(); + if (is_dack) + _color_labels = Qt::white; + else + _color_labels = Qt::black; + } + } +} diff --git a/gui/gtl_gui_chart_axis_x.h b/gui/gtl_gui_chart_axis_x.h new file mode 100644 index 0000000..eb0d825 --- /dev/null +++ b/gui/gtl_gui_chart_axis_x.h @@ -0,0 +1,22 @@ +#ifndef CHART_AXIS_X_H +#define CHART_AXIS_X_H + +#include "chart/axis_horz.h" + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT chart_axis_x : public ::chart::axis_horz + { + Q_OBJECT + public: + chart_axis_x(); + void set_theme(bool is_dack); + }; + } +} + +#endif // CHART_AXIS_X_H diff --git a/gui/gtl_gui_chart_axis_y.cpp b/gui/gtl_gui_chart_axis_y.cpp new file mode 100644 index 0000000..59f24c6 --- /dev/null +++ b/gui/gtl_gui_chart_axis_y.cpp @@ -0,0 +1,84 @@ +#include "gtl_gui_chart_axis_y.h" + +namespace gtl +{ + namespace gui + { + chart_axis_y::chart_axis_y(qreal pos_min, qreal pos_max) + : ::chart::axis_vert(down_to_up, false) + { + _pos_min = pos_min; + _pos_max = pos_max; + } + + void chart_axis_y::save(QDomElement &root_element) + { + root_element.setAttribute("min", min()); + root_element.setAttribute("max", max()); + } + + void chart_axis_y::load(const QDomElement &root_element) + { + set_range(root_element.attribute("min", "-1").toDouble(), root_element.attribute("max", "1").toDouble()); + } + + void chart_axis_y::set_theme(bool is_dack) + { + prepareGeometryChange(); + if (is_dack) + _color_labels = Qt::white; + else + _color_labels = Qt::black; + } + + void chart_axis_y::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + ::chart::axis_vert::paint(painter, option, widget); + + QList series; + emit get_series(series); + + QRectF boundary = boundingRect(); + + int left = boundary.right(); + + int width = 0; + int height = 0; + + int y = 5; + for (auto s: series) + { + if (!s->isVisible()) + continue; + + QRectF rect(boundary.right() - 105, boundary.top() + y, 100, 20); + QString text = s->name(); + + int flags_alignment = Qt::AlignRight | Qt::AlignTop; + painter->setPen(s->color()); + rect = painter->boundingRect(rect, flags_alignment, text); + + if (rect.left() < left) + left = rect.left(); + + draw_label(painter, rect,flags_alignment, s->color(), text); + + /* + QString unit = ""; + + if (!unit.isEmpty()) + { + QRectF r = painter->boundingRect(QRectF(), Qt::AlignCenter, unit); + width += r.width() + 10; + if (r.height() > height) + height = r.height(); + + } + */ + y += 15; + + } + } + + } +} diff --git a/gui/gtl_gui_chart_axis_y.h b/gui/gtl_gui_chart_axis_y.h new file mode 100644 index 0000000..1143866 --- /dev/null +++ b/gui/gtl_gui_chart_axis_y.h @@ -0,0 +1,35 @@ +#ifndef AXIS_Y_H +#define AXIS_Y_H + +#include + +#include "chart/axis_vert.h" + +#include "gtl_gui_chart_series.h" + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT chart_axis_y : public ::chart::axis_vert + { + Q_OBJECT + public: + chart_axis_y(qreal pos_min = 0, qreal pos_max = 1); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + void set_theme(bool is_dack); + + private: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + + signals: + void get_series(QList &series); + }; + } +} + +#endif // AXIS_Y_H diff --git a/gui/gtl_gui_chart_line.cpp b/gui/gtl_gui_chart_line.cpp new file mode 100644 index 0000000..b3e6a43 --- /dev/null +++ b/gui/gtl_gui_chart_line.cpp @@ -0,0 +1,85 @@ +#include "gtl_gui_chart_line.h" + +namespace gtl +{ + namespace gui + { + chart_line::chart_line(::chart::series::series *parent, Qt::Orientation orientation) + : ::chart::instrument::primitive::line(parent) + , _orientation(orientation) + , _behavior(magnetic_to_sample) + { + setFlag(QGraphicsItem::ItemIsMovable, true); + + if(_orientation == Qt::Vertical) + _cursor = Qt::SplitHCursor; + else + _cursor = Qt::SplitVCursor; + + _pen.setStyle(Qt::PenStyle::DashLine); + + set_pos(0); + } + + void chart_line::set_pos(qreal x) + { + emit get_nearest_x(x, _behavior); + if(_orientation == Qt::Vertical) + set_line(x, 0, x, 1); + else + set_line(0, x, 1, x); + } + + qreal chart_line::pos() + { + if(_orientation == Qt::Vertical) + return _p0.x(); + else + return _p0.y(); + } + + chart_line::pos_behaviors chart_line::behavior() const + { + return _behavior; + } + + void chart_line::set_behavior(pos_behaviors behavior) + { + _behavior = behavior; + set_pos(pos()); + } + + void chart_line::set_width(qreal value) + { + prepareGeometryChange(); + _pen.setWidth(value); + } + + qreal chart_line::width() const + { + return _pen.width(); + } + + QVariant chart_line::itemChange(GraphicsItemChange change, const QVariant &value) + { + if (change == ItemPositionChange) + { + blockSignals(true); + QVariant value0 = chart::instrument::primitive::line::itemChange(change, value); + blockSignals(false); + + qreal x = pos(); +// emit get_nearest_x(x, _behavior); + set_pos(x); + + + emit signal_moved(); + + return value0; + + } + + return chart::instrument::primitive::line::itemChange(change, value); + } + } +} diff --git a/gui/gtl_gui_chart_line.h b/gui/gtl_gui_chart_line.h new file mode 100644 index 0000000..3953eeb --- /dev/null +++ b/gui/gtl_gui_chart_line.h @@ -0,0 +1,45 @@ +#ifndef CHART_LINE_H +#define CHART_LINE_H + +#include "chart/instruments/primitives/primitive_line.h" + +namespace gtl +{ + namespace gui + { + class chart_line : public ::chart::instrument::primitive::line + { + Q_OBJECT + public: + enum pos_behaviors{ + magnetic_to_sample, + free + }; + + public: + chart_line(::chart::series::series *parent, Qt::Orientation orientation = Qt::Vertical); + + void set_pos(qreal x); + qreal pos(); + + pos_behaviors behavior() const; + void set_behavior(pos_behaviors behavior); + + void set_width(qreal value); + qreal width() const; + + private: + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); + + protected: + pos_behaviors _behavior; + Qt::Orientation _orientation; + + + signals: + void get_nearest_x(qreal& x, pos_behaviors); + }; + } +} + +#endif // CHART_LINE_H diff --git a/gui/gtl_gui_chart_marker.cpp b/gui/gtl_gui_chart_marker.cpp new file mode 100644 index 0000000..f68ecce --- /dev/null +++ b/gui/gtl_gui_chart_marker.cpp @@ -0,0 +1,115 @@ +#include "gtl_gui_chart_marker.h" + +namespace gtl +{ + namespace gui + { + chart_marker::chart_marker(::chart::series::series* parent, bool is_dark_theme) + : ::chart::instrument::instrument(parent) + , _idx(-1) + { + if(is_dark_theme) + _color = Qt::white; + else + _color = Qt::black; + + _line = new chart_marker_line(parent, _idx); + ::chart::instrument::instrument::add(_line); + connect(_line, &chart_marker_line::get_nearest_x, this, &chart_marker::get_nearest_x); + connect(_line, &chart_marker_line::get_series_data, this, &chart_marker::get_series_data); + connect(_line, &chart_marker_line::signal_moved, this, &chart_marker::line_moved); + + /* + _label = new chart_marker_label(parent); + _label->set_color(Qt::black); + ::chart::instrument::instrument::add(_label); + */ + } + + chart_marker::~chart_marker() + { + emit deleting(); + } + + void chart_marker::set_pos(QPoint p) + { + qreal x = _series->axis_x()->map_from_widget(p.x()); + + _line->set_pos(x); + } + + void chart_marker::set_pos(qreal x) + { + _line->set_pos(x); + } + + void chart_marker::set_pos() + { + set_pos(pos()); + } + + qreal chart_marker::pos() const + { + return _line->pos(); + } + + bool chart_marker::contains(const QPointF& point) const + { + return _line->contains(point); + } + + void chart_marker::set_idx(int idx) + { + if(idx != _idx) + { + _line->set_idx(idx); + _idx = idx; + + emit idx_changed(); + } + } + + int chart_marker::idx() const + { + return _line->idx(); + } + + qreal chart_marker::value(int series_idx) + { + QVariantList values; + emit get_series_data(_line->pos(), false, values); + + return values[series_idx*2].toDouble(); + } + + void chart_marker::set_width(qreal width) + { + _line->set_width(width); + } + + qreal chart_marker::width() const + { + return _line->width(); + } + + void chart_marker::add(const QPointF &/*point*/) + { + + } + + void chart_marker::draw(const QPointF &/*point*/) + { + + } + + void chart_marker::line_moved() + { + emit position_changed(); + } + + void chart_marker::set_color(const QColor &color) + { + ::chart::instrument::instrument::set_color(color); + } + } +} diff --git a/gui/gtl_gui_chart_marker.h b/gui/gtl_gui_chart_marker.h new file mode 100644 index 0000000..2a84c39 --- /dev/null +++ b/gui/gtl_gui_chart_marker.h @@ -0,0 +1,57 @@ +#ifndef CHART_MARKER_H +#define CHART_MARKER_H + +#include "chart/instruments/instrument.h" + +#include "gtl_gui_chart_marker_line.h" + +namespace gtl +{ + namespace gui + { + class chart_marker : public ::chart::instrument::instrument + { + Q_OBJECT + public: + chart_marker(::chart::series::series* parent, bool is_dark_theme); + virtual ~chart_marker(); + + void set_pos(QPoint p); + virtual void set_pos(qreal x); + void set_pos(); + qreal pos() const; + + virtual bool contains(const QPointF& point) const; + virtual void set_idx(int idx); + int idx() const; + + virtual qreal value(int series_idx); + virtual void set_width(qreal width); + virtual qreal width() const; + + protected: + chart_marker_line* _line; + int _idx; + + private: + virtual void add(const QPointF &point) override; + virtual void draw(const QPointF &point) override; + + signals: + void get_nearest_x(qreal& x, chart_line::pos_behaviors); + void get_series_data(qreal x, bool is_widget_pos, QVariantList &data); + void get_series_values(qreal x_min, qreal x_max, int series_idx, QVariantList &data); + void position_changed(); + void idx_changed(); + void deleting(); + + protected slots: + virtual void line_moved(); + + public slots: + virtual void set_color(const QColor &color); + }; + } +} + +#endif // CHART_MARKER_H diff --git a/gui/gtl_gui_chart_marker_color_delegate.cpp b/gui/gtl_gui_chart_marker_color_delegate.cpp new file mode 100644 index 0000000..ceb0df0 --- /dev/null +++ b/gui/gtl_gui_chart_marker_color_delegate.cpp @@ -0,0 +1,63 @@ +#include "gtl_gui_chart_marker_color_delegate.h" + +#include "gui/gtl_gui_color_box.h" +#include "gtl_gui_chart_markers_model.h" + +namespace gtl +{ + namespace gui + { + chart_marker_color_delegate::chart_marker_color_delegate(QObject *parent) + : QStyledItemDelegate{parent} + { + + } + + QWidget *chart_marker_color_delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(option) + + color_box *editor = new color_box(parent); + editor->setMinimumSize(0, 0); +// editor->setFrame(false); + + ((chart_markers_model*)index.model())->connect_color_changed_signal(editor, index); + + return editor; + } + + void chart_marker_color_delegate::setEditorData(QWidget *editor, const QModelIndex &index) const + { + if(!index.isValid()) + return; + QColor value = index.model()->data(index, Qt::DisplayRole).value(); + static_cast(editor)->set_color(value); + } + + void chart_marker_color_delegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const + { + model->setData(index, static_cast(editor)->color(), Qt::EditRole); + } + + void chart_marker_color_delegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + //QRect rect(option.rect.left() + 3, option.rect.top() + 3, option.rect.width() - 6, option.rect.height() - 6); + editor->setGeometry(option.rect.marginsRemoved(QMargins(1, 1, 1, 1))); + +// editor->setGeometry(option.rect); + } + + void chart_marker_color_delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(painter) + Q_UNUSED(option) + Q_UNUSED(index) + } + + QSize chart_marker_color_delegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QSize s = index.data(Qt::SizeHintRole).toSize(); + return s.isValid() ? s: QStyledItemDelegate::sizeHint(option, index); + } + } +} diff --git a/gui/gtl_gui_chart_marker_color_delegate.h b/gui/gtl_gui_chart_marker_color_delegate.h new file mode 100644 index 0000000..9c97b30 --- /dev/null +++ b/gui/gtl_gui_chart_marker_color_delegate.h @@ -0,0 +1,30 @@ +#ifndef BAND_MARKER_COLOR_DELEGATE_H +#define BAND_MARKER_COLOR_DELEGATE_H + +#include + +#include "gui/gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT chart_marker_color_delegate : public QStyledItemDelegate + { + Q_OBJECT + public: + explicit chart_marker_color_delegate(QObject *parent = nullptr); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + }; + } +} + +#endif // BAND_MARKER_COLOR_DELEGATE_H diff --git a/gui/gtl_gui_chart_marker_kill_delegate.cpp b/gui/gtl_gui_chart_marker_kill_delegate.cpp new file mode 100644 index 0000000..02cd217 --- /dev/null +++ b/gui/gtl_gui_chart_marker_kill_delegate.cpp @@ -0,0 +1,65 @@ +#include "gtl_gui_chart_marker_kill_delegate.h" +#include "gtl_gui_chart_markers_model.h" + +#include + +namespace gtl +{ + namespace gui + { + chart_marker_kill_delegate::chart_marker_kill_delegate(QObject *parent) + : QStyledItemDelegate{parent} + { + + } + + QWidget *chart_marker_kill_delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QToolButton *editor = new QToolButton(parent); + editor->setIconSize(QSize(16, 16)); + editor->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + editor->setFixedSize(20, 20); + editor->setIcon(QIcon(":/dock/close")); + + ((chart_markers_model*)index.model())->connect_kill_signal(editor, index); + + return editor; + } + + void chart_marker_kill_delegate::setEditorData(QWidget *editor, const QModelIndex &index) const + { + Q_UNUSED(editor) + Q_UNUSED(index) + } + + void chart_marker_kill_delegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const + { + Q_UNUSED(editor) + Q_UNUSED(model) + Q_UNUSED(index) + } + + void chart_marker_kill_delegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(index) +// QRect rect(option.rect.left() + option.rect.width()/2 - editor->width()/2, option.rect.top(), editor->width(), option.rect.height()); +// editor->setGeometry(rect); + QRect rect(option.rect.left() + 3, option.rect.top() + option.rect.height()/2 - editor->height()/2, option.rect.width(), editor->height()); + editor->setGeometry(rect); + + } + + void chart_marker_kill_delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(painter) + Q_UNUSED(option) + Q_UNUSED(index) + } + + QSize chart_marker_kill_delegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QSize s = index.data(Qt::SizeHintRole).toSize(); + return s.isValid() ? s: QStyledItemDelegate::sizeHint(option, index); + } + } +} diff --git a/gui/gtl_gui_chart_marker_kill_delegate.h b/gui/gtl_gui_chart_marker_kill_delegate.h new file mode 100644 index 0000000..b562a67 --- /dev/null +++ b/gui/gtl_gui_chart_marker_kill_delegate.h @@ -0,0 +1,30 @@ +#ifndef CHART_MARKER_KILL_DELEGATE_H +#define CHART_MARKER_KILL_DELEGATE_H + +#include + +#include "gui/gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT chart_marker_kill_delegate : public QStyledItemDelegate + { + Q_OBJECT + public: + explicit chart_marker_kill_delegate(QObject *parent = nullptr); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + }; + } +} + +#endif // CHART_MARKER_KILL_DELEGATE_H diff --git a/gui/gtl_gui_chart_marker_line.cpp b/gui/gtl_gui_chart_marker_line.cpp new file mode 100644 index 0000000..8607da8 --- /dev/null +++ b/gui/gtl_gui_chart_marker_line.cpp @@ -0,0 +1,119 @@ +#include "gtl_gui_chart_marker_line.h" + +namespace gtl +{ + namespace gui + { + chart_marker_line::chart_marker_line(::chart::series::series *parent, int idx, bool is_label_visible, Qt::Orientation orientation) + : chart_line(parent, orientation) + , _width(15) + , _idx(idx) + , _is_label_visible(is_label_visible) + { + setZValue(1); + } + + void chart_marker_line::set_idx(int idx) + { + prepareGeometryChange(); + + _idx = idx; + } + + int chart_marker_line::idx() const + { + return _idx; + } + + void chart_marker_line::set_label_visible(bool value) + { + prepareGeometryChange(); + _is_label_visible = value; + } + + void chart_marker_line::set_label(QString label) + { + prepareGeometryChange(); + _lbl_rect.setWidth(100); + _label = label; + } + + QRectF chart_marker_line::boundingRect() const + { + QRectF rect = chart::instrument::primitive::line::boundingRect(); + + QRectF lbl_rect = _lbl_rect; + + if(_orientation == Qt::Vertical) + { + if(rect.center().x() - lbl_rect.width() - 3 > 0) + lbl_rect.moveTo(rect.center().x() - lbl_rect.width() - 3, rect.top()); + else + lbl_rect.moveTo(rect.center().x() + 3, rect.top()); + + return QRectF(rect.center().x() - _width / 2, rect.top(), _width, rect.height()).united(lbl_rect); + } + else + { + if(rect.center().y() - lbl_rect.height() - 3 > 0) + lbl_rect.moveTo(rect.right() - lbl_rect.width() - 3, rect.center().y() - lbl_rect.height() - 3); + else + lbl_rect.moveTo(rect.right() - lbl_rect.width() - 3, rect.center().y() + 3); + + return QRectF(rect.left(), rect.center().y() - _width / 2, rect.width(), _width).united(lbl_rect); + } + } + + void chart_marker_line::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + ::chart::instrument::primitive::line::paint(painter, option, widget); + + if(_is_label_visible) + { + + QString lbl = _label; + if(_label.isEmpty()) + lbl = QString::number(_idx + 1); + + QRectF rect = /*boundingRect()*/chart::instrument::primitive::line::boundingRect(); + _lbl_rect = painter->fontMetrics().boundingRect(lbl); + + if(_orientation == Qt::Vertical) + { + if(rect.center().x() - _lbl_rect.width() - 3 > 0) + _lbl_rect.moveTo(rect.center().x() - _lbl_rect.width() - 3, rect.top()); + else + _lbl_rect.moveTo(rect.center().x() + 3, rect.top()); + } + else + { + if(rect.center().y() - _lbl_rect.height() - 3 > 0) + _lbl_rect.moveTo(rect.right() - _lbl_rect.width() - 3, rect.center().y() - _lbl_rect.height() - 3); + else + _lbl_rect.moveTo(rect.right() - _lbl_rect.width() - 3, rect.center().y() + 3); + } + + painter->drawText(_lbl_rect, Qt::AlignCenter, lbl); + } + + qreal dx = _width / 2.0; + qreal dy = _width / 2.0; + + QVariantList data; + emit get_series_data(pos(), true, data); + + for(int i = 0;i < data.size(); i += 2) + { + if(data[i].toDouble() == qQNaN()) + continue; + + qreal y = /*_series->axis_y()->map_to_widget(*/data[i].toDouble()/*)*/; + qreal x = _series->axis_x()->map_to_widget(pos()); + + painter->setPen(QPen(data[i + 1].value(), 0)); + painter->drawLine((QPointF(x - dx, y - dy)), (QPointF(x + dx, y + dy))); + painter->drawLine((QPointF(x - dx, y + dy)), (QPointF(x + dx, y - dy))); + } + } + } +} diff --git a/gui/gtl_gui_chart_marker_line.h b/gui/gtl_gui_chart_marker_line.h new file mode 100644 index 0000000..328e5c4 --- /dev/null +++ b/gui/gtl_gui_chart_marker_line.h @@ -0,0 +1,37 @@ +#ifndef CHART_MARKER_LINE_H +#define CHART_MARKER_LINE_H + +#include "gtl_gui_chart_line.h" + +namespace gtl +{ + namespace gui + { + class chart_marker_line : public chart_line + { + Q_OBJECT + public: + chart_marker_line(::chart::series::series *parent, int idx, bool is_label_visible = true, Qt::Orientation orientation = Qt::Vertical); + void set_idx(int idx); + int idx() const; + void set_label_visible(bool value); + void set_label(QString label); + + private: + qreal _width; + int _idx; + bool _is_label_visible; + QString _label; + QRectF _lbl_rect; + + private: + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + signals: + void get_series_data(qreal x, bool is_widget_pos, QVariantList &data); + }; + } +} + +#endif // CHART_MARKER_LINE_H diff --git a/gui/gtl_gui_chart_markers.cpp b/gui/gtl_gui_chart_markers.cpp new file mode 100644 index 0000000..b32a0d4 --- /dev/null +++ b/gui/gtl_gui_chart_markers.cpp @@ -0,0 +1,296 @@ +#include "gtl_gui_chart_markers.h" + +namespace gtl +{ + namespace gui + { + class markers_cmp + { + public: + bool operator()(chart_marker* m0, chart_marker* m1) const + { + return m0->pos() < m1->pos(); + } + }; + + chart_markers::chart_markers(QObject *parent) + : QAbstractTableModel(parent) + , _color(Qt::black) + , _bgnd_color(Qt::white) + { + } + + QVariant chart_markers::headerData(int section, Qt::Orientation orientation, int role) const + { + + if(role == Qt::DisplayRole) + { + if(orientation == Qt::Vertical) + { + if(section == 0) + return ""; + else + return _series[section - 1]->name(); + } + else + { + return 1; + } + + } + else if (role == Qt::BackgroundRole) + { + return QVariant(QBrush(/*Qt::white*/_bgnd_color)); + } + else if (role == Qt::ForegroundRole) + { + if (section == 0) + return QVariant(QBrush(/*Qt::black*/_color)); + else + return QVariant(QBrush(_series[section - 1]->color())); + } + + return QVariant(); + } + + int chart_markers::rowCount(const QModelIndex &/*parent*/) const + { + return _series.size() + 1; + } + + int chart_markers::columnCount(const QModelIndex &/*parent*/) const + { + return _markers.size()*2 + 1; + } + + QVariant chart_markers::data(const QModelIndex &index, int role) const + { + if (!index.isValid()) + return QVariant(); + + if(role == Qt::DisplayRole) + { + if (index.column() == 0) + return index.row() == 0 ? "x" : "y"; + if (index.column() % 2 == 1) + { + if(index.row() != 0) + return QVariant(QString::number(index.column() / 2 + 1)); + } + else if (index.row() == 0) + return QVariant(QString::number(_markers[(index.column() - 1) / 2]->pos(), 'f', 3)); + else + { + qreal value = _markers[(index.column() - 1) / 2]->value(index.row() - 1); + return qIsNaN(value) ? "-" : QString::number(value, 'f', 6); + } + + } + else if (role == Qt::BackgroundRole) + { + return QVariant(QBrush(/*Qt::white*/_bgnd_color)); + } + else if (role == Qt::ForegroundRole) + { + if (index.row() == 0) + if (index.column() == 0) + return QVariant(QBrush(/*Qt::black*/_color)); + else + return QVariant(QBrush(/*Qt::black*//*_color*/_markers[(index.column() - 1) / 2]->color())); + else if(index.column() % 2 == 1) + return QVariant(QBrush(/*Qt::black*//*_color*/_markers[(index.column() - 1) / 2]->color())); + else + return QVariant(QBrush(_series[index.row() - 1]->color())); + } + else if (role == Qt::TextAlignmentRole) + return QVariant(Qt::AlignCenter); + else if (role == Qt::DecorationRole) + { + if (index.column() % 2 == 1) + { + if(index.row() == 0) + { + if(QString(_markers[(index.column() - 1) / 2]->metaObject()->className()).contains("chart_marker")) + return QVariant(QPixmap(":chart/single_marker")); + else if(QString(_markers[(index.column() - 1) / 2]->metaObject()->className()).contains("band_marker")) + return QVariant(QPixmap(":chart/band_marker")); + else if(QString(_markers[(index.column() - 1) / 2]->metaObject()->className()).contains("harm_marker")) + return QVariant(QPixmap(":chart/harm_marker")); + } + } + } +/* + else if (role == Qt::SizeHintRole) + { + if (index.column() % 2 == 1) + { + if(index.row() == 0) + return QVariant(QSize(10, 10)); + } + } +*/ + return QVariant(); + } + + void chart_markers::add_marker(chart_marker *marker) + { + +// marker->set_color(_color); + + beginInsertColumns(QModelIndex(), (int)_markers.size(), (int)_markers.size() + 1); + + _markers.push_back(marker); + + endInsertColumns(); + + connect(marker, &chart_marker::position_changed, this, &chart_markers::marker_position_changed); + connect(marker, &chart_marker::idx_changed, this, &chart_markers::marker_position_changed); + + sort_markers(); + } + + void chart_markers::remove_marker(chart_marker *marker) + { + int idx = index_of(marker); + beginRemoveColumns(QModelIndex(), idx*2 + 1, idx*2 + 2); + _markers.erase(_markers.begin() + idx); + endRemoveColumns(); + + sort_markers(); + } + + void chart_markers::add_series(chart_series *series) + { + beginInsertRows(QModelIndex(), (int)_series.size() + 1, (int)_series.size() + 1); + + _series.push_back(series); + + endInsertRows(); + + connect(series, &chart_series::data_changed, this, &chart_markers::series_data_changed); + + if(_series.size() == 1) + { + foreach(chart_marker* marker, _markers) + marker->set_pos(); + } + + } + + void chart_markers::remove_series(chart_series *series) + { + int idx = index_of(series); + beginRemoveRows(QModelIndex(), idx + 1, idx + 1); + + _series.erase(_series.begin() + idx); + + endRemoveRows(); + + } + + chart_marker *chart_markers::marker_at(const QPointF &point) const + { + foreach(chart_marker* marker, _markers) + { + if(marker->contains(point)) + return marker; + } + + return NULL; + } + + int chart_markers::count_markers() const + { + return (int)_markers.size(); + } + + chart_marker *chart_markers::marker(int idx) const + { + return _markers[idx]; + } + + void chart_markers::set_color(QColor color) + { +// for(auto marker: _markers) +// marker->set_color(color); + + _color = color; + + emit color_changed(); + + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), QList() << Qt::ForegroundRole); + } + + QColor chart_markers::color() const + { + return _color; + } + + void chart_markers::set_bgnd_color(QColor color) + { + _bgnd_color = color; + + emit color_changed(); + + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1), QList() << Qt::ForegroundRole); + } + + QColor chart_markers::bgnd_color() const + { + return _bgnd_color; + } + + + QVariant chart_markers::get_marker_value(int idx_marker, int idx_series) const + { + if(_series[idx_series]->size() < 2) + return QVariant("-"); + + qreal x = _markers[idx_marker]->pos(); + + auto it = std::lower_bound(_series[idx_series]->begin_data(), _series[idx_series]->end_data(), QPointF(x, 0), []( + const QPointF &p0, const QPointF &p1) + { + return p0.x() < p1.x(); + } + ); + + + if(it->x() == x) + return QString::number(it->y(), 'f', 6); + + return QVariant("-"); + } + + int chart_markers::index_of(chart_marker *marker) const + { + return std::find(_markers.begin(), _markers.end(), marker) - _markers.begin(); + } + + int chart_markers::index_of(chart_series *series) const + { + return std::find(_series.begin(), _series.end(), series) - _series.begin(); + } + + void chart_markers::sort_markers() + { + std::sort(_markers.begin(), _markers.end(), markers_cmp()); + for(int i = 0; i < _markers.size(); i++) + _markers[i]->set_idx(i); + } + + void chart_markers::marker_position_changed() + { + sort_markers(); + + int idx = index_of(static_cast(sender())); + emit dataChanged(index(0, idx*2 + 2), index(rowCount() - 1, idx*2 + 1), QList() << Qt::DisplayRole); + } + + void chart_markers::series_data_changed() + { + int idx = index_of(static_cast(sender())); + emit dataChanged(index(idx + 1, 0), index(idx + 1, columnCount() - 1), QList() << Qt::DisplayRole); + } + } +} diff --git a/gui/gtl_gui_chart_markers.h b/gui/gtl_gui_chart_markers.h new file mode 100644 index 0000000..3f2efa4 --- /dev/null +++ b/gui/gtl_gui_chart_markers.h @@ -0,0 +1,70 @@ +#ifndef CHART_MARKERS_H +#define CHART_MARKERS_H + +#include + +#include "gtl_gui_chart_marker.h" +#include "gtl_gui_chart_series.h" + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT chart_markers : public QAbstractTableModel + { + Q_OBJECT + + public: + explicit chart_markers(QObject *parent = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + void add_marker(chart_marker *marker); + void remove_marker(chart_marker* marker); + void add_series(chart_series *series); + void remove_series(chart_series *series); + + chart_marker* marker_at(const QPointF &point) const; + + int count_markers() const; + + chart_marker* marker(int idx) const; + + + void set_color(QColor color); + QColor color() const; + void set_bgnd_color(QColor color); + QColor bgnd_color() const; + + private: + std::vector _markers; + std::vector _series; + QColor _color; + QColor _bgnd_color; + + private: + QVariant get_marker_value(int idx_marker, int idx_series) const; + int index_of(chart_marker* marker) const; + int index_of(chart_series* series) const; + void sort_markers(); + + signals: + void color_changed(); + + private slots: + void marker_position_changed(); + void series_data_changed(); + }; + } +} + +#endif // CHART_MARKERS_H diff --git a/gui/gtl_gui_chart_markers_item_delegate.cpp b/gui/gtl_gui_chart_markers_item_delegate.cpp new file mode 100644 index 0000000..d834b07 --- /dev/null +++ b/gui/gtl_gui_chart_markers_item_delegate.cpp @@ -0,0 +1,30 @@ +#include "gtl_gui_chart_markers_item_delegate.h" + +namespace gtl +{ + namespace gui + { + chart_markers_item_delegate::chart_markers_item_delegate(QObject *parent) + : QItemDelegate(parent) + { + + } + + void chart_markers_item_delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + painter->fillRect(option.rect, /*Qt::black*/index.data(Qt::BackgroundRole).value()); + + painter->setPen(index.data(Qt::ForegroundRole).value()); + painter->drawText(option.rect, option.decorationAlignment, index.data().toString()); + + painter->drawPixmap(QRect(option.rect.left() + option.rect.width()/2 - 8, option.rect.top() + option.rect.height()/2 - 8, 16, 16), index.data(Qt::DecorationRole).value(), index.data(Qt::DecorationRole).value().rect()); + + painter->setPen(Qt::darkGray); + painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight()); + if (index.column() == 0 || index.column() % 2 == 0) + painter->drawLine(option.rect.topRight(), option.rect.bottomRight()); + if(index.column() == 0) + painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft()); + } + } +} diff --git a/gui/gtl_gui_chart_markers_item_delegate.h b/gui/gtl_gui_chart_markers_item_delegate.h new file mode 100644 index 0000000..1859218 --- /dev/null +++ b/gui/gtl_gui_chart_markers_item_delegate.h @@ -0,0 +1,21 @@ +#ifndef CHART_MARKERS_ITEM_DELEGATE_H +#define CHART_MARKERS_ITEM_DELEGATE_H + +#include +#include + +namespace gtl +{ + namespace gui + { + class chart_markers_item_delegate : public QItemDelegate + { + public: + chart_markers_item_delegate(QObject *parent); + private: + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + }; + } +} + +#endif // CHART_MARKERS_ITEM_DELEGATE_H diff --git a/gui/gtl_gui_chart_markers_model.cpp b/gui/gtl_gui_chart_markers_model.cpp new file mode 100644 index 0000000..c17cf11 --- /dev/null +++ b/gui/gtl_gui_chart_markers_model.cpp @@ -0,0 +1,95 @@ +#include "gtl_gui_chart_markers_model.h" + +namespace gtl +{ + namespace gui + { + chart_markers_model::chart_markers_model(QObject *parent) + : QAbstractItemModel{parent} + { + + } + + chart_marker *chart_markers_model::at(int idx) const + { + return _markers[idx]; + } + + void chart_markers_model::add(chart_marker *marker) + { + beginInsertRows(QModelIndex(), (int)_markers.size(), (int)_markers.size()); + + _markers.push_back(marker); + + connect(marker, &chart_marker::position_changed, this, &chart_markers_model::marker_changed); + connect(marker, &chart_marker::idx_changed, this, &chart_markers_model::marker_index_changed); + connect(marker, &chart_marker::deleting, this, &chart_markers_model::marker_deleting); + + endInsertRows(); + + // for openPersistentEditor + emit dataChanged(index(-1, -1), index(-1, -1)); + } + + void chart_markers_model::remove(chart_marker *marker) + { + auto it = std::find(_markers.begin(), _markers.end(), marker); + if(it == _markers.end()) + return; + + int idx = std::distance(_markers.begin(), it); + beginRemoveRows(QModelIndex(), idx, idx); + + _markers.erase(it); + + endRemoveRows(); + + } + + int chart_markers_model::rowCount(const QModelIndex &parent) const + { + if(parent.isValid()) + return 0; + + return _markers.size(); + } + + QModelIndex chart_markers_model::index(int row, int column, const QModelIndex &parent) const + { + return createIndex(row, column); + } + + QModelIndex chart_markers_model::parent(const QModelIndex &index) const + { + return QModelIndex(); + } + + void chart_markers_model::connect_color_changed_signal(color_box *sender, const QModelIndex &index) + { + connect(sender, &color_box::color_changed, _markers[index.row()], &chart_marker::set_color); + } + + void chart_markers_model::connect_kill_signal(QAbstractButton *sender, const QModelIndex &index) + { + connect(sender, &QAbstractButton::clicked, _markers[index.row()], &chart_marker::deleteLater); + } + + void chart_markers_model::marker_index_changed() + { + beginResetModel(); + + std::sort(_markers.begin(), _markers.end(), [](chart_marker* marker0, chart_marker* marker1){return marker0->idx() < marker1->idx();}); + + endResetModel(); + + emit changed(); + +// emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); + } + + void chart_markers_model::marker_deleting() + { + remove(static_cast(sender())); + } + } +} diff --git a/gui/gtl_gui_chart_markers_model.h b/gui/gtl_gui_chart_markers_model.h new file mode 100644 index 0000000..17e2baa --- /dev/null +++ b/gui/gtl_gui_chart_markers_model.h @@ -0,0 +1,45 @@ +#ifndef CHART_MARKERS_MODEL_H +#define CHART_MARKERS_MODEL_H + +#include + +#include "gui/gtl_gui_chart_marker.h" +#include "gui/gtl_gui_color_box.h" + +namespace gtl +{ + namespace gui + { + class chart_markers_model : public QAbstractItemModel + { + Q_OBJECT + public: + explicit chart_markers_model(QObject *parent = nullptr); + chart_marker* at(int idx) const; + + virtual void add(chart_marker* marker); + void remove(chart_marker* marker); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + virtual QModelIndex parent(const QModelIndex& index) const override; + + virtual void connect_color_changed_signal(color_box* sender, const QModelIndex& index); + virtual void connect_kill_signal(QAbstractButton* sender, const QModelIndex& index); + + protected: + std::vector _markers; + + signals: + void changed(); + + protected slots: + virtual void marker_changed() = 0; + virtual void marker_index_changed(); + virtual void marker_deleting(); + }; + } +} + +#endif // MARKERS_H diff --git a/gui/gtl_gui_chart_markers_view.cpp b/gui/gtl_gui_chart_markers_view.cpp new file mode 100644 index 0000000..09510e3 --- /dev/null +++ b/gui/gtl_gui_chart_markers_view.cpp @@ -0,0 +1,145 @@ +#include "gtl_gui_chart_markers_view.h" + +#include "gtl_gui_chart_markers_item_delegate.h" +#include "gtl_gui_chart_markers.h" + +namespace gtl +{ + namespace gui + { + chart_markers_view::chart_markers_view(QWidget* parent) + : QTableView(parent) + { + setItemDelegate(new chart_markers_item_delegate(this)); + + setCornerButtonEnabled(false); + setShowGrid(false); + + QFont font("MS Shell Dlg 2", 8); + setFont(font); + + setSelectionMode(QAbstractItemView::SelectionMode::NoSelection); + setFocusPolicy(Qt::FocusPolicy::NoFocus); + + verticalHeader()->setDefaultSectionSize(18); + verticalHeader()->setFont(font); + verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + horizontalHeader()->setVisible(false); + horizontalHeader()->setMaximumHeight(16); + horizontalHeader()->setMinimumSectionSize(0); + horizontalHeader()->setFont(font); + + set_height(); + } + + void chart_markers_view::setModel(QAbstractItemModel *model) + { + QTableView::setModel(model); + + connect(model, &QAbstractItemModel::rowsInserted, this, &chart_markers_view::rows_and_columns_count_changed); + connect(model, &QAbstractItemModel::rowsRemoved, this, &chart_markers_view::rows_and_columns_count_changed); + connect(model, &QAbstractItemModel::columnsInserted, this, &chart_markers_view::rows_and_columns_count_changed); + connect(model, &QAbstractItemModel::columnsRemoved, this, &chart_markers_view::rows_and_columns_count_changed); + + connect(static_cast(model), &chart_markers::color_changed, this, &chart_markers_view::markers_color_changed); + + markers_color_changed(); + + } + + void chart_markers_view::set_height() + { + int height = 0; + + if(model() == NULL) + { + + } + else if(model()->rowCount() > 1 && model()->columnCount() > 2) + { + height = horizontalHeader()->height(); + + if (model()->columnCount() == 0) + { + // height = 0; + // setVisible(false); + + } + else + { + + // setVisible(true); + + + + for (int i = 0; i < model()->rowCount(); i++) + height += rowHeight(i)/* + 1*/; + } + + if (horizontalScrollBar()->isVisible()) + { + height += horizontalScrollBar()->height(); + } + } + + height += 2; + + + + + setMinimumSize(0, height); + setMaximumSize(1000000, height); + + if(model() == NULL) + return; + + QFontMetrics metrics(font()); + + for (int i = 0; i < model()->columnCount(); i++) + { + QString str = model()->data(model()->index(0, i), Qt::DisplayRole).toString(); + QRect rect = metrics.boundingRect(str); + + if (i == 0) + { + setColumnWidth(i, rect.width() + 10); + horizontalHeader()->setSectionResizeMode(i, QHeaderView::Fixed); + } + else if (i % 2 == 1) + { + if (rowSpan(1, i) != model()->rowCount()) + setSpan(1, i, model()->rowCount(), 1); + + setColumnWidth(i, rect.width() + 10); + horizontalHeader()->setSectionResizeMode(i, QHeaderView::Fixed); + } + else + { +// if (columnWidth(i) < rect.width() + 10) + setColumnWidth(i, rect.width() + 10); + + horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + + } + } + } + + void chart_markers_view::resizeEvent(QResizeEvent *event) + { + QTableView::resizeEvent(event); + + set_height(); + } + + + void chart_markers_view::rows_and_columns_count_changed(const QModelIndex &/*parent*/, int/* start*/, int/* end*/) + { + set_height(); + } + + void chart_markers_view::markers_color_changed() + { + setStyleSheet( "QTableView{background-color: " + static_cast(model())->bgnd_color().name() + ";}" + "QHeaderView:section{background-color: " + static_cast(model())->bgnd_color().name() + "; color: " + static_cast(model())->color().name() + "; border-style:none; border-bottom: 1px solid #808080;}"); + } + } +} diff --git a/gui/gtl_gui_chart_markers_view.h b/gui/gtl_gui_chart_markers_view.h new file mode 100644 index 0000000..f84b070 --- /dev/null +++ b/gui/gtl_gui_chart_markers_view.h @@ -0,0 +1,31 @@ +#ifndef CHART_MARKERS_VIEW_H +#define CHART_MARKERS_VIEW_H + +#include +#include +#include + + +namespace gtl +{ + namespace gui + { + class chart_markers_view : public QTableView + { + Q_OBJECT + public: + chart_markers_view(QWidget* parent = NULL); + virtual void setModel(QAbstractItemModel *model) override; + + private: + void set_height(); + void resizeEvent(QResizeEvent *event) override; + + private slots: + virtual void rows_and_columns_count_changed(const QModelIndex& parent, int start, int end); + void markers_color_changed(); + }; + } +} + +#endif // CHART_MARKERS_VIEW_H diff --git a/gui/gtl_gui_chart_series.cpp b/gui/gtl_gui_chart_series.cpp new file mode 100644 index 0000000..21f4feb --- /dev/null +++ b/gui/gtl_gui_chart_series.cpp @@ -0,0 +1,160 @@ +#include "gtl_gui_chart_series.h" + +namespace gtl +{ + namespace gui + { + chart_series::chart_series(gtl::analog_data* ad, chart::axis_horz* axis_x, chart::axis_vert* axis_y) + : chart::series::xy::line(axis_x, axis_y) + , _ad(ad) + , _updater(this) + , _is_updating(true) + { + connect(&_updater, &QThread::finished, this, &chart_series::update_series); + connect(&_updater, &QThread::finished, this, &chart_series::data_changed); + + if(_ad) + { + connect(_ad, >l::analog_data::color_changed, this, &chart_series::set_icolor); + set_icolor(); + } + } + + chart_series::~chart_series() + { + _updater.terminate(); + } + + void chart_series::set_updating(bool value) + { + _is_updating = value; + } + + analog_data *chart_series::ad() + { + return _ad; + } + + QString chart_series::name() const + { + if(_ad) + return _ad->name(); + + return ""; + } + + qreal chart_series::left() const + { + if(empty()) + return 0; + + return front_x(); + } + + qreal chart_series::right() const + { + if(empty()) + return 0; + + return back_x(); + } + + qreal chart_series::get_y(qreal x) + { + if(size() < 2) + return 0; + + qreal x_[2]; + qreal y_[2]; + int idx = 0; + + if(x < front_x()) + { + idx = 0; + } + else if(x > back_x()) + { + idx = size() - 2; + } + else + { + qreal dx = at_x(1) - at_x(0); + idx = (x - at_x(0))/dx; + } + + x_[0] = at_x(idx); + x_[1] = at_x(idx + 1); + y_[0] = at_y(idx); + y_[1] = at_y(idx + 1); + + qreal a = (y_[0] - y_[1])/(x_[0] - x_[1]); + qreal b = y_[0] - a*x_[0]; + + return a*x + b; + } + + void chart_series::set_tool_tip(QPoint pos) + { + setToolTip(""); + } + + update_thread::update_thread(chart::series::xy::line *s) + : QThread(NULL) + , _series(s) + { + + } + + void update_thread::set_data(std::vector &data) + { + _mutex.lock(); + _data = data; + _is_updated = true; + _mutex.unlock(); + + if(!isRunning()) + start(); + } + + void update_thread::get_data(std::back_insert_iterator> data) + { + std::copy(_data.begin(), _data.end(), data); + } + + void update_thread::stop() + { + _is_running = false; + wait(); + } + + void update_thread::run() + { + _is_running = true; + while(_is_updated) + { + _mutex.lock(); + std::vector buffer = _data; + _is_updated = false; + _mutex.unlock(); + + for(int i = 0; i < buffer.size(); i++) + { + _series->set_y(i, buffer[i], false); + if(!_is_running) + break; + } + } + } + + void chart_series::update_series() + { + prepareGeometryChange(); + } + + void chart_series::set_icolor() + { + set_color(QColor(_ad->color())); + } + + } +} diff --git a/gui/gtl_gui_chart_series.h b/gui/gtl_gui_chart_series.h new file mode 100644 index 0000000..d9442d9 --- /dev/null +++ b/gui/gtl_gui_chart_series.h @@ -0,0 +1,75 @@ +#ifndef SERIES_H +#define SERIES_H + +#include + +#include "chart/series/xy/series_line.h" + +#include "core/gtl_analog_data.h" + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT update_thread: public QThread + { + public: + update_thread(chart::series::xy::line *s); + void set_data(std::vector &data); + void get_data(std::back_insert_iterator> data); + void stop(); + + private: + chart::series::xy::line* _series; + std::vector _data; + bool _is_updated; + QMutex _mutex; + bool _is_running; + + private: + void run(); + + + + }; + + class GTL_GUI_EXPORT chart_series : public chart::series::xy::line + { + Q_OBJECT + public: + chart_series(gtl::analog_data* ad, chart::axis_horz* axis_x, chart::axis_vert* axis_y); + virtual ~chart_series(); + + void set_updating(bool value); + + gtl::analog_data* ad(); + + virtual QString name() const; + virtual qreal left() const; + virtual qreal right() const; + + virtual void set_tool_tip(QPoint pos); + + protected: + gtl::analog_data* _ad; + update_thread _updater; + bool _is_updating; + + protected: + qreal get_y(qreal x); + + private slots: + void update_series(); + void set_icolor(); + + signals: + void data_changed(); + + }; + + + } +} +#endif // SERIES_H diff --git a/gui/gtl_gui_chart_single_markers.cpp b/gui/gtl_gui_chart_single_markers.cpp new file mode 100644 index 0000000..0cd393c --- /dev/null +++ b/gui/gtl_gui_chart_single_markers.cpp @@ -0,0 +1,104 @@ +#include "gtl_gui_chart_single_markers.h" + +namespace gtl +{ + namespace gui + { + chart_single_markers::chart_single_markers(QObject *parent) + : chart_markers_model{parent} + { + + } + + QVariant chart_single_markers::headerData(int section, Qt::Orientation orientation, int role) const + { + if(role == Qt::DisplayRole) + { + if(orientation == Qt::Horizontal) + { + if(section == 0) + return tr("ID"); + else if(section == 1) + return tr("pos"); + else if(section == 2) + return tr("color"); + else if(section == 3) + return tr("kill"); + } + } + + return QVariant(); + } + + int chart_single_markers::columnCount(const QModelIndex &parent) const + { + return 4; + } + + QVariant chart_single_markers::data(const QModelIndex &index, int role) const + { + if (!index.isValid()) + return QVariant(); + + if(role == Qt::DisplayRole) + { + if(index.column() == 0) + return _markers[index.row()]->idx() + 1; + else if(index.column() == 1) + return _markers[index.row()]->pos(); + else if(index.column() == 2) + return _markers[index.row()]->color(); + } + + return QVariant(); + } + + bool chart_single_markers::setData(const QModelIndex &index, const QVariant &value, int role) + { + if(index.column() == 1) + { + bool is_ok; + qreal double_value = value.toDouble(&is_ok); + + if (is_ok) + { + + _markers[index.row()]->set_pos(double_value); + + emit dataChanged(chart_markers_model::index(index.row(), 1), chart_markers_model::index(index.row(), 3), {Qt::DisplayRole}); + return true; + } + } + else if(index.column() == 2) + { + _markers[index.row()]->set_color(value.value()); + + emit dataChanged(chart_markers_model::index(index.row(), 2), chart_markers_model::index(index.row(), 2), {Qt::DisplayRole}); + return true; + } + + return false; + } + + Qt::ItemFlags chart_single_markers::flags(const QModelIndex &index) const + { + if (!index.isValid()) + return Qt::NoItemFlags; + + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + + if(index.column() >= 1 && index.column() <= 2) + flags |= Qt::ItemIsEditable; + + return flags; + } + + void chart_single_markers::marker_changed() + { + auto it = std::find(_markers.begin(), _markers.end(), sender()); + int idx = std::distance(_markers.begin(), it); + if(idx < _markers.size()) + emit dataChanged(index(idx, 1), index(idx, 1)); + } + } +} diff --git a/gui/gtl_gui_chart_single_markers.h b/gui/gtl_gui_chart_single_markers.h new file mode 100644 index 0000000..7ed20ba --- /dev/null +++ b/gui/gtl_gui_chart_single_markers.h @@ -0,0 +1,36 @@ +#ifndef SINGLE_MARKERS_H +#define SINGLE_MARKERS_H + +#include "gtl_gui_chart_markers_model.h" + +namespace gtl +{ + namespace gui + { + class chart_single_markers : public chart_markers_model + { + Q_OBJECT + public: + explicit chart_single_markers(QObject *parent = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // Editable: + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + + private slots: + virtual void marker_changed() override; + }; + } +} + +#endif // SINGLE_MARKERS_H diff --git a/gui/gtl_gui_chart_single_markers_view.cpp b/gui/gtl_gui_chart_single_markers_view.cpp new file mode 100644 index 0000000..bc2a7a2 --- /dev/null +++ b/gui/gtl_gui_chart_single_markers_view.cpp @@ -0,0 +1,58 @@ +#include "gtl_gui_chart_single_markers_view.h" + +#include + +#include "gui/gtl_gui_chart_marker_color_delegate.h" +#include "gui/gtl_gui_chart_marker_kill_delegate.h" + +namespace gtl +{ + namespace gui + { + chart_single_markers_view::chart_single_markers_view(chart_single_markers* markers, QWidget *parent) + : QTreeView(parent) + , _markers(markers) + { +// verticalHeader()->setDefaultSectionSize(24); + setModel(markers); + setItemDelegateForColumn(2, new chart_marker_color_delegate(this)); + setItemDelegateForColumn(3, new chart_marker_kill_delegate(this)); + setColumnWidth(3, 30); + /* + connect(markers, + &QAbstractItemModel::dataChanged, + [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) + { + for(int i = 0; i < markers->rowCount(); i++) + { + openPersistentEditor(markers->index(i, 2)); + openPersistentEditor(markers->index(i, 3)); + } + } + ); + */ + connect(markers, + &QAbstractItemModel::rowsInserted, + [=](const QModelIndex &parent, int first, int last) + { + for(int i = first; i <= last; i++) + { + openPersistentEditor(markers->index(i, 2, parent)); + openPersistentEditor(markers->index(i, 3, parent)); + } + } + ); + + connect(markers, >l::gui::chart_markers_model::changed, this, &chart_single_markers_view::open_persistent_editors); + } + + void chart_single_markers_view::open_persistent_editors() + { + for(int i = 0; i < _markers->rowCount(); i++) + { + openPersistentEditor(_markers->index(i, 2)); + openPersistentEditor(_markers->index(i, 3)); + } + } + } +} diff --git a/gui/gtl_gui_chart_single_markers_view.h b/gui/gtl_gui_chart_single_markers_view.h new file mode 100644 index 0000000..227a25e --- /dev/null +++ b/gui/gtl_gui_chart_single_markers_view.h @@ -0,0 +1,29 @@ +#ifndef CHART_SINGLE_MARKERS_VIEW_H +#define CHART_SINGLE_MARKERS_VIEW_H + +#include + +#include "gtl_gui_chart_single_markers.h" + +#include "gui/gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT chart_single_markers_view : public QTreeView + { + Q_OBJECT + public: + chart_single_markers_view(chart_single_markers* markers, QWidget *parent = nullptr); + + private: + chart_single_markers* _markers; + + private slots: + void open_persistent_editors(); + }; + } +} + +#endif // CHART_SINGLE_MARKERS_VIEW_H diff --git a/gui/gtl_gui_chart_widget.cpp b/gui/gtl_gui_chart_widget.cpp new file mode 100644 index 0000000..779711c --- /dev/null +++ b/gui/gtl_gui_chart_widget.cpp @@ -0,0 +1,22 @@ +#include "gtl_gui_chart_widget.h" + +namespace gtl +{ + namespace gui + { + chart_widget::chart_widget(chart *chart, QWidget *parent) + : QWidget{parent} + { + setLayout(new QVBoxLayout); + layout()->setContentsMargins(0, 0, 0, 0); + layout()->setSpacing(0); + + layout()->addWidget(chart); + + /*chart_markers_view*/QTableView* view = new chart_markers_view(this); + layout()->addWidget(view); + + view->setModel(chart->chart_markers_model()); + } + } +} diff --git a/gui/gtl_gui_chart_widget.h b/gui/gtl_gui_chart_widget.h new file mode 100644 index 0000000..cceda54 --- /dev/null +++ b/gui/gtl_gui_chart_widget.h @@ -0,0 +1,28 @@ +#ifndef GTL_GUI_CHART_WIDGET_H +#define GTL_GUI_CHART_WIDGET_H + +#include +#include + +#include "gtl_gui_chart.h" +#include "gtl_gui_chart_markers_view.h" + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT chart_widget : public QWidget + { + Q_OBJECT + public: + explicit chart_widget(chart *chart, QWidget *parent = nullptr); + + signals: + + }; + } +} + +#endif // CHART_WIDGET_H diff --git a/gui/gtl_gui_color_box.cpp b/gui/gtl_gui_color_box.cpp new file mode 100644 index 0000000..5bdde31 --- /dev/null +++ b/gui/gtl_gui_color_box.cpp @@ -0,0 +1,60 @@ +#include "gtl_gui_color_box.h" + +#include + +namespace gtl +{ + namespace gui + { + color_box::color_box(QWidget *parent) + :QLabel(parent) + { + + setFrameShape(QFrame::Box); + setFrameShadow(QFrame::Plain); + setGeometry(0, 0, 111, 21); + setMinimumSize(111, 21); + setStyleSheet(QStringLiteral("background-color: rgb(255, 255, 255);")); + set_color(Qt::white); + + + setToolTip(tr("click to select color")); + } + + void color_box::set_color(QColor color) + { + if (_color != color) + { + _color = color; + setStyleSheet(("background-color: rgb(" + QString::number(_color.red()) + ", " + QString::number(_color.green()) + ", " + QString::number(_color.blue()) + ");")); + + emit color_changed(_color); + emit icolor_changed(icolor()); + } + } + + void color_box::set_icolor(int color) + { + set_color(QColor((color >> 16) & (0xff), (color >> 8) & (0xff), (color)& (0xff), (color >> 24) & (0xff))); + } + + QColor color_box::color() const + { + return _color; + } + + uint color_box::icolor() const + { + return _color.alpha() << 24 | _color.red() << 16 | _color.green() << 8 | _color.blue(); + } + + void color_box::mousePressEvent(QMouseEvent *event) + { + QColor color = QColorDialog::getColor(_color); + if (color.isValid()) + { + set_color(color); + } + } + } +} diff --git a/gui/gtl_gui_color_box.h b/gui/gtl_gui_color_box.h new file mode 100644 index 0000000..64f45c5 --- /dev/null +++ b/gui/gtl_gui_color_box.h @@ -0,0 +1,36 @@ +#ifndef COLOR_BOX_H +#define COLOR_BOX_H + +#include + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT color_box : public QLabel + { + Q_OBJECT + public: + color_box(QWidget *parent); + + void set_color(QColor color); + void set_icolor(int color); + QColor color() const; + uint icolor() const; + + private: + QColor _color; + + signals: + void color_changed(QColor); + void icolor_changed(int); + + private: + virtual void mousePressEvent(QMouseEvent *event); + }; + } +} + +#endif // COLOR_BOX_H diff --git a/gui/gtl_gui_cross_spec_chart.cpp b/gui/gtl_gui_cross_spec_chart.cpp new file mode 100644 index 0000000..f67d00e --- /dev/null +++ b/gui/gtl_gui_cross_spec_chart.cpp @@ -0,0 +1,170 @@ + +#include "gtl_gui_cross_spec_chart.h" + +namespace gtl { + namespace gui { + + cross_spec_chart::cross_spec_chart(QWidget* parent) + : chart(parent) + , _frequency(200) + , _resolution(10) + , _window(5) + , _lines(20) + , _average(1) + , _overlap(0) + { + set_axis_y_mode(true); + } + + cross_spec_chart::~cross_spec_chart() + { + if(_cross_spec) + delete _cross_spec; + } + + void cross_spec_chart::set_x_log(bool value) + { + + } + + chart_series *cross_spec_chart::create_series(analog_data *ai) + { + + if(!_series.size()) + _ref = ai; + + if(_series.size() == 1 && static_cast(_series[0])->ad() != _ref) + _ref = _data; + + _data = ai; + + while(_series.size()) + remove_series(static_cast(_series.back())); + + + if(!_cross_spec) + { + _cross_spec = new gtl::math::cross_spec(_ref, _data); + connect(_cross_spec, >l::math::cross_spec::frequency_changed, this, >l::gui::cross_spec_chart::frequency_changed); + connect(_cross_spec, >l::math::cross_spec::resolution_changed, this, >l::gui::cross_spec_chart::resolution_changed); + connect(_cross_spec, >l::math::cross_spec::window_changed, this, >l::gui::cross_spec_chart::window_changed); + connect(_cross_spec, >l::math::cross_spec::lines_changed, this, >l::gui::cross_spec_chart::lines_changed); + connect(_cross_spec, >l::math::cross_spec::average_changed, this, >l::gui::cross_spec_chart::average_changed); + connect(_cross_spec, >l::math::cross_spec::overlap_changed, this, >l::gui::cross_spec_chart::overlap_changed); + + _cross_spec->set_average(_average); + _cross_spec->set_overlap(_overlap); + _cross_spec->set_frequency(_frequency); + _cross_spec->set_lines(_lines); + _cross_spec->set_window(static_cast(_window)); + } + _cross_spec->set_ref(_ref); + _cross_spec->set_data(_data); + + cross_spec_series *series = new cross_spec_series(is_updating(), _cross_spec, _data, _axis_x, _axis_y); + QString name = tr("cross_spec: ") + _ref->name() + tr(", ") + _data->name(); + series->set_name(name); + series->update(); + + return series; + } + + qreal cross_spec_chart::frequency() const + { + if(_cross_spec) + return _cross_spec->frequency(); + return _frequency; + } + + void cross_spec_chart::set_frequency(qreal newFrequency) + { + if (qFuzzyCompare(_frequency, newFrequency)) + return; + _frequency = newFrequency; + if(_cross_spec && _ref && _data) + _cross_spec->set_frequency(_frequency); + } + + qreal cross_spec_chart::resolution() const + { + if(_cross_spec) + return _cross_spec->resolution(); + return _resolution; + } + + void cross_spec_chart::set_resolution(qreal newResolution) + { + if (qFuzzyCompare(_resolution, newResolution)) + return; + _resolution = newResolution; + if(_cross_spec && _ref && _data) + _cross_spec->set_resolution(_resolution); + } + + int cross_spec_chart::window() const + { + if(_cross_spec) + return static_cast(_cross_spec->window()); + return _window; + } + + void cross_spec_chart::set_window(int newWindow) + { + if (_window == newWindow) + return; + _window = newWindow; + if(_cross_spec && _ref && _data) + _cross_spec->set_window(static_cast(_window)); + } + + int cross_spec_chart::lines() const + { + if(_cross_spec) + return _cross_spec->lines(); + return _lines; + } + + void cross_spec_chart::set_lines(int newLines) + { + if (_lines == newLines) + return; + _lines = newLines; + if(_cross_spec && _ref && _data) + _cross_spec->set_lines(_lines); + } + + int cross_spec_chart::average() const + { + if(_cross_spec) + return _cross_spec->average(); + return _average; + } + + void cross_spec_chart::set_average(int newAverage) + { + if (_average == newAverage) + return; + _average = newAverage; + if(_cross_spec && _ref && _data) + _cross_spec->set_average(_average); + } + + qreal cross_spec_chart::overlap() const + { + if(_cross_spec) + return _cross_spec->overlap(); + return _overlap; + } + + void cross_spec_chart::set_overlap(qreal newOverlap) + { + if (qFuzzyCompare(_overlap, newOverlap)) + return; + _overlap = newOverlap; + if(_cross_spec && _ref && _data) + _cross_spec->set_overlap(_overlap); + } + + } // namespace gui +} // namespace gtl + diff --git a/gui/gtl_gui_cross_spec_chart.h b/gui/gtl_gui_cross_spec_chart.h new file mode 100644 index 0000000..9d5c096 --- /dev/null +++ b/gui/gtl_gui_cross_spec_chart.h @@ -0,0 +1,73 @@ +#ifndef GTL_GUI_CROSS_SPEC_CHART_H +#define GTL_GUI_CROSS_SPEC_CHART_H + + +#include "gtl_gui_chart.h" +#include "gui/gtl_gui_cross_spec_series.h" + +#include "gui_global.h" + + +namespace gtl { + namespace gui { + + class GTL_GUI_EXPORT cross_spec_chart : public gtl::gui::chart + { + Q_OBJECT + public: + cross_spec_chart(QWidget* parent = NULL); + ~cross_spec_chart(); + + qreal frequency() const; + void set_frequency(qreal newFrequency); + qreal resolution() const; + void set_resolution(qreal newResolution); + int window() const; + void set_window(int newWindow); + int lines() const; + void set_lines(int newLines); + int average() const; + void set_average(int newAverage); + qreal overlap() const; + void set_overlap(qreal newOverlap); + + public slots: + void set_x_log(bool value); + + signals: + void frequency_changed(); + void resolution_changed(); + void window_changed(); + void lines_changed(); + void average_changed(); + void overlap_changed(); + void signals_changed(QList *sigs); + + protected: + virtual chart_series* create_series(gtl::analog_data* ai); + + private: + gtl::math::cross_spec *_cross_spec = nullptr; + analog_data *_ref = nullptr; + analog_data *_data = nullptr; + + qreal _frequency; + qreal _resolution; + int _window; + int _lines; + int _average; + qreal _overlap; + QList _signals; + + Q_PROPERTY(qreal frequency READ frequency WRITE set_frequency NOTIFY frequency_changed) + Q_PROPERTY(qreal resolution READ resolution WRITE set_resolution NOTIFY resolution_changed) + Q_PROPERTY(int window READ window WRITE set_window NOTIFY window_changed) + Q_PROPERTY(int lines READ lines WRITE set_lines NOTIFY lines_changed) + Q_PROPERTY(int average READ average WRITE set_average NOTIFY average_changed) + Q_PROPERTY(qreal overlap READ overlap WRITE set_overlap NOTIFY overlap_changed) + }; + + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_CROSS_SPEC_CHART_H diff --git a/gui/gtl_gui_cross_spec_series.cpp b/gui/gtl_gui_cross_spec_series.cpp new file mode 100644 index 0000000..63a058e --- /dev/null +++ b/gui/gtl_gui_cross_spec_series.cpp @@ -0,0 +1,53 @@ + +#include "gtl_gui_cross_spec_series.h" + +namespace gtl { + namespace gui { + + cross_spec_series::cross_spec_series(bool is_updating, gtl::math::cross_spec* cross_spec, gtl::analog_data* ai, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y) + : chart_series(ai, axis_x, axis_y) + , _cross_spec(cross_spec) + { + _is_updating = is_updating; + + connect(_cross_spec, >l::math::cross_spec::changed, this, &cross_spec_series::update); + connect(_cross_spec, >l::math::cross_spec::initialized, this, &cross_spec_series::update); + } + + cross_spec_series::~cross_spec_series() + { + + } + + void cross_spec_series::set_cross_spec(math::cross_spec *cross_spec) + { + disconnect(_cross_spec, >l::math::cross_spec::changed, this, &cross_spec_series::update); + disconnect(_cross_spec, >l::math::cross_spec::initialized, this, &cross_spec_series::update); + _cross_spec = cross_spec; + connect(_cross_spec, >l::math::cross_spec::changed, this, &cross_spec_series::update); + connect(_cross_spec, >l::math::cross_spec::initialized, this, &cross_spec_series::update); + } + + QString cross_spec_series::name() const + { + return _name; + } + + void cross_spec_series::set_name(const QString &newName) + { + _name = newName; + } + + void cross_spec_series::update() + { + if(_cross_spec) + { + axis_x()->set_boundaries(0, _cross_spec->frequency()); + set_y(&(*_cross_spec)[0], _cross_spec->size(), _cross_spec->resolution()); + emit data_changed(); + } + } + + } // namespace gui +} // namespace gtl + diff --git a/gui/gtl_gui_cross_spec_series.h b/gui/gtl_gui_cross_spec_series.h new file mode 100644 index 0000000..37d40ec --- /dev/null +++ b/gui/gtl_gui_cross_spec_series.h @@ -0,0 +1,34 @@ +#ifndef GTL_GUI_CROSS_SPEC_SERIES_H +#define GTL_GUI_CROSS_SPEC_SERIES_H + +#include "math/gtl_math_cross_spec.h" + +#include "gtl_gui_chart_series.h" + +namespace gtl { + namespace gui { + + class cross_spec_series : public gtl::gui::chart_series + { + Q_OBJECT + public: + cross_spec_series(bool is_updating, gtl::math::cross_spec *cross_spec, gtl::analog_data* ai, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y); + ~cross_spec_series(); + + void set_cross_spec(gtl::math::cross_spec *cross_spec); + + QString name() const; + void set_name(const QString &newName); + + private: + gtl::math::cross_spec *_cross_spec = nullptr; + QString _name = ""; + + public slots: + void update(); + }; + + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_CROSS_SPEC_SERIES_H diff --git a/gui/gtl_gui_cross_spec_widget.cpp b/gui/gtl_gui_cross_spec_widget.cpp new file mode 100644 index 0000000..ca48f5a --- /dev/null +++ b/gui/gtl_gui_cross_spec_widget.cpp @@ -0,0 +1,107 @@ +#include "gtl_gui_cross_spec_widget.h" +#include "ui_gtl_gui_cross_spec_widget.h" + +#include "gui/gtl_gui_chart_widget.h" +namespace gtl { + namespace gui { + + cross_spec_widget::cross_spec_widget(QWidget *parent, gtl::data_model* model) : + QWidget(parent), + ui(new Ui::cross_spec_widget) + { + ui->setupUi(this); + + _selection_data_model = new gtl::selection_data_model(this); + _selection_data_model->setSourceModel(model); + + ui->signals_->setModel(_selection_data_model); + ui->signals_->expandAll(); + + _chart = new gtl::gui::cross_spec_chart(this); + gtl::gui::chart_widget *chart_widget = new gtl::gui::chart_widget(_chart, this); + ui->splitter->insertWidget(0, chart_widget); + + connect(_selection_data_model, >l::selection_data_model::selected, _chart, >l::gui::chart::add_ad); + connect(_selection_data_model, >l::selection_data_model::deselected, _chart, >l::gui::chart::remove_ad); + + QMetaEnum meta_enum = QMetaEnum::fromType(); + ui->window->disconnect(); + ui->window->clear(); + for(int i = 0; i < meta_enum.keyCount(); i++) + { + ui->window->addItem(meta_enum.valueToKey(i)); + } + ui->window->setCurrentIndex(_chart->window()); + connect(ui->window, &QComboBox::currentIndexChanged, _chart, >l::gui::cross_spec_chart::set_window); + + ui->frequency->disconnect(); + ui->frequency->setMaximum(100000); + ui->frequency->setValue(_chart->frequency()); + connect(ui->frequency, &QDoubleSpinBox::valueChanged, _chart, >l::gui::cross_spec_chart::set_frequency); + + ui->resolution->disconnect(); + ui->resolution->setMaximum(100000); + ui->resolution->setValue(_chart->resolution()); + connect(ui->resolution, &QDoubleSpinBox::valueChanged, _chart, >l::gui::cross_spec_chart::set_resolution); + + ui->lines->disconnect(); + ui->lines->setMaximum(100000); + ui->lines->setValue(_chart->lines()); + connect(ui->lines, &QDoubleSpinBox::valueChanged, _chart, >l::gui::cross_spec_chart::set_lines); + + ui->average->disconnect(); + ui->average->setMaximum(100); + ui->average->setValue(_chart->average()); + connect(ui->average, &QDoubleSpinBox::valueChanged, _chart, >l::gui::cross_spec_chart::set_average); + + ui->overlap->disconnect(); + ui->overlap->setMaximum(99); + ui->overlap->setValue(_chart->overlap()); + connect(ui->overlap, &QDoubleSpinBox::valueChanged, _chart, >l::gui::cross_spec_chart::set_overlap); + + connect(_chart, >l::gui::cross_spec_chart::frequency_changed, this, >l::gui::cross_spec_widget::update_parameters); + connect(_chart, >l::gui::cross_spec_chart::resolution_changed, this, >l::gui::cross_spec_widget::update_parameters); + connect(_chart, >l::gui::cross_spec_chart::lines_changed, this, >l::gui::cross_spec_widget::update_parameters); + + ui->x_log->setVisible(false); + connect(ui->x_log, &QCheckBox::toggled, _chart, >l::gui::cross_spec_chart::set_x_log); + + } + + cross_spec_widget::~cross_spec_widget() + { + delete ui; + } + + void cross_spec_widget::save(QDomElement &root_element) + { + + } + + void cross_spec_widget::load(const QDomElement &root_element) + { + + } + + void cross_spec_widget::update_parameters() + { + ui->frequency->disconnect(); + ui->resolution->disconnect(); + ui->lines->disconnect(); + + if( ui->frequency->value() != _chart->frequency() ) + ui->frequency->setValue(_chart->frequency()); + + if( ui->resolution->value() != _chart->resolution() ) + ui->resolution->setValue(_chart->resolution()); + + if( ui->lines->value() != (int) _chart->lines() ) + ui->lines->setValue(_chart->lines()); + + connect(ui->frequency, &QDoubleSpinBox::valueChanged, _chart, >l::gui::cross_spec_chart::set_frequency); + connect(ui->resolution, &QDoubleSpinBox::valueChanged, _chart, >l::gui::cross_spec_chart::set_resolution); + connect(ui->lines, &QDoubleSpinBox::valueChanged, _chart, >l::gui::cross_spec_chart::set_lines); + } + + } // namespace gui +} // namespace gtl diff --git a/gui/gtl_gui_cross_spec_widget.h b/gui/gtl_gui_cross_spec_widget.h new file mode 100644 index 0000000..84238d1 --- /dev/null +++ b/gui/gtl_gui_cross_spec_widget.h @@ -0,0 +1,43 @@ +#ifndef GTL_GUI_GTL_GUI_CROSS_SPEC_WIDGET_H +#define GTL_GUI_GTL_GUI_CROSS_SPEC_WIDGET_H + +#include + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" +#include "gui/gtl_gui_cross_spec_chart.h" +#include "gui_global.h" + +namespace Ui { +class cross_spec_widget; +} + +namespace gtl +{ + namespace gui + { + + class GTL_GUI_EXPORT cross_spec_widget : public QWidget + { + Q_OBJECT + + public: + explicit cross_spec_widget(QWidget *parent = nullptr, gtl::data_model* model = nullptr); + ~cross_spec_widget(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + Ui::cross_spec_widget *ui; + gtl::selection_data_model* _selection_data_model; + gtl::gui::cross_spec_chart* _chart; + + private: + void update_parameters(); + }; + + + } // namespace gui +} // namespace gtl +#endif // GTL_GUI_GTL_GUI_CROSS_SPEC_WIDGET_H diff --git a/gui/gtl_gui_cross_spec_widget.ui b/gui/gtl_gui_cross_spec_widget.ui new file mode 100644 index 0000000..7a49c9e --- /dev/null +++ b/gui/gtl_gui_cross_spec_widget.ui @@ -0,0 +1,169 @@ + + + cross_spec_widget + + + + 0 + 0 + 488 + 415 + + + + Form + + + + + + Qt::Horizontal + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + Qt::Horizontal + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Frequency + + + + + + + + + + Resolution + + + + + + + + + + Lines + + + + + + + + + + Window + + + + + + + + + + Average + + + + + + + 1.000000000000000 + + + 100.000000000000000 + + + + + + + Overlap + + + + + + + 100.000000000000000 + + + + + + + + + log x + + + + + + + + + + + + diff --git a/gui/gtl_gui_dock.cpp b/gui/gtl_gui_dock.cpp new file mode 100644 index 0000000..1084f63 --- /dev/null +++ b/gui/gtl_gui_dock.cpp @@ -0,0 +1,65 @@ +#include "gtl_gui_dock.h" + + +namespace gtl +{ + namespace gui + { + dock::dock(QWidget* parent) + : QDockWidget(parent) + { + _title = new dock_title(this); + setTitleBarWidget(_title); + + connect(_title, &dock_title::close, this, &QDockWidget::close); + connect(_title, &dock_title::pin, this, &dock::pin); + connect(_title, &dock_title::maximize, this, &dock::maximize); + + connect(this, &QDockWidget::topLevelChanged, this, &dock::floating_changed); + } + + void dock::set_title(QString title) + { + _title->set_text(title); + } + + void dock::pin(bool value) + { + setFloating(!value); + setAllowedAreas(value ? Qt::LeftDockWidgetArea : Qt::NoDockWidgetArea); + +// if (value) + // set_is_maximized(false); + + _title->set_pin_status(!value); + } + + void dock::floating_changed(bool value) + { + setAllowedAreas(value ? Qt::LeftDockWidgetArea : Qt::NoDockWidgetArea); + _title->set_pin_status(value); + } + + void dock::maximize(bool value) + { + if (value) + { + _state = saveGeometry(); + _is_floating = isFloating(); + + setFloating(true); + setAllowedAreas(Qt::NoDockWidgetArea); + + showMaximized(); + + + } + else + { + restoreGeometry(_state); + setFloating(_is_floating); + setAllowedAreas(!_is_floating ? Qt::LeftDockWidgetArea : Qt::NoDockWidgetArea); + } + } + } +} diff --git a/gui/gtl_gui_dock.h b/gui/gtl_gui_dock.h new file mode 100644 index 0000000..6595f05 --- /dev/null +++ b/gui/gtl_gui_dock.h @@ -0,0 +1,38 @@ +#ifndef DOCK_H +#define DOCK_H + +#include +#include + +#include "gtl_gui_dock_title.h" + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT dock : public QDockWidget + { + public: + dock(QWidget* parent = NULL); + void set_title(QString title); + + protected: + dock_title *_title; + + private: + QByteArray _state; + bool _is_floating; + + public slots: + virtual void pin(bool); + + protected slots: + void floating_changed(bool); + void maximize(bool); + }; + } +} + +#endif // DOCK_H diff --git a/gui/gtl_gui_dock_title.cpp b/gui/gtl_gui_dock_title.cpp new file mode 100644 index 0000000..ac16d87 --- /dev/null +++ b/gui/gtl_gui_dock_title.cpp @@ -0,0 +1,79 @@ +#include "gtl_gui_dock_title.h" +#include "ui_gtl_gui_dock_title.h" + +dock_title::dock_title(QWidget *parent) + : QWidget(parent) + , ui(new Ui::dock_title) + , _is_maximized(false) + +{ + ui->setupUi(this); + + connect(ui->pin, &QToolButton::clicked, this, &dock_title::pin); + connect(ui->maximize, &QToolButton::clicked, this, &dock_title::maximize_); + connect(ui->close, &QToolButton::clicked, this, &dock_title::close); +} + +dock_title::~dock_title() +{ + delete ui; +} + +QSize dock_title::minimumSizeHint() const +{ + return QSize(26, 26); +} + +QSize dock_title::sizeHint() const +{ + return QSize(26, 26); +} + +void dock_title::set_pin_status(bool status) +{ + ui->pin->setChecked(!status); + + if(!status) + { + _is_maximized = false; + set_maximze_icons(); + } +} + +void dock_title::set_text(QString value) +{ + ui->title->setText(value); +} + +bool dock_title::is_pinned() const +{ + return ui->pin->isChecked(); +} + +void dock_title::insert_widget(int idx, QWidget *widget) +{ + static_cast(ui->frame_title_dock->layout())->insertWidget(idx, widget); +} + +void dock_title::set_maximze_icons() +{ + if(_is_maximized) + { + ui->maximize->setIcon((QIcon(":/dock/restore"))); + ui->maximize->setToolTip(tr("Restore")); + } + else + { + ui->maximize->setIcon((QIcon(":/dock/maximize"))); + ui->maximize->setToolTip(tr("Maximize")); + } +} + +void dock_title::maximize_() +{ + _is_maximized = !_is_maximized; + + set_maximze_icons(); + + emit maximize(_is_maximized); +} diff --git a/gui/gtl_gui_dock_title.h b/gui/gtl_gui_dock_title.h new file mode 100644 index 0000000..616cefd --- /dev/null +++ b/gui/gtl_gui_dock_title.h @@ -0,0 +1,46 @@ +#ifndef GTL_GUI_DOCK_TITLE_H +#define GTL_GUI_DOCK_TITLE_H + +#include + +#include "gui_global.h" + +namespace Ui { +class dock_title; +} + +class GTL_GUI_EXPORT dock_title : public QWidget +{ + Q_OBJECT + +public: + explicit dock_title(QWidget *parent = nullptr); + ~dock_title(); + + virtual QSize minimumSizeHint() const; + virtual QSize sizeHint() const; + + void set_pin_status(bool status); + + void set_text(QString value); + bool is_pinned() const; + + void insert_widget(int idx, QWidget *widget); + +private: + Ui::dock_title *ui; + bool _is_maximized; + +private: + void set_maximze_icons(); + +signals: + void close(); + void maximize(bool); + void pin(bool); + +private slots: + void maximize_(); +}; + +#endif // GTL_GUI_DOCK_TITLE_H diff --git a/gui/gtl_gui_dock_title.ui b/gui/gtl_gui_dock_title.ui new file mode 100644 index 0000000..39f7ab8 --- /dev/null +++ b/gui/gtl_gui_dock_title.ui @@ -0,0 +1,245 @@ + + + dock_title + + + + 0 + 0 + 348 + 30 + + + + + 0 + 0 + + + + + Calibri + true + + + + Form + + + false + + + background-color: #606060; +color:white; + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::Box + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 20 + 20 + + + + + 20 + 20 + + + + Toggle pin status + + + ... + + + + :/dock/resources/pin.png + :/dock/resources/pin_on.png:/dock/resources/pin.png + + + + 20 + 20 + + + + true + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 0 + 20 + + + + + + + + + 0 + 0 + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + + Calibri + 9 + true + + + + TextLable + + + + + + + + 20 + 20 + + + + + 20 + 20 + + + + Maximize + + + ... + + + + :/dock/maximize:/dock/maximize + + + true + + + + + + + + 20 + 20 + + + + + 20 + 20 + + + + + true + + + + Close + + + ... + + + + :/dock/close + + + + true + + + + + + + + + + + + + diff --git a/gui/gtl_gui_group_options_widget.cpp b/gui/gtl_gui_group_options_widget.cpp new file mode 100644 index 0000000..c0109ab --- /dev/null +++ b/gui/gtl_gui_group_options_widget.cpp @@ -0,0 +1,23 @@ +#include "gtl_gui_group_options_widget.h" + +namespace gtl +{ + namespace gui + { + group_options_widget::group_options_widget(QWidget *parent) + : QWidget{parent} + { + + } + + void group_options_widget::save(QSettings *settings) + { + + } + + void group_options_widget::restore(QSettings *settings) + { + + } + } +} diff --git a/gui/gtl_gui_group_options_widget.h b/gui/gtl_gui_group_options_widget.h new file mode 100644 index 0000000..0f55e5f --- /dev/null +++ b/gui/gtl_gui_group_options_widget.h @@ -0,0 +1,31 @@ +#ifndef GROUP_OPTIONS_H +#define GROUP_OPTIONS_H + +#include +#include + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT group_options_widget : public QWidget + { + Q_OBJECT + public: + explicit group_options_widget(QWidget *parent = nullptr); + + virtual void apply() = 0; + virtual void discard() = 0; + + virtual void save(QSettings* settings); + virtual void restore(QSettings* settings); + + signals: + + }; + } +} + +#endif // GROUP_OPTIONS_H diff --git a/gui/gtl_gui_log_viewer.cpp b/gui/gtl_gui_log_viewer.cpp new file mode 100644 index 0000000..9fdb35a --- /dev/null +++ b/gui/gtl_gui_log_viewer.cpp @@ -0,0 +1,618 @@ +#include "gtl_gui_log_viewer.h" + +#include +#include +#include +#include +#include +#include + +namespace gtl +{ + namespace gui + { + /* * * * * * * * * * * * * * * * * * * * * * * * * * + * + * log_viewer + */ + log_viewer::log_viewer(QWidget *parent) + : QWidget{parent} + { + QVBoxLayout* mainLayout = new QVBoxLayout; + setLayout( mainLayout ); + + m_view = new MyTableVeiw(this); + m_view->verticalHeader()->setVisible(false); + m_view->setAutoScroll(false); + m_view->verticalHeader()->setDefaultSectionSize(5); + m_view->setSelectionMode(QAbstractItemView::ContiguousSelection); + + // * * * * * * * * * * * * * * * + // * change color of selected cell + const QColor hlClr = Qt::white; // highlight color to set + const QColor txtClr = Qt::black; // highlighted text color to set + QPalette p = palette(); + p.setColor(QPalette::Highlight, hlClr); + p.setColor(QPalette::HighlightedText, txtClr); + setPalette(p); + + connect(m_view, &QTableView::clicked, this, &log_viewer::slt_table_click); + connect(m_view->verticalScrollBar(), &QScrollBar::rangeChanged, this, &log_viewer::slt_set_scroll_range); + connect(m_view->verticalScrollBar(), &QScrollBar::valueChanged, this, &log_viewer::slt_set_scroll_value); + + m_model = new log_viewer_model(this); + connect(gtl::logger::getInstance(), >l::logger::sgn_send_to_viewer, m_model, &log_viewer_model::slt_append_string); + + m_proxy_model = new filter_proxy_model(this); + m_proxy_model->setSourceModel(m_model); + + m_view->setModel(m_proxy_model); + m_view->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Stretch); + + connect(m_model, &QAbstractTableModel::rowsAboutToBeInserted, this, &log_viewer::slt_rows_about_to_be_inserted); + connect(m_model, &QAbstractTableModel::rowsInserted, this, &log_viewer::slt_data_changed); + + // * * * * * * * * * * * * * * * + // * + // * levels + // * + // * * * * * * * * * * * * * * * + m_cbb_level = new QComboBox(); + connect(m_cbb_level, &QComboBox::currentIndexChanged, this, &log_viewer::slt_check_levels); + m_cbb_level->addItem("info"); + m_cbb_level->addItem("warning"); + m_cbb_level->addItem("error"); + m_cbb_level->addItem("debug"); + + // * * * * * * * * * * * * * * * + // * + // * searc text + // * + // * * * * * * * * * * * * * * * + m_le_search_string = new QLineEdit(); + m_le_search_string->setClearButtonEnabled(true); + m_le_search_string->setPlaceholderText("Search text ..."); + connect(m_le_search_string, &QLineEdit::textChanged, this, &log_viewer::slt_update_search_string); + + // * * * * * * * * * * * * * * * + // * + // * date time filter + // * + // * * * * * * * * * * * * * * * + QHBoxLayout* hl_date_time = new QHBoxLayout; + m_le_dt_from = new QLineEdit(); + m_le_dt_from->setClearButtonEnabled(true); + m_action_le_dt_from = m_le_dt_from->addAction(this->style()->standardIcon(QStyle::SP_TitleBarNormalButton), QLineEdit::LeadingPosition); + m_le_dt_from->setPlaceholderText("from ..."); + connect(m_action_le_dt_from, &QAction::triggered, this, &log_viewer::slt_dialog_set_datetime); + connect(m_le_dt_from, &QLineEdit::textChanged, this, &log_viewer::slt_update_dt); + + + m_le_dt_to = new QLineEdit(); + m_le_dt_to->setClearButtonEnabled(true); + m_action_le_dt_to = m_le_dt_to->addAction(this->style()->standardIcon(QStyle::SP_TitleBarNormalButton), QLineEdit::LeadingPosition); + m_le_dt_to->setPlaceholderText("to ..."); + connect(m_action_le_dt_to, &QAction::triggered, this, &log_viewer::slt_dialog_set_datetime); + connect(m_le_dt_to, &QLineEdit::textChanged, this, &log_viewer::slt_update_dt); + + QLabel* l_date_time = new QLabel(" - "); + + hl_date_time->addWidget(m_le_dt_from); + hl_date_time->addWidget(l_date_time); + hl_date_time->addWidget(m_le_dt_to); + + // * * * * * * * * * * * * * * * + // * + // * btn reset search + // * + // * * * * * * * * * * * * * * * + m_b_reset_search = new QPushButton(); + connect(m_b_reset_search, &QPushButton::clicked, this, &log_viewer::slt_reset_search); + m_b_reset_search->setText("Reset search"); + + // * * * * * * * * * * * * * * * + // * + // * btn clear data + // * + // * * * * * * * * * * * * * * * + m_b_clear_data = new QPushButton(); + connect(m_b_clear_data, &QPushButton::clicked, [this]() + { + m_model->clear(); + }); + m_b_clear_data->setText("Clear"); + + QHBoxLayout* hl_common = new QHBoxLayout; + hl_common->addWidget(m_cbb_level); + hl_common->addWidget(m_le_search_string); + hl_common->addLayout(hl_date_time); + hl_common->addWidget(m_b_reset_search); + hl_common->addWidget(m_b_clear_data); + + mainLayout->addLayout(hl_common); + mainLayout->addWidget(m_view); + } + + void log_viewer::slt_check_levels(int index) + { + QString str_mass_filter = ""; + + switch(index) + { + case 0: + str_mass_filter += "INFO|WARNING|ERROR"; + break; + + case 1: + str_mass_filter += "WARNING|ERROR"; + break; + + case 2: + str_mass_filter += "ERROR"; + break; + + case 3: + str_mass_filter += "DEBUG"; + break; + } + + m_proxy_model->set_filter_level(str_mass_filter); + } + + + void log_viewer::slt_update_search_string(void) + { + if(m_le_search_string->text().isEmpty()) + m_proxy_model->set_disable_search_string(); + else + { + m_proxy_model->set_search_string(m_le_search_string->text()); + m_proxy_model->set_enable_search_string(); + } + } + + + void log_viewer::slt_reset_search(void) + { + m_le_search_string->setText(""); + + m_le_dt_from->setText(""); + m_le_dt_to->setText(""); + + m_proxy_model->set_search_string(""); + m_proxy_model->set_disable_search_string(); + m_proxy_model->set_disable_date_filter(); + } + + + void log_viewer::slt_dialog_set_datetime(void) + { + QAction *action_sender = (QAction *)sender(); + + QVBoxLayout* layout = new QVBoxLayout; + + QTime timeNow = QTime::currentTime(); + + QSpacerItem *space_left = new QSpacerItem(10, 10, QSizePolicy::Expanding); + QSpacerItem *space_right = new QSpacerItem(10, 10, QSizePolicy::Expanding); + + QLabel *l_hour = new QLabel(":"); + QLabel *l_minute = new QLabel(":"); + + QSpinBox *hour = new QSpinBox(); + hour->setMinimum(0); + hour->setMaximum(23); + hour->setValue(timeNow.hour()); + + QSpinBox *minute = new QSpinBox(); + minute->setMinimum(0); + minute->setMaximum(59); + minute->setValue(timeNow.minute()); + + QSpinBox *second = new QSpinBox(); + second->setMinimum(0); + second->setMaximum(59); + second->setValue(timeNow.second()); + + QHBoxLayout* layout_time = new QHBoxLayout; + + layout_time->addSpacerItem(space_left); + layout_time->addWidget(hour); + layout_time->addWidget(l_hour); + layout_time->addWidget(minute); + layout_time->addWidget(l_minute); + layout_time->addWidget(second); + layout_time->addSpacerItem(space_right); + + QPushButton *btn_ok = new QPushButton("&ОК"); + btn_ok->setObjectName("btn_ok"); + QPushButton *btn_cancel = new QPushButton("&Cancel"); + btn_cancel->setObjectName("btn_cancel"); + QHBoxLayout *layout_btn = new QHBoxLayout; + layout_btn->addWidget(btn_ok); + layout_btn->addWidget(btn_cancel); + + m_calendar_widget = new QCalendarWidget(this); + + layout->addWidget(m_calendar_widget); + layout->addLayout(layout_time); + layout->addLayout(layout_btn); + + m_dialog_set_datetime = new QDialog(this); + m_dialog_set_datetime->setLayout(layout); + m_dialog_set_datetime->setWindowTitle("Select date and time"); + + connect(btn_ok, &QPushButton::clicked, m_dialog_set_datetime, &QDialog::accept); + connect(btn_cancel, &QPushButton::clicked, m_dialog_set_datetime, &QDialog::reject); + + QString result_str; + if(m_dialog_set_datetime->exec() == QDialog::Accepted) + { + result_str = m_calendar_widget->selectedDate().toString("dd.MM.yyyy ") + + QString("%1:").arg(hour->value(), 2, 10, QLatin1Char('0')) + + QString("%1:").arg(minute->value(), 2, 10, QLatin1Char('0')) + + QString("%1").arg(second->value(), 2, 10, QLatin1Char('0')); + + if(action_sender == m_action_le_dt_from) + m_le_dt_from->setText(result_str); + else if(action_sender == m_action_le_dt_to) + m_le_dt_to->setText(result_str); + } + } + + void log_viewer::slt_update_dt(void) + { + if( (false == m_le_dt_from->text().isEmpty()) && + (false == m_le_dt_to->text().isEmpty()) ) + { + QDateTime dt_from = QDateTime::fromString(m_le_dt_from->text(), "dd.MM.yyyy hh:mm:ss"); + QDateTime dt_to = QDateTime::fromString(m_le_dt_to->text(), "dd.MM.yyyy hh:mm:ss"); + if(dt_from > dt_to) + { + QMessageBox::warning(this, + "Warning", + "Date From must be earlier than Date To", + QMessageBox::Ok, + QMessageBox::Ok ); + m_proxy_model->set_disable_date_filter(); + } + else + { + m_proxy_model->set_datetime_filter(dt_from, dt_to); + m_proxy_model->set_enable_date_filter(); + } + } + else + m_proxy_model->set_disable_date_filter(); + } + + void log_viewer::slt_rows_about_to_be_inserted() + { + m_prev_index = m_view->currentIndex(); + m_prev_value = m_view->verticalScrollBar()->value(); + } + + void log_viewer::slt_data_changed(void) + { + // this condition is necessary for the correct display of the last rows of the table + if((m_scroll_max - m_view->verticalScrollBar()->value()) > 4 ) + m_view->scrollTo( m_prev_index, QAbstractItemView::PositionAtCenter ); + else + m_view->scrollTo( m_prev_index, QAbstractItemView::PositionAtTop ); + m_prev_value = m_view->verticalScrollBar()->value(); + } + + void log_viewer::slt_set_scroll_range(int min, int max) + { + m_scroll_min = min; + m_scroll_max = max; + } + + void log_viewer::slt_table_click(const QModelIndex &index) + { + m_prev_index = index; + m_view->setCurrentIndex(index); + m_prev_value = m_view->verticalScrollBar()->value(); + } + + void log_viewer::slt_set_scroll_value() + { + int curr_value = m_view->verticalScrollBar()->value(); + + // if scroll in top position - then start autoscroll + if(curr_value == m_scroll_min) + { + m_prev_index = QModelIndex(); + m_view->setCurrentIndex(m_prev_index); + m_prev_value = curr_value; + return; + } + + // if not select cell + if(m_prev_index == QModelIndex()) + { + m_prev_index = m_view->indexAt(m_view->rect().center()); + m_view->setCurrentIndex(m_prev_index); + m_prev_value = curr_value; + } + + // if move slider or page UP/DOWN + if(m_view->verticalScrollBar()->isSliderDown() || m_view->verticalScrollBar()->hasFocus() || m_view->verticalScrollBar()->underMouse()) + { + if(m_prev_value < curr_value) + { + int row = m_prev_index.row() + (curr_value - m_prev_value); + if(row > m_scroll_max) + row = m_scroll_max; + m_prev_index = QModelIndex(m_view->model()->index(row, m_prev_index.column())); + m_view->setCurrentIndex(m_prev_index); + m_prev_value = curr_value; + } + else if(m_prev_value > curr_value) + { + int row = m_prev_index.row() - (m_prev_value - curr_value); + if(row < m_scroll_min) + row = m_scroll_min; + m_prev_index = QModelIndex(m_view->model()->index(row, m_prev_index.column())); + m_view->setCurrentIndex(m_prev_index); + m_prev_value = curr_value; + } + else + { + m_prev_value = curr_value; + } + } + else // if mouse whell active + { + if(m_view->flag_whell_rotate == MyTableVeiw::Rotate::DOWN) + { + m_view->flag_whell_rotate = MyTableVeiw::Rotate::NONE; + if(m_prev_value < curr_value) + { + int row = m_prev_index.row() + (curr_value - m_prev_value); + if(row > m_scroll_max) + row = m_scroll_max; + m_prev_index = QModelIndex(m_view->model()->index(row, m_prev_index.column())); + m_view->setCurrentIndex(m_prev_index); + m_prev_value = curr_value; + } + else + { + m_prev_value = curr_value; + } + + } + else if(m_view->flag_whell_rotate ==MyTableVeiw::Rotate::UP) + { + m_view->flag_whell_rotate = MyTableVeiw::Rotate::NONE; + if(m_prev_value > curr_value) + { + int row = m_prev_index.row() - (m_prev_value - curr_value); + if(row < m_scroll_min) + row = m_scroll_min; + m_prev_index = QModelIndex(m_view->model()->index(row, m_prev_index.column())); + m_view->setCurrentIndex(m_prev_index); + m_prev_value = curr_value; + } + else + { + m_prev_value = curr_value; + } + } + } + } + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * + * + * MySortFilterProxyModel + */ + filter_proxy_model::filter_proxy_model(QObject *parent) + : QSortFilterProxyModel(parent) + { + m_filter_level = "INFO|WARNING|ERROR|DEBUG"; + m_search_string = ""; + m_enable_text_search = false; + m_enable_date_filter = false; + } + + bool filter_proxy_model::filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const + { + // TEXT TAG: find rows containing search strings in Tag and Text + if(m_enable_text_search) + { + if(false == m_search_string.isEmpty()) + { + QModelIndex indexTag = sourceModel()->index(sourceRow, 3, sourceParent); + QModelIndex indexText = sourceModel()->index(sourceRow, 4, sourceParent); + if( (false == (sourceModel()->data(indexTag).toString().contains(m_search_string, Qt::CaseInsensitive))) && + (false == (sourceModel()->data(indexText).toString().contains(m_search_string, Qt::CaseInsensitive))) ) + return false; + } + } + + // DATE TIME: find rows with correct date time + if(m_enable_date_filter) + { + // get date + QModelIndex index = sourceModel()->index(sourceRow, 1, sourceParent); + QString check_dt = sourceModel()->data(index).toString() + " "; + + // get time + index = sourceModel()->index(sourceRow, 2, sourceParent); + check_dt += sourceModel()->data(index).toString(); + + if(false == check_date_in_range( QDateTime::fromString(check_dt, "dd.MM.yyyy hh:mm:ss.zzz")) ) + return false; + } + + // LEVEL: find rows with selected levels + QModelIndex indexLevel = sourceModel()->index(sourceRow, 0, sourceParent); + + if(false == m_filter_level.isEmpty()) + { + QStringList list = m_filter_level.split("|"); + if(!list.isEmpty()) + { + foreach(QString str, list) + { + if((sourceModel()->data(indexLevel).toString().contains(str) ) ) + { + return true; + } + } + } + } + + return false; + } + + bool filter_proxy_model::check_date_in_range(QDateTime date) const + { + return ((date >= m_dt_from) && + (date <= m_dt_to)); + } + + void filter_proxy_model::set_filter_level(const QString &level) + { + m_filter_level = level; + invalidateFilter(); + } + + void filter_proxy_model::set_search_string(const QString &str) + { + m_search_string = str; + } + + void filter_proxy_model::set_enable_search_string(void) + { + m_enable_text_search = true; + invalidateFilter(); + } + + void filter_proxy_model::set_disable_search_string(void) + { + m_enable_text_search = false; + invalidateFilter(); + } + + void filter_proxy_model::set_datetime_filter(QDateTime from, QDateTime to) + { + m_dt_from = from; + m_dt_to = to; + } + + void filter_proxy_model::set_enable_date_filter(void) + { + m_enable_date_filter = true; + invalidateFilter(); + } + + void filter_proxy_model::set_disable_date_filter(void) + { + m_enable_date_filter = false; + invalidateFilter(); + } + + /* * * * * * * * * * * * * * * * * * * * * * * * * * + * + * LogViewerModel + */ + log_viewer_model::log_viewer_model( QObject* parent ) : QAbstractTableModel( parent ) + { + } + + int log_viewer_model::rowCount( const QModelIndex& parent ) const + { + Q_UNUSED( parent ) + return m_logs.count(); + } + + int log_viewer_model::columnCount( const QModelIndex& parent ) const + { + Q_UNUSED( parent ) + return LAST; + } + + QVariant log_viewer_model::headerData( int section, Qt::Orientation orientation, int role ) const + { + if( role != Qt::DisplayRole ) { + return QVariant(); + } + + if( orientation == Qt::Vertical ) { + return section; + } + + switch( section ) { + case LEVEL: + return "Level"; + case DATE: + return "Date"; + case TIME: + return "Time"; + case TAG: + return "Tag"; + case TEXT: + return "Text"; + } + + return QVariant(); + } + + + QVariant log_viewer_model::data( const QModelIndex& index, int role ) const + { + if( !index.isValid() || + m_logs.count() <= index.row() || + ( role != Qt::DisplayRole)) + { + return QVariant(); + } + + return m_logs[ index.row() ][ Column( index.column() ) ]; + } + + void log_viewer_model::slt_append_string( const QString& level, const QString& date, const QString& time, const QString& tag, const QString& text ) + { + LogViewerData log; + log[ LEVEL ] = level; + log[ DATE ] = date; + log[ TIME ] = time; + log[ TAG ] = tag; + log[ TEXT ] = text; + + beginInsertRows( QModelIndex(), 0, 0 ); + m_logs.insert(0, log ); + endInsertRows(); + + emit sgnl_set_data(); + } + + + bool log_viewer_model::setData( const QModelIndex& index, const QVariant& value, int role ) { + if( !index.isValid() || role != Qt::EditRole || m_logs.count() <= index.row() ) { + return false; + } + + m_logs[ index.row() ][ Column( index.column() ) ] = value; + emit dataChanged( index, index ); + + return true; + } + + void log_viewer_model::set_level_value(uint8_t val) + { + m_select_levels = val; + } + + void log_viewer_model::clear() + { + beginResetModel(); + m_logs.clear(); + endResetModel(); + } + } +} + diff --git a/gui/gtl_gui_log_viewer.h b/gui/gtl_gui_log_viewer.h new file mode 100644 index 0000000..ea2933d --- /dev/null +++ b/gui/gtl_gui_log_viewer.h @@ -0,0 +1,200 @@ +#ifndef GTL_GUI_LOG_VIEWER_H +#define GTL_GUI_LOG_VIEWER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gui_global.h" + +#include "core/gtl_logger.h" + +namespace gtl +{ + namespace gui + { + /* * * * * * * * * * * * * * * * * * * * * * * * * * + * + * main view model + */ + class log_viewer_model : public QAbstractTableModel { + Q_OBJECT + public: + log_viewer_model( QObject* parent = 0 ); + + int rowCount( const QModelIndex& parent = QModelIndex() ) const; + int columnCount( const QModelIndex& paren = QModelIndex() ) const; + QVariant data( const QModelIndex& index, int role ) const; + bool setData( const QModelIndex& index, const QVariant& value, int role ); + QVariant headerData( int section, Qt::Orientation orientation, int role ) const; + + void set_level_value(uint8_t val); + void clear(void); + public slots: + void slt_append_string( const QString& level, const QString& date, const QString& time, const QString& tag, const QString& text ); + + signals: + void sgnl_set_data(void); + + private: + enum Column { + LEVEL = 0, + DATE, + TIME, + TAG, + TEXT, + LAST + }; + + uint8_t m_select_levels; + + typedef QHash< Column, QVariant > LogViewerData; + typedef QList< LogViewerData > Logs; + Logs m_logs; + }; + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * + * + * proxy model + */ + class filter_proxy_model : public QSortFilterProxyModel + { + Q_OBJECT + + public: + filter_proxy_model(QObject *parent = nullptr); + void set_filter_level(const QString &level); + void set_search_string(const QString &str); + + void set_enable_search_string(void); + void set_disable_search_string(void); + + void set_enable_date_filter(void); + void set_disable_date_filter(void); + + void set_datetime_filter(QDateTime from, QDateTime to); + + protected: + QString m_filter_level; + QString m_search_string; + + QDateTime m_dt_from; + QDateTime m_dt_to; + + bool m_enable_text_search; + bool m_enable_date_filter; + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + private: + bool check_date_in_range(QDateTime date) const; + }; + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * + * + * custom QTableView class + */ + class MyTableVeiw : public QTableView + { + Q_OBJECT + + public: + explicit MyTableVeiw(QWidget *parent = nullptr){}; + enum Rotate { + NONE = 0, + DOWN, + UP + }; + + int flag_whell_rotate = DOWN; + + protected: + virtual void wheelEvent(QWheelEvent *e) override + { + if (e->angleDelta().y() < 0) + { + flag_whell_rotate = DOWN; + } + else + { + flag_whell_rotate = UP; + } + QTableView::wheelEvent(e); + }; + }; + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * + * + * log_viewer + */ + class GTL_GUI_EXPORT log_viewer : public QWidget + { + Q_OBJECT + public: + explicit log_viewer(QWidget *parent = nullptr); + + public slots: + void slt_data_changed(void); + void slt_rows_about_to_be_inserted(); + void slt_check_levels(int index); + void slt_update_search_string(void); + + void slt_reset_search(void); + void slt_dialog_set_datetime(void); + void slt_update_dt(void); + void slt_set_scroll_range(int min, int max); + void slt_set_scroll_value(); + void slt_table_click(const QModelIndex &index); + + + private: + filter_proxy_model *m_proxy_model; + + MyTableVeiw *m_view; + log_viewer_model *m_model; + + QModelIndex m_prev_index = QModelIndex(); + int m_prev_value = -1; + + QComboBox *m_cbb_level; + + QLineEdit *m_le_search_string; + QLineEdit *m_le_search_date; + QLineEdit *m_le_search_time; + + QLineEdit *m_le_dt_from; + QLineEdit *m_le_dt_to; + + QAction *m_action_le_dt_from; + QAction *m_action_le_dt_to; + + QDate m_date_from; + QTime m_time_from; + QDate m_date_to; + QTime m_time_to; + + QPushButton *m_b_reset_search; + QPushButton *m_b_clear_data; + + QCalendarWidget *m_calendar_widget; + QDialog *m_dialog_set_datetime; + + int m_scroll_min; + int m_scroll_max; + }; + } +} + +#endif // GTL_GUI_LOG_VIEWER_H diff --git a/gui/gtl_gui_meas_widget.cpp b/gui/gtl_gui_meas_widget.cpp new file mode 100644 index 0000000..1042be4 --- /dev/null +++ b/gui/gtl_gui_meas_widget.cpp @@ -0,0 +1,77 @@ +#include "gtl_gui_meas_widget.h" +#include "ui_gtl_gui_meas_widget.h" + +namespace gtl { + namespace gui { + + meas_widget::meas_widget(QWidget *parent, QAbstractItemModel* model, QStyledItemDelegate *delegate) : + QWidget(parent), + ui(new Ui::meas_widget), + _model(model), + _delegate(delegate), + _edited_column_count(2) + { + ui->setupUi(this); + + _tableView = new QTableView; + _tableView->horizontalHeader()->setStretchLastSection(true); + ui->verticalLayout->addWidget(_tableView); + + if(_model && _delegate) + { + _tableView->setModel(_model); + _tableView->setItemDelegateForColumn(0, _delegate); + _tableView->setItemDelegateForColumn(1, _delegate); + _tableView->setSelectionBehavior(QAbstractItemView::SelectRows); + _tableView->setContentsMargins(0,0,0,0); + + connect(ui->addButton, &QPushButton::clicked, this, &meas_widget::add); + connect(ui->removeButton, &QPushButton::clicked, this, &meas_widget::remove); + } + + this->hide(); + } + + meas_widget::~meas_widget() + { + delete ui; + } + + void meas_widget::add() + { + if(_tableView && _model) + { + int row = _model->rowCount(); + _model->insertRows(row, 1, QModelIndex()); + for(int c = 0; c < _edited_column_count; ++c) + { + QModelIndex index = _model->index(row, c, QModelIndex()); + _tableView->openPersistentEditor(index); + } + } + } + + void meas_widget::remove() + { + if(_tableView && _model) + { + for(int r = 0; r < _model->rowCount(); ++r) + for(int c = 0; c < _edited_column_count; ++c) + { + QModelIndex index = _model->index(r, c, QModelIndex()); + _tableView->closePersistentEditor(index); + } + + int row = _tableView->currentIndex().row(); + _model->removeRows(row, 1, QModelIndex()); + + for(int r = 0; r < _model->rowCount(); ++r) + for(int c = 0; c < _edited_column_count; ++c) + { + QModelIndex index = _model->index(r, c, QModelIndex()); + _tableView->openPersistentEditor(index); + } + } + } + } // namespace gui +} // namespace gtl diff --git a/gui/gtl_gui_meas_widget.h b/gui/gtl_gui_meas_widget.h new file mode 100644 index 0000000..5b7f063 --- /dev/null +++ b/gui/gtl_gui_meas_widget.h @@ -0,0 +1,41 @@ +#ifndef GTL_GUI_MEAS_WIDGET_H +#define GTL_GUI_MEAS_WIDGET_H + +#include +#include +#include +#include + +#include "gui_global.h" + +namespace Ui { +class meas_widget; +} + +namespace gtl { + namespace gui { + + class GTL_GUI_EXPORT meas_widget : public QWidget + { + Q_OBJECT + + public: + explicit meas_widget(QWidget *parent = nullptr, QAbstractItemModel *model = nullptr, QStyledItemDelegate *delegate = nullptr); + ~meas_widget(); + + private: + Ui::meas_widget *ui; + QAbstractItemModel *_model = nullptr; + QStyledItemDelegate *_delegate = nullptr; + QTableView *_tableView = nullptr; + int _edited_column_count; + + private slots: + void add(); + void remove(); + + + }; + } // namespace gui +} // namespace gtl +#endif // GTL_GUI_MEAS_WIDGET_H diff --git a/gui/gtl_gui_meas_widget.ui b/gui/gtl_gui_meas_widget.ui new file mode 100644 index 0000000..b5f4fda --- /dev/null +++ b/gui/gtl_gui_meas_widget.ui @@ -0,0 +1,52 @@ + + + meas_widget + + + + 0 + 0 + 391 + 303 + + + + Spectrum parameters + + + + + + + + + + + + + + + + - + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + diff --git a/gui/gtl_gui_octv_chart.cpp b/gui/gtl_gui_octv_chart.cpp new file mode 100644 index 0000000..82ff969 --- /dev/null +++ b/gui/gtl_gui_octv_chart.cpp @@ -0,0 +1,239 @@ +#include "gtl_gui_octv_chart.h" + +namespace gtl +{ + namespace gui + { + octv_chart::octv_chart(QWidget* parent) + : chart(parent) + , _minimum(1) + , _maximum(1000) + , _ratio(0) + , _look(0) + , _fraction(0) + , _unit(0) + { + + } + + qreal octv_chart::minimum() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _minimum = static_cast(*iter_series)->minimum(); + } + return _minimum; + } + + qreal octv_chart::maximum() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _maximum = static_cast(*iter_series)->maximum(); + } + return _maximum; + } + + int octv_chart::ratio() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _ratio = static_cast(*iter_series)->ratio(); + } + return _ratio; + } + + int octv_chart::look() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _look = static_cast(*iter_series)->look(); + } + return _look; + } + + int octv_chart::fraction() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _fraction = static_cast(*iter_series)->fraction(); + } + return _fraction; + } + + int octv_chart::unit() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _unit = static_cast(*iter_series)->unit(); + } + return _unit; + } + + void octv_chart::save(QDomElement &root_element) + { +// chart::save(root_element); + +// root_element.setAttribute("minimum", _minimum); +// root_element.setAttribute("maximum", _maximum); +// root_element.setAttribute("ratio", _ratio); +// root_element.setAttribute("look", _look); +// root_element.setAttribute("fraction", _fraction); +// root_element.setAttribute("unit", _unit); + } + + void octv_chart::load(const QDomElement &root_element) + { +// chart::load(root_element); + +// _minimum = root_element.attribute("minimum", "1").toDouble(); +// _maximum = root_element.attribute("maximum", "1000").toDouble(); +// _ratio = root_element.attribute("ratio", "0").toInt(); +// _look = root_element.attribute("look", "0").toUInt(); +// _fraction = root_element.attribute("fraction", "0").toUInt(); +// _unit = root_element.attribute("unit", "0").toInt(); + } + + void octv_chart::set_minimum(qreal value) + { + if(value != _minimum) + { + if(_series.empty()) + { + _minimum = value; + } + else + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_minimum(value); + _minimum = static_cast(*iter_series)->minimum(); + } + } + + emit minimum_changed(); + } + } + + void octv_chart::set_maximum(qreal value) + { + if(value != _maximum) + { + if(_series.empty()) + { + _maximum = value; + } + else + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_maximum(value); + _maximum = static_cast(*iter_series)->maximum(); + } + } + + emit minimum_changed(); + } + } + + void octv_chart::set_ratio(int value) + { + if(value != _ratio) + { + if(_series.empty()) + { + _ratio = value; + } + else + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_ratio(value); + _ratio = static_cast(*iter_series)->ratio(); + } + } + + emit ratio_changed(); + } + } + + void octv_chart::set_look(int value) + { + if(value != _look) + { + if(_series.empty()) + { + _look = value; + } + else + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_look(value); + _look = static_cast(*iter_series)->look(); + } + } + + emit look_changed(); + } + } + + void octv_chart::set_fraction(int value) + { + if(value != _fraction) + { + if(_series.empty()) + { + _fraction = value; + } + else + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_fraction(value); + _fraction = static_cast(*iter_series)->fraction(); + } + } + + emit fraction_changed(); + } + } + + void octv_chart::set_unit(int value) + { + if(value != _unit) + { + if(_series.empty()) + { + _unit = value; + } + else + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_unit(value); + _unit = static_cast(*iter_series)->unit(); + } + } + + emit unit_changed(); + } + } + + chart_series *octv_chart::create_series(analog_data *ai) + { + octv_series *series = new octv_series(is_updating(), ai, _axis_x, _axis_y); + + series->set_minimum(_minimum); + series->set_maximum(_maximum); + series->set_ratio(_ratio); + series->set_look(_look); + series->set_fraction(_fraction); + series->set_unit(_unit); + + series->update(); + + return series; + } + } +} diff --git a/gui/gtl_gui_octv_chart.h b/gui/gtl_gui_octv_chart.h new file mode 100644 index 0000000..bdcf0e8 --- /dev/null +++ b/gui/gtl_gui_octv_chart.h @@ -0,0 +1,58 @@ +#ifndef OCTV_CHART_H +#define OCTV_CHART_H + +#include "gtl_gui_chart.h" + +#include "gtl_gui_octv_series.h" + +namespace gtl +{ + namespace gui + { + class octv_chart : public gtl::gui::chart + { + Q_OBJECT + public: + octv_chart(QWidget* parent = NULL); + + qreal minimum(); + qreal maximum(); + int ratio(); + int look(); + int fraction(); + int unit(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + qreal _minimum; + qreal _maximum; + int _ratio; + int _look; + int _fraction; + int _unit; + + signals: + void minimum_changed(); + void maximum_changed(); + void ratio_changed(); + void look_changed(); + void fraction_changed(); + void unit_changed(); + + public slots: + void set_minimum(qreal value); + void set_maximum(qreal value); + void set_ratio(int value); + void set_look(int value); + void set_fraction(int value); + void set_unit(int value); + + protected: + virtual chart_series* create_series(gtl::analog_data* ai); + }; + } +} + +#endif // OCTV_CHART_H diff --git a/gui/gtl_gui_octv_series.cpp b/gui/gtl_gui_octv_series.cpp new file mode 100644 index 0000000..a726ea8 --- /dev/null +++ b/gui/gtl_gui_octv_series.cpp @@ -0,0 +1,102 @@ +#include "gtl_gui_octv_series.h" + +namespace gtl +{ + namespace gui + { + octv_series::octv_series(bool is_updating, gtl::analog_data* ai, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y) + : chart_series(ai, axis_x, axis_y) + { + _is_updating = is_updating; + + _octv = new gtl::math::octv(ai); + + connect(_octv, >l::math::octv::changed, this, &octv_series::update); + connect(_octv, >l::math::octv::initialized, this, &octv_series::update); + } + + octv_series::~octv_series() + { + delete _octv; + } + + qreal octv_series::minimum() const + { + return _octv->minimum(); + } + + qreal octv_series::maximum() const + { + return _octv->maximum(); + } + + int octv_series::ratio() const + { + return static_cast(_octv->ratio()); + } + + int octv_series::look() const + { + return static_cast(_octv->look()); + } + + int octv_series::fraction() const + { + return static_cast(_octv->fraction()); + } + + int octv_series::unit() const + { + return static_cast(_octv->unit()); + } + + void octv_series::set_minimum(qreal value) + { + _octv->set_minimum(value); + } + + void octv_series::set_maximum(qreal value) + { + _octv->set_maximum(value); + } + + void octv_series::set_ratio(int value) + { + _octv->set_ratio(static_cast(value)); + } + + void octv_series::set_look(int value) + { + _octv->set_look(static_cast(value)); + } + + void octv_series::set_fraction(int value) + { + _octv->set_fraction(static_cast(value)); + } + + void octv_series::set_unit(int value) + { + _octv->set_unit(static_cast(value)); + } + + void octv_series::update() + { + clear(); + + /* + for(auto it : *_octv) + add(QPointF(it.first, it.second)); + */ + + for(int i = 0; i < _octv->size(); i++) + { + add(QPointF(_octv->left(i), _octv->at(i).second)); + add(QPointF(_octv->at(i).first, _octv->at(i).second)); + add(QPointF(_octv->right(i), _octv->at(i).second)); + } + + emit data_changed(); + } + } +} diff --git a/gui/gtl_gui_octv_series.h b/gui/gtl_gui_octv_series.h new file mode 100644 index 0000000..6f63bed --- /dev/null +++ b/gui/gtl_gui_octv_series.h @@ -0,0 +1,42 @@ +#ifndef OCTV_SERIES_H +#define OCTV_SERIES_H + +#include "math/gtl_math_octv.h" + +#include "gtl_gui_chart_series.h" + +namespace gtl +{ + namespace gui + { + class octv_series : public gtl::gui::chart_series + { + Q_OBJECT + public: + octv_series(bool is_updating, gtl::analog_data* ai, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y); + ~octv_series(); + + qreal minimum() const; + qreal maximum() const; + int ratio() const; + int look() const; + int fraction() const; + int unit() const; + + void set_minimum(qreal value); + void set_maximum(qreal value); + void set_ratio(int value); + void set_look(int value); + void set_fraction(int value); + void set_unit(int value); + + protected: + gtl::math::octv *_octv; + + public slots: + void update(); + }; + } +} + +#endif // OCTV_SERIES_H diff --git a/gui/gtl_gui_octv_widget.cpp b/gui/gtl_gui_octv_widget.cpp new file mode 100644 index 0000000..2575202 --- /dev/null +++ b/gui/gtl_gui_octv_widget.cpp @@ -0,0 +1,152 @@ +#include "gtl_gui_octv_widget.h" +#include "ui_gtl_gui_octv_widget.h" + +#include "gui/gtl_gui_chart_widget.h" + +namespace gtl +{ + namespace gui + { + octv_widget::octv_widget(QWidget *parent, gtl::data_model* model) + : QWidget(parent) + , ui(new Ui::octv_widget) + { + ui->setupUi(this); + + _selection_data_model = new gtl::selection_data_model(this); + _selection_data_model->setSourceModel(model); + + ui->signals_->setModel(_selection_data_model); + ui->signals_->expandAll(); + + _chart = new gtl::gui::octv_chart(this); + gtl::gui::chart_widget *chart_widget = new gtl::gui::chart_widget(_chart, this); + ui->splitter->insertWidget(0, chart_widget); + + connect(_selection_data_model, >l::selection_data_model::selected, _chart, >l::gui::chart::add_ad); + connect(_selection_data_model, >l::selection_data_model::deselected, _chart, >l::gui::chart::remove_ad); + + ui->minimum->disconnect(); + ui->minimum->setMinimum(0.1); + ui->minimum->setMaximum(100000); + ui->minimum->setValue(_chart->minimum()); + connect(ui->minimum, &QDoubleSpinBox::valueChanged,_chart, >l::gui::octv_chart::set_minimum); + + ui->maximum->disconnect(); + ui->maximum->setMinimum(1); + ui->maximum->setMaximum(100000); + ui->maximum->setValue(_chart->maximum()); + connect(ui->maximum, &QDoubleSpinBox::valueChanged,_chart, >l::gui::octv_chart::set_maximum); + + + QMetaEnum meta_enum = QMetaEnum::fromType(); + ui->ratio->disconnect(); + ui->ratio->clear(); + for(int i = 0; i < meta_enum.keyCount(); i++) + { + ui->ratio->addItem(meta_enum.valueToKey(i)); + } + ui->ratio->setCurrentIndex(_chart->ratio()); + connect(ui->ratio, &QComboBox::currentIndexChanged, _chart, >l::gui::octv_chart::set_ratio); + + meta_enum = QMetaEnum::fromType(); + ui->look->disconnect(); + ui->look->clear(); + for(int i = 0; i < meta_enum.keyCount(); i++) + { + ui->look->addItem(meta_enum.valueToKey(i)); + } + ui->look->setCurrentIndex(_chart->look()); + connect(ui->look, &QComboBox::currentIndexChanged, _chart, >l::gui::octv_chart::set_look); + + meta_enum = QMetaEnum::fromType(); + ui->fraction->disconnect(); + ui->fraction->clear(); + for(int i = 0; i < meta_enum.keyCount(); i++) + { + ui->fraction->addItem(meta_enum.valueToKey(i)); + } + ui->fraction->setCurrentIndex(_chart->fraction()); + connect(ui->fraction, &QComboBox::currentIndexChanged, _chart, >l::gui::octv_chart::set_fraction); + + ui->unit->disconnect(); + ui->unit->clear(); + ui->unit->addItem("unit"); + ui->unit->addItem("db"); + ui->unit->setCurrentIndex(_chart->unit()); + connect(ui->unit, &QComboBox::currentIndexChanged, _chart, >l::gui::octv_chart::set_unit); + } + + octv_widget::~octv_widget() + { + delete ui; + } + + void octv_widget::save(QDomElement &root_element) + { + QDomElement selection_element = root_element.ownerDocument().createElement("selection"); + root_element.appendChild(selection_element); + _selection_data_model->save(selection_element); + + QDomElement options_element = root_element.ownerDocument().createElement("options"); + root_element.appendChild(options_element); + _chart->save(options_element); + + QDomElement splitter_element = root_element.ownerDocument().createElement("splitter"); + root_element.appendChild(splitter_element); + splitter_element.setAttribute("state", QString(ui->splitter->saveState().toBase64())); + } + + void octv_widget::load(const QDomElement &root_element) + { + QDomElement options_element = root_element.firstChildElement("options"); + _chart->load(options_element); + + update_parameters(); + + QDomElement selection_element = root_element.firstChildElement("selection"); + _selection_data_model->load(selection_element); + + + + QDomElement splitter_element = root_element.firstChildElement("splitter"); + ui->splitter->restoreState(QByteArray::fromBase64(splitter_element.attribute("state", "").toUtf8())); + } + + void octv_widget::update_parameters() + { + ui->minimum->disconnect(); + ui->maximum->disconnect(); + ui->ratio->disconnect(); + ui->look->disconnect(); + ui->fraction->disconnect(); + ui->unit->disconnect(); + + if( ui->minimum->value() != _chart->minimum() ) + ui->minimum->setValue(_chart->minimum()); + + if( ui->maximum->value() != _chart->maximum() ) + ui->maximum->setValue(_chart->maximum()); + + if( ui->ratio->currentIndex() != (int) _chart->ratio() ) + ui->ratio->setCurrentIndex(_chart->ratio()); + + if( ui->look->currentIndex() != (int) _chart->look() ) + ui->look->setCurrentIndex(_chart->look()); + + if( ui->fraction->currentIndex() != (int) _chart->fraction() ) + ui->fraction->setCurrentIndex(_chart->fraction()); + + if( ui->unit->currentIndex() != _chart->unit() ) + ui->unit->setCurrentIndex(_chart->unit()); + + connect(ui->minimum, &QDoubleSpinBox::valueChanged, _chart, >l::gui::octv_chart::set_minimum); + connect(ui->maximum, &QDoubleSpinBox::valueChanged, _chart, >l::gui::octv_chart::set_maximum); + connect(ui->ratio, &QComboBox::currentIndexChanged, _chart, >l::gui::octv_chart::set_ratio); + connect(ui->look, &QComboBox::currentIndexChanged, _chart, >l::gui::octv_chart::set_look); + connect(ui->fraction, &QComboBox::currentIndexChanged, _chart, >l::gui::octv_chart::set_fraction); + connect(ui->unit, &QComboBox::currentIndexChanged, _chart, >l::gui::octv_chart::set_unit); + + } + } +} diff --git a/gui/gtl_gui_octv_widget.h b/gui/gtl_gui_octv_widget.h new file mode 100644 index 0000000..11952dd --- /dev/null +++ b/gui/gtl_gui_octv_widget.h @@ -0,0 +1,45 @@ +#ifndef GTL_GUI_OCTV_WIDGET_H +#define GTL_GUI_OCTV_WIDGET_H + +#include + + + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" + +#include "gui/gtl_gui_octv_chart.h" + +#include "gui_global.h" + +namespace Ui { +class octv_widget; +} + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT octv_widget : public QWidget + { + Q_OBJECT + + public: + explicit octv_widget(QWidget *parent, gtl::data_model* model); + ~octv_widget(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + Ui::octv_widget *ui; + gtl::selection_data_model* _selection_data_model; + gtl::gui::octv_chart* _chart; + + void update_parameters(); + + }; + } +} + +#endif // GTL_GUI_OCTV_WIDGET_H diff --git a/gui/gtl_gui_octv_widget.ui b/gui/gtl_gui_octv_widget.ui new file mode 100644 index 0000000..3d9afdc --- /dev/null +++ b/gui/gtl_gui_octv_widget.ui @@ -0,0 +1,173 @@ + + + octv_widget + + + + 0 + 0 + 468 + 313 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Minimum + + + + + + + + + + Maximum + + + + + + + + + + Unit + + + + + + + + + + Ratio + + + + + + + + + + Look + + + + + + + + + + Fraction + + + + + + + + + + + + multi axis y + + + + + + + log x + + + + + + + + + + + + diff --git a/gui/gtl_gui_options_dialog.cpp b/gui/gtl_gui_options_dialog.cpp new file mode 100644 index 0000000..5086acc --- /dev/null +++ b/gui/gtl_gui_options_dialog.cpp @@ -0,0 +1,46 @@ +#include "gtl_gui_options_dialog.h" +#include "ui_gtl_gui_options_dialog.h" + +namespace gtl +{ + namespace gui + { + options_dialog::options_dialog(QWidget *parent) + : QDialog(parent) + , ui(new Ui::option_dialog) + { + ui->setupUi(this); + setWindowTitle("Options"); + + _options = new gtl::gui::options_widget(this); + + ui->verticalLayout->insertWidget(0, _options); + + connect(ui->apply, &QPushButton::clicked, _options, >l::gui::options_widget::apply); + connect(ui->apply, &QPushButton::clicked, this, &options_dialog::save_settings); + + connect(ui->ok, &QPushButton::clicked, _options, >l::gui::options_widget::apply); + connect(ui->ok, &QPushButton::clicked, this, &QDialog::accept); + connect(ui->ok, &QPushButton::clicked, this, &options_dialog::save_settings); + + connect(ui->cancel, &QPushButton::clicked, _options, >l::gui::options_widget::discard); + connect(ui->cancel, &QPushButton::clicked, this, &QDialog::reject); + } + + options_dialog::~options_dialog() + { + delete ui; + } + + void options_dialog::add_options_widget(QString group, group_options_widget *widget) + { + _options->add_group(group, widget); + _options->restore(&_settings); + } + + void options_dialog::save_settings() + { + _options->save(&_settings); + } + } +} diff --git a/gui/gtl_gui_options_dialog.h b/gui/gtl_gui_options_dialog.h new file mode 100644 index 0000000..7a49996 --- /dev/null +++ b/gui/gtl_gui_options_dialog.h @@ -0,0 +1,39 @@ +#ifndef OPTIONS_DIALOG_H +#define OPTIONS_DIALOG_H + +#include + +#include "gui/gtl_gui_options_widget.h" + +#include "gui_global.h" + +namespace Ui { +class option_dialog; +} + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT options_dialog : public QDialog + { + Q_OBJECT + + public: + explicit options_dialog(QWidget *parent = nullptr); + ~options_dialog(); + + void add_options_widget(QString group, group_options_widget* widget); + + private: + Ui::option_dialog *ui; + gtl::gui::options_widget* _options; + QSettings _settings; + + private slots: + void save_settings(); + }; + } +} + +#endif // OPTIONS_DIALOG_H diff --git a/gui/gtl_gui_options_dialog.ui b/gui/gtl_gui_options_dialog.ui new file mode 100644 index 0000000..c51c64c --- /dev/null +++ b/gui/gtl_gui_options_dialog.ui @@ -0,0 +1,87 @@ + + + option_dialog + + + + 0 + 0 + 400 + 300 + + + + Options + + + true + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + + + + QLayout::SetMinimumSize + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + OK + + + + + + + Cancel + + + + + + + Apply + + + + + + + + + + diff --git a/gui/gtl_gui_options_widget.cpp b/gui/gtl_gui_options_widget.cpp new file mode 100644 index 0000000..85d00c3 --- /dev/null +++ b/gui/gtl_gui_options_widget.cpp @@ -0,0 +1,51 @@ +#include "gtl_gui_options_widget.h" +#include "ui_gtl_gui_options_widget.h" + +namespace gtl +{ + namespace gui + { + options_widget::options_widget(QWidget *parent) : + QWidget(parent), + ui(new Ui::options_widget) + { + ui->setupUi(this); + } + + options_widget::~options_widget() + { + delete ui; + } + + void options_widget::add_group(QString name, group_options_widget *widget) + { + ui->widgets->addWidget(widget); + ui->groups->addItem(name); + ui->groups->setCurrentRow(ui->groups->count() - 1); + } + + void options_widget::save(QSettings *settings) + { + for(int i = 0; i < ui->widgets->count(); i++) + static_cast(ui->widgets->widget(i))->save(settings); + } + + void options_widget::restore(QSettings *settings) + { + for(int i = 0; i < ui->widgets->count(); i++) + static_cast(ui->widgets->widget(i))->restore(settings); + } + + void options_widget::apply() + { + for(int i = 0; i < ui->widgets->count(); i++) + static_cast(ui->widgets->widget(i))->apply(); + } + + void options_widget::discard() + { + for(int i = 0; i < ui->widgets->count(); i++) + static_cast(ui->widgets->widget(i))->discard(); + } + } +} diff --git a/gui/gtl_gui_options_widget.h b/gui/gtl_gui_options_widget.h new file mode 100644 index 0000000..9d142b0 --- /dev/null +++ b/gui/gtl_gui_options_widget.h @@ -0,0 +1,41 @@ +#ifndef GTL_GUI_OPTIONS_WIDGET_H +#define GTL_GUI_OPTIONS_WIDGET_H + +#include + +#include "gui/gtl_gui_group_options_widget.h" + +#include "gui_global.h" + +namespace Ui { +class options_widget; +} + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT options_widget : public QWidget + { + Q_OBJECT + + public: + explicit options_widget(QWidget *parent = nullptr); + ~options_widget(); + + void add_group(QString name, group_options_widget* widget); + + virtual void save(QSettings* settings); + virtual void restore(QSettings* settings); + + private: + Ui::options_widget *ui; + + public slots: + virtual void apply(); + virtual void discard(); + }; + } +} + +#endif // GTL_GUI_OPTIONS_WIDGET_H diff --git a/gui/gtl_gui_options_widget.ui b/gui/gtl_gui_options_widget.ui new file mode 100644 index 0000000..cab74dc --- /dev/null +++ b/gui/gtl_gui_options_widget.ui @@ -0,0 +1,68 @@ + + + options_widget + + + + 0 + 0 + 501 + 353 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + + + + groups + currentRowChanged(int) + widgets + setCurrentIndex(int) + + + 351 + 205 + + + 493 + 143 + + + + + diff --git a/gui/gtl_gui_record_chart.cpp b/gui/gtl_gui_record_chart.cpp new file mode 100644 index 0000000..fbfff76 --- /dev/null +++ b/gui/gtl_gui_record_chart.cpp @@ -0,0 +1,462 @@ +#include "gtl_gui_record_chart.h" + +#include "hw/gtl_hw_player_file_gtr.h" +#include "hw/gtl_hw_player_file_wav.h" +#include "gui/gtl_gui_record_chart_axis_x.h" + +#include "gui/gtl_gui_record_chart_series.h" + +namespace gtl +{ + namespace gui + { + record_chart::record_chart(QWidget* parent) + : chart(parent, new config_record_chart_axis_x()) + , _cacher(this, &_series) + { + connect(&_cacher, &record_chart_cacher::progress, static_cast(_axis_x), &config_record_chart_axis_x::show_progress); + connect(&_cacher, &record_chart_cacher::finished, this, &record_chart::set_series_data); + connect(&_cacher, &record_chart_cacher::finished, this, &record_chart::fit_all); + connect(_axis_x, &::chart::axis::signal_range_changed, this, &record_chart::set_series_data); + + _axis_y_mode_action = new QAction("Multy axes y"); + _axis_y_mode_action->setCheckable(true); + _menu->addAction(_axis_y_mode_action); + + connect(_axis_y_mode_action, &QAction::toggled, this, &chart::set_axis_y_mode); + } + + void record_chart::set_file(QString path) + { + + _axis_x->blockSignals(true); + + remove_series(); + + gtl::hw::player_file* file = NULL; + + QFileInfo info(path); + if(info.suffix().toLower() == "gtr") + file =new gtl::hw::player_file_gtr(this, path); + else if(info.suffix().toLower() == "wav") + file =new gtl::hw::player_file_wav(this, path); + + + + if(file) + { + if(file->is_ok()) + { + for(int i = 0; i < file->channels(); i++) + { + add_series(new record_chart_series(file->channel(i), file->color(i), _axis_x, _axis_y)); + } + + qreal dt = 1.0/file->rate(); + + _axis_x->set_boundaries(0.0, file->time() - dt); + _axis_x->set_range(0.0, file->time() - dt); + } + else + { + delete file; + file = NULL; + } + + } + + _axis_x->blockSignals(false); + + _cacher.start(file); + + + } + + chart_series *record_chart::create_series(analog_data */*ai*/) + { + return NULL; + } + + void record_chart::fit_axis(::chart::axis *axis) + { + if(axis == _axis_x) + axis->set_range(axis->min_boundary(), axis->max_boundary()); + else + ::chart::widget::fit_axis(axis); + } + + void record_chart::resizeEvent(QResizeEvent *event) + { + chart::resizeEvent(event); + set_series_data(); + } + + hw::player_file *record_chart::file() + { + return _cacher.file(); + } + + void record_chart::set_series_data() + { + _cacher.set_series_data(_axis_x->min(), _axis_x->max()); + } + + record_chart_cacher::record_chart_cacher(QObject *parent, std::vector<::chart::series::series*> *series) + : QThread(parent) + , _file(NULL) + , _series(series) + { + + } + + record_chart_cacher::~record_chart_cacher() + { + stop(); + clear(); + } + + void record_chart_cacher::start(hw::player_file *file) + { + stop(); + clear(); + + _file = file; + + if(_file) + QThread::start(QThread::LowPriority); + } + + void record_chart_cacher::stop() + { + _is_running = false; + wait(); + } + + void record_chart_cacher::set_series_data(qreal min, qreal max) + { + if(_series->empty()) + return; + + if (isRunning()) + return; + + if (_file == NULL) + return; + + int samples = _file->samples(); + int idx_min = std::max(0, qRound(min / _file->dt()) - 1); + int idx_max = std::min(qRound(max / _file->dt()) + 1, samples - 1); + + int width = _series->front()->axis_x()->boundingRect().width()/* * 2*/; + + if (width == 0) + return; + + if (width < 0 || width > 10000) + width = 1000; + + int step = qRound((qreal)(idx_max - idx_min) / width); + + + if (step < 1) + step = 1; + + + for (auto series : *_series) + static_cast(series)->clear(); + + + int k = 0; + int channels = (int)_series->size(); + for (int idx = idx_min; idx < idx_max/* - step*/; idx += step, k++) + { + + std::vector> buffers_min(channels); + std::vector> buffers_max(channels); + + if (step == 1) + { + std::vector buffer; + get_cache_data(0, idx, std::back_inserter(buffer)); + for (int i = 0; i < channels; i++) + { + buffers_min[i].push_back(buffer[i*2 + 0]); + buffers_max[i].push_back(buffer[i*2 + 1]); + } + + //add(QPointF(idx*_dt, _min[0][idx])); + } + else + { + int idx_start = idx; + int idx_stop = std::min(idx + step, samples - 1); + + std::vector buffer_min; + std::vector buffer_max; + + while (idx_start != idx_stop) + { + + for (int deg = (int)_cache.size()/* - 1*/; deg >= 0; deg--) + { + int size = (1 << deg); + if (idx_start % size == 0 && idx_start + size <= idx_stop) + { + + + std::vector buffer; + get_cache_data(deg, idx_start / size, std::back_inserter(buffer)); + for (int c = 0; c < channels; c++) + { + buffers_min[c].push_back(buffer[c*2 + 0]); + buffers_max[c].push_back(buffer[c*2 + 1]); + } + /* + buffer_min.push_back(_min[i][idx_start / size]); + buffer_max.push_back(_max[i][idx_start / size]); + */ + idx_start += size; + break; + } + } + + } + + + + + + } + + for (int i = 0; i < channels; i++) + { + std::vector::iterator iter_min = std::min_element(buffers_min[i].begin(), buffers_min[i].end()); + std::vector::iterator iter_max = std::max_element(buffers_max[i].begin(), buffers_max[i].end()); + + if (k % 2 == 0) + { + static_cast(_series->at(i))->add(QPointF(idx*_file->dt(), *iter_min)); + static_cast(_series->at(i))->add(QPointF(idx*_file->dt(), *iter_max)); + } + else + { + static_cast(_series->at(i))->add(QPointF(idx*_file->dt(), *iter_max)); + static_cast(_series->at(i))->add(QPointF(idx*_file->dt(), *iter_min)); + } + } + + } + } + + hw::player_file *record_chart_cacher::file() + { + return _file; + } + + void record_chart_cacher::run() + { + QStringList paths_tmp = QStandardPaths::standardLocations(QStandardPaths::CacheLocation); + QDir().mkpath(paths_tmp.front()); + + + QString path_cache = paths_tmp.front() + "/" + QFileInfo(_file->path()).completeBaseName(); + + +// qreal dt = 1.0 / _file->rate(); + + + _is_running = true; + + + + + + int channels = _file->channels(); + if (channels == 0) + return; + + int floats = 2097152 / 4; + int sampes_read = /*65536*/floats / channels; + int samples_total = _file->samples(); + + + std::vector buffer(sampes_read*channels); + + std::vector>> min(channels); + std::vector>> max(channels); + for (int i = 0; i < channels; i++) + { + min[i].resize(1); + max[i].resize(1); + } + + std::vector> buffers; + + QElapsedTimer timer; + timer.start(); + int iterval_100 = 1; + + _file->seek_to_start(); + + emit progress(0); + int idx = 0; + + bool is_complete = false; + while (/*idx < samples_total*/!is_complete) + { + + int samples = sampes_read; + is_complete = _file->get_data(&buffer[0], idx, samples); + // idx += samples; + + for (int s = 0; s < samples; s++) + { + for (int c = 0; c < channels; c++) + { + min[c][0].push_back(buffer[s*channels + c]); + max[c][0].push_back(buffer[s*channels + c]); + + for (int d = 0; d < min[c].size(); d++) + { + int cnt_values = (int)min[c][d].size(); + if (cnt_values == 2) + { + + if (d == min[c].size() - 1) + { + min[c].push_back(std::vector()); + max[c].push_back(std::vector()); + + if (_cache.size() < d + 1) + { + QFile* file = new QFile(path_cache + "_" + QString::number(d + 1) + ".cache"); + if (file->open(QIODevice::ReadWrite)) + { + + } + + _cache.push_back(file); + buffers.push_back(std::vector()); + } + } + + int idx0 = cnt_values - 2; + int idx1 = cnt_values - 1; + + float min_value = std::min(min[c][d][idx0], min[c][d][idx1]); + float max_value = std::max(max[c][d][idx0], max[c][d][idx1]); + + min[c][d + 1].push_back(min_value); + max[c][d + 1].push_back(max_value); + + + /* + files[d]->write((char*)(&min_value), sizeof(min_value)); + files[d]->write((char*)(&max_value), sizeof(max_value)); + */ + + buffers[d].push_back(min_value); + buffers[d].push_back(max_value); + + min[c][d].clear(); + max[c][d].clear(); + + } + } + } + + + for (int d = 0; d < buffers.size(); d++) + { + if (buffers[d].size() >= floats) + { + _cache.at(d)->write((char*)(&buffers[d][0]), buffers[d].size()*sizeof(float)); + + buffers[d].clear(); + } + } + + if (!_is_running) + break; + + } + + if (timer.elapsed() > iterval_100 * 500) + { + emit progress(100.0*idx / samples_total); + iterval_100++; + // msleep(10); + } + + + + + + if (!_is_running) + break; + } + + emit progress(100.0); + + for (int d = 0; d < buffers.size(); d++) + { + if (buffers[d].size() > 0) + { + _cache.at(d)->write((char*)(&buffers[d][0]), buffers[d].size()*sizeof(float)); + + buffers[d].clear(); + } + } + + qDebug() << "caching time: " << timer.elapsed(); + } + + void record_chart_cacher::clear() + { + for (std::vector::iterator iter_file = _cache.begin(); iter_file != _cache.end(); iter_file++) + { + (*iter_file)->close(); + (*iter_file)->remove(); + } + + _cache.clear(); + + if (_file) + delete _file; + + _file = NULL; + } + + void record_chart_cacher::get_cache_data(int deg, int idx, std::back_insert_iterator > data) const + { + if (_series->empty()) + return; + + if (deg == 0) + { + std::vector buffer(_series->size()); + int samples = 1; + _file->get_data(&buffer[0], idx, samples); + + + for (int i = 0; i < _series->size(); i++) + { + data = buffer[i]; + data++; + data = buffer[i]; + data++; + } + + + } + else + { + std::vector buffer(_series->size() * 2); + _cache[deg - 1]->seek(idx*buffer.size()*sizeof(float)); + _cache[deg - 1]->read((char*)(&buffer[0]), buffer.size()*sizeof(float)); + + std::copy(buffer.begin(), buffer.end(), data); + } + } + } +} diff --git a/gui/gtl_gui_record_chart.h b/gui/gtl_gui_record_chart.h new file mode 100644 index 0000000..0bc272c --- /dev/null +++ b/gui/gtl_gui_record_chart.h @@ -0,0 +1,73 @@ +#ifndef GTL_GUI_RECORD_CHART_H +#define GTL_GUI_RECORD_CHART_H + +#include "gtl_gui_chart.h" +#include "hw/gtl_hw_player_file.h" + +#include "gui/gui_global.h" + + +namespace gtl +{ + namespace gui + { + class record_chart_cacher : public QThread + { + Q_OBJECT + public: + record_chart_cacher(QObject* parent, std::vector<::chart::series::series*> *series); + ~record_chart_cacher(); + + void start(gtl::hw::player_file* file); + void stop(); + + void set_series_data(qreal min, qreal max); + + gtl::hw::player_file* file(); + + private: + void run() override; + void clear(); + void get_cache_data(int deg, int idx, std::back_insert_iterator> data) const; + + private: + gtl::hw::player_file* _file; + std::vector _cache; + bool _is_running; + std::vector<::chart::series::series*> *_series; + + signals: + void progress(qreal); + }; + + + class GTL_GUI_EXPORT record_chart : public chart + { + Q_OBJECT + public: + record_chart(QWidget* parent = NULL); + + virtual void set_file(QString path); + + private: + virtual chart_series* create_series(gtl::analog_data* ai) override; + virtual void fit_axis(::chart::axis* axis) override; + virtual void resizeEvent(QResizeEvent *event) override; + + private: + record_chart_cacher _cacher; + + protected: + gtl::hw::player_file* file(); + + protected: + QAction *_axis_y_mode_action; + + private slots: + void set_series_data(); + + }; + } +} + +#endif // GTL_GUI_RECORD_CHART_H diff --git a/gui/gtl_gui_record_chart_axis_x.cpp b/gui/gtl_gui_record_chart_axis_x.cpp new file mode 100644 index 0000000..a856088 --- /dev/null +++ b/gui/gtl_gui_record_chart_axis_x.cpp @@ -0,0 +1,40 @@ +#include "gtl_gui_record_chart_axis_x.h" + +namespace gtl +{ + namespace gui + { + config_record_chart_axis_x::config_record_chart_axis_x() + : _progress_value(100) + { + } + + void config_record_chart_axis_x::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + chart::axis_horz::paint(painter, option, widget); + + if (_progress_value < 100) + { + painter->setPen(/*Qt::yellow*/Qt::black); + painter->setFont(QFont("Courier New", 12)); +// painter->setCompositionMode(QPainter::CompositionMode_Screen); + QRectF rect_text; + + QRectF rect = boundingRect(); + + painter->drawText(QRectF(rect.left(), rect.top(), rect.width(), rect.height()/2 - 1), Qt::AlignHCenter | Qt::AlignBottom, tr("loading") + ": " + QString::number(_progress_value, 'f', 1) + "%", &rect_text); + + QPointF center = boundingRect().center(); + + painter->drawRect(center.x() - 60, rect_text.bottom() + 2, 120, 10); + painter->fillRect(QRect(center.x() - 60, rect_text.bottom() + 2, 120.0 / 100.0 * _progress_value, 10), QBrush(Qt::blue/*yellow*/)); + } + } + + void config_record_chart_axis_x::show_progress(qreal value) + { + prepareGeometryChange(); + _progress_value = value; + } + } +} diff --git a/gui/gtl_gui_record_chart_axis_x.h b/gui/gtl_gui_record_chart_axis_x.h new file mode 100644 index 0000000..eef3aa1 --- /dev/null +++ b/gui/gtl_gui_record_chart_axis_x.h @@ -0,0 +1,27 @@ +#ifndef CONFIG_WIDGET_PLAYER_CHART_AXIS_X_H +#define CONFIG_WIDGET_PLAYER_CHART_AXIS_X_H + +#include "gtl_gui_chart_axis_x.h" + +namespace gtl +{ + namespace gui + { + class config_record_chart_axis_x : public chart_axis_x + { + Q_OBJECT + public: + config_record_chart_axis_x(); + private: + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + private: + qreal _progress_value; + + public slots: + void show_progress(qreal value); + }; + } +} + +#endif // CONFIG_WIDGET_PLAYER_CHART_AXIS_X_H diff --git a/gui/gtl_gui_record_chart_series.cpp b/gui/gtl_gui_record_chart_series.cpp new file mode 100644 index 0000000..b8e4af6 --- /dev/null +++ b/gui/gtl_gui_record_chart_series.cpp @@ -0,0 +1,29 @@ +#include "gtl_gui_record_chart_series.h" + +namespace gtl +{ + namespace gui + { + record_chart_series::record_chart_series(QString name, QColor color, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y) + : chart_series(NULL, axis_x, axis_y) + , _name(name) + { + set_color(color); + } + + QString record_chart_series::name() const + { + return _name; + } + + qreal record_chart_series::left() const + { + return _axis_horz->min_boundary(); + } + + qreal record_chart_series::right() const + { + return _axis_horz->max_boundary(); + } + } +} diff --git a/gui/gtl_gui_record_chart_series.h b/gui/gtl_gui_record_chart_series.h new file mode 100644 index 0000000..87f4d3d --- /dev/null +++ b/gui/gtl_gui_record_chart_series.h @@ -0,0 +1,27 @@ +#ifndef RECORD_CHART_SERIES_H +#define RECORD_CHART_SERIES_H + +#include "gui/gtl_gui_chart_series.h" + +namespace gtl +{ + namespace gui + { + class record_chart_series : public chart_series + { + Q_OBJECT + public: + record_chart_series(QString name, QColor color, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y); + + virtual QString name() const override; + + virtual qreal left() const override; + virtual qreal right() const override; + + private: + QString _name; + }; + } +} + +#endif // RECORD_CHART_SERIES_H diff --git a/gui/gtl_gui_recorder_widget.cpp b/gui/gtl_gui_recorder_widget.cpp new file mode 100644 index 0000000..6d41edb --- /dev/null +++ b/gui/gtl_gui_recorder_widget.cpp @@ -0,0 +1,149 @@ +#include "gtl_gui_recorder_widget.h" +#include "ui_gtl_gui_recorder_widget.h" + +#include + +#include "hw/gtl_hw_recorder_gtr.h" +#include "hw/gtl_hw_recorder_wav.h" + +#include "gui/gtl_gui_chart_widget.h" + +namespace gtl +{ + namespace gui + { + recorder_widget::recorder_widget(gtl::data_model* model, QWidget *parent) + : QWidget(parent) + , ui(new Ui::recorder_widget) + , _recorder(NULL) + { + ui->setupUi(this); + + _selection_data_model = new gtl::selection_data_model(this); + _selection_data_model->setSourceModel(model); + + ui->tree->setModel(_selection_data_model); + + connect(ui->select_dir, &QToolButton::clicked, this, &recorder_widget::set_dir); + connect(ui->write, &QToolButton::toggled, this, &recorder_widget::write); + + QHBoxLayout *layout = new QHBoxLayout(ui->progress); + layout->setContentsMargins(0, 0, 0, 0); + _time_label = new QLabel(ui->progress); + _time_label->setAlignment(Qt::AlignCenter); + layout->addWidget(_time_label); + ui->progress->setLayout(layout); + + _chart = new gtl::gui::record_chart(this); + gtl::gui::chart_widget *chart_widget = new gtl::gui::chart_widget(_chart, this); + ui->splitter_2->addWidget(chart_widget); + + } + + recorder_widget::~recorder_widget() + { + delete ui; + } + + void recorder_widget::save(QDomElement &root_element) + { + QDomElement selection_element = root_element.ownerDocument().createElement("selection"); + root_element.appendChild(selection_element); + _selection_data_model->save(selection_element); + + root_element.setAttribute("dir", ui->dir->text()); + root_element.setAttribute("file", ui->file->text()); + root_element.setAttribute("time", ui->time->value()); + + root_element.setAttribute("comment", ui->comment->toPlainText()); + + QDomElement splitter_element = root_element.ownerDocument().createElement("splitter"); + root_element.appendChild(splitter_element); + splitter_element.setAttribute("state", QString(ui->splitter->saveState().toBase64())); + + QDomElement splitter_2_element = root_element.ownerDocument().createElement("splitter_2"); + root_element.appendChild(splitter_2_element); + splitter_2_element.setAttribute("state", QString(ui->splitter_2->saveState().toBase64())); + } + + void recorder_widget::load(const QDomElement &root_element) + { + QDomElement selection_element = root_element.firstChildElement("selection"); + _selection_data_model->load(selection_element); + + ui->dir->setText(root_element.attribute("dir", "")); + ui->file->setText(root_element.attribute("file", "record")); + ui->time->setValue(root_element.attribute("time", "1").toDouble()); + + ui->comment->setPlainText(root_element.attribute("comment", "")); + + QDomElement splitter_element = root_element.firstChildElement("splitter"); + ui->splitter->restoreState(QByteArray::fromBase64(splitter_element.attribute("state", "").toUtf8())); + + QDomElement splitter_2_element = root_element.firstChildElement("splitter_2"); + ui->splitter_2->restoreState(QByteArray::fromBase64(splitter_2_element.attribute("state", "").toUtf8())); + } + + void recorder_widget::set_dir() + { + QString path = QFileDialog::getExistingDirectory(this, tr("Select directory"), ui->dir->text()); + if(!path.isEmpty()) + ui->dir->setText(path); + } + + void recorder_widget::write(bool value) + { + if(value) + { + if(_recorder) + delete _recorder; + + std::vector ad; + _selection_data_model->get_selections(std::back_inserter(ad)); + + _recorder = new gtl::hw::recorder_wav(ad, ui->time->value(), ui->comment->toPlainText(), ui->dir->text(), ui->file->text(), this); + + + _chart->set_file(""); + _recorder->start(); + + ui->progress->setMaximum(ui->time->value() == 0 ? 0 : 1000); + + if(!_recorder->is_success()) + { + ui->write->setChecked(false); + return; + } + + + connect(_recorder, >l::hw::recorder::finished, this, &recorder_widget::recording_finished); + connect(_recorder, >l::hw::recorder::progress, this, &recorder_widget::recording_progress); + } + else + { + if(_recorder) + { + QString path = _recorder->path(); + + delete _recorder; + _recorder = NULL; + + _chart->set_file(path); + } + + ui->progress->setMaximum(1000); + } + } + + void recorder_widget::recording_finished() + { + ui->write->setChecked(false); + } + + void recorder_widget::recording_progress(qreal value) + { + _time_label->setText(QTime::fromMSecsSinceStartOfDay(_recorder->time()*1000).toString("hh:mm:ss.zzz")); + ui->progress->setValue(qRound(value*1000)); + } + } +} diff --git a/gui/gtl_gui_recorder_widget.h b/gui/gtl_gui_recorder_widget.h new file mode 100644 index 0000000..f69c0c3 --- /dev/null +++ b/gui/gtl_gui_recorder_widget.h @@ -0,0 +1,52 @@ +#ifndef GTL_GUI_RECORDER_WIDGET_H +#define GTL_GUI_RECORDER_WIDGET_H + +#include +#include +#include + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" + +#include "hw/gtl_hw_recorder.h" + +#include "gui/gtl_gui_record_chart.h" + +#include "gui_global.h" + +namespace Ui { +class recorder_widget; +} + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT recorder_widget : public QWidget + { + Q_OBJECT + + public: + explicit recorder_widget(gtl::data_model* model, QWidget *parent = nullptr); + ~recorder_widget(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + Ui::recorder_widget *ui; + gtl::selection_data_model* _selection_data_model; + gtl::hw::recorder* _recorder; + QLabel *_time_label; + gtl::gui::record_chart *_chart; + + private slots: + void set_dir(); + void write(bool); + void recording_finished(); + void recording_progress(qreal); + }; + } +} + +#endif // GTL_GUI_RECORDER_WIDGET_H diff --git a/gui/gtl_gui_recorder_widget.ui b/gui/gtl_gui_recorder_widget.ui new file mode 100644 index 0000000..82f829d --- /dev/null +++ b/gui/gtl_gui_recorder_widget.ui @@ -0,0 +1,148 @@ + + + recorder_widget + + + + 0 + 0 + 864 + 532 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + + + 2 + + + + + 2 + + + + + + + + ... + + + + + + + + + file name + + + 9 + + + + + + + QAbstractSpinBox::NoButtons + + + 3 + + + 0.000000000000000 + + + 10000.000000000000000 + + + 1.000000000000000 + + + + + + + Qt::Vertical + + + + + + + + 2 + + + + + 1000 + + + 0 + + + false + + + + + + + + + + + :/recorder/resources/record.png + :/player/resources/stop.png:/recorder/resources/record.png + + + + 24 + 24 + + + + true + + + + + + + + + + + + + + + + diff --git a/gui/gtl_gui_scr_editor.cpp b/gui/gtl_gui_scr_editor.cpp new file mode 100644 index 0000000..c601062 --- /dev/null +++ b/gui/gtl_gui_scr_editor.cpp @@ -0,0 +1,41 @@ +#include "gtl_gui_scr_editor.h" + +#include + +namespace gtl +{ + namespace gui + { + scr_editor::scr_editor(QWidget* parent) + :QPlainTextEdit(parent) + { + + } + + void scr_editor::contextMenuEvent(QContextMenuEvent *event) + { + QMenu* menu = createStandardContextMenu(event->globalPos()); + + emit init_menu(menu); + + menu->exec(event->globalPos()); + delete menu; + } + + bool scr_editor::load(QString path) + { + clear(); + + QFile file(path); + if(file.open(QFile::ReadOnly)) + { + setPlainText(QString(file.readAll())); + + file.close(); + return true; + } + + return false; + } + } +} diff --git a/gui/gtl_gui_scr_editor.h b/gui/gtl_gui_scr_editor.h new file mode 100644 index 0000000..58ed7b7 --- /dev/null +++ b/gui/gtl_gui_scr_editor.h @@ -0,0 +1,30 @@ +#ifndef SCR_EDITOR_H +#define SCR_EDITOR_H + +#include +#include + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT scr_editor : public QPlainTextEdit + { + Q_OBJECT + public: + scr_editor(QWidget* parent = NULL); + private: + virtual void contextMenuEvent(QContextMenuEvent* event) override; + + signals: + void init_menu(QMenu *menu); + + public slots: + bool load(QString path); + }; + } +} + +#endif // SCR_EDITOR_H diff --git a/gui/gtl_gui_scr_options_widget.cpp b/gui/gtl_gui_scr_options_widget.cpp new file mode 100644 index 0000000..c6355d2 --- /dev/null +++ b/gui/gtl_gui_scr_options_widget.cpp @@ -0,0 +1,77 @@ +#include "gtl_gui_scr_options_widget.h" +#include "ui_gtl_gui_scr_options_widget.h" + +#include + +#include "script/gtl_scr_engine.h" + +namespace gtl +{ + namespace gui + { + scr_options_widget::scr_options_widget(QWidget *parent) : + gtl::gui::group_options_widget(parent), + ui(new Ui::scr_options_widget) + { + ui->setupUi(this); + + connect(ui->add, &QToolButton::clicked, this, &scr_options_widget::add_path); + connect(ui->remove, &QToolButton::clicked, this, &scr_options_widget::remove_path); + } + + scr_options_widget::~scr_options_widget() + { + delete ui; + } + + void scr_options_widget::apply() + { + gtl::scr::engine::import_paths.clear(); + for(int i = 0; i < ui->paths->count(); i++) + gtl::scr::engine::import_paths.push_back(ui->paths->item(i)->text()); + + } + + void scr_options_widget::discard() + { + ui->paths->clear(); + for(auto it : gtl::scr::engine::import_paths) + ui->paths->addItem(it); + } + + void scr_options_widget::save(QSettings *settings) + { + QList paths; + for(int i = 0; i < ui->paths->count(); i++) + paths.push_back(ui->paths->item(i)->text()); + + settings->setValue("options/script/paths", QVariant(paths)); + } + + void scr_options_widget::restore(QSettings *settings) + { + QList paths = settings->value("options/script/paths").toList(); + + ui->paths->clear(); + for(auto it : paths) + ui->paths->addItem(it.toString()); + + apply(); + } + + void scr_options_widget::add_path() + { + QString path = QFileDialog::getExistingDirectory(this, "Select import direcotry"); + if(path.isEmpty()) + return; + + ui->paths->addItem(path); + } + + void scr_options_widget::remove_path() + { + if(ui->paths->currentItem()) + delete ui->paths->currentItem(); + } + } +} diff --git a/gui/gtl_gui_scr_options_widget.h b/gui/gtl_gui_scr_options_widget.h new file mode 100644 index 0000000..dccb082 --- /dev/null +++ b/gui/gtl_gui_scr_options_widget.h @@ -0,0 +1,42 @@ +#ifndef GTL_GUI_SCR_OPTIONS_WIDGET_H +#define GTL_GUI_SCR_OPTIONS_WIDGET_H + +#include + +#include "gui/gtl_gui_group_options_widget.h" + +#include "gui_global.h" + +namespace Ui { +class scr_options_widget; +} + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT scr_options_widget : public gtl::gui::group_options_widget + { + Q_OBJECT + + public: + explicit scr_options_widget(QWidget *parent = nullptr); + ~scr_options_widget(); + + virtual void apply() override; + virtual void discard() override; + + virtual void save(QSettings* settings) override; + virtual void restore(QSettings* settings) override; + + private: + Ui::scr_options_widget *ui; + + private slots: + void add_path(); + void remove_path(); + }; + } +} + +#endif // GTL_GUI_SCR_OPTIONS_WIDGET_H diff --git a/gui/gtl_gui_scr_options_widget.ui b/gui/gtl_gui_scr_options_widget.ui new file mode 100644 index 0000000..c69bc5e --- /dev/null +++ b/gui/gtl_gui_scr_options_widget.ui @@ -0,0 +1,92 @@ + + + scr_options_widget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + + + ... + + + + :/options/resources/add_24.png:/options/resources/add_24.png + + + + 24 + 24 + + + + + + + + ... + + + + :/options/resources/remove_24.png:/options/resources/remove_24.png + + + + 24 + 24 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + diff --git a/gui/gtl_gui_scr_qml_widget.cpp b/gui/gtl_gui_scr_qml_widget.cpp new file mode 100644 index 0000000..6fc03ec --- /dev/null +++ b/gui/gtl_gui_scr_qml_widget.cpp @@ -0,0 +1,86 @@ +#include "gtl_gui_scr_qml_widget.h" +#include "ui_gtl_gui_scr_qml_widget.h" + +namespace gtl +{ + namespace gui + { + scr_qml_widget::scr_qml_widget(gtl::data_model* model, QWidget *parent) : + QWidget(parent), + ui(new Ui::qml_widget) + { + ui->setupUi(this); + + _selection_data_model = new gtl::selection_data_model(this); + _selection_data_model->setSourceModel(model); + + ui->hw->setModel(_selection_data_model); + ui->hw->expandAll(); + + gtl::scr::engine* engine = new gtl::scr::engine(this); + _widget = new gtl::gui::scr_quick_widget(engine, this); + + ui->splitter->insertWidget(1, _widget); + + _qml_editor = new gtl::gui::scr_editor(this); + ui->splitter->insertWidget(2, _qml_editor); + + connect(_qml_editor, >l::gui::scr_editor::init_menu, this, &scr_qml_widget::init_scr_editors_menu); + + _run_action = new QAction(QIcon(":/player/play"), "Run", this); + connect(_run_action, &QAction::triggered, this, &scr_qml_widget::run); + } + + scr_qml_widget::~scr_qml_widget() + { + delete ui; + } + + void scr_qml_widget::save(QDomElement &root_element) + { + QDomElement selection_element = root_element.ownerDocument().createElement("selection"); + root_element.appendChild(selection_element); + _selection_data_model->save(selection_element); + + QDomElement qml_element = root_element.ownerDocument().createElement("qml"); + root_element.appendChild(qml_element); + qml_element.setAttribute("text", _qml_editor->toPlainText()); + + QDomElement splitter_element = root_element.ownerDocument().createElement("splitter"); + root_element.appendChild(splitter_element); + splitter_element.setAttribute("state", QString(ui->splitter->saveState().toBase64())); + } + + void scr_qml_widget::load(const QDomElement &root_element) + { + QDomElement selection_element = root_element.firstChildElement("selection"); + _selection_data_model->load(selection_element); + + _qml_editor->clear(); + QDomElement qml_element = root_element.firstChildElement("qml"); + _qml_editor->appendPlainText(qml_element.attribute("text", "")); + + QDomElement splitter_element = root_element.firstChildElement("splitter"); + ui->splitter->restoreState(QByteArray::fromBase64(splitter_element.attribute("state", "").toUtf8())); + } + + QAction *scr_qml_widget::run_action() const + { + return _run_action; + } + + void scr_qml_widget::init_scr_editors_menu(QMenu *menu) + { + menu->addSeparator(); + menu->addAction(_run_action); + } + + void scr_qml_widget::run() + { + std::vector ad; + _selection_data_model->get_selections(std::back_inserter(ad)); + + _widget->run(ad.begin(), ad.end(), _qml_editor->toPlainText()); + } + } +} diff --git a/gui/gtl_gui_scr_qml_widget.h b/gui/gtl_gui_scr_qml_widget.h new file mode 100644 index 0000000..64db7c1 --- /dev/null +++ b/gui/gtl_gui_scr_qml_widget.h @@ -0,0 +1,51 @@ +#ifndef QML_WIDGET_H +#define QML_WIDGET_H + +#include + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" + +#include "gui/gtl_gui_scr_quick_widget.h" +#include "gui/gtl_gui_scr_editor.h" + +#include "gui_global.h" + + +namespace Ui { +class qml_widget; +} + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT scr_qml_widget : public QWidget + { + Q_OBJECT + + public: + explicit scr_qml_widget( gtl::data_model* model, QWidget *parent = nullptr); + ~scr_qml_widget(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + QAction* run_action() const; + + private: + Ui::qml_widget *ui; + gtl::gui::scr_quick_widget* _widget; + gtl::selection_data_model* _selection_data_model; + gtl::gui::scr_editor* _qml_editor; + + QAction* _run_action; + + private slots: + void init_scr_editors_menu(QMenu *menu); + void run(); + }; + } +} + +#endif // QML_WIDGET_H diff --git a/gui/gtl_gui_scr_qml_widget.ui b/gui/gtl_gui_scr_qml_widget.ui new file mode 100644 index 0000000..21e6e24 --- /dev/null +++ b/gui/gtl_gui_scr_qml_widget.ui @@ -0,0 +1,44 @@ + + + qml_widget + + + + 0 + 0 + 606 + 300 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + + + + + + diff --git a/gui/gtl_gui_scr_quick_widget.cpp b/gui/gtl_gui_scr_quick_widget.cpp new file mode 100644 index 0000000..cdb0b55 --- /dev/null +++ b/gui/gtl_gui_scr_quick_widget.cpp @@ -0,0 +1,139 @@ +#include "gtl_gui_scr_quick_widget.h" + +#include +#include +#include + +#include "core/gtl_logger.h" + +namespace gtl +{ + namespace gui + { + scr_quick_widget::scr_quick_widget(gtl::scr::engine* engine, QWidget *parent) + : QFrame(parent) + , _engine(engine) + , _widget(nullptr) + { + setLayout(new QVBoxLayout(this)); + layout()->setContentsMargins(0, 0, 0, 0); + + setFrameStyle(QFrame::Box); + } + + void scr_quick_widget::run(std::vector::iterator begin_ad, std::vector::iterator end_ad, QString qml_script) + { + _engine->set_ad(begin_ad, end_ad); + + run(qml_script); + + } + + void scr_quick_widget::run(QString qml_script) + { + _engine->evaluate(""); + + connect(_engine->qml_engine(), &QQmlEngine::warnings, this, &scr_quick_widget::qml_warnings); + + clear_qml_modules(); + copy_qml_modules(); + + QString qml_file_path = imports_to_url(qml_script, "script.qml"); + + if(_widget) + delete _widget; + + _widget = new QQuickWidget(_engine->qml_engine(), this); + _widget->quickWindow()->setGraphicsApi(QSGRendererInterface::OpenGL); + _widget->setResizeMode(QQuickWidget::SizeRootObjectToView); + QUrl url = QUrl::fromLocalFile(qml_file_path); + _string_url = url.toString() + ":"; + _widget->setSource(url); + + QList errors = _widget->errors(); + for(auto it : errors) + gtl::logger::error("qml", it.toString().replace(_string_url, "")); + + layout()->addWidget(_widget); + + } + + QUrl scr_quick_widget::get_import_url(QString file_name) + { + if(QFileInfo::exists(file_name)) + return QUrl::fromLocalFile(file_name); + + for(auto it : gtl::scr::engine::import_paths) + { + QFileInfo file(it + "/" + file_name); + if(file.exists()) + { + return QUrl::fromLocalFile(file.absoluteFilePath()); + } + } + + gtl::logger::error("qml", tr("can't find module file: ") + file_name); + + + return QUrl(); + } + + QString scr_quick_widget::imports_to_url(QString &script, QString file_name) + { + QRegularExpression import_re("import\\s+\"\\s*(\\w*(\\.|:|/+|\\\\)*)+\\s*\"\\s+as"); + QRegularExpressionMatchIterator i = import_re.globalMatch(script); + while(i.hasNext()) + { + QRegularExpressionMatch import_match = i.next(); + QStringList parts = import_match.captured().split("\""); + + qDebug() << parts[1] << import_match.capturedStart() << import_match.capturedLength() << import_match.capturedEnd(); + QUrl import_url = get_import_url(parts[1]); + + script.replace(script.indexOf(parts[1], import_match.capturedStart()), parts[1].length(), import_url.toString().replace(":///", ":")); + } + + QString file_path = QStandardPaths::standardLocations(QStandardPaths::TempLocation)[0] + "/" + file_name; + QFile qml_file(file_path); + if(qml_file.open(QFile::WriteOnly)) + { + qml_file.write(script.toUtf8()); + qml_file.close(); + + } + + return file_path; + } + + void scr_quick_widget::clear_qml_modules() + { + for(auto qml_file_info: QDir(QStandardPaths::standardLocations(QStandardPaths::TempLocation)[0]).entryInfoList(QStringList() << "*.qml", QDir::Filter::Files)) + QFile::remove(qml_file_info.absoluteFilePath()); + } + + void scr_quick_widget::copy_qml_modules() + { + for(auto it : gtl::scr::engine::import_paths) + { + for(auto qml_file_info: QDir(it).entryInfoList(QStringList() << "*.qml", QDir::Filter::Files)) + { + QFile qml_file(qml_file_info.absoluteFilePath()); + if(qml_file.open(QFile::ReadOnly)) + { + QString qml_script(qml_file.readAll()); + imports_to_url(qml_script, qml_file_info.fileName()); + + qml_file.close(); + } + + } + } + } + + void scr_quick_widget::qml_warnings(const QList &warnings) + { + for(auto it : warnings) + gtl::logger::warning("qml", it.toString().replace(_string_url, "")); + } + } +} diff --git a/gui/gtl_gui_scr_quick_widget.h b/gui/gtl_gui_scr_quick_widget.h new file mode 100644 index 0000000..7d66312 --- /dev/null +++ b/gui/gtl_gui_scr_quick_widget.h @@ -0,0 +1,43 @@ +#ifndef SCR_QUICK_WIDGET_H +#define SCR_QUICK_WIDGET_H + +#include +#include + +#include "script/gtl_scr_engine.h" + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT scr_quick_widget : public QFrame + { + Q_OBJECT + public: + scr_quick_widget(gtl::scr::engine* engine, QWidget *parent = nullptr); + + void run(std::vector::iterator begin_ad, std::vector::iterator end_ad, QString qml_script); + void run(QString qml_script); + + protected: + gtl::scr::engine* _engine; + + private: + QQuickWidget* _widget; + QString _string_url; + + private: + QUrl get_import_url(QString file_name); + QString imports_to_url(QString &script, QString file_name); + void clear_qml_modules(); + void copy_qml_modules(); + + private slots: + void qml_warnings(const QList &warnings); + }; + } +} + +#endif // SCR_QUICK_WIDGET_H diff --git a/gui/gtl_gui_scr_specs_chart.cpp b/gui/gtl_gui_scr_specs_chart.cpp new file mode 100644 index 0000000..1650ad5 --- /dev/null +++ b/gui/gtl_gui_scr_specs_chart.cpp @@ -0,0 +1,79 @@ +#include "gtl_gui_scr_specs_chart.h" + +#include "core/gtl_device.h" +#include "gtl_gui_scr_specs_series.h" + +namespace gtl +{ + namespace gui + { + scr_specs_chart::scr_specs_chart(QWidget* parent) + :spec_chart(gtl::math::spec::undef, parent) + { + QAction* axis_y_mode_action = new QAction("Multy axes y"); + axis_y_mode_action->setCheckable(true); + _menu->addAction(axis_y_mode_action); + + connect(axis_y_mode_action, &QAction::toggled, this, &chart::set_axis_y_mode); + } + + int scr_specs_chart::series_count() const + { + return (int)_series.size(); + } + + chart_series *scr_specs_chart::series(int idx) const + { + return static_cast(_series[idx]); + } + + void scr_specs_chart::remove(scr::spec *spec) + { + for(int i = 0; i < (int)_series.size(); i++) + { + if(static_cast(_series[i])->spec() == spec) + { + gtl::data_model_node* device = spec->ad()->root(); + _devices[device].remove(static_cast(_series[i])); + if(_devices[device].empty()) + { +// disconnect(static_cast(device), >l::device::recieved_data, this, >l::gui::chart::device_recieved_data); + _devices.erase(device); + } + + remove_series(static_cast(_series[i])); + break; + } + } + } + + void scr_specs_chart::add_spec(gtl::scr::spec *spec) + { + scr_specs_series *series = new scr_specs_series(spec, _axis_x, _axis_y); + + connect(spec, >l::math::spec::deleting, this, &scr_specs_chart::deleting_spec); + connect(spec, >l::math::spec::frequency_changed, this, &scr_specs_chart::set_range); + + add_series(series); + + fit_axis(series->axis_y()); + } + + void scr_specs_chart::remove_spec(scr::spec *spec) + { + remove(spec); + } + + void scr_specs_chart::deleting_spec() + { + remove(static_cast(sender())); + } + + void scr_specs_chart::set_range() + { + set_bounds_x(); + _axis_x->set_range(); + } + + } +} diff --git a/gui/gtl_gui_scr_specs_chart.h b/gui/gtl_gui_scr_specs_chart.h new file mode 100644 index 0000000..cb1c3ed --- /dev/null +++ b/gui/gtl_gui_scr_specs_chart.h @@ -0,0 +1,37 @@ +#ifndef SCR_SPECS_CHART_H +#define SCR_SPECS_CHART_H + +#include "gtl_gui_spec_chart.h" + +#include "math/gtl_math_spec.h" +#include "script/gtl_scr_engine.h" + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT scr_specs_chart : public spec_chart + { + Q_OBJECT + public: + scr_specs_chart(QWidget* parent = NULL); + int series_count() const; + chart_series* series(int idx) const; + + private: + void remove(gtl::scr::spec *spec); + + public slots: + void add_spec(gtl::scr::spec *spec); + void remove_spec(gtl::scr::spec *spec); + + private slots: + void deleting_spec(); + void set_range(); + + }; + } +} +#endif // SCR_SPECS_CHART_H diff --git a/gui/gtl_gui_scr_specs_series.cpp b/gui/gtl_gui_scr_specs_series.cpp new file mode 100644 index 0000000..a9b5f0f --- /dev/null +++ b/gui/gtl_gui_scr_specs_series.cpp @@ -0,0 +1,198 @@ +#include "gtl_gui_scr_specs_series.h" + +namespace gtl +{ + namespace gui + { + scr_specs_series::scr_specs_series(gtl::scr::spec* spec, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y) + : spec_series(true, gtl::math::spec::undef, spec->ad(), axis_x, axis_y) + { + delete _spec; + + _spec = spec; + + set_color(spec->color()); + + + connect(_spec, >l::math::spec::changed, this, &spec_series::update); + connect(_spec, >l::math::spec::initialized, this, &spec_series::update); + connect(spec, >l::scr::spec::color_changed, this, &scr_specs_series::spec_color_changed); + connect(spec, >l::scr::spec::harms_set_visible_changed, this, &scr_specs_series::update); + + update(); + + } + + QString scr_specs_series::name() const + { + if(static_cast(_spec)->name().isEmpty()) + return spec_series::name(); + + return static_cast(_spec)->name(); + } + + gtl::scr::spec *scr_specs_series::spec() const + { + return static_cast(_spec); + } + + void scr_specs_series::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + spec_series::paint(painter, option, widget); + + _harms.clear(); + static_cast(_spec)->get_harms(std::back_inserter(_harms)); + + std::vector peaks; + static_cast(_spec)->get_peaks(std::back_inserter(peaks)); + + qreal rx = qAbs(_axis_horz->map_from_widget(0) - _axis_horz->map_from_widget(5)); + qreal ry = qAbs(_axis_vert->map_from_widget(0) - _axis_vert->map_from_widget(5)); + + for(auto peak_idx : peaks) + { + + auto it = std::find_if(_harms.begin(), _harms.end(), [=](gtl::scr::spec_harm* harm){return harm->peak_freq() == peak_idx*_spec->resolution();}); + + if(it == _harms.end()) + painter->setBrush(Qt::NoBrush); + else + painter->setBrush(painter->pen().color()); + + + + painter->drawEllipse( + QPointF(peak_idx*_spec->resolution(), _spec->at(peak_idx)), + rx, + ry + ); + } + + + std::vector smoothed_line; + static_cast(_spec)->get_smoothed_line(std::back_inserter(smoothed_line)); + + if(smoothed_line.empty()) + return; + + + + + + std::vector polyline; + for(int i = 0; i< smoothed_line.size(); i++) + polyline.push_back( + QPointF( + i*_spec->resolution(), + smoothed_line[i] + ) + ); + + QPen pen(QColor(static_cast(_spec)->smoothed_line_color()), 0); + painter->setPen(pen); + + painter->drawPolyline(&polyline[0], polyline.size()); + + + + painter->resetTransform(); + + QRectF rect = boundingRect(); + + for(auto harm : _harms) + { + QPen pen(QColor(harm->color()), harm->weight()); + painter->setPen(pen); + + qreal x = _axis_horz->map_to_widget(harm->freq()); + qreal y = _axis_vert->map_to_widget(static_cast(_spec)->get_amplitude(harm->freq())); + + painter->drawLine(x, y, x, rect.bottom()); + + if(harm->is_present()) + { + painter->setBrush(QBrush(harm->color())); + painter->drawEllipse(QPointF(x, y), 3, 3); + } + } + + std::vector humps; + static_cast(_spec)->get_humps(std::back_inserter(humps)); + for(auto hump : humps) + { + QPen pen(QColor(hump->color())); + QColor bruhs_color(hump->color()); + bruhs_color.setAlpha(32); + painter->setBrush(QBrush(bruhs_color)); + painter->setPen(pen); + + qreal x0 = hump->x0(); + qreal dx = hump->dx(); + + std::vector line; + hump->get_line(std::back_inserter(line)); + QPolygonF polyline; + polyline.push_back(QPointF(_axis_horz->map_to_widget(x0), rect.bottom())); + for(int i = 0; i < line.size(); i++) + polyline.push_back(QPointF(_axis_horz->map_to_widget(x0 + i*dx), _axis_vert->map_to_widget(line[i]))); + + polyline.push_back(QPointF(_axis_horz->map_to_widget(x0 + line.size()*dx), rect.bottom())); + + QPainterPath path; + path.addPolygon(polyline); + painter->drawPath(path); + + bruhs_color.setAlpha(96); + painter->setBrush(QBrush(bruhs_color)); + + for(int idx = 0; idx < hump->count(); idx++) + { + QPainterPath hump_path; + QPolygonF hump_polygon; + for(int i = (*hump)[idx].left_idx; i <= (*hump)[idx].right_idx; i++) + { + hump_polygon.push_back(QPointF(_axis_horz->map_to_widget(x0 + i*dx), _axis_vert->map_to_widget(line[i]))); + } + + hump_path.addPolygon(hump_polygon); + painter->drawPath(hump_path); + } + + } + } + + void scr_specs_series::set_tool_tip(QPoint pos) + { + QRectF rect = boundingRect(); + + for(auto harm : _harms) + { + qreal x = _axis_horz->map_to_widget(harm->freq()); + qreal y = _axis_vert->map_to_widget(static_cast(_spec)->get_amplitude(harm->freq())); + + QRectF harm_rect(x - 1, y, 2, rect.bottom() - y); + + if(harm_rect.contains(pos)) + { + QString text = tr("peak freq") + ": " + (harm->peak_freq() < 0 ? "-" : QString::number(harm->peak_freq(), 'f', 3)) + + "\n" + tr("freq") + ": " + QString::number(harm->freq(), 'f', 3) + + "\n" + tr("base") + ": " + QString::number(harm->base(), 'f', 3) + + "\n" + tr("level") + ": " + QString::number(harm->level(), 'f', 3) + + "\n" + tr("amplitude") + ": " + QString::number(harm->amplitude(), 'f', 3) + + "\n" + tr("integral index") + ": " + QString::number(harm->integral_index(), 'f', 3) + ; + + setToolTip(text); + return; + } + } + + setToolTip(""); + } + + void scr_specs_series::spec_color_changed() + { + set_color(QColor(static_cast(_spec)->color())); + } + } +} diff --git a/gui/gtl_gui_scr_specs_series.h b/gui/gtl_gui_scr_specs_series.h new file mode 100644 index 0000000..0f89391 --- /dev/null +++ b/gui/gtl_gui_scr_specs_series.h @@ -0,0 +1,37 @@ +#ifndef SCR_SPECS_SERIES_H +#define SCR_SPECS_SERIES_H + +#include "gtl_gui_spec_series.h" +#include "script/gtl_scr_spec.h" + +namespace gtl +{ + namespace gui + { + class scr_specs_series : public spec_series + { + Q_OBJECT + public: + scr_specs_series(gtl::scr::spec* spec, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y); + + virtual QString name() const override; + + gtl::scr::spec* spec() const; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + + + private: + virtual void set_tool_tip(QPoint pos) override; + + private: + std::vector _harms; + + private slots: + void spec_color_changed(); + + }; + } +} + +#endif // SCR_SPECS_SERIES_H diff --git a/gui/gtl_gui_scr_specs_widget.cpp b/gui/gtl_gui_scr_specs_widget.cpp new file mode 100644 index 0000000..0b1149e --- /dev/null +++ b/gui/gtl_gui_scr_specs_widget.cpp @@ -0,0 +1,62 @@ +#include "gtl_gui_scr_specs_widget.h" +#include "ui_gtl_gui_scr_specs_widget.h" + +#include "gtl_gui_chart_widget.h" +#include "script/gtl_scr_specs_model.h" + +namespace gtl +{ + namespace gui + { + scr_specs_widget::scr_specs_widget(gtl::scr::engine *engine, QWidget *parent) : + QWidget(parent), + ui(new Ui::scr_specs_widget) + { + ui->setupUi(this); + + gtl::scr::specs_model* model = new gtl::scr::specs_model(this); + ui->series->setModel(model); + + connect(engine, >l::scr::engine::spec_created, model, >l::scr::specs_model::add_spec); + + _chart = new scr_specs_chart(this); + gtl::gui::chart_widget *chart_widget = new gtl::gui::chart_widget(_chart, this); + connect(model, >l::scr::specs_model::show_spec, _chart, >l::gui::scr_specs_chart::add_spec); + connect(model, >l::scr::specs_model::hide_spec, _chart, >l::gui::scr_specs_chart::remove_spec); + + ui->splitter->insertWidget(0, chart_widget); + + ui->y_mode->setChecked(_chart->is_axis_y_multi()); + connect(ui->y_mode, &QCheckBox::toggled, _chart, >l::gui::chart::set_axis_y_mode); + connect(_chart, >l::gui::chart::axis_y_mode_changed, ui->y_mode, &QCheckBox::setChecked); + + + + } + + scr_specs_widget::~scr_specs_widget() + { + delete ui; + } + + void scr_specs_widget::save(QDomElement &root_element) + { + QDomElement options_element = root_element.ownerDocument().createElement("options"); + root_element.appendChild(options_element); + _chart->save(options_element); + + QDomElement splitter_element = root_element.ownerDocument().createElement("splitter"); + root_element.appendChild(splitter_element); + splitter_element.setAttribute("state", QString(ui->splitter->saveState().toBase64())); + } + + void scr_specs_widget::load(const QDomElement &root_element) + { + QDomElement options_element = root_element.firstChildElement("options"); + _chart->load(options_element); + + QDomElement splitter_element = root_element.firstChildElement("splitter"); + ui->splitter->restoreState(QByteArray::fromBase64(splitter_element.attribute("state", "").toUtf8())); + } + } +} diff --git a/gui/gtl_gui_scr_specs_widget.h b/gui/gtl_gui_scr_specs_widget.h new file mode 100644 index 0000000..74d725a --- /dev/null +++ b/gui/gtl_gui_scr_specs_widget.h @@ -0,0 +1,36 @@ +#ifndef GTL_GUI_SCR_SPECS_WIDGET_H +#define GTL_GUI_SCR_SPECS_WIDGET_H + +#include + +#include "gui_global.h" + +#include "gtl_gui_scr_specs_chart.h" + +namespace Ui { +class scr_specs_widget; +} + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT scr_specs_widget : public QWidget + { + Q_OBJECT + + public: + explicit scr_specs_widget(gtl::scr::engine *engine, QWidget *parent = nullptr); + ~scr_specs_widget(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + Ui::scr_specs_widget *ui; + scr_specs_chart *_chart; + }; + } +} + +#endif // GTL_GUI_SCR_SPECS_WIDGET_H diff --git a/gui/gtl_gui_scr_specs_widget.ui b/gui/gtl_gui_scr_specs_widget.ui new file mode 100644 index 0000000..876e739 --- /dev/null +++ b/gui/gtl_gui_scr_specs_widget.ui @@ -0,0 +1,57 @@ + + + scr_specs_widget + + + + 0 + 0 + 491 + 356 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + + + + + + + multy axis x + + + + + + + + + + + + diff --git a/gui/gtl_gui_scr_table.cpp b/gui/gtl_gui_scr_table.cpp new file mode 100644 index 0000000..1f25145 --- /dev/null +++ b/gui/gtl_gui_scr_table.cpp @@ -0,0 +1,81 @@ +#include "gtl_gui_scr_table.h" + +namespace gtl +{ + namespace gui + { + scr_table::scr_table(gtl::data_model* model, QWidget* parent) + : QTableView(parent) + { + _ui.setupUi(this); + + _engine = new gtl::scr::engine_table(this); + + _editor = new gtl::gui::scr_editor(this); + + _ui.splitter_2->addWidget(_editor); + + + _selection_data_model = new gtl::selection_data_model(/*_model*/this); + _selection_data_model->setSourceModel(model); + + _ui.data_tree->setModel(_selection_data_model); + _ui.data_tree->expandAll(); + _ui.table->setModel(_engine->model()); + + connect(_selection_data_model, >l::selection_data_model::selected, _engine, >l::scr::engine::add_ad); + connect(_selection_data_model, >l::selection_data_model::deselected, _engine, >l::scr::engine::remove_ad); + + connect(_engine, >l::scr::engine::analog_inputs_changed, this, &scr_table::evaluate); + + + } + + scr_table::~scr_table() + { + _engine = nullptr; + } + + void scr_table::save(QDomElement &root_element) + { + QDomElement selection_element = root_element.ownerDocument().createElement("selection"); + root_element.appendChild(selection_element); + _selection_data_model->save(selection_element); + + QDomElement script_element = root_element.ownerDocument().createElement("script"); + root_element.appendChild(script_element); + script_element.setAttribute("text", _editor->toPlainText()); + + QDomElement splitter_element = root_element.ownerDocument().createElement("splitter"); + root_element.appendChild(splitter_element); + splitter_element.setAttribute("state", QString(_ui.splitter->saveState().toBase64())); + + QDomElement splitter_2_element = root_element.ownerDocument().createElement("splitter_2"); + root_element.appendChild(splitter_2_element); + splitter_2_element.setAttribute("state", QString(_ui.splitter_2->saveState().toBase64())); + } + + void scr_table::load(const QDomElement &root_element) + { + QDomElement selection_element = root_element.firstChildElement("selection"); + _selection_data_model->load(selection_element); + + _editor->clear(); + QDomElement script_element = root_element.firstChildElement("script"); + _editor->appendPlainText(script_element.attribute("text", "")); + evaluate(); + + QDomElement splitter_element = root_element.firstChildElement("splitter"); + _ui.splitter->restoreState(QByteArray::fromBase64(splitter_element.attribute("state", "").toUtf8())); + + QDomElement splitter_element_2 = root_element.firstChildElement("splitter_2"); + _ui.splitter_2->restoreState(QByteArray::fromBase64(splitter_element_2.attribute("state", "").toUtf8())); + } + + void scr_table::evaluate() + { + if(_engine) + static_cast(_engine)->evaluate(_editor->toPlainText()); + } + } +} diff --git a/gui/gtl_gui_scr_table.h b/gui/gtl_gui_scr_table.h new file mode 100644 index 0000000..5613174 --- /dev/null +++ b/gui/gtl_gui_scr_table.h @@ -0,0 +1,45 @@ +#ifndef SCR_TABLE_H +#define SCR_TABLE_H + +#include + +#include "gui_global.h" + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" + +#include "gui/gtl_gui_scr_editor.h" + +#include "script/gtl_scr_engine_table.h" + +#include "ui_gtl_gui_scr_table.h" + + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT scr_table : public QTableView + { + Q_OBJECT + public: + scr_table(gtl::data_model* model, QWidget* parent = NULL); + ~scr_table(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + Ui::script_table _ui; + gtl::selection_data_model* _selection_data_model; + scr_editor* _editor; + gtl::scr::engine_table* _engine; + + public slots: + void evaluate(); + + }; + } +} + +#endif // SCR_TABLE_H diff --git a/gui/gtl_gui_scr_table.ui b/gui/gtl_gui_scr_table.ui new file mode 100644 index 0000000..7d88dda --- /dev/null +++ b/gui/gtl_gui_scr_table.ui @@ -0,0 +1,42 @@ + + + script_table + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + Qt::Horizontal + + + + + Qt::Vertical + + + + true + + + false + + + + + + + + + + diff --git a/gui/gtl_gui_scr_widget.cpp b/gui/gtl_gui_scr_widget.cpp new file mode 100644 index 0000000..efaa36b --- /dev/null +++ b/gui/gtl_gui_scr_widget.cpp @@ -0,0 +1,140 @@ +#include "gtl_gui_scr_widget.h" +#include "ui_gtl_gui_scr_widget.h" + +#include "gui/gtl_gui_chart_widget.h" + +namespace gtl +{ + namespace gui + { + scr_widget::scr_widget(gtl::scr::engine* engine, gtl::data_model* model, QWidget *parent) + : QWidget(parent) + , ui(new Ui::scr_widget) + , _engine(engine) + , _files(this) + , _selection(&_files) + { + ui->setupUi(this); + + _editor = new gtl::gui::scr_editor(this); + connect(_editor, &scr_editor::init_menu, this, &scr_widget::init_editor_menu); + + ui->splitter->addWidget(_editor); + + + _selection_data_model = new gtl::selection_data_model(this); + _selection_data_model->setSourceModel(model); + + ui->data_tree->setModel(_selection_data_model); + ui->data_tree->expandAll(); + + connect(_selection_data_model, >l::selection_data_model::selected, _engine, >l::scr::engine::add_ad); + connect(_selection_data_model, >l::selection_data_model::deselected, _engine, >l::scr::engine::remove_ad); + +// connect(_engine, >l::scr::engine::analog_inputs_changed, this, &scr_widget::evaluate); + + _chart = new record_chart(this); + chart_widget *chart_widget_ = new chart_widget(_chart, this); + ui->files_splitter->insertWidget(0, chart_widget_); + + _files.setNameFilterDisables(false); + _files.setNameFilters(QStringList() << "*.gtr" << "*.wav"); + _files.setFilter(QDir::Files | QDir::AllDirs | QDir::NoDotAndDotDot); + ui->files->setModel(&_files); + ui->files->setSelectionModel(&_selection); + + ui->files->setRootIndex(_files.setRootPath("")); + ui->files->setSortingEnabled(true); + ui->files->header()->setSectionsClickable(true); + ui->files->header()->setSortIndicatorShown(true); + ui->files->header()->setSortIndicator(0, Qt::SortOrder::AscendingOrder); + + connect(&_selection, &QItemSelectionModel::currentChanged, this, &scr_widget::selection_changed); + + connect(ui->mode, &QTabWidget::currentChanged, this, &scr_widget::mode_changed); + } + + scr_widget::~scr_widget() + { + delete ui; + } + + void scr_widget::save(QDomElement &root_element) + { + QDomElement selection_element = root_element.ownerDocument().createElement("selection"); + root_element.appendChild(selection_element); + _selection_data_model->save(selection_element); + + QDomElement script_element = root_element.ownerDocument().createElement("script"); + root_element.appendChild(script_element); + script_element.setAttribute("text", _editor->toPlainText()); + + QDomElement splitter_element = root_element.ownerDocument().createElement("splitter"); + root_element.appendChild(splitter_element); + splitter_element.setAttribute("state", QString(ui->splitter->saveState().toBase64())); + + QDomElement files_splitter_element = root_element.ownerDocument().createElement("files_splitter"); + root_element.appendChild(files_splitter_element); + files_splitter_element.setAttribute("state", QString(ui->files_splitter->saveState().toBase64())); + + root_element.setAttribute("file", _files.filePath(_selection.currentIndex())); + + root_element.setAttribute("mode", ui->mode->currentIndex()); + } + + void scr_widget::load(const QDomElement &root_element) + { + QDomElement selection_element = root_element.firstChildElement("selection"); + _selection_data_model->load(selection_element); + + _editor->clear(); + QDomElement script_element = root_element.firstChildElement("script"); + _editor->appendPlainText(script_element.attribute("text", "")); +// evaluate(); + + QDomElement splitter_element = root_element.firstChildElement("splitter"); + ui->splitter->restoreState(QByteArray::fromBase64(splitter_element.attribute("state", "").toUtf8())); + + QDomElement files_splitter_element = root_element.firstChildElement("files_splitter"); + ui->files_splitter->restoreState(QByteArray::fromBase64(files_splitter_element.attribute("state", "").toUtf8())); + + _selection.setCurrentIndex(_files.index(root_element.attribute("file", "")), QItemSelectionModel::Select); + + ui->mode->setCurrentIndex(root_element.attribute("mode", "0").toInt()); + } + + void scr_widget::evaluate() + { + if(_engine) + _engine->evaluate(_editor->toPlainText()); + } + + void scr_widget::selection_changed(const QModelIndex ¤t, const QModelIndex &previous) + { + + QString path = _files.filePath(current); + _chart->set_file(path); + + if(ui->mode->currentIndex() != 0) + _engine->set_file(path); + } + + void scr_widget::mode_changed(int value) + { + if(value == 0) + { + _engine->set_file(); + + std::vector ad; + _selection_data_model->get_selections(std::back_inserter(ad)); + + for(auto it : ad) + _engine->add_ad(it); + } + else + { + _engine->set_file(_files.filePath(_selection.currentIndex())); + } + } + } +} diff --git a/gui/gtl_gui_scr_widget.h b/gui/gtl_gui_scr_widget.h new file mode 100644 index 0000000..bd63753 --- /dev/null +++ b/gui/gtl_gui_scr_widget.h @@ -0,0 +1,63 @@ +#ifndef GTL_GUI_SCR_WIDGET_H +#define GTL_GUI_SCR_WIDGET_H + +#include +#include + +#include "gui_global.h" + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" + +#include "gui/gtl_gui_scr_editor.h" +#include "gui/config/gtl_gui_config_widget_player_files.h" +#include "gui/gtl_gui_record_chart.h" + +#include "script/gtl_scr_engine.h" + + +namespace Ui { +class scr_widget; +} + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT scr_widget : public QWidget + { + Q_OBJECT + + public: + explicit scr_widget(gtl::scr::engine* engine, gtl::data_model* model, QWidget *parent = nullptr); + ~scr_widget(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + Ui::scr_widget *ui; + + gtl::selection_data_model* _selection_data_model; + scr_editor* _editor; + gtl::scr::engine* _engine; + + record_chart *_chart; + + config::widget_player_files _files; + QItemSelectionModel _selection; + + public slots: + void evaluate(); + + private slots: + void selection_changed(const QModelIndex ¤t, const QModelIndex &previous); + void mode_changed(int value); + + signals: + void init_editor_menu(QMenu* menu); + }; + } +} + +#endif // GTL_GUI_SCR_WIDGET_H diff --git a/gui/gtl_gui_scr_widget.ui b/gui/gtl_gui_scr_widget.ui new file mode 100644 index 0000000..0aa5ea7 --- /dev/null +++ b/gui/gtl_gui_scr_widget.ui @@ -0,0 +1,103 @@ + + + scr_widget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + 0 + + + + Hardware + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + + Files + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Vertical + + + + + + + + + + + + + + diff --git a/gui/gtl_gui_spec_chart.cpp b/gui/gtl_gui_spec_chart.cpp new file mode 100644 index 0000000..6e90cbf --- /dev/null +++ b/gui/gtl_gui_spec_chart.cpp @@ -0,0 +1,398 @@ +#include "gtl_gui_spec_chart.h" +#include "math/gtl_math_spec_meas.h" + +#include "gui/spec/gtl_gui_spec_band_marker.h" +#include "gui/spec/gtl_gui_spec_harm_marker.h" + +namespace gtl +{ + namespace gui + { + spec_chart::spec_chart(gtl::math::spec::types type, QWidget* parent) + : chart(parent) + , _type(type) + , _frequency(100000) + , _resolution(100) + , _window(5) + , _lines(_frequency/_resolution) + , _average(1) + , _overlap(0) + , _unit(0) + , _meas_model(nullptr) + , _meas_cnt(0) + , _band_markers{new spec::band_markers(this)} + , _harm_markers{new spec::harm_markers(this)} + { + _band_marker_action_add = new QAction(tr("Add band marker"), this); + _markers_menu->insertAction(_marker_action_remove, _band_marker_action_add); + connect(_band_marker_action_add, &QAction::triggered, this, &spec_chart::add_band_marker); + + _harm_marker_action_add = new QAction(tr("Add harmonic marker"), this); + _markers_menu->insertAction(_marker_action_remove, _harm_marker_action_add); + connect(_harm_marker_action_add, &QAction::triggered, this, &spec_chart::add_harm_marker); + } + + spec_chart::~spec_chart() + { + remove(); + } + + qreal spec_chart::frequency() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _frequency = static_cast(*iter_series)->frequency(); + } + return _frequency; + } + + qreal spec_chart::resolution() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _resolution = static_cast(*iter_series)->resolution(); + } + return _resolution; + } + + int spec_chart::window() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _window = static_cast(static_cast(*iter_series)->window()); + } + return _window; + } + + int spec_chart::lines() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _lines = static_cast(*iter_series)->lines(); + } + return _lines; + } + + int spec_chart::average() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _average = static_cast(*iter_series)->average(); + } + return _average; + } + + qreal spec_chart::overlap() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _overlap = static_cast(*iter_series)->overlap(); + } + return _overlap; + } + + int spec_chart::unit() + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + _unit = static_cast(static_cast(*iter_series)->unit()); + } + return _unit; + } + + void spec_chart::save(QDomElement &root_element) + { + chart::save(root_element); + + root_element.setAttribute("type", _type); + root_element.setAttribute("freq", _frequency); + root_element.setAttribute("resolution", _resolution); + root_element.setAttribute("window", _window); + root_element.setAttribute("lines", _lines); + root_element.setAttribute("avg", _average); + root_element.setAttribute("overlap", _overlap); + root_element.setAttribute("unit", _unit); + } + + void spec_chart::load(const QDomElement &root_element) + { + chart::load(root_element); + + _type = (gtl::math::spec::types)root_element.attribute("type", "0").toInt(); + _frequency = root_element.attribute("freq", "100000").toDouble(); + _resolution = root_element.attribute("resolution", "100").toDouble(); + _window = root_element.attribute("window", "5").toInt(); + _lines = root_element.attribute("lines", QString::number(qRound(_frequency/_resolution))).toUInt(); + _average = root_element.attribute("avg", "1").toUInt(); + _overlap = root_element.attribute("overlap", "0").toDouble(); + _unit = root_element.attribute("unit", "0").toInt(); + } + + spec::band_markers *spec_chart::band_markers() const + { + return _band_markers; + } + + spec::harm_markers *spec_chart::harm_markers() const + { + return _harm_markers; + } + + void spec_chart::set_type(int value) + { + _type = (gtl::math::spec::types)value; + + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + static_cast(*iter_series)->set_type(_type); + + } + + math::spec::types spec_chart::type() const + { + return _type; + } + + void spec_chart::add_instrument(const QPointF &pos) + { + _instruments.back()->add(pos); + if (_instruments.back()->is_complete()) + { + _mouse_action = ::chart::edit_instruments; + } + } + + void spec_chart::draw_instrument(const QPointF &pos) + { + _instruments.back()->draw(QPointF(_axis_x->map_from_widget(pos.x()), _axis_y->map_from_widget(pos.y()))); + } + + void spec_chart::set_frequency(qreal value) + { + if(value != _frequency) + { + if(_series.empty()) + { + _frequency = value; + } + else + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_frequency(value); + _frequency = static_cast(*iter_series)->frequency(); + } + } + + emit frequency_changed(); + } + } + + void spec_chart::set_resolution(qreal value) + { + if(value != _resolution) + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_resolution(value); + _resolution = static_cast(*iter_series)->resolution(); + } + + emit resolution_changed(); + } + } + + void spec_chart::set_window(int value) + { + if(value != _window) + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_window(value); + _window = static_cast(static_cast(*iter_series)->window() - 1); + } + + emit window_changed(); + } + } + + void spec_chart::set_lines(int value) + { + if(value != _lines) + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_lines(value); + _lines = static_cast(*iter_series)->lines(); + } + + emit lines_changed(); + } + } + + void spec_chart::set_average(int value) + { + if(value != _average) + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_average(value); + _average = static_cast(*iter_series)->average(); + } + + emit average_changed(); + } + } + + void spec_chart::set_overlap(qreal value) + { + if(value != _overlap) + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_overlap(value); + _overlap = static_cast(*iter_series)->overlap(); + } + + emit overlap_changed(); + } + } + + void spec_chart::set_unit(int value) + { + if(value != _unit) + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + static_cast(*iter_series)->set_unit(value); + _unit = static_cast(static_cast(*iter_series)->unit()); + } + + emit unit_changed(); + } + } + + void spec_chart::set_measures(spec_meas_model* model) + { + _meas_model = model; + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + static_cast(*iter_series)->set_measures(_meas_model); + } + + void spec_chart::set_x_log(bool value) + { + if(value) + { + qreal min = .001; + + for(auto series: _series) + { + if(_series.size() < 2) + continue; + + if(static_cast<::chart::series::xy::xy*>(series)->at_x(1) < min) + min = static_cast<::chart::series::xy::xy*>(series)->at_x(1); + } + + + _axis_x->set_boundaries(min, _axis_x->max_boundary()); + } + else + _axis_x->set_boundaries(0, _axis_x->max_boundary()); + + _axis_x->set_scale(value ? ::chart::axis::logarithmic : ::chart::axis::linear); + } + + void spec_chart::handle_measures(SpecMeasParamsListPtr meas) + { + for(int i = 0; i < meas->count(); i++) + for(int j = 0; j < _meas_model->measures()->count(); j++) + { + if(meas->at(i).id == _meas_model->measures()->at(j).id) + { + gtl::math::spec_meas::params *p = const_cast(&_meas_model->measures()->at(j)); + p->value = meas->at(i).value; + _meas_cnt++; + } + } + + if(_meas_cnt >= _meas_model->measures()->count()) + { + _meas_model->all_done(); + _meas_cnt = 0; + } + } + + gtl::gui::spgr::widget* spec_chart::create_spgr(QWidget *parent, data_model_node *node) + { + if(node) + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + { + spec_series* ser = static_cast(*iter_series); + if(ser->ad()->name() == node->name()) + return ser->create_spgr(parent); + } + return nullptr; + } + + void spec_chart::add_band_marker() + { + spec::band_marker* marker = new spec::band_marker(_marker_series, _set_theme_action->isChecked()); + add(marker); + + connect(marker, &chart_marker::get_nearest_x, this, &spec_chart::get_neares_series_x); + connect(marker, &chart_marker::get_series_data, this, &spec_chart::get_series_data); + connect(marker, &chart_marker::get_series_values, this, &spec_chart::get_series_values); + connect(marker, &chart_marker::deleting, this, &spec_chart::remove_marker); + +// marker->set_pos(_mouse_pos_release); + + _markers->add_marker(marker); + + _mouse_action = ::chart::add_instruments; + draw_instrument(_mouse_pos_release); + + _band_markers->add(marker); + } + + void spec_chart::add_harm_marker() + { + chart_marker* marker = new spec::harm_marker(_marker_series, _set_theme_action->isChecked()); + add(marker); + + connect(marker, &chart_marker::get_nearest_x, this, &spec_chart::get_neares_series_x); + connect(marker, &chart_marker::get_series_data, this, &spec_chart::get_series_data); + connect(marker, &chart_marker::get_series_values, this, &spec_chart::get_series_values); + connect(marker, &chart_marker::deleting, this, &spec_chart::remove_marker); + + marker->set_pos(_mouse_pos_release); + + _markers->add_marker(marker); + _harm_markers->add(marker); + } + + chart_series *spec_chart::create_series(analog_data *ai) + { + spec_series *series = new spec_series(is_updating(), _type, ai, _axis_x, _axis_y); + + connect(series, &spec_series::initialized, this, &spec_chart::set_bounds_x); + connect(series, &spec_series::measures_changed, this, &spec_chart::handle_measures); + + series->set_frequency(_frequency); + set_frequency(series->frequency()); + + series->set_resolution(_resolution); + series->set_window(_window); + series->set_lines(_lines); + series->set_average(_average); + series->set_overlap(_overlap); + series->set_unit(_unit); + series->set_measures(_meas_model); + + series->update(); + + return series; + } + + + } +} diff --git a/gui/gtl_gui_spec_chart.h b/gui/gtl_gui_spec_chart.h new file mode 100644 index 0000000..4154fdf --- /dev/null +++ b/gui/gtl_gui_spec_chart.h @@ -0,0 +1,98 @@ +#ifndef GTL_GUI_SPEC_CHART_H +#define GTL_GUI_SPEC_CHART_H + +#include "gtl_gui_chart.h" +#include "spec/gtl_gui_spec_band_markers.h" +#include "spec/gtl_gui_spec_harm_markers.h" +#include "gui/gtl_gui_spec_series.h" +#include "gui/gtl_gui_spec_meas_model.h" +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT spec_chart : public gtl::gui::chart + { + Q_OBJECT + public: + spec_chart(gtl::math::spec::types type, QWidget* parent = NULL); + ~spec_chart(); + + qreal frequency(); + qreal resolution(); + int window(); + int lines(); + int average(); + qreal overlap(); + int unit(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + spec::band_markers* band_markers() const; + spec::harm_markers* harm_markers() const; + + gtl::math::spec::types type() const; + + protected: + virtual void add_instrument(const QPointF &pos) override; + virtual void draw_instrument(const QPointF &pos) override; + + private: + gtl::math::spec::types _type; + qreal _frequency; + qreal _resolution; + int _window; + int _lines; + int _average; + qreal _overlap; + int _unit; + + spec_meas_model* _meas_model; + int _meas_cnt; + + QAction* _band_marker_action_add; + QAction* _harm_marker_action_add; + + spec::band_markers* _band_markers; + spec::harm_markers* _harm_markers; + + signals: + void frequency_changed(); + void resolution_changed(); + void window_changed(); + void lines_changed(); + void average_changed(); + void overlap_changed(); + void unit_changed(); + + public slots: + void set_type(int value); + + void set_frequency(qreal value); + void set_resolution(qreal value); + void set_window(int value); + void set_lines(int value); + void set_average(int value); + void set_overlap(qreal value); + void set_unit(int value); + + void set_x_log(bool value); + void set_measures(spec_meas_model* m); + void handle_measures(SpecMeasParamsListPtr meas); + + gtl::gui::spgr::widget* create_spgr(QWidget *parent, gtl::data_model_node* node); + + private slots: + void add_band_marker(); + void add_harm_marker(); + + protected: + virtual chart_series* create_series(gtl::analog_data* ai); + + }; + } +} + +#endif // GTL_GUI_SPEC_CHART_H diff --git a/gui/gtl_gui_spec_meas_delegate.cpp b/gui/gtl_gui_spec_meas_delegate.cpp new file mode 100644 index 0000000..41bc90a --- /dev/null +++ b/gui/gtl_gui_spec_meas_delegate.cpp @@ -0,0 +1,101 @@ + +#include "gtl_gui_spec_meas_delegate.h" +#include "gui/gtl_gui_spec_meas_model.h" + +namespace gtl { + namespace gui { + + + spec_meas_delegate::spec_meas_delegate(QObject *parent, QAbstractItemModel *channels) : + QStyledItemDelegate(parent), + _channels(channels) + { + } + + QWidget *spec_meas_delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(option) + Q_UNUSED(index) + + int col = index.column(); + + if(col == spec_meas_model::CHAN) + { + spec_meas_chan_widget *editor = new spec_meas_chan_widget(parent, _channels); + connect(editor, &spec_meas_chan_widget::chan_changed , [=]{setModelData(editor, const_cast(index.model()), index);}); + return editor; + } + else if(col == spec_meas_model::PARAM) + { + spec_meas_param_widget *editor = new spec_meas_param_widget(parent); + connect(editor, &spec_meas_param_widget::param_changed , [=]{setModelData(editor, const_cast(index.model()), index);}); + return editor; + } + + return 0; + } + + void spec_meas_delegate::setEditorData(QWidget *editor, const QModelIndex &index) const + { + int col = index.column(); + + if(col == spec_meas_model::CHAN) + { + spec_meas_chan_widget *widget = static_cast(editor); + widget->blockSignals(true); + widget->setCurrentIndex(index.data(spec_meas_model::CustomRoles::IdxRole).toInt()); + widget->blockSignals(false); + } + else if(col == spec_meas_model::PARAM) + { + spec_meas_param_widget *widget = static_cast(editor); + widget->blockSignals(true); + + widget->setCurrentIndex(index.data(Qt::DisplayRole).toInt()); + + widget->blockSignals(false); + } + } + + void spec_meas_delegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const + { + int col = index.column(); + + editor->blockSignals(true); + if(col == spec_meas_model::CHAN) + { + spec_meas_chan_widget *widget = static_cast(editor); + model->setData(index, widget->currentIdx(), spec_meas_model::CustomRoles::IdxRole); + model->setData(index, widget->currentText(), Qt::EditRole); + } + else if(col == spec_meas_model::PARAM) + { + spec_meas_param_widget *widget = static_cast(editor); + model->setData(index, widget->currentIndex(), Qt::EditRole); + } + editor->blockSignals(false); + } + + void spec_meas_delegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(index) + editor->setGeometry(option.rect); + } + + void spec_meas_delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(painter) + Q_UNUSED(option) + Q_UNUSED(index) + } + + QSize spec_meas_delegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QSize s = index.data(Qt::SizeHintRole).toSize(); + return s.isValid() ? s: QStyledItemDelegate::sizeHint(option, index); + } + + + } // namespace gui +} // namespace gtl + diff --git a/gui/gtl_gui_spec_meas_delegate.h b/gui/gtl_gui_spec_meas_delegate.h new file mode 100644 index 0000000..e15a417 --- /dev/null +++ b/gui/gtl_gui_spec_meas_delegate.h @@ -0,0 +1,138 @@ + +#ifndef GTL_GUI_SPEC_MEAS_DELEGATE_H +#define GTL_GUI_SPEC_MEAS_DELEGATE_H + +#include +#include +#include +#include +#include +#include +#include + +#include "gui_global.h" +#include "math/gtl_math_spec_meas.h" + +namespace gtl { + namespace gui { + + class GTL_GUI_EXPORT spec_meas_chan_widget : public QWidget + { + Q_OBJECT + public: + spec_meas_chan_widget(QWidget *parent=nullptr, QAbstractItemModel *model = nullptr) : + QWidget(parent), + comboBox(new QComboBox) + { + if(model) + { + QHBoxLayout *layout = new QHBoxLayout(this); + comboBox->setModel(model); + comboBox->setContentsMargins(0,0,0,0); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(comboBox); + + connect(comboBox, &QComboBox::currentIndexChanged, [=]{emit chan_changed();}); + } + } + + QString currentText() + { + return comboBox->currentText(); + } + + int currentIdx() + { + int idx = comboBox->currentIndex(); + return idx; + } + + void setCurrentIndex(QString name) + { + int idx = comboBox->findText(name); + if(idx != comboBox->currentIndex()) + { + if(idx >= 0 && idx < comboBox->count()) + comboBox->setCurrentIndex(idx); + emit chan_changed(); + } + } + + void setCurrentIndex(int idx) + { + if(idx != comboBox->currentIndex()) + { + if(idx >= 0 && idx < comboBox->count()) + comboBox->setCurrentIndex(idx); + emit chan_changed(); + } + } + + signals: + void chan_changed(); + + private: + QComboBox *comboBox; + }; + + class GTL_GUI_EXPORT spec_meas_param_widget : public QWidget + { + Q_OBJECT + public: + spec_meas_param_widget(QWidget *parent=nullptr) : + QWidget(parent) + { + comboBox = new QComboBox; + QMetaEnum meta_enum = QMetaEnum::fromType(); + for(int i = 0; i < meta_enum.keyCount(); i++) + comboBox->addItem(meta_enum.valueToKey(i)); + comboBox->setContentsMargins(0,0,0,0); + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(comboBox); + + connect(comboBox, &QComboBox::currentIndexChanged, [=]{emit param_changed();}); + } + + int currentIndex() + { + return comboBox->currentIndex(); + } + + void setCurrentIndex(int index) + { + comboBox->setCurrentIndex(index); + return; + } + + private: + QComboBox *comboBox; + + signals: + void param_changed(); + }; + + class GTL_GUI_EXPORT spec_meas_delegate : public QStyledItemDelegate + { + Q_OBJECT + + public: + explicit spec_meas_delegate(QObject *parent = nullptr, QAbstractItemModel *channels = nullptr); + + QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; + void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + + private: + QAbstractItemModel *_channels = nullptr; + }; + + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_SPEC_MEAS_DELEGATE_H diff --git a/gui/gtl_gui_spec_meas_model.cpp b/gui/gtl_gui_spec_meas_model.cpp new file mode 100644 index 0000000..3d1c6a8 --- /dev/null +++ b/gui/gtl_gui_spec_meas_model.cpp @@ -0,0 +1,197 @@ +#include +#include "core/gtl_selection_data_model.h" +#include "gtl_gui_spec_meas_model.h" + +namespace gtl { + namespace gui { + + spec_meas_model::spec_meas_model(QObject *parent, QAbstractItemModel *channels) : + QAbstractTableModel(parent), + _channels(channels) + { + _measures = SpecMeasParamsListPtr(new QListSpecMeasParams); + } + + QVariant spec_meas_model::headerData(int section, Qt::Orientation orientation, int role) const + { + if(role != Qt::DisplayRole) + return QVariant(); + + if(orientation == Qt::Vertical) + return section; + + switch(section) { + case CHAN: + return tr("channel"); + case PARAM: + return tr("parameter"); + case VALUE: + return tr("value"); + } + + return QVariant(); + } + + int spec_meas_model::rowCount(const QModelIndex &parent) const + { + if (parent.isValid()) + return 0; + + if(_measures) + return _measures->count(); + return 0; + } + + int spec_meas_model::columnCount(const QModelIndex &parent) const + { + if (parent.isValid()) + return 0; + + return VALUE + 1; + } + + QVariant spec_meas_model::data(const QModelIndex &index, int role) const + { + if (!index.isValid()) + return QVariant(); + + int col = index.column(); + int row = index.row(); + + if(row >= _measures->size()) + return QVariant(); + + switch (role){ + case Qt::DisplayRole: + if(col == CHAN) + return _measures->at(row).chan; + else if(col == PARAM) + return static_cast(_measures->at(row).type); + else if(col == VALUE) + return _measures->at(row).value; + break; + case CustomRoles::IdxRole: + { + selection_list* sel = static_cast(_channels); + for(int i = 0; i < sel->size(); i++) + if(sel->at(i) == _measures->at(row).ad) + return i; + return QVariant(); + } + break; + case Qt::BackgroundRole: + if(col == VALUE) + return QBrush(QColor(127,255,127,255)); + default: + return QVariant(); + } + + return QVariant(); + } + + bool spec_meas_model::setData(const QModelIndex &index, const QVariant &value, int role) + { + if(data(index, role) != value) + { + int col = index.column(); + int row = index.row(); + + if(row >= _measures->size() || col < 0) + return false; + + math::spec_meas::params param = _measures->at(row); + bool param_flag = true; + switch (role){ + case Qt::EditRole: + if(col == CHAN) + param.chan = value.toString(); + else if(col == PARAM) + param.type = static_cast(value.toInt()); + else if(col == VALUE) + { + param.value = value.toDouble(); + param_flag = false; + } + break; + case CustomRoles::IdxRole: + { + selection_list* chs = static_cast(_channels); + if(chs->size() && chs->size() > value.toInt()) param.ad = chs->at(value.toInt()); + else param.ad = nullptr; + } + break; + default: + return false; + } + + _measures->replace(row, param); + if(param_flag) + emit measure_changed(row); + emit dataChanged(index, index, {role}); + return true; + } + return false; + } + + Qt::ItemFlags spec_meas_model::flags(const QModelIndex &index) const + { + if (!index.isValid()) + return Qt::NoItemFlags; + + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } + + bool spec_meas_model::insertRows(int row, int count, const QModelIndex &parent) + { + if(static_cast(_channels)->size()) + { + beginInsertRows(parent, row, row + count - 1); + math::spec_meas::params param; + param.chan = _channels->index(0, 0, QModelIndex()).data(Qt::DisplayRole).toString(); + for(int i = 0; i < count; i++) + { + param.id = QUuid::createUuid().data1; + param.ad = static_cast(_channels)->at(0); + _measures->insert(row, param); + } + endInsertRows(); + emit measure_changed(row); + return true; + } + else + return false; + } + + bool spec_meas_model::removeRows(int row, int count, const QModelIndex &parent) + { + if(_measures->count() && row >= 0 && _measures->count() >= (row + count)) + { + beginRemoveRows(parent, row, row + count - 1); + _measures->remove(row,count); + endRemoveRows(); + emit measure_changed(row); + return true; + } + return false; + } + + SpecMeasParamsListPtr spec_meas_model::measures() + { + return _measures; + } + + QAbstractItemModel *spec_meas_model::channels() + { + return _channels; + } + + void spec_meas_model::all_done() + { + QModelIndex top_left = index(0,VALUE, QModelIndex()); + QModelIndex bottom_right = index(_measures->count(), VALUE, QModelIndex()); + emit dataChanged(top_left, bottom_right, {Qt::DisplayRole}); + } + + } // namespace gui +} // namespace gtl + diff --git a/gui/gtl_gui_spec_meas_model.h b/gui/gtl_gui_spec_meas_model.h new file mode 100644 index 0000000..95c2c5a --- /dev/null +++ b/gui/gtl_gui_spec_meas_model.h @@ -0,0 +1,62 @@ + +#ifndef GTL_GUI_SPEC_MEAS_MODEL_H +#define GTL_GUI_SPEC_MEAS_MODEL_H + +#include +#include + +#include "gui_global.h" +#include "math/gtl_math_spec_meas.h" + +namespace gtl { + namespace gui { + + class GTL_GUI_EXPORT spec_meas_model : public QAbstractTableModel + { + Q_OBJECT + + public: + + enum CustomRoles{ + IdxRole = Qt::UserRole + }; + + enum Column { CHAN = 0, PARAM, VALUE, LAST }; + + explicit spec_meas_model(QObject *parent = nullptr, QAbstractItemModel *channels = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // Editable: + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + + SpecMeasParamsListPtr measures(); + QAbstractItemModel* channels(); + void all_done(); + + private: + + SpecMeasParamsListPtr _measures; + QAbstractItemModel* _channels; + + signals: + void measure_changed(int idx); + }; + + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_SPEC_MEAS_MODEL_H diff --git a/gui/gtl_gui_spec_series.cpp b/gui/gtl_gui_spec_series.cpp new file mode 100644 index 0000000..7c016a2 --- /dev/null +++ b/gui/gtl_gui_spec_series.cpp @@ -0,0 +1,185 @@ +#include "gtl_gui_spec_series.h" +#include "gui/spgr/gtl_gui_spgr_widget.h" + +namespace gtl +{ + namespace gui + { + spec_series::spec_series(bool is_updating, gtl::math::spec::types type, gtl::analog_data* ai, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y) + : chart_series(ai, axis_x, axis_y) + { + _is_updating = is_updating; + + _spec = new gtl::math::spec(type, ai); + _measures = SpecMeasParamsListPtr(new QListSpecMeasParams); + + connect(_spec, >l::math::spec::changed, this, &spec_series::update); + connect(_spec, >l::math::spec::initialized, this, &spec_series::update); + connect(_spec, >l::math::spec::frequency_changed, this, &spec_series::initialized); + + connect(axis_x, &::chart::axis_horz::signal_range_changed, this, &spec_series::measure); + } + + spec_series::~spec_series() + { + _spec->disconnect(); + axis_x()->disconnect(); + + if(_spgr) delete _spgr; + _spgr = nullptr; + delete _spec; + } + + qreal spec_series::frequency() const + { + return _spec->frequency(); + } + + qreal spec_series::resolution() const + { + return _spec->resolution(); + } + + int spec_series::window() const + { + return static_cast(_spec->window()); + } + + int spec_series::lines() const + { + return _spec->lines(); + } + + int spec_series::average() const + { + return _spec->average(); + } + + qreal spec_series::overlap() const + { + return _spec-> overlap(); + } + + int spec_series::unit() const + { + return static_cast(_spec->unit()); + } + + void spec_series::set_frequency(qreal value) + { + _spec->set_frequency(value); + } + + void spec_series::set_resolution(qreal value) + { + _spec->set_resolution(value); + } + + void spec_series::set_window(int value) + { + _spec->set_window(static_cast(value)); + } + + void spec_series::set_lines(int value) + { + _spec->set_lines(value); + } + + void spec_series::set_average(int value) + { + _spec->set_average(value); + } + + void spec_series::set_overlap(qreal value) + { + _spec->set_overlap(value); + } + + void spec_series::set_unit(int value) + { + _spec->set_unit(static_cast(value)); + } + + void spec_series::set_measures(spec_meas_model* model) + { + if(!model) + return; + _measures->clear(); + for(int i = 0; i < model->measures()->count(); i++) + if(model->measures()->at(i).ad == _ad) + _measures->append(model->measures()->at(i)); + measure(); + } + + gtl::gui::spgr::widget* spec_series::create_spgr(QWidget *parent) + { + _spgr = new gtl::gui::spgr::widget(parent, _spec); + + return _spgr; + } + + void spec_series::set_type(math::spec::types type) + { + _spec->set_type(type); + } + + void spec_series::update() + { + set_y(&(*_spec)[0], _spec->size(), _spec->resolution()); + + measure(); + + if(_spgr && _spgr->isHidden()) + { + delete _spgr; + _spgr = nullptr; + } + emit data_changed(); + } + + void spec_series::measure() + { + if(!_measures) + return; + + if(!_measures->count()) + return; + + if(_spec->empty()) + return; + + _ad->lock_device(); + + int meas_cnt = 0; + std::vector::iterator l = _spec->begin() + axis_x()->min()/_spec->resolution(); + std::vector::iterator r = _spec->begin() + axis_x()->max()/_spec->resolution(); + for(int i = 0; i < _measures->count(); i++) + { + if(_measures->at(i).ad == _ad) + { + meas_cnt++; + math::spec_meas::params *p = const_cast(&_measures->at(i)); + switch (p->type) + { + case math::spec_meas::types::rms: + p->value = math::spec_meas::rms(l, r); + break; + case math::spec_meas::types::max: + p->value = math::spec_meas::max(l, r); + break; + case math::spec_meas::types::freq_max: + p->value = math::spec_meas::freq_max(l, r, _spec->resolution()); + break; + default: + break; + } + } + } + + if(meas_cnt) + emit measures_changed(_measures); + + _ad->unlock_device(); + } + } +} diff --git a/gui/gtl_gui_spec_series.h b/gui/gtl_gui_spec_series.h new file mode 100644 index 0000000..26e4611 --- /dev/null +++ b/gui/gtl_gui_spec_series.h @@ -0,0 +1,58 @@ +#ifndef SPEC_SERIES_H +#define SPEC_SERIES_H + +#include "spgr/gtl_gui_spgr_widget.h" +#include "math/gtl_math_spec.h" +#include "gtl_gui_chart_series.h" +#include "gtl_gui_spec_meas_model.h" + +namespace gtl +{ + namespace gui + { + class spec_series : public gtl::gui::chart_series + { + Q_OBJECT + public: + spec_series(bool is_updating, gtl::math::spec::types type, gtl::analog_data* ai, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y); + ~spec_series(); + + qreal frequency() const; + qreal resolution() const; + int window() const; + int lines() const; + int average() const; + qreal overlap() const; + int unit() const; + + void set_frequency(qreal value); + void set_resolution(qreal value); + void set_window(int value); + void set_lines(int value); + void set_average(int value); + void set_overlap(qreal value); + void set_unit(int value); + void set_measures(spec_meas_model *model); + + gtl::gui::spgr::widget* create_spgr(QWidget *parent); + gtl::gui::spgr::widget* spgr(); + + void set_type(gtl::math::spec::types type); + + protected: + gtl::math::spec* _spec; + gtl::gui::spgr::widget* _spgr = nullptr; + SpecMeasParamsListPtr _measures = nullptr; + void measure(); + + public slots: + void update(); + + signals: + void initialized(); + void measures_changed(SpecMeasParamsListPtr meas); + }; + } +} + +#endif // SPEC_SERIES_H diff --git a/gui/gtl_gui_spec_widget.cpp b/gui/gtl_gui_spec_widget.cpp new file mode 100644 index 0000000..b7bdac8 --- /dev/null +++ b/gui/gtl_gui_spec_widget.cpp @@ -0,0 +1,275 @@ +#include "gtl_gui_spec_widget.h" +#include "gui/ui_gtl_gui_spec_widget.h" +#include "gui/gtl_gui_chart_widget.h" +#include "gui/gtl_gui_spec_meas_model.h" +#include "gui/gtl_gui_spec_meas_delegate.h" + +#include "gui/gtl_gui_chart_single_markers_view.h" +#include "gui/spec/gtl_gui_spec_band_markers_view.h" +#include "gui/spec/gtl_gui_spec_harm_markers_view.h" + +namespace gtl +{ + namespace gui + { + spec_widget::spec_widget(QWidget *parent, gtl::math::spec::types type, gtl::data_model* model) : + QWidget(parent), + ui(new Ui::spec_widget) + { + ui->setupUi(this); + + _selection_data_model = new gtl::selection_data_model(this); + _selection_data_model->setSourceModel(model); + + ui->signals_->setModel(_selection_data_model); + ui->signals_->expandAll(); + +// ui->signals_->setContextMenuPolicy(Qt::CustomContextMenu); +// connect(ui->signals_, &QWidget::customContextMenuRequested, this, &spec_widget::menu_request); +// _spectrogramm_action = new QAction(tr("spectrogramm"), this); +// connect(_spectrogramm_action, &QAction::triggered, this, &spec_widget::spectrogramm); + + _chart = new gtl::gui::spec_chart(type, this); + gtl::gui::chart_widget *chart_widget = new gtl::gui::chart_widget(_chart, this); + ui->splitter->insertWidget(0, chart_widget); + + connect(_selection_data_model, >l::selection_data_model::selected, _chart, >l::gui::chart::add_ad); + connect(_selection_data_model, >l::selection_data_model::deselected, _chart, >l::gui::chart::remove_ad); + + ui->frequency->disconnect(); + ui->frequency->setMaximum(100000); + ui->frequency->setValue(_chart->frequency()); + connect(ui->frequency, &QDoubleSpinBox::valueChanged, _chart, >l::gui::spec_chart::set_frequency); + + ui->resolution->disconnect(); + ui->resolution->setMaximum(100000); + ui->resolution->setValue(_chart->resolution()); + connect(ui->resolution, &QDoubleSpinBox::valueChanged, _chart, >l::gui::spec_chart::set_resolution); + + ui->lines->disconnect(); + ui->lines->setMaximum(100000); + ui->lines->setValue(_chart->lines()); + connect(ui->lines, &QDoubleSpinBox::valueChanged, _chart, >l::gui::spec_chart::set_lines); + + QMetaEnum meta_enum = QMetaEnum::fromType(); + ui->window->disconnect(); + ui->window->clear(); + for(int i = 0; i < meta_enum.keyCount(); i++) + { + ui->window->addItem(meta_enum.valueToKey(i)); + } +// ui->window-> addItems(_chart->windows_list()); + ui->window->setCurrentIndex(_chart->window()); + connect(ui->window, &QComboBox::currentIndexChanged, _chart, >l::gui::spec_chart::set_window); + + meta_enum = QMetaEnum::fromType(); + ui->type->disconnect(); + ui->type->clear(); + for(int i = 0; i < meta_enum.keyCount() - 1; i++) + ui->type->addItem(meta_enum.valueToKey(i)); + ui->type->setCurrentIndex(_chart->type()); + connect(ui->type, &QComboBox::currentIndexChanged, _chart, >l::gui::spec_chart::set_type); + + + ui->average->disconnect(); + ui->average->setMaximum(100); + ui->average->setValue(_chart->average()); + connect(ui->average, &QDoubleSpinBox::valueChanged, _chart, >l::gui::spec_chart::set_average); + + ui->overlap->disconnect(); + ui->overlap->setMaximum(99); + ui->overlap->setValue(_chart->overlap()); + connect(ui->overlap, &QDoubleSpinBox::valueChanged, _chart, >l::gui::spec_chart::set_overlap); + + ui->unit->disconnect(); + ui->unit->clear(); + ui->unit->addItem("unit"); + ui->unit->addItem("db"); +// ui->unit-> addItems(_chart->units_list()); + ui->unit->setCurrentIndex(_chart->unit()); + connect(ui->unit, &QComboBox::currentIndexChanged, _chart, >l::gui::spec_chart::set_unit); + + connect(_chart, >l::gui::spec_chart::frequency_changed, this, >l::gui::spec_widget::update_parameters); + connect(_chart, >l::gui::spec_chart::resolution_changed, this, >l::gui::spec_widget::update_parameters); + connect(_chart, >l::gui::spec_chart::lines_changed, this, >l::gui::spec_widget::update_parameters); + connect(_chart, >l::gui::spec_chart::window_changed, this, >l::gui::spec_widget::update_parameters); + + ui->y_mode->setChecked(_chart->is_axis_y_multi()); + connect(ui->y_mode, &QCheckBox::toggled, _chart, >l::gui::chart::set_axis_y_mode); + connect(_chart, >l::gui::chart::axis_y_mode_changed, ui->y_mode, &QCheckBox::setChecked); + + ui->x_log->setVisible(false); + connect(ui->x_log, &QCheckBox::toggled, _chart, >l::gui::spec_chart::set_x_log); + + gui::spec_meas_model *meas_model = new spec_meas_model(this, _selection_data_model->selection()); + _chart->set_measures(meas_model); + gui::spec_meas_delegate *meas_delegate = new spec_meas_delegate(this, meas_model->channels()); + _meas_widget = new gtl::gui::meas_widget(this, meas_model, meas_delegate); + ui->tab_parameters->setVisible(false); + ui->tab_parameters->addTab(_meas_widget, tr("Meas Parameters")); + connect(ui->meas_check, &QCheckBox::toggled, this, &spec_widget::toogle_meas); + connect(meas_model, &spec_meas_model::measure_changed, [=]{ _chart->set_measures(meas_model); }); + +// connect(parent, &QObject::destroyed, [=]{ +// delete _chart; +// _chart = nullptr; +// }); + + ui->tab_parameters->addTab(new chart_single_markers_view(_chart->single_markers(), this), "Single markers"); + ui->tab_parameters->addTab(new spec::band_markers_view(_chart->band_markers(), this), "Band markers"); + ui->tab_parameters->addTab(new spec::harm_markers_view(_chart->harm_markers(), this), "Harmonic markers"); + + } + + + spec_widget::~spec_widget() + { + if(_chart) delete _chart; + if(_meas_widget) delete _meas_widget; + delete ui; + } + + void spec_widget::save(QDomElement &root_element) + { + QDomElement selection_element = root_element.ownerDocument().createElement("selection"); + root_element.appendChild(selection_element); + _selection_data_model->save(selection_element); + + QDomElement options_element = root_element.ownerDocument().createElement("options"); + root_element.appendChild(options_element); + _chart->save(options_element); + + QDomElement splitter_element = root_element.ownerDocument().createElement("splitter"); + root_element.appendChild(splitter_element); + splitter_element.setAttribute("state", QString(ui->splitter->saveState().toBase64())); + } + + void spec_widget::load(const QDomElement &root_element) + { + QDomElement options_element = root_element.firstChildElement("options"); + _chart->load(options_element); + + update_parameters(); + + QDomElement selection_element = root_element.firstChildElement("selection"); + _selection_data_model->load(selection_element); + + + + QDomElement splitter_element = root_element.firstChildElement("splitter"); + ui->splitter->restoreState(QByteArray::fromBase64(splitter_element.attribute("state", "").toUtf8())); + } + + data_model_node *spec_widget::active_node() + { + QModelIndex index = ui->signals_->currentIndex(); + + if(index.isValid() && (index.model()->data(index, Qt::CheckStateRole) == Qt::Checked)) + return static_cast(index.internalPointer()); + + return nullptr; + } + + spgr::widget *spec_widget::create_spgr(QWidget *parent) + { + return create_spgr(parent, active_node()); + } + + spgr::widget *spec_widget::create_spgr(QWidget *parent, data_model_node *node) + { + return _chart->create_spgr(parent, node); + } + + void spec_widget::set_type(math::spec::types type) + { + _chart->set_type(type); + } + + void spec_widget::update_parameters() + { + ui->type->blockSignals(true); + ui->frequency->blockSignals(true); + ui->resolution->blockSignals(true); + ui->lines->blockSignals(true); + ui->window->blockSignals(true); + ui->average->blockSignals(true); + ui->overlap->blockSignals(true); + ui->unit->blockSignals(true); + + if( ui->type->currentIndex() != _chart->type() ) + ui->type->setCurrentIndex(_chart->type()); + + if( ui->frequency->value() != _chart->frequency() ) + ui->frequency->setValue(_chart->frequency()); + + if( ui->resolution->value() != _chart->resolution() ) + ui->resolution->setValue(_chart->resolution()); + + if( ui->lines->value() != (int) _chart->lines() ) + ui->lines->setValue(_chart->lines()); + + if( ui->window->currentIndex() != _chart->window() ) + ui->window->setCurrentIndex(_chart->window()); + + if( ui->average->value() != (int) _chart->average() ) + ui->average->setValue(_chart->average()); + + if( ui->overlap->value() != (int) _chart->overlap() ) + ui->overlap->setValue(_chart->overlap()); + + if( ui->unit->currentIndex() != _chart->unit() ) + ui->unit->setCurrentIndex(_chart->unit()); + + + ui->type->blockSignals(false); + ui->frequency->blockSignals(false); + ui->resolution->blockSignals(false); + ui->lines->blockSignals(false); + ui->window->blockSignals(false); + ui->average->blockSignals(false); + ui->overlap->blockSignals(false); + ui->unit->blockSignals(false); + } + + void spec_widget::toogle_meas(bool enabled) + { + ui->tab_parameters->setVisible(enabled); + /* + if(_meas_widget) + { + if(enabled) + { + ui->meas_layout->addWidget(_meas_widget); + _meas_widget->show(); + } + else + { + ui->meas_layout->removeWidget(_meas_widget); + _meas_widget->hide(); + } + } + + return; + */ + } + +// void spec_widget::menu_request(const QPoint &pos) +// { +// QModelIndex index = ui->signals_->indexAt(pos); + +// if(index.model()->data(index, Qt::CheckStateRole) == Qt::Checked) +// { +// _popup_node = static_cast(index.internalPointer()); +// QMenu menu; +// menu.addAction(_spectrogramm_action); +// menu.exec(ui->signals_->viewport()->mapToGlobal(pos)); +// } +// } + +// void spec_widget::spectrogramm() +// { +// gtl::gui::spgr_widget* spgr = _chart->create_spgr(this, _popup_node); +// spgr->show(); +// } + } +} diff --git a/gui/gtl_gui_spec_widget.h b/gui/gtl_gui_spec_widget.h new file mode 100644 index 0000000..44c511f --- /dev/null +++ b/gui/gtl_gui_spec_widget.h @@ -0,0 +1,58 @@ +#ifndef GTL_GUI_SPEC_WIDGET_H +#define GTL_GUI_SPEC_WIDGET_H + +#include + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" + +#include "gui/gtl_gui_spec_chart.h" +#include "gui/gtl_gui_meas_widget.h" + +#include "gui_global.h" + +namespace Ui { +class spec_widget; +} + +namespace gtl +{ + namespace gui + { + class GTL_GUI_EXPORT spec_widget : public QWidget + { + Q_OBJECT + + public: + explicit spec_widget(QWidget *parent, gtl::math::spec::types type, gtl::data_model* model); + ~spec_widget(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + gtl::data_model_node *active_node(); + gtl::gui::spgr::widget* create_spgr(QWidget *parent); + gtl::gui::spgr::widget* create_spgr(QWidget *parent, gtl::data_model_node* node); + + void set_type(gtl::math::spec::types type); + + private: + Ui::spec_widget *ui; + gtl::selection_data_model* _selection_data_model; + gtl::gui::spec_chart* _chart; + gtl::gui::meas_widget* _meas_widget = nullptr; + +// QAction* _spectrogramm_action; +// gtl::data_model_node* _popup_node; + + void update_parameters(); + void toogle_meas(bool enable); + +// private slots: +// void spectrogramm(); +// void menu_request(const QPoint &pos); + }; + } +} + +#endif // GTL_GUI_SPEC_WIDGET_H diff --git a/gui/gtl_gui_spec_widget.ui b/gui/gtl_gui_spec_widget.ui new file mode 100644 index 0000000..4e5a832 --- /dev/null +++ b/gui/gtl_gui_spec_widget.ui @@ -0,0 +1,214 @@ + + + spec_widget + + + + 0 + 0 + 400 + 394 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Type + + + + + + + Frequency + + + + + + + + + + Resolution + + + + + + + + + + Lines + + + + + + + + + + Window + + + + + + + Average + + + + + + + 1.000000000000000 + + + 100.000000000000000 + + + + + + + Overlap + + + + + + + 100.000000000000000 + + + + + + + Unit + + + + + + + + + + + + + + + + + + multi axis y + + + + + + + log x + + + + + + + parameters + + + + + + + + + + + + + + + diff --git a/gui/gui.pro b/gui/gui.pro new file mode 100644 index 0000000..2c66850 --- /dev/null +++ b/gui/gui.pro @@ -0,0 +1,285 @@ +#QT -= gui +QT += gui widgets qml xml multimedia quickwidgets + +TEMPLATE = lib +DEFINES += GTL_GUI_LIB +TARGET = gtl_gui + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + config/gtl_gui_config_filter_response_chart.cpp \ + config/gtl_gui_config_filter_response_chart_series.cpp \ + config/gtl_gui_config_filter_response_chart_series_axis_y.cpp \ + config/gtl_gui_config_hardware_dialog.cpp \ + config/gtl_gui_config_hw_widget.cpp \ + config/gtl_gui_config_hw_widget_audio.cpp \ + config/gtl_gui_config_hw_widget_dac.cpp \ + config/gtl_gui_config_hw_widget_generator.cpp \ + config/gtl_gui_config_widget.cpp \ + config/gtl_gui_config_widget_analog.cpp \ + config/gtl_gui_config_widget_device.cpp \ + config/gtl_gui_config_widget_node.cpp \ + config/gtl_gui_config_widget_player.cpp \ + config/gtl_gui_config_widget_player_files.cpp \ + gtl_gui.cpp \ + gtl_gui_apfc_chart.cpp \ + gtl_gui_apfc_series.cpp \ + gtl_gui_apfc_widget.cpp \ + gtl_gui_chart.cpp \ + gtl_gui_chart_axis_x.cpp \ + gtl_gui_chart_axis_y.cpp \ + gtl_gui_chart_line.cpp \ + gtl_gui_chart_marker.cpp \ + gtl_gui_chart_marker_color_delegate.cpp \ + gtl_gui_chart_marker_kill_delegate.cpp \ + gtl_gui_chart_marker_line.cpp \ + gtl_gui_chart_markers.cpp \ + gtl_gui_chart_markers_item_delegate.cpp \ + gtl_gui_chart_markers_model.cpp \ + gtl_gui_chart_markers_view.cpp \ + gtl_gui_chart_series.cpp \ + gtl_gui_chart_single_markers.cpp \ + gtl_gui_chart_single_markers_view.cpp \ + gtl_gui_chart_widget.cpp \ + gtl_gui_color_box.cpp \ + gtl_gui_cross_spec_chart.cpp \ + gtl_gui_cross_spec_series.cpp \ + gtl_gui_cross_spec_widget.cpp \ + gtl_gui_dock.cpp \ + gtl_gui_dock_title.cpp \ + gtl_gui_group_options_widget.cpp \ + gtl_gui_log_viewer.cpp \ + gtl_gui_meas_widget.cpp \ + gtl_gui_options_dialog.cpp \ + gtl_gui_options_widget.cpp \ + gtl_gui_octv_chart.cpp \ + gtl_gui_octv_series.cpp \ + gtl_gui_octv_widget.cpp \ + gtl_gui_record_chart.cpp \ + gtl_gui_record_chart_axis_x.cpp \ + gtl_gui_record_chart_series.cpp \ + gtl_gui_recorder_widget.cpp \ + gtl_gui_scr_editor.cpp \ + gtl_gui_scr_options_widget.cpp \ + gtl_gui_scr_qml_widget.cpp \ + gtl_gui_scr_quick_widget.cpp \ + gtl_gui_scr_specs_chart.cpp \ + gtl_gui_scr_specs_series.cpp \ + gtl_gui_scr_specs_widget.cpp \ + gtl_gui_scr_table.cpp \ + gtl_gui_scr_widget.cpp \ + gtl_gui_spec_chart.cpp \ + gtl_gui_spec_meas_delegate.cpp \ + gtl_gui_spec_meas_model.cpp \ + gtl_gui_spec_series.cpp \ + gtl_gui_spec_widget.cpp \ + osc/gtl_gui_osc_chart.cpp \ + osc/gtl_gui_osc_meas_delegate.cpp \ + osc/gtl_gui_osc_meas_model.cpp \ + osc/gtl_gui_osc_series.cpp \ + osc/gtl_gui_osc_widget.cpp \ + orbit/gtl_gui_orbit_chart.cpp \ + orbit/gtl_gui_orbit_series.cpp \ + orbit/gtl_gui_orbit_widget.cpp \ + player/gtl_gui_player_chart.cpp \ + player/gtl_gui_player_chart_position.cpp \ + player/gtl_gui_player_chart_position_line.cpp \ + player/gtl_gui_player_chart_range.cpp \ + player/gtl_gui_player_chart_range_body.cpp \ + player/gtl_gui_player_chart_range_line.cpp \ + player/gtl_gui_player_resampling_dialog.cpp \ + spec/gtl_gui_spec_band_marker.cpp \ + spec/gtl_gui_spec_band_marker_body.cpp \ + spec/gtl_gui_spec_band_marker_type_delegate.cpp \ + spec/gtl_gui_spec_band_markers.cpp \ + spec/gtl_gui_spec_band_markers_view.cpp \ + spec/gtl_gui_spec_harm_marker.cpp \ + spec/gtl_gui_spec_harm_markers.cpp \ + spec/gtl_gui_spec_harm_markers_view.cpp \ + spgr/gtl_gui_spgr_chart.cpp \ + spgr/gtl_gui_spgr_chart_marker.cpp \ + spgr/gtl_gui_spgr_line.cpp \ + spgr/gtl_gui_spgr_series.cpp \ + spgr/gtl_gui_spgr_widget.cpp + +HEADERS += \ + config/gtl_gui_config_filter_response_chart.h \ + config/gtl_gui_config_filter_response_chart_series.h \ + config/gtl_gui_config_filter_response_chart_series_axis_y.h \ + config/gtl_gui_config_hardware_dialog.h \ + config/gtl_gui_config_hw_widget.h \ + config/gtl_gui_config_hw_widget_audio.h \ + config/gtl_gui_config_hw_widget_dac.h \ + config/gtl_gui_config_hw_widget_generator.h \ + config/gtl_gui_config_widget.h \ + config/gtl_gui_config_widget_analog.h \ + config/gtl_gui_config_widget_device.h \ + config/gtl_gui_config_widget_node.h \ + config/gtl_gui_config_widget_player.h \ + config/gtl_gui_config_widget_player_files.h \ + gtl_gui_apfc_chart.h \ + gtl_gui_apfc_series.h \ + gtl_gui_apfc_widget.h \ + gtl_gui_chart.h \ + gtl_gui_chart_axis_x.h \ + gtl_gui_chart_axis_y.h \ + gtl_gui_chart_line.h \ + gtl_gui_chart_marker.h \ + gtl_gui_chart_marker_color_delegate.h \ + gtl_gui_chart_marker_kill_delegate.h \ + gtl_gui_chart_marker_line.h \ + gtl_gui_chart_markers.h \ + gtl_gui_chart_markers_item_delegate.h \ + gtl_gui_chart_markers_model.h \ + gtl_gui_chart_markers_view.h \ + gtl_gui_chart_series.h \ + gtl_gui_chart_single_markers.h \ + gtl_gui_chart_single_markers_view.h \ + gtl_gui_chart_widget.h \ + gtl_gui_color_box.h \ + gtl_gui_cross_spec_chart.h \ + gtl_gui_cross_spec_series.h \ + gtl_gui_cross_spec_widget.h \ + gtl_gui_dock.h \ + gtl_gui_dock_title.h \ + gtl_gui_group_options_widget.h \ + gtl_gui_log_viewer.h \ + gtl_gui_meas_widget.h \ + gtl_gui_options_dialog.h \ + gtl_gui_options_widget.h \ + gtl_gui_octv_chart.h \ + gtl_gui_octv_series.h \ + gtl_gui_octv_widget.h \ + gtl_gui_record_chart.h \ + gtl_gui_record_chart_axis_x.h \ + gtl_gui_record_chart_series.h \ + gtl_gui_recorder_widget.h \ + gtl_gui_scr_editor.h \ + gtl_gui_scr_options_widget.h \ + gtl_gui_scr_qml_widget.h \ + gtl_gui_scr_quick_widget.h \ + gtl_gui_scr_specs_chart.h \ + gtl_gui_scr_specs_series.h \ + gtl_gui_scr_specs_widget.h \ + gtl_gui_scr_table.h \ + gtl_gui_scr_widget.h \ + gtl_gui_spec_chart.h \ + gtl_gui_spec_meas_delegate.h \ + gtl_gui_spec_meas_model.h \ + gtl_gui_spec_series.h \ + gtl_gui_spec_widget.h \ + gui_global.h \ + gtl_gui.h \ + osc/gtl_gui_osc_chart.h \ + osc/gtl_gui_osc_meas_delegate.h \ + osc/gtl_gui_osc_meas_model.h \ + osc/gtl_gui_osc_series.h \ + osc/gtl_gui_osc_widget.h \ + orbit/gtl_gui_orbit_chart.h \ + orbit/gtl_gui_orbit_series.h \ + orbit/gtl_gui_orbit_widget.h \ + player/gtl_gui_player_chart.h \ + player/gtl_gui_player_chart_position.h \ + player/gtl_gui_player_chart_position_line.h \ + player/gtl_gui_player_chart_range.h \ + player/gtl_gui_player_chart_range_body.h \ + player/gtl_gui_player_chart_range_line.h \ + player/gtl_gui_player_resampling_dialog.h \ + spec/gtl_gui_spec_band_marker.h \ + spec/gtl_gui_spec_band_marker_body.h \ + spec/gtl_gui_spec_band_marker_type_delegate.h \ + spec/gtl_gui_spec_band_markers.h \ + spec/gtl_gui_spec_band_markers_view.h \ + spec/gtl_gui_spec_harm_marker.h \ + spec/gtl_gui_spec_harm_markers.h \ + spec/gtl_gui_spec_harm_markers_view.h \ + spgr/gtl_gui_spgr_chart.h \ + spgr/gtl_gui_spgr_chart_marker.h \ + spgr/gtl_gui_spgr_line.h \ + spgr/gtl_gui_spgr_series.h \ + spgr/gtl_gui_spgr_widget.h + +TRANSLATIONS += \ + gui_ru_RU.ts + +# Default rules for deployment. +unix { + target.path = /usr/lib +} +!isEmpty(target.path): INSTALLS += target + +INCLUDEPATH += $$PWD/.. +DEPENDPATH += $$PWD/.. + +win32:CONFIG(release, debug|release): DESTDIR = ../.output/release +else:win32:CONFIG(debug, debug|release): DESTDIR = ../.output/debug +else:unix: DESTDIR = ../../.ouput + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_core +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_core + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_script +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_script +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_script + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_hw +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_hw +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_script + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_math +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_math +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_math + + +win32:CONFIG(release, debug|release): LIBS += -L$$(COMPONENTSPATH)/qt6/.output/release/ -lchart +else:win32:CONFIG(debug, debug|release): LIBS += -L$$(COMPONENTSPATH)/qt6/.output/debug/ -lchart +else:unix: LIBS += -L$$(COMPONENTSPATH)/qt6/.output/ -lchart + +INCLUDEPATH += $$(COMPONENTSPATH)/qt6 +DEPENDPATH += $$(COMPONENTSPATH)/qt6 + +INCLUDEPATH += $$(COMPONENTSPATH)/!c++ +DEPENDPATH += $$(COMPONENTSPATH)!c++ + +RESOURCES += $$(COMPONENTSPATH)/qt6/chart/Resources/chart.qrc \ + gui.qrc + +INCLUDEPATH += $$(DSPFILTERSPATH)/include +DEPENDPATH += $$(DSPFILTERSPATH)/include +INCLUDEPATH += $$(FFTWPATH) + +FORMS += \ + config/gtl_gui_config_hardware_dialog.ui \ + config/gtl_gui_config_hw_widget_audio.ui \ + config/gtl_gui_config_hw_widget_dac.ui \ + config/gtl_gui_config_hw_widget_generator.ui \ + config/gtl_gui_config_widget.ui \ + config/gtl_gui_config_widget_analog.ui \ + config/gtl_gui_config_widget_device.ui \ + config/gtl_gui_config_widget_player.ui \ + gtl_gui_apfc_widget.ui \ + gtl_gui_cross_spec_widget.ui \ + gtl_gui_dock_title.ui \ + gtl_gui_meas_widget.ui \ + gtl_gui_meas_widget.ui \ + gtl_gui_options_dialog.ui \ + gtl_gui_options_widget.ui \ + gtl_gui_octv_widget.ui \ + gtl_gui_recorder_widget.ui \ + gtl_gui_scr_options_widget.ui \ + gtl_gui_scr_qml_widget.ui \ + gtl_gui_scr_specs_widget.ui \ + gtl_gui_scr_table.ui \ + gtl_gui_scr_widget.ui \ + gtl_gui_spec_widget.ui \ + osc/gtl_gui_osc_widget.ui \ + player/gtl_gui_player_resampling_dialog.ui \ + orbit/gtl_gui_orbit_widget.ui \ + spgr/gtl_gui_spgr_widget.ui + diff --git a/gui/gui.qrc b/gui/gui.qrc new file mode 100644 index 0000000..f5d6182 --- /dev/null +++ b/gui/gui.qrc @@ -0,0 +1,40 @@ + + + resources/decline_16.png + resources/front_16.png + resources/NEXT2.png + resources/pause_24.png + resources/REFRESH.png + + + resources/pause_24.png + resources/play_16.png + resources/stop.png + resources/REFRESH.png + resources/save_32.png + resources/add_range.png + resources/remove_range.png + + + resources/CLOSE_16.png + resources/maximize.png + resources/pin.png + resources/pin_on.png + resources/restore.png + + + + resources/record.png + + + resources/add_24.png + resources/remove_24.png + + + resources/moon.png + resources/sun.png + resources/band_marker.png + resources/single_marker_24.png + resources/harm_marker_24.png + + diff --git a/gui/gui_global.h b/gui/gui_global.h new file mode 100644 index 0000000..d4b5334 --- /dev/null +++ b/gui/gui_global.h @@ -0,0 +1,12 @@ +#ifndef GUI_GLOBAL_H +#define GUI_GLOBAL_H + +#include + +#if defined(GTL_GUI_LIB) +# define GTL_GUI_EXPORT Q_DECL_EXPORT +#else +# define GTL_GUI_EXPORT Q_DECL_IMPORT +#endif + +#endif // GUI_GLOBAL_H diff --git a/gui/gui_ru_RU.ts b/gui/gui_ru_RU.ts new file mode 100644 index 0000000..743f57f --- /dev/null +++ b/gui/gui_ru_RU.ts @@ -0,0 +1,3 @@ + + + diff --git a/gui/orbit/gtl_gui_orbit_chart.cpp b/gui/orbit/gtl_gui_orbit_chart.cpp new file mode 100644 index 0000000..e73553e --- /dev/null +++ b/gui/orbit/gtl_gui_orbit_chart.cpp @@ -0,0 +1,153 @@ +#include "gtl_gui_orbit_chart.h" +#include "gtl_gui_orbit_series.h" + +namespace gtl { + namespace gui { + namespace orbit{ + + chart::chart(QWidget* parent, ::chart::axis_horz *axis_x, ::chart::axis_vert *axis_y) + : gtl::gui::chart(parent, static_cast(axis_x), static_cast(axis_y)) + { + setBackgroundBrush(QBrush(Qt::white)); + + _marker_action_add->disconnect(); + connect(_marker_action_add, &QAction::triggered, this, &chart::add_marker); + + _orbit_menu = new QMenu(this); + _orbit_menu->addAction(_set_theme_action); + + } + + chart::~chart() + { + remove(); + } + + void chart::add(::chart::series::series *s) + { + remove(); + ::chart::widget::add(s); + } + + void chart::add_marker() + { + if(_series.empty()) + return; + + chart_marker* marker = new chart_marker(_series[0], _set_theme_action->isChecked()); + gtl::gui::chart::add(marker); + + connect(marker, &chart_marker::get_nearest_x, this, &chart::get_neares_series_x); + connect(marker, &chart_marker::get_series_data, this, &chart::get_neares_series_y); +// connect(marker, &chart_marker::get_series_values, this, &chart::get_series_values); + connect(marker, &chart_marker::deleting, this, &chart::remove_marker); + + marker->set_pos(_mouse_pos_release); + + _markers->add_marker(marker); + _single_markers->add(marker); + } + + void chart::mouseDoubleClickEvent(QMouseEvent *event) + { + if(event->button() == Qt::LeftButton) + { + if(_series.empty()) + return; + + series* s = static_cast(_series[0]); + s->autoscale(); + } + } + + void chart::contextMenuEvent(QContextMenuEvent *event) + { + _orbit_menu->popup(event->globalPos()); + } + + void chart::get_neares_series_x(qreal &x, chart_line::pos_behaviors behavior) + { + if(_series.empty()) + return; + } + + void chart::get_neares_series_y(qreal x, bool is_widget_pos, QVariantList &data) + { + if(_series.empty()) + return; + + series* s = static_cast(_series[0]); + auto p_begin = s->points_begin(); + auto p_end = s->points_end(); + + + if(std::distance(p_begin, p_end) < 4) + { + data.push_back(QVariant(qQNaN())); + data.push_back(QVariant(QColor(Qt::black))); + return; + } + + auto it = std::lower_bound(p_begin, p_end, QPointF(x, 0), [](const QPointF &p0, const QPointF &p1) + { + return p0.x() < p1.x(); + } + ); + + if(it > p_begin + 1 && it < p_end - 1) + { + qreal x1 = (it-1)->x(); + qreal y1 = (it-1)->y(); + + qreal x2 = it->x(); + qreal y2 = it->y(); + + qreal k = (y2 - y1) / (x2 - x1); + + qreal y = k*(x - x1) + y1; + + data.push_back(QVariant(s->map_to_widget(x, y).y())); + data.push_back(QVariant(s->color())); + } + else + { + data.push_back(QVariant(qQNaN())); + data.push_back(QVariant(QColor(Qt::black))); + } + } + +// void chart::get_series_values(qreal x_min, qreal x_max, int series_idx, QVariantList &data) +// { +// if(_series.empty()) +// return; + +// series* s = static_cast(_series[0]); +// auto p_begin = s->points_begin(); +// auto p_end = s->points_end(); + + +// if(std::distance(p_begin, p_end) < 4) +// return; + +// auto it_min = std::lower_bound(p_begin, p_end, QPointF(x_min, 0), []( +// const QPointF &p0, const QPointF &p1) +// { +// return p0.x() < p1.x(); +// } +// ); + +// auto it_max = std::upper_bound(p_begin, p_end, QPointF(x_max, 0), []( +// const QPointF &p0, const QPointF &p1) +// { +// return p0.x() < p1.x(); +// } +// ); + +// for(auto it = it_min; it != it_max; it++) +// data.push_back(it->y()); + +// } + } + } // namespace gui +} // namespace gtl + diff --git a/gui/orbit/gtl_gui_orbit_chart.h b/gui/orbit/gtl_gui_orbit_chart.h new file mode 100644 index 0000000..4815b99 --- /dev/null +++ b/gui/orbit/gtl_gui_orbit_chart.h @@ -0,0 +1,42 @@ +#ifndef GTL_GUI_ORBIT_CHART_H +#define GTL_GUI_ORBIT_CHART_H + +#include "gui/gtl_gui_chart.h" +#include "gui/gui_global.h" + +namespace gtl { + namespace gui { + namespace orbit{ + + class GTL_GUI_EXPORT chart : public gtl::gui::chart + { + Q_OBJECT + public: + chart(QWidget* parent = nullptr, ::chart::axis_horz *axis_x = nullptr, ::chart::axis_vert *axis_y = nullptr); + ~chart(); + + virtual void add(::chart::series::series *s); + + private: + void add_marker(); + void clear_marker(); + + void mouseDoubleClickEvent(QMouseEvent *event); + virtual void contextMenuEvent(QContextMenuEvent *event) override; + + private: + QMenu* _orbit_menu; + + private slots: + void get_neares_series_x(qreal &x, chart_line::pos_behaviors); + void get_neares_series_y(qreal x, bool is_widget_pos, QVariantList &data); +// void get_series_values(qreal x_min, qreal x_max, int series_idx, QVariantList &data); + + protected: + virtual chart_series* create_series(gtl::analog_data* ai){ return nullptr; } + }; + } + } // namespace gui +} // namespace gt + +#endif // GTL_GUI_ORBIT_CHART_H diff --git a/gui/orbit/gtl_gui_orbit_series.cpp b/gui/orbit/gtl_gui_orbit_series.cpp new file mode 100644 index 0000000..d37091c --- /dev/null +++ b/gui/orbit/gtl_gui_orbit_series.cpp @@ -0,0 +1,390 @@ +#include "orbit/gtl_gui_orbit_series.h" +#include + +namespace gtl { + namespace gui { + + namespace orbit { + + series::series(QWidget* parent, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y) + : ::chart::series::series(axis_x, axis_y), + _x(nullptr), + _y(nullptr), + _time(0.1), + _start(0.), + _stop(_time), + _turns(1), + _is_updating(true), + _max(1.5), + _device(nullptr), + _freq(nullptr), + _samples_x(0), + _samples_y(0), + _size(0) + { + _axis_horz->set_range(-_max, _max, 0); + _axis_vert->set_range(-_max, _max, 0); + + init(); + } + + series::~series() + { + _data_x.clear(); + _data_y.clear(); + _points.clear(); + + if(_freq) + delete _freq; + } + + void series::save(QDomElement &root_element) + { + root_element.setAttribute("time", _time); + root_element.setAttribute("start", _start); + root_element.setAttribute("stop", _stop); + root_element.setAttribute("turns", _turns); + root_element.setAttribute("max", _max); + } + + void series::load(const QDomElement &root_element) + { + _time = root_element.attribute("time", "1").toDouble(); + _start = root_element.attribute("start", "0").toDouble(); + _stop = root_element.attribute("stop", "1").toDouble(); + _turns = root_element.attribute("turns", "1").toDouble(); + _max = root_element.attribute("max", "1").toDouble(); + } + + void series::set_updating(bool value) + { + _is_updating = value; + } + + void series::autoscale() + { + if(_x && _y) + { + prepareGeometryChange(); + + qreal x_max = *std::max_element(_x->begin(), _x->end()); + qreal y_max = *std::max_element(_y->begin(), _y->end()); + + _max = std::max(x_max, y_max); + _max *= 1.5; + + _axis_horz->set_range(-_max, _max, 0); + _axis_vert->set_range(-_max, _max, 0); + } + } + + void series::set_pos(QPointF pos) + { +// _pos = pos; + } + + QString series::name() const + { + if(_x) + return _x->name(); + return ""; + } + + void series::set_tool_tip(QPoint pos) + { + + } + + void series::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + if(!_x || !_y) + return; + + if(_samples_x != _samples_y) + return; + + QRectF boundary = boundingRect(); + painter->setClipRect(_axis_vert->boundingRect()); + painter->setTransform(QTransform().translate( + boundary.left(), + boundary.top() + ) + .scale( + boundary.width() / _axis_horz->range_scaled(), + -boundary.height() / _axis_vert->range_scaled() + ) + .translate( + -_axis_horz->min_scaled(), + - _axis_vert->max_scaled() + ) + ); + + painter->setPen(QPen(QColor(_x->color()), 0)); + int start = _start * _x->get_rate(); + int size = (_stop - _start) * _x->get_rate(); + if( (start + size) <= _points.size()) + painter->drawPolyline(_points.data() + start, size); + else + painter->drawPolyline(_points.data(), _points.size()); + } + + gtl::analog_data *series::x() const + { + return _x; + } + + void series::set_x(gtl::analog_data *value) + { + if (_x == value) + return; + + if(_x) + disconnect(_x, >l::analog_data::data_changed, this, &series::handle_x_changed); + + _x = value; + + if(_freq) + { + delete _freq; + _freq = nullptr; + } + + if(_x) + { + _freq = new gtl::math::freq(_x); + _freq->set_time(_time); + connect(_freq, >l::math::freq::value_changed, [=]{emit freq_changed();}); + + init(); + connect(_x, >l::analog_data::data_changed, this, &series::handle_x_changed); + } + + + emit x_changed(); + } + + gtl::analog_data *series::y() const + { + return _y; + } + + void series::set_y(gtl::analog_data *value) + { + if (_y == value) + return; + + if(_y) + disconnect(_y, >l::analog_data::data_changed, this, &series::handle_y_changed); + + _y = value; + + if(_y) + { + if(_device) + disconnect(_device, >l::device::recieved_data, this, &series::update_series); + _device = static_cast(_y->root()); + + init(); + + connect(_y, >l::analog_data::data_changed, this, &series::handle_y_changed); + if(_device) + connect(_device, >l::device::recieved_data, this, &series::update_series); + } + + emit y_changed(); + } + + qreal series::time() const + { + return _time; + } + + void series::set_time(qreal value) + { + if (qFuzzyCompare(_time, value)) + return; + _time = value; + + if(_freq) + _turns = _time*_freq->value(); + + if(_stop > _time) + _stop = _time; + + if(_start > _time) + _start = 0; + init(); + + emit time_changed(); + } + + qreal series::start() const + { + return _start; + } + + void series::set_start(qreal value) + { + if (qFuzzyCompare(_start, value)) + return; + _start = value; + + if(_start >= _time) + _start = 0; + + emit start_changed(); + } + + qreal series::stop() const + { + return _stop; + } + + void series::set_stop(qreal value) + { + if (qFuzzyCompare(_stop, value)) + return; + _stop = value; + + if(_stop >= _time) + _stop = _time; + + emit stop_changed(); + } + + qreal series::turns() const + { + return _turns; + } + + void series::set_turns(qreal value) + { + if (qFuzzyCompare(_turns, value)) + return; + _turns = value; + + if(_freq) + { + if(_freq->value() > 0) + _time = _turns/_freq->value(); + init(); + } + + emit turns_changed(); + } + + qreal series::freq() const + { + if(_freq) + return _freq->value(); + return 0; + } + + QColor series::color() + { + if(_x) + return _x->color(); + + return QColor(Qt::black); + } + + void series::update_series() + { + if(_samples_x != _samples_y) + { + _data_x.clear(); + _data_y.clear(); + _points.clear(); + + _samples_x = 0; + _samples_y = 0; + + return; + } + + int size = std::min(_data_x.size(), _data_y.size()); + + if((_points.size() + size) > _size) + { + int psize = size + _points.size() - _size; + if(psize > 0 && _points.size() >= psize) + _points.erase(_points.begin(), _points.begin() + psize); + } + + for(int i = 0; i < size; i++) + { + QPointF point(_data_x.at(i), _data_y.at(i)); + _points.push_back(point); + } + + if(size) + { + _data_x.erase(_data_x.begin(), _data_x.begin() + size); + _data_y.erase(_data_y.begin(), _data_y.begin() + size); + } + + prepareGeometryChange(); + } + + void series::set_icolor() + { + + } + + void series::handle_x_changed() + { + _x->lock_device(); + + _samples_x += _x->size(); + + std::copy(_x->begin(), _x->end(), std::back_inserter(_data_x)); + + _x->unlock_device(); + } + + void series::handle_y_changed() + { + _y->lock_device(); + + _samples_y += _y->size(); + + std::copy(_y->begin(), _y->end(), std::back_inserter(_data_y)); + + _y->unlock_device(); + } + + void series::init() + { + if(_x && _y) + { + _x->lock_device(); + _y->lock_device(); + + _samples_x = 0; + _samples_y = 0; + + _data_x.clear(); + _data_y.clear(); + _points.clear(); + + if(_freq) + { + if(_freq->value() > 0) + _turns = _time*_freq->value(); + if(_time > 0) + _freq->set_time(_time); + } + + _size = _x->get_rate()*_time; + + _points.reserve(_size); + + _x->unlock_device(); + _y->unlock_device(); + } + } + + } + } // namespace gui +} // namespace gtl + diff --git a/gui/orbit/gtl_gui_orbit_series.h b/gui/orbit/gtl_gui_orbit_series.h new file mode 100644 index 0000000..60ed75e --- /dev/null +++ b/gui/orbit/gtl_gui_orbit_series.h @@ -0,0 +1,105 @@ +#ifndef GTL_GUI_ORBIT_SERIES_H +#define GTL_GUI_ORBIT_SERIES_H + +#include "chart/series/series.h" +#include "core/gtl_analog_data.h" +#include "core/gtl_device.h" +#include "math/gtl_math_freq.h" +#include "gui/gui_global.h" +#include + +namespace gtl { + namespace gui { + namespace orbit { + + class GTL_GUI_EXPORT series : public ::chart::series::series + { + Q_OBJECT + +// Q_PROPERTY(gtl::analog_data *x READ x WRITE set_x NOTIFY x_changed) +// Q_PROPERTY(gtl::analog_data *y READ y WRITE set_y NOTIFY y_changed) +// Q_PROPERTY(qreal time READ time WRITE set_time NOTIFY time_changed) +// Q_PROPERTY(qreal start READ start WRITE set_start NOTIFY start_changed) +// Q_PROPERTY(qreal stop READ stop WRITE set_stop NOTIFY stop_changed) +// Q_PROPERTY(qreal turns READ turns WRITE set_turns NOTIFY turns_changed) + + public: + series(QWidget* parent, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y); + ~series(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + void set_updating(bool value); +// gtl::analog_data* ad(); + void autoscale(); + void set_pos(QPointF pos); + + virtual QString name() const; + virtual void set_tool_tip(QPoint pos); + + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + virtual bool get_range_y(qreal x_min, qreal x_max, qreal &y_min, qreal &y_max){return true;} + virtual bool get_ranges(qreal &x_min, qreal &x_max, qreal &y_min, qreal &y_max){return true;} + virtual bool get_tool_tip(QPoint pos, QString &text, QRect &rect){return true;} + + gtl::analog_data* x() const; + void set_x(gtl::analog_data *value); + gtl::analog_data* y() const; + void set_y(gtl::analog_data *value); + qreal time() const; + void set_time(qreal value); + qreal start() const; + void set_start(qreal value); + qreal stop() const; + void set_stop(qreal value); + qreal turns() const; + void set_turns(qreal value); + qreal freq() const; + + std::vector::iterator points_begin(){ return _points.begin(); } + std::vector::iterator points_end(){ return _points.end(); } + QColor color(); + + private: + gtl::analog_data* _x; + gtl::analog_data* _y; + qreal _time; + qreal _start; + qreal _stop; + qreal _turns; + + bool _is_updating; + qreal _max; + gtl::device* _device; + gtl::math::freq* _freq; + std::vector _data_x; + std::vector _data_y; + unsigned long _samples_x; + unsigned long _samples_y; + std::vector _points; + int _size; + + private slots: + void update_series(); + void set_icolor(); + void handle_x_changed(); + void handle_y_changed(); + void init(); + + signals: + void x_changed(); + void y_changed(); + void time_changed(); + void start_changed(); + void stop_changed(); + void turns_changed(); + void freq_changed(); + }; + + } // namespace orbit + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_ORBIT_SERIES_H diff --git a/gui/orbit/gtl_gui_orbit_widget.cpp b/gui/orbit/gtl_gui_orbit_widget.cpp new file mode 100644 index 0000000..d88caec --- /dev/null +++ b/gui/orbit/gtl_gui_orbit_widget.cpp @@ -0,0 +1,161 @@ +#include "gtl_gui_orbit_widget.h" +#include "gtl_gui_chart_widget.h" +#include "gui/ui_gtl_gui_orbit_widget.h" +#include + +namespace gtl { + namespace gui { + namespace orbit { + + widget::widget(QWidget *parent, gtl::data_model* model) + : QWidget(parent), + ui(new Ui::orbit_widget) + { + ui->setupUi(this); + + _selection_data_model = new gtl::selection_data_model(this); + _selection_data_model->setSourceModel(model); + + ui->signals_->setModel(_selection_data_model); + ui->signals_->expandAll(); + + ::chart::axis_horz* axis_x = new ::chart::axis_horz(); + ::chart::axis_vert* axis_y = new ::chart::axis_vert(::chart::axis_vert::down_to_up, false); + _series = new series(parent, axis_x, axis_y); + _chart = new chart(this, axis_x, axis_y); + _chart->add(_series); + ui->splitter->insertWidget(0, _chart); + + connect(ui->sig_x, &QComboBox::currentIndexChanged, [=]{ + std::vector ads; + _selection_data_model->get_selections(std::back_insert_iterator>(ads)); + int idx = ui->sig_x->currentIndex(); + if(idx >= 0 && idx < ads.size()) + _series->set_x(ads.at(idx)); + else + _series->set_x(nullptr); + }); + ui->sig_x->setModel(_selection_data_model->selection()); + + connect(ui->sig_y, &QComboBox::currentIndexChanged, [=]{ + std::vector ads; + _selection_data_model->get_selections(std::back_insert_iterator>(ads)); + int idx = ui->sig_y->currentIndex(); + if(idx >= 0 && idx < ads.size()) + _series->set_y(ads.at(idx)); + else + _series->set_y(nullptr); + }); + ui->sig_y->setModel(_selection_data_model->selection()); + + ui->time->setMinimum(0); + ui->time->setMaximum(10000); + ui->time->setSingleStep(0.1); + ui->time->setValue(_series->time()); + connect(ui->time, &QDoubleSpinBox::valueChanged, _series, &series::set_time); + connect(_series, &series::time_changed, this, &widget::update_parameters); + + ui->start->setMinimum(0); + ui->start->setMaximum(10000); + ui->start->setSingleStep(0.1); + ui->start->setValue(_series->start()); + connect(ui->start, &QDoubleSpinBox::valueChanged, _series, &series::set_start); + connect(_series, &series::start_changed, this, &widget::update_parameters); + + ui->stop->setMinimum(0); + ui->stop->setMaximum(10000); + ui->stop->setSingleStep(0.1); + ui->stop->setValue(_series->stop()); + connect(ui->stop, &QDoubleSpinBox::valueChanged, _series, &series::set_stop); + connect(_series, &series::stop_changed, this, &widget::update_parameters); + + ui->turns->setMinimum(0); + ui->turns->setMaximum(10000); + ui->turns->setSingleStep(0.1); + ui->turns->setValue(_series->turns()); + connect(ui->turns, &QDoubleSpinBox::valueChanged, _series, &series::set_turns); + connect(_series, &series::turns_changed, this, &widget::update_parameters); + + connect(_series, &series::freq_changed, [=]{ + ui->freq->setText(QString::number(_series->freq())); + }); + + + } + + widget::~widget() + { + delete _chart; + delete ui; + } + + void widget::save(QDomElement &root_element) + { + QDomElement selection_element = root_element.ownerDocument().createElement("selection"); + root_element.appendChild(selection_element); + _selection_data_model->save(selection_element); + + QDomElement options_element = root_element.ownerDocument().createElement("options"); + root_element.appendChild(options_element); + _series->save(options_element); + + QDomElement splitter_element = root_element.ownerDocument().createElement("splitter"); + root_element.appendChild(splitter_element); + splitter_element.setAttribute("state", QString(ui->splitter->saveState().toBase64())); + } + + void widget::load(const QDomElement &root_element) + { + QDomElement options_element = root_element.firstChildElement("options"); + _series->load(options_element); + + update_parameters(); + + QDomElement selection_element = root_element.firstChildElement("selection"); + _selection_data_model->load(selection_element); + + + + QDomElement splitter_element = root_element.firstChildElement("splitter"); + ui->splitter->restoreState(QByteArray::fromBase64(splitter_element.attribute("state", "").toUtf8())); + } + + void widget::update_parameters() + { + blockSignals(true); + _series->blockSignals(true); + ui->time->blockSignals(true); + ui->start->blockSignals(true); + ui->stop->blockSignals(true); + ui->turns->blockSignals(true); + + qreal value; + + value = _series->time(); + if(value != ui->time->value()) + ui->time->setValue(value); + + value = _series->start(); + if(value != ui->start->value()) + ui->start->setValue(value); + + value = _series->stop(); + if(value != ui->stop->value()) + ui->stop->setValue(value); + + value = _series->turns(); + if(value != ui->turns->value()) + ui->turns->setValue(value); + + blockSignals(false); + _series->blockSignals(false); + ui->time->blockSignals(false); + ui->start->blockSignals(false); + ui->stop->blockSignals(false); + ui->turns->blockSignals(false); + } + + } + } // namespace gui +} // namespace gtl + diff --git a/gui/orbit/gtl_gui_orbit_widget.h b/gui/orbit/gtl_gui_orbit_widget.h new file mode 100644 index 0000000..fef4e3d --- /dev/null +++ b/gui/orbit/gtl_gui_orbit_widget.h @@ -0,0 +1,48 @@ +#ifndef GTL_GUI_ORBIT_WIDGET_H +#define GTL_GUI_ORBIT_WIDGET_H + + +#include + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" +#include "gtl_gui_orbit_series.h" +#include "gtl_gui_orbit_chart.h" + +#include "gui/gui_global.h" + +namespace Ui { +class orbit_widget; +} + +namespace gtl { + namespace gui { + namespace orbit { + + class GTL_GUI_EXPORT widget : public QWidget + { + Q_OBJECT + public: + explicit widget(QWidget *parent, gtl::data_model* model); + ~widget(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + Ui::orbit_widget *ui; + gtl::selection_data_model* _selection_data_model; + series* _series; + chart* _chart; + + private slots: + void update_parameters(); + + signals: + + }; + } + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_ORBIT_WIDGET_H diff --git a/gui/orbit/gtl_gui_orbit_widget.ui b/gui/orbit/gtl_gui_orbit_widget.ui new file mode 100644 index 0000000..428fba6 --- /dev/null +++ b/gui/orbit/gtl_gui_orbit_widget.ui @@ -0,0 +1,198 @@ + + + orbit_widget + + + + 0 + 0 + 624 + 586 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Signal X + + + + + + + + + + Signal Y + + + + + + + + + + Time + + + + + + + 100.000000000000000 + + + + + + + Turns + + + + + + + + + + Start + + + + + + + + + + Stop + + + + + + + + + + Frequency + + + + + + + false + + + + 0 + 0 + + + + + 100 + 16777215 + + + + 200 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + false + + + + + + + + + + + + + + diff --git a/gui/osc/gtl_gui_osc_chart.cpp b/gui/osc/gtl_gui_osc_chart.cpp new file mode 100644 index 0000000..8f6388f --- /dev/null +++ b/gui/osc/gtl_gui_osc_chart.cpp @@ -0,0 +1,236 @@ +#include "gtl_gui_osc_chart.h" +#include "gtl_gui_osc_series.h" + +#include "core/gtl_device.h" + +namespace gtl +{ + namespace gui + { + namespace osc + { + chart::chart(QWidget* parent) + : gtl::gui::chart(parent) + , _time(0.01) + , _is_triggering(false) + , _triggering_threshold(0) + , _triggering_is_front(true) + , _triggering_prehistory(0.1) + , _triggering_channel(-1) + , _meas_model(nullptr) + , _meas_cnt(0) + { + //QChartView::chart()->createDefaultAxes(); + } + + qreal chart::time() const + { + return _time; + } + + bool chart::is_triggering() const + { + return _is_triggering; + } + + qreal chart::triggering_threshold() const + { + return _triggering_threshold; + } + + bool chart::triggering_is_front() const + { + return _triggering_is_front; + } + + qreal chart::triggering_prehistory() const + { + return _triggering_prehistory*100; + } + + int chart::triggering_channel() const + { + return _triggering_channel; + } + + void chart::save(QDomElement &root_element) + { + gtl::gui::chart::save(root_element); + + root_element.setAttribute("time", _time); + + root_element.setAttribute("triggering", _is_triggering); + root_element.setAttribute("triggering_channel", _triggering_channel); + root_element.setAttribute("triggering_threshold", _triggering_threshold); + root_element.setAttribute("triggering_prehistory", triggering_prehistory()); + root_element.setAttribute("triggering_is_front", _triggering_is_front); + } + + void chart::load(const QDomElement &root_element) + { + gtl::gui::chart::load(root_element); + + set_time(root_element.attribute("time", "0.1").toDouble()); + + set_triggering(root_element.attribute("triggering", "0").toInt() == 1); + set_triggering_channel(root_element.attribute("triggering_channel", "-1").toInt() == 1); + set_triggering_threshold(root_element.attribute("triggering_threshold", "0").toDouble()); + set_triggering_prehistory(root_element.attribute("triggering_prehistory", "0.1").toDouble()); + set_triggering_is_front(root_element.attribute("triggering_is_front", "1").toInt() == 1); + } + + chart_series *chart::create_series(analog_data *ai) + { + series* s = new series(time(), is_updating(), ai, _axis_x, _axis_y); + + connect(s, &series::measures_changed, this, &chart::handle_measures); + s->set_measures(_meas_model); + + return s; + } + + void chart::set_time(qreal time) + { + if(time != _time) + { + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + static_cast(*iter_series)->set_time(time); + + _time = time; + + set_bounds_x(); + fit_axis(_axis_x); + + emit time_changed(_time); + } + } + + void chart::set_triggering(bool value) + { + if(value != _is_triggering) + { + _is_triggering = value; + emit triggering_changed(value); + } + } + + void chart::set_triggering_threshold(qreal value) + { + if(_triggering_threshold != value) + { + _triggering_threshold = value; + emit triggering_threshold_changed(value); + } + } + + void chart::set_triggering_is_front(bool value) + { + if(_triggering_is_front != value) + { + _triggering_is_front = value; + emit triggering_is_front_changed(value); + } + } + + void chart::set_triggering_prehistory(qreal value) + { + value /= 100.0; + if(_triggering_prehistory != value) + { + _triggering_prehistory = value; + emit triggering_prehistory_changed(value*100.0); + } + } + + void chart::set_triggering_channel(int value) + { + if(_triggering_channel != value) + { + _triggering_channel = value; + emit triggering_channel_changed(value); + } + } + + void chart::set_measures(meas_model *model) + { + _meas_model = model; + for(std::vector<::chart::series::series*>::iterator iter_series = _series.begin(); iter_series != _series.end(); iter_series++) + static_cast(*iter_series)->set_measures(_meas_model); + } + + void chart::handle_measures(OscMeasParamsListPtr meas) + { + for(int i = 0; i < meas->count(); i++) + for(int j = 0; j < _meas_model->measures()->count(); j++) + { + if(meas->at(i).id == _meas_model->measures()->at(j).id) + { + gtl::math::osc_meas::params *p = const_cast(&_meas_model->measures()->at(j)); + p->value = meas->at(i).value; + _meas_cnt++; + } + } + + if(_meas_cnt >= _meas_model->measures()->count()) + { + _meas_model->all_done(); + _meas_cnt = 0; + } + + // qDebug() << "osc_chart: handle_measures"; + } + + void chart::device_recieved_data() + { + auto it = _devices.find(static_cast(sender())); + if(it != _devices.end()) + { + series* triggering_series = NULL; + if(_triggering_channel >= 0 && _triggering_channel < _series.size() && _is_triggering) + triggering_series = static_cast(_series[_triggering_channel]); + + auto it_series = std::find(it->second.begin(), it->second.end(), triggering_series); + + static_cast(it->first)->lock_ai(); + + if(it_series == it->second.end()) + { + bool is_complete = false; + for(auto series : it->second) + is_complete |= static_cast(series)->update_(); + + if(is_complete && ! is_cyclic()) + set_updating(false); + } + else + { + int ad_idx = -1; + bool is_complete = static_cast(*it_series)->update(_triggering_threshold, _triggering_is_front, _triggering_prehistory, ad_idx); + + for(auto series : it->second) + { + if(series == triggering_series) + continue; + + static_cast(series)->update(ad_idx, _triggering_prehistory); + } + + if(is_complete) + emit acq_completed(); + + if(is_complete && ! is_cyclic()) + set_updating(false); + + } + + + + + static_cast(it->first)->unlock_ai(); + } + + + } + } + } +} diff --git a/gui/osc/gtl_gui_osc_chart.h b/gui/osc/gtl_gui_osc_chart.h new file mode 100644 index 0000000..a22c8c3 --- /dev/null +++ b/gui/osc/gtl_gui_osc_chart.h @@ -0,0 +1,76 @@ +#ifndef GTL_GUI_OSC_CHART_H +#define GTL_GUI_OSC_CHART_H + +#include "gui/gtl_gui_chart.h" +#include "gui/osc/gtl_gui_osc_meas_model.h" +#include "gui/gui_global.h" + +namespace gtl +{ + namespace gui + { + namespace osc + { + class GTL_GUI_EXPORT chart : public gtl::gui::chart + { + Q_OBJECT + public: + chart(QWidget* parent = NULL); + + qreal time() const; + + bool is_triggering() const; + + qreal triggering_threshold() const; + bool triggering_is_front() const; + qreal triggering_prehistory() const; + int triggering_channel() const; + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + qreal _time; + + bool _is_triggering; + qreal _triggering_threshold; + bool _triggering_is_front; + qreal _triggering_prehistory; + int _triggering_channel; + + meas_model* _meas_model; + int _meas_cnt; + + protected: + virtual chart_series* create_series(gtl::analog_data* ai); + + signals: + void time_changed(qreal); + + void triggering_changed(bool); + void triggering_channel_changed(int); + void triggering_threshold_changed(qreal); + void triggering_prehistory_changed(qreal); + void triggering_is_front_changed(bool); + + void acq_completed(); + + public slots: + void set_time(qreal time); + void set_triggering(bool value); + void set_triggering_threshold(qreal value); + void set_triggering_is_front(bool value); + void set_triggering_prehistory(qreal value); + void set_triggering_channel(int value); + + void set_measures(meas_model* m); + void handle_measures(OscMeasParamsListPtr meas); + + private slots: + virtual void device_recieved_data(); + }; + } + } +} + +#endif // GTL_GUI_OSC_CHART_H diff --git a/gui/osc/gtl_gui_osc_meas_delegate.cpp b/gui/osc/gtl_gui_osc_meas_delegate.cpp new file mode 100644 index 0000000..a9a97b4 --- /dev/null +++ b/gui/osc/gtl_gui_osc_meas_delegate.cpp @@ -0,0 +1,100 @@ + +#include "gtl_gui_osc_meas_delegate.h" +#include "gtl_gui_osc_meas_model.h" + +namespace gtl { + namespace gui { + namespace osc { + + + meas_delegate::meas_delegate(QObject *parent, QAbstractItemModel *channels) : + QStyledItemDelegate(parent), + _channels(channels) + { + } + + QWidget *meas_delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(option) + Q_UNUSED(index) + + int col = index.column(); + + if(col == meas_model::CHAN) + { + meas_chan_widget *editor = new meas_chan_widget(parent, _channels); + connect(editor, &meas_chan_widget::chan_changed , [=]{setModelData(editor, const_cast(index.model()), index);}); + return editor; + } + else if(col == meas_model::PARAM) + { + meas_param_widget *editor = new meas_param_widget(parent); + connect(editor, &meas_param_widget::param_changed , [=]{setModelData(editor, const_cast(index.model()), index);}); + return editor; + } + + return 0; + } + + void meas_delegate::setEditorData(QWidget *editor, const QModelIndex &index) const + { + int col = index.column(); + + if(col == meas_model::CHAN) + { + meas_chan_widget *widget = static_cast(editor); + widget->blockSignals(true); + widget->setCurrentIndex(index.data(meas_model::CustomRoles::IdxRole).toInt()); + widget->blockSignals(false); + } + else if(col == meas_model::PARAM) + { + meas_param_widget *widget = static_cast(editor); + widget->blockSignals(true); + widget->setCurrentIndex(index.data(Qt::DisplayRole).toInt()); + widget->blockSignals(false); + } + } + + void meas_delegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const + { + int col = index.column(); + + editor->blockSignals(true); + if(col == meas_model::CHAN) + { + meas_chan_widget *widget = static_cast(editor); + model->setData(index, widget->currentIdx(), meas_model::CustomRoles::IdxRole); + model->setData(index, widget->currentText(), Qt::EditRole); + } + else if(col == meas_model::PARAM) + { + meas_param_widget *widget = static_cast(editor); + model->setData(index, widget->currentIndex(), Qt::EditRole); + } + editor->blockSignals(false); + } + + void meas_delegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(index) + editor->setGeometry(option.rect); + } + + void meas_delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(painter) + Q_UNUSED(option) + Q_UNUSED(index) + } + + QSize meas_delegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QSize s = index.data(Qt::SizeHintRole).toSize(); + return s.isValid() ? s: QStyledItemDelegate::sizeHint(option, index); + } + + } + } // namespace gui +} // namespace gtl + diff --git a/gui/osc/gtl_gui_osc_meas_delegate.h b/gui/osc/gtl_gui_osc_meas_delegate.h new file mode 100644 index 0000000..d959a49 --- /dev/null +++ b/gui/osc/gtl_gui_osc_meas_delegate.h @@ -0,0 +1,144 @@ + +#ifndef GTL_GUI_OSC_MEAS_DELEGATE_H +#define GTL_GUI_OSC_MEAS_DELEGATE_H + +#include +#include +#include +#include +#include +#include +#include + +#include "gui_global.h" +#include "math/gtl_math_osc_meas.h" + +namespace gtl { + namespace gui { + namespace osc{ + + + class GTL_GUI_EXPORT meas_chan_widget : public QWidget + { + Q_OBJECT + public: + meas_chan_widget(QWidget *parent=nullptr, QAbstractItemModel *model = nullptr) : + QWidget(parent), + comboBox(new QComboBox) + { + if(model) + { + QHBoxLayout *layout = new QHBoxLayout(this); + comboBox->setModel(model); + comboBox->setContentsMargins(0,0,0,0); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(comboBox); + + connect(comboBox, &QComboBox::currentIndexChanged, [=]{emit chan_changed();}); + } + } + + QString currentText() + { + return comboBox->currentText(); + } + + int currentIdx() + { + int idx = comboBox->currentIndex(); + return idx; + } + + void setCurrentIndex(QString name) + { + int idx = comboBox->findText(name); + if(idx != comboBox->currentIndex()) + { + if(idx >= 0 && idx < comboBox->count()) + comboBox->setCurrentIndex(idx); + emit chan_changed(); + } + } + + void setCurrentIndex(int idx) + { + if(idx != comboBox->currentIndex()) + { + if(idx >= 0 && idx < comboBox->count()) + comboBox->setCurrentIndex(idx); + emit chan_changed(); + } + } + + signals: + void chan_changed(); + + private: + QComboBox *comboBox; + }; + + class GTL_GUI_EXPORT meas_param_widget : public QWidget + { + Q_OBJECT + public: + meas_param_widget(QWidget *parent=nullptr) : + QWidget(parent) + { + comboBox = new QComboBox; + QMetaEnum meta_enum = QMetaEnum::fromType(); + for(int i = 0; i < meta_enum.keyCount(); i++) + comboBox->addItem(meta_enum.valueToKey(i)); + comboBox->setContentsMargins(0,0,0,0); + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(comboBox); + + connect(comboBox, &QComboBox::currentIndexChanged, [=]{emit param_changed();}); + } + + int currentIndex() + { + return comboBox->currentIndex(); + } + + void setCurrentIndex(int index) + { + if(index >= 0 && index < comboBox->count()) + comboBox->setCurrentIndex(index); + emit param_changed(); + + return; + } + + private: + QComboBox *comboBox; + + signals: + void param_changed(); + }; + + class GTL_GUI_EXPORT meas_delegate : public QStyledItemDelegate + { + Q_OBJECT + + public: + explicit meas_delegate(QObject *parent = nullptr, QAbstractItemModel *channels = nullptr); + + QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; + void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + + private: + QAbstractItemModel *_channels = nullptr; + }; + + } + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_OSC_MEAS_DELEGATE_H diff --git a/gui/osc/gtl_gui_osc_meas_model.cpp b/gui/osc/gtl_gui_osc_meas_model.cpp new file mode 100644 index 0000000..deeaa11 --- /dev/null +++ b/gui/osc/gtl_gui_osc_meas_model.cpp @@ -0,0 +1,198 @@ +#include +#include "core/gtl_selection_data_model.h" +#include "gtl_gui_osc_meas_model.h" + +namespace gtl { + namespace gui { + namespace osc { + + meas_model::meas_model(QObject *parent, QAbstractItemModel *channels) : + QAbstractTableModel(parent), + _channels(channels) + { + _measures = OscMeasParamsListPtr(new QListOscMeasParams); + } + + QVariant meas_model::headerData(int section, Qt::Orientation orientation, int role) const + { + if(role != Qt::DisplayRole) + return QVariant(); + + if(orientation == Qt::Vertical) + return section; + + switch(section) { + case CHAN: + return tr("channel"); + case PARAM: + return tr("parameter"); + case VALUE: + return tr("value"); + } + + return QVariant(); + } + + int meas_model::rowCount(const QModelIndex &parent) const + { + if (parent.isValid()) + return 0; + + if(_measures) + return _measures->count(); + return 0; + } + + int meas_model::columnCount(const QModelIndex &parent) const + { + if (parent.isValid()) + return 0; + + return VALUE + 1; + } + + QVariant meas_model::data(const QModelIndex &index, int role) const + { + if (!index.isValid()) + return QVariant(); + + int col = index.column(); + int row = index.row(); + + if(row >= _measures->size()) + return QVariant(); + + switch (role){ + case Qt::DisplayRole: + if(col == CHAN) + return _measures->at(row).chan; + else if(col == PARAM) + return static_cast(_measures->at(row).type); + else if(col == VALUE) + return _measures->at(row).value; + break; + case CustomRoles::IdxRole: + { + selection_list* sel = static_cast(_channels); + for(int i = 0; i < sel->size(); i++) + if(sel->at(i) == _measures->at(row).ad) + return i; + return QVariant(); + } + break; + case Qt::BackgroundRole: + if(col == VALUE) + return QBrush(QColor(127,255,127,255)); + default: + return QVariant(); + } + + return QVariant(); + } + + bool meas_model::setData(const QModelIndex &index, const QVariant &value, int role) + { + if (data(index, role) != value) + { + int col = index.column(); + int row = index.row(); + + if(row >= _measures->size() || col < 0) + return false; + + math::osc_meas::params param = _measures->at(row); + bool param_flag = true; + switch (role){ + case Qt::EditRole: + if(col == CHAN) + param.chan = value.toString(); + else if(col == PARAM) + param.type = static_cast(value.toInt()); + else if(col == VALUE) + { + param.value = value.toDouble(); + param_flag = false; + } + break; + case CustomRoles::IdxRole: + { + selection_list* chs = static_cast(_channels); + if(chs->size() && chs->size() > value.toInt()) param.ad = chs->at(value.toInt()); + else param.ad = nullptr; + } + break; + default: + return false; + } + + _measures->replace(row, param); + if(param_flag) + emit measure_changed(row); + emit dataChanged(index, index, {role}); + return true; + } + return false; + } + + Qt::ItemFlags meas_model::flags(const QModelIndex &index) const + { + if (!index.isValid()) + return Qt::NoItemFlags; + + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } + + bool meas_model::insertRows(int row, int count, const QModelIndex &parent) + { + if(static_cast(_channels)->size()) + { + beginInsertRows(parent, row, row + count - 1); + math::osc_meas::params param; + param.chan = _channels->index(0, 0, QModelIndex()).data(Qt::DisplayRole).toString(); + for(int i = 0; i < count; i++) + { + param.id = QUuid::createUuid().data1; + param.ad = static_cast(_channels)->at(0); + _measures->insert(row, param); + } + endInsertRows(); + emit measure_changed(row); + return true; + } + else + return false; + } + + bool meas_model::removeRows(int row, int count, const QModelIndex &parent) + { + if(_measures->count() && row >= 0 && _measures->count() >= (row + count)) + { + beginRemoveRows(parent, row, row + count - 1); + _measures->remove(row,count); + endRemoveRows(); + emit measure_changed(row); + return true; + } + return false; + } + + OscMeasParamsListPtr meas_model::measures() + { + return _measures; + } + + QAbstractItemModel *meas_model::channels() + { + return _channels; + } + + void meas_model::all_done() + { + QModelIndex top_left = index(0,VALUE, QModelIndex()); + QModelIndex bottom_right = index(_measures->count(), VALUE, QModelIndex()); + emit dataChanged(top_left, bottom_right, {Qt::DisplayRole}); + } + } + } // namespace gui +} // namespace gtl + diff --git a/gui/osc/gtl_gui_osc_meas_model.h b/gui/osc/gtl_gui_osc_meas_model.h new file mode 100644 index 0000000..e59b749 --- /dev/null +++ b/gui/osc/gtl_gui_osc_meas_model.h @@ -0,0 +1,65 @@ + +#ifndef GTL_GUI_OSC_MEAS_MODEL_H +#define GTL_GUI_OSC_MEAS_MODEL_H + +#include +#include + +#include "gui/gui_global.h" +#include "math/gtl_math_osc_meas.h" + +namespace gtl { + namespace gui { + namespace osc{ + + class GTL_GUI_EXPORT meas_model : public QAbstractTableModel + { + Q_OBJECT + + public: + + enum CustomRoles{ + IdxRole = Qt::UserRole + }; + + enum Column { CHAN = 0, PARAM, VALUE, LAST }; + + explicit meas_model(QObject *parent = nullptr, QAbstractItemModel *channels = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // Editable: + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + + OscMeasParamsListPtr measures(); + QAbstractItemModel* channels(); + void all_done(); + + private: + + OscMeasParamsListPtr _measures; + QAbstractItemModel* _channels; + + void remove(int row, QModelIndex& index); + + signals: + void measure_changed(int idx); + }; + } + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_OSC_MEAS_MODEL_H diff --git a/gui/osc/gtl_gui_osc_series.cpp b/gui/osc/gtl_gui_osc_series.cpp new file mode 100644 index 0000000..6dc4e55 --- /dev/null +++ b/gui/osc/gtl_gui_osc_series.cpp @@ -0,0 +1,336 @@ +#include "gtl_gui_osc_series.h" + +namespace gtl +{ + namespace gui + { + namespace osc + { + series::series(qreal time, bool is_updating, gtl::analog_data* ai, chart::axis_horz* axis_x, chart::axis_vert* axis_y) + : chart_series(ai, axis_x, axis_y) + , _buffer(time * ai->get_rate()) + , _ptr(0) + , _time(time) + , _prehistory(0) + , _scale(1) + { + + _is_updating = is_updating; + + _measures = OscMeasParamsListPtr(new QListOscMeasParams); + + connect(ai, >l::analog_data::rate_changed, this, &series::rate_changed); + connect(chart_series::axis_x(), &chart::axis::signal_range_changed, this, >l::gui::osc::series::handle_measure); + + init(); + + // data_recieved(); + } + + series::~series() + { + disconnect(ad(), >l::analog_data::rate_changed, this, &series::rate_changed); + } + + void series::set_time(qreal time) + { + if(time != _time) + { + _time = time; + init(); + } + } + + bool series::update(qreal threshold, bool is_front, qreal prehistory, int& analog_data_idx) + { + _prehistory = prehistory; + bool is_complete = false; + for(int i = 0; i < (int)_ad->size(); i++) + { + _buffer[_ptr] = _ad->at(i); + + if(_begin_ptr == -1) + { + + if( + (is_front && _buffer[_ptr] >= threshold && _buffer[(_ptr + _buffer.size() - 1) % _buffer.size()] < threshold) || + (!is_front && _buffer[_ptr] <= threshold && _buffer[(_ptr + _buffer.size() - 1) % _buffer.size()] > threshold) + ) + { + _begin_ptr = (_ptr + qRound(_buffer.size() * (1.0 - prehistory))) % _buffer.size(); + analog_data_idx = i; + } + + } + + _ptr = (_ptr + 1) % _buffer.size(); + + if(_ptr == _begin_ptr) + { + if(/*!_updater.isRunning() && */_is_updating) + { + std::vector data_buffer; + + std::copy(_buffer.begin() + _begin_ptr, _buffer.end(), std::back_inserter(data_buffer)); + std::copy(_buffer.begin(), _buffer.begin() + _begin_ptr, std::back_inserter(data_buffer)); + measure(data_buffer); + _updater.set_data(data_buffer); + + // _updater.start(); + + is_complete = true; + } + + + + _begin_ptr = -1; + } + } + + if(_is_updating&& !is_complete && _begin_ptr != -1) + { + std::vector data_buffer(_buffer.size(), 0); + + if(_ptr > _begin_ptr) + { + std::copy(_buffer.begin() + _begin_ptr, _buffer.begin() + _ptr, data_buffer.begin()); + } + else + { + std::copy(_buffer.begin() + _begin_ptr, _buffer.end(), data_buffer.begin()); + std::copy(_buffer.begin(), _buffer.begin() + _ptr, data_buffer.begin() + std::distance(_buffer.begin() + _begin_ptr, _buffer.end())); + } + + measure(data_buffer); + _updater.set_data(data_buffer); + } + + return is_complete; + } + + void series::update(int ad_idx, qreal prehistory) + { + bool is_complete = false; + _prehistory = prehistory; + for(int i = 0; i < (int)_ad->size(); i++) + { + _buffer[_ptr] = _ad->at(i); + + if(_begin_ptr == -1) + { + + if(ad_idx == i) + { + _begin_ptr = (_ptr + qRound(_buffer.size() * (1.0 - prehistory))) % _buffer.size(); + } + + } + + _ptr = (_ptr + 1) % _buffer.size(); + + if(_ptr == _begin_ptr) + { + if(/*!_updater.isRunning() && */_is_updating) + { + std::vector data_buffer; + + std::copy(_buffer.begin() + _begin_ptr, _buffer.end(), std::back_inserter(data_buffer)); + std::copy(_buffer.begin(), _buffer.begin() + _begin_ptr, std::back_inserter(data_buffer)); + measure(data_buffer); + _updater.set_data(data_buffer); + + } + + _begin_ptr = -1; + is_complete = true; + } + } + + if(_is_updating&& !is_complete && _begin_ptr != -1) + { + std::vector data_buffer(_buffer.size(), 0); + + if(_ptr > _begin_ptr) + { + std::copy(_buffer.begin() + _begin_ptr, _buffer.begin() + _ptr, data_buffer.begin()); + } + else + { + std::copy(_buffer.begin() + _begin_ptr, _buffer.end(), data_buffer.begin()); + std::copy(_buffer.begin(), _buffer.begin() + _ptr, data_buffer.begin() + std::distance(_buffer.begin() + _begin_ptr, _buffer.end())); + } + + measure(data_buffer); + _updater.set_data(data_buffer); + } + } + + bool series::update_() + { + _prehistory = 0; + + if(_begin_ptr == -1) + _begin_ptr = _ptr; + + bool is_complete = false; + + if(_ad->empty()) + return false; + + for(int i = 0; i < (int)_ad->size(); i++) + { + _buffer[_ptr] = _ad->at(i); + + + _ptr = (_ptr + 1) % _buffer.size(); + + if(_ptr == _begin_ptr) + { + if(_is_updating) + { + std::vector data_buffer; + + std::copy(_buffer.begin() + _begin_ptr, _buffer.end(), std::back_inserter(data_buffer)); + std::copy(_buffer.begin(), _buffer.begin() + _begin_ptr, std::back_inserter(data_buffer)); + measure(data_buffer); + _updater.set_data(data_buffer); + + } + + _begin_ptr = -1; + is_complete = true; + } + } + + if(_is_updating && !is_complete) + { + std::vector data_buffer; + + std::copy(_buffer.begin() + _ptr, _buffer.end(), std::back_inserter(data_buffer)); + std::copy(_buffer.begin(), _buffer.begin() + _ptr, std::back_inserter(data_buffer)); + measure(data_buffer); + _updater.set_data(data_buffer); + + } + + return is_complete; + } + + void series::set_measures(meas_model *model) + { + if(!model) + return; + _measures->clear(); + for(int i = 0; i < model->measures()->count(); i++) + if(model->measures()->at(i).ad == _ad) + _measures->append(model->measures()->at(i)); + // qDebug() << "osc_series: set_measure"; + measure(); + } + + void series::measure(std::vector &data) + { + if(!_measures) + return; + + if(!_measures->count()) + return; + + if(data.empty()) + return; + + int meas_cnt = 0; + std::vector::iterator l = data.begin() + axis_x()->min()*ad()->get_rate(); + std::vector::iterator r = data.begin() + axis_x()->max()*ad()->get_rate(); + for(int i = 0; i < _measures->count(); i++) + { + if(_measures->at(i).ad == _ad) + { + meas_cnt++; + math::osc_meas::params *p = const_cast(&_measures->at(i)); + switch (p->type) + { + case math::osc_meas::types::min: + p->value = math::osc_meas::min(l, r); + break; + case math::osc_meas::types::max: + p->value = math::osc_meas::max(l, r); + break; + case math::osc_meas::types::offset: + p->value = math::osc_meas::offset(l, r); + break; + case math::osc_meas::types::peak: + p->value = math::osc_meas::peak(l, r); + break; + case math::osc_meas::types::peak_to_peak: + p->value = math::osc_meas::peak_to_peak(l, r); + break; + case math::osc_meas::types::rms: + p->value = math::osc_meas::rms(l, r); + break; + case math::osc_meas::types::freq: + p->value = math::osc_meas::freq(l, r, ad()->get_rate()); + break; + case math::osc_meas::types::period: + p->value = math::osc_meas::period(l, r, ad()->get_rate()); + break; + case math::osc_meas::types::kurt: + p->value = math::osc_meas::kurt(l, r); + break; + default: + break; + } + } + } + + if(meas_cnt) + emit measures_changed(_measures); + } + + void series::measure() + { + if(!_measures) + return; + + if(!_measures->count()) + return; + + std::vector x; + std::vector y; + xy::get_values_arrays(std::back_inserter(x), std::back_inserter(y)); + measure(y); + } + + void series::init() + { + _updater.stop(); + + + _ad->lock_device(); + + _buffer.resize(_time * _ad->get_rate()); + std::fill(_buffer.begin(), _buffer.end(), 0); + _ptr = 0; + _begin_ptr = -1; + set_y(&_buffer[0], (int)_buffer.size(), 1/_ad->get_rate()*_scale); + + _ad->unlock_device(); + } + + void series::data_recieved() + { + + } + + void series::rate_changed() + { + init(); + } + + void series::handle_measure() + { + measure(); + } + } + } +} diff --git a/gui/osc/gtl_gui_osc_series.h b/gui/osc/gtl_gui_osc_series.h new file mode 100644 index 0000000..c5cd5ad --- /dev/null +++ b/gui/osc/gtl_gui_osc_series.h @@ -0,0 +1,57 @@ +#ifndef GTL_GUI_OSC_SERIES_H +#define GTL_GUI_OSC_SERIES_H + +#include "gui/gtl_gui_chart_series.h" +#include "gtl_gui_osc_meas_model.h" +#include "gui/gui_global.h" + +namespace gtl +{ + namespace gui + { + namespace osc + { + class GTL_GUI_EXPORT series : public chart_series + { + Q_OBJECT + public: + series(qreal time, bool is_updating, gtl::analog_data* ai, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y); + ~series(); + void set_time(qreal time); + + bool update(qreal threshold, bool is_front, qreal prehistory, int& analog_data_idx); + void update(int ad_idx, qreal prehistory); + bool update_(); + + void set_measures(meas_model* model); + + private: + std::vector _buffer; + int _ptr; + int _begin_ptr; + qreal _time; + OscMeasParamsListPtr _measures = nullptr; + void measure(std::vector &data); + void measure(); + + protected: + qreal _prehistory; + qreal _scale; + + private: + void init(); + + private slots: + void data_recieved(); + void rate_changed(); + void handle_measure(); + + + signals: + void measures_changed(OscMeasParamsListPtr meas); + }; + } + } +} + +#endif // GTL_GUI_OSC_SERIES_H diff --git a/gui/osc/gtl_gui_osc_widget.cpp b/gui/osc/gtl_gui_osc_widget.cpp new file mode 100644 index 0000000..ed40848 --- /dev/null +++ b/gui/osc/gtl_gui_osc_widget.cpp @@ -0,0 +1,183 @@ +#include "gtl_gui_osc_widget.h" +#include "ui_gtl_gui_osc_widget.h" + +#include + +#include "gui/gtl_gui_chart_widget.h" +#include "gui/osc/gtl_gui_osc_meas_model.h" +#include "gui/osc/gtl_gui_osc_meas_delegate.h" + +namespace gtl +{ + namespace gui + { + namespace osc + { + widget::widget(QWidget *parent, gtl::data_model* model) : + QWidget(parent), + ui(new Ui::osc_widget) + { + ui->setupUi(this); + + _selection_data_model = new gtl::selection_data_model(this); + _selection_data_model->setSourceModel(model); + + ui->tree_data->setModel(_selection_data_model); + ui->tree_data->expandAll(); + + + + + _chart = new chart(this); + gtl::gui::chart_widget *chart_widget = new gtl::gui::chart_widget(_chart, this); + + _cyclic_action = new QAction(QIcon(":/osc/cycling"), tr("Cycling"), this); + _cyclic_action->setCheckable(true); + _cyclic_action->setChecked(_chart->is_cyclic()); + connect(_cyclic_action, &QAction::toggled, _chart, &chart::set_cyclic); + connect(_chart, &chart::cyclic_changed, _cyclic_action, &QAction::setChecked); + + ui->cyclic->setDefaultAction(_cyclic_action); +/* + ui->cyclic->setChecked(_chart->is_cyclic()); + connect(ui->cyclic, &QAbstractButton::toggled, _chart, &chart::set_cyclic); + connect(_chart, &chart::cyclic_changed, ui->cyclic, &QAbstractButton::setChecked); +*/ + + ui->cb_axis_y_mode->setChecked(_chart->is_axis_y_multi()); + connect(ui->cb_axis_y_mode, &QCheckBox::toggled, _chart, &chart::set_axis_y_mode); + connect(_chart, &chart::axis_y_mode_changed, ui->cb_axis_y_mode, &QCheckBox::setChecked); + + ui->num_time->setValue(_chart->time()); + connect(ui->num_time, &QDoubleSpinBox::valueChanged, _chart, &chart::set_time); + connect(_chart, &chart::time_changed, ui->num_time, &QDoubleSpinBox::setValue); + + _updating_action = new QAction(QIcon(":/osc/updating"), tr("Updating"), this); + _updating_action->setCheckable(true); + _updating_action->setChecked(_chart->is_updating()); + connect(_updating_action, &QAction::toggled, _chart, >l::gui::chart::set_updating); + connect(_chart, >l::gui::chart::updating_changed, _updating_action, &QAction::setChecked); + ui->updating->setDefaultAction(_updating_action); +/* + ui->updating->setChecked(_chart->is_updating()); + connect(ui->updating, &QPushButton::toggled, _chart, >l::gui::chart::set_updating); + connect(_chart, >l::gui::chart::updating_changed, ui->updating, &QPushButton::setChecked); +*/ + connect(_selection_data_model, >l::selection_data_model::selected, _chart, >l::gui::chart::add_ad); + connect(_selection_data_model, >l::selection_data_model::deselected, _chart, >l::gui::chart::remove_ad); + + ui->splitter->insertWidget(0, chart_widget); + + QButtonGroup* triggering_front = new QButtonGroup(this); + triggering_front->addButton(ui->triggering_decline, 0); + triggering_front->addButton(ui->triggering_front, 1); + + + connect(ui->triggering_button, &QCheckBox::toggled, _chart, &chart::set_triggering); + connect(ui->triggering_button, &QCheckBox::toggled, ui->triggering, &QWidget::setVisible); + connect(_chart, &chart::triggering_changed, ui->triggering_button, &QCheckBox::setChecked); + ui->triggering->setVisible(false); + ui->triggering_button->setChecked(_chart->is_triggering()); + + + ui->triggering_channels->setModel(_selection_data_model->selection()); + + ui->triggering_channels->setCurrentIndex(_chart->triggering_channel()); + connect(ui->triggering_channels, &QComboBox::currentIndexChanged, _chart, &chart::set_triggering_channel); + connect(_chart, &chart::triggering_channel_changed, ui->triggering_channels, &QComboBox::setCurrentIndex); + + ui->triggering_threshold->setValue(_chart->triggering_threshold()); + connect(ui->triggering_threshold, &QDoubleSpinBox::valueChanged, _chart, &chart::set_triggering_threshold); + connect(_chart, &chart::triggering_threshold_changed, ui->triggering_threshold, &QDoubleSpinBox::setValue); + + ui->triggering_prehistory->setValue(_chart->triggering_prehistory()); + connect(ui->triggering_prehistory, &QDoubleSpinBox::valueChanged, _chart, &chart::set_triggering_prehistory); + connect(_chart, &chart::triggering_prehistory_changed, ui->triggering_prehistory, &QDoubleSpinBox::setValue); + + ui->triggering_front->setChecked(_chart->triggering_is_front()); + connect(ui->triggering_front, &QAbstractButton::toggled, _chart, &chart::set_triggering_is_front); + connect(_chart, &chart::triggering_is_front_changed, this, &widget::triggered_is_front_changed); + + gtl::gui::osc::meas_model *meas_model = new gtl::gui::osc::meas_model(this, _selection_data_model->selection()); + _chart->set_measures(meas_model); + gtl::gui::osc::meas_delegate *meas_delegate = new gtl::gui::osc::meas_delegate(this, meas_model->channels()); + _meas_widget = new gtl::gui::meas_widget(this, meas_model, meas_delegate); + connect(ui->meas_check, &QCheckBox::toggled, this, &widget::toogle_meas); + connect(meas_model, &meas_model::measure_changed, [=]{ _chart->set_measures(meas_model); }); + } + + widget::~widget() + { + delete ui; + } + + void widget::save(QDomElement &root_element) + { + QDomElement selection_element = root_element.ownerDocument().createElement("selection"); + root_element.appendChild(selection_element); + _selection_data_model->save(selection_element); + + QDomElement options_element = root_element.ownerDocument().createElement("options"); + root_element.appendChild(options_element); + _chart->save(options_element); + + QDomElement splitter_element = root_element.ownerDocument().createElement("splitter"); + root_element.appendChild(splitter_element); + // splitter_element.setAttribute("geometry", QString(ui->splitter->saveGeometry().toBase64())); + splitter_element.setAttribute("state", QString(ui->splitter->saveState().toBase64())); + } + + void widget::load(const QDomElement &root_element) + { + QDomElement selection_element = root_element.firstChildElement("selection"); + _selection_data_model->load(selection_element); + + QDomElement options_element = root_element.firstChildElement("options"); + _chart->load(options_element); + + QDomElement splitter_element = root_element.firstChildElement("splitter"); + // ui->splitter->restoreGeometry(QByteArray::fromBase64(splitter_element.attribute("geometry", "").toUtf8())); + ui->splitter->restoreState(QByteArray::fromBase64(splitter_element.attribute("state", "").toUtf8())); + + + } + + QAction *widget::cyclic_action() const + { + return _cyclic_action; + } + + QAction *widget::updating_action() const + { + return _updating_action; + } + + void widget::triggered_is_front_changed(bool value) + { + if(value) + ui->triggering_front->setChecked(true); + else + ui->triggering_decline->setChecked(true); + } + + void widget::toogle_meas(bool enabled) + { + if(_meas_widget) + { + if(enabled) + { + ui->parameters_frame->layout()->addWidget(_meas_widget); + _meas_widget->show(); + } + else + { + ui->parameters_frame->layout()->removeWidget(_meas_widget); + _meas_widget->hide(); + } + } + + return; + } + } + } +} diff --git a/gui/osc/gtl_gui_osc_widget.h b/gui/osc/gtl_gui_osc_widget.h new file mode 100644 index 0000000..25f3b85 --- /dev/null +++ b/gui/osc/gtl_gui_osc_widget.h @@ -0,0 +1,55 @@ +#ifndef GTL_GUI_OSC_WIDGET_H +#define GTL_GUI_OSC_WIDGET_H + +#include +#include + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" + +#include "gui/osc/gtl_gui_osc_chart.h" +#include "gui/gtl_gui_meas_widget.h" + +#include "gui/gui_global.h" + +namespace Ui { +class osc_widget; +} + +namespace gtl +{ + namespace gui + { + namespace osc + { + class GTL_GUI_EXPORT widget : public QWidget + { + Q_OBJECT + + public: + explicit widget(QWidget *parent, gtl::data_model* model); + ~widget(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + QAction* cyclic_action() const; + QAction* updating_action() const; + + protected: + Ui::osc_widget *ui; + gtl::selection_data_model* _selection_data_model; + chart* _chart; + gtl::gui::meas_widget* _meas_widget = nullptr; + QAction* _cyclic_action; + QAction* _updating_action; + + private slots: + void triggered_is_front_changed(bool value); + void toogle_meas(bool enable); + }; + } + } +} + +#endif // GTL_GUI_OSC_WIDGET_H diff --git a/gui/osc/gtl_gui_osc_widget.ui b/gui/osc/gtl_gui_osc_widget.ui new file mode 100644 index 0000000..75b79c2 --- /dev/null +++ b/gui/osc/gtl_gui_osc_widget.ui @@ -0,0 +1,400 @@ + + + osc_widget + + + + 0 + 0 + 400 + 320 + + + + Form + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + false + + + + + + + + + QLayout::SetMaximumSize + + + + + + + + time + + + sec + + + 3 + + + 0.001000000000000 + + + 100.000000000000000 + + + + + + + is multi axis y + + + + + + + + + + 0 + 0 + + + + + 65 + 0 + + + + + + 0 + -1 + 31 + 30 + + + + run + + + + :/osc/resources/NEXT2.png + :/osc/resources/pause_24.png:/osc/resources/NEXT2.png + + + + 24 + 24 + + + + true + + + + + + 35 + -1 + 31 + 30 + + + + ... + + + + :/osc/resources/REFRESH.png:/osc/resources/REFRESH.png + + + + 24 + 24 + + + + true + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Plain + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + triggering + + + + + + + QFrame::Raised + + + + QFormLayout::FieldsStayAtSizeHint + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Channel + + + + + + + + + + Threshold + + + + + + + QAbstractSpinBox::NoButtons + + + 6 + + + -100000.000000000000000 + + + 100000.000000000000000 + + + + + + + Prehistory, % + + + + + + + QAbstractSpinBox::NoButtons + + + 0 + + + + + + + Front + + + + + + + + + ... + + + + :/osc/resources/front_16.png:/osc/resources/front_16.png + + + + 16 + 16 + + + + true + + + true + + + false + + + + + + + ... + + + + :/osc/resources/decline_16.png:/osc/resources/decline_16.png + + + + 16 + 16 + + + + true + + + true + + + false + + + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Plain + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + parameters + + + + + + + + + + + + + + + + + diff --git a/gui/player/gtl_gui_player_chart.cpp b/gui/player/gtl_gui_player_chart.cpp new file mode 100644 index 0000000..8d870ff --- /dev/null +++ b/gui/player/gtl_gui_player_chart.cpp @@ -0,0 +1,157 @@ +#include "gtl_gui_player_chart.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + chart::chart(QWidget* parent) + : gtl::gui::record_chart(parent) + , _current_range(nullptr) + { + _ranges_menu = _menu->addMenu(tr("Ranges")); + _menu->insertMenu(_axis_y_mode_action, _ranges_menu); + + _add_range_action = _ranges_menu->addAction(QIcon(":/player/add_range"), tr("Add range")); + connect(_add_range_action, &QAction::triggered, this, &chart::add_new_range); + + _remove_range_action = _ranges_menu->addAction(QIcon(":/player/remove_range"), tr("Remove range")); + connect(_remove_range_action, &QAction::triggered, this, &chart::remove_current_range); + + _position = new chart_position(_marker_series); + add(_position); + + } + + chart::~chart() + { + save_ranges(); + } + + void chart::save_ranges() + { + if(file() == nullptr) + return; + + if(_mouse_action == ::chart::add_instruments) + _mouse_action = ::chart::edit_instruments; + + std::vector> ranges; + for(auto range : _ranges) + ranges.push_back(std::pair(range->left(), range->right())); + + file()->set_ranges(ranges.begin(), ranges.end()); + } + + void chart::set_position(qreal time) + { + _position->set_pos((time/**file()->rate()*/)); + } + + void chart::set_file(QString path) + { + save_ranges(); + + gtl::gui::record_chart::set_file(path); + + restore_ragnes(); + } + + void chart::add_instrument(const QPointF &pos) + { + _instruments.back()->add(pos); + if (_instruments.back()->is_complete()) + { + _mouse_action = ::chart::edit_instruments; + } + } + + void chart::draw_instrument(const QPointF &pos) + { + _instruments.back()->draw(QPointF(_axis_x->map_from_widget(pos.x()), _axis_y->map_from_widget(pos.y()))); + } + + chart_range *chart::get_range(QPointF pos) + { + for(auto range: _ranges) + { + if(range->is_inside(pos)) + return range; + } + + return nullptr; + } + + void chart::contextMenuEvent(QContextMenuEvent *event) + { + gtl::gui::record_chart::contextMenuEvent(event); + + _current_range = get_range(event->pos()); + + _remove_range_action->setVisible(_current_range); + _popup_point = event->pos(); + } + + void chart::clear_ranges() + { + while(!_ranges.empty()) + remove_range(_ranges.front()); + } + + void chart::restore_ragnes() + { + clear_ranges(); + + if(file() == nullptr) + return; + + std::vector> ranges; + file()->get_ranges(std::back_inserter(ranges)); + for(auto range_pair: ranges) + { + add_range()->set(range_pair.first, range_pair.second); + } + + } + + void chart::remove_range(chart_range *range) + { + _ranges.erase(std::remove(_ranges.begin(), _ranges.end(), range), _ranges.end()); + remove(range); + delete range; + } + + chart_range *chart::add_range() + { + chart_range* range = new chart_range(_marker_series); + connect(range, &chart_range::get_nearest_x, this, &chart::get_neares_series_x); + + add(range); + _ranges.push_back(range); + + return range; + } + + void chart::leaveEvent(QEvent *event) + { + save_ranges(); + } + + void chart::add_new_range() + { + _mouse_action = ::chart::add_instruments; + + add_range(); + + draw_instrument(_popup_point); + } + + void chart::remove_current_range() + { + remove_range(_current_range); + _current_range = nullptr; + } + } + } +} diff --git a/gui/player/gtl_gui_player_chart.h b/gui/player/gtl_gui_player_chart.h new file mode 100644 index 0000000..2966c31 --- /dev/null +++ b/gui/player/gtl_gui_player_chart.h @@ -0,0 +1,62 @@ +#ifndef PLAYER_CHART_H +#define PLAYER_CHART_H + +#include "gui/gtl_gui_record_chart.h" +#include "gtl_gui_player_chart_range.h" +#include "gui/player/gtl_gui_player_chart_position.h" + +#include "gui_global.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + class GTL_GUI_EXPORT chart : public gtl::gui::record_chart + { + Q_OBJECT + public: + chart(QWidget* parent = NULL); + ~chart(); + + void save_ranges(); + void set_position(qreal time); + + virtual void set_file(QString path) override; + + private: + QMenu* _ranges_menu; + QAction* _add_range_action; + QAction* _remove_range_action; + + std::vector _ranges; + chart_range* _current_range; + QPoint _popup_point; + + chart_position* _position; + + private: + virtual void add_instrument(const QPointF &pos) override; + virtual void draw_instrument(const QPointF &pos) override; + + chart_range* get_range(QPointF pos); + virtual void contextMenuEvent(QContextMenuEvent *event) override; + + void clear_ranges(); + void restore_ragnes(); + + void remove_range(chart_range *range); + chart_range* add_range(); + + void leaveEvent(QEvent *event) override; + + private slots: + void add_new_range(); + void remove_current_range(); + }; + } + } +} + +#endif // PLAYER_CHART_H diff --git a/gui/player/gtl_gui_player_chart_position.cpp b/gui/player/gtl_gui_player_chart_position.cpp new file mode 100644 index 0000000..0653d48 --- /dev/null +++ b/gui/player/gtl_gui_player_chart_position.cpp @@ -0,0 +1,35 @@ +#include "gtl_gui_player_chart_position.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + chart_position::chart_position(::chart::series::series* parent) + : ::chart::instrument::instrument(parent) + { + _line = new chart_position_line(parent); + ::chart::instrument::instrument::add(_line); + + _line->set_pos(0); + + } + + void chart_position::add(const QPointF &point) + { + + } + + void chart_position::draw(const QPointF &point) + { + + } + + void chart_position::set_pos(qreal pos) + { + _line->set_pos(pos); + } + } + } +} diff --git a/gui/player/gtl_gui_player_chart_position.h b/gui/player/gtl_gui_player_chart_position.h new file mode 100644 index 0000000..06f00a0 --- /dev/null +++ b/gui/player/gtl_gui_player_chart_position.h @@ -0,0 +1,32 @@ +#ifndef CHART_POSITION_H +#define CHART_POSITION_H + +#include "chart/instruments/instrument.h" + +#include "gui/player/gtl_gui_player_chart_position_line.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + class chart_position : public ::chart::instrument::instrument + { + Q_OBJECT + public: + chart_position(::chart::series::series* parent); + + virtual void add(const QPointF &point) override; + virtual void draw(const QPointF &point) override; + + void set_pos(qreal pos); + + private: + chart_position_line* _line; + }; + } + } +} + +#endif // CHART_POSITION_H diff --git a/gui/player/gtl_gui_player_chart_position_line.cpp b/gui/player/gtl_gui_player_chart_position_line.cpp new file mode 100644 index 0000000..3ea5470 --- /dev/null +++ b/gui/player/gtl_gui_player_chart_position_line.cpp @@ -0,0 +1,24 @@ +#include "gtl_gui_player_chart_position_line.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + chart_position_line::chart_position_line(::chart::series::series *parent) + : gtl::gui::chart_line(parent) + { + setFlag(QGraphicsItem::ItemIsMovable, false); + setFlag(QGraphicsItem::ItemIsSelectable, false); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, false); + + _pen.setStyle(Qt::PenStyle::SolidLine); + + setAcceptHoverEvents(false); + + set_pos(0); + } + } + } +} diff --git a/gui/player/gtl_gui_player_chart_position_line.h b/gui/player/gtl_gui_player_chart_position_line.h new file mode 100644 index 0000000..8a1c0b1 --- /dev/null +++ b/gui/player/gtl_gui_player_chart_position_line.h @@ -0,0 +1,22 @@ +#ifndef CHART_POSITION_LINE_H +#define CHART_POSITION_LINE_H + +#include "gui/gtl_gui_chart_line.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + class chart_position_line : public gtl::gui::chart_line + { + Q_OBJECT + public: + chart_position_line(::chart::series::series *parent); + }; + } + } +} + +#endif // CHART_POSITION_LINE_H diff --git a/gui/player/gtl_gui_player_chart_range.cpp b/gui/player/gtl_gui_player_chart_range.cpp new file mode 100644 index 0000000..4758998 --- /dev/null +++ b/gui/player/gtl_gui_player_chart_range.cpp @@ -0,0 +1,75 @@ +#include "gtl_gui_player_chart_range.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + chart_range::chart_range(::chart::series::series* parent) + : ::chart::instrument::instrument(parent) + , _cnt_clicks(0) + { + _line0 = new chart_range_line(parent); + ::chart::instrument::instrument::add(_line0); + connect(_line0, &chart_range_line::signal_moved, this, &chart_range::set_body); + connect(_line0, &chart_line::get_nearest_x, this, &chart_range::get_nearest_x); + + _line1 = new chart_range_line(parent); + ::chart::instrument::instrument::add(_line1); + connect(_line1, &chart_range_line::signal_moved, this, &chart_range::set_body); + connect(_line1, &chart_line::get_nearest_x, this, &chart_range::get_nearest_x); + + _body = new chart_range_body(parent); + ::chart::instrument::instrument::add(_body); + + set_body(); + } + + void chart_range::set(qreal left, qreal right) + { + _line0->set_pos(left); + _line1->set_pos(right); + set_body(); + } + + bool chart_range::is_inside(const QPointF &pos) const + { + return _body->boundingRect().contains(pos) || _line0->boundingRect().contains(pos) || _line1->boundingRect().contains(pos); + } + + qreal chart_range::left() const + { + return qMin(_line0->pos(), _line1->pos()); + } + + qreal chart_range::right() const + { + return qMax(_line0->pos(), _line1->pos()); + } + + void chart_range::add(const QPointF &point) + { + _cnt_clicks++; + + if (_cnt_clicks == 2) + complete(); + } + + void chart_range::draw(const QPointF &point) + { + if (_cnt_clicks < 1) + _line0->set_pos(point.x()); + if (_cnt_clicks < 2) + _line1->set_pos(point.x()); + + set_body(); + } + + void chart_range::set_body() + { + _body->set_range(_line0->pos(), _line1->pos()); + } + } + } +} diff --git a/gui/player/gtl_gui_player_chart_range.h b/gui/player/gtl_gui_player_chart_range.h new file mode 100644 index 0000000..da0afcc --- /dev/null +++ b/gui/player/gtl_gui_player_chart_range.h @@ -0,0 +1,49 @@ +#ifndef CHART_RANGE_H +#define CHART_RANGE_H + +#include +#include "chart/instruments/instrument.h" + +#include "gtl_gui_player_chart_range_line.h" +#include "gtl_gui_player_chart_range_body.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + class chart_range : public ::chart::instrument::instrument + { + Q_OBJECT + public: + chart_range(::chart::series::series* parent); + + void set(qreal left, qreal right); + + bool is_inside(const QPointF& pos) const; + + qreal left() const; + qreal right() const; + + private: + virtual void add(const QPointF &point) override; + virtual void draw(const QPointF &point) override; + + private: + int _cnt_clicks; + chart_range_line* _line0; + chart_range_line* _line1; + chart_range_body* _body; + + private slots: + void set_body(); + + signals: + void get_nearest_x(qreal& x, gtl::gui::chart_line::pos_behaviors); + }; + } + } +} + +#endif // CHART_RANGE_H diff --git a/gui/player/gtl_gui_player_chart_range_body.cpp b/gui/player/gtl_gui_player_chart_range_body.cpp new file mode 100644 index 0000000..8d08691 --- /dev/null +++ b/gui/player/gtl_gui_player_chart_range_body.cpp @@ -0,0 +1,46 @@ +#include "gtl_gui_player_chart_range_body.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + chart_range_body::chart_range_body(::chart::series::series *parent) + : ::chart::instrument::primitive::primitive(parent) + { + setFlag(QGraphicsItem::ItemIsMovable, false); + setFlag(QGraphicsItem::ItemIsSelectable, false); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, false); + + setAcceptHoverEvents(false); + } + + void chart_range_body::set_range(qreal x0, qreal x1) + { + prepareGeometryChange(); + + _left = qMin(x0, x1); + _right = qMax(x0, x1); + } + + QRectF chart_range_body::boundingRect() const + { + qreal left = _series->axis_x()->map_to_widget(_left); + qreal right = _series->axis_x()->map_to_widget(_right); + + QRectF rect_axis = _series->axis_x()->boundingRect(); + QRectF rect = QRectF(left, rect_axis.top(), right - left, rect_axis.height()); + + return rect; + } + + QPainterPath chart_range_body::shape() const { return QPainterPath(); } + + void chart_range_body::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + painter->fillRect(boundingRect(), QBrush(QColor(128, 128, 128, 64))); + } + } + } +} diff --git a/gui/player/gtl_gui_player_chart_range_body.h b/gui/player/gtl_gui_player_chart_range_body.h new file mode 100644 index 0000000..1a49db9 --- /dev/null +++ b/gui/player/gtl_gui_player_chart_range_body.h @@ -0,0 +1,34 @@ +#ifndef CHART_RANGE_BODY_H +#define CHART_RANGE_BODY_H + +#include "chart/instruments/primitives/primitive.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + class chart_range_body : public ::chart::instrument::primitive::primitive + { + Q_OBJECT + public: + chart_range_body(::chart::series::series *parent); + + void set_range(qreal x0, qreal x1); + virtual QRectF boundingRect() const; + + private: + qreal _left; + qreal _right; + + private: + + virtual QPainterPath shape() const; + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + }; + } + } +} + +#endif // CHART_RANGE_BODY_H diff --git a/gui/player/gtl_gui_player_chart_range_line.cpp b/gui/player/gtl_gui_player_chart_range_line.cpp new file mode 100644 index 0000000..caad9da --- /dev/null +++ b/gui/player/gtl_gui_player_chart_range_line.cpp @@ -0,0 +1,51 @@ +#include "gtl_gui_player_chart_range_line.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + chart_range_line::chart_range_line(::chart::series::series *parent) + : gtl::gui::chart_line(parent) + { + set_pos(-1); + setZValue(0); + } +/* + void chart_range_line::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + gtl::gui::chart_line::paint(painter, option, widget); + + + QRectF rect = boundingRect(); + + painter->fillRect(rect, Qt::red); + + painter->setPen(QPen(Qt::white, 1)); + qreal xc = rect.center().x(); + painter->drawLine((QPointF(xc, rect.top())), (QPointF(xc, rect.bottom()))); + + + + + return; + + painter->setPen(Qt::white); + painter->drawLine((QPointF(0, rect.top())), (QPointF(0, rect.bottom()))); + painter->drawLine((QPointF(2, rect.top())), (QPointF(2, rect.bottom()))); + painter->setPen(Qt::black); + painter->drawLine((QPointF(1, rect.top())), (QPointF(1, rect.bottom()))); + } +*/ +/* + QRectF chart_range_line::boundingRect() const + { + QRectF rect = chart::instrument::primitive::line::boundingRect(); + + return QRectF(rect.center().x() - 1.5, rect.top(), 3, rect.height()); + } +*/ + } + } +} diff --git a/gui/player/gtl_gui_player_chart_range_line.h b/gui/player/gtl_gui_player_chart_range_line.h new file mode 100644 index 0000000..e400a02 --- /dev/null +++ b/gui/player/gtl_gui_player_chart_range_line.h @@ -0,0 +1,29 @@ +#ifndef RANGE_LINE_H +#define RANGE_LINE_H + +#include "gui/gtl_gui_chart_line.h" + +namespace gtl +{ + namespace gui + { + namespace player + { + class chart_range_line : public gtl::gui::chart_line + { + Q_OBJECT + public: + chart_range_line(::chart::series::series *parent); + + private: +// void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + private: +// QRectF boundingRect() const; + + }; + } + } +} + +#endif // RANGE_LINE_H diff --git a/gui/player/gtl_gui_player_resampling_dialog.cpp b/gui/player/gtl_gui_player_resampling_dialog.cpp new file mode 100644 index 0000000..14326c0 --- /dev/null +++ b/gui/player/gtl_gui_player_resampling_dialog.cpp @@ -0,0 +1,130 @@ +#include "gtl_gui_player_resampling_dialog.h" +#include "ui_gtl_gui_player_resampling_dialog.h" + +#include "hw/gtl_hw_recorder_wav.h" + +#include + +namespace gtl +{ + namespace gui + { + namespace player + { + resampling_dialog::resampling_dialog(QString path, QWidget *parent) + : QDialog(parent, Qt::Dialog) + , ui(new Ui::player_resampling_dialog) + , _player{ new gtl::hw::player(this)} + { + ui->setupUi(this); + + _time = new QLabel(ui->progress); + _time->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + + QHBoxLayout* progress_layout = new QHBoxLayout(ui->progress); + progress_layout->setContentsMargins(0, 0, 0, 0); + progress_layout->addWidget(_time); + ui->progress->setLayout(progress_layout); + + _player->start(path, -1); + ui->path->setText(path); + for(int i = 0; i < _player->count_ai(); i++) + { + ui->channels->addItem(_player->ai(i)->name()); + ui->channels->item(i)->setCheckState(Qt::Checked); + } + + QVariant data; + _player->get_parameter(4, data); + ui->comment->setText(data.toJsonObject()["comment"].toString()); + + ui->rate->setRange(0.001, _player->rate()); + ui->rate->setValue(_player->rate()); + + + connect(ui->select_path, &QAbstractButton::clicked, this, &resampling_dialog::select_path); + connect(ui->rate, &QDoubleSpinBox::editingFinished, this, &resampling_dialog::rate_changed); + connect(ui->save, &QAbstractButton::clicked, this, &resampling_dialog::save); + connect(_player, >l::hw::player::finished, this, &resampling_dialog::playing_finished); + } + + resampling_dialog::~resampling_dialog() + { + delete ui; + } + + void resampling_dialog::select_path() + { + QString path = QFileDialog::getSaveFileName(this, tr("Select file"), /*QFileInfo(*/ui->path->text()/*).dir().absolutePath()*/, "*.wav"); + if(!path.isEmpty()) + ui->path->setText(path); + } + + void resampling_dialog::rate_changed() + { + QVariant playing_rate = ui->rate->value(); + _player->set_parameter(5, playing_rate); + _player->get_parameter(5, playing_rate); + ui->rate->setValue(playing_rate.toDouble()); + } + + void resampling_dialog::save(bool value) + { + if(value) + { + _player->set_parameter(3, QVariant(100/*0*/)); + + QVariant total_time; + _player->get_parameter(0, total_time); + ui->progress->setMaximum(qRound(total_time.toDouble()*1000)); + ui->progress->setValue(0); + qreal total = total_time.toDouble(); + + + _player->start(_player->id(), -1); + + std::vector ad; + for(int i = 0; i < _player->count_ai(); i++) + { + if(ui->channels->item(i)->checkState() == Qt::Checked) + ad.push_back(_player->ai(i)); + } + + QFileInfo info(ui->path->text()); + gtl::hw::recorder_wav recorder(ad, 0, ui->comment->toPlainText(), info.dir().absolutePath(), info.completeBaseName()); + recorder.start(); + + + _player->start(_player->id(), 0); + + _is_playing = true; + + while(_is_playing) + { + QVariant time; + _player->get_parameter(1, time); + ui->progress->setValue(qRound(time.toDouble()*1000)); + _time->setText(QString::asprintf("%0.3f/%0.3f", time.toDouble(), total)); + + if(!ui->save->isChecked()) + break; + + QApplication::processEvents(); + QThread::msleep(10); + } + + _time->setText(tr("Complete")); + + recorder.stop(); + _player->stop(); + } + } + + void resampling_dialog::playing_finished() + { + _is_playing = false; + ui->save->setChecked(false); + } + } + } +} diff --git a/gui/player/gtl_gui_player_resampling_dialog.h b/gui/player/gtl_gui_player_resampling_dialog.h new file mode 100644 index 0000000..504b438 --- /dev/null +++ b/gui/player/gtl_gui_player_resampling_dialog.h @@ -0,0 +1,44 @@ +#ifndef GTL_GUI_PLAYER_RESAMPLING_DIALOG_H +#define GTL_GUI_PLAYER_RESAMPLING_DIALOG_H + +#include "hw/gtl_hw_player.h" + +#include +#include + +namespace Ui { +class player_resampling_dialog; +} + +namespace gtl +{ + namespace gui + { + namespace player + { + class resampling_dialog : public QDialog + { + Q_OBJECT + + public: + explicit resampling_dialog(QString path, QWidget *parent = nullptr); + ~resampling_dialog(); + + private: + Ui::player_resampling_dialog *ui; + gtl::hw::player* _player; + bool _is_playing; + QLabel* _time; + + + private slots: + void select_path(); + void rate_changed(); + void save(bool); + void playing_finished(); + }; + } + } +} + +#endif // GTL_GUI_PLAYER_RESAMPLING_DIALOG_H diff --git a/gui/player/gtl_gui_player_resampling_dialog.ui b/gui/player/gtl_gui_player_resampling_dialog.ui new file mode 100644 index 0000000..14f9717 --- /dev/null +++ b/gui/player/gtl_gui_player_resampling_dialog.ui @@ -0,0 +1,167 @@ + + + player_resampling_dialog + + + + 0 + 0 + 400 + 300 + + + + Save as + + + + :/player/resources/save_32.png:/player/resources/save_32.png + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + 2 + + + + + + + + ... + + + + + + + + + Qt::Horizontal + + + + + + + + + + 0 + 0 + + + + Rate + + + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + QAbstractSpinBox::NoButtons + + + false + + + 3 + + + + + + + + + + + + + + + + + + + 0 + + + false + + + + + + + Save + + + true + + + false + + + + + + + Close + + + true + + + false + + + + + + + + + + + + + close + clicked() + player_resampling_dialog + reject() + + + 358 + 284 + + + 322 + 270 + + + + + diff --git a/gui/resources/CLOSE_16.png b/gui/resources/CLOSE_16.png new file mode 100644 index 0000000..e7bd6b6 Binary files /dev/null and b/gui/resources/CLOSE_16.png differ diff --git a/gui/resources/NEXT2.png b/gui/resources/NEXT2.png new file mode 100644 index 0000000..5e6ff30 Binary files /dev/null and b/gui/resources/NEXT2.png differ diff --git a/gui/resources/REFRESH.png b/gui/resources/REFRESH.png new file mode 100644 index 0000000..b21cc3e Binary files /dev/null and b/gui/resources/REFRESH.png differ diff --git a/gui/resources/add_24.png b/gui/resources/add_24.png new file mode 100644 index 0000000..77e5231 Binary files /dev/null and b/gui/resources/add_24.png differ diff --git a/gui/resources/add_range.png b/gui/resources/add_range.png new file mode 100644 index 0000000..3f2098f Binary files /dev/null and b/gui/resources/add_range.png differ diff --git a/gui/resources/band_marker.png b/gui/resources/band_marker.png new file mode 100644 index 0000000..cb651f1 Binary files /dev/null and b/gui/resources/band_marker.png differ diff --git a/gui/resources/decline_16.png b/gui/resources/decline_16.png new file mode 100644 index 0000000..1d2d903 Binary files /dev/null and b/gui/resources/decline_16.png differ diff --git a/gui/resources/front_16.png b/gui/resources/front_16.png new file mode 100644 index 0000000..f630605 Binary files /dev/null and b/gui/resources/front_16.png differ diff --git a/gui/resources/harm_marker_24.png b/gui/resources/harm_marker_24.png new file mode 100644 index 0000000..da668a9 Binary files /dev/null and b/gui/resources/harm_marker_24.png differ diff --git a/gui/resources/maximize.png b/gui/resources/maximize.png new file mode 100644 index 0000000..566b7b6 Binary files /dev/null and b/gui/resources/maximize.png differ diff --git a/gui/resources/moon.png b/gui/resources/moon.png new file mode 100644 index 0000000..d95c45d Binary files /dev/null and b/gui/resources/moon.png differ diff --git a/gui/resources/pause_24.png b/gui/resources/pause_24.png new file mode 100644 index 0000000..682a988 Binary files /dev/null and b/gui/resources/pause_24.png differ diff --git a/gui/resources/pin.png b/gui/resources/pin.png new file mode 100644 index 0000000..6925d55 Binary files /dev/null and b/gui/resources/pin.png differ diff --git a/gui/resources/pin_on.png b/gui/resources/pin_on.png new file mode 100644 index 0000000..c344a18 Binary files /dev/null and b/gui/resources/pin_on.png differ diff --git a/gui/resources/play_16.png b/gui/resources/play_16.png new file mode 100644 index 0000000..b2e9a13 Binary files /dev/null and b/gui/resources/play_16.png differ diff --git a/gui/resources/record.png b/gui/resources/record.png new file mode 100644 index 0000000..c06aec0 Binary files /dev/null and b/gui/resources/record.png differ diff --git a/gui/resources/remove_24.png b/gui/resources/remove_24.png new file mode 100644 index 0000000..4ba9742 Binary files /dev/null and b/gui/resources/remove_24.png differ diff --git a/gui/resources/remove_range.png b/gui/resources/remove_range.png new file mode 100644 index 0000000..a837994 Binary files /dev/null and b/gui/resources/remove_range.png differ diff --git a/gui/resources/restore.png b/gui/resources/restore.png new file mode 100644 index 0000000..115794e Binary files /dev/null and b/gui/resources/restore.png differ diff --git a/gui/resources/save_32.png b/gui/resources/save_32.png new file mode 100644 index 0000000..26a3845 Binary files /dev/null and b/gui/resources/save_32.png differ diff --git a/gui/resources/single_marker_24.png b/gui/resources/single_marker_24.png new file mode 100644 index 0000000..6167d09 Binary files /dev/null and b/gui/resources/single_marker_24.png differ diff --git a/gui/resources/stop.png b/gui/resources/stop.png new file mode 100644 index 0000000..9b6ecde Binary files /dev/null and b/gui/resources/stop.png differ diff --git a/gui/resources/sun.png b/gui/resources/sun.png new file mode 100644 index 0000000..30b10d9 Binary files /dev/null and b/gui/resources/sun.png differ diff --git a/gui/spec/gtl_gui_spec_band_marker.cpp b/gui/spec/gtl_gui_spec_band_marker.cpp new file mode 100644 index 0000000..2412f09 --- /dev/null +++ b/gui/spec/gtl_gui_spec_band_marker.cpp @@ -0,0 +1,155 @@ +#include "gtl_gui_spec_band_marker.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + band_marker::band_marker(::chart::series::series* parent, bool is_dark_theme) + : chart_marker(parent, is_dark_theme) + , _value_type(rms) + , _cnt_clicks(0) + { + _line->set_label_visible(false); + + _line1 = new chart_marker_line(parent, -1, false); + ::chart::instrument::instrument::add(_line1); + connect(_line1, &chart_marker_line::get_nearest_x, this, &chart_marker::get_nearest_x); + connect(_line1, &chart_marker_line::get_series_data, this, &chart_marker::get_series_data); + connect(_line1, &chart_marker_line::signal_moved, this, &band_marker::line_moved); + + _body = new band_marker_body(parent, _line, _line1); + ::chart::instrument::instrument::add(_body); + connect(_body, &band_marker_body::signal_moved, this, &band_marker::line_moved); + + + } + + void band_marker::set_idx(int idx) + { + if(idx != _idx) + _body->update(); + + chart_marker::set_idx(idx); + } + + qreal band_marker::left() const + { + return qMin(_line->pos(), _line1->pos()); + } + + void band_marker::set_left(qreal value) + { + if(_line->pos() < _line1->pos()) + _line->set_pos(value); + else + _line1->set_pos(value); + + _body->update(); + line_moved(); + } + + qreal band_marker::right() const + { + return qMax(_line->pos(), _line1->pos()); + } + + void band_marker::set_right(qreal value) + { + if(_line->pos() > _line1->pos()) + _line->set_pos(value); + else + _line1->set_pos(value); + + _body->update(); + line_moved(); + } + + qreal band_marker::width() const + { + return qAbs(_line->pos() - _line1->pos()); + } + + void band_marker::set_width(qreal value) + { + if(_line->pos() < _line1->pos()) + _line1->set_pos(_line->pos() + value); + else + _line->set_pos(_line1->pos() + value); + + _body->update(); + line_moved(); + } + + qreal band_marker::value(int series_idx) + { + QVariantList values; + emit get_series_values(left(), right(), series_idx, values); + + if(values.empty()) + return 0; + + if(_value_type == rms) + { + qreal v = 0; + for(auto it : values) + v += qPow(it.toDouble(), 2); + + return qSqrt(v/2); + } + else if(_value_type == peak) + { + return std::max_element(values.begin(), values.end(), [](const QVariant &value0, const QVariant &value1){return value0.toDouble() < value1.toDouble();})->toDouble(); + } + + return 0; + } + + void band_marker::set_value_type(value_types value_type) + { + if(value_type != _value_type) + { + _value_type = value_type; + emit position_changed(); + } + } + + bool band_marker::contains(const QPointF &point) const + { + return _line->contains(point) ||_line1->contains(point) || _body->contains(point); + } + + band_marker::value_types band_marker::value_type() const + { + return _value_type; + } + + void band_marker::add(const QPointF &point) + { + _cnt_clicks++; + + if (_cnt_clicks == 2) + complete(); + + _body->update(); + } + + void band_marker::draw(const QPointF &point) + { + if (_cnt_clicks < 1) + _line->set_pos(point.x()); + if (_cnt_clicks < 2) + _line1->set_pos(point.x()); + + line_moved(); + _body->update(); + } + + void band_marker::set_type(int type) + { + set_value_type((value_types)type); + } + } + } +} diff --git a/gui/spec/gtl_gui_spec_band_marker.h b/gui/spec/gtl_gui_spec_band_marker.h new file mode 100644 index 0000000..1de9921 --- /dev/null +++ b/gui/spec/gtl_gui_spec_band_marker.h @@ -0,0 +1,60 @@ +#ifndef BAND_MARKER_H +#define BAND_MARKER_H + +#include "gui/gtl_gui_chart_marker.h" +#include "spec/gtl_gui_spec_band_marker_body.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + class band_marker : public chart_marker + { + Q_OBJECT + public: + enum value_types + { + rms, + peak + }; + + public: + band_marker(::chart::series::series* parent, bool is_dark_theme); + virtual void set_idx(int idx) override; + + qreal left() const; + void set_left(qreal value); + qreal right() const; + void set_right(qreal value); + qreal width() const; + void set_width(qreal value); + + virtual qreal value(int series_idx) override; + + value_types value_type() const; + void set_value_type(value_types value_type); + virtual bool contains(const QPointF& point) const; + + protected: + chart_marker_line* _line1; + band_marker_body* _body; + value_types _value_type; + + private: + int _cnt_clicks; + + private: + virtual void add(const QPointF &point) override; + virtual void draw(const QPointF &point) override; + + + public slots: + void set_type(int type); + }; + } + } +} + +#endif // BAND_MARKER_H diff --git a/gui/spec/gtl_gui_spec_band_marker_body.cpp b/gui/spec/gtl_gui_spec_band_marker_body.cpp new file mode 100644 index 0000000..1540f72 --- /dev/null +++ b/gui/spec/gtl_gui_spec_band_marker_body.cpp @@ -0,0 +1,117 @@ +#include "gtl_gui_spec_band_marker_body.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + band_marker_body::band_marker_body(::chart::series::series *parent, chart_marker_line* line0, chart_marker_line* line1) + : ::chart::instrument::primitive::primitive(parent) + , _bounding_line0(line0) + , _bounding_line1(line1) + { + connect(_bounding_line0, &chart_line::signal_moved, this, &band_marker_body::bounding_line_moved); + connect(_bounding_line1, &chart_line::signal_moved, this, &band_marker_body::bounding_line_moved); + + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); + setAcceptHoverEvents(true); + + _cursor = Qt::SizeHorCursor; + } + + void band_marker_body::update() + { + prepareGeometryChange(); + } + + QRectF band_marker_body::boundingRect() const + { + qreal l = qMin(_bounding_line0->pos(), _bounding_line1->pos()); + qreal r = qMax(_bounding_line0->pos(), _bounding_line1->pos()); + + qreal left = _series->axis_x()->map_to_widget(l); + qreal right = _series->axis_x()->map_to_widget(r); + + QRectF rect_axis = _series->axis_x()->boundingRect(); + QRectF rect = QRectF(left, rect_axis.top(), right - left, rect_axis.height()); + + return rect; + } + + QPainterPath band_marker_body::shape() const + { + QPainterPath path; + path.addRect(boundingRect()); + return path; + } + + void band_marker_body::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + QColor color = _pen.color(); + color.setAlpha(64); + painter->fillRect(boundingRect(), QBrush(color)); + + QString lbl = QString::number(_bounding_line0->idx() + 1); + QRectF rect = boundingRect(); + QRectF rect_lbl = painter->fontMetrics().boundingRect(lbl); + + + if(rect.width() > rect_lbl.width()) + rect_lbl.moveTo(rect.center().x() - rect_lbl.width()/2, rect.top()); + else if(rect.left() - rect_lbl.width() - 3 > 0) + rect_lbl.moveTo(rect.left() - rect_lbl.width() - 3, rect.top()); + else + rect_lbl.moveTo(rect.center().x() + 3, rect.top()); + + painter->setPen(_pen); + painter->drawText(rect_lbl, Qt::AlignCenter, lbl); + } + + QVariant band_marker_body::itemChange(GraphicsItemChange change, const QVariant &value) + { + if (change == ItemPositionChange) + { + qreal dx = _series->axis_x()->map_from_widget(_p.x() + value.toPointF().x()) - _series->axis_x()->map_from_widget(_p.x()); + _bounding_line0->set_pos(_line0_pos + dx); + _bounding_line1->set_pos(_line1_pos + dx); + + set_tool_tip(); + + emit signal_moved(); + + prepareGeometryChange(); + return QVariant(QPointF(0, 0)); + } + + return chart::instrument::primitive::primitive::itemChange(change, value); + } + + void band_marker_body::mousePressEvent(QGraphicsSceneMouseEvent *event) + { + _p = event->pos(); + _line0_pos = _bounding_line0->pos(); + _line1_pos = _bounding_line1->pos(); + primitive::mousePressEvent(event); + } + + void band_marker_body::set_tool_tip() + { + qreal left = qMin(_bounding_line0->pos(), _bounding_line1->pos()); + qreal right = qMax(_bounding_line0->pos(), _bounding_line1->pos()); + QString value; + QTextStream(&value) << "left: " << left << "\nright: " << right << "\nband: " << right - left; + + setToolTip(value); + } + + void band_marker_body::bounding_line_moved() + { + prepareGeometryChange(); + + set_tool_tip(); + } + } + } +} diff --git a/gui/spec/gtl_gui_spec_band_marker_body.h b/gui/spec/gtl_gui_spec_band_marker_body.h new file mode 100644 index 0000000..db956b7 --- /dev/null +++ b/gui/spec/gtl_gui_spec_band_marker_body.h @@ -0,0 +1,43 @@ +#ifndef BAND_MARKER_BODY_H +#define BAND_MARKER_BODY_H + +#include "gtl_gui_chart_marker_line.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + class band_marker_body : public ::chart::instrument::primitive::primitive + { + Q_OBJECT + public: + band_marker_body(::chart::series::series *parent, chart_marker_line* line0, chart_marker_line* line1); + void update(); + + private: + virtual QRectF boundingRect() const; + virtual QPainterPath shape() const; + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); + virtual void mousePressEvent(QGraphicsSceneMouseEvent * event); + + void set_tool_tip(); + + private: + chart_marker_line* _bounding_line0; + chart_marker_line* _bounding_line1; + QPointF _p; + qreal _line0_pos; + qreal _line1_pos; + + private slots: + void bounding_line_moved(); + + }; + } + } +} + +#endif // BAND_MARKER_BODY_H diff --git a/gui/spec/gtl_gui_spec_band_marker_type_delegate.cpp b/gui/spec/gtl_gui_spec_band_marker_type_delegate.cpp new file mode 100644 index 0000000..de42611 --- /dev/null +++ b/gui/spec/gtl_gui_spec_band_marker_type_delegate.cpp @@ -0,0 +1,71 @@ +#include "gtl_gui_spec_band_marker_type_delegate.h" + +#include + +#include "gui/spec/gtl_gui_spec_band_markers.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + band_marker_type_delegate::band_marker_type_delegate(QObject *parent) + : QStyledItemDelegate{parent} + { + + } + + QWidget *band_marker_type_delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(option) + + + + QComboBox *editor = new QComboBox(parent); + editor->setFrame(false); + editor->addItems({"rms", "peak"}); + + band_marker* marker = static_cast(((band_markers*)index.model())->at(index.row())); + + connect(editor, &QComboBox::currentIndexChanged, marker, &band_marker::set_type); + + return editor; + } + + void band_marker_type_delegate::setEditorData(QWidget *editor, const QModelIndex &index) const + { + int value = index.model()->data(index, Qt::DisplayRole).toInt(); + static_cast(editor)->setCurrentIndex(value); + } + + void band_marker_type_delegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const + { + model->setData(index, static_cast(editor)->currentIndex(), Qt::EditRole); + } + + void band_marker_type_delegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + editor->setGeometry(option.rect); + } + + void band_marker_type_delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_UNUSED(painter) + Q_UNUSED(option) + Q_UNUSED(index) + } + + QSize band_marker_type_delegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QSize s = index.data(Qt::SizeHintRole).toSize(); + return s.isValid() ? s: QStyledItemDelegate::sizeHint(option, index); + } + + void band_marker_type_delegate::value_changed(int idx) + { + qDebug() << sender() << idx; + } + } + } +} diff --git a/gui/spec/gtl_gui_spec_band_marker_type_delegate.h b/gui/spec/gtl_gui_spec_band_marker_type_delegate.h new file mode 100644 index 0000000..f8ba9c6 --- /dev/null +++ b/gui/spec/gtl_gui_spec_band_marker_type_delegate.h @@ -0,0 +1,36 @@ +#ifndef BAND_MARKER_TYPE_DELEGATE_H +#define BAND_MARKER_TYPE_DELEGATE_H + +#include + +#include "gui/gui_global.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + class GTL_GUI_EXPORT band_marker_type_delegate : public QStyledItemDelegate + { + Q_OBJECT + public: + explicit band_marker_type_delegate(QObject *parent = nullptr); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + private slots: + void value_changed(int idx); + }; + } + } +} + +#endif // BAND_MARKER_TYPE_DELEGATE_H diff --git a/gui/spec/gtl_gui_spec_band_markers.cpp b/gui/spec/gtl_gui_spec_band_markers.cpp new file mode 100644 index 0000000..f0ae1d8 --- /dev/null +++ b/gui/spec/gtl_gui_spec_band_markers.cpp @@ -0,0 +1,144 @@ +#include "gtl_gui_spec_band_markers.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + band_markers::band_markers(QObject *parent) + : gtl::gui::chart_markers_model(parent) + { + } + + QVariant band_markers::headerData(int section, Qt::Orientation orientation, int role) const + { + if(role == Qt::DisplayRole) + { + if(orientation == Qt::Horizontal) + { + if(section == 0) + return tr("ID"); + else if(section == 1) + return tr("left"); + else if(section == 2) + return tr("right"); + else if(section == 3) + return tr("width"); + else if(section == 4) + return tr("type"); + else if(section == 5) + return tr("color"); + else if(section == 6) + return tr("kill"); + } + } + + return QVariant(); + } + + int band_markers::columnCount(const QModelIndex &parent) const + { + return 7; + } + + QVariant band_markers::data(const QModelIndex &index, int role) const + { + if (!index.isValid()) + return QVariant(); + + if(role == Qt::DisplayRole) + { + if(index.column() == 0) + return _markers[index.row()]->idx() + 1; + else if(index.column() == 1) + return static_cast(_markers[index.row()])->left(); + else if(index.column() == 2) + return static_cast(_markers[index.row()])->right(); + else if(index.column() == 3) + return static_cast(_markers[index.row()])->width(); + else if(index.column() == 4) + return static_cast(_markers[index.row()])->value_type(); + else if(index.column() == 5) + return _markers[index.row()]->color(); + } + + + return QVariant(); + } + + bool band_markers::setData(const QModelIndex &index, const QVariant &value, int role) + { + + if(index.column() >= 1 && index.column() <= 3) + { + bool is_ok; + qreal double_value = value.toDouble(&is_ok); + + if (is_ok) + { + if(index.column() == 1) + static_cast(_markers[index.row()])->set_left(double_value); + else if(index.column() == 2) + static_cast(_markers[index.row()])->set_right(double_value); + else if(index.column() == 3) + static_cast(_markers[index.row()])->set_width(double_value); + + emit dataChanged(band_markers::index(index.row(), 1), band_markers::index(index.row(), 3), {Qt::DisplayRole}); + return true; + } + } + else if(index.column() == 4) + { + bool is_ok; + qreal int_value = value.toInt(&is_ok); + + if (is_ok) + { + static_cast(_markers[index.row()])->set_value_type((band_marker::value_types)int_value); + + emit dataChanged(band_markers::index(index.row(), 4), band_markers::index(index.row(), 4), {Qt::DisplayRole}); + return true; + } + } + else if(index.column() == 5) + { + _markers[index.row()]->set_color(value.value()); + + emit dataChanged(band_markers::index(index.row(), 5), band_markers::index(index.row(), 5), {Qt::DisplayRole}); + return true; + } + + return false; + } + + Qt::ItemFlags band_markers::flags(const QModelIndex &index) const + { + if (!index.isValid()) + return Qt::NoItemFlags; + + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + + if(index.column() >= 1 && index.column() <= 5) + flags |= Qt::ItemIsEditable; + + return flags; + } + + + + void band_markers::marker_changed() + { + auto it = std::find(_markers.begin(), _markers.end(), sender()); + int idx = std::distance(_markers.begin(), it); + if(idx < _markers.size()) + emit dataChanged(index(idx, 1), index(idx, 3)); + } + + void band_markers::marker_deleting() + { + remove(static_cast(sender())); + } + } + } +} diff --git a/gui/spec/gtl_gui_spec_band_markers.h b/gui/spec/gtl_gui_spec_band_markers.h new file mode 100644 index 0000000..a84200c --- /dev/null +++ b/gui/spec/gtl_gui_spec_band_markers.h @@ -0,0 +1,44 @@ +#ifndef BAND_MARKERS_H +#define BAND_MARKERS_H + +#include + +#include "gtl_gui_spec_band_marker.h" +#include "gtl_gui_chart_markers_model.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + class band_markers : public gtl::gui::chart_markers_model + { + Q_OBJECT + + public: + explicit band_markers(QObject *parent = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // Editable: + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + + private slots: + virtual void marker_changed() override; + virtual void marker_deleting() override; + }; + } + } +} + +#endif // BAND_MARKERS_H diff --git a/gui/spec/gtl_gui_spec_band_markers_view.cpp b/gui/spec/gtl_gui_spec_band_markers_view.cpp new file mode 100644 index 0000000..d1fb700 --- /dev/null +++ b/gui/spec/gtl_gui_spec_band_markers_view.cpp @@ -0,0 +1,52 @@ +#include "gtl_gui_spec_band_markers_view.h" + +#include + +#include "gui/gtl_gui_chart_marker_color_delegate.h" +#include "gui/gtl_gui_chart_marker_kill_delegate.h" +#include "gui/spec/gtl_gui_spec_band_marker_type_delegate.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + band_markers_view::band_markers_view(band_markers* markers, QWidget *parent) + : QTreeView(parent) + , _markers(markers) + { + setModel(markers); + setItemDelegateForColumn(4, new band_marker_type_delegate(this)); + setItemDelegateForColumn(5, new chart_marker_color_delegate(this)); + setItemDelegateForColumn(6, new chart_marker_kill_delegate(this)); + setColumnWidth(6, 30); + + connect(markers, + &QAbstractItemModel::rowsInserted, + [=](const QModelIndex &parent, int first, int last) + { + for(int i = first; i <= last; i++) + { + openPersistentEditor(markers->index(i, 4, parent)); + openPersistentEditor(markers->index(i, 5, parent)); + openPersistentEditor(markers->index(i, 6, parent)); + } + } + ); + + connect(markers, >l::gui::chart_markers_model::changed, this, &band_markers_view::open_persistent_editors); + } + + void band_markers_view::open_persistent_editors() + { + for(int i = 0; i < _markers->rowCount(); i++) + { + openPersistentEditor(_markers->index(i, 4)); + openPersistentEditor(_markers->index(i, 5)); + openPersistentEditor(_markers->index(i, 6)); + } + } + } + } +} diff --git a/gui/spec/gtl_gui_spec_band_markers_view.h b/gui/spec/gtl_gui_spec_band_markers_view.h new file mode 100644 index 0000000..92aa11a --- /dev/null +++ b/gui/spec/gtl_gui_spec_band_markers_view.h @@ -0,0 +1,31 @@ +#ifndef BAND_MARKERS_VIEW_H +#define BAND_MARKERS_VIEW_H + +#include +#include "gtl_gui_spec_band_markers.h" + +#include "gui/gui_global.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + class GTL_GUI_EXPORT band_markers_view : public QTreeView + { + Q_OBJECT + public: + band_markers_view(band_markers* markers, QWidget *parent = nullptr); + + private: + band_markers* _markers; + + private slots: + void open_persistent_editors(); + }; + } + } +} + +#endif // BAND_MARKERS_VIEW_H diff --git a/gui/spec/gtl_gui_spec_harm_marker.cpp b/gui/spec/gtl_gui_spec_harm_marker.cpp new file mode 100644 index 0000000..b7ac853 --- /dev/null +++ b/gui/spec/gtl_gui_spec_harm_marker.cpp @@ -0,0 +1,303 @@ +#include "gtl_gui_spec_harm_marker.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + harm_marker::harm_marker(::chart::series::series* parent, bool is_dark_theme) + : chart_marker(parent, is_dark_theme) + { + _line->set_behavior(gtl::gui::chart_line::free); + _harms.push_back(_line); + } + + void harm_marker::set_pos(qreal x) + { + chart_marker::set_pos(x); + + set_harms_pos(); + } + + int harm_marker::hamrs() const + { + return _harms.size(); + } + + void harm_marker::set_harms(int harms) + { + if(harms < 1) + harms = 1; + + while(_harms.size() != harms) + { + if(_harms.size() < harms) + { + gtl::gui::chart_marker_line* line = new chart_marker_line(_series, -1, true); + line->set_behavior(gtl::gui::chart_line::free); + line->set_width(width()); + ::chart::instrument::instrument::add(line); + connect(line, &chart_marker_line::get_nearest_x, this, &chart_marker::get_nearest_x); + connect(line, &chart_marker_line::get_series_data, this, &chart_marker::get_series_data); + connect(line, &chart_marker_line::signal_moved, this, &harm_marker::line_moved); + + _harms.push_back(line); + } + else if(_harms.size() > harms) + { + gtl::gui::chart_marker_line* line = _harms.back(); + ::chart::instrument::instrument::remove(line); + _harms.erase(--_harms.end()); + line->deleteLater(); + } + } + + set_harms_pos(); + + set_labels(); + + set_modulations_lines(); + + } + + qreal harm_marker::get_harms_pos(int idx) + { + return _harms[idx]->pos(); + } + + void harm_marker::set_idx(int idx) + { + gtl::gui::chart_marker::set_idx(idx); + + set_labels(); + } + + modulation *harm_marker::modulation_(int idx) const + { + return _modulations[idx]; + } + + int harm_marker::modulations() const + { + return _modulations.size(); + } + + void harm_marker::remove_modulation(modulation *m) + { + auto it = std::find(_modulations.begin(), _modulations.end(), m); + if(it == _modulations.end()) + return; + + while(!m->lines.empty()) + { + gtl::gui::chart_marker_line* line = m->lines.back(); + ::chart::instrument::instrument::remove(line); + m->lines.erase(--m->lines.end()); + line->deleteLater(); + } + + + + + int idx = std::distance(_modulations.begin(), it); + + emit modulation_removing(idx); + + _modulations.erase(it); + + emit modulation_removed(); + } + + int harm_marker::index_of(const modulation *m) const + { + return std::distance(_modulations.begin(), std::find(_modulations.begin(), _modulations.end(), m)); + } + + void harm_marker::set_width(qreal value) + { + for(auto harm: _harms) + harm->set_width(value); + } + + void harm_marker::set_harms_pos(int idx) + { + qreal freq = _harms[idx]->pos() / (idx + 1); + + for(int i = 0; i < _harms.size(); i++) + _harms[i]->set_pos(freq * (i + 1)); + + set_modulations_lines(); + } + + void harm_marker::set_labels() + { + int marker_idx = _line->idx(); + for(int i = 0; i < _harms.size(); i++) + _harms[i]->set_label(QString::number(marker_idx + 1) + "." + QString::number(i + 1)); + } + + void harm_marker::set_modulation_lines(modulation *m) + { + int cnt = hamrs() * m->count() * 2; + while(m->lines.size() != cnt) + { + if(m->lines.size() < cnt) + { + gtl::gui::chart_marker_line* line = new chart_marker_line(_series, -1, true); + line->set_behavior(gtl::gui::chart_line::free); + line->set_label_visible(false); + ::chart::instrument::instrument::add(line); + line->set_color(m->color()); + line->set_width(m->width()); + connect(line, &chart_marker_line::get_nearest_x, this, &chart_marker::get_nearest_x); + connect(line, &chart_marker_line::get_series_data, this, &chart_marker::get_series_data); + connect(line, &chart_marker_line::signal_moved, m, &modulation::line_moved); + + m->lines.push_back(line); + } + else if(m->lines.size() > cnt) + { + gtl::gui::chart_marker_line* line = m->lines.back(); + ::chart::instrument::instrument::remove(line); + m->lines.erase(--m->lines.end()); + line->deleteLater(); + } + } + + for(int i = 0; i < _harms.size(); i++) + { + for(int j = 0; j < m->count(); j++) + { + m->lines[i*m->count()*2 + j*2]->set_pos(_harms[i]->pos() + (j + 1)*m->freq()); + m->lines[i*m->count()*2 + j*2 + 1]->set_pos(_harms[i]->pos() - (j + 1)*m->freq()); + } + } + } + + void harm_marker::set_modulations_lines() + { + for(auto m : _modulations) + set_modulation_lines(m); + } + + void harm_marker::line_moved() + { + int idx = std::distance(_harms.begin(), std::find(_harms.begin(), _harms.end(), sender())); + + set_harms_pos(idx); + + chart_marker::line_moved(); + } + + void harm_marker::set_color(const QColor &color) + { + for(auto harm: _harms) + harm->set_color(color); + + _color = color; + } + + modulation* harm_marker::add_modulation() + { + modulation* m = new modulation(this); + _modulations.push_back(m); + set_modulation_lines(m); + return m; + } + + modulation::modulation(harm_marker *parent) + : QObject(parent) + , _parent(parent) + , _freq(10) + , _cnt(1) + , _color(parent->color()) + { + + } + + modulation::~modulation() + { + _parent->remove_modulation(this); + } + + harm_marker *modulation::parent_marker() const + { + return _parent; + } + + qreal modulation::freq() const + { + return qAbs(_freq); + } + + void modulation::set_freq(qreal value) + { + _freq = value; + _parent->set_modulation_lines(this); + } + + int modulation::count() const + { + return _cnt; + } + + void modulation::set_count(int value) + { + if(value < 1) + value = 1; + + _cnt = value; + _parent->set_modulation_lines(this); + } + + QColor modulation::color() const + { + return _color; + } + + qreal modulation::width() const + { + if(lines.empty()) + return 1; + + return lines.front()->width(); + } + + void modulation::set_width(qreal value) + { + for(auto line: lines) + line->set_width(value); + } + + int modulation::index() const + { + return _parent->index_of(this); + } + + void modulation::set_color(QColor value) + { + _color = value; + for(auto line: lines) + line->set_color(_color); + } + + void modulation::line_moved() + { + auto it = std::find(lines.begin(), lines.end(), sender()); + int idx = std::distance(lines.begin(), it); + + int harm_idx = idx/(2*_cnt); + + int line_idx = idx%(2*_cnt)/2; + + _freq = (_parent->get_harms_pos(harm_idx) - (*it)->pos())/(line_idx + 1); + + _parent->set_modulation_lines(this); + + emit freq_changed(); + } + + } + } +} diff --git a/gui/spec/gtl_gui_spec_harm_marker.h b/gui/spec/gtl_gui_spec_harm_marker.h new file mode 100644 index 0000000..5997c72 --- /dev/null +++ b/gui/spec/gtl_gui_spec_harm_marker.h @@ -0,0 +1,98 @@ +#ifndef HARM_MARKER_H +#define HARM_MARKER_H + +#include "gui/gtl_gui_chart_marker.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + class modulation; + class harm_marker : public chart_marker + { + Q_OBJECT + + friend class modulation; + + public: + harm_marker(::chart::series::series* parent, bool is_dark_theme); + + virtual void set_pos(qreal x) override; + + int hamrs() const; + virtual void set_harms(int); + qreal get_harms_pos(int idx); + + virtual void set_idx(int idx) override; + + modulation* add_modulation(); + modulation* modulation_(int idx) const; + int modulations() const; + void remove_modulation(modulation* m); + + int index_of(const modulation* m) const; + + void set_width(qreal value) override; + + private: + void set_harms_pos(int idx = 0); + void set_labels(); + void set_modulation_lines(modulation* m); + void set_modulations_lines(); + + private: + std::vector _harms; + std::vector _modulations; + + signals: + void modulation_removing(int idx); + void modulation_removed(); + + protected slots: + virtual void line_moved() override; + virtual void set_color(const QColor &color) override; + }; + + class modulation : public QObject + { + Q_OBJECT + + friend class harm_marker; + + public: + modulation(harm_marker* parent); + ~modulation(); + harm_marker* parent_marker() const; + + qreal freq() const; + void set_freq(qreal value); + int count() const; + void set_count(int value); + QColor color() const; + + qreal width() const; + void set_width(qreal value); + + int index() const; + + std::vector lines; + + private: + harm_marker* _parent; + qreal _freq; + int _cnt; + QColor _color; + + public slots: + void set_color(QColor value); + void line_moved(); + signals: + void freq_changed(); + }; + } + } +} + +#endif // HARM_MARKER_H diff --git a/gui/spec/gtl_gui_spec_harm_markers.cpp b/gui/spec/gtl_gui_spec_harm_markers.cpp new file mode 100644 index 0000000..4762dd3 --- /dev/null +++ b/gui/spec/gtl_gui_spec_harm_markers.cpp @@ -0,0 +1,293 @@ +#include "gtl_gui_spec_harm_markers.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + harm_markers::harm_markers(QObject *parent) + : gtl::gui::chart_markers_model{parent} + { + + } + + QVariant harm_markers::headerData(int section, Qt::Orientation orientation, int role) const + { + if(role == Qt::DisplayRole) + { + if(orientation == Qt::Horizontal) + { + if(section == 0) + return tr("ID"); + else if(section == 1) + return tr("freq"); + else if(section == 2) + return tr("count"); + else if(section == 3) + return tr("width"); + else if(section == 4) + return tr("color"); + else if(section == 5) + return tr("kill"); + } + } + + return QVariant(); + } + + int harm_markers::columnCount(const QModelIndex &parent) const + { + return 6; + } + + int harm_markers::rowCount(const QModelIndex &parent) const + { + if(!parent.isValid()) + return (int)_markers.size(); + else if(marker_at(parent)) + return static_cast(parent.internalPointer())->modulations(); + else + return 0; + + } + + QModelIndex harm_markers::index(int row, int column, const QModelIndex &parent) const + { + harm_marker* m = marker_at(parent); + if(/*parent.isValid()*/m) + { + if(row < 0 || row >= m->modulations()) + return QModelIndex(); + else + return createIndex(row, column, m->modulation_(row)); + } + + if(row < 0 || row >= _markers.size()) + return QModelIndex(); + + return createIndex(row, column, _markers[row]); + } + + QModelIndex harm_markers::parent(const QModelIndex &index) const + { + if(!index.isValid()) + return QModelIndex(); + + harm_marker* maker = marker_at(index); + if(maker) + return QModelIndex(); + else + { + modulation* m = static_cast(index.internalPointer()); + int idx = std::distance(_markers.begin(), std::find(_markers.begin(), _markers.end(), m->parent_marker())); + + if(idx >= _markers.size()) + return QModelIndex(); + + return createIndex(idx, /*index.column()*/0, _markers[idx]); + } + + } + + QVariant harm_markers::data(const QModelIndex &index, int role) const + { + if (!index.isValid()) + return QVariant(); + + if(role == Qt::DisplayRole) + { + if(index.parent().isValid()) + { + harm_marker* marker = marker_at(index.parent()); + if(index.column() == 0) + return index.row() + 1; + else if(index.column() == 1) + return marker->modulation_(index.row())->freq(); + else if(index.column() == 2) + return marker->modulation_(index.row())->count(); + else if(index.column() == 3) + return qMax(1.0, marker->modulation_(index.row())->width()); + else if(index.column() == 4) + return marker->modulation_(index.row())->color(); + } + else + { + if(index.column() == 0) + return _markers[index.row()]->idx() + 1; + else if(index.column() == 1) + return _markers[index.row()]->pos(); + else if(index.column() == 2) + return static_cast(_markers[index.row()])->hamrs(); + else if(index.column() == 3) + return qMax(1.0, _markers[index.row()]->width()); + else if(index.column() == 4) + return _markers[index.row()]->color(); + } + } + + return QVariant(); + } + + bool harm_markers::setData(const QModelIndex &index, const QVariant &value, int role) + { + + harm_marker* marker = marker_at(index.parent()); + + if(index.column() == 1) + { + bool is_ok; + qreal double_value = value.toDouble(&is_ok); + + if (is_ok) + { + if(marker) + marker->modulation_(index.row())->set_freq(double_value); + else + _markers[index.row()]->set_pos(double_value); + + emit dataChanged(index, index, {Qt::DisplayRole}); + return true; + } + } + if(index.column() == 2) + { + bool is_ok; + qreal int_value = value.toInt(&is_ok); + + if (is_ok) + { + + if(marker) + marker->modulation_(index.row())->set_count(int_value); + else + static_cast(_markers[index.row()])->set_harms(int_value); + + emit dataChanged(index, index, {Qt::DisplayRole}); + return true; + } + } + if(index.column() == 3) + { + bool is_ok; + qreal int_value = qMax(1, value.toInt(&is_ok)); + + if (is_ok) + { + + if(marker) + marker->modulation_(index.row())->set_width(int_value); + else + static_cast(_markers[index.row()])->set_width(int_value); + + emit dataChanged(index, index, {Qt::DisplayRole}); + return true; + } + } + else if(index.column() == 4) + { + if(marker) + marker->modulation_(index.row())->set_color(value.value()); + else + _markers[index.row()]->set_color(value.value()); + + emit dataChanged(index, index, {Qt::DisplayRole}); + return true; + } + + + return false; + } + + Qt::ItemFlags harm_markers::flags(const QModelIndex &index) const + { + if (!index.isValid()) + return Qt::NoItemFlags; + + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + + if(index.column() >= 1 && index.column() <= 4) + flags |= Qt::ItemIsEditable; + + return flags; + } + + void harm_markers::connect_color_changed_signal(color_box *sender, const QModelIndex &index) + { + harm_marker* marker = marker_at(index.parent()); + if(marker) + connect(sender, &color_box::color_changed, marker->modulation_(index.row()), &modulation::set_color); + else + chart_markers_model::connect_color_changed_signal(sender, index); + } + + void harm_markers::connect_kill_signal(QAbstractButton *sender, const QModelIndex &index) + { + harm_marker* marker = marker_at(index.parent()); + if(marker) + { + connect(sender, &QAbstractButton::clicked, marker->modulation_(index.row()), &modulation::deleteLater); + } + else + chart_markers_model::connect_kill_signal(sender, index); + } + + void harm_markers::add(chart_marker *marker) + { + gtl::gui::chart_markers_model::add(marker); + + connect(static_cast(marker), &harm_marker::modulation_removing, this, &harm_markers::begin_remove_modulation); + connect(static_cast(marker), &harm_marker::modulation_removed, this, &harm_markers::end_remove_modulation); + } + + void harm_markers::add_modulations(const QModelIndex &index) + { + harm_marker* marker = marker_at(index); + if(marker) + { + beginInsertRows(createIndex(index.row(), 0, index.internalPointer()), marker->modulations(), marker->modulations()); + modulation* m = marker->add_modulation(); + endInsertRows(); + + connect(m, &modulation::freq_changed, this, &harm_markers::modulation_freq_changed); + } + } + + harm_marker *harm_markers::marker_at(const QModelIndex &index) const + { + if(index.internalPointer()) + if(QString(static_cast(index.internalPointer())->metaObject()->className()).contains("harm_marker")) + return static_cast(index.internalPointer()); + + return nullptr; + } + + void harm_markers::marker_changed() + { + auto it = std::find(_markers.begin(), _markers.end(), sender()); + int idx = std::distance(_markers.begin(), it); + if(idx < _markers.size()) + emit dataChanged(index(idx, 1), index(idx, 1)); + } + + void harm_markers::begin_remove_modulation(int idx) + { + int row = std::distance(_markers.begin(), std::find(_markers.begin(), _markers.end(), sender())); + if(row < _markers.size()) + beginRemoveRows(index(row, 0), idx, idx); + } + + void harm_markers::end_remove_modulation() + { + endRemoveRows(); + } + + void harm_markers::modulation_freq_changed() + { + QModelIndex index = createIndex(static_cast(sender())->index(), 1, sender()); + + emit dataChanged(index, index); + } + } + } +} diff --git a/gui/spec/gtl_gui_spec_harm_markers.h b/gui/spec/gtl_gui_spec_harm_markers.h new file mode 100644 index 0000000..9696f20 --- /dev/null +++ b/gui/spec/gtl_gui_spec_harm_markers.h @@ -0,0 +1,57 @@ +#ifndef HARM_MARKERS_H +#define HARM_MARKERS_H + +#include "gtl_gui_chart_markers_model.h" +#include "gtl_gui_spec_harm_marker.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + class harm_markers : public gtl::gui::chart_markers_model + { + Q_OBJECT + public: + explicit harm_markers(QObject *parent = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + virtual QModelIndex parent(const QModelIndex& index) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // Editable: + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + virtual void connect_color_changed_signal(color_box* sender, const QModelIndex& index) override; + virtual void connect_kill_signal(QAbstractButton* sender, const QModelIndex& index) override; + + virtual void add(chart_marker* marker) override; + + void add_modulations(const QModelIndex& index); + + private: + harm_marker* marker_at(const QModelIndex& index) const; + + private slots: + virtual void marker_changed() override; + void begin_remove_modulation(int idx); + void end_remove_modulation(); + void modulation_freq_changed(); + + }; + } + } +} + +#endif // HARM_MARKERS_H diff --git a/gui/spec/gtl_gui_spec_harm_markers_view.cpp b/gui/spec/gtl_gui_spec_harm_markers_view.cpp new file mode 100644 index 0000000..fa30cef --- /dev/null +++ b/gui/spec/gtl_gui_spec_harm_markers_view.cpp @@ -0,0 +1,106 @@ +#include "gtl_gui_spec_harm_markers_view.h" + +#include + +#include "gui/gtl_gui_chart_marker_color_delegate.h" +#include "gui/gtl_gui_chart_marker_kill_delegate.h" + +#include "spec/gtl_gui_spec_harm_marker.h" + + +namespace gtl +{ + namespace gui + { + namespace spec + { + harm_markers_view::harm_markers_view(harm_markers* markers, QWidget *parent) + : QTreeView{parent} + , _markers(markers) + { +// verticalHeader()->setDefaultSectionSize(24); + setContextMenuPolicy(Qt::DefaultContextMenu); + setModel(markers); + setItemDelegateForColumn(4, new chart_marker_color_delegate(this)); + setItemDelegateForColumn(5, new chart_marker_kill_delegate(this)); + setColumnWidth(5, 30); + setLocale(QLocale(QLocale::C)); +/* + connect(markers, + &QAbstractItemModel::dataChanged, + [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) + { + for(int i = 0; i < markers->rowCount(); i++) + { + openPersistentEditor(markers->index(i, 3)); + openPersistentEditor(markers->index(i, 4)); + } + } + ); +*/ + connect(markers, + &QAbstractItemModel::rowsInserted, + [=](const QModelIndex &parent, int first, int last) + { + for(int i = first; i <= last; i++) + { + openPersistentEditor(markers->index(i, 4, parent)); + openPersistentEditor(markers->index(i, 5, parent)); + } + } + ); + connect(this, &QTreeView::expanded, this, &harm_markers_view::node_expanded); + + connect(markers, >l::gui::chart_markers_model::changed, this, &harm_markers_view::open_persistent_editors); + + _add_modulating_harmonics_action = _popup_menu.addAction(tr("Add modulating harmonics")); + connect(_add_modulating_harmonics_action, &QAction::triggered, this, &harm_markers_view::add_modulation); + } + + void harm_markers_view::contextMenuEvent(QContextMenuEvent *event) + { + _popup_index = indexAt(event->pos()); + if(!_popup_index.isValid()) + return; + if(_popup_index.parent().isValid()) + return; + + _popup_menu.popup(event->globalPos()); + } + + void harm_markers_view::add_modulation() + { + _markers->add_modulations(_popup_index); + expand(_popup_index); + } + + void harm_markers_view::open_persistent_editors() + { + for(int i = 0; i < _markers->rowCount(); i++) + { + openPersistentEditor(_markers->index(i, 4)); + openPersistentEditor(_markers->index(i, 5)); + /* + QModelIndex marker_index = _markers->index(i, 0); + for(int j = 0; j < _markers->rowCount(marker_index); j++) + { + openPersistentEditor(_markers->index(j, 3, marker_index)); + openPersistentEditor(_markers->index(j, 4, marker_index)); + } + */ + + } + + } + + void harm_markers_view::node_expanded(const QModelIndex &index) + { + for(int j = 0; j < _markers->rowCount(index); j++) + { + openPersistentEditor(_markers->index(j, 4, index)); + openPersistentEditor(_markers->index(j, 5, index)); + } + } + } + } +} diff --git a/gui/spec/gtl_gui_spec_harm_markers_view.h b/gui/spec/gtl_gui_spec_harm_markers_view.h new file mode 100644 index 0000000..ff7b2e6 --- /dev/null +++ b/gui/spec/gtl_gui_spec_harm_markers_view.h @@ -0,0 +1,40 @@ +#ifndef HARM_MARKERS_VIEW_H +#define HARM_MARKERS_VIEW_H + +#include +#include "gtl_gui_spec_harm_markers.h" + +#include "gui/gui_global.h" + +namespace gtl +{ + namespace gui + { + namespace spec + { + class GTL_GUI_EXPORT harm_markers_view : public QTreeView + { + Q_OBJECT + public: + harm_markers_view(harm_markers* markers, QWidget *parent = nullptr); + + private: + QMenu _popup_menu; + QModelIndex _popup_index; + QAction* _add_modulating_harmonics_action; + harm_markers* _markers; + + private: + virtual void contextMenuEvent(QContextMenuEvent *event) override; + + private slots: + void add_modulation(); + void open_persistent_editors(); + void node_expanded(const QModelIndex&); + + }; + } + } +} + +#endif // HARM_MARKERS_VIEW_H diff --git a/gui/spgr/gtl_gui_spgr_chart.cpp b/gui/spgr/gtl_gui_spgr_chart.cpp new file mode 100644 index 0000000..cf6d584 --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_chart.cpp @@ -0,0 +1,121 @@ + +#include "gtl_gui_spgr_chart.h" + +namespace gtl { + namespace gui { + namespace spgr{ + + chart::chart(QWidget* parent, ::chart::axis_horz *axis_x, ::chart::axis_vert *axis_y) + : ::chart::widget(parent, axis_x, axis_y) + , _marker(nullptr) + { + } + + chart::~chart() + { + remove(); + } + + void chart::add(::chart::series::series *s) + { + if(_marker) + _marker->set_series(s); + + remove(); + ::chart::widget::add(s); + + connect(static_cast(s), &series::freq_chart_changed, this, &chart::f_widget_changed); + connect(static_cast(s), &series::time_chart_changed, this, &chart::t_widget_changed); + + if(_marker == nullptr) + { + _marker = new spgr::marker(s); + connect(_marker, &spgr::marker::get_nearest_x, this, &chart::get_neares_series_x); + connect(_marker, &spgr::marker::get_nearest_y, this, &chart::get_neares_series_y); + connect(_marker, &spgr::marker::position_changed, this, &chart::marker_pos_changed); + ::chart::widget::add(_marker); + } + + _marker->set_pos(); + } + + void chart::mousePressEvent(QMouseEvent *event) + { + _mouse_pos_press = event->pos(); + + ::chart::widget::mousePressEvent(event); + } + + void chart::mouseReleaseEvent(QMouseEvent *event) + { + if(_series.empty()) + return; + + if(_tool_zooming->boundingRect().width() < 5) + { + _marker->set_pos(_axis_x->map_from_widget(event->pos().x()), _axis_y->map_from_widget(event->pos().y())); + marker_pos_changed(); + } + + ::chart::widget::mouseReleaseEvent(event); + } + + void chart::mouseDoubleClickEvent(QMouseEvent *event) + { + if(event->button() == Qt::LeftButton) + { + if(_series.empty()) + return; + + series* s = static_cast(_series[0]); + s->autoscale(); + } + } + + void chart::mouseMoveEvent(QMouseEvent *event) + { + // if(!_tool_zooming->isVisible()) + // { + // for(auto it : _series) + // static_cast(it)->set_tool_tip(event->pos()); + // } + + ::chart::widget::mouseMoveEvent(event); + } + + void chart::get_neares_series_x(qreal &x, chart_line::pos_behaviors) + { + series* s = static_cast(_series[0]); + qreal res = s->spec_res(); + int idx = std::round(x/res); + x = idx * res/* + res/2*/; + } + + void chart::get_neares_series_y(qreal &y, chart_line::pos_behaviors) + { + series* s = static_cast(_series[0]); + qreal time = s->spec_time(); + int idx = (y/time); + y = idx * time + time/2; + } + + void chart::marker_pos_changed() + { + series* s = static_cast(_series[0]); + s->set_pos(_marker->pos()); + } + + void chart::t_widget_changed() + { + _marker->set_visible_vert_line(static_cast(_series[0])->time_widget()); + } + + void chart::f_widget_changed() + { + _marker->set_visible_horz_line(static_cast(_series[0])->freq_widget()); + } + + } + } // namespace gui +} // namespace gtl + diff --git a/gui/spgr/gtl_gui_spgr_chart.h b/gui/spgr/gtl_gui_spgr_chart.h new file mode 100644 index 0000000..0fbc7b1 --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_chart.h @@ -0,0 +1,49 @@ + +#ifndef GTL_GUI_SPGR_CHART_H +#define GTL_GUI_SPGR_CHART_H + +#include + +#include "chart/widget_chart.h" +#include "gui/gui_global.h" +#include "gtl_gui_spgr_series.h" + +#include "gui/spgr/gtl_gui_spgr_chart_marker.h" + + +namespace gtl { + namespace gui { + namespace spgr{ + + class GTL_GUI_EXPORT chart : public ::chart::widget + { + Q_OBJECT + public: + chart(QWidget* parent = NULL, ::chart::axis_horz *axis_x = NULL, ::chart::axis_vert *axis_y = NULL); + ~chart(); + + virtual void add(::chart::series::series *s); + + private: + QPoint _mouse_pos_press; + QPoint _mouse_pos_release; + spgr::marker* _marker; + + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + + private slots: + void get_neares_series_x(qreal &x, chart_line::pos_behaviors); + void get_neares_series_y(qreal &y, chart_line::pos_behaviors); + void marker_pos_changed(); + void t_widget_changed(); + void f_widget_changed(); + + }; + } + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_SPGR_CHART_H diff --git a/gui/spgr/gtl_gui_spgr_chart_marker.cpp b/gui/spgr/gtl_gui_spgr_chart_marker.cpp new file mode 100644 index 0000000..06b13db --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_chart_marker.cpp @@ -0,0 +1,72 @@ +#include "gtl_gui_spgr_chart_marker.h" + +namespace gtl +{ + namespace gui + { + namespace spgr + { + marker::marker(::chart::series::series* parent) + : ::chart::instrument::instrument(parent) + { + set_color(Qt::white); + _vert_line = new chart_marker_line(parent, 0); + _vert_line->setVisible(false); + ::chart::instrument::instrument::add(_vert_line); + connect(_vert_line, &chart_line::get_nearest_x, this, &marker::get_nearest_x); +// connect(_line, &chart_marker_line::get_series_data, this, &chart_marker::get_series_data); + connect(_vert_line, &chart_line::signal_moved, this, &marker::position_changed); + connect(_vert_line, &chart_line::signal_moved, this, &marker::set_labels); + + _horz_line = new chart_marker_line(parent, 0, true, Qt::Horizontal); + _horz_line->setVisible(false); + ::chart::instrument::instrument::add(_horz_line); + connect(_horz_line, &chart_line::get_nearest_x, this, &marker::get_nearest_y); +// connect(_line, &chart_marker_line::get_series_data, this, &chart_marker::get_series_data); + connect(_horz_line, &chart_line::signal_moved, this, &marker::position_changed); + connect(_horz_line, &chart_line::signal_moved, this, &marker::set_labels); + + set_labels(); + } + + void marker::set_pos(qreal x, qreal y) + { + _vert_line->set_pos(x); + _horz_line->set_pos(y); + set_labels(); + } + + void marker::set_pos() + { + set_pos(_vert_line->pos(), _horz_line->pos()); + } + + QPointF marker::pos() const + { + return QPointF(_vert_line->pos(), _horz_line->pos()); + } + + void marker::add(const QPointF &point) + { + + } + + void marker::set_labels() + { + _vert_line->set_label(QString::number(_vert_line->pos())); + _horz_line->set_label(QString::number(_horz_line->pos())); + + } + + void marker::set_visible_vert_line(bool value) + { + _vert_line->setVisible(value); + } + + void marker::set_visible_horz_line(bool value) + { + _horz_line->setVisible(value); + } + } + } +} diff --git a/gui/spgr/gtl_gui_spgr_chart_marker.h b/gui/spgr/gtl_gui_spgr_chart_marker.h new file mode 100644 index 0000000..63171fa --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_chart_marker.h @@ -0,0 +1,47 @@ +#ifndef CHART_MAKER_H +#define CHART_MAKER_H + +#include "chart/instruments/instrument.h" + +#include "gui/gtl_gui_chart_marker_line.h" + +namespace gtl +{ + namespace gui + { + namespace spgr + { + class marker : public ::chart::instrument::instrument + { + Q_OBJECT + public: + marker(::chart::series::series* parent); + void set_pos(qreal x, qreal y); + void set_pos(); + QPointF pos() const; + + private: + chart_marker_line* _vert_line; + chart_marker_line* _horz_line; + + private: + virtual void add(const QPointF &point) override; +// virtual void draw(const QPointF &point) override + + private slots: + void set_labels(); + + public slots: + void set_visible_vert_line(bool value); + void set_visible_horz_line(bool value); + + signals: + void get_nearest_x(qreal& x, chart_line::pos_behaviors); + void get_nearest_y(qreal& x, chart_line::pos_behaviors); + void position_changed(); + }; + } + } +} + +#endif // CHART_MAKER_H diff --git a/gui/spgr/gtl_gui_spgr_line.cpp b/gui/spgr/gtl_gui_spgr_line.cpp new file mode 100644 index 0000000..c196427 --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_line.cpp @@ -0,0 +1,45 @@ +#include "gtl_gui_spgr_line.h" + +namespace gtl { + namespace gui + { + namespace spgr + { + line_widget::line_widget(QWidget* parent, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y, gtl::analog_data* ad) + : QWidget(parent, Qt::Window) + ,_series(nullptr) + ,_chart(nullptr) + ,_widget(nullptr) + { + _series = new gui::chart_series(ad, axis_x, axis_y); + + _chart = new line_chart(this, static_cast(axis_x), static_cast(axis_y)); + _chart->add(_series); + + _widget = new gui::chart_widget(_chart, this); + + setLayout(new QVBoxLayout); + layout()->setContentsMargins(0, 0, 0, 0); + layout()->setSpacing(0); + layout()->addWidget(_widget); + } + + line_widget::~line_widget() + { + _chart->remove(); + if(_widget) delete _widget; + emit deleted(); + } + + void line_widget::set_data(qreal* values, qreal size, qreal dx) + { + if(_series) _series->set_y(values, size, dx); + } + + void line_widget::closeEvent(QCloseEvent *event) + { + emit closed(); + } + } + } +} diff --git a/gui/spgr/gtl_gui_spgr_line.h b/gui/spgr/gtl_gui_spgr_line.h new file mode 100644 index 0000000..b5bb8c1 --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_line.h @@ -0,0 +1,64 @@ + +#ifndef GTL_GUI_SPGR_LINE_H +#define GTL_GUI_SPGR_LINE_H + +#include "gui/gtl_gui_chart.h" +#include "gui/gtl_gui_chart_series.h" +#include "gui/gtl_gui_chart_widget.h" +#include "gui/gui_global.h" + +namespace gtl { + namespace gui { + namespace spgr { + + class GTL_GUI_EXPORT line_chart : public gui::chart + { + Q_OBJECT + public: + line_chart(QWidget* parent = NULL, chart_axis_x *axis_x = NULL, chart_axis_y *axis_y = NULL) + : gui::chart(parent, axis_x, axis_y) + {} + + virtual void save(QDomElement& root_element){} + virtual void load(const QDomElement& root_element){} + + void add(gui::chart_series* series){ ::chart::widget::add(series); }; + + protected: + virtual chart_axis_y* create_axis_y(){ return nullptr; } + virtual chart_series* create_series(gtl::analog_data* ai){ return nullptr; } + virtual void contextMenuEvent(QContextMenuEvent *event){} + + public slots: + virtual void add_ad(gtl::analog_data* ad){} + virtual void remove_ad(gtl::analog_data* ad){} + + protected slots: + virtual void device_recieved_data(){} + }; + + class GTL_GUI_EXPORT line_widget : public QWidget + { + Q_OBJECT + public: + line_widget(QWidget* parent = NULL, ::chart::axis_horz* axis_x = NULL, ::chart::axis_vert* axis_y = NULL, gtl::analog_data* ad = nullptr); + ~line_widget(); + + void set_data(qreal* values, qreal size, qreal dx); + + private: + gui::chart_series* _series; + line_chart* _chart; + gui::chart_widget* _widget; + + void closeEvent(QCloseEvent *event); + + signals: + void closed(); + void deleted(); + }; + } + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_SPGR_LINE_H diff --git a/gui/spgr/gtl_gui_spgr_series.cpp b/gui/spgr/gtl_gui_spgr_series.cpp new file mode 100644 index 0000000..86eb4b9 --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_series.cpp @@ -0,0 +1,458 @@ + +#include "gtl_gui_spgr_series.h" + +namespace gtl { + namespace gui { + namespace spgr{ + + series::series(QWidget* parent, gtl::math::spec* spec, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y) + : ::chart::series::series(axis_x, axis_y) + , _parent(parent) + , _spec(spec) + , _is_updating(true) + , _time(1) + , _size(10) + , _spec_time(_time/_size) + , _max(1) + , _min(0) + , _spec_size(10) + , _spec_res(10) + , _x_log(false) + , _t_chart(false) + , _f_chart(false) + , _pos(QPointF(0,0)) + , _t_idx(0) + , _f_idx(0) + , _t_widget(nullptr) + , _f_widget(nullptr) + { + if(_spec) + { + _ad = _spec->ad(); + if(_ad) + { + connect(_ad, >l::analog_data::color_changed, this, &series::set_icolor); + set_icolor(); + } + + init(); + + connect(_spec, &math::spec::changed, this, &series::handle_spec_changed); + connect(_spec, &math::spec::frequency_changed, this, &series::init); + connect(_spec, &math::spec::lines_changed, this, &series::init); + connect(_spec, &math::spec::resolution_changed, this, &series::init); + connect(_spec, &math::spec::unit_changed, this, &series::init); + connect(_spec, &math::spec::overlap_changed, this, &series::init); + connect(_spec, &math::spec::average_changed, this, &series::init); + } + } + + series::~series() + { + if(_t_widget) delete _t_widget; + if(_f_widget) delete _f_widget; + + _data.clear(); + } + + void series::set_updating(bool value) + { + _is_updating = value; + } + + analog_data *series::ad() + { + return _ad; + } + + void series::autoscale() + { + prepareGeometryChange(); + + if(_data.empty()) + return; + if(_data[0].empty()) + return; + + _max = _data[0][0]; + _min = _data[0][0]; + + qreal max, min = 0; + for (int i = 0; i < _data.size(); ++i) + { + max = *std::max_element(_data[i].begin(), _data[i].end()); + min = *std::min_element(_data[i].begin(), _data[i].end()); + + if(max > _max) _max = max; + if(min < _min) _min = min; + } + + _axis_horz->set_range(0, _spec->frequency(), 0); + _axis_vert->set_range(0, _time, 0); + + emit min_changed(); + } + + void series::set_pos(QPointF pos) + { + _pos = pos; + _t_idx = std::round(_pos.x()/_spec_res); + _f_idx = /*std::round*/(_pos.y()*_size/_time); + + // if(_t_widget) + // _t_widget->setWindowTitle(name() + tr(" - time chart (f = ") + QString::number((qreal)_t_idx*_spec_res) + tr(")")); + // if(_f_widget) + // _f_widget->setWindowTitle(name() + tr(" - freq chart (t = ") + QString::number((qreal)_f_idx*_time/_size) + tr(")")); + } + + qreal series::time() + { + return _time; + } + + void series::set_time(qreal value) + { + if(_time != value && value > 0) + { + _time = value; + init(); + } + } + + qreal series::spec_time() const + { + return _spec_res; + } + + qreal series::spec_res() const + { + return _spec_time; + } + + qreal series::min() + { + return _min; + } + + void series::set_min(qreal value) + { + if(_min != value) + _min = value; + } + + qreal series::max() + { + return _max; + } + + void series::set_max(qreal value) + { + if(_max != value) + _max = value; + } + + bool series::x_log() + { + return _x_log; + } + + void series::set_x_log(bool value) + { + if(_x_log != value) + _x_log = value; + + if(_x_log) + { + _axis_horz->set_boundaries(_spec_res, _axis_horz->max_boundary()); + _axis_horz->set_scale(::chart::axis::logarithmic); + } + else + { + _axis_horz->set_boundaries(0, _axis_horz->max_boundary()); + _axis_horz->set_scale(::chart::axis::linear); + } + } + + bool series::time_chart() + { + return _t_chart; + } + + void series::set_time_chart(bool value) + { + if(_t_chart != value) + _t_chart = value; + + if(_t_chart && !_t_widget) + { + ::chart::axis_horz* axis_x = new /*::chart::axis_horz()*/chart_axis_x(); + axis_x->set_range(0, _time - _time/_size, 0); + + ::chart::axis_vert* axis_y = new chart_axis_y()/*::chart::axis_vert()*/; + axis_y->set_range(_min, _max*1.1, 0); + axis_y->set_fixed(false); + + _t_widget = new line_widget(_parent, axis_x, axis_y, _ad); + // _t_widget->setWindowTitle(name() + tr(" - time chart (f = ") + QString::number((qreal)_t_idx*_spec_res) + tr(")")); + // _t_widget->show(); + + // connect(_t_widget, &spgr_line_widget::closed, [=]{ + // set_time_chart(false); + // emit time_chart_changed(); + // }); + } + else + { + if(_t_widget) delete _t_widget; + _t_widget = nullptr; + } + + emit time_chart_changed(); + } + + bool series::freq_chart() + { + return _f_chart; + } + + void series::set_freq_chart(bool value) + { + if(_f_chart != value) + _f_chart = value; + + if(_f_chart && !_f_widget) + { + ::chart::axis_horz* axis_x = new /*::chart::axis_horz()*/chart_axis_x(); + axis_x->set_range(0, _spec->frequency(), 0); + + ::chart::axis_vert* axis_y = new /*::chart::axis_vert()*/chart_axis_y(); + axis_y->set_range(_min, _max*1.1, 0); + // axis_y->set_fixed(false); + + _f_widget = new line_widget(_parent, axis_x, axis_y, _ad); + // _f_widget->setWindowTitle(name() + tr(" - freq chart (t = ") + QString::number((qreal)_f_idx*_time/_size) + tr(")")); + // _f_widget->show(); + + // connect(_f_widget, &spgr_line_widget::closed, [=]{ + // set_freq_chart(false); + // emit freq_chart_changed(); + // }); + } + else + { + if(_f_widget) delete _f_widget; + _f_widget = nullptr; + } + + emit freq_chart_changed(); + } + + line_widget *series::time_widget() + { + return _t_widget; + } + + line_widget *series::freq_widget() + { + return _f_widget; + } + + QString series::name() const + { + if(_ad) + return _ad->name(); + + return ""; + } + + void series::set_tool_tip(QPoint pos) + { + setToolTip(""); + } + + void series::update_series() + { + prepareGeometryChange(); + } + + void series::set_icolor() + { + if(_ad) + set_color(QColor(_ad->color())); + } + + void series::handle_spec_changed() + { + _data.pop_front(); + _data.push_back(static_cast>(*_spec)); + + if(_t_chart && _t_idx < _data[0].size() && _t_widget) + { + if(_t_widget->isHidden()) + { + set_time_chart(false); + emit time_chart_changed(); + } + else + { + std::vector data; + for(int i = 0; i < _data.size(); ++i) + data.push_back(_data[i][_t_idx]); + _t_widget->set_data(data.data(), data.size(), _time/_size); + } + } + + if(_f_chart && _f_idx < _data.size() && _f_widget) + { + if(_f_widget->isHidden()) + { + set_freq_chart(false); + emit freq_chart_changed(); + } + else + _f_widget->set_data(_data[_f_idx].data(), _data[_f_idx].size(), _spec_res); + } + + update_series(); + + // qDebug() << _spec->acq_time(); + } + + void series::init() + { + if(_spec) + { + blockSignals(true); + + _data.clear(); + _spec_time = _spec->acq_time()/_spec->average()/(1. - _spec->overlap()/100.); + _size = _time/_spec_time; + + _spec_res = _spec->resolution(); + _spec_size = _spec->size(); + std::vector tmp(_spec_size); + for (int i = 0; i < _size; ++i) + _data.push_back(tmp); + + _axis_horz->set_range(0, _spec->frequency(), 0); + _axis_horz->set_fixed(true); + set_x_log(_x_log); + + _axis_vert->set_range(0, _time, 0); + _axis_vert->set_fixed(true); + + blockSignals(false); + } + } + + QColor series::get_color(qreal value) + { + qreal range = (_max - _min)/4; + int r = 0; + int g = 0; + int b = 0; + + if(value <= (range + _min)) + { + if(value < _min) value = _min; + + qreal ratio = qAbs((value - _min)/range); + r = 0; + g = (int)(ratio*255); + b = 255; + } + else if(value > (range + _min) && value <= (range*2 + _min)) + { + qreal ratio = qAbs((value - (range + _min))/range); + r = 0; + g = (int)(ratio*255); + b = (int)((1-ratio)*255); + } + else if(value > (range*2 + _min) && value <= (range*3 + _min)) + { + qreal ratio = qAbs((value - (range*2 + _min))/range); + r = (int)(ratio*255); + g = 255; + b = 0; + } + else + { + if(value > _max) value = _max; + + qreal ratio = qAbs((value - (range*3 + _min))/range); + r = 255; + g = (int)((1-ratio)*255); + b = 0; + } + + if(r > 255) + r = 255; + else if(r < 0) + r = 0; + if(g > 255) + g = 255; + else if(g < 0) + g = 0; + if(b > 255) + b = 255; + else if(b < 0) + b = 0; + + return QColor::fromRgb(r, g, b); + } + + void series::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + if(_data.empty()) + return; + if(!_spec) + return; + + qreal ratio = _size/_time; + QRectF boundary = boundingRect(); + painter->setClipRect(_axis_vert->boundingRect()); + painter->setTransform(QTransform().translate( + boundary.left(), + boundary.top() + ) + .scale( + boundary.width() / _axis_horz->range_scaled(), + -boundary.height() / _axis_vert->range_scaled() * (1./ratio) + ) + .translate( + -_axis_horz->min_scaled(), + - (_size) + ) + ); + + QColor color; + QRectF rect; + qreal x; + qreal w; + + for (int i = 0; i < _data.size(); ++i) + for (int j = 0; j < _data[i].size(); ++j) + { + color = get_color(_data[i][j]); + painter->setPen(QPen(color, 0)); + painter->setBrush(color); + if(!_x_log) + { + x = (j-0.5)*_spec_res; + w = std::ceil(_spec_res); + } + else + { + x = _axis_horz->get_paint_value((j-0.5)*_spec_res); + w = std::ceil(_axis_horz->get_paint_value((j+0.5)*_spec_res) - _axis_horz->get_paint_value((j-0.5)*_spec_res)); + } + + rect.setRect(x,i,w,1); + painter->drawRect(rect); + + } + } + } + } // namespace gui +} // namespace gtl + diff --git a/gui/spgr/gtl_gui_spgr_series.h b/gui/spgr/gtl_gui_spgr_series.h new file mode 100644 index 0000000..aaef4b4 --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_series.h @@ -0,0 +1,108 @@ + +#ifndef GTL_GUI_SPGR_SERIES_H +#define GTL_GUI_SPGR_SERIES_H + +#include + +#include "chart/series/series.h" +#include "core/gtl_analog_data.h" +#include "math/gtl_math_spec.h" +#include "gui/spgr/gtl_gui_spgr_line.h" +#include "gui/gui_global.h" + +namespace gtl { + namespace gui { + namespace spgr { + + class GTL_GUI_EXPORT series : public ::chart::series::series + { + Q_OBJECT + public: + series(QWidget* parent, gtl::math::spec* spec, ::chart::axis_horz* axis_x, ::chart::axis_vert* axis_y); + ~series(); + + void set_updating(bool value); + gtl::analog_data* ad(); + void autoscale(); + void set_pos(QPointF pos); + + qreal time(); + void set_time(qreal value); + + qreal spec_res() const; + qreal spec_time() const; + + qreal min(); + void set_min(qreal value); + + qreal max(); + void set_max(qreal value); + + bool x_log(); + void set_x_log(bool value); + + bool time_chart(); + void set_time_chart(bool value); + + bool freq_chart(); + void set_freq_chart(bool value); + + line_widget* time_widget(); + line_widget* freq_widget(); + + + virtual QString name() const; + virtual void set_tool_tip(QPoint pos); + + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + + virtual bool get_range_y(qreal x_min, qreal x_max, qreal &y_min, qreal &y_max){return true;} + virtual bool get_ranges(qreal &x_min, qreal &x_max, qreal &y_min, qreal &y_max){return true;} + virtual bool get_tool_tip(QPoint pos, QString &text, QRect &rect){return true;} + + protected: + QWidget* _parent; + gtl::analog_data* _ad; + gtl::math::spec* _spec; + bool _is_updating; + qreal _time; + int _size; + qreal _spec_time; + qreal _max; + qreal _min; + int _spec_size; + qreal _spec_res; + bool _x_log; + bool _t_chart; + bool _f_chart; + QPointF _pos; + int _t_idx; + int _f_idx; + line_widget* _t_widget; + line_widget* _f_widget; + + + std::deque> _data; + + private slots: + void update_series(); + void set_icolor(); + void handle_spec_changed(); + void init(); + QColor get_color(qreal value); + + signals: + void data_changed(); + void time_changed(); + void min_changed(); + void max_changed(); + void x_log_changed(); + void time_chart_changed(); + void freq_chart_changed(); + + }; + } + } // namespace gui +} // namespace gtl + +#endif // GTL_GUI_SPGR_SERIES_H diff --git a/gui/spgr/gtl_gui_spgr_widget.cpp b/gui/spgr/gtl_gui_spgr_widget.cpp new file mode 100644 index 0000000..16a53d0 --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_widget.cpp @@ -0,0 +1,113 @@ +#include "gtl_gui_spgr_widget.h" +#include "gui/ui_gtl_gui_spgr_widget.h" + +namespace gtl +{ + namespace gui + { + namespace spgr + { + widget::widget(QWidget *parent, gtl::math::spec *spec) : + QWidget(parent, Qt::Window), + ui(new Ui::spgr_widget) + { + ui->setupUi(this); + + ::chart::axis_horz* axis_x = new ::chart::axis_horz(); + ::chart::axis_vert* axis_y = new ::chart::axis_vert(); + _series = new series(parent, spec, axis_x, axis_y); + _chart = new chart(parent, axis_x, axis_y); + _chart->add(_series); + ui->splitter->insertWidget(0, _chart); + + QList sizes; + sizes.append(0.99 * sizeHint().width()); + sizes.append(0.01 * sizeHint().width()); + ui->splitter->setSizes(sizes); + + ui->time->setValue(_series->time()); + connect(ui->time, &QDoubleSpinBox::valueChanged, _series, &series::set_time); + + ui->min->setValue(_series->min()); + connect(ui->min, &QDoubleSpinBox::valueChanged, _series, &series::set_min); + + ui->max->setValue(_series->max()); + connect(ui->max, &QDoubleSpinBox::valueChanged, _series, &series::set_max); + + ui->x_log->setChecked(_series->x_log()); + connect(ui->x_log, &QCheckBox::clicked, _series, &series::set_x_log); + + ui->time_chart->setChecked(_series->time_chart()); + connect(ui->time_chart, &QCheckBox::clicked, _series, &series::set_time_chart); + + ui->freq_chart->setChecked(_series->freq_chart()); + connect(ui->freq_chart, &QCheckBox::clicked, _series, &series::set_freq_chart); + + gradient_widget *gradient = new gradient_widget(this); + gradient->setFixedHeight(20); + ui->min_max_vl->addWidget(gradient); + + connect(_series, &series::time_changed, this, &widget::update_parameters); + connect(_series, &series::min_changed, this, &widget::update_parameters); + connect(_series, &series::max_changed, this, &widget::update_parameters); + // connect(_series, &spgr_series::time_chart_changed, this, &spgr_widget::update_parameters); + // connect(_series, &spgr_series::freq_chart_changed, this, &spgr_widget::update_parameters); + connect(_series, &series::time_chart_changed, [=]{ + update_parameters(); + emit time_chart_changed(); + }); + connect(_series, &series::freq_chart_changed, [=]{ + update_parameters(); + emit freq_chart_changed(); + }); + } + + + widget::~widget() + { + if(_chart) delete _chart; + _chart = nullptr; + delete ui; + emit deleted(); + } + + line_widget *widget::time_widget() + { + if(_series) + return _series->time_widget(); + return nullptr; + } + + line_widget *widget::freq_widget() + { + if(_series) + return _series->freq_widget(); + return nullptr; + } + + void widget::update_parameters() + { + if(!_series) + return; + + _series->blockSignals(true); + + ui->time->setValue(_series->time()); + ui->min->setValue(_series->min()); + ui->max->setValue(_series->max()); + + ui->time_chart->setChecked(_series->time_chart()); + ui->freq_chart->setChecked(_series->freq_chart()); + + _series->blockSignals(false); + } + + void widget::closeEvent(QCloseEvent *event) + { + // if(_chart) delete _chart; + // _chart = nullptr; + emit closed(); + } + } + } +} diff --git a/gui/spgr/gtl_gui_spgr_widget.h b/gui/spgr/gtl_gui_spgr_widget.h new file mode 100644 index 0000000..d007b00 --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_widget.h @@ -0,0 +1,74 @@ +#ifndef GTL_GUI_SPGR_WIDGET_H +#define GTL_GUI_SPGR_WIDGET_H + +#include +#include "qpainter.h" + +#include "math/gtl_math_spec.h" +#include "gui/spgr/gtl_gui_spgr_series.h" +#include "gui/spgr/gtl_gui_spgr_chart.h" +#include "gui/gui_global.h" + +namespace Ui { +class spgr_widget; +} + +namespace gtl +{ + namespace gui + { + namespace spgr + { + + class GTL_GUI_EXPORT gradient_widget : public QWidget + { + Q_OBJECT + + public: + gradient_widget(QWidget *parent) : QWidget(parent){} + + void paintEvent(QPaintEvent *) + { + QPainter painter(this); + QLinearGradient gradient(0, 0, width(), 0); + gradient.setColorAt(0, Qt::blue); + gradient.setColorAt(0.25, Qt::cyan); + gradient.setColorAt(0.5, Qt::green); + gradient.setColorAt(0.75, Qt::yellow); + gradient.setColorAt(1, Qt::red); + painter.setBrush(gradient); + painter.drawRect(rect()); + } + }; + + class GTL_GUI_EXPORT widget : public QWidget + { + Q_OBJECT + + public: + explicit widget(QWidget *parent, gtl::math::spec *spec); + ~widget(); + + line_widget* time_widget(); + line_widget* freq_widget(); + + private: + Ui::spgr_widget *ui; + QDomElement* _root_element; + series* _series; + chart* _chart; + + void update_parameters(); + void closeEvent(QCloseEvent *event); + + signals: + void closed(); + void deleted(); + void time_chart_changed(); + void freq_chart_changed(); + }; + } + } +} + +#endif // GTL_GUI_SPGR_WIDGET_H diff --git a/gui/spgr/gtl_gui_spgr_widget.ui b/gui/spgr/gtl_gui_spgr_widget.ui new file mode 100644 index 0000000..b281cab --- /dev/null +++ b/gui/spgr/gtl_gui_spgr_widget.ui @@ -0,0 +1,221 @@ + + + spgr_widget + + + + 0 + 0 + 645 + 600 + + + + Spectrogramm + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Qt::Horizontal + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::AlignHCenter|Qt::AlignTop + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + Time, s + + + + + + + 0.000000000000000 + + + 999.990000000000009 + + + + + + + + + Qt::Horizontal + + + + + + + + + + + min + + + + + + + + + + 6 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + max + + + + + + + + + + 6 + + + -10000.000000000000000 + + + 10000.000000000000000 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + x log + + + + + + + time chart + + + + + + + freq chart + + + + + + + + + + + + diff --git a/hw/.gitignore b/hw/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/hw/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/hw/ADCCluster/.gitignore b/hw/ADCCluster/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/hw/ADCCluster/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/hw/ADCCluster/ADCCluster.json b/hw/ADCCluster/ADCCluster.json new file mode 100644 index 0000000..1e81138 --- /dev/null +++ b/hw/ADCCluster/ADCCluster.json @@ -0,0 +1,3 @@ +{ + "Keys" : [ ] +} diff --git a/hw/ADCCluster/ADCCluster.pro b/hw/ADCCluster/ADCCluster.pro new file mode 100644 index 0000000..1941171 --- /dev/null +++ b/hw/ADCCluster/ADCCluster.pro @@ -0,0 +1,56 @@ +QT += network xml + +TEMPLATE = lib +DEFINES += ADCCLUSTER_LIBRARY + +CONFIG += plugin + +CONFIG += c++17 + +DISTFILES += ADCCluster.json + +SOURCES += \ + adc.cpp \ + adcchannel.cpp \ + adccluster.cpp \ + adccluster_device.cpp \ + adcqueue.cpp \ + clusterstack.cpp \ + idserver.cpp \ + rrdstack.cpp \ + singlestack.cpp \ + stpclient.cpp + +HEADERS += \ + adc.h \ + adcchannel.h \ + adccluster.h \ + adccluster_device.h \ + adcqueue.h \ + adcregisters.h \ + clusterstack.h \ + idserver.h \ + rrdstack.h \ + singlestack.h \ + stpclient.h \ + stpdefine.h + +win32:CONFIG(release, debug|release): DESTDIR = ../../.output/.hwplugins/release +else:win32:CONFIG(debug, debug|release): DESTDIR = ../../.output/.hwplugins/debug +else:unix: DESTDIR = ../../.ouput/.hwplugins + +unix { + target.path = $$[QT_INSTALL_PLUGINS]/generic +} +!isEmpty(target.path): INSTALLS += target + +INCLUDEPATH += $$PWD/../.. +DEPENDPATH += $$PWD/../.. + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_hw +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_hw +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_hw + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_core +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_core diff --git a/hw/ADCCluster/adc.cpp b/hw/ADCCluster/adc.cpp new file mode 100644 index 0000000..3f058ff --- /dev/null +++ b/hw/ADCCluster/adc.cpp @@ -0,0 +1,424 @@ +#include "adc.h" + +ADC::ADC(uint16_t sn, QObject *parent) + : QObject{parent}, + _state(ADC_ns::ADCState::DISCONNECTED), + _stpClient(new stpClient(QHostAddress())), + _adcqueue(new ADCQueue()), + _idServer(new idServer(sn)), + _discoverTimer(new QTimer()), + _dataCounter(0), + _modelId(0), + _sn(sn), + _channelsCount(0), + _isTachoSignal(false), + _isGen(false), + _freq(0), + _bitDepth(ADC_ns::bitDepth::BIT16), + _dhcp_mode(true), + _mac_address(QByteArray()), + _default_ip(QHostAddress()), + _default_gate(QHostAddress()), + _default_mask(QHostAddress()), + _static_ip(QHostAddress()), + _static_gate(QHostAddress()), + _static_mask(QHostAddress()), + _channels(new QList()) +{ + #ifdef ADC_DEBUG + qDebug() << "adc created: " << sn; + #endif + + _discoverTimer->setInterval(DISCOVER_PERIOD); + + QObject::connect(_stpClient, &stpClient::STP_connected, [=] { + _dataCounter = 0; + _state = ADC_ns::ADCState::CONFIGURATION_READING; + _packReadADCParams(); + + emit ADC_connected(); + }); + + QObject::connect(_stpClient, &stpClient::STP_disconnected, [=] { + if (_state != ADC_ns::ADCState::DISCONNECTED) { + _state = ADC_ns::ADCState::DISCONNECTED; + _adcqueue->stop(); + + emit ADC_disconnected(); + } + }); + + + QObject::connect(_stpClient, &stpClient::STP_newDataPacketReceived, [=](QByteArray data) { + if (_adcqueue->receiveData(data)) return; + emit ADC_dataPacketReceived(data); + }); + + QObject::connect(_discoverTimer, &QTimer::timeout, [=]{ + _idServer->sendIdRequest(); + }); + + QObject::connect(_idServer, &idServer::newIdMessage, [=](uint16_t model, uint16_t sn, QHostAddress address){ + if (_state == ADC_ns::ADCState::DISCOVER) { + if (sn == _sn) { + #ifdef ADC_DEBUG + qDebug() << "Found ADC: " << sn << " on IP: " << address.toString(); + #endif + _state = ADC_ns::ADCState::CONNECTING; + + _discoverTimer->stop(); + _stpClient->setAddress(address); + _stpClient->connectToHost(); + } + } + }); + + QObject::connect(_adcqueue, &ADCQueue::sendData, [=](QByteArray data) { + _stpClient->sendData(&data); + }); + + QObject::connect(_adcqueue, &ADCQueue::queueIsEmpty, [=] { + if (_state == ADC_ns::ADCState::CONFIGURATION_READING) { + _state = ADC_ns::ADCState::WAITING_COMMAND; + + emit ADC_configReadingFinished(); + } else if (_state == ADC_ns::ADCState::ADC_START_SEND) { + _state = ADC_ns::ADCState::RUNNING; + + emit ADC_started(); + } else if (_state == ADC_ns::ADCState::ADC_STOP_SEND) { + _state = ADC_ns::ADCState::WAITING_COMMAND; + } + }); + + QObject::connect(_adcqueue, &ADCQueue::registerReadFinished, [=](ADCQueue_ns::cmdItem cmd) { + _parseRegisterData(cmd); + }); + + QObject::connect(_adcqueue, &ADCQueue::registerWriteFinished, [=](ADCQueue_ns::cmdItem cmd) { + #ifdef ADC_DEBUG + qDebug() << "ADC " << _sn << "Register writen: " << cmd.regNumber; + #endif + }); + + QObject::connect(_adcqueue, &ADCQueue::registerReadError, [=](ADCQueue_ns::cmdItem cmd) { + #ifdef ADC_DEBUG + qDebug() << "ADC " << _sn << "Register read error: " << cmd.regNumber; + #endif + }); + + QObject::connect(_adcqueue, &ADCQueue::registerWriteError, [=](ADCQueue_ns::cmdItem cmd) { + #ifdef ADC_DEBUG + qDebug() << "ADC " << _sn << "Register write error: " << cmd.regNumber; + #endif + }); + + QObject::connect(_adcqueue, &ADCQueue::queueTimeout, [=] { + #ifdef ADC_DEBUG + qDebug() << "ADC " << _sn << "Queue timeout"; + #endif + }); + + QObject::connect(_adcqueue, &ADCQueue::queueError, [=] { + #ifdef ADC_DEBUG + qDebug() << "ADC " << _sn << "Queue error"; + #endif + }); +} + +ADC::~ADC() +{ + delete _adcqueue; + delete _stpClient; + #ifdef ADC_DEBUG + qDebug() << "ADC destructor" << _sn; + #endif +} + +void ADC::start() +{ + #ifdef ADC_DEBUG + qDebug() << "ADC class start" << _sn; + #endif + + _idServer->sendIdRequest(); + _discoverTimer->start(); + _state = ADC_ns::ADCState::DISCOVER; +} + +void ADC::stop() +{ + #ifdef ADC_DEBUG + qDebug() << "ADC class stop" << _sn; + #endif + + _state = ADC_ns::ADCState::DISCONNECTED; + _adcqueue->stop(); + _discoverTimer->stop(); + _stpClient->disconnectFromHost(); +} + +void ADC::startADC(ADC_ns::SYNCMode mode) +{ + _state = ADC_ns::ADCState::ADC_START_SEND; + _packStartADC(mode); +} + +void ADC::stopADC() +{ + _state = ADC_ns::ADCState::ADC_STOP_SEND; + _packStopADC(); +} + +void ADC::_packReadADCParams() +{ + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, MODEL_ID); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, NETWORK_MODE); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, MAC_ADDRESS); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, DEFAULT_IP); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, DEFAULT_GATE); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, DEFAULT_MASK); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, STATIC_IP); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, STATIC_GATE); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, STATIC_MASK); + + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, CHANNELS_COUNT); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, IS_TACHO_SIGNAL); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, IS_GEN); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, ADC_FREQ); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, BIT_DEPTH); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_READ, CALIBRATION_FACTOR); +} + +void ADC::_parseRegisterData(ADCQueue_ns::cmdItem cmd) +{ + QByteArray * __regData = ((QByteArray *)cmd.regData); + + switch (cmd.regNumber) { + + case MODEL_ID: + trans_ns::bytes2word __modelId; + __modelId.bytes.L = (*__regData)[0]; + __modelId.bytes.H = (*__regData)[1]; + _modelId = __modelId.word; + break; + + case DEVICE_SN: + trans_ns::bytes2word __sn; + __sn.bytes.L = (*__regData)[0]; + __sn.bytes.H = (*__regData)[1]; + _sn = __sn.word; + break; + + case CHANNELS_COUNT: + trans_ns::bytes2word __channelsCount; + __channelsCount.bytes.L = (*__regData)[0]; + __channelsCount.bytes.H = (*__regData)[1]; + _channelsCount = __channelsCount.word; + break; + + case IS_TACHO_SIGNAL: + trans_ns::bytes2word __isTachoSignal; + __isTachoSignal.bytes.L = (*__regData)[0]; + __isTachoSignal.bytes.H = (*__regData)[1]; + _isTachoSignal = (__isTachoSignal.word == 1); + break; + + case IS_GEN: + trans_ns::bytes2word __isGen; + __isGen.bytes.L = (*__regData)[0]; + __isGen.bytes.H = (*__regData)[1]; + _isGen = (__isGen.word == 1); + break; + + + case ADC_FREQ: + trans_ns::bytes2long __freq; + __freq.bytes.LL = (*__regData)[0]; + __freq.bytes.LH = (*__regData)[1]; + __freq.bytes.HL = (*__regData)[2]; + __freq.bytes.HH = (*__regData)[3]; + _freq = __freq.twinWord; + break; + + case CALIBRATION_FACTOR: + _channels->clear(); + + if (__regData->size() < _channelsCount * 8) { + #ifdef ADC_DEBUG + qDebug() << "ADC " << _sn << "scale factor error"; + #endif + + for (int i=0; i<_channelsCount; i++) { + ADCChannel * __channel = new ADCChannel(); + __channel->setFactor(1); + _channels->append(__channel); + } + break; + } + + for (int i=0; i<_channelsCount; i++) { + trans_ns::bytes2float __factor; + __factor.bytes.LL = (*__regData)[i*8]; + __factor.bytes.LH = (*__regData)[i*8 + 1]; + __factor.bytes.HL = (*__regData)[i*8 + 2]; + __factor.bytes.HH = (*__regData)[i*8 + 3]; + + ADCChannel * __channel = new ADCChannel(); + + __channel->setFactor(__factor.floatValue); + + _channels->append(__channel); + } + + break; + + case BIT_DEPTH: + trans_ns::bytes2word __bitDepth; + __bitDepth.bytes.L = (*__regData)[0]; + __bitDepth.bytes.H = (*__regData)[1]; + + if (__bitDepth.word == 1 ) { + _bitDepth = ADC_ns::bitDepth::BIT24; + } else { + _bitDepth = ADC_ns::bitDepth::BIT16; + } + + break; + + case NETWORK_MODE: + trans_ns::bytes2word __networkMode; + __networkMode.bytes.L = (*__regData)[0]; + __networkMode.bytes.H = (*__regData)[1]; + _dhcp_mode = __networkMode.word == 0; + break; + + case MAC_ADDRESS: + _mac_address = QByteArray(*__regData); + break; + + case DEFAULT_IP: + trans_ns::bytes2long __defaultIP; + __defaultIP.bytes.LL = (*__regData)[3]; + __defaultIP.bytes.LH = (*__regData)[2]; + __defaultIP.bytes.HL = (*__regData)[1]; + __defaultIP.bytes.HH = (*__regData)[0]; + _default_ip = QHostAddress(__defaultIP.twinWord); + break; + + case DEFAULT_GATE: + trans_ns::bytes2long __defaultGate; + __defaultGate.bytes.LL = (*__regData)[3]; + __defaultGate.bytes.LH = (*__regData)[2]; + __defaultGate.bytes.HL = (*__regData)[1]; + __defaultGate.bytes.HH = (*__regData)[0]; + _default_gate = QHostAddress(__defaultGate.twinWord); + break; + + case DEFAULT_MASK: + trans_ns::bytes2long __defaultMask; + __defaultMask.bytes.LL = (*__regData)[3]; + __defaultMask.bytes.LH = (*__regData)[2]; + __defaultMask.bytes.HL = (*__regData)[1]; + __defaultMask.bytes.HH = (*__regData)[0]; + _default_mask = QHostAddress(__defaultMask.twinWord); + break; + + case STATIC_IP: + trans_ns::bytes2long __staticIP; + __staticIP.bytes.LL = (*__regData)[3]; + __staticIP.bytes.LH = (*__regData)[2]; + __staticIP.bytes.HL = (*__regData)[1]; + __staticIP.bytes.HH = (*__regData)[0]; + _static_ip = QHostAddress(__staticIP.twinWord); + break; + + case STATIC_GATE: + trans_ns::bytes2long __staticGate; + __staticGate.bytes.LL = (*__regData)[3]; + __staticGate.bytes.LH = (*__regData)[2]; + __staticGate.bytes.HL = (*__regData)[1]; + __staticGate.bytes.HH = (*__regData)[0]; + _static_gate = QHostAddress(__staticGate.twinWord); + break; + + case STATIC_MASK: + trans_ns::bytes2long __staticMask; + __staticMask.bytes.LL = (*__regData)[3]; + __staticMask.bytes.LH = (*__regData)[2]; + __staticMask.bytes.HL = (*__regData)[1]; + __staticMask.bytes.HH = (*__regData)[0]; + _static_mask = QHostAddress(__staticMask.twinWord); + break; + + } +} + +void ADC::_packStartADC(ADC_ns::SYNCMode mode) +{ + QByteArray * __cmd2 = new QByteArray(); + __cmd2->resize(1); + (*__cmd2)[0] = mode; + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_WRITE, SYNC_MODE, __cmd2); + + QByteArray * __cmd3 = new QByteArray(); + __cmd3->resize(1); + (*__cmd3)[0] = 0x01; + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_WRITE, ADC_START, __cmd3); + +} + +void ADC::_packStopADC() +{ + QByteArray * __cmd = new QByteArray(); + __cmd->resize(1); + (*__cmd)[0] = 0x00; + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_WRITE, ADC_START, __cmd); +} + +void ADC::setChannelIEPE(int channel, bool value) +{ + QByteArray * __cmd = new QByteArray(); + __cmd->resize(1); + (*__cmd)[0] = value; + _channels->at(channel)->setIEPE(value); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_WRITE, CH_IEPE_OFFSET + channel, __cmd); + + if (!value) { + setChannelMODE(channel, _channels->at(channel)->getMode()); + } +} + +void ADC::setChannelMODE(int channel, ADC_ns::channelMode mode) +{ + QByteArray * __cmd = new QByteArray(); + __cmd->resize(1); + (*__cmd)[0] = mode; + _channels->at(channel)->setMode(mode); + _adcqueue->appendCmd(stpProtocol_ns::stpCommand::REG_WRITE, CH_ACDC_OFFSET + channel, __cmd); +} + +int ADC::getSamplesCount() +{ + int __result; + + switch (_modelId) { + case 1: // 2 CH 24B + GEN + __result = 222; + break; + case 2: // 2 CH 16B + TACHO + __result = 333; + break; + case 4: // 4 CH 16B + TACHO + __result = 167; + break; + + + default: + __result = 0; + break; + } + + return __result; +} + diff --git a/hw/ADCCluster/adc.h b/hw/ADCCluster/adc.h new file mode 100644 index 0000000..be602d7 --- /dev/null +++ b/hw/ADCCluster/adc.h @@ -0,0 +1,95 @@ +#ifndef ADC_H +#define ADC_H + +#include + +#include "stpclient.h" +#include "adcqueue.h" +#include "adcregisters.h" +#include "idserver.h" +#include "adcchannel.h" + +class ADC : public QObject +{ + Q_OBJECT +public: + explicit ADC(uint16_t sn, QObject *parent = nullptr); + ~ADC(); + + void start(); + void stop(); + + void startADC(ADC_ns::SYNCMode mode); + void stopADC(); + + ADC_ns::ADCState getADCState() { return _state; }; + + uint16_t getModelID() { return _modelId; } + uint16_t getSN() {return _sn; } + uint16_t getChannelsCount() { return _channelsCount; } + bool getIsTachoSignal() { return _isTachoSignal; } + bool getIsGen() { return _isGen; } + uint32_t getFreq() { return _freq; } + ADC_ns::bitDepth getBitDeph() { return _bitDepth; } + + // сеть + bool getIsDHCPMode() { return _dhcp_mode; } + QByteArray getMacAddress() { return _mac_address; } + QHostAddress getDefaultIP() { return _default_ip; } + QHostAddress getDefaultGate() { return _default_gate; } + QHostAddress getDefaultMask() { return _default_mask; } + QHostAddress getStaticIP() { return _static_ip; } + QHostAddress getStaticGate() { return _static_gate; } + QHostAddress getStaticMask() { return _static_mask; } + QList * getChannelsConfig() { return _channels; } + uint32_t getDataCounter() { return _dataCounter; } + void setDataCounter(uint32_t value) { _dataCounter=value; } + void incDataCounter() { _dataCounter++; } + void setChannelIEPE(int index, bool IEPE); + void setChannelMODE(int index, ADC_ns::channelMode mode); + + int getSamplesCount(); +signals: + void ADC_connected(); + void ADC_disconnected(); + void ADC_configReadingFinished(); + void ADC_dataPacketReceived(QByteArray dataPacket); + void ADC_started(); + +private: + void _packReadADCParams(); + void _parseRegisterData(ADCQueue_ns::cmdItem cmd); + void _packStartADC(ADC_ns::SYNCMode mode); + void _packStopADC(); + + ADC_ns::ADCState _state; + stpClient * _stpClient; + ADCQueue * _adcqueue; + idServer * _idServer; + QTimer * _discoverTimer; + uint32_t _dataCounter; + + // АЦП + uint16_t _modelId; + uint16_t _sn; + uint16_t _channelsCount; + bool _isTachoSignal; + bool _isGen; + uint32_t _freq; + ADC_ns::bitDepth _bitDepth; + + // сеть + bool _dhcp_mode; + QByteArray _mac_address; + QHostAddress _default_ip; + QHostAddress _default_gate; + QHostAddress _default_mask; + QHostAddress _static_ip; + QHostAddress _static_gate; + QHostAddress _static_mask; + QList * _channels; + + +}; + +#endif // ADC_H diff --git a/hw/ADCCluster/adcchannel.cpp b/hw/ADCCluster/adcchannel.cpp new file mode 100644 index 0000000..e771d28 --- /dev/null +++ b/hw/ADCCluster/adcchannel.cpp @@ -0,0 +1,7 @@ +#include "adcchannel.h" + +ADCChannel::ADCChannel(QObject *parent) + : QObject{parent} +{ + +} diff --git a/hw/ADCCluster/adcchannel.h b/hw/ADCCluster/adcchannel.h new file mode 100644 index 0000000..ea75154 --- /dev/null +++ b/hw/ADCCluster/adcchannel.h @@ -0,0 +1,31 @@ +#ifndef ADCCHANNEL_H +#define ADCCHANNEL_H + +#include + +#include "stpdefine.h" + +class ADCChannel : public QObject +{ + Q_OBJECT +public: + explicit ADCChannel(QObject *parent = nullptr); + + void setMode(ADC_ns::channelMode value) { _mode = value; } + void setIEPE(bool value) { _IEPE = value; } + void setFactor(float value) { _factor = value; } + + float getFactor() { return _factor; } + ADC_ns::channelMode getMode() { return _mode; } + +private: + float _factor; + ADC_ns::channelMode _mode; + bool _IEPE; + + +signals: + +}; + +#endif // ADCCHANNEL_H diff --git a/hw/ADCCluster/adccluster.cpp b/hw/ADCCluster/adccluster.cpp new file mode 100644 index 0000000..0889242 --- /dev/null +++ b/hw/ADCCluster/adccluster.cpp @@ -0,0 +1,16 @@ +#include "adccluster.h" + +ADCCluster::ADCCluster(QObject *parent) +{ + +} + +ADCCluster::~ADCCluster() +{ + +} + +gtl::hw::device *ADCCluster::create_device(QObject *parent) +{ + return new ADCCluster_device(); +} diff --git a/hw/ADCCluster/adccluster.h b/hw/ADCCluster/adccluster.h new file mode 100644 index 0000000..7710ce2 --- /dev/null +++ b/hw/ADCCluster/adccluster.h @@ -0,0 +1,27 @@ +#ifndef ADCCLUSTER_H +#define ADCCLUSTER_H + +#include + +#include "../gtl_hardware_interface.h" +#include "adccluster_device.h" + + +class ADCCluster: public QObject, public gtl::hardware_interface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "gtlab.hardware" FILE "ADCCluster.json") + Q_INTERFACES(gtl::hardware_interface) + + +public: + explicit ADCCluster(QObject *parent = nullptr); + ~ADCCluster(); + + virtual QString id() const { return "ADCCLuster"; } + + virtual gtl::hw::device* create_device(QObject* parent = NULL); + +}; + +#endif // ADCCLUSTER_H diff --git a/hw/ADCCluster/adccluster_device.cpp b/hw/ADCCluster/adccluster_device.cpp new file mode 100644 index 0000000..5fb48fb --- /dev/null +++ b/hw/ADCCluster/adccluster_device.cpp @@ -0,0 +1,730 @@ +#include "adccluster_device.h" + +ADCCluster_device::ADCCluster_device(QObject *parent) + : gtl::hw::device(parent), + _slavesADC(new QList()), + _adcs(new QMap), + _config(new QJsonObject()), + _state(ADCCluster_ns::clusterState::IDLE), + _rrdStack(new RRDStack()), + _isMasterAllreadyInited(false), + _currentSingleStack(new singleStack(0, ADC_ns::bitDepth::BIT16, new QList(), 0)), + _singleQueue(new QQueue()), + _clusterQueue(new QQueue()), + _singleMode(true), + _initError(false) +{ + +} + +ADCCluster_device::~ADCCluster_device() +{ + #ifdef CLUSTER_DEBUG + qDebug() << "cluster desctructor"; + #endif + stop(); +} + +void ADCCluster_device::restart() +{ + +} + +ADC *ADCCluster_device::_initMasterADC(QList * adcSNs) +{ + int __masterSN = adcSNs->at(0); + + ADC * __masterADC = new ADC(__masterSN); + + QObject::connect(__masterADC, &ADC::ADC_connected, [=]() { + #ifdef CLUSTER_DEBUG + qDebug() << QTime::currentTime().toString() << "master adc connected" << __masterSN; + #endif + }); + + QObject::connect(__masterADC, &ADC::ADC_disconnected, [=]() { + #ifdef CLUSTER_DEBUG + qDebug() << QTime::currentTime().toString() << "master adc disconnected" << __masterSN; + #endif + + _destroySlaves(); + + // hw + _device->set_name("ADCCluster [MASTER DISCONNECTED]"); + // hw + + _state = ADCCluster_ns::clusterState::CONFIG; + }); + + QObject::connect(__masterADC, &ADC::ADC_started, [=]() { + _rrdStack = new RRDStack(); + _state = ADCCluster_ns::clusterState::RUNNING; + __masterADC->setDataCounter(0); + _device->set_name("ADCCluster [RUNNING]"); + }); + + + QObject::connect(__masterADC, &ADC::ADC_configReadingFinished, [=]() { + + #ifdef CLUSTER_DEBUG + _debugPrintADCParams(__masterADC); + #endif + + if (__masterADC->getModelID() == 0) { // TODO критериии некорректной инициализации + #ifdef CLUSTER_DEBUG + qDebug() << "init failed"; + #endif + return; + } + + for (int __slaveID = 1; __slaveID < adcSNs->count(); __slaveID++) { + ADC * __slave = _initSlaveADC(adcSNs->at(__slaveID), __slaveID); + _slavesADC->append(__slave); + __slave->start(); + } + + // hw + _device->set_name("ADCCluster [WAITING SLAVES]"); + // hw + + for (int __i=0; __i<_slavesADC->count(); __i++) { + ADC * __slave = _slavesADC->at(__i); + QObject::connect(__slave, &ADC::ADC_disconnected, [=]() { + #ifdef CLUSTER_DEBUG + qDebug() << QTime::currentTime().toString() << "slave adc disconnected" << __slave->getSN(); + #endif + + // hw + _device->set_name("ADCCluster [SLAVE DISONNECTED]"); + // hw + + _state = ADCCluster_ns::clusterState::CONFIG; + + if (__masterADC->getADCState() == ADC_ns::ADCState::RUNNING) { + __masterADC->stopADC(); + } + _stopSlaves(); + }); + + QObject::connect(__slave, &ADC::ADC_started, [=]{ + bool __allReady = true; + + for (int __i = 0; __i < _slavesADC->count(); __i++) { + if (_slavesADC->at(__i)->getADCState() != ADC_ns::ADCState::RUNNING) { + __allReady = false; + } + + if (_slavesADC->at(__i)->getADCState() == ADC_ns::ADCState::WAITING_COMMAND) { + _slavesADC->at(__i)->startADC(ADC_ns::SYNCMode::SLAVE); + } + + if (_slavesADC->at(__i)->getModelID() != __masterADC->getModelID()) { + __allReady = false; + _state = ADCCluster_ns::clusterState::CONFIG; + _device->set_name("ADCCluster [SLAVES INCOMPATIBILITY]"); + } + } + + if (__allReady) { + if (!_isMasterAllreadyInited) { + // hw api + _rate = __masterADC->getFreq(); + _device->set_rate(__masterADC->getFreq()); + _device->set_id("ADCCluster"); + _device->set_name("ADCCluster [STARTING CLUSTER]"); + _count_ai = (_slavesADC->count() + 1) * (__masterADC->getChannelsCount() + __masterADC->getIsTachoSignal()); + set_ai(); + + for (int __i = 0; __i < _count_ai; __i++) { + int __adcNo = __i / (__masterADC->getChannelsCount() + __masterADC->getIsTachoSignal()); + int __channelNo = __i % (__masterADC->getChannelsCount() + __masterADC->getIsTachoSignal()); + + int __adcSN = 0; + + if (__adcNo == 0) { + __adcSN = __masterADC->getSN(); + } else { + __adcSN = _slavesADC->at(__adcNo - 1)->getSN(); + } + + QString __prefix = QString::number(__adcSN); + + if (__channelNo == __masterADC->getChannelsCount()) { + ai(__i)->set_name(__prefix + " [ pulse ]"); + } else { + ai(__i)->set_name(__prefix + " [ " + QString::number(__channelNo) + " ]"); + } + } + + // hw api + + _isMasterAllreadyInited = true; + } + + __masterADC->startADC(ADC_ns::SYNCMode::MASTER); + } + + }); + } + }); + + QObject::connect(__masterADC, &ADC::ADC_dataPacketReceived, [=](QByteArray packet) { + trans_ns::bytes2long __num; + __num.bytes.LL = packet[0]; + __num.bytes.LH = packet[1]; + __num.bytes.HL = packet[2]; + __num.bytes.HH = packet[3]; + + if (_masterADC->getDataCounter() != __num.twinWord) { + #ifdef CLUSTER_DEBUG + qDebug() << "propusk: " << _masterADC->getSN() << _masterADC->getDataCounter() << " : " << __num.twinWord << " packets lost = " << __num.twinWord - _masterADC->getDataCounter(); + #endif + _masterADC->setDataCounter(__num.twinWord); + } + _masterADC->incDataCounter(); + + clusterStack * __currentStack = _rrdStack->getStackByPacketNumber(__num.twinWord); + + if (__currentStack == nullptr) { + __currentStack = new clusterStack(45 * (int)(__num.twinWord / 45), _masterADC->getBitDeph(), _masterADC->getChannelsConfig(), _masterADC->getSamplesCount(), _slavesADC); + _clusterQueue->enqueue(_rrdStack->pushStack(__currentStack)); + } + + int __packetIndex = __num.twinWord - __currentStack->getFirstPacketIndex(); + + __currentStack->insert(0, __packetIndex, packet); + + }); + + return __masterADC; +} + +void ADCCluster_device::_stopSlaves() +{ + foreach(ADC * __slave, *_slavesADC) { + if (__slave->getADCState() == ADC_ns::ADCState::RUNNING){ + __slave->stopADC(); + } + } +} + +void ADCCluster_device::_destroySlaves() +{ + while (!_slavesADC->isEmpty()) { + ADC * __slave = _slavesADC->takeAt(0); + __slave->stop(); + delete __slave; + } +} + + +ADC * ADCCluster_device::_initSlaveADC(uint16_t sn, int adcNo) +{ + ADC * __slaveADC = new ADC(sn); + + QObject::connect(__slaveADC, &ADC::ADC_connected, [=]() { + #ifdef CLUSTER_DEBUG + qDebug() << QTime::currentTime().toString() << "slave adc connected" << sn; + #endif + }); + + QObject::connect(__slaveADC, &ADC::ADC_configReadingFinished, [=]() { + #ifdef CLUSTER_DEBUG + _debugPrintADCParams(__slaveADC); + #endif + + if (__slaveADC->getModelID() == 0) { // TODO критериии некорректной инициализации + #ifdef CLUSTER_DEBUG + qDebug() << "init failed"; + #endif + //emit status_changed(fail); + return; + } + + __slaveADC->startADC(ADC_ns::SYNCMode::SLAVE); + }); + + QObject::connect(__slaveADC, &ADC::ADC_dataPacketReceived, [=](QByteArray packet) { + trans_ns::bytes2long __num; + __num.bytes.LL = packet[0]; + __num.bytes.LH = packet[1]; + __num.bytes.HL = packet[2]; + __num.bytes.HH = packet[3]; + + if (__slaveADC->getDataCounter() != __num.twinWord) { + #ifdef CLUSTER_DEBUG + qDebug() << "propusk: " << __slaveADC->getSN() << __slaveADC->getDataCounter() << " : " << __num.twinWord << " packets lost = " << __num.twinWord - __slaveADC->getDataCounter(); + #endif + __slaveADC->setDataCounter(__num.twinWord); + } + __slaveADC->incDataCounter(); + + clusterStack * __currentStack = _rrdStack->getStackByPacketNumber(__num.twinWord); + + if (__currentStack == nullptr) { + __currentStack = new clusterStack(45 * (int)(__num.twinWord / 45), _masterADC->getBitDeph(), _masterADC->getChannelsConfig(), _masterADC->getSamplesCount(), _slavesADC); + _clusterQueue->enqueue(_rrdStack->pushStack(__currentStack)); + } + + int __packetIndex = __num.twinWord - __currentStack->getFirstPacketIndex(); + + __currentStack->insert(adcNo, __packetIndex, packet); + }); + + return __slaveADC; +} + +void ADCCluster_device::_runSingleMode() +{ + int __samplesCount = _singleADC->getSamplesCount(); + int __clusterPacketSize = 45; + int __outputChannelsCount = _singleADC->getChannelsCount() + (int)_singleADC->getIsTachoSignal(); + int __tachoCannelIndex = _singleADC->getChannelsCount(); + int __oneBufferSize = __clusterPacketSize * __samplesCount * __outputChannelsCount; + + QQueue __temp; + + while (!_singleQueue->isEmpty()) { + __temp.enqueue(_singleQueue->dequeue()); + } + + if (__temp.count() == 0) return; + + _buffer_read.resize(__oneBufferSize * __temp.count()); + + for (int __i = 0; __i < __oneBufferSize * __temp.count(); __i++) { + _buffer_read[__i] = 0; + } + + int __counter = 0; + + while (!__temp.isEmpty()) { + singleStack * __clusterPacket = __temp.dequeue(); + + for (int __packetNo = 0; __packetNo < __clusterPacketSize; __packetNo++) { + for (int __sampleNo = 0; __sampleNo < __samplesCount; __sampleNo++) { + // номер пакета * количество отсчетов * количество каналов + количество каналов * номер отсчета + int __samplePosition = __outputChannelsCount * (__packetNo * __samplesCount + __sampleNo); + + for (int __channelIndex = 0; __channelIndex < _singleADC->getChannelsCount(); __channelIndex++) { + // количество отсчетов * номер канала + порядковый номер отсчета + _buffer_read[__oneBufferSize * __counter + __samplePosition + __channelIndex] + = __clusterPacket->getValue(__packetNo, __channelIndex, __sampleNo); + } + + if (_singleADC->getIsTachoSignal()) { + _buffer_read[__oneBufferSize * __counter + __samplePosition + __tachoCannelIndex] + = __clusterPacket->getTachoValue(__packetNo, __sampleNo) ? 1.0 : 0.0; + } + } + } + delete __clusterPacket; + __counter++; + } + + send_data(); +} + +void ADCCluster_device::_runClusterMode() +{ + int __adcCount = _slavesADC->count() + 1; // '1' это мастер + int __aiCount = _masterADC->getChannelsCount(); + //int __samplesCount = 333; + int __samplesCount = _masterADC->getSamplesCount(); + int __clusterPacketSize = 45; + int __outputChannelsCount = (_masterADC->getChannelsCount() + _masterADC->getIsTachoSignal()) * (_slavesADC->count() + 1); + int __oneBufferSize = __clusterPacketSize * __samplesCount * __outputChannelsCount; + + QQueue __snapshotQueue; + + while (!_clusterQueue->isEmpty()) { + clusterStack * __stackTemp = _clusterQueue->dequeue(); + if (__stackTemp!=nullptr) __snapshotQueue.enqueue(__stackTemp); + } + + if (__snapshotQueue.count() == 0) return; + + _buffer_read.resize(__oneBufferSize * __snapshotQueue.count()); + + for (int __i = 0; __i < __oneBufferSize * __snapshotQueue.count(); __i++) { + _buffer_read[__i] = 0; + } + + int __counter = 0; + + while (!__snapshotQueue.isEmpty()) { + clusterStack * __clusterPacket = __snapshotQueue.dequeue(); + + for (int __packetNo = 0; __packetNo < __clusterPacketSize; __packetNo++) { + + for (int __sampleNo = 0; __sampleNo < __samplesCount; __sampleNo++) { + + // номер пакета * количество отсчетов * количество каналов + количество каналов * номер отсчета + int __samplePosition = __outputChannelsCount * (__packetNo * __samplesCount + __sampleNo); + + int __offset = 0; + + for (int __adcNo = 0; __adcNo < __adcCount; __adcNo++) { + for (int __channelIndex = 0; __channelIndex < __aiCount; __channelIndex++) { + // количество отсчетов * номер канала + порядковый номер отсчета + if (__oneBufferSize * __counter + __samplePosition + __offset > _buffer_read.size()) { + qDebug() << __oneBufferSize * __counter + __samplePosition + __offset; + } + _buffer_read[__oneBufferSize * __counter + __samplePosition + __offset] + = __clusterPacket->getValue(__adcNo, __packetNo, __channelIndex, __sampleNo); + __offset++; + } + + if (_masterADC->getIsTachoSignal()) { + _buffer_read[__oneBufferSize * __counter + __samplePosition + __offset] + = __clusterPacket->getTachoValue(__adcNo, __packetNo, __sampleNo) ? 1.0 : 0.0; // TODO tacho + __offset++; + } + } + } + } + + delete __clusterPacket; + __counter++; + } + send_data(); +} + +void ADCCluster_device::_debugPrintADCParams(ADC *adc) +{ + qDebug() << "Model id: " << adc->getModelID(); + qDebug() << "Device sn: " << adc->getSN(); + qDebug() << "DHCP mode: " << adc->getIsDHCPMode(); + qDebug() << "MAC address: " << adc->getMacAddress().toHex('-'); + qDebug() << "Default address: " << adc->getDefaultIP().toString(); + qDebug() << "Default gate: " << adc->getDefaultGate().toString(); + qDebug() << "Default mask: " << adc->getDefaultMask().toString(); + qDebug() << "Static address: " << adc->getStaticIP().toString(); + qDebug() << "Static gate: " << adc->getStaticGate().toString(); + qDebug() << "Static mask: " << adc->getStaticMask().toString(); + qDebug() << "Channels count: " << adc->getChannelsCount(); + qDebug() << "Bit depth: " << (adc->getBitDeph() == ADC_ns::bitDepth::BIT16 ? "16" : "24"); + + for (int i=0; i< adc->getChannelsConfig()->count(); i++) { + qDebug() << "Channel " << i << " Factor: " << adc->getChannelsConfig()->at(i)->getFactor(); + } + + qDebug() << "Is tacho signal: " << adc->getIsTachoSignal(); + qDebug() << "Is gen: " << adc->getIsGen(); + qDebug() << "ADC freq: " << adc->getFreq(); +} + +void ADCCluster_device::_stateChanged(gtl::hw::device::ADCState state) +{ + emit status_changed(state); +} + +void ADCCluster_device::ai_iepe_changed() +{ + if (_singleMode) { + for (int __i = 0; __i < _count_ai; __i++) { + if (ai(__i) == sender()) { + #ifdef CLUSTER_DEBUG + qDebug() << "single mode: iepe changed: channel: " << __i << "to:" << ai(__i)->is_iepe(); + #endif + _singleADC->setChannelIEPE(__i, ai(__i)->is_iepe()); + } + } + } else { + for (int __i = 0; __i < _count_ai; __i++) { + if (ai(__i) == sender()) { + int __adcIndex = __i / (_masterADC->getChannelsCount() + _masterADC->getIsTachoSignal()); + int __channelIndex = __i % (_masterADC->getChannelsCount() + _masterADC->getIsTachoSignal()); + bool __IPEPEnable = ai(__i)->is_iepe(); + + if (__adcIndex == 0) { + // master + #ifdef CLUSTER_DEBUG + qDebug() << "cluster iepe changed: master"; + qDebug() << "cluster iepe changed: ADC SN: " << _masterADC->getSN(); + qDebug() << "cluster iepe changed: channel: " << __channelIndex; + qDebug() << "cluster iepe changed: " << __IPEPEnable; + #endif + _masterADC->setChannelIEPE(__channelIndex, __IPEPEnable); + } else { + // slave + __adcIndex = __adcIndex - 1; + + #ifdef CLUSTER_DEBUG + qDebug() << "cluster iepe changed: slave index: " << __adcIndex; + qDebug() << "cluster iepe changed: ADC SN: " << _slavesADC->at(__adcIndex)->getSN(); + qDebug() << "cluster iepe changed: channel: " << __channelIndex; + qDebug() << "cluster iepe changed: " << __IPEPEnable; + #endif + _slavesADC->at(__adcIndex)->setChannelIEPE(__channelIndex, __IPEPEnable); + } + } + } + } +} + +void ADCCluster_device::ai_coupling_changed() +{ + if (_singleMode) { + for (int __i = 0; __i < _count_ai; __i++) { + if (ai(__i) == sender()) { + #ifdef CLUSTER_DEBUG + qDebug() << "single mode: channel coupling changed: channel: " << __i << "to:" << ai(__i)->is_coupling(); + #endif + if (ai(__i)->is_coupling()) { + _singleADC->setChannelMODE(__i, ADC_ns::channelMode::AC); + } else { + _singleADC->setChannelMODE(__i, ADC_ns::channelMode::ACDC); + } + } + } + } else { + for (int __i = 0; __i < _count_ai; __i++) { + if (ai(__i) == sender()) { + int __adcIndex = __i / (_masterADC->getChannelsCount() + _masterADC->getIsTachoSignal()); + int __channelIndex = __i % (_masterADC->getChannelsCount() + _masterADC->getIsTachoSignal()); + bool __couplingEnable = ai(__i)->is_coupling(); + + if (__adcIndex == 0) { + // master + #ifdef CLUSTER_DEBUG + qDebug() << "cluster coupling changed: master"; + qDebug() << "cluster coupling changed: ADC SN: " << _masterADC->getSN(); + qDebug() << "cluster coupling changed: channel: " << __channelIndex; + qDebug() << "cluster coupling changed: " << __couplingEnable; + #endif + if (ai(__i)->is_coupling()) { + _masterADC->setChannelMODE(__channelIndex, ADC_ns::channelMode::AC); + } else { + _masterADC->setChannelMODE(__channelIndex, ADC_ns::channelMode::ACDC); + } + } else { + // slave + __adcIndex = __adcIndex - 1; + + #ifdef CLUSTER_DEBUG + qDebug() << "cluster coupling changed: slave index: " << __adcIndex; + qDebug() << "cluster coupling changed: ADC SN: " << _slavesADC->at(__adcIndex)->getSN(); + qDebug() << "cluster coupling changed: channel: " << __channelIndex; + qDebug() << "cluster coupling changed: " << __couplingEnable; + #endif + if (ai(__i)->is_coupling()) { + _slavesADC->at(__adcIndex)->setChannelMODE(__channelIndex, ADC_ns::channelMode::AC); + } else { + _slavesADC->at(__adcIndex)->setChannelMODE(__channelIndex, ADC_ns::channelMode::ACDC); + } + } + } + } + } +} + +ADC *ADCCluster_device::_initSingleADC(uint16_t sn) +{ + ADC * __singleADC = new ADC(sn); + + QObject::connect(__singleADC, &ADC::ADC_connected, [=]() { + #ifdef CLUSTER_DEBUG + qDebug() << QTime::currentTime().toString() << "single adc connected" << sn; + #endif + }); + + QObject::connect(__singleADC, &ADC::ADC_disconnected, [=]() { + #ifdef CLUSTER_DEBUG + qDebug() << QTime::currentTime().toString() << "single adc disconnected"; + #endif + + // hw api + _count_ai = 0; + set_ai(); + _stateChanged(gtl::hw::device::ADCState::FAILED); + // hw api + }); + + QObject::connect(__singleADC, &ADC::ADC_started, [=](){ + _state = ADCCluster_ns::clusterState::RUNNING; + _stateChanged(gtl::hw::device::ADCState::OK); + }); + + QObject::connect(__singleADC, &ADC::ADC_configReadingFinished, [=]() { + // TODO применить загруженные из XML настройки и как-то передать их в ADC + // и вызвать ещё раз load - т.к. мы только сейчас знаем какие настойки + + #ifdef CLUSTER_DEBUG + _debugPrintADCParams(__singleADC); + #endif + + if (__singleADC->getModelID() == 0) { // TODO критериии некорректной инициализации + #ifdef CLUSTER_DEBUG + qDebug() << "init failed"; + #endif + _stateChanged(gtl::hw::device::ADCState::INIT_FAILED); + return; + } + + // hw api + _rate = __singleADC->getFreq(); + _device->set_rate(__singleADC->getFreq()); + _count_ai = __singleADC->getChannelsCount() + (int)__singleADC->getIsTachoSignal(); + + set_ai(); + + for (int __i = 0; __i < __singleADC->getChannelsCount(); __i++) { + ai(__i)->set_name("input [" + QString::number(__i) + "]"); + } + + if (__singleADC->getIsTachoSignal()) ai(__singleADC->getChannelsCount())->set_name("pulse"); + // hw api + + __singleADC->startADC(ADC_ns::SYNCMode::MASTER); + }); + + QObject::connect(__singleADC, &ADC::ADC_dataPacketReceived, [=](QByteArray packet) { + trans_ns::bytes2long __num; + __num.bytes.LL = packet[0]; + __num.bytes.LH = packet[1]; + __num.bytes.HL = packet[2]; + __num.bytes.HH = packet[3]; + + int __packetIndex = __num.twinWord - _currentSingleStack->getFirstPacketIndex(); + + if (__num.twinWord - _currentSingleStack->getFirstPacketIndex() > 44) { + _singleQueue->enqueue(_currentSingleStack); + _currentSingleStack = new singleStack(__num.twinWord, __singleADC->getBitDeph(), __singleADC->getChannelsConfig(), __singleADC->getSamplesCount()); + _currentSingleStack->insert(__packetIndex % 45, packet); + } else { + _currentSingleStack->insert(__packetIndex, packet); + } + + if (__singleADC->getDataCounter() != __num.twinWord) { + + #ifdef CLUSTER_DEBUG + qDebug() << "propusk: " << __singleADC->getSN() << __singleADC->getDataCounter() + << " : " + << __num.twinWord + << " packets lost = " + << __num.twinWord - __singleADC->getDataCounter(); + #endif + + __singleADC->setDataCounter(__num.twinWord); + } + __singleADC->incDataCounter(); + }); + + // hw api + _device->set_name("S_" + QString::number(sn)); + _device->set_id(QString::number(sn)); + _id = QString::number(sn); + _stateChanged(gtl::hw::device::ADCState::INIT_IN_PROGRESS); + // hw api + + return __singleADC; +} + +bool ADCCluster_device::start(QString id, qreal rate) +{ + #ifdef CLUSTER_DEBUG + qDebug() << "external cluster start: " << id; + #endif + + bool __idIsDigit; + uint16_t __sn = id.toInt(&__idIsDigit); + + if (__idIsDigit) { + // TODO вычитать настройки из XML и применить их + _state = ADCCluster_ns::clusterState::CONFIG; + _id = id; + + #ifdef CLUSTER_DEBUG + qDebug() << "cluster in simple mode"; + #endif + _singleADC = _initSingleADC(__sn); + _singleADC->start(); + _singleMode = true; + } else { + _state = ADCCluster_ns::clusterState::CONFIG; + + _id = id; + + #ifdef CLUSTER_DEBUG + qDebug() << "cluster in extended mode"; + #endif + + _device->set_name("ADCCluster [starting...]"); + + QList * __SNs = new QList(); + + foreach(QString __subString, id.split(',')) { + bool __snCheck; + int __ADCsn = __subString.toInt(&__snCheck); + if (__snCheck) __SNs->append(__ADCsn); + } + + if (__SNs->count() == 0) { + _initError = true; + return false; + } + + _masterADC = _initMasterADC(__SNs); + _masterADC->start(); + + _singleMode = false; + } + + QThread::start(QThread::HighestPriority); + + return true; +} + +bool ADCCluster_device::stop() +{ + if (_initError) return true; + + if (_state != ADCCluster_ns::clusterState::IDLE) { + if (_singleMode) { + _state = ADCCluster_ns::clusterState::IDLE; + msleep(110); + _singleADC->stop(); + delete(_singleADC); + } else { + _state = ADCCluster_ns::clusterState::IDLE; + + _isMasterAllreadyInited = false; + + msleep(110); + _masterADC->stop(); + delete(_masterADC); + + while (_slavesADC->count() > 0) { + ADC * __slave =_slavesADC->takeAt(0); + __slave->stop(); + msleep(110); + delete(__slave); + } + } + } + + return true; +} + +void ADCCluster_device::run() +{ + while (_state != ADCCluster_ns::clusterState::IDLE) + { + if (_state == ADCCluster_ns::clusterState::RUNNING) { + if (_singleMode) { + _runSingleMode(); + } else { + _runClusterMode(); + } + } + msleep(150); + } +} + +void ADCCluster_device::send_data() +{ + set_ai_data(&_buffer_read[0], (int)_buffer_read.size()); + + emit received_data(); +} diff --git a/hw/ADCCluster/adccluster_device.h b/hw/ADCCluster/adccluster_device.h new file mode 100644 index 0000000..bfcb6c6 --- /dev/null +++ b/hw/ADCCluster/adccluster_device.h @@ -0,0 +1,72 @@ +#ifndef ADCCLUSTER_DEVICE_H +#define ADCCLUSTER_DEVICE_H + +#include +#include +#include +#include + +#include "singlestack.h" +#include "clusterstack.h" +#include "adc.h" +#include "hw/gtl_hw_device.h" +#include "rrdstack.h" + +class ADCCluster_device : public gtl::hw::device +{ + Q_OBJECT +public: + explicit ADCCluster_device(QObject *parent = nullptr); + ~ADCCluster_device(); + + // HW API + virtual void restart() override; + virtual bool start(QString id, qreal rate) override; + virtual bool stop() override; + virtual QString type() const override { return "ADCCluster"; } + virtual int channels() { return 1; } + virtual qreal max_amplitude() const { return 10; } + virtual void ai_iepe_changed() override; + virtual void ai_coupling_changed() override; + // HW API + +signals: + void newADCCreated(ADC * newAdc); + +private: + ADC * _initSingleADC(uint16_t sn); + ADC * _initMasterADC(QList * adcSNs); + + ADC * _initSlaveADC(uint16_t sn, int adcNo); + void _runSingleMode(); + void _runClusterMode(); + + void _stopSlaves(); + void _destroySlaves(); + void _debugPrintADCParams(ADC * adc); + + void _stateChanged(gtl::hw::device::ADCState state); + + // HW API + void run() override; + std::vector _buffer_read; + void send_data(); + // HW API + + ADC * _singleADC; + ADC * _masterADC; + QList * _slavesADC; + QMap * _adcs; + QJsonObject * _config; + ADCCluster_ns::clusterState _state; + RRDStack * _rrdStack; + bool _isMasterAllreadyInited; + + singleStack * _currentSingleStack; + QQueue * _singleQueue; + QQueue * _clusterQueue; + bool _singleMode; + bool _initError; +}; + +#endif // ADCCLUSTER_DEVICE_H diff --git a/hw/ADCCluster/adcqueue.cpp b/hw/ADCCluster/adcqueue.cpp new file mode 100644 index 0000000..5b20525 --- /dev/null +++ b/hw/ADCCluster/adcqueue.cpp @@ -0,0 +1,191 @@ +#include "adcqueue.h" + +ADCQueue::ADCQueue(QObject *parent) + : QObject{parent}, + _cmdItems(new QQueue), + _state(ADCQueue_ns::queueState::stopped), + _currentCmd(0), + _timer(new QTimer()), + _repeatCount(0) +{ + _timer->setInterval(QUEUE_TIMEOUT); + + QObject::connect(_timer, &QTimer::timeout, [=]{ + if (_currentItem.waiting == true) { + _timer->stop(); + _repeatCount++; + _repeatCurrent(); + } + }); +} + +ADCQueue::~ADCQueue() +{ + #ifdef STP_DEBUG + qDebug() << "queue desctructor"; + #endif +} + +bool ADCQueue::clear() +{ + if (_state !=ADCQueue_ns::queueState::stopped) return false; + + _cmdItems->clear(); + _currentCmd = 0; + + return true; +} + +void ADCQueue::appendCmd(ADCQueue_ns::cmdItem cmd) +{ + _currentCmd++; + cmd.id = _currentCmd; + _cmdItems->enqueue(cmd); + + if (_state == ADCQueue_ns::queueState::stopped) start(); +} + +void ADCQueue::appendCmd(stpProtocol_ns::stpCommand cmd, uint16_t regNumber, QByteArray * regData) +{ + ADCQueue_ns::cmdItem __cmd; + __cmd.cmd = cmd; + __cmd.regNumber = regNumber; + __cmd.regData = regData; + appendCmd(__cmd); +} + +bool ADCQueue::start() +{ + if (_state !=ADCQueue_ns::queueState::stopped) return false; + if (_cmdItems->isEmpty()) return false; + + _startNext(); + + return true; +} + +bool ADCQueue::stop() +{ + if (_state == ADCQueue_ns::queueState::stopped) return false; + + _state = ADCQueue_ns::queueState::stopped; + + clear(); + + return true; +} + +bool ADCQueue::receiveData(QByteArray data) +{ + + if (_headerCheck(&data)) { + QByteArray * __header = new QByteArray(data.first(CONST_REGISTER_HEADER_SIZE)); + + _currentItem.waiting = false; + _timer->stop(); + + switch ((*__header)[0]) { + case stpProtocol_ns::stpCommand::REG_READ_ACK: + _currentItem.regData = new QByteArray(data.last(data.size() - CONST_REGISTER_HEADER_SIZE)); + emit registerReadFinished(_currentItem); + break; + + case stpProtocol_ns::stpCommand::REG_READ_ERROR: + emit registerReadError(_currentItem); + break; + + case stpProtocol_ns::stpCommand::REG_WRITE_ACK: + emit registerWriteFinished(_currentItem); + break; + + case stpProtocol_ns::stpCommand::REG_WRITE_ERROR: + emit registerWriteError(_currentItem); + break; + } + + _startNext(); + return true; + } + return false; +} + +void ADCQueue::_startNext() +{ + if (!_cmdItems->isEmpty()) { + _currentItem = _cmdItems->dequeue(); + _currentItem.waiting = true; + emit sendData(_packCommand(_currentItem)); + + _repeatCount = 0; + _timer->start(); + + _state = ADCQueue_ns::queueState::waiting; + } else { + _state = ADCQueue_ns::queueState::stopped; + emit queueIsEmpty(); + } +} + +void ADCQueue::_repeatCurrent() +{ + if (_repeatCount == QUEUE_TIMEOUT_COUNT) { + _startNext(); + } else { + emit sendData(_packCommand(_currentItem)); + _timer->start(); + } +} + +bool ADCQueue::_headerCheck(QByteArray *data) +{ + if (_state != ADCQueue_ns::queueState::waiting) return false; + + if (data->size() < CONST_REGISTER_HEADER_SIZE) return false; + + QByteArray * __header = new QByteArray(data->first(CONST_REGISTER_HEADER_SIZE)); + + if (_currentItem.cmd == stpProtocol_ns::stpCommand::REG_READ) + if (!((*__header)[0] == stpProtocol_ns::stpCommand::REG_READ_ACK || (*__header)[0] == stpProtocol_ns::stpCommand::REG_READ_ERROR)) return false; + + if (_currentItem.cmd == stpProtocol_ns::stpCommand::REG_WRITE) + if (!((*__header)[0] == stpProtocol_ns::stpCommand::REG_WRITE_ACK || (*__header)[0] == stpProtocol_ns::stpCommand::REG_WRITE_ERROR)) return false; + + trans_ns::bytes2word __regNumber; + __regNumber.bytes.L = (*__header)[3]; + __regNumber.bytes.H = (*__header)[4]; + + if (__regNumber.word != _currentItem.regNumber) return false; + + trans_ns::bytes2word __id; + __id.bytes.L = (*__header)[1]; + __id.bytes.H = (*__header)[2]; + + if (__id.word != _currentItem.id) return false; + + return true; +} + +QByteArray ADCQueue::_packCommand(ADCQueue_ns::cmdItem cmd) +{ + QByteArray __result; + + trans_ns::bytes2word __currentCmd; + __currentCmd.word = cmd.id; + + trans_ns::bytes2word __regNum; + __regNum.word = cmd.regNumber; + + __result.resize(CONST_REGISTER_HEADER_SIZE); + __result[0]=cmd.cmd; + __result[1]=__currentCmd.bytes.L; + __result[2]=__currentCmd.bytes.H; + __result[3]=__regNum.bytes.L; + __result[4]=__regNum.bytes.H; + + if (cmd.cmd == stpProtocol_ns::stpCommand::REG_WRITE) { + __result.append(*cmd.regData); + } + + return __result; +} + diff --git a/hw/ADCCluster/adcqueue.h b/hw/ADCCluster/adcqueue.h new file mode 100644 index 0000000..0f443a5 --- /dev/null +++ b/hw/ADCCluster/adcqueue.h @@ -0,0 +1,54 @@ +#ifndef ADCQUEUE_H +#define ADCQUEUE_H + +#include +#include +#include +#include + +#include "stpdefine.h" + +class ADCQueue : public QObject +{ + Q_OBJECT + +public: + + explicit ADCQueue(QObject *parent = nullptr); + ~ADCQueue(); + + bool clear(); + void appendCmd(ADCQueue_ns::cmdItem cmd); + void appendCmd(stpProtocol_ns::stpCommand cmd, uint16_t regNumber, QByteArray * regData = new QByteArray()); + bool start(); + bool stop(); + + ADCQueue_ns::queueState getState() {return _state; }; + + bool receiveData(QByteArray data); + +signals: + void queueIsEmpty(); + void queueTimeout(); + void queueError(); + void registerReadFinished(ADCQueue_ns::cmdItem cmd); + void registerReadError(ADCQueue_ns::cmdItem cmd); + void registerWriteFinished(ADCQueue_ns::cmdItem cmd); + void registerWriteError(ADCQueue_ns::cmdItem cmd); + void sendData(QByteArray data); + +private: + void _startNext(); + void _repeatCurrent(); + bool _headerCheck(QByteArray * data); + QByteArray _packCommand(ADCQueue_ns::cmdItem cmd); + + QQueue * _cmdItems; + ADCQueue_ns::queueState _state; + ADCQueue_ns::cmdItem _currentItem; + uint16_t _currentCmd; + QTimer * _timer; + int _repeatCount; +}; + +#endif // ADCQUEUE_H diff --git a/hw/ADCCluster/adcregisters.h b/hw/ADCCluster/adcregisters.h new file mode 100644 index 0000000..ed3349e --- /dev/null +++ b/hw/ADCCluster/adcregisters.h @@ -0,0 +1,40 @@ +#ifndef ADCREGISTERS_H +#define ADCREGISTERS_H + +#define NETWORK_MODE 0 +#define MAC_ADDRESS 1 +#define DEFAULT_IP 2 +#define DEFAULT_GATE 3 +#define DEFAULT_MASK 4 +#define STATIC_IP 5 +#define STATIC_GATE 6 +#define STATIC_MASK 7 + +#define MODEL_ID 100 +#define DEVICE_SN 101 + +#define CMD_REBOOT 200 +#define CMD_SAVE_REBOOT 201 +#define SYNC_MODE 202 +#define ADC_START 203 +#define DAC_START 204 +#define CMD_RESET_NETWORK 205 + +#define CHANNELS_COUNT 300 +#define IS_TACHO_SIGNAL 301 +#define IS_GEN 302 +#define ADC_FREQ 303 +#define CALIBRATION_FACTOR 304 +#define BIT_DEPTH 305 + +#define CH_IEPE_OFFSET 311 +#define CH_ACDC_OFFSET 321 + +#define DAC_HPF 400 +#define DAC_LPF 401 +#define DAC_MODE 402 +#define DAM_AMP 403 + +#define BLIND_REGISTER 1111 + +#endif // ADCREGISTERS_H diff --git a/hw/ADCCluster/clusterstack.cpp b/hw/ADCCluster/clusterstack.cpp new file mode 100644 index 0000000..b95a84f --- /dev/null +++ b/hw/ADCCluster/clusterstack.cpp @@ -0,0 +1,123 @@ +#include "clusterstack.h" + +clusterStack::clusterStack(int firstPacketIndex, ADC_ns::bitDepth bitDepth, QList *channelsConfig, int channelOffset, QList * slaves, QObject *parent) + : QObject{parent}, + _bitDepth(bitDepth), + _channelsCount(0), + _tachoOffset(0), + _bytesInSample(2), + _firstPacketIndex(firstPacketIndex) +{ + double __baseFactor = 0; + + if (bitDepth == ADC_ns::bitDepth::BIT16) { + __baseFactor = (double)12500/32768; + _bytesInSample = 2; + } else if (bitDepth == ADC_ns::bitDepth::BIT24) { + __baseFactor = (double)12500/8388608; + _bytesInSample = 3; + } + + for (int i=0; icount(); i++) { + _channelFactors[0][i] = __baseFactor * channelsConfig->at(i)->getFactor() / 1000; // mV -> V + } + + for (int __slaveIndex = 0; __slaveIndex < slaves->count(); __slaveIndex++) { + QList * __channelsConfig = slaves->at(__slaveIndex)->getChannelsConfig(); + + for (int i=0; i<__channelsConfig->count(); i++) { + _channelFactors[__slaveIndex + 1][i] = __baseFactor * __channelsConfig->at(i)->getFactor() / 1000; // mV -> V + } + } + + _tachoOffset = _bytesInSample * channelOffset * channelsConfig->count() + DATA_OFFSET; + _channelsCount = channelsConfig->count(); +} + +void clusterStack::insert(int adcIndex, int packetIndex, QByteArray packet) +{ + _packets[adcIndex][packetIndex] = packet; +} + +double clusterStack::getValue(int adcIndex, int packetIndex, uint8_t channel, int sampleNo) +{ + trans_ns::bytes2word __point; + trans_ns::bytes2long __longPoint; + + int __pointOffset = (channel + _channelsCount * sampleNo) * _bytesInSample + DATA_OFFSET; + + if (__pointOffset < 0) { + #ifdef CLUSTER_DEBUG + qDebug() << "(!) pointOffset: " << __pointOffset; + qDebug() << "(!) channel: " << channel; + qDebug() << "(!) channelsCount: " << _channelsCount; + qDebug() << "(!) sampleNo: " << sampleNo; + qDebug() << "(!) bytesInSample: " << _bytesInSample; + #endif + + return (double)0; + } + + if (_packets[adcIndex][packetIndex].size() < __pointOffset + _bytesInSample) { + return (double)0; + } else { + if (_bytesInSample == 2) { + __point.bytes.L = _packets[adcIndex][packetIndex][__pointOffset]; + __point.bytes.H = _packets[adcIndex][packetIndex][__pointOffset + 1]; + return (double)((int16_t)__point.word * _channelFactors[adcIndex][channel]); + } else if (_bytesInSample == 3) { + __longPoint.bytes.LL = _packets[adcIndex][packetIndex][__pointOffset]; + __longPoint.bytes.LH = _packets[adcIndex][packetIndex][__pointOffset + 1]; + __longPoint.bytes.HL = _packets[adcIndex][packetIndex][__pointOffset + 2]; + if (__longPoint.bytes.HL > 127) { + __longPoint.bytes.HH = 0xFF; + } else { + __longPoint.bytes.HH = 0x00; + } + + return (double)((int32_t)__longPoint.twinWord * _channelFactors[adcIndex][channel]); + + } else return (double)0; } +} + +bool clusterStack::getTachoValue(int adcIndex, int packetIndex, int sampleNo) +{ + trans_ns::byte2bits __byte; + int __byteOffset = _tachoOffset + (int)(sampleNo / 8); + int __bitOffset = sampleNo % 8; + + if (_packets[adcIndex][packetIndex].size() < __byteOffset + 1) { + return false; + } else { + __byte.byte = _packets[adcIndex][packetIndex][__byteOffset]; + + switch (__bitOffset) { + case 0: + return __byte.bits.a0 == 1; + break; + case 1: + return __byte.bits.a1 == 1; + break; + case 2: + return __byte.bits.a2 == 1; + break; + case 3: + return __byte.bits.a3 == 1; + break; + case 4: + return __byte.bits.a4 == 1; + break; + case 5: + return __byte.bits.a5 == 1; + break; + case 6: + return __byte.bits.a6 == 1; + break; + case 7: + return __byte.bits.a7 == 1; + break; + + default: return false; + } + } +} diff --git a/hw/ADCCluster/clusterstack.h b/hw/ADCCluster/clusterstack.h new file mode 100644 index 0000000..94b7e9c --- /dev/null +++ b/hw/ADCCluster/clusterstack.h @@ -0,0 +1,31 @@ +#ifndef CLUSTERSTACK_H +#define CLUSTERSTACK_H + +#include "adc.h" +#include "adcchannel.h" +#include + +#define DATA_OFFSET 4 // смещение из-за 4 байт спереди - номер пакета + +class clusterStack : public QObject +{ + Q_OBJECT +public: + explicit clusterStack(int firstPacketIndex, ADC_ns::bitDepth bitDepth, QList * channelsConfig, int channelOffset, QList * slaves, QObject *parent = nullptr); + + void insert(int adcIndex, int packetIndex, QByteArray packet); + double getValue(int adcIndex, int packetIndex, uint8_t channel, int sampleNo); + bool getTachoValue(int adcIndex, int packet, int point); + int getFirstPacketIndex() { return _firstPacketIndex; } + +private: + QByteArray _packets[16][45]; + ADC_ns::bitDepth _bitDepth; + double _channelFactors[16][4]; + int _channelsCount; + int _tachoOffset; + uint8_t _bytesInSample; + uint32_t _firstPacketIndex; +}; + +#endif // CLUSTERSTACK_H diff --git a/hw/ADCCluster/idserver.cpp b/hw/ADCCluster/idserver.cpp new file mode 100644 index 0000000..2f90edf --- /dev/null +++ b/hw/ADCCluster/idserver.cpp @@ -0,0 +1,61 @@ +#include "idserver.h" + +idServer::idServer(uint16_t sn, QObject *parent) + : QObject{parent}, + _socket(new QUdpSocket()), + _addresses(new QList()), + _sn(sn) +{ + _socket = new QUdpSocket(); + + QObject::connect(_socket, &QUdpSocket::readyRead, [=] { + _processData(_socket->receiveDatagram()); + }); + + foreach(auto __if, QNetworkInterface::allInterfaces()) { + foreach(auto __addressEntry, __if.addressEntries()) { + bool __isIPv4 = false; + __addressEntry.ip().toIPv4Address(&__isIPv4); + if (__isIPv4) { + _addresses->append(__addressEntry.broadcast()); + } + } + } +} + +void idServer::sendIdRequest() +{ + foreach(auto __address, *_addresses) { + _socket->writeDatagram(stpClient::packIdSnRequest(_sn), __address, 7000); + } +} + +void idServer::_processData(QNetworkDatagram dg) +{ + QByteArray __data = dg.data(); + + if (__data.size() < CONST_HEADER_SIZE) return; + + trans_ns::bytes2word __size; + + __size.bytes.L = __data[1]; + __size.bytes.H = __data[2]; + + if (__data[0] == stpProtocol_ns::packetType::DATA) { + if (uint8_t(__data[3]) == uint8_t(stpProtocol_ns::stpCommand::ID) + && (__size.word == 5)) { + { + trans_ns::bytes2word __model; + __model.bytes.L = __data[4]; + __model.bytes.H = __data[5]; + + trans_ns::bytes2word __sn; + __sn.bytes.L = __data[6]; + __sn.bytes.H = __data[7]; + + emit newIdMessage(__model.word, __sn.word, dg.senderAddress()); + } + } + } +} + diff --git a/hw/ADCCluster/idserver.h b/hw/ADCCluster/idserver.h new file mode 100644 index 0000000..14aef0e --- /dev/null +++ b/hw/ADCCluster/idserver.h @@ -0,0 +1,31 @@ +#ifndef IDSERVER_H +#define IDSERVER_H + +#include +#include +#include +#include +#include + +#include "stpdefine.h" +#include "stpclient.h" + +class idServer : public QObject +{ + Q_OBJECT +public: + explicit idServer(uint16_t sn, QObject *parent = nullptr); + + void sendIdRequest(); + +signals: + void newIdMessage(int modelId, int sn, QHostAddress address); + +private: + void _processData(QNetworkDatagram dg); + QUdpSocket * _socket; + QList * _addresses; + uint16_t _sn; +}; + +#endif // IDSERVER_H diff --git a/hw/ADCCluster/rrdstack.cpp b/hw/ADCCluster/rrdstack.cpp new file mode 100644 index 0000000..982c86c --- /dev/null +++ b/hw/ADCCluster/rrdstack.cpp @@ -0,0 +1,40 @@ +#include "rrdstack.h" + +RRDStack::RRDStack(QObject *parent) + : QObject{parent} +{ + for (int __i=0; __igetFirstPacketIndex() < 45) { + __stack = _stacks[__i]; + } + } + } + + return __stack; +} + +clusterStack *RRDStack::pushStack(clusterStack *newClusterStack) +{ + clusterStack * __pushingStack = _stacks[_stackPointer]; + _stacks[_stackPointer] = newClusterStack; + + _stackPointer++; + + if (_stackPointer == STACK_SIZE) { + _stackPointer = 0; + } + + return __pushingStack; +} diff --git a/hw/ADCCluster/rrdstack.h b/hw/ADCCluster/rrdstack.h new file mode 100644 index 0000000..bf38ae6 --- /dev/null +++ b/hw/ADCCluster/rrdstack.h @@ -0,0 +1,26 @@ +#ifndef RRDSTACK_H +#define RRDSTACK_H + +#include + +#include "clusterstack.h" + +#define STACK_SIZE 10 + +class RRDStack : public QObject +{ + Q_OBJECT +public: + explicit RRDStack(QObject *parent = nullptr); + clusterStack * getStackByPacketNumber(uint32_t packetNumber); + clusterStack * pushStack(clusterStack * newClusterStack); + +private: + int _stackPointer; + clusterStack * _stacks[STACK_SIZE]; + +signals: + +}; + +#endif // RRDSTACK_H diff --git a/hw/ADCCluster/singlestack.cpp b/hw/ADCCluster/singlestack.cpp new file mode 100644 index 0000000..9a33e51 --- /dev/null +++ b/hw/ADCCluster/singlestack.cpp @@ -0,0 +1,111 @@ +#include "singlestack.h" + +singleStack::singleStack(uint32_t firstPacketIndex, ADC_ns::bitDepth bitDepth, QList * channelsConfig, int channelOffset, QObject *parent) + : QObject{parent}, + _bitDepth(bitDepth), + _channelsCount(0), + _tachoOffset(0), + _bytesInSample(2), + _firstPacketIndex(firstPacketIndex) +{ + for (int __i = 0; __i < 45; __i++) { + _packets[__i] = QByteArray(); + } + + double __baseFactor = 0; + + if (bitDepth == ADC_ns::bitDepth::BIT16) { + __baseFactor = (double)12500/32768; + _bytesInSample = 2; + } else if (bitDepth == ADC_ns::bitDepth::BIT24) { + __baseFactor = (double)12500/8388608; + _bytesInSample = 3; + } + + for (int i=0; icount(); i++) { + _channelFactors[i] = __baseFactor * channelsConfig->at(i)->getFactor() / 1000; // mV -> V + } + + _tachoOffset = _bytesInSample * channelOffset * channelsConfig->count() + DATA_OFFSET; + _channelsCount = channelsConfig->count(); + +} + +void singleStack::insert(int index, QByteArray packet) +{ + _packets[index] = packet; +} + +double singleStack::getValue(int packet, uint8_t channel, int sampleNo) +{ + trans_ns::bytes2word __point; + trans_ns::bytes2long __longPoint; + + int __pointOffset = (channel + _channelsCount * sampleNo) * _bytesInSample + DATA_OFFSET; + + if (_packets[packet].size() < __pointOffset + _bytesInSample) { + return (double)0; + } else { + if (_bytesInSample == 2) { + __point.bytes.L = _packets[packet][__pointOffset]; + __point.bytes.H = _packets[packet][__pointOffset + 1]; + return (double)((int16_t)__point.word * _channelFactors[channel]); + } else if (_bytesInSample == 3) { + __longPoint.bytes.LL = _packets[packet][__pointOffset]; + __longPoint.bytes.LH = _packets[packet][__pointOffset + 1]; + __longPoint.bytes.HL = _packets[packet][__pointOffset + 2]; + if (__longPoint.bytes.HL > 127) { + __longPoint.bytes.HH = 0xFF; + } else { + __longPoint.bytes.HH = 0x00; + } + + return (double)((int32_t)__longPoint.twinWord * _channelFactors[channel]); + + } else return (double)0; + } +} + +bool singleStack::getTachoValue(int packet, int point) +{ + trans_ns::byte2bits __byte; + int __byteOffset = _tachoOffset + (int)(point / 8); + int __bitOffset = point % 8; + + if (_packets[packet].size() < __byteOffset + 1) { + return false; + } else { + __byte.byte = _packets[packet][__byteOffset]; + + switch (__bitOffset) { + case 0: + return __byte.bits.a0 == 1; + break; + case 1: + return __byte.bits.a1 == 1; + break; + case 2: + return __byte.bits.a2 == 1; + break; + case 3: + return __byte.bits.a3 == 1; + break; + case 4: + return __byte.bits.a4 == 1; + break; + case 5: + return __byte.bits.a5 == 1; + break; + case 6: + return __byte.bits.a6 == 1; + break; + case 7: + return __byte.bits.a7 == 1; + break; + + default: return false; + } + } +} + + diff --git a/hw/ADCCluster/singlestack.h b/hw/ADCCluster/singlestack.h new file mode 100644 index 0000000..c1b85ce --- /dev/null +++ b/hw/ADCCluster/singlestack.h @@ -0,0 +1,35 @@ +#ifndef SINGLESTACK_H +#define SINGLESTACK_H + +#include +#include "adcchannel.h" +#include "stpdefine.h" +#include + +#define DATA_OFFSET 4 // смещение из-за 4 байт спереди - номер пакета + +class singleStack : public QObject +{ + Q_OBJECT +public: + explicit singleStack(uint32_t firstPacketIndex, ADC_ns::bitDepth bitDepth, QList * channelsConfig, int channelOffset, QObject *parent = nullptr); + void insert(int index, QByteArray packet); + double getValue(int packet, uint8_t channel, int point); + bool getTachoValue(int packet, int point); + int getFirstPacketIndex() { return _firstPacketIndex; } + + +private: + QByteArray _packets[45]; + ADC_ns::bitDepth _bitDepth; + double _channelFactors[4]; + int _channelsCount; + int _tachoOffset; + uint8_t _bytesInSample; + uint32_t _firstPacketIndex; + +signals: + +}; + +#endif // SINGLESTACK_H diff --git a/hw/ADCCluster/stpclient.cpp b/hw/ADCCluster/stpclient.cpp new file mode 100644 index 0000000..f37ca50 --- /dev/null +++ b/hw/ADCCluster/stpclient.cpp @@ -0,0 +1,224 @@ +#include "stpclient.h" + +stpClient::stpClient(QHostAddress address, int port, QObject *parent) + : QObject{parent}, + _state(stpClient_ns::stpState::DISCONNECTED), + _pingState(stpClient_ns::pingState::IDLE), + _socket(new QUdpSocket()), + _address(address), + _port(port), + _pingTimer(new QTimer()), + _pingTimeoutCount(0), + _reconnectionTimer(new QTimer()) +{ + _reconnectionTimer->setInterval(STP_RECONNECTION_PERIOD); + + QObject::connect(_reconnectionTimer, &QTimer::timeout, [=]{ + connectToHost(); + }); + + QObject::connect(_socket, &QUdpSocket::readyRead, + this, [=](){ + + #ifdef STP_MANY_PACKETS_DEBUG + int __count = 0; + #endif + + while (_socket->hasPendingDatagrams()) { + #ifdef STP_MANY_PACKETS_DEBUG + __count++; + #endif + _processData(_socket->receiveDatagram()); + } + + #ifdef STP_MANY_PACKETS_DEBUG + if (__count > 44) qDebug() << "Received packets at once(!): " << __count; + #endif + }); + + _pingTimer->setTimerType(Qt::PreciseTimer); + _pingTimer->setInterval(STP_PING_INTERVAL_MS); + + QObject::connect(_pingTimer, &QTimer::timeout, [=]{ + if (_state == stpClient_ns::stpState::CONNECTED) { + _socket->writeDatagram(_pack(stpProtocol_ns::packetType::PING), _address, _port); + _pingState = stpClient_ns::pingState::PENDING; + QTimer::singleShot(STP_TIMEOUT_MS, [=]{ + if (_pingState == stpClient_ns::pingState::PENDING) { + #ifdef STP_DEBUG + qDebug() << QTime::currentTime().toString() << "ping timeout"; + #endif + + _pingTimeoutCount++; + + if (_pingTimeoutCount >= STP_TIMEOUT_COUNT) { + disconnectFromHost(); + emit STP_disconnected(); + } + } + }); + } + }); +} + +stpClient::~stpClient() +{ + delete _pingTimer; + delete _reconnectionTimer; + delete _socket; + #ifdef STP_DEBUG + qDebug() << "stpClient desctructor"; + #endif +} + +void stpClient::connectToHost() +{ + if ((_state == stpClient_ns::stpState::DISCONNECTED) || + (_state == stpClient_ns::stpState::CONNECTING)) { + _socket->writeDatagram(_pack(stpProtocol_ns::packetType::SYNCReq), _address, _port); + _state = stpClient_ns::stpState::CONNECTING; + _reconnectionTimer->start(); + } +} + +void stpClient::disconnectFromHost() +{ + if (_state != stpClient_ns::stpState::DISCONNECTED) { + _socket->writeDatagram(_pack(stpProtocol_ns::packetType::RESET), _address, _port); + _state = stpClient_ns::stpState::DISCONNECTED; + _pingState = stpClient_ns::pingState::IDLE; + _pingTimer->stop(); + } +} + +void stpClient::sendData(QByteArray *buff) +{ + if (_state == stpClient_ns::stpState::CONNECTED) { + _socket->writeDatagram(_pack(stpProtocol_ns::packetType::DATA, buff), _address, _port); + } +} + +QByteArray stpClient::_pack(stpProtocol_ns::packetType packetType, QByteArray *data) +{ + QByteArray __result; + + if ( (packetType == stpProtocol_ns::packetType::DATA) + && (data != nullptr) ) { + // data packet + trans_ns::bytes2word __size; + __size.word = data->size(); + + __result.resize(CONST_HEADER_SIZE); + __result[0] = packetType; + __result[1] = __size.bytes.L; + __result[2] = __size.bytes.H; + __result.append(*data); + } else { + // system packets + __result.resize(CONST_HEADER_SIZE); + __result[0] = packetType; + __result[1] = 0; + __result[2] = 0; + } + + return __result; +} + +QByteArray stpClient::packIdRequest() +{ + QByteArray __result; + + __result.resize(CONST_HEADER_SIZE + 1); + __result[0] = stpProtocol_ns::packetType::DATA; + __result[1] = 0x01; + __result[2] = 0x00; + __result[3] = (uint8_t)stpProtocol_ns::stpCommand::REQ_ID; + return __result; +} + +QByteArray stpClient::packIdSnRequest(uint16_t sn) +{ + trans_ns::bytes2word __sn; + + __sn.word = sn; + + QByteArray __result; + + __result.resize(CONST_HEADER_SIZE + 3); + __result[0] = stpProtocol_ns::packetType::DATA; + __result[1] = 0x03; + __result[2] = 0x00; + __result[3] = (uint8_t)stpProtocol_ns::stpCommand::REQ_ID_SN; + __result[4] = __sn.bytes.L; + __result[5] = __sn.bytes.H; + return __result; +} + +void stpClient::_processData(QNetworkDatagram dg) +{ + int __size = dg.data().size(); + + if (__size < CONST_HEADER_SIZE) return; + + QByteArray __header = dg.data().first(CONST_HEADER_SIZE); + QByteArray __data = dg.data().last(__size - CONST_HEADER_SIZE); + + trans_ns::bytes2word __declaredSize; + + __declaredSize.bytes.L = __header[1]; + __declaredSize.bytes.H = __header[2]; + + if (__data.size() != __declaredSize.word) { + #ifdef STP_DEBUG + qDebug() << dg.senderAddress().toString() << "badPacketSize (packetSize: " << __data.size() << "; declaredSize: " << __declaredSize.word << ")"; + #endif + return; + } + + switch (__header[0]) { + case stpProtocol_ns::packetType::PING: + if (_state == stpClient_ns::stpState::CONNECTED) { + _socket->writeDatagram(_pack(stpProtocol_ns::packetType::PONG), _address, _port); + } else { + _socket->writeDatagram(_pack(stpProtocol_ns::packetType::RESET), _address, _port); + } + break; + + case stpProtocol_ns::packetType::PONG: + if (_pingState == stpClient_ns::pingState::PENDING) { + _pingState = stpClient_ns::pingState::IDLE; + _pingTimeoutCount = 0; + } + break; + + case stpProtocol_ns::packetType::DATA: + emit STP_newDataPacketReceived(__data); + break; + + case stpProtocol_ns::packetType::RESET: + #ifdef STP_DEBUG + qDebug() << "stp conection reset from: " << dg.senderAddress().toString(); + #endif + _state = stpClient_ns::stpState::DISCONNECTED; + _pingState = stpClient_ns::pingState::IDLE; + _pingTimer->stop(); + emit STP_disconnected(); + break; + + case stpProtocol_ns::packetType::SYNCAck: + if (_state == stpClient_ns::stpState::CONNECTED) { + #ifdef STP_DEBUG + qDebug() << _address.toString() << "(!!!) came when client is already connected"; + #endif + } else { + _state = stpClient_ns::stpState::CONNECTED; + _pingTimer->start(); + emit STP_connected(); + } + break; + + case stpProtocol_ns::packetType::SYNCReq: + // клиент не реагирует + break; + } +} diff --git a/hw/ADCCluster/stpclient.h b/hw/ADCCluster/stpclient.h new file mode 100644 index 0000000..eff8f79 --- /dev/null +++ b/hw/ADCCluster/stpclient.h @@ -0,0 +1,55 @@ +#ifndef STPCLIENT_H +#define STPCLIENT_H + +#include +#include +#include +#include +#include + +#include "stpdefine.h" + +class stpClient : public QObject +{ + Q_OBJECT + + +public: + explicit stpClient(QHostAddress address, int port = DEFAULT_DEST_PORT, QObject *parent = nullptr); + ~stpClient(); + + void connectToHost(); + void disconnectFromHost(); + + void setAddress(QHostAddress address) { _address = address; } + + stpClient_ns::stpState getState() { return _state; } + + void sendData(QByteArray * buff); + + static QByteArray packIdRequest(); + static QByteArray packIdSnRequest(uint16_t sn); + +signals: + void STP_newDataPacketReceived(QByteArray data); + void STP_connected(); + void STP_disconnected(); + +private: + QByteArray _pack(stpProtocol_ns::packetType packetType, QByteArray * data = nullptr); + void _processData(QNetworkDatagram data); + //void _resetConnection(); + + stpClient_ns::stpState _state; + stpClient_ns::pingState _pingState; + QUdpSocket * _socket; + QHostAddress _address; + int _port; + QTimer * _pingTimer; + int _pingTimeoutCount; + QTimer * _reconnectionTimer; + //bool _isReadingDatagramms; + +}; + +#endif // STPCLIENT_H diff --git a/hw/ADCCluster/stpdefine.h b/hw/ADCCluster/stpdefine.h new file mode 100644 index 0000000..6c43c16 --- /dev/null +++ b/hw/ADCCluster/stpdefine.h @@ -0,0 +1,154 @@ +#ifndef STPDEFINE_H +#define STPDEFINE_H + +#include +#include + +#define STP_DEBUG +#define STP_MANY_PACKETS_DEBUG +#define ADC_DEBUG +#define CLUSTER_DEBUG + +#define CONST_HEADER_SIZE 3 +#define CONST_REGISTER_HEADER_SIZE 5 +#define STP_RECONNECTION_PERIOD 1000 +#define DEFAULT_DEST_PORT 7000 +#define STP_PING_INTERVAL_MS 1000 +#define STP_TIMEOUT_MS 400 +#define STP_TIMEOUT_COUNT 2 +#define QUEUE_TIMEOUT 400 +#define QUEUE_TIMEOUT_COUNT 2 +#define DISCOVER_PERIOD 1000 + +namespace stpProtocol_ns { + + enum packetType { + NOP = 0x00, + SYNCReq = 0x01, + SYNCAck = 0x02, + PING = 0x03, + PONG = 0x04, + RESET = 0x05, + DATA = 0x06 + }; + enum stpCommand { + REG_READ = 0x01, + REG_READ_ACK = 0x02, + REG_READ_ERROR = 0x03, + REG_WRITE = 0x11, + REG_WRITE_ACK = 0x12, + REG_WRITE_ERROR = 0x13, + REQ_ID_SN = 0xFD, + REQ_ID = 0xFE, + ID = 0xFF + }; +} + +namespace ADC_ns { + enum ADCState { + DISCOVER, + CONNECTING, + CONNECTED, + CONFIGURATION_READING, + WAITING_COMMAND, + ADC_START_SEND, + ADC_STOP_SEND, + INIT_FAILED, + DISCONNECTED, + RUNNING, + }; + + enum channelMode { + AC = 0x00, + ACDC = 0x01, + }; + + enum bitDepth { + BIT16 = 0x0000, + BIT24 = 0x0001 + }; + enum DACMode { + SIN, + NOISE + }; + enum SYNCMode { + SLAVE, + MASTER + }; +} + +namespace ADCCluster_ns { + enum clusterState { + IDLE, + CONFIG, + RUNNING, + FAILED + }; +} + +namespace trans_ns { + struct couple { + uint8_t L; + uint8_t H; + }; + union bytes2word { + couple bytes; + uint16_t word; + }; + struct twinCouple { + uint8_t LL; + uint8_t LH; + uint8_t HL; + uint8_t HH; + }; + union bytes2long { + twinCouple bytes; + uint32_t twinWord; + }; + union bytes2float { + twinCouple bytes; + float floatValue; + }; + + union byte2bits { + uint8_t byte; + struct bit8map { + unsigned a0: 1; + unsigned a1: 1; + unsigned a2: 1; + unsigned a3: 1; + unsigned a4: 1; + unsigned a5: 1; + unsigned a6: 1; + unsigned a7: 1; + } bits; + }; + +} + +namespace stpClient_ns { + enum stpState { + DISCONNECTED, + CONNECTING, + CONNECTED + }; + enum pingState { + IDLE, + PENDING + }; +} + +namespace ADCQueue_ns { + enum queueState { + stopped, + waiting + }; + struct cmdItem { + stpProtocol_ns::stpCommand cmd; + QByteArray * regData; + uint16_t regNumber; + uint16_t id; + bool waiting; + }; +} +#endif // STPDEFINE_H diff --git a/hw/adlink.h b/hw/adlink.h new file mode 100644 index 0000000..2595cb9 --- /dev/null +++ b/hw/adlink.h @@ -0,0 +1,6 @@ +#ifndef ADLINK_H +#define ADLINK_H + +#define ADLINK_SN_FIXED { 0x4e, 0xdf, 0x0c, 0x76, 0x86, 0x07, 0x43, 0x9d, 0xa6, 0xb2, 0x19, 0xcb, 0x52, 0xb2, 0x7f, 0x96 } + +#endif // ADLINK_H \ No newline at end of file diff --git a/hw/d001/.gitignore b/hw/d001/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/hw/d001/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/hw/d001/d001.cpp b/hw/d001/d001.cpp new file mode 100644 index 0000000..9a59775 --- /dev/null +++ b/hw/d001/d001.cpp @@ -0,0 +1,13 @@ +#include "d001.h" + +d001::d001(QObject *parent) + : QObject(parent) +{ +} + +gtl::hw::device *d001::create_device(QObject *parent) +{ + return new device_d001(parent); +} + + diff --git a/hw/d001/d001.h b/hw/d001/d001.h new file mode 100644 index 0000000..777de84 --- /dev/null +++ b/hw/d001/d001.h @@ -0,0 +1,26 @@ +#ifndef D001_H +#define D001_H + +#include + +#include "../gtl_hardware_interface.h" +#include "device_d001.h" + +class d001 : public QObject, public gtl::hardware_interface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "gtlab.hardware" FILE "d001.json") + Q_INTERFACES(gtl::hardware_interface) + +public: + explicit d001(QObject *parent = nullptr); + + virtual QString id() const {return "d001";} + + virtual gtl::hw::device* create_device(QObject* parent = NULL); + +private: + +}; + +#endif // D001_H diff --git a/hw/d001/d001.json b/hw/d001/d001.json new file mode 100644 index 0000000..1e81138 --- /dev/null +++ b/hw/d001/d001.json @@ -0,0 +1,3 @@ +{ + "Keys" : [ ] +} diff --git a/hw/d001/d001.pro b/hw/d001/d001.pro new file mode 100644 index 0000000..58bda75 --- /dev/null +++ b/hw/d001/d001.pro @@ -0,0 +1,53 @@ +QT += gui xml + +TEMPLATE = lib +CONFIG += plugin + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + device_d001.cpp \ + d001.cpp + + +HEADERS += \ + device_d001.h \ + d001.h + + +TRANSLATIONS += \ + d001_ru_RU.ts + +DISTFILES += d001.json + +win32:CONFIG(release, debug|release): DESTDIR = ../../.output/.hwplugins/release +else:win32:CONFIG(debug, debug|release): DESTDIR = ../../.output/.hwplugins/debug +else:unix: DESTDIR = ../../.ouput/.hwplugins + +# Default rules for deployment. +unix { + target.path = $$[QT_INSTALL_PLUGINS]/generic +} +!isEmpty(target.path): INSTALLS += target + +unix|win32: LIBS += -LC:/ADLINK/UDASK/Lib64/ -lUSB-Dask64 + +INCLUDEPATH += C:/ADLINK/UDASK/Include +DEPENDPATH += C:/ADLINK/UDASK/Include + +INCLUDEPATH += $$PWD/../.. +DEPENDPATH += $$PWD/../.. + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_hw +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_hw +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_hw + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_core +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_core + + diff --git a/hw/d001/d001_ru_RU.ts b/hw/d001/d001_ru_RU.ts new file mode 100644 index 0000000..743f57f --- /dev/null +++ b/hw/d001/d001_ru_RU.ts @@ -0,0 +1,3 @@ + + + diff --git a/hw/d001/device_d001.cpp b/hw/d001/device_d001.cpp new file mode 100644 index 0000000..827b2d4 --- /dev/null +++ b/hw/d001/device_d001.cpp @@ -0,0 +1,470 @@ +#include "device_d001.h" + +#include "../adlink.h" + +device_d001::device_d001(QObject *parent) : gtl::hw::device(parent), _is_full_restarting(true) +{ + _count_ai = 4; + set_ai(); + + _device->set_name("d001"); +} + +device_d001::~device_d001() +{ + stop(); +} + +QString device_d001::type() const +{ + return "d001"; +} + + +bool device_d001::start(QString id, qreal rate) +{ + + emit status_changed(INIT_IN_PROGRESS); + + _ids.clear(); + _modules.clear(); + + _id = id; + + std::vector ids; + QStringList str_ids = id.split(','); + for(int i = 0; i < str_ids.size(); i++) + { + bool is_ok; + int int_id = str_ids[i].toInt(&is_ok); + if(is_ok) + ids.push_back(int_id); + } + + _ids_current.clear(); + std::copy(ids.begin(), ids.end(), std::back_inserter(_ids_current)); + + U8 sn_test[] = ADLINK_SN_FIXED; + + for (int i = 0; i < ids.size(); i++) + { + I16 err = UD_Register_Card(USB_2405, ids[i]); + + _ids.insert(std::pair(i, err)); + + if (err >= 0 && err < MAX_USB_DEVICE) + { + U8 sn[16]; + UD_Serial_Number_Read(err, sn); + + if (memcmp(sn_test, sn, sizeof(sn)) == 0) + _modules.push_back(err); + else + err = MAX_USB_DEVICE + 1; + } + } + + ULONG access_cnt; + I16 err; + + for (std::map::iterator iter_ids = _ids.begin(); iter_ids != _ids.end(); iter_ids++) + { + if (iter_ids->second < 0 || iter_ids->second >= MAX_USB_DEVICE) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(iter_ids->first + 1), tr("Error starting module")); + } + //emit error("init hardware #" + QString::number(iter_ids->first + 1), "UD_Register_Card error. code: " + QString::number(iter_ids->second)); + } + + _buffer.resize(_modules.size()); + _buffer_read.resize(_modules.size()); + _count_ai = (int)_modules.size()*4; + + _rate = rate; + + set_ai(); + + QJsonArray array_config = _config.toArray(); + for(int i = 0; i < qMin(array_config.size(), count_ai()); i++) + { + ai(i)->blockSignals(true); + + ai(i)->set_iepe(array_config[i].toObject().value("iepe").toBool()); + ai(i)->set_coupling(array_config[i].toObject().value("ac").toBool()); + + ai(i)->blockSignals(false); + } + + + + if (_is_full_restarting) + emit channels_changed(); + + if (_modules.empty()) + return false; + + + for (int i = 0; i < _modules.size(); i++) + { + err = UD_AI_AsyncClear(_modules[i], &access_cnt); + err = UD_GPTC_Control(_modules[i], 0, IntENABLE, 0); + } + + for (int i = 0; i < _modules.size(); i++) + { + + err = UD_AI_2405_Chan_Config(_modules[i], get_channel_config(i * 4 + 0), get_channel_config(i * 4 + 1), get_channel_config(i * 4 + 2), get_channel_config(i * 4 + 3)); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); + //emit error("start acquisition", "UD_AI_2405_Chan_Config error. code: " + QString::number(err)); + break; + } + + err = UD_AI_AsyncDblBufferMode(_modules[i], 1); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); + //emit error("start acquisition", "UD_AI_AsyncDblBufferMode error. code: " + QString::number(err)); + break; + } + + + U16 trig_ctrl; + U16 conv_src; + + if (_modules.size() == 1) + { + conv_src = P2405_AI_CONVSRC_INT; + trig_ctrl = P2405_AI_TRGSRC_SOFT; + } + else + { + conv_src = P2405_AI_CONVSRC_EXT; + + if (i == _modules.size() - 1) + { + err = UD_DIO_2405_Config(_modules[i], P2405_PULSE_OUTPUT, P2405_DIGITAL_INPUT); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_DIO_2405_Config error. code: " + QString::number(err)); + break; + } + + err = UD_GPTC_Clear(_modules[i], 0); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_GPTC_Clear error. code: " + QString::number(err)); + break; + } + + + err = UD_GPTC_Setup(_modules[i], 0, ContGatedPulseGenPWM, 0, GPTC_OUTPUT_HACTIVE, qRound(40e+6 / rate), 0, 0); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_GPTC_Setup error. code: " + QString::number(err)); + break; + } + + //err = UD_GPTC_Control(_modules[i], 0, IntENABLE, 0); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_GPTC_Control error. code: " + QString::number(err)); + break; + } + + trig_ctrl = P2405_AI_TRGSRC_SOFT; + } + else + { + err = UD_DIO_2405_Config(_modules[i], P2405_DIGITAL_INPUT, P2405_DIGITAL_INPUT); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_DIO_2405_Config error. code: " + QString::number(err)); + break; + } + + trig_ctrl = P2405_AI_TRGSRC_DTRIG; + } + } + + + + + + + err = UD_AI_2405_Trig_Config(_modules[i], conv_src, P2405_AI_TRGMOD_POST, trig_ctrl, 0, 0, 0, 0); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); + //emit error("start acquisition", "UD_AI_2405_Trig_Config error. code: " + QString::number(err)); + break; + } + + int samples = rate/10; + samples = qRound(samples / 128.0) * 128; + + U32 size_available = 0; + err = UD_AI_InitialMemoryAllocated(_modules[i], &size_available); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); +// emit error("start acquisition", "UD_AI_InitialMemoryAllocated error. code: " + QString::number(err)); + break; + } + + int factor_size = 2; + + size_available *= 1024; + int size = samples * sizeof(I32) * 4 * factor_size; + + if (size > size_available) + { + samples = size_available / sizeof(I32) / factor_size / channels(); + + samples = qRound(samples / 128.0) * 128; + + if (samples * sizeof(I32)*4 > size_available) + samples--; + } + + _buffer[i].resize(samples * 4 * factor_size); + + + F64 rate_actual = 0; + UD_AI_DDS_ActualRate_Get(_modules[i], rate, &rate_actual); + + + //emit set_timing(_settings->rate(), samples); + + U16 gains[] = { AD_B_10_V, AD_B_10_V, AD_B_10_V, AD_B_10_V }; + + U16 ch[4] = { 0, 1, 2, 3 }; + + + err = UD_AI_ContReadMultiChannels(_modules[i], 4, ch, gains, (U16*)&_buffer[i][0], (_buffer[i].size() * 1), _rate, ASYNCH_OP); + + + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); + //emit error("start acquisition", "UD_AI_ContReadMultiChannels error. code: " + QString::number(err)); + break; + } + + if (i == _modules.size() - 1 && _modules.size() != 1) + { + err = UD_GPTC_Control(_modules[i], 0, IntENABLE, 1); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d001 ") + " #" + QString::number(i + 1), tr("Error starting module")); +// emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_GPTC_Control error. code: " + QString::number(err)); + break; + } + } + + } + + _device->set_name(QString("d001") + "[" + id + "]"); + _device->set_id(_id); + _device->set_rate(rate); + + QThread::start(QThread::HighestPriority); + + emit status_changed(OK); + + return true; +} + + +bool device_d001::stop() +{ + if (isRunning()) + { + ULONG access_cnt; + I16 err; + + bool res = true; + + for (int i = 0; i < _modules.size(); i++) + { + err = UD_AI_AsyncClear(_modules[i], &access_cnt); + + res &= err == NoError_; + } + + wait(); +// return res; + } + + ULONG access_cnt; + I16 err; + + for (int i = 0; i < _modules.size(); i++) + { + err = UD_AI_AsyncClear(_modules[i], &access_cnt); + //err = UD_GPTC_Control(_modules[i], 0, IntENABLE, 0); + err = UD_GPTC_Clear(_modules[i], 0); + err = UD_DIO_2405_Config(_modules[i], P2405_DIGITAL_INPUT, P2405_DIGITAL_INPUT); + err = UD_Release_Card(_modules[i]); + } + + return true; +} + +void device_d001::set_config(const QJsonValue &config) +{ + gtl::hw::device::set_config(config); + + if(isRunning()) + restart(); +} + + +void device_d001::run() +{ + + qDebug() << "device started"; + + + BOOLEAN HalfReady; + BOOLEAN fstop; + I16 err; + + + + while (true) + { + + bool is_stop = true; + + for (int i = 0; i < _modules.size(); i++) + { + _buffer_read[i].resize(_buffer[i].size() / 2); + + do + { + err = UD_AI_AsyncDblBufferHalfReady(_modules[i], &HalfReady, &fstop); + + if (err != NoError_) + { + emit status_changed(FAILED); + QString title_err = "acquisition. module: " + QString::number(_modules[i]); + QString msg_err = QString("UD_AI_AsyncDblBufferHalfReady error. code: ") + QString::number(err); + emit error(title_err, msg_err); + qDebug() << title_err << msg_err; + return; + } + + + err = UD_AI_AsyncDblBufferTransfer32(_modules[i], &_buffer_read[i][0]); + + if (err != NoError_) + { + emit status_changed(FAILED); + QString title_err = "acquisition. module: " + QString::number(_modules[i]); + QString msg_err = QString("UD_AI_AsyncDblBufferTransfer32 error. code: ") + QString::number(err); + emit error(title_err, msg_err); + qDebug() << title_err << msg_err; + break; + } + + + } while (!HalfReady && !fstop); + +// qDebug() << "read half buffer"; + + is_stop &= (bool)fstop; + } + + if (is_stop) + break; + + send_data(); + + } + + emit status_changed(IDLE); +} + +void device_d001::send_data() +{ + + _buffer_send.clear(); + + int samples = _buffer_read[0].size() / 4; + + for (int i = 0; i < samples; i++) + { + for (int j = 0; j < _buffer_read.size(); j++) + { + for (int k = 0; k < 4; k++) + { + F64 voltage; + UD_AI_VoltScale32(_modules[j], AD_B_10_V, 0, _buffer_read[j][i*4 + k], &voltage); + + if (voltage > 10) + voltage = 10; + else if (voltage < -10) + voltage = -10; + + _buffer_send.push_back(voltage); + } + } + } + + set_ai_data(&_buffer_send[0], (int)_buffer_send.size()); + + +// qDebug() << "send data"; + + emit received_data(); +} + +U16 device_d001::get_channel_config(int channel) +{ + U16 iepe = (ai(channel)->is_iepe() ? P2405_AI_EnableIEPE : P2405_AI_DisableIEPE); + U16 coupling = (ai(channel)->is_iepe() ? P2405_AI_Coupling_AC : (ai(channel)->is_coupling() ? P2405_AI_Coupling_AC : P2405_AI_Coupling_None)); + + return iepe | coupling | P2405_AI_Differential; +} + +void device_d001::ai_iepe_changed() +{ + restart(); +} + +void device_d001::ai_coupling_changed() +{ + restart(); +} + + diff --git a/hw/d001/device_d001.h b/hw/d001/device_d001.h new file mode 100644 index 0000000..0353853 --- /dev/null +++ b/hw/d001/device_d001.h @@ -0,0 +1,57 @@ +#ifndef DEVICE_D001_H +#define DEVICE_D001_H + + +#include +#include + +#include "hw/gtl_hw_device.h" + +#include "usbdask64.h" + +class device_d001 : public gtl::hw::device +{ + Q_OBJECT + +public: + device_d001(QObject *parent); + ~device_d001(); + + virtual QString type() const override; + + virtual bool start(QString id, qreal rate); + virtual bool stop(); + + virtual int channels() { return 4 * (int)_modules.size();} + + virtual qreal max_amplitude() const { return 10; } + + virtual void set_config(const QJsonValue& config) override; + +private: + + std::map _ids; + std::vector _modules; + std::vector> _buffer; + std::vector _buffer_send; + std::vector> _buffer_read; + + std::vector _ids_current; + + bool _is_full_restarting; + +private: + void run(); + + void send_data(); + + U16 get_channel_config(int channel); + + +private slots: + virtual void ai_iepe_changed(); + virtual void ai_coupling_changed(); + +}; + +#endif // ADLINK2405_H diff --git a/hw/d002/.gitignore b/hw/d002/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/hw/d002/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/hw/d002/d002.cpp b/hw/d002/d002.cpp new file mode 100644 index 0000000..85d6c0b --- /dev/null +++ b/hw/d002/d002.cpp @@ -0,0 +1,13 @@ +#include "d002.h" + +d002::d002(QObject *parent) + : QObject(parent) +{ +} + + + +gtl::hw::device *d002::create_device(QObject *parent) +{ + return new device_d002(parent); +} diff --git a/hw/d002/d002.h b/hw/d002/d002.h new file mode 100644 index 0000000..afb30c0 --- /dev/null +++ b/hw/d002/d002.h @@ -0,0 +1,25 @@ +#ifndef D002_H +#define D002_H + +#include +#include "../gtl_hardware_interface.h" +#include "device_d002.h" + +class d002 : public QObject, public gtl::hardware_interface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "gtlab.hardware" FILE "d002.json") + Q_INTERFACES(gtl::hardware_interface) + +public: + explicit d002(QObject *parent = nullptr); + + virtual QString id() const {return "d002";} + + virtual gtl::hw::device *create_device(QObject* parent = NULL); + +private: + +}; + +#endif // D002_H diff --git a/hw/d002/d002.json b/hw/d002/d002.json new file mode 100644 index 0000000..1e81138 --- /dev/null +++ b/hw/d002/d002.json @@ -0,0 +1,3 @@ +{ + "Keys" : [ ] +} diff --git a/hw/d002/d002.pro b/hw/d002/d002.pro new file mode 100644 index 0000000..c276713 --- /dev/null +++ b/hw/d002/d002.pro @@ -0,0 +1,51 @@ +QT += gui xml + +TEMPLATE = lib +CONFIG += plugin + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + d002.cpp \ + device_d002.cpp + +HEADERS += \ + d002.h \ + device_d002.h + +TRANSLATIONS += \ + d002_ru_RU.ts + +DISTFILES += d002.json + +win32:CONFIG(release, debug|release): DESTDIR = ../../.output/.hwplugins/release +else:win32:CONFIG(debug, debug|release): DESTDIR = ../../.output/.hwplugins/debug +else:unix: DESTDIR = ../../.ouput/.hwplugins + +# Default rules for deployment. +unix { + target.path = $$[QT_INSTALL_PLUGINS]/generic +} +!isEmpty(target.path): INSTALLS += target + +unix|win32: LIBS += -LC:/ADLINK/UDASK/Lib64/ -lUSB-Dask64 + +INCLUDEPATH += C:/ADLINK/UDASK/Include +DEPENDPATH += C:/ADLINK/UDASK/Include + +INCLUDEPATH += $$PWD/../.. +DEPENDPATH += $$PWD/../.. + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_hw +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_hw +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_hw + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_core +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_core + + diff --git a/hw/d002/d002_ru_RU.ts b/hw/d002/d002_ru_RU.ts new file mode 100644 index 0000000..743f57f --- /dev/null +++ b/hw/d002/d002_ru_RU.ts @@ -0,0 +1,3 @@ + + + diff --git a/hw/d002/device_d002.cpp b/hw/d002/device_d002.cpp new file mode 100644 index 0000000..1694a7e --- /dev/null +++ b/hw/d002/device_d002.cpp @@ -0,0 +1,418 @@ +#include "device_d002.h" + +#include "../adlink.h" + +device_d002::device_d002(QObject *parent) : gtl::hw::device(parent) +{ + _device->set_name("d002"); +} + +device_d002::~device_d002() +{ + stop(); +} + +QString device_d002::type() const +{ + return "d002"; +} + + +bool device_d002::start(QString id, qreal rate) +{ + + emit status_changed(INIT_IN_PROGRESS); + + _ids.clear(); + _modules.clear(); + + _id = id; + + bool is_ok; + int int_id = id.toInt(&is_ok); + + + I16 err = UD_Register_Card(USB_1210, int_id); + + if (err >= 0 && err < MAX_USB_DEVICE) + { + U8 sn_test[] = ADLINK_SN_FIXED; + U8 sn[16]; + UD_Serial_Number_Read(err, sn); + + if (memcmp(sn_test, sn, sizeof(sn)) == 0) + _modules.push_back(err); + else + err = MAX_USB_DEVICE + 1; + } + +// _ids.insert(std::pair(0, err)); + + ULONG access_cnt; + + for (std::map::iterator iter_ids = _ids.begin(); iter_ids != _ids.end(); iter_ids++) + { + if (iter_ids->second < 0 || iter_ids->second >= MAX_USB_DEVICE) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + } + //emit error("init hardware #" + QString::number(iter_ids->first + 1), "UD_Register_Card error. code: " + QString::number(iter_ids->second)); + } + + _buffer.resize(_modules.size()); + _buffer_read.resize(_modules.size()); + _count_ai = (int)_modules.size()*4; + + _rate = rate; + + set_ai(); + + emit channels_changed(); + + if (_modules.empty()) + return false; + + + for (int i = 0; i < _modules.size(); i++) + { + err = UD_AI_AsyncClear(_modules[i], &access_cnt); +// err = UD_GPTC_Control(_modules[i], 0, IntENABLE, 0); + } + + for (int i = 0; i < _modules.size(); i++) + { + + err = UD_AI_Channel_Config(_modules[i], get_channel_config(i * 4 + 0), get_channel_config(i * 4 + 1), get_channel_config(i * 4 + 2), get_channel_config(i * 4 + 3)); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition", "UD_AI_Channel_Config error. code: " + QString::number(err)); + break; + } + + err = UD_AI_AsyncDblBufferMode(_modules[i], 1); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition", "UD_AI_AsyncDblBufferMode error. code: " + QString::number(err)); + break; + } + + + U16 trig_ctrl; + U16 conv_src; + + if (_modules.size() == 1) + { + conv_src = UD_AI_CONVSRC_INT; + trig_ctrl = UD_AI_TRGSRC_SOFT; + } + else + { + conv_src = UD_AI_CONVSRC_EXT; + + if (i == _modules.size() - 1) + { + err = UD_DIO_Config(_modules[i], UD_DIO_PULSE_OUTPUT, UD_DIO_DIGITAL_INPUT); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_DIO_Config error. code: " + QString::number(err)); + break; + } + + err = UD_GPTC_Clear(_modules[i], 0); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_GPTC_Clear error. code: " + QString::number(err)); + break; + } + + err = UD_GPTC_Setup(_modules[i], 0, ContGatedPulseGenPWM, 0, GPTC_OUTPUT_HACTIVE, qRound(40e+6 / rate), 0, 0); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_GPTC_Setup error. code: " + QString::number(err)); + break; + } + + //err = UD_GPTC_Control(_modules[i], 0, IntENABLE, 0); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_GPTC_Control error. code: " + QString::number(err)); + break; + } + + trig_ctrl = UD_AI_TRGSRC_SOFT; + } + else + { + err = UD_DIO_Config(_modules[i], UD_DIO_DIGITAL_INPUT, UD_DIO_DIGITAL_INPUT); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_DIO_Config error. code: " + QString::number(err)); + break; + } + + trig_ctrl = UD_AI_TRGSRC_DTRIG; + } + } + + + + + + + err = UD_AI_Trigger_Config(_modules[i], conv_src, UD_AI_TRGMOD_POST, trig_ctrl, 0, 0, 0, 0); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition", "UD_AI_Trigger_Config error. code: " + QString::number(err)); + break; + } + + int samples = rate/10/*50*/; + samples = qRound(samples / 128.0) * 128; + + U32 size_available = 0; + err = UD_AI_InitialMemoryAllocated(_modules[i], &size_available); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition", "UD_AI_InitialMemoryAllocated error. code: " + QString::number(err)); + break; + } + + int factor_size = 2; + + size_available *= 1024; + int size = samples * sizeof(I16) * 4 * factor_size; + + if (size > size_available) + { + samples = size_available / sizeof(I16) / factor_size / channels(); + + samples = qRound(samples / 128.0) * 128; + + if (samples * sizeof(I16)*4 > size_available) + samples--; + } + + _buffer[i].resize(samples * 4 * factor_size); + + + F64 rate_actual = 0; + UD_AI_DDS_ActualRate_Get(_modules[i], rate, &rate_actual); + + //emit set_timing(_settings->rate(), samples); + + U16 gains[] = { AD_B_10_V, AD_B_10_V, AD_B_10_V, AD_B_10_V }; + + U16 ch[4] = { 0, 1, 2, 3 }; + + + + err = UD_AI_ContReadMultiChannels(_modules[i], 4, ch, gains, (U16*)&_buffer[i][0], (_buffer[i].size() * 1), _rate, ASYNCH_OP); + + + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition", "UD_AI_ContReadMultiChannels error. code: " + QString::number(err)); + break; + } + + if (i == _modules.size() - 1 && _modules.size() != 1) + { + err = UD_GPTC_Control(_modules[i], 0, IntENABLE, 1); + + if (err != NoError_) + { + emit status_changed(INIT_FAILED); + emit error(tr("Init") + QString("d002 "), tr("Error starting module")); + //emit error("start acquisition. module: " + QString::number(_modules[i]), "UD_GPTC_Control error. code: " + QString::number(err)); + break; + } + } + + } + + + + _device->set_name(QString("d002") + "[" + id + "]"); + _device->set_id(_id); + _device->set_rate(rate); + + QThread::start(QThread::HighestPriority); + + emit status_changed(OK); + + return true; +} + + +bool device_d002::stop() +{ + if (isRunning()) + { + ULONG access_cnt; + I16 err; + + bool res = true; + + for (int i = 0; i < _modules.size(); i++) + { + err = UD_AI_AsyncClear(_modules[i], &access_cnt); + + res &= err == NoError_; + } + + wait(); +// return res; + } + + + + + ULONG access_cnt; + I16 err; + + for (int i = 0; i < _modules.size(); i++) + { + err = UD_AI_AsyncClear(_modules[i], &access_cnt); + //err = UD_GPTC_Control(_modules[i], 0, IntENABLE, 0); + err = UD_GPTC_Clear(_modules[i], 0); + err = UD_DIO_Config(_modules[i], UD_DIO_DIGITAL_INPUT, UD_DIO_DIGITAL_INPUT); + err = UD_Release_Card(_modules[i]); + } + + return true; +} + + + + +void device_d002::run() +{ + + + BOOLEAN HalfReady; + BOOLEAN fstop; + I16 err; + + + + while (true) + { + + bool is_stop = true; + + for (int i = 0; i < _modules.size(); i++) + { + _buffer_read[i].resize(_buffer[i].size() / 2); + + do + { + err = UD_AI_AsyncDblBufferHalfReady(_modules[i], &HalfReady, &fstop); + + if (err != NoError_) + { + emit status_changed(FAILED); + emit error("acquisition. module: " + QString::number(_modules[i]), "UD_AI_AsyncDblBufferHalfReady error. code: " + QString::number(err)); + return; + } + + + err = UD_AI_AsyncDblBufferTransfer(_modules[i], &_buffer_read[i][0]); + + if (err != NoError_) + { + emit status_changed(FAILED); + emit error("acquisition. module: " + QString::number(_modules[i]), "UD_AI_AsyncDblBufferTransfer32 error. code: " + QString::number(err)); + break; + } + + } while (!HalfReady && !fstop); + +// qDebug() << "read half buffer"; + + is_stop &= (bool)fstop; + } + + if (is_stop) + break; + + send_data(); + + } + + emit status_changed(IDLE); +} + +void device_d002::send_data() +{ + + _buffer_send.clear(); + + + int samples = _buffer_read[0].size() / 4; + + + + for (int i = 0; i < samples; i++) + { + for (int j = 0; j < _buffer_read.size(); j++) + { + for (int k = 0; k < 4; k++) + { + F64 voltage; + UD_AI_VoltScale(_modules[j], AD_B_10_V, _buffer_read[j][i*4 + k], &voltage); +// UD_AI_VoltScale32(_modules[j], AD_B_10_V, 0, _buffer_read[j][i*4 + k], &voltage); + + if (voltage > 10) + voltage = 10; + else if (voltage < -10) + voltage = -10; + + _buffer_send.push_back(voltage); + } + } + } + + set_ai_data(&_buffer_send[0], (int)_buffer_send.size()); + + +// qDebug() << "send data"; + + emit received_data(); +} + +U16 device_d002::get_channel_config(int channel) const +{ + return UD_AI_Differential; +} + diff --git a/hw/d002/device_d002.h b/hw/d002/device_d002.h new file mode 100644 index 0000000..cc92d7f --- /dev/null +++ b/hw/d002/device_d002.h @@ -0,0 +1,44 @@ +#ifndef DEVICE_D002_H +#define DEVICE_D002_H + + +#include +#include + +#include "usbdask64.h" + +#include "hw/gtl_hw_device.h" + +class device_d002 : public gtl::hw::device +{ + Q_OBJECT + +public: + device_d002(QObject *parent); + ~device_d002(); + + virtual QString type() const override; + + virtual bool start(QString id, qreal rate); + virtual bool stop(); + + virtual int channels() { return 4 * (int)_modules.size(); } + virtual qreal max_amplitude() const { return 10; } + +private: + std::map _ids; + std::vector _modules; + std::vector> _buffer; + std::vector _buffer_send; + std::vector> _buffer_read; + +private: + void run(); + + void send_data(); + + U16 get_channel_config(int channel) const; + +}; + +#endif // ADLINK2405_H diff --git a/hw/gtl_analog_input.cpp b/hw/gtl_analog_input.cpp new file mode 100644 index 0000000..8fc26fa --- /dev/null +++ b/hw/gtl_analog_input.cpp @@ -0,0 +1,168 @@ +#include "gtl_analog_input.h" + +namespace gtl +{ + + namespace hw + { + + analog_input::analog_input(qreal rate, QString name, gtl::device *parent) + : gtl::analog_data(parent), + _is_iepe(false), + _is_coupling(false), + _sensitivity(1), + _gain(1), + _offset(0), + _is_inverting(false) + { + _name = name; + _rate = rate; + _reference = 1; + } + + void analog_input::set_data(qreal *data, int size, int offset, int step) + { + clear(); + + for(int i = offset; i < size; i+= step) + push_back((_is_inverting ? -1 : 1)*data[i]/_sensitivity/_gain - _offset); + + gtl::analog_data::set_data(this->begin(), this->end()); + +/* + emit data_changed(); + + for(std::vector::iterator iter_node = _children.begin(); iter_node != _children.end(); iter_node++) + { + if((*iter_node)->type() != data_model_node::analog) + continue; + + static_cast(*iter_node)->set_data(this->begin(), this->end()); + } +*/ + } + + void analog_input::set_iepe(bool value) + { + if(_is_iepe != value) + { + _is_iepe = value; + + emit iepe_changed(); + } + } + + bool analog_input::is_iepe() const + { + return _is_iepe; + } + + void analog_input::set_coupling(bool value) + { + if(_is_coupling != value) + { + _is_coupling = value; + + emit coupling_changed(); + } + } + + bool analog_input::is_coupling() const + { + return _is_coupling; + } + + void analog_input::set_sensitivity(qreal value) + { + _sensitivity = value; + } + + qreal analog_input::sensitivity() const + { + return _sensitivity; + } + + void analog_input::set_gain(qreal value) + { + _gain = value; + } + + qreal analog_input::gain() const + { + return _gain; + } + + void analog_input::set_offset(qreal value) + { + _offset = value; + } + + bool analog_input::offset() const + { + return _offset; + } + + void analog_input::set_inverting(bool value) + { + _is_inverting = value; + } + + bool analog_input::is_inverting() const + { + return _is_inverting; + } + + void analog_input::set_reference(qreal value) + { + if(value != _reference) + { + _reference = value; + emit reference_changed(); + } + } + + void analog_input::save(QDomElement &root_element) + { + root_element.setAttribute("sensitivity", _sensitivity); + root_element.setAttribute("gain", _gain); + root_element.setAttribute("offset", _offset); + root_element.setAttribute("reference", _reference); + root_element.setAttribute("is_inverting", _is_inverting); + root_element.setAttribute("is_couping", _is_coupling); + root_element.setAttribute("is_iepe", _is_iepe); + + gtl::analog_data::save(root_element); + } + + void analog_input::load(const QDomElement &root_element) + { + _sensitivity = root_element.attribute("sensitivity", "1").toDouble(); + _gain = root_element.attribute("gain", "1").toDouble(); + _offset = root_element.attribute("offset", "0").toDouble(); + _reference = root_element.attribute("reference", "1").toDouble(); + _is_inverting = root_element.attribute("is_inverting", "0").toInt(); + _is_coupling = root_element.attribute("is_couping", "0").toInt(); + _is_iepe = root_element.attribute("is_iepe", "0").toInt(); + + gtl::analog_data::load(root_element); + } + + void analog_input::get_state(QJsonObject &root) + { + root["sensitivity"] = _sensitivity; + root["gain"] = _gain; + root["offset"] = _offset; + root["reference"] = _reference; + root["is_inverting"] = _is_inverting; + root["is_couping"] = _is_coupling; + root["is_iepe"] = _is_iepe; + + gtl::analog_data::get_state(root); + } + + qreal analog_input::reference() const + { + return _reference; + } + } +} diff --git a/hw/gtl_analog_input.h b/hw/gtl_analog_input.h new file mode 100644 index 0000000..f55012f --- /dev/null +++ b/hw/gtl_analog_input.h @@ -0,0 +1,75 @@ +#ifndef ANALOG_INPUT_H +#define ANALOG_INPUT_H + +#include + +#include "core/gtl_analog_data.h" + +#include "hw_global.h" + +#include "core/gtl_device.h" + + +namespace gtl +{ + namespace hw + { + class HW_EXPORT analog_input : public gtl::analog_data + { + Q_OBJECT + Q_PROPERTY(qreal reference READ reference WRITE set_reference NOTIFY reference_changed) + public: + explicit analog_input(qreal rate, QString name, gtl::device *parent); + + void set_data(qreal* data, int size, int offset, int step); + + void set_iepe(bool value); + bool is_iepe() const; + + void set_coupling(bool value); + bool is_coupling() const; + + void set_sensitivity(qreal value); + qreal sensitivity() const; + + void set_gain(qreal value); + qreal gain() const; + + void set_offset(qreal value); + bool offset() const; + + void set_inverting(bool value); + bool is_inverting() const; + + void set_reference(qreal value); + + virtual void save(QDomElement& root_element) override; + virtual void load(const QDomElement& root_element) override; + virtual void get_state(QJsonObject& root) override; + + virtual qreal reference() const override; + + private: + + bool _is_iepe; + bool _is_coupling; + + qreal _sensitivity; + qreal _gain; + qreal _offset; + bool _is_inverting; + + qreal _reference; + + + signals: + void iepe_changed(); + void coupling_changed(); + + }; + + } + +} + +#endif // ANALOG_INPUT_H diff --git a/hw/gtl_hardware_interface.h b/hw/gtl_hardware_interface.h new file mode 100644 index 0000000..eb7943d --- /dev/null +++ b/hw/gtl_hardware_interface.h @@ -0,0 +1,27 @@ +#ifndef GTL_HARDWARE_INTERFACE_H +#define GTL_HARDWARE_INTERFACE_H + +#include + +#include "hw/gtl_hw_device.h" + +namespace gtl +{ + +class hardware_interface +{ +public: + virtual ~hardware_interface() {}; + + virtual QString id() const = 0; + + virtual gtl::hw::device* create_device(QObject* parent = NULL) = 0; +}; + +} + +#define idd_device_interface "gtlab.hardware_interface/0.0" +Q_DECLARE_INTERFACE(gtl::hardware_interface, idd_device_interface) + + +#endif // GTL_HARDWARE_INTERFACE_H diff --git a/hw/gtl_hw.cpp b/hw/gtl_hw.cpp new file mode 100644 index 0000000..34801a1 --- /dev/null +++ b/hw/gtl_hw.cpp @@ -0,0 +1,46 @@ +#include "gtl_hw.h" + +namespace gtl +{ + namespace hw + { + hw::hw(QString path, QObject* parent) : QObject(parent) + { + QDir dir(path); + + qDebug() << "loading hardware plugins. path:" << dir.absolutePath(); + + QStringList plugins = dir.entryList(QStringList() << "*.dll", QDir::Files); + + for (int i = 0; i < plugins.size(); i++) + { + QPluginLoader loader(dir.absoluteFilePath(plugins[i])); + QObject* obj = loader.instance(); + if (obj) + { + if(loader.metaData().value("IID").toString() == "gtlab.hardware") + _instances.insert(qobject_cast(obj)->id(), obj); + else + loader.unload(); + } + + } + + + } + + QStringList hw::devices() const + { + return _instances.keys(); + } + + device *hw::create_device(QString device, QObject* parent) + { + QMultiMap::iterator iter_device = _instances.find(device); + if(iter_device == _instances.end()) + return NULL; + + return qobject_cast(iter_device.value())->create_device(parent); + } + } +} diff --git a/hw/gtl_hw.h b/hw/gtl_hw.h new file mode 100644 index 0000000..0410f9e --- /dev/null +++ b/hw/gtl_hw.h @@ -0,0 +1,32 @@ +#ifndef GTL_HW_H +#define GTL_HW_H + +#include +#include +#include +#include + +#include "hw_global.h" + +#include "gtl_hardware_interface.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT hw : public QObject + { + public: + hw(QString path, QObject* parent = NULL); + + QStringList devices() const; + device* create_device(QString device, QObject* parent = NULL); + + private: + QMultiMap _instances; + + }; + } +} + +#endif // GTL_HW_H diff --git a/hw/gtl_hw_audio.cpp b/hw/gtl_hw_audio.cpp new file mode 100644 index 0000000..be0b338 --- /dev/null +++ b/hw/gtl_hw_audio.cpp @@ -0,0 +1,141 @@ +#include "gtl_hw_audio.h" + +namespace gtl +{ + namespace hw + { + audio::audio(int channels, QAudioFormat::SampleFormat sample_format, QObject *parent) + : device{parent} + , _sample_format(sample_format) + , _audio_input(NULL) + { + _count_ai = channels; + + _audio_device = new audio_input_device(this); + _audio_device->open(QIODevice::WriteOnly); + + } + + audio::~audio() + { + stop(); + } + + QString audio::type() const + { + return "audio"; + } + + bool audio::start(QString id, qreal rate) + { + _rate = rate; + _id = id; + + const auto audio_inputs = QMediaDevices::audioInputs(); + + auto iter = std::find_if(audio_inputs.begin(), audio_inputs.end(), [=](const QAudioDevice &device){return QString(device.id()) == id;}); + + QString name; + + if(iter == audio_inputs.end()) + name = tr("unknown device"); + else + { + name = iter->description(); + + + _format.setSampleRate(_rate); + _format.setChannelCount(_count_ai); + _format.setSampleFormat(_sample_format); + + if(_audio_input != NULL) + delete _audio_input; + + _audio_input = new QAudioSource(_format); + + } + + + set_ai(); + + _device->set_name(tr("audio") + "[" + name + "]"); + _device->set_id(id); + _device->set_rate(rate); + + _is_continue = true; + + + +// _buffer->seek(0); + _audio_device->set_buffer(_rate, _format.bytesPerFrame()); + _audio_input->start(_audio_device); + + QThread::start(QThread::HighestPriority); + + emit status_changed(OK); + + return true; + } + + bool audio::stop() + { + _is_continue = false; + wait(); + if(_audio_input) + { + delete _audio_input; + _audio_input = NULL; + } + + + return true; + } + + void audio::run() + { + std::vector buffer; + std::vector buffer_recv; + int sample_bytes = _format.bytesPerSample(); + + while(_is_continue) + { + buffer.clear(); + _audio_device->get_data(std::back_inserter(buffer)); + + + if(!buffer.empty()) + { + buffer_recv.clear(); + for(int i = 0; i < buffer.size(); i += sample_bytes) + { + float value = _format.normalizedSampleValue(&buffer[i]); + buffer_recv.push_back(value); + } + + set_ai_data(&buffer_recv[0], (int)buffer_recv.size()); + + emit received_data(); + +// _buffer->seek(0); + +// qDebug() << "recieved data"; + } + + msleep(100); + + } + } + + void audio::save(QDomElement &root_element) + { + device::save(root_element); + root_element.setAttribute("sample_format", _sample_format); + } + + void audio::load(const QDomElement &root_element) + { + device::load(root_element); + _sample_format = (QAudioFormat::SampleFormat)root_element.attribute("sample_format", QString::number(_sample_format)).toInt(); + } + } +} diff --git a/hw/gtl_hw_audio.h b/hw/gtl_hw_audio.h new file mode 100644 index 0000000..1dce057 --- /dev/null +++ b/hw/gtl_hw_audio.h @@ -0,0 +1,48 @@ +#ifndef AUDIO_H +#define AUDIO_H + +#include +#include +#include + +#include "hw/gtl_hw_device.h" +#include "hw/gtl_hw_generator_analog_input.h" + +#include "hw/gtl_hw_audio_input_device.h" + +#include "hw_global.h" + +namespace gtl +{ + namespace hw + { + class audio : public device + { + Q_OBJECT + public: + explicit HW_EXPORT audio(int channels = 0, QAudioFormat::SampleFormat sample_format = QAudioFormat::Unknown, QObject *parent = nullptr); + virtual ~audio(); + + virtual QString type() const override; + + virtual bool start(QString id, qreal rate) override; + virtual bool stop() override; + + private: + virtual void run() override; + + + private: + QAudioFormat::SampleFormat _sample_format; + QAudioSource* _audio_input; + QAudioFormat _format; + audio_input_device *_audio_device; + + protected slots: + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + }; + } +} + +#endif // AUDIO_H diff --git a/hw/gtl_hw_audio_input_device.cpp b/hw/gtl_hw_audio_input_device.cpp new file mode 100644 index 0000000..df3d286 --- /dev/null +++ b/hw/gtl_hw_audio_input_device.cpp @@ -0,0 +1,75 @@ +#include "gtl_hw_audio_input_device.h" + +#include + +namespace gtl +{ + namespace hw + { + audio_input_device::audio_input_device(QObject *parent) + : QIODevice{parent} + , _buffer(1000) + , _ptr_recv(0) + , _ptr_send(0) + , _frame_bytes(1) + { + + } + + void audio_input_device::set_buffer(qreal rate, int frame_bytes) + { + _buffer.resize(rate * frame_bytes); + _ptr_recv = 0; + _ptr_send = 0; + _frame_bytes = frame_bytes; + } + + void audio_input_device::get_data(std::back_insert_iterator> buffer) + { + int ptr_send = _ptr_send; + int cnt = 0; + while(ptr_send != _ptr_recv) + { + ptr_send = (ptr_send + 1) % _buffer.size(); + cnt++; + + if(cnt % _frame_bytes == 0) + { + auto begin = _buffer.begin() + _ptr_send; + auto end = ptr_send == 0 ? _buffer.end() : _buffer.begin() + ptr_send; + + std::copy(begin, end, buffer); + _ptr_send = ptr_send; + } + } + +// qDebug() << "prt_send: " << _ptr_send; + } + + qint64 audio_input_device::readData(char *data, qint64 maxSize) + { + Q_UNUSED(data); + Q_UNUSED(maxSize); + return -1; + } + + qint64 audio_input_device::writeData(const char *data, qint64 maxSize) + { + int cnt = 0; + while(cnt != maxSize) + { + int size = std::min(_buffer.size() - _ptr_recv, maxSize - cnt); + + std::copy(&data[cnt], &data[cnt + size], &_buffer[_ptr_recv]); + + _ptr_recv = (_ptr_recv + size) % _buffer.size(); + + cnt += size; + } + +// qDebug() << "prt_recv: " << _ptr_recv; + + return maxSize; + } + } +} diff --git a/hw/gtl_hw_audio_input_device.h b/hw/gtl_hw_audio_input_device.h new file mode 100644 index 0000000..b89a24b --- /dev/null +++ b/hw/gtl_hw_audio_input_device.h @@ -0,0 +1,34 @@ +#ifndef AUIDIO_INPUT_DEVICE_H +#define AUIDIO_INPUT_DEVICE_H + +#include + +#include "hw_global.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT audio_input_device : public QIODevice + { + Q_OBJECT + public: + explicit audio_input_device(QObject *parent = nullptr); + void set_buffer(qreal rate, int frame_bytes); + + void get_data(std::back_insert_iterator > buffer); + + protected: + qint64 readData(char *data, qint64 maxSize) override; + qint64 writeData(const char *data, qint64 maxSize) override; + + private: + std::vector _buffer; + int _ptr_recv; + int _ptr_send; + int _frame_bytes; + }; + } +} + +#endif // AUIDIO_INPUT_DEVICE_H diff --git a/hw/gtl_hw_device.cpp b/hw/gtl_hw_device.cpp new file mode 100644 index 0000000..adf63d7 --- /dev/null +++ b/hw/gtl_hw_device.cpp @@ -0,0 +1,170 @@ + #include "gtl_hw_device.h" + +namespace gtl +{ + namespace hw + { + device::device(QObject *parent) + : QThread(parent) +// , _default_iepe(false) + , _is_created_success(true) + , _rate(0) + , _state(IDLE) + { + _uuid = QUuid::createUuid(); + + _device = new gtl::device(NULL); + connect(_device, >l::device::get_type, this, &device::type); + + connect(_device, >l::device::start, this, &device::start); + connect(_device, >l::device::stop, this, &device::stop); + connect(_device, >l::device::restart, this, &device::restart); + + connect(_device, >l::device::save_device, this, &device::save); + connect(_device, >l::device::load_device, this, &device::load); + connect(this, &device::received_data, _device, >l::device::recieved_data); + + connect(_device, >l::device::get_device_parameter, this, &device::get_parameter); + connect(_device, >l::device::set_device_parameter, this, &device::set_parameter); + + connect(this, &QThread::started, _device, >l::device::started); + connect(this, &QThread::finished, _device, >l::device::stopped); + + } + + device::~device() + { + stop(); + delete _device; + } + + qreal device::rate() const + { + return _rate; + } + + QString device::id() const + { + return _id; + } + + analog_input *device::ai(int idx) + { + gtl::analog_data* input = _device->ai(idx); + if(input == NULL) + return NULL; + + return static_cast(input); + } + + gtl::device *device::gtl_device() const + { + return _device; + } + + void device::set_config(const QJsonValue &config) + { + _config = config; + + emit config_changed(); + } + + const QJsonValue &device::config() const + { + return _config; + } + + device::ADCState device::state() const + { + return _state; + } + + void device::get_parameter(int idx, QVariant &value) + { + + } + + void device::set_parameter(int /*idx*/, const QVariant &/*value*/) + { + + } + + void device::clear_ai() + { + _device->clear(); + } + + void device::set_ai() + { + if(count_ai() != _device->count_ai()) + { + clear_ai(); + + for(int i = 0; i < count_ai(); i++) + { + analog_input* ai_ = create_ai(i); +// ai_->set_iepe(_default_iepe); + + connect(ai_, &analog_input::iepe_changed, this, &device::ai_iepe_changed); + connect(ai_, &analog_input::coupling_changed, this, &device::ai_coupling_changed); + + _device->add_ai(ai_); + } + } + + for(int i = 0; i < _device->count_ai(); i++) + _device->ai(i)->set_rate(_rate); + } + + void device::set_ai_data(qreal *data, int size) + { + _device->lock_ai(); + + for(int i = 0; i < _device->count_ai(); i++) + ai(i)->set_data(data, size, i, _device->count_ai()); + + _device->unlock_ai(); + } + + analog_input *device::create_ai(int idx) + { + return new analog_input(_rate, tr("intput") + " " + QString::number(idx), _device); + } + + bool device::stop() + { + if(_is_continue) + { + _is_continue = false; + wait(); + } + return true; + } + + void device::restart() + { +// if(!isRunning()) +// return; + + stop(); + + start(_id, _rate); + } + + void device::save(QDomElement &root_element) + { + root_element.setAttribute("type", type()); + root_element.setAttribute("id", _id); + root_element.setAttribute("rate", _rate); + root_element.setAttribute("is_running", isRunning()); + root_element.setAttribute("count_ai", _count_ai); + root_element.setAttribute("uuid", _uuid.toString()); + } + + void device::load(const QDomElement &root_element) + { + _count_ai = root_element.attribute("count_ai", QString::number(_count_ai)).toInt(); + _uuid = QUuid::fromString(root_element.attribute("uuid", _uuid.toString())); + } + } +} diff --git a/hw/gtl_hw_device.h b/hw/gtl_hw_device.h new file mode 100644 index 0000000..3d0dd6b --- /dev/null +++ b/hw/gtl_hw_device.h @@ -0,0 +1,121 @@ +#ifndef HW_DEVICE_H +#define HW_DEVICE_H + +#include +#include + +#include "hw_global.h" + + +#include "core/gtl_device.h" + +#include "gtl_analog_input.h" + +#include "core/gtl_analog_data.h" + + +namespace gtl +{ + +namespace hw +{ + + class HW_EXPORT device : public QThread + { + Q_OBJECT + + public: + enum ADCState + { + IDLE, + INIT_IN_PROGRESS, + OK, + INIT_FAILED, + FAILED, + DRIVER_NOT_FOUND + }; + + public: + device(QObject *parent); + ~device(); + + // virtual bool restart(){ return true; } + + virtual qreal rate() const; + + int count_ai() const { return _count_ai; } + + + bool is_created() { return _is_created_success; } + + QString id() const; + + analog_input *ai(int idx); + + gtl::device* gtl_device() const; + + virtual void set_config(const QJsonValue& config); + const QJsonValue& config() const; + + ADCState state() const; + + protected: + void clear_ai(); + void set_ai(); + void set_ai_data(qreal *data, int size); + + virtual analog_input* create_ai(int idx); + + protected: + gtl::device* _device; + bool _is_created_success; + QString _id; + qreal _rate; + int _count_ai; + bool _is_continue; + + QJsonValue _config; + + ADCState _state; + QUuid _uuid; + + public slots: + virtual bool start(QString id, qreal rate) = 0; + virtual bool stop(); + virtual void restart(); + + virtual void get_parameter(int idx, QVariant &value); + virtual void set_parameter(int idx, const QVariant &value); + + virtual QString type() const = 0; + + protected slots: + virtual void ai_iepe_changed(){}; + virtual void ai_coupling_changed(){}; + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + + + signals : + void received_data(); + void error(QString title, QString message); + void channels_changed(); + void remove_channel(int idx); + void rate_changed(); + + void begin_reset(); + void end_reset(); + + void set_channels_sensitivity(qreal); + void name_changed(); + void status_changed(int); + + void config_changed(); + + }; + +} +} + +#endif // HW_DEVICE_H diff --git a/hw/gtl_hw_generator.cpp b/hw/gtl_hw_generator.cpp new file mode 100644 index 0000000..e8a91da --- /dev/null +++ b/hw/gtl_hw_generator.cpp @@ -0,0 +1,103 @@ +#include "gtl_hw_generator.h" + +namespace gtl +{ + namespace hw + { + generator::generator(QObject *parent) + : device{parent} + { + _count_ai = 4; + set_ai(); + } + + generator::~generator() + { + _is_continue = false; + wait(); + } + + QString generator::type() const + { + return "generator"; + } + + bool generator::start(QString id, qreal rate) + { + _rate = rate; + _id = id; + + set_ai(); + + _device->set_name(tr("generator") + "[" + id + "]"); + _device->set_id(_id); + _device->set_rate(rate); + + _is_continue = true; + + QThread::start(QThread::HighestPriority); + + emit status_changed(OK); + + return true; + } + + bool generator::stop() + { + _is_continue = false; + wait(); + return true; + } + + void generator::run() + { + + + qreal t = 0; + qreal dt = 1.0/rate(); + int cnt = 0; + + qint64 time_last = QDateTime::currentMSecsSinceEpoch(); + + + while(_is_continue) + { + qint64 time = QDateTime::currentMSecsSinceEpoch() - time_last; + + if (time >= 100) + { + time_last = QDateTime::currentMSecsSinceEpoch(); + + + int samples = time / dt / 1000 + 0.5; + _buffer.clear(); + + for (int i = 0; i < samples; i++) + { + for(int j = 0; j < _count_ai; j++) + { + _buffer.push_back(static_cast(ai(j))->get_value(t)); + } + + cnt++; + t = cnt*dt; + } + + if(!_buffer.empty()) + set_ai_data(&_buffer[0], (int)_buffer.size()); + + emit received_data(); + } + else + { + QThread::msleep(10); + } + } + } + + analog_input *generator::create_ai(int idx) + { + return new generator_analog_input(_rate, tr("intput") + " " + QString::number(idx), _device); + } + } +} diff --git a/hw/gtl_hw_generator.h b/hw/gtl_hw_generator.h new file mode 100644 index 0000000..76d26bb --- /dev/null +++ b/hw/gtl_hw_generator.h @@ -0,0 +1,37 @@ +#ifndef GENERATOR_H +#define GENERATOR_H + +#include "hw_global.h" + +#include + +#include "hw/gtl_hw_device.h" +#include "hw/gtl_hw_generator_analog_input.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT generator : public gtl::hw::device + { + Q_OBJECT + public: + explicit generator(QObject *parent = nullptr); + virtual ~generator(); + + virtual QString type() const; + + virtual bool start(QString id, qreal rate); + virtual bool stop(); + + private: + void run(); + virtual analog_input* create_ai(int idx); + + private: + std::vector _buffer; + }; + } +} + +#endif // GENERATOR_H diff --git a/hw/gtl_hw_generator_analog_input.cpp b/hw/gtl_hw_generator_analog_input.cpp new file mode 100644 index 0000000..895c4de --- /dev/null +++ b/hw/gtl_hw_generator_analog_input.cpp @@ -0,0 +1,80 @@ +#include "gtl_hw_generator_analog_input.h" + +namespace gtl +{ + namespace hw + { + generator_analog_input::generator_analog_input(qreal rate, QString name, gtl::device *parent) + : analog_input(rate, name, parent) + , _freq(100) + , _ampl(1) + , _phase(0) + { + + } + + qreal generator_analog_input::get_value(qreal t) + { + + return _ampl * qSin(2 * M_PI * t * _freq + _phase / 180.0*M_PI); + } + + qreal generator_analog_input::freq() const + { + return _freq; + } + + qreal generator_analog_input::ampl() const + { + return _ampl; + } + + qreal generator_analog_input::phase() const + { + return _phase; + } + + void generator_analog_input::save(QDomElement &root_element) + { + root_element.setAttribute("freq", _freq); + root_element.setAttribute("phase", _phase); + + gtl::analog_data::save(root_element); + } + + void generator_analog_input::load(const QDomElement &root_element) + { + _freq = root_element.attribute("freq", "100").toDouble(); + _phase = root_element.attribute("phase", "0").toDouble(); + + gtl::analog_data::load(root_element); + } + + void generator_analog_input::set_freq(qreal value) + { + if(_freq != value) + { + _freq = value; + emit freq_changed(); + } + } + + void generator_analog_input::set_ampl(qreal value) + { + if(_ampl != value && value > 0) + { + _ampl = value; + emit ampl_changed(); + } + } + + void generator_analog_input::set_phase(qreal value) + { + if(_phase != value && value < 360) + { + _phase = value; + emit phase_changed(); + } + } + } +} diff --git a/hw/gtl_hw_generator_analog_input.h b/hw/gtl_hw_generator_analog_input.h new file mode 100644 index 0000000..c197e59 --- /dev/null +++ b/hw/gtl_hw_generator_analog_input.h @@ -0,0 +1,50 @@ +#ifndef GENERATOR_ANALOG_INPUT_H +#define GENERATOR_ANALOG_INPUT_H + +#include "hw_global.h" + +#include "hw/gtl_analog_input.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT generator_analog_input : public gtl::hw::analog_input + { + Q_OBJECT + Q_PROPERTY(qreal freq READ freq WRITE set_freq NOTIFY freq_changed) + Q_PROPERTY(qreal ampl READ ampl WRITE set_ampl NOTIFY ampl_changed) + Q_PROPERTY(qreal phase READ phase WRITE set_phase NOTIFY phase_changed) + + public: + generator_analog_input(qreal rate, QString name, gtl::device *parent); + + qreal get_value(qreal t); + + qreal freq() const; + qreal ampl() const; + qreal phase() const; + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + + private: + qreal _freq; + qreal _ampl; + qreal _phase; + + public slots: + void set_freq(qreal value); + void set_ampl(qreal value); + void set_phase(qreal value); + + signals: + void freq_changed(); + void ampl_changed(); + void phase_changed(); + + }; + } +} + +#endif // GENERATOR_ANALOG_INPUT_H diff --git a/hw/gtl_hw_player.cpp b/hw/gtl_hw_player.cpp new file mode 100644 index 0000000..bf0394b --- /dev/null +++ b/hw/gtl_hw_player.cpp @@ -0,0 +1,377 @@ +#include "gtl_hw_player.h" + +#include + +#include "hw/gtl_hw_player_file_gtr.h" +#include "hw/gtl_hw_player_file_wav.h" +#include "hw/gtl_hw_player_analog_input.h" + +namespace gtl +{ + namespace hw + { + class contain + { + private: + int _idx; + public: + contain(int idx) : _idx(idx) {} + bool operator()(const std::pair range) const { return _idx >= range.first && _idx < range.second; } + }; + + class greater + { + private: + int _idx; + public: + greater(int idx) : _idx(idx) {} + bool operator()(const std::pair range) const { return _idx < range.first; } + }; + + player::player(QObject* parent) + : device{parent} + , _file(NULL) + , _is_cyclic(false) + , _time(0) + , _speed_factor(1) + , _sampling(1) + { + _device->set_name(tr("player")); + } + + QString player::type() const + { + return "player"; + } + + bool player::start(QString id, qreal rate) + { + stop(); + + if(_id != id) + { + _id = id; + +/* + //_device->remove_children(); + while(_device->count() != 0) + { + data_model_node* child = _device->child(0); +// _device->remove(child); + delete child; + } +*/ + _device->clear(); + + if(_file) + { + delete _file; + _file = NULL; + } + + QFileInfo info(_id); + if(info.suffix().toLower() == "gtr") + _file =new player_file_gtr(this, _id); + else if(info.suffix().toLower() == "wav") + _file =new player_file_wav(this, _id); + + + + if(_file) + { + if(!_file->is_ok()) + { + delete _file; + _file = NULL; + } + } + + if(_file) + { + _rate = _file->rate()/_sampling; + for(int i = 0;i < _file->channels(); i++) + _device->add_ai(new player_analog_input(_rate, _file->channel_info(i), _device)); + _count_ai = _file->channels(); + + _device->set_name(tr("player") + "[" + info.fileName() + "]"); + } + else + { + _rate = 0; + _device->set_name(tr("player")); + } + + + _device->set_id(id); + _device->set_rate(_rate); + _time = 0; + + } + else if(_file) + { +// if(_rate != _file->rate()/_sampling) + { + _rate = _file->rate()/_sampling; + for(int i = 0; i < _file->channels(); i++) + ai(i)->set_rate(_rate); + + _device->set_rate(_rate); + } + } + + if(_file && rate == 0) + { + QThread::start(QThread::HighPriority); + } + + return true; + } + + void player::save(QDomElement &root_element) + { + _rate = -1; + device::save(root_element); + } + + player_file *player::file() const + { + return _file; + } + + void player::run() + { + qDebug() << "starting playing"; + _is_continue = true; + _is_suspended = false; + + quint64 last_time = QDateTime::currentMSecsSinceEpoch(); + qreal dt = 1.0 /_file->rate(); + std::vector buffer; + int idx = 0; + _time = 0; + _pos = 0; + + quint64 intervalms = 100; + + int sampling_idx = 0; + + _ranges_indices.clear(); + _file->get_ranges_indices(std::back_inserter(_ranges_indices)); + + while(_is_continue) + { + quint64 current_time = QDateTime::currentMSecsSinceEpoch(); + quint64 time = current_time - last_time; + + if(time >= intervalms/1/*00*/) + { + if (!_is_suspended) + { + int samples = qRound(intervalms/dt/1000*_speed_factor); + //buffer.resize(samples * _file->channels()); + buffer.clear(); + + //bool is_complete = _file->get_data(&buffer[0], idx, samples, _is_cyclic); + bool is_complete = get_data(idx, samples, _is_cyclic, std::back_inserter(buffer)); + + + if(_sampling != 1) + { + int cnt = 0; + for(int i = sampling_idx; i < samples; i += _sampling) + { + for(int j = 0; j < _file->channels(); j++) + buffer[cnt* _file->channels() + j] = buffer[i*_file->channels() + j]; + + cnt++; + } + + sampling_idx = _sampling - 1 - (samples - sampling_idx)%_sampling; + samples = cnt; + +// qDebug() << sampling_idx << samples; + } + + if(samples) + { + set_ai_data(&buffer[0], /*(int)buffer.size()*/samples*_file->channels()); + + emit received_data(); + } + + _time += samples*dt; + _pos = idx*dt; + + if(is_complete) + { + if(/*idx == 0*/!_is_cyclic) + _time = total_time(); + break; + } + + + } + + last_time = current_time; + } + else + { + QThread::msleep(10); + } + } + } + + bool player::get_data(int &idx, int &samples, bool is_cyclic, std::back_insert_iterator> data) + { + if (_file == NULL) + { + samples = 0; + return true; + } + + + _buffer_tmp.resize(samples*_file->channels()); + + if (_buffer_tmp.empty()) + { + samples = 0; + return true; + } + + + int samples_total = samples; + int samples_readed = 0; + + bool is_completed = false; + + + + while (samples_readed < samples_total) + { + + int idx_start = idx; + + std::vector>::const_iterator iter_range_start = std::find_if(_ranges_indices.begin(), _ranges_indices.end(), contain(idx_start)); + if (iter_range_start == _ranges_indices.end()) + { + iter_range_start = std::find_if(_ranges_indices.begin(), _ranges_indices.end(), greater(idx_start)); + if (iter_range_start == _ranges_indices.end()) + { + if (is_cyclic) + iter_range_start = std::find_if(_ranges_indices.begin(), _ranges_indices.end(), greater(0)); + else + { + is_completed = true; + break; + } + + } + + + idx_start = iter_range_start->first; + } + + int idx_stop = idx_start + samples; + std::vector>::const_iterator iter_range_stop = std::find_if(_ranges_indices.begin(), _ranges_indices.end(), contain(idx_stop)); + if (iter_range_stop != iter_range_start) + idx_stop = iter_range_start->second; + + idx = idx_start; + int samples_read = idx_stop - idx_start; + if (samples_readed + samples_read > samples_total) + samples_read = samples_total - samples_readed; + + + is_completed = _file->get_data(&_buffer_tmp[0], idx, samples_read, is_cyclic); + std::copy(_buffer_tmp.begin(), _buffer_tmp.begin() + samples_read*_file->channels(), data); + samples_readed += samples_read; + + if (is_completed) + break; + + } + + samples = samples_readed; + + + return is_completed; + } + + qreal player::total_time() + { + if(_file == nullptr) + return 0; + + _ranges_indices.clear(); + _file->get_ranges_indices(std::back_inserter(_ranges_indices)); + + if(_ranges_indices.empty()) + return _file->time(); + + + qreal time = 0; + std::pair range = _ranges_indices[0]; + + for(int i = 1; i < _ranges_indices.size(); i++) + { + if(range.second >= _ranges_indices[i].first) + { + range.second = std::max(range.second, _ranges_indices[i].second); + } + else + { + time += (range.second - range.first) / _file->rate(); + range = _ranges_indices[i]; + } + } + + time += (range.second - range.first) / _file->rate(); + + return time; + } + + void player::get_parameter(int idx, QVariant &value) + { + if(idx == 0) + value = total_time(); + else if(idx == 1) + value = _time; + else if(idx == 2) + value = _is_cyclic; + else if(idx == 3) + value = _speed_factor; + else if(idx == 4 && _file) + value = QVariant(_file->data()); + else if(idx == 5) + { + if(_file) + value = _file->rate() / _sampling; + else + value = 0; + } + else if(idx == 6) + value = _pos; + else if(idx == 7) + value = _uuid; + + } + + void player::set_parameter(int idx, const QVariant &value) + { + if(idx == 2) + _is_cyclic = value.toBool(); + else if(idx == 3) + _speed_factor = value.toDouble(); + else if(idx == 5) + { + if(_file) + _sampling = qRound(_file->rate()/value.toDouble()); + else + _sampling = 1; + + if(_sampling < 1) + _sampling = 1; + } + } + } +} diff --git a/hw/gtl_hw_player.h b/hw/gtl_hw_player.h new file mode 100644 index 0000000..bf22300 --- /dev/null +++ b/hw/gtl_hw_player.h @@ -0,0 +1,52 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include "hw_global.h" +#include "hw/gtl_hw_device.h" +#include "hw/gtl_hw_generator_analog_input.h" +#include "hw/gtl_hw_player_file.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT player : public device + { + Q_OBJECT + public: + player(QObject* parent = nullptr); + + virtual QString type() const override; + + virtual bool start(QString id, qreal rate) override; +// virtual + + virtual void save(QDomElement& root_element) override; + + player_file* file() const; + + private: + virtual void run() override; + bool get_data(int &idx, int& samples, bool is_cyclic, std::back_insert_iterator>); + qreal total_time(); + + private: + player_file* _file; + bool _is_suspended; + bool _is_cyclic; + qreal _time; + qreal _pos; + qreal _speed_factor; + int _sampling; + std::vector> _ranges_indices; + std::vector _buffer_tmp; + + public slots: + virtual void get_parameter(int idx, QVariant &value) override; + virtual void set_parameter(int idx, const QVariant &value) override; + + }; + } +} + +#endif // PLAYER_H diff --git a/hw/gtl_hw_player_analog_input.cpp b/hw/gtl_hw_player_analog_input.cpp new file mode 100644 index 0000000..f681f6b --- /dev/null +++ b/hw/gtl_hw_player_analog_input.cpp @@ -0,0 +1,20 @@ +#include "gtl_hw_player_analog_input.h" + +namespace gtl +{ + namespace hw + { + player_analog_input::player_analog_input(qreal rate, QJsonObject info, gtl::device *parent) + : analog_input(rate, "", parent) + , _info(info) + { + _name = info["name"].toString(); + _color = info["color"].toInt(); + } + + const QJsonObject &player_analog_input::info() const + { + return _info; + } + } +} diff --git a/hw/gtl_hw_player_analog_input.h b/hw/gtl_hw_player_analog_input.h new file mode 100644 index 0000000..1d6abd6 --- /dev/null +++ b/hw/gtl_hw_player_analog_input.h @@ -0,0 +1,27 @@ +#ifndef PLAYER_ANALOG_INPUT_H +#define PLAYER_ANALOG_INPUT_H + +#include + +#include "hw/gtl_analog_input.h" + +#include "hw_global.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT player_analog_input : public gtl::hw::analog_input + { + Q_OBJECT + public: + player_analog_input(qreal rate, QJsonObject info, gtl::device *parent); + const QJsonObject& info() const; + + private: + QJsonObject _info; + }; + } +} + +#endif // PLAYER_ANALOG_INPUT_H diff --git a/hw/gtl_hw_player_file.cpp b/hw/gtl_hw_player_file.cpp new file mode 100644 index 0000000..d3a9cfa --- /dev/null +++ b/hw/gtl_hw_player_file.cpp @@ -0,0 +1,194 @@ +#include "hw/gtl_hw_player_file.h" +#include "qjsonarray.h" + +#include + +namespace gtl +{ + namespace hw + { + player_file::player_file(QObject *parent, QString path) + : QObject(parent) + , _path(path) + , _stream(NULL) + , _is_ok(false) + , _time(0) + , _channels(0) + { + _file = new QFile(path); + + if (_file->open(QIODevice::ReadOnly)) + { + _stream = new QDataStream(_file); + _stream->setByteOrder(QDataStream::LittleEndian); + _stream->setFloatingPointPrecision(QDataStream::FloatingPointPrecision::SinglePrecision); + _is_ok = true; + } + + QFile info_file(path + ".info"); + if(info_file.open(QFile::ReadOnly)) + { + QByteArray data = info_file.readAll(); + _data = QJsonDocument::fromJson(data).object(); + info_file.close(); + } + } + + player_file::~player_file() + { + if (_stream) + delete _stream; + + _file->close(); + + delete _file; + } + + QString player_file::channel(int idx) const + { + QString name = _data["hw"]["channels"].toArray().at(idx).toObject().value("name").toString(); + if(name.isEmpty()) + name = "input " + QString::number(idx); + + return name; + } + + QString player_file::unit(int idx) const + { + return _data["hw"]["channels"].toArray().at(idx).toObject().value("unit").toString(); + } + + int player_file::color(int idx) const + { + return _data["hw"]["channels"].toArray().at(idx).toObject().value("color").toInt(); + } + + QJsonObject player_file::channel_info(int idx) const + { + return _data["hw"]["channels"].toArray().at(idx).toObject(); + } + + qreal player_file::reference(int idx) const + { + qreal reference = get_root(idx).value("reference").toDouble(); + if(reference == 0) + reference = 1; + + return reference; + } + + qreal player_file::sensitivity(int idx) const + { + qreal sensitivity = get_root(idx).value("sensitivity").toDouble(); + if(sensitivity == 0) + sensitivity = 1; + + return sensitivity; + } + + qreal player_file::gain(int idx) const + { + qreal gain = get_root(idx).value("gain").toDouble(); + if(gain == 0) + gain = 1; + + return gain; + } + + qreal player_file::offset(int idx) const + { + return get_root(idx).value("offset").toDouble(); + } + + QString player_file::device() const + { + return _data["hw"]["name"].toString(); + } + + QString player_file::comment() const + { + return _data["comment"].toString(); + } + + const QJsonObject &player_file::data() const + { + return _data; + } + + void player_file::seek_to_start() + { + _file->seek(_pos_data); + } + + void player_file::get_ranges_indices(std::back_insert_iterator>> ranges) const + { + QJsonObject data_object; + QFile info_file(_path + ".info"); + if(info_file.open(QFile::ReadOnly)) + { + QByteArray data = info_file.readAll(); + data_object = QJsonDocument::fromJson(data).object(); + + QJsonArray ranges_array = data_object["ranges"].toArray(); + if(ranges_array.empty()) + { + ranges = std::pair(0, samples()); + ranges++; + } + + + for(auto it = ranges_array.begin(); it != ranges_array.end(); it++, ranges++) + ranges = std::pair(qRound(it->toObject()["left"].toDouble()*_rate), qRound(it->toObject()["right"].toDouble()*_rate)); + + + info_file.close(); + } + } + + void player_file::set_ranges(std::vector>::iterator begin, std::vector>::iterator end) + { + std::sort(begin, end); + + QJsonArray ranges; + for(auto it = begin; it!= end; it++) + { + QJsonObject range; + range.insert("left", it->first); + range.insert("right", it->second); + + ranges.append(range); + } + + _data["ranges"] = ranges; + + QFile info_file(_path + ".info"); + if(info_file.open(QFile::WriteOnly)) + { + info_file.write(QJsonDocument(_data).toJson()); + info_file.close(); + } + + } + + void player_file::get_ranges(std::back_insert_iterator>> ranges) const + { + QJsonArray ranges_array = _data["ranges"].toArray(); + for(auto it = ranges_array.begin(); it != ranges_array.end(); it++, ranges++) + ranges = std::pair(it->toObject()["left"].toDouble(), it->toObject()["right"].toDouble()); + } + + QJsonObject player_file::get_root(int idx) const + { + QJsonObject root = _data["hw"]["channels"].toArray().at(idx).toObject(); + while(!root.isEmpty()) + { + if(root.value("node").toString().contains("analog_input")) + break; + + root = root.value("parent").toObject(); + } + + return root; + } + } +} diff --git a/hw/gtl_hw_player_file.h b/hw/gtl_hw_player_file.h new file mode 100644 index 0000000..0f12375 --- /dev/null +++ b/hw/gtl_hw_player_file.h @@ -0,0 +1,92 @@ +#ifndef GTL_HW_PLAYER_FILE_H +#define GTL_HW_PLAYER_FILE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hw_global.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT player_file : public QObject + { + Q_OBJECT + + public: + player_file(QObject *parent, QString path); + ~player_file(); + + bool is_ok() const { return _is_ok; } + int channels() const { return _channels; } + qreal rate() const { return _rate; } + qreal dt() const { return _rate == 0 ? 0 : 1.0/_rate; } + qreal time() const { return _time; } + QString info() const { return _info; } + int samples() const { return qRound(_time*_rate); } + QString channel(int idx) const; + QString unit(int idx) const; + int color(int idx) const; + QJsonObject channel_info(int idx) const; + + qreal reference(int idx) const; + qreal sensitivity(int idx) const; + qreal gain(int idx) const; + qreal offset(int idx) const; + + QString device() const; + virtual QString comment() const; + + const QJsonObject& data() const; + + + virtual bool get_data(qreal *data, int &samples, bool is_cyclic, bool &is_continued) = 0; + virtual bool get_data(qreal *data, int& idx, int &samples, bool is_cyclic = false) = 0; + + void seek_to_start(); + QString path() const { return _path; } + + void get_ranges_indices(std::back_insert_iterator>> ranges) const; + void set_ranges(std::vector>::iterator begin, std::vector>::iterator end); + void get_ranges(std::back_insert_iterator>> out) const; + + + protected: + QString _path; + QFile* _file; + QDataStream* _stream; + + bool _is_ok; + + qreal _rate; + qreal _time; + int _channels; + + QString _info; + + quint64 _pos_data; + QMutex _mutex; + + QJsonObject _data; + + private: + QJsonObject get_root(int idx) const; + + + + private: + + + }; + } +} + +#endif // GTL_HW_PLAYER_FILE_H diff --git a/hw/gtl_hw_player_file_gtr.cpp b/hw/gtl_hw_player_file_gtr.cpp new file mode 100644 index 0000000..76902b5 --- /dev/null +++ b/hw/gtl_hw_player_file_gtr.cpp @@ -0,0 +1,226 @@ +#include "gtl_hw_player_file_gtr.h" +#include "qjsonarray.h" + +#include + +namespace gtl +{ + namespace hw + { + player_file_gtr::player_file_gtr(QObject *parent, QString path) : player_file(parent, path) + { + + if (_stream == NULL) + return; + + _stream->setFloatingPointPrecision(QDataStream::SinglePrecision); + + QByteArray header; + QDomDocument doc; + + + + *_stream >> header; + + QTextCodec *codec = QTextCodec::codecForName("Windows-1251"); + + header = header.replace("windows-1251", "UTF-8"); + + QString error_msg; + int error_line, error_column; + if (doc.setContent(/*QString(header).toUtf8()*/codec->toUnicode(header), false, &error_msg, &error_line, &error_column)) + { + QDomElement root = doc.firstChildElement("gtr_header"); + if (!root.isNull()) + { + _device = root.attribute("device", ""); + _rate = root.attribute("rate", "0").toDouble(); + _time = root.attribute("time", 0).toDouble(); + _comment = root.attribute("comment", ""); + _freq = root.attribute("freq", "0"); + + _data["comment"] = _comment; + + QJsonObject hw_object; + hw_object["name"] = _device; + hw_object["rate"] = _rate; + + + + _element_device = root.firstChildElement("recorder"); + + if (!_element_device.isNull()) + { + QDomElement element_child_input = _element_device.firstChildElement("input"); + + QJsonArray channels_array; + + while (!element_child_input.isNull()) + { + QJsonObject channel_object; + channel_object["color"] = element_child_input.attribute("color", "0").toInt(); + channel_object["gain"] = element_child_input.attribute("gain", "1").toDouble(); + channel_object["is_coupling"] = element_child_input.attribute("coupling", "0").toInt() == 1; + channel_object["is_iepe"] = element_child_input.attribute("iepe", "0").toInt() == 1; + channel_object["is_inverting"] = false; + channel_object["name"] = element_child_input.attribute("name", "input_" + QString::number(channels_array.size())); + channel_object["node"] = "gtl::analog_input"; + channel_object["offset"] = element_child_input.attribute("offset", "0").toDouble(); + channel_object["reference"] = element_child_input.attribute("reference", "1").toDouble(); + channel_object["sensitivity"] = element_child_input.attribute("sensitivity", "1").toDouble(); + channel_object["unit"] = element_child_input.attribute("unit", ""); + + channels_array.append(channel_object); + + element_child_input = element_child_input.nextSiblingElement("input"); + } + + hw_object.insert("channels", channels_array); + _data.insert("hw", hw_object); + + _channels = channels_array.size(); + + _time = 1.0 / _rate * (_file->size() - _file->pos()) / sizeof(float) / channels(); + _pos_data = _file->pos(); + + + + _info += tr("device:") + " " + _device + ";\r\n"; + _info += tr("rate:") + " " + QString::number(_rate, 'f', 0) + tr(" hz") + ";\r\n"; + _info += tr("time:") + " " + QString::number(_time) + tr(" sec") + ";\r\n"; + _info += "\r\n" + _comment; + + _time = 1.0 / _rate * (_file->size() - _file->pos()) / sizeof(float) / channels(); + if (channels() == 0) + _time = 0; + _pos_data = _file->pos(); + + + _is_ok = true; + return; + } + } + } + else + qDebug() << error_line; + + + _is_ok = false; + } + + player_file_gtr::~player_file_gtr() + { + + } + + bool player_file_gtr::get_data(qreal *data, int &samples, bool is_cyclic, bool &is_continued) + { + int smps = 0; + + std::vector buffer(samples * channels()); + + while (smps < samples) + { + int smps_to_end = (_file->size() - _file->pos()) / sizeof(float) / channels(); + + if (smps_to_end < (samples - smps)) + { + int bytes = _stream->readRawData((char*)&buffer[smps*channels()], smps_to_end*channels()*sizeof(float)); + if (bytes <= 0) + { + samples = smps; + _mutex.unlock(); + return false; + } + + smps += bytes / channels() / sizeof(float); + _file->seek(_pos_data); + if (!is_cyclic) + { + samples = smps; + is_continued = false; + break; + } + } + else + { + int bytes = _stream->readRawData((char*)&buffer[smps*channels()], (samples - smps)*channels()*sizeof(float)); + if (bytes <= 0) + { + samples = smps; + _mutex.unlock(); + return false; + } + smps += bytes / channels() / sizeof(float); + } + } + + std::copy(buffer.begin(), buffer.end(), &data[0]); + + return true; + } + + bool player_file_gtr::get_data(qreal *data, int& idx, int &samples, bool is_cyclic /*= false*/) + { + _mutex.lock(); + + int smps = 0; + + _file->seek(_pos_data + idx*channels()*sizeof(float)); + + std::vector buffer(samples * channels()); + + while (smps < samples) + { + int smps_to_end = (_file->size() - _file->pos()) / sizeof(float) / channels(); + + if (smps_to_end < (samples - smps)) + { + int bytes = _stream->readRawData((char*)&buffer[smps*channels()], smps_to_end*channels()*sizeof(float)); + if (bytes < 0) + return false; + + smps += bytes / channels() / sizeof(float); + + if (!is_cyclic) + { + samples = smps; + break; + } + else + _file->seek(_pos_data); + } + else + { + int bytes = _stream->readRawData((char*)&buffer[smps*channels()], (samples - smps)*channels()*sizeof(float)); + if (bytes < 0) + return false; + smps += bytes / channels() / sizeof(float); + } + } + + std::copy(buffer.begin(), buffer.end(), &data[0]); + + bool is_compete = _file->pos() == _file->size(); + if (is_compete) + seek_to_start(); + + idx = (_file->pos() - _pos_data) / sizeof(float) / channels(); + + _mutex.unlock(); + + return is_compete && !is_cyclic; + } + + QString player_file_gtr::comment() const + { + return _comment; + } + + QString player_file_gtr::getFreq() const + { + return _freq; + } + + } +} diff --git a/hw/gtl_hw_player_file_gtr.h b/hw/gtl_hw_player_file_gtr.h new file mode 100644 index 0000000..e0885af --- /dev/null +++ b/hw/gtl_hw_player_file_gtr.h @@ -0,0 +1,44 @@ +#ifndef GTLAB_FILE_H +#define GTLAB_FILE_H + +#include "gtl_hw_player_file.h" +#include +#include + +#include "hw_global.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT player_file_gtr : public player_file + { + Q_OBJECT + + public: + player_file_gtr(QObject *parent, QString path); + ~player_file_gtr(); + + + QDomElement* device_element() { return &_element_device; } + + virtual bool get_data(qreal *data, int &samples, bool is_cyclic, bool &is_continued); + virtual bool get_data(qreal *data, int& idx, int &samples, bool is_cyclic = false); + + + virtual QString comment() const; + QString getFreq() const; + + private: + QDomElement _element_device; + + QString _device; + QString _comment; + QString _freq; + + + }; + } +} + +#endif // GTLAB_FILE_H diff --git a/hw/gtl_hw_player_file_wav.cpp b/hw/gtl_hw_player_file_wav.cpp new file mode 100644 index 0000000..3005a8b --- /dev/null +++ b/hw/gtl_hw_player_file_wav.cpp @@ -0,0 +1,293 @@ +#include "gtl_hw_player_file_wav.h" + +#include "qmath.h" + +namespace gtl +{ + namespace hw + { + player_file_wav::player_file_wav(QObject *parent, QString path) : player_file(parent, path) + { + if (!_is_ok) + return; + + _is_ok = false; + + quint32 id_chunk; + quint32 size_chunk; + + *_stream >> id_chunk; + if (id_chunk != 0x46464952 && id_chunk != 0x34365742) + return; + + *_stream >> size_chunk; + // if (size_chunk != _file->size() - _file->pos()) + // return; + + *_stream >> id_chunk; + if (id_chunk != 0x45564157) + return; + + while (!_stream->atEnd()) + { + *_stream >> id_chunk; + if (id_chunk == 0x20746d66) + { + read_fmt_chunk(); + + if (_format != 1 && _format != 3) + return; + } + else if (id_chunk == 0x61746164) + { + read_data_chunk(); + break; + } + else if (id_chunk == 0x34367364) + { + read_ds64_chunk(); + } + else + { + read_unknown_chunk(); + } + } + + _file->seek(_pos_data + _size_data); + + _time = (qreal)_size_data / _block_align / _rate; + + _info = tr("rate: ") + QString::number(_rate, 'f', 0) + tr(" hz") + ";\r\n"; + _info += tr("time: ") + QString::number(_time) + tr(" sec") + ";\r\n"; + _info += tr("bits per sample: ") + QString::number(_bits_per_sample) + ";\r\n"; + _info += "\r\n" + comment(); + + _is_ok = true; + } + + player_file_wav::~player_file_wav() + { + + } + + bool player_file_wav::get_data(qreal *data, int &samples, bool is_cyclic, bool &is_continued) + { + quint64 smps = 0; + + QByteArray buffer(samples * _block_align, 0); + + while (smps < samples) + { + quint64 smps_to_end = (_pos_data + _size_data - _file->pos()) / _block_align; + + if (smps_to_end < (samples - smps)) + { + int bytes = _stream->readRawData((char*)&buffer.data()[smps*_block_align], smps_to_end*_block_align); + if (bytes < 0) + return false; + + smps += bytes / _block_align; + _file->seek(_pos_data); + if (!is_cyclic) + { + samples = smps; + is_continued = false; + break; + } + } + else + { + int bytes = _stream->readRawData((char*)&buffer.data()[smps*_block_align], (samples - smps)*_block_align); + if (bytes < 0) + return false; + smps += bytes / _block_align; + } + } + + qint32 sample; + int bytes_per_sample = _block_align / channels(); + + int bits_shift = 32 - _bits_per_sample; + qreal factor = qPow(2, bits_shift); + + for (int i = 0; i < samples * channels(); i++) + { + std::copy(&buffer.data()[i*bytes_per_sample], &buffer.data()[i*bytes_per_sample + bytes_per_sample], (char*)&sample); + + if (_bits_per_sample) + sample -= 128; + + sample = sample << bits_shift; + + data[i] = 10.0 * sample / (sample < 0 ? 2147483648 : 2147483647); + } + + return true; + } + + + + bool player_file_wav::get_data(qreal *data, int &idx, int &samples, bool is_cyclic /*= false*/) + { + _mutex.lock(); + + quint64 smps = 0; + + _file->seek(_pos_data + idx*_block_align); + + QByteArray buffer(samples * _block_align, 0); + + while (smps < samples) + { + quint64 smps_to_end = (_pos_data + _size_data - _file->pos()) / _block_align; + + if (smps_to_end < (samples - smps)) + { + int bytes = _stream->readRawData((char*)&buffer.data()[smps*_block_align], smps_to_end*_block_align); + if (bytes <= 0) + { + _mutex.unlock(); + samples = smps; + return false; + } + + + smps += bytes / _block_align; + + if (!is_cyclic) + { + samples = smps; + break; + } + else + _file->seek(_pos_data); + } + else + { + int bytes = _stream->readRawData((char*)&buffer.data()[smps*_block_align], (samples - smps)*_block_align); + if (bytes < 0) + { + _mutex.unlock(); + samples = smps; + return false; + } + smps += bytes / _block_align; + } + } + + qint32 sample; + int bytes_per_sample = _block_align / channels(); + + int bits_shift = 32 - _bits_per_sample; + qreal factor = qPow(2, bits_shift); + + for (int i = 0; i < samples * channels(); i++) + { + std::copy(&buffer.data()[i*bytes_per_sample], &buffer.data()[i*bytes_per_sample + bytes_per_sample], (char*)&sample); + + if (_format == 3) + { + data[i] = *((float*)&sample); + } + else + { + if (_bits_per_sample) + sample -= 128; + + sample = sample << bits_shift; + + data[i] = 10.0 * sample / (sample < 0 ? 2147483648 : 2147483647); + } + } + + int pos = _file->pos(); + int size = _pos_data + _size_data; + bool is_compete = _file->pos() == _pos_data + _size_data; + if (is_compete) + seek_to_start(); + + idx = (_file->pos() - _pos_data) / _block_align; + + _mutex.unlock(); + + return is_compete && !is_cyclic; + + } + + void player_file_wav::read_fmt_chunk() + { + quint32 size_chunk; + + *_stream >> size_chunk; + + quint64 pos_chunk = _file->pos(); + + quint32 tmp_32; + quint16 tmp_16; + + *_stream >> _format; + + if (_format == 1 || _format == 3) + { + *_stream >> tmp_16; + _channels = tmp_16; + + *_stream >> tmp_32; + _rate = tmp_32; + + *_stream >> tmp_32; // avg bytes per second + + *_stream >> _block_align; + + *_stream >> _bits_per_sample; + } + + _file->seek(pos_chunk + size_chunk); + + } + + void player_file_wav::read_data_chunk() + { + quint32 size; + *_stream >> size; + + if (size != 0xffffffff) + _size_data = size; + + _pos_data = _file->pos(); + + // _stream->skipRawData(_size_data); + } + + void player_file_wav::read_ds64_chunk() + { + quint32 high; + quint32 low; + + quint32 size; + *_stream >> size; + + *_stream >> low; + *_stream >> high; + + *_stream >> low; + *_stream >> high; + + _size_data = low + ((quint64)high << 32); + + *_stream >> low; + *_stream >> high; + + *_stream >> low; + } + + void player_file_wav::read_unknown_chunk() + { + quint32 size; + + *_stream >> size; + + _stream->skipRawData(size); + } + } +} diff --git a/hw/gtl_hw_player_file_wav.h b/hw/gtl_hw_player_file_wav.h new file mode 100644 index 0000000..caedda1 --- /dev/null +++ b/hw/gtl_hw_player_file_wav.h @@ -0,0 +1,39 @@ +#ifndef WAV_FILE_H +#define WAV_FILE_H + +#include "gtl_hw_player_file.h" + +#include "hw_global.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT player_file_wav : public player_file + { + Q_OBJECT + + public: + player_file_wav(QObject *parent, QString path); + ~player_file_wav(); + + virtual bool get_data(qreal *data, int &samples, bool is_cyclic, bool &is_continued); + virtual bool get_data(qreal *data, int &idx, int &samples, bool is_cyclic = false); + + private: + quint16 _format; + quint16 _block_align; + quint16 _bits_per_sample; + quint64 _size_data; + + private: + void read_fmt_chunk(); + void read_data_chunk(); + void read_ds64_chunk(); + void read_unknown_chunk(); + + }; + } +} + +#endif // WAV_FILE_H diff --git a/hw/gtl_hw_recorder.cpp b/hw/gtl_hw_recorder.cpp new file mode 100644 index 0000000..f484905 --- /dev/null +++ b/hw/gtl_hw_recorder.cpp @@ -0,0 +1,224 @@ +#include "gtl_hw_recorder.h" + +#include +#include +#include + +#include "core/gtl_logger.h" + +namespace gtl +{ + namespace hw + { + recorder::recorder(const std::vector &ad, qreal time, QString comment, QString dir, QString file_name, QString extension, QObject *parent) + : QObject{parent} + , _samples(0) + , _size(0) + , _stream(nullptr) + , _file(nullptr) + , _device(nullptr) + { + + QJsonArray channels; + + for(auto it : ad) + { + if(_device) + { + if(_device != static_cast(it->root())) + continue; + + } + else + _device = static_cast(it->root()); + +/* + QJsonArray ad_objects; + gtl::data_model_node* node = it; + + while(node->parent_node()) + { + + QJsonObject node_object; + node->get_state(node_object); + ad_objects.append(node_object); + + node = node->parent_node(); + } + + channels.append(ad_objects); +*/ + + QJsonObject ad_object; + get_state(ad_object, it); + channels.append(ad_object); + + _ad.push_back(it); + } + + + if(_device == nullptr) + { + gtl::logger::warning("recorder", "nothing record"); + + return; + } + + QJsonObject device_object; + device_object["name"] = _device->name(); + device_object["rate"] = _device->rate(); + device_object["channels"] = channels; + + + + + QDir dir_(dir); + dir_.mkpath(dir); + QString name = file_name + "." + extension; + int idx = 0; + while(dir_.exists(name)) + { + idx++; + + name = file_name + " (" + QString::number(idx) + ")." + extension; + } + + QJsonDocument info; + QJsonObject root; + root["hw"] = device_object; + root["comment"] = comment; + info.setObject(root); + + + _path = dir_.absoluteFilePath(name); + _file = new QFile(_path); + if(!_file->open(QFile::WriteOnly)) + { + delete _file; + _file = nullptr; + + gtl::logger::error("recorder", "error creatig file: " + _path); + + return; + } + + QFile info_file(_path + ".info"); + if(info_file.open(QFile::WriteOnly)) + { + info_file.write(info.toJson()); + info_file.close(); + } + + _stream = new QDataStream(_file); + _stream->setFloatingPointPrecision(QDataStream::SinglePrecision); + _stream->setByteOrder(QDataStream::LittleEndian); + + + _size = _device->rate()*time; + if(_size == 0) + _size -=1; + + } + + recorder::~recorder() + { + stop(); + + delete _stream; + + if(_file) + delete _file; + } + + void recorder::start() + { + if(_device) + connect(_device, >l::device::recieved_data, this, &recorder::device_recieved_data); + + emit progress(0); + + } + + void recorder::stop() + { + if(_device) + disconnect(_device, >l::device::recieved_data, this, &recorder::device_recieved_data); + } + + bool recorder::is_success() const + { + if(_device == nullptr) + return false; + + if(_file == nullptr) + return false; + + return true; + } + + qreal recorder::time() const + { + return _samples/_device->rate(); + } + + QString recorder::path() const + { + return _path; + } + + void recorder::get_state(QJsonObject &root, data_model_node *node) + { + if(node->parent_node()) + { + node->get_state(root); + + QJsonObject parent_object; + get_state(parent_object, node->parent_node()); + + if(!parent_object.isEmpty()) + root.insert("parent", parent_object); + } + } + + void recorder::write(qreal value) + { + *_stream << value; + } + + void recorder::device_recieved_data() + { + if(_samples == _size) + return; + + _device->lock_ai(); + + for(int i = 0; i < (int)_ad.front()->size(); i++) + { + for(auto it : _ad) + { + write(it->at(i)); + } + + _samples++; + + + + if(_samples == _size) + break; + } + + _device->unlock_ai(); + + emit progress((qreal)_samples/_size); + + if(_samples == _size) + { + + finish_writing(); + _file->flush(); + emit finished(); + } + + } + } +} diff --git a/hw/gtl_hw_recorder.h b/hw/gtl_hw_recorder.h new file mode 100644 index 0000000..f6aab84 --- /dev/null +++ b/hw/gtl_hw_recorder.h @@ -0,0 +1,62 @@ +#ifndef RECORDER_H +#define RECORDER_H + +#include +#include + +#include "core/gtl_device.h" +#include "core/gtl_analog_data.h" + +#include "hw_global.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT recorder : public QObject + { + Q_OBJECT + public: + explicit recorder(const std::vector &ad, qreal time, QString comment, QString dir, QString file_name, QString extension, QObject *parent = nullptr); + virtual ~recorder(); + + void start(); + void stop(); + + bool is_success() const; + + qreal time() const; + QString path() const; + + private: + quint64 _samples; + quint64 _size; + + private: + void get_state(QJsonObject & root, gtl::data_model_node* node); + + protected: + std::vector _ad; + QDataStream* _stream; + QFile* _file; + gtl::device* _device; + QString _path; + + protected: + virtual void write(qreal value); + virtual void finish_writing(){}; + + + signals: + void finished(); + void progress(qreal); + + + private slots: + void device_recieved_data(); + + }; + } +} + +#endif // RECORDER_H diff --git a/hw/gtl_hw_recorder_gtr.cpp b/hw/gtl_hw_recorder_gtr.cpp new file mode 100644 index 0000000..88e4ce8 --- /dev/null +++ b/hw/gtl_hw_recorder_gtr.cpp @@ -0,0 +1,45 @@ +#include "gtl_hw_recorder_gtr.h" + +#include + +namespace gtl +{ + namespace hw + { + recorder_gtr::recorder_gtr(const std::vector &ad, qreal time, QString comment, QString dir, QString file, QObject *parent) + : recorder(ad, time, comment, dir, file, "gtr", parent) + { + if(!is_success()) + return; + + + QDomDocument doc; + QDomElement root = doc.createElement("gtr_header"); + root.setAttribute("device", _device->name()); + root.setAttribute("rate", _device->rate()); + root.setAttribute("time", time); + root.setAttribute("comment", comment); + doc.appendChild(root); + + QDomElement recorder_element = doc.createElement("recorder"); + root.appendChild(recorder_element); + + for(auto it : _ad) + { + QDomElement channel_element = doc.createElement("input"); + recorder_element.appendChild(channel_element); + + channel_element.setAttribute("name", it->name()); + channel_element.setAttribute("color", it->color()); + channel_element.setAttribute("reference", it->reference()); + } + + QByteArray header; + QTextStream stream(&header); + doc.save(stream, 0, QDomNode::EncodingFromTextStream); + + *_stream << header; + + } + } +} diff --git a/hw/gtl_hw_recorder_gtr.h b/hw/gtl_hw_recorder_gtr.h new file mode 100644 index 0000000..de5f82c --- /dev/null +++ b/hw/gtl_hw_recorder_gtr.h @@ -0,0 +1,21 @@ +#ifndef RECORDER_GTR_H +#define RECORDER_GTR_H + +#include "gtl_hw_recorder.h" + +#include "hw_global.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT recorder_gtr : public recorder + { + + public: + explicit recorder_gtr(const std::vector &ad, qreal time, QString comment, QString dir, QString file, QObject *parent = nullptr); + }; + } +} + +#endif // RECORDER_GTR_H diff --git a/hw/gtl_hw_recorder_wav.cpp b/hw/gtl_hw_recorder_wav.cpp new file mode 100644 index 0000000..d099f2d --- /dev/null +++ b/hw/gtl_hw_recorder_wav.cpp @@ -0,0 +1,118 @@ +#include "gtl_hw_recorder_wav.h" + +namespace gtl +{ + namespace hw + { + recorder_wav::recorder_wav(const std::vector &ad, qreal time, QString comment, QString dir, QString file, QObject *parent) + : recorder(ad, time, comment, dir, file, "wav", parent) + , _is_finished(false) + { + if(!is_success()) + return; + + *_stream << (quint32)0x46464952; // RIFF + *_stream << (quint32)0x00000000; + + *_stream << (quint32)0x45564157; //WAVE + + *_stream << (quint32)0x4b4e554a; //JUNK + *_stream << (quint32)28; + for (int i = 0; i < 7; i++) + *_stream << (quint32)0; + + *_stream << (quint32)0x20746d66; //fmt + *_stream << (quint32)16; + + + *_stream << (quint16)(3); + *_stream << (quint16)_ad.size(); + *_stream << (quint32)qRound(_device->rate()); + + *_stream << (quint32)(_device->rate()*_ad.size() * 4); + *_stream << (quint16)(_ad.size() * 4); + *_stream << (quint16)32; + + /* + *_stream << (quint16)(_coding == float_32_bit ? 3 : 1); + *_stream << (quint16)_signals.size(); + *_stream << (quint32)_rate; + + if (_coding == signed_16_bit_pcm) + { + *_stream << (quint32)(_rate*_signals.size() * 2); + *_stream << (quint16)(_signals.size() * 2); + *_stream << (quint16)16; + } + else if (_coding == signed_24_bit_pcm) + { + *_stream << (quint32)(_rate*_signals.size() * 3); + *_stream << (quint16)(_signals.size() * 3); + *_stream << (quint16)24; + } + else if (_coding == signed_32_bit_pcm | _coding == float_32_bit) + { + *_stream << (quint32)(_rate*_signals.size() * 4); + *_stream << (quint16)(_signals.size() * 4); + *_stream << (quint16)32; + } + else if (_coding == unsigned_8_bit_pcm) + { + *_stream << (quint32)(_rate*_signals.size() * 1); + *_stream << (quint16)(_signals.size() * 1); + *_stream << (quint16)8; + } + */ + + *_stream << (quint32)0x61746164; //data + *_stream << (quint32)0x00000000; + } + + recorder_wav::~recorder_wav() + { + if(!_is_finished) + finish_writing(); + } + + void recorder_wav::finish_writing() + { + if(_file == nullptr) + return; + + quint64 size_file = _file->size() - 8; + + if (size_file < 0xffffffff) + { + _file->seek(4); + quint32 size = (quint32)size_file; + + *_stream << size; + + _file->seek(40 + 36); + size -= 36 + 36; + *_stream << size; + } + else + { + _file->seek(4); + *_stream << (quint32) -1; + + _file->seek(40 + 36); + *_stream << (quint32)-1; + + _file->seek(12); + *_stream << 0x34367364; + + _file->seek(20); + *_stream << (quint32) (size_file & 0xffffffff); + *_stream << (quint32)((size_file >> 32) & 0xffffffff); + + size_file -= 36 + 36; + *_stream << (quint32)(size_file & 0xffffffff); + *_stream << (quint32)((size_file >> 32) & 0xffffffff); + } + + _is_finished = true; + } + } +} diff --git a/hw/gtl_hw_recorder_wav.h b/hw/gtl_hw_recorder_wav.h new file mode 100644 index 0000000..709cb69 --- /dev/null +++ b/hw/gtl_hw_recorder_wav.h @@ -0,0 +1,27 @@ +#ifndef RECORDER_WAV_H +#define RECORDER_WAV_H + +#include "gtl_hw_recorder.h" + +#include "hw_global.h" + +namespace gtl +{ + namespace hw + { + class HW_EXPORT recorder_wav : public recorder + { + public: + explicit recorder_wav(const std::vector &ad, qreal time, QString comment, QString dir, QString file, QObject *parent = nullptr); + virtual ~recorder_wav(); + + private: + bool _is_finished; + + protected: + virtual void finish_writing() override; + }; + } +} + +#endif // RECORDER_WAV_H diff --git a/hw/hw.pro b/hw/hw.pro new file mode 100644 index 0000000..c8fc45f --- /dev/null +++ b/hw/hw.pro @@ -0,0 +1,72 @@ +QT -= gui +QT += xml multimedia core5compat + +TEMPLATE = lib +DEFINES += HW_LIBRARY +TARGET = gtl_hw + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + gtl_analog_input.cpp \ + gtl_hw.cpp \ + gtl_hw_audio.cpp \ + gtl_hw_audio_input_device.cpp \ + gtl_hw_device.cpp \ + gtl_hw_generator.cpp \ + gtl_hw_generator_analog_input.cpp \ + gtl_hw_player.cpp \ + gtl_hw_player_analog_input.cpp \ + gtl_hw_player_file.cpp \ + gtl_hw_player_file_gtr.cpp \ + gtl_hw_player_file_wav.cpp \ + gtl_hw_recorder.cpp \ + gtl_hw_recorder_gtr.cpp \ + gtl_hw_recorder_wav.cpp + +HEADERS += \ + gtl_analog_input.h \ + gtl_hardware_interface.h \ + gtl_hw.h \ + gtl_hw_audio.h \ + gtl_hw_audio_input_device.h \ + gtl_hw_device.h \ + gtl_hw_generator.h \ + gtl_hw_generator_analog_input.h \ + gtl_hw_player.h \ + gtl_hw_player_analog_input.h \ + gtl_hw_player_file.h \ + gtl_hw_player_file_gtr.h \ + gtl_hw_player_file_wav.h \ + gtl_hw_recorder.h \ + gtl_hw_recorder_gtr.h \ + gtl_hw_recorder_wav.h \ + hw_global.h + +TRANSLATIONS += \ + hw_ru_RU.ts + +# Default rules for deployment. +unix { + target.path = /usr/lib +} +!isEmpty(target.path): INSTALLS += target + +SUBDIRS += \ + gtl_device/gtl_device.pro + +win32:CONFIG(release, debug|release): DESTDIR = ../.output/release +else:win32:CONFIG(debug, debug|release): DESTDIR = ../.output/debug +else:unix: DESTDIR = ../../.ouput + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_core +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_core + +INCLUDEPATH += $$PWD/../ +DEPENDPATH += $$PWD/../ diff --git a/hw/hw_global.h b/hw/hw_global.h new file mode 100644 index 0000000..c222310 --- /dev/null +++ b/hw/hw_global.h @@ -0,0 +1,12 @@ +#ifndef HW_GLOBAL_H +#define HW_GLOBAL_H + +#include + +#if defined(HW_LIBRARY) +# define HW_EXPORT Q_DECL_EXPORT +#else +# define HW_EXPORT Q_DECL_IMPORT +#endif + +#endif // HW_GLOBAL_H diff --git a/hw/hw_ru_RU.ts b/hw/hw_ru_RU.ts new file mode 100644 index 0000000..ccd56d3 --- /dev/null +++ b/hw/hw_ru_RU.ts @@ -0,0 +1,104 @@ + + + + + gtl::hw::audio + + + unknown device + неизвестное устройство + + + + audio + аудио + + + + gtl::hw::device + + + intput + вход + + + + gtl::hw::generator + + + generator + генератор + + + + intput + вход + + + + gtl::hw::player + + + + + player + плеер + + + + gtl::hw::player_file_gtr + + + device: + устройство: + + + + rate: + + + + + hz + + + + + time: + + + + + sec + + + + + gtl::hw::player_file_wav + + + rate: + + + + + hz + + + + + time: + + + + + sec + + + + + bits per sample: + + + + diff --git a/hw/ni/.gitignore b/hw/ni/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/hw/ni/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/hw/ni/device_ni.cpp b/hw/ni/device_ni.cpp new file mode 100644 index 0000000..4b74a87 --- /dev/null +++ b/hw/ni/device_ni.cpp @@ -0,0 +1,312 @@ +#include "device_ni.h" + +#include "core/gtl_logger.h" + +device_ni::device_ni(QObject *parent) : device(parent), _is_full_restarting(true), _max_amplitude(0), _name("NI device"), _is_support_iepe(false) +{ + _device->set_name("ni"); + + gtl::logger::info("ni_device", "created;"); +} + +device_ni::~device_ni() +{ + stop(); + gtl::logger::info("ni_device", "distroyed;"); +} + +QString device_ni::type() const +{ + return "ni"; +} + +bool device_ni::start(QString id, qreal rate) +{ + + gtl::logger::info("ni_device", "starting device. id: " + id + "; rate: " + QString::number(rate)); + + emit status_changed(INIT_IN_PROGRESS); + + _task = 0; + + int err = 0; + + _id = id; + _rate = rate; + + _count_ai = 0; + + char buffer[256]; + + err = DAQmxGetDevProductType(_id.toStdString().c_str(), buffer, 256); + + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "init device" << "DAQmxGetDevProductType error: " + get_error_message(err); + return false; + } + + _name = QString(buffer); + emit name_changed(); + + gtl::logger::info("ni_device", "device name:" + _name + ";"); + + err = DAQmxGetDevAIPhysicalChans(_id.toStdString().c_str(), buffer, 256); + + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "init device" << "DAQmxGetDevAOPhysicalChans error: " + get_error_message(err); + return false; + } + + gtl::logger::info("ni_device", "analog input channels: " + QString(buffer) + ";"); + + QStringList channels = QString(buffer).split(","); + + + int32 idata[256]; + std::fill(&idata[0], &idata[256], 0); + err = DAQmxGetDevAISupportedMeasTypes(_id.toStdString().c_str(), idata, 256); + + _is_support_iepe = std::find(&idata[0], &idata[256], 10356); + + gtl::logger::info("ni_device", "is iepe support: " + QString::number(_is_support_iepe) + ";"); + + _count_ai = channels.size(); + + set_ai(); + + QJsonArray array_config = _config.toArray(); + for(int i = 0; i < qMin(array_config.size(), count_ai()); i++) + { + ai(i)->blockSignals(true); + + ai(i)->set_iepe(array_config[i].toObject().value("iepe").toBool()); + ai(i)->set_coupling(array_config[i].toObject().value("ac").toBool()); + + ai(i)->blockSignals(false); + } + + /* + if (_is_full_restarting) + { + _iepe.resize(_channels); + std::fill(_iepe.begin(), _iepe.end(), false); + + emit channels_changed(); + } + */ + + + + + float64 range[2]; + err = DAQmxGetDevAIVoltageRngs(_id.toStdString().c_str(), range, 2); + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "init device" << "DAQmxGetDevAIVoltageRngs error: " + get_error_message(err); + return false; + } + + gtl::logger::info("ni_device", "voltage range: " + QString::number(range[0]) + " - " + QString::number(range[1])+ ";"); + + _max_amplitude = range[1]; + + float64 rate_max; + err = DAQmxGetDevAIMaxMultiChanRate(_id.toStdString().c_str(), &rate_max); + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "init device" << "DAQmxGetSampClkMaxRate error. code: " + get_error_message(err); + return false; + } + + gtl::logger::info("ni_device", "max rate: " + QString::number(rate_max) + ";"); + + if (rate > rate_max) + _rate = rate_max; + + + + err = DAQmxCreateTask("", &_task); + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "start acquisition" << "DAQmxCreateTask error: " + get_error_message(err); + return false; + } + + + + + + + int samples = _rate * 0.1; + + for (int i = 0; i < _count_ai; i++) + { + channels[i] = channels[i].trimmed(); + + if (ai(i)->is_iepe()) + { + err = DAQmxCreateAIAccelChan(_task, channels[i].toStdString().c_str(), "", DAQmx_Val_Cfg_Default, range[0], range[1], DAQmx_Val_AccelUnit_g, 1, DAQmx_Val_VoltsPerG, DAQmx_Val_Internal, .002, NULL); + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "start acquisition" << "DAQmxCreateAIVoltageChan error: " + get_error_message(err); + return false; + } + } + else + { + err = DAQmxCreateAIVoltageChan(_task, channels[i].toStdString().c_str(), "", DAQmx_Val_Cfg_Default, range[0], range[1], DAQmx_Val_Volts, NULL); + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "start acquisition" << "DAQmxCreateAIVoltageChan error: " + get_error_message(err); + return false; + } + } + + + err = DAQmxSetAICoupling(_task, channels[i].toStdString().c_str(), ((ai(i)->is_coupling() | ai(i)->is_iepe()) ? DAQmx_Val_AC : DAQmx_Val_DC)); + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "start acquisition" << "DAQmxSetAICoupling error: " + get_error_message(err); + return false; + } + } + + err = DAQmxCfgSampClkTiming(_task, "", _rate, DAQmx_Val_Rising, DAQmx_Val_ContSamps, samples); + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "start acquisition" << "DAQmxCfgSampClkTiming error. code: " + get_error_message(err); + return false; + } + + err = DAQmxGetSampClkRate(_task, &_rate); + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "start acquisition" << "DAQmxGetSampClkRate error: " + get_error_message(err); + return false; + } + + qDebug() << "rate" << _rate; + + _buffer_read.resize(samples * _count_ai); + + err = DAQmxStartTask(_task); + if (err != 0) + { + emit status_changed(INIT_FAILED); + qDebug() << "start acquisition" << "DAQmxStartTask error. code: " + QString::number(err); + return false; + } + + _device->set_name(_name + "[" + id + "]"); + _device->set_id(_id); + _device->set_rate(_rate); + + QThread::start(QThread::HighestPriority); + + emit status_changed(OK); + + return true; +} + +bool device_ni::stop() +{ + if (_is_full_restarting) + { + + } + + gtl::logger::info("ni_device", "stopping...;"); + + int err = DAQmxTaskControl(_task, DAQmx_Val_Task_Abort); + _is_running = false; + wait(); + + _task = 0; + return true; +} + +void device_ni::set_config(const QJsonValue &config) +{ + gtl::hw::device::set_config(config); + + if(isRunning()) + restart(); +} + +void device_ni::run() +{ + gtl::logger::info("ni_device", "started. rate: " + QString::number(_rate) + ";"); + + _is_running = true; + + int32 read = 0; + + do + { + int err = DAQmxReadAnalogF64(_task, (int)_buffer_read.size() / count_ai(), -1, DAQmx_Val_GroupByScanNumber, &_buffer_read[0], (int)_buffer_read.size(), &read, NULL); + if (err != 0) + { + if (err != -88710 && err != -88709) + { + qDebug() << "start acquisition" << "DAQmxReadAnalogF64 error: " + get_error_message(err); + emit status_changed(FAILED); + break; + } + } +// gtl::logger::info("ni_device", "sendign data;"); + + send_data(); + } + while (_is_running); + +// emit status_changed(undef); + + DAQmxStopTask(_task); + DAQmxClearTask(_task); + + gtl::logger::info("ni_device", "stopped;"); + +} + +void device_ni::send_data() +{ + + _buffer_send.clear(); + + set_ai_data(&_buffer_read[0], (int)_buffer_read.size()); + + QDateTime time_current = QDateTime::currentDateTime(); + + _time_send = time_current; + + emit received_data(); +} + +QString device_ni::get_error_message(int32 code) +{ + char buffer[2048]; + DAQmxGetErrorString(code, buffer, 2048); + return QString(buffer); +} + +void device_ni::ai_iepe_changed() +{ + restart(); +} + +void device_ni::ai_coupling_changed() +{ + restart(); +} diff --git a/hw/ni/device_ni.h b/hw/ni/device_ni.h new file mode 100644 index 0000000..75bb3a7 --- /dev/null +++ b/hw/ni/device_ni.h @@ -0,0 +1,65 @@ +#ifndef DEVICE_NI_H +#define DEVICE_NI_H + +#include +#include + +#include "hw/gtl_hw_device.h" + +#include + +#include + +class device_ni : public gtl::hw::device +{ + Q_OBJECT + +public: + device_ni(QObject *parent); + ~device_ni(); + + virtual QString type() const override; + + virtual bool start(QString id, qreal rate); + virtual bool stop(); + + virtual qreal max_amplitude() const { return _max_amplitude; } + + virtual void set_config(const QJsonValue& config) override; + +private: + QString _name; + + TaskHandle _task; + + std::vector _buffer_send; + std::vector _buffer_read; + + QDateTime _time_send; + + bool _is_full_restarting; + + qreal _max_amplitude; + + bool _is_support_iepe; + + bool _is_running; + + +private: + virtual void run(); + + void send_data(); + + QString get_error_message(int32 code); + + + +private slots: + virtual void ai_iepe_changed(); + virtual void ai_coupling_changed(); + + +}; + +#endif // NI9234_H diff --git a/hw/ni/ni.cpp b/hw/ni/ni.cpp new file mode 100644 index 0000000..d16181c --- /dev/null +++ b/hw/ni/ni.cpp @@ -0,0 +1,13 @@ +#include "ni.h" + +gtl::hw::device *ni::create_device(QObject *parent) +{ + return new device_ni(parent); +} + +ni::ni(QObject *parent) + : QObject(parent) +{ +} + + diff --git a/hw/ni/ni.h b/hw/ni/ni.h new file mode 100644 index 0000000..fb8418d --- /dev/null +++ b/hw/ni/ni.h @@ -0,0 +1,26 @@ +#ifndef NI_H +#define NI_H + +#include + +#include "../gtl_hardware_interface.h" +#include "device_ni.h" + +class ni : public QObject, public gtl::hardware_interface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "gtlab.hardware" FILE "ni.json") + Q_INTERFACES(gtl::hardware_interface) + + virtual QString id() const {return "ni";} + + virtual gtl::hw::device* create_device(QObject* parent = NULL); + +public: + explicit ni(QObject *parent = nullptr); + +private: + +}; + +#endif // NI_H diff --git a/hw/ni/ni.json b/hw/ni/ni.json new file mode 100644 index 0000000..1e81138 --- /dev/null +++ b/hw/ni/ni.json @@ -0,0 +1,3 @@ +{ + "Keys" : [ ] +} diff --git a/hw/ni/ni.pro b/hw/ni/ni.pro new file mode 100644 index 0000000..e2145c6 --- /dev/null +++ b/hw/ni/ni.pro @@ -0,0 +1,50 @@ +QT += gui xml + +TEMPLATE = lib +CONFIG += plugin + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + device_ni.cpp \ + ni.cpp + +HEADERS += \ + device_ni.h \ + ni.h + +TRANSLATIONS += \ + ni_ru_RU.ts + +DISTFILES += ni.json + +win32:CONFIG(release, debug|release): DESTDIR = ../../.output/.hwplugins/release +else:win32:CONFIG(debug, debug|release): DESTDIR = ../../.output/.hwplugins/debug +else:unix: DESTDIR = ../../.ouput/.hwplugins + +# Default rules for deployment. +unix { + target.path = $$[QT_INSTALL_PLUGINS]/generic +} +!isEmpty(target.path): INSTALLS += target + +unix|win32: LIBS += -L'C:/Program Files (x86)/National Instruments/Shared/ExternalCompilerSupport/C/lib64/msvc/' -lNIDAQmx + +INCLUDEPATH += 'C:/Program Files (x86)/National Instruments/Shared/ExternalCompilerSupport/C/include' +DEPENDPATH += 'C:/Program Files (x86)/National Instruments/Shared/ExternalCompilerSupport/C/include' + +INCLUDEPATH += $$PWD/../.. +DEPENDPATH += $$PWD/../.. + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_hw +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_hw +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_hw + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_core +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_core + diff --git a/hw/ni/ni_ru_RU.ts b/hw/ni/ni_ru_RU.ts new file mode 100644 index 0000000..743f57f --- /dev/null +++ b/hw/ni/ni_ru_RU.ts @@ -0,0 +1,3 @@ + + + diff --git a/hw/test_hw/.gitignore b/hw/test_hw/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/hw/test_hw/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/hw/test_hw/main.cpp b/hw/test_hw/main.cpp new file mode 100644 index 0000000..62bbc6c --- /dev/null +++ b/hw/test_hw/main.cpp @@ -0,0 +1,82 @@ +#include +#include + +#include + +#include "hw/gtl_hw.h" +#include "hw/gtl_hw_device.h" +#include "hw/gtl_hw_generator.h" +//#include "math/gtl_math_sum.h" +//#include "math/gtl_math_rms.h" +//#include "math/gtl_math_spec.h" +//#include "math/gtl_math_delta_phase_spec.h" +#include "math/gtl_math_max.h" + + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + //создаем объект hardware. параметр - путь к папке с плагинами. + gtl::hw::hw hw("../../.output/.hwplugins/debug"); + + //получаем список доступных устройств. + QStringList devices = hw.devices(); + + qDebug() << devices; + + //создаем устройство. параметр имя устройства. + //gtl::hw::device* d = hw.create_device("ni"); + gtl::hw::device* d = new gtl::hw::generator(); + + if(d == NULL) + { + qDebug() << "error creating device"; + } + else + { +// gtl::math::sum *sum; +// gtl::math::rms *rms; +// gtl::math::spec *spec; +// gtl::math::delta_phase_spec *delta_phase_spec; + gtl::math::max *max; + + //задаем параметры каналов. +// d->ai(0)->set_iepe(true); +// d->ai(0)->set_sensitivity(0.001); + gtl::hw::generator_analog_input* input = static_cast(d->ai(0)); +// input->set_phase(87); + input->set_freq(100); + input->set_ampl(5); +// input = static_cast(d->ai(1)); +// input->set_freq(100); + +// input = static_cast(d->ai(1)); + + //запускаем устройство. первый параметр - id, второй - частота дискретизации + qDebug() << d->start("Dev1", 51200); + + //в библиотеке будет класс объектов, предназначеных для вычисления скалярных величин, характеризующих сигнал. скз, частота и т.п. + //это объект, вычисляющий сумму поступающих аналоговых данных. для примера. + //параметр - канал аналоговых данных. в данном случае входной канал устройства. +// sum = new gtl::math::sum(d->ai(3)); +// rms = new gtl::math::rms(d->ai(3)); +// spec = new gtl::math::spec(gtl::math::spec::types::phase, d->ai(3)); +// delta_phase_spec = new gtl::math::delta_phase_spec(d->ai(0), d->ai(1)); +// delta_phase_spec->set_frequency(100); +// delta_phase_spec->set_resolution(10); + max = new gtl::math::max(d->ai(0)); + +// spec->set_frequency(2560); + + //слот получения новых данных. +// QObject::connect(d, >l::hw::device::received_data, [=](){qDebug() << "data recieved sum:\t" << sum->value();}); +// QObject::connect(d, >l::hw::device::received_data, [=](){qDebug() << "data recieved rms:\t" << rms->value();}); +// QObject::connect(spec, >l::math::spec::max_harm_idx, [=](int val){qDebug() << "idx spec:\t" << val;}); +// QObject::connect(delta_phase_spec, >l::math::delta_phase_spec::value_changed, [=](){qDebug() << "delta phase:\t" << delta_phase_spec->value();}); + QObject::connect(d, >l::hw::device::received_data, [=](){qDebug() << "data recieved max:\t" << max->value();}); + } + + return a.exec(); + +} diff --git a/hw/test_hw/test_hw.pro b/hw/test_hw/test_hw.pro new file mode 100644 index 0000000..b2d0ec2 --- /dev/null +++ b/hw/test_hw/test_hw.pro @@ -0,0 +1,43 @@ +QT += core xml +QT -= gui + +CONFIG += c++11 console +CONFIG -= app_bundle + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +INCLUDEPATH += $$PWD/../.. +DEPENDPATH += $$PWD/../.. + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_core +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_core + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_math +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_math +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_core + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../.output/release/ -lgtl_hw +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../.output/debug/ -lgtl_hw +else:unix: LIBS += -L$$PWD/../../.output/ -lgtl_hw + +INCLUDEPATH += $$(FFTWPATH) + +LIBS += -L$$(FFTWPATH) -llibfftw3-3 \ + -L$$(FFTWPATH) -llibfftw3f-3 \ + -L$$(FFTWPATH) -llibfftw3l-3 \ + +INCLUDEPATH += $$(DSPFILTERSPATH)/include +DEPENDPATH += $$(DSPFILTERSPATH)/include diff --git a/math/.gitignore b/math/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/math/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/math/gtl_math.cpp b/math/gtl_math.cpp new file mode 100644 index 0000000..f10b3b3 --- /dev/null +++ b/math/gtl_math.cpp @@ -0,0 +1,78 @@ +#include "gtl_math.h" +#include +#include +#include + +mathFunctions::mathFunctions() +{ +} + +qreal mathFunctions::squared_sum(const std::vector::iterator &begin, const std::vector::iterator &end) +{ + qreal sum = 0; + for(auto it = begin; it != end; ++it) { + sum += qPow(*it, 2); + } + return sum; +} + +qreal mathFunctions::rms(const std::vector::iterator &begin, const std::vector::iterator &end) +{ + return qSqrt(mathFunctions::squared_sum(begin, end)/std::distance(begin,end)); +} + +qreal mathFunctions::max(const std::vector::iterator &begin, const std::vector::iterator &end) +{ + return *std::max_element(begin, end); +} + +qreal mathFunctions::min(const std::vector::iterator &begin, const std::vector::iterator &end) +{ + return *std::min_element(begin, end); +} + +qreal mathFunctions::mean(const std::vector::iterator &begin, const std::vector::iterator &end) +{ + return std::accumulate(begin, end, 0.)/std::distance(begin, end); +} + +qreal mathFunctions::kurt(const std::vector::iterator &begin, const std::vector::iterator &end) +{ + qreal value = 0; + int n = std::distance(begin, end); + if(n > 3) + { + qreal mean = mathFunctions::mean(begin, end); + qreal variance = 0.; + for(auto it = begin; it != end; ++it) + variance += pow((*it - mean), 2); + variance /= n; + + qreal sigma = 0; + for(auto it = begin; it != end; ++it) + sigma += pow((*it - mean), 4); + + sigma /= n; + + qreal k1 = (qreal)(n*n - 1) / (qreal)((n-2)*(n-3)); + qreal k2 = (qreal)((6. / (n + 1.)) - 3.); + + value = k1*(sigma/pow(variance,2) + k2); + + } + return value; +} + +qreal mathFunctions::var(const std::vector::iterator &begin, const std::vector::iterator &end) +{ + int n = std::distance(begin, end); + + qreal mean = std::accumulate(begin, end, 0.)/n; + + qreal v = 0.; + for(auto it = begin; it != end; ++it) + v += qPow((*it - mean), 2); + v /= n-1; + + return v; +} diff --git a/math/gtl_math.h b/math/gtl_math.h new file mode 100644 index 0000000..c17caba --- /dev/null +++ b/math/gtl_math.h @@ -0,0 +1,21 @@ +#ifndef GTL_MATH_H +#define GTL_MATH_H + +#include "math_global.h" +#include + +class MATH_EXPORT mathFunctions +{ +public: + mathFunctions(); + + static qreal squared_sum(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal rms(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal max(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal min(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal mean(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal kurt(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal var(const std::vector::iterator& begin, const std::vector::iterator& end); +}; + +#endif // GTL_MATH_H diff --git a/math/gtl_math_ampl.cpp b/math/gtl_math_ampl.cpp new file mode 100644 index 0000000..65b7d80 --- /dev/null +++ b/math/gtl_math_ampl.cpp @@ -0,0 +1,63 @@ +#include "gtl_math_ampl.h" + + + +namespace gtl +{ + namespace math + { + ampl::ampl(gtl::analog_data *data) + : analog_value(data) + , _sum(0) + , _dc(0) + { + _name = "amplitude"; + } + + void ampl::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _sum -= std::accumulate(begin, end, 0.0); + } + + void ampl::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _sum += std::accumulate(begin, end, 0.0); + _dc = _sum/_data.size(); + + auto [min, max] = std::minmax_element(begin, end); + auto ampl_iter = abs(*max - _dc) > abs(*min - _dc) ? max : min; + + std::set::iterator iter_remove_begin = std::lower_bound(_ampl_indexes.begin(), _ampl_indexes.end(), std::distance(_data.begin(), begin)); + std::set::iterator iter_remove_end = std::lower_bound(_ampl_indexes.begin(), _ampl_indexes.end(), std::distance(_data.begin(), end)); + _ampl_indexes.erase(iter_remove_begin, iter_remove_end); + + _ampl_indexes.insert(std::distance(_data.begin(), ampl_iter)); + } + + void ampl::data_changed() + { + + analog_value::data_changed(); + + if(_ampl_indexes.empty()) + { + _value = 0; + return; + } + + qreal ampl = abs(_data[*_ampl_indexes.begin()] - _dc); + + for(int it: _ampl_indexes) + { + qreal ampl_current = abs(_data[it] - _dc); + if(ampl_current > ampl) + ampl = ampl_current; + } + + _value = ampl; + + emit value_changed(); + + } + } +} diff --git a/math/gtl_math_ampl.h b/math/gtl_math_ampl.h new file mode 100644 index 0000000..67c1207 --- /dev/null +++ b/math/gtl_math_ampl.h @@ -0,0 +1,48 @@ +#ifndef AMPL +#define AMPL + +#include + +#include + +#include "gtl_math_analog_value.h" + +#include "math_global.h" + +#include + +namespace gtl +{ + namespace math + { + class MATH_EXPORT ampl : public analog_value + { + Q_OBJECT + + + public: + ampl(gtl::analog_data *data); + + private: + qreal _sum; + std::set _ampl_indexes; + qreal _dc; + + protected: + virtual void before_copying_data(std::vector::iterator begin, std::vector::iterator end); + virtual void after_copying_data(std::vector::iterator begin, std::vector::iterator end); + + + + signals: + + + + protected slots: + virtual void data_changed(); + + }; + } +} + +#endif // AMPL diff --git a/math/gtl_math_analog_value.cpp b/math/gtl_math_analog_value.cpp new file mode 100644 index 0000000..6795ee6 --- /dev/null +++ b/math/gtl_math_analog_value.cpp @@ -0,0 +1,152 @@ +#include "gtl_math_analog_value.h" + +#include +#include + +namespace gtl +{ + namespace math + { + analog_value::analog_value(gtl::analog_data *data) + : QObject{/*data*/NULL} + , _avg_cnt(1) + , _value(0) + , _ad(data) + , _is_acq(true) + { + set_time(0.1); + connect(_ad, >l::analog_data::data_changed, this, &analog_value::data_changed); + } + + analog_value::~analog_value() + { + + } + + QString analog_value::name() const + { + return _name; + } + + qreal analog_value::value() const + { + return _value; + } + + qreal analog_value::time() const + { + return _time; + } + + quint32 analog_value::avg_cnt() const + { + return _avg_cnt; + } + + qreal analog_value::avg_value() const + { + if(_values.empty()) + return 0; + + return std::accumulate(_values.begin(), _values.end(), 0.0)/_values.size(); + } + + QList analog_value::values() const + { + return _values; + } + + void analog_value::set_time(qreal value) + { + if(value != _time) + { + _ad->lock_device(); + + _time = value; + + _data.resize(qRound(_time * _ad->get_rate())); + std::fill(_data.begin(), _data.end(), 0.0); + _data_ptr = 0; + _value = 0; + + _values.clear(); + + _ad->unlock_device(); + + emit time_changed(); + + + } + } + + void analog_value::set_avg_cnt(quint32 value) + { + if(value != _avg_cnt) + { + _avg_cnt = value; + + emit avg_cnt_changed(); + } + } + + void analog_value::stop_acq() + { + _is_acq = false; + } + + void analog_value::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + + } + + void analog_value::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + + } + + void analog_value::set_name(QString value) + { + if(_name != value) + { + _name = value; + emit name_changed(); + } + } + + void analog_value::data_changed() + { + if(!_is_acq) + return; + + _ad->lock_device(); + + int idx = /*std::max(0, (int)_ad->size() - (int)_data.size())*/0; + while(idx != _ad->size()) + { + int size = std::min((int)_ad->size() - idx, (int)_data.size() - _data_ptr); + + std::vector::iterator begin = _data.begin() + _data_ptr; + std::vector::iterator end = begin + size; + + before_copying_data(begin, end); + + std::copy(_ad->begin() + idx, _ad->begin() + idx + size, begin); + + after_copying_data(begin, end); + + if(end == _data.end()) + { + _values.push_back(value()); + while(_values.size() > _avg_cnt) + _values.pop_front(); + } + + + _data_ptr = (_data_ptr + size) % _data.size(); + idx += size; + } + + _ad->unlock_device(); + } + } +} diff --git a/math/gtl_math_analog_value.h b/math/gtl_math_analog_value.h new file mode 100644 index 0000000..6c9d636 --- /dev/null +++ b/math/gtl_math_analog_value.h @@ -0,0 +1,79 @@ +#ifndef VALUE_H +#define VALUE_H + +#include + +#include + +#include "core/gtl_analog_data.h" + +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT analog_value : public QObject + { + Q_OBJECT + Q_PROPERTY(QString name READ name WRITE set_name NOTIFY name_changed) + Q_PROPERTY(qreal time READ time WRITE set_time NOTIFY time_changed) + Q_PROPERTY(qreal value READ value NOTIFY value_changed) + Q_PROPERTY(qreal avg_value READ avg_value NOTIFY avg_value_changed) + Q_PROPERTY(quint32 avg_cnt READ avg_cnt WRITE set_avg_cnt NOTIFY avg_cnt_changed) + Q_PROPERTY(QList values READ values CONSTANT) + + public: + analog_value(gtl::analog_data *data); + virtual ~analog_value(); + + QString name() const; + qreal value() const; + qreal time() const; + quint32 avg_cnt() const; + + qreal avg_value() const; + + QList values() const; + + private: + qreal _time; + + protected: + virtual void before_copying_data(std::vector::iterator begin, std::vector::iterator end); + virtual void after_copying_data(std::vector::iterator begin, std::vector::iterator end); + + protected: + QString _name; + qreal _value; + gtl::analog_data* _ad; + + std::vector _data; + int _data_ptr; + + QList _values; + quint32 _avg_cnt; + + bool _is_acq; + + signals: + void name_changed(); + void value_changed(); + void avg_value_changed(); + void time_changed(); + void avg_cnt_changed(); + + public slots: + void set_name(QString value); + virtual void set_time(qreal value); + void set_avg_cnt(quint32 value); + void stop_acq(); + + protected slots: + virtual void data_changed(); + + }; + } +} + +#endif // VALUE_H diff --git a/math/gtl_math_apfc.cpp b/math/gtl_math_apfc.cpp new file mode 100644 index 0000000..6feb570 --- /dev/null +++ b/math/gtl_math_apfc.cpp @@ -0,0 +1,425 @@ +#include "gtl_math_apfc.h" + +namespace gtl { + namespace math { + + apfc::apfc(gtl::analog_data *ref, gtl::analog_data *data) : + _is_acq(true), + _av(1), + _afc(afcs::magnitude), + _pfc(pfcs::deg), + _av_cnt(0), + _av_filled(false), + _threshold(50), + _ref(nullptr), + _data(nullptr) + { + _device = static_cast(data->root()); + + if(_device) + _device->lock_ai(); + + _ref = new gtl::math::fft(ref); + _data = new gtl::math::fft(data); + + connect(_device, >l::device::recieved_data, this, >l::math::apfc::device_recieved_data); + connect(_data, >l::math::fft::initialized, this, &apfc::init); + connect(_data, >l::math::fft::frequency_changed, this, &apfc::frequency_changed); + connect(_data, >l::math::fft::resolution_changed, this, &apfc::resolution_changed); + connect(_data, >l::math::fft::window_changed, this, &apfc::window_changed); + connect(_data, >l::math::fft::lines_changed, this, &apfc::lines_changed); + connect(_data, >l::math::fft::overlap_changed, this, &apfc::overlap_changed); + + init(); + + if(_device) + _device->unlock_ai(); + } + + apfc::~apfc() + { + emit deleting(); + + cleanup(); + if(_ref) + delete _ref; + if(_data) + delete _data; + } + + void apfc::set_ref(analog_data *ref) + { + if(_device) + _device->lock_ai(); + + if(_ref) + delete _ref; + + _ref = new gtl::math::fft(ref); + + _ref->set_frequency(_data->frequency()); + _ref->set_resolution(_data->resolution()); + _ref->set_overlap(_data->overlap()); + _ref->set_window(_data->window()); + + init(); + + if(_device) + _device->unlock_ai(); + } + + void apfc::set_data(analog_data *data) + { + _device = static_cast(data->root()); + + if(_device) + _device->lock_ai(); + + disconnect(_device, >l::device::recieved_data, this, >l::math::apfc::device_recieved_data); + disconnect(_data, >l::math::fft::initialized, this, &apfc::init); + disconnect(_data, >l::math::fft::frequency_changed, this, &apfc::frequency_changed); + disconnect(_data, >l::math::fft::resolution_changed, this, &apfc::resolution_changed); + disconnect(_data, >l::math::fft::window_changed, this, &apfc::window_changed); + disconnect(_data, >l::math::fft::lines_changed, this, &apfc::lines_changed); + disconnect(_data, >l::math::fft::overlap_changed, this, &apfc::overlap_changed); + + if(_data) + delete _data; + + _data = new gtl::math::fft(data); + + _data->set_frequency(_ref->frequency()); + _data->set_resolution(_ref->resolution()); + _data->set_overlap(_ref->overlap()); + _data->set_window(_ref->window()); + + connect(_device, >l::device::recieved_data, this, >l::math::apfc::device_recieved_data); + connect(_data, >l::math::fft::initialized, this, >l::math::apfc::init); + connect(_data, >l::math::fft::frequency_changed, this, >l::math::apfc::frequency_changed); + connect(_data, >l::math::fft::resolution_changed, this, >l::math::apfc::resolution_changed); + connect(_data, >l::math::fft::window_changed, this, >l::math::apfc::window_changed); + connect(_data, >l::math::fft::lines_changed, this, >l::math::apfc::lines_changed); + connect(_data, >l::math::fft::overlap_changed, this, >l::math::apfc::overlap_changed); + + init(); + + if(_device) + _device->unlock_ai(); + } + + qreal apfc::frequency() const + { + return _data->frequency(); + } + + void apfc::set_frequency(qreal value) + { + _ref->set_frequency(value); + _data->set_frequency(value); + + init(); + } + + qreal apfc::resolution() const + { + return _data->resolution(); + } + + void apfc::set_resolution(qreal value) + { + _ref->set_resolution(value); + _data->set_resolution(value); + + init(); + } + + apfc::windows apfc::window() const + { + return (apfc::windows) _data->window(); + } + + void apfc::set_window(windows value) + { + _ref->set_window((fft::windows) value); + _data->set_window((fft::windows) value); + } + + int apfc::lines() const + { + return _data->lines(); + } + + void apfc::set_lines(int value) + { + _ref->set_lines(value); + _data->set_lines(value); + + init(); + } + + uint apfc::average() const + { + return _av; + } + + void apfc::set_average(int value) + { + if( value != _av && value > 0) + { + _av = value; + init(); + emit average_changed(); + } + } + + qreal apfc::overlap() const + { + return _data->overlap(); + } + + void apfc::set_overlap(int value) + { + _ref->set_overlap(value); + _data->set_overlap(value); + } + + qreal apfc::acq_time() const + { + int nl = _data->lines(); + qreal f = _data->frequency(); + qreal ol = _data->overlap(); + + return (nl + 1)/f * ((_av - 1)* (1.0 - ol/100.0) + 1); + } + + apfc::afcs apfc::afc() const + { + return _afc; + } + + void apfc::set_afc(afcs value) + { + if( value != _afc ) + { + _afc = value; + emit afc_changed(); + } + } + + apfc::pfcs apfc::pfc() const + { + return _pfc; + } + + void apfc::set_pfc(pfcs value) + { + if( value != _pfc ) + { + _pfc = value; + emit pfc_changed(); + } + } + + qreal apfc::threshold() const + { + return _threshold; + } + + void apfc::set_threshold(qreal newThreshold) + { + if (qFuzzyCompare(_threshold, newThreshold)) + return; + _threshold = newThreshold; + emit threshold_changed(); + } + + void apfc::calculate() + { + qreal ref_a = 0; + qreal data_a = 0; + qreal ref_p = 0; + qreal data_p = 0; + + qreal ref_akf = 0; + qreal data_akf = 0; + qreal vkf = 0; + + vector ref_a_arr(_size_fft_out); + qreal ref_a_max = 0; + for(int i = 0; i < _size_fft_out; i++) + { + ref_a_arr[i] = abs(_ref->at(i))/_size_fft_calc; + if(ref_a_arr[i] > ref_a_max) + ref_a_max = ref_a_arr[i]; + } + + for(int i = 0; i < _size_fft_out; i++) + { + ref_a = ref_a_arr[i];//abs(_ref->at(i))/_size_fft_calc; + data_a = abs(_data->at(i))/_size_fft_calc; + + if(ref_a >= ref_a_max*(_threshold/100.)) + { + if(ref_a > 1e-5 && data_a > 1e-5) + { + qreal calc_value = 0; + ////AMPL//// + if(_afc == magnitude) + calc_value = data_a/ref_a; + else if(_afc == db) + calc_value= 20*log(data_a/ref_a); + else if(_afc == coherence) + { + vkf = pow(abs(_ref->at(i)*std::conj(_data->at(i)))/_size_fft_calc, 2); // Модуль ВКФ в квадрате + ref_akf = abs(_ref->at(i)*std::conj(_ref->at(i)))/_size_fft_calc; // АКФ ref + data_akf = abs(_data->at(i)*std::conj(_data->at(i)))/_size_fft_calc; // АКФ data + + calc_value = vkf/(ref_akf*data_akf); + } + + (_calculated_it + i)->first = roundf(calc_value*1000)/1000; + + ////PHASE//// + ref_p = arg(_ref->at(i)); + data_p = arg(_data->at(i)); + qreal delta_phase = data_p - ref_p; + if(delta_phase < 0) + delta_phase += 2*M_PI; + + if(_pfc == deg) + calc_value = 180.*delta_phase/M_PI; + else if(_pfc == rad) + calc_value = delta_phase; + + (_calculated_it + i)->second = roundf(calc_value*1000)/1000; + } + + } + else + { + (_calculated_it + i)->first = 0; + (_calculated_it + i)->second = 0; + } + } + } + + void apfc::before_updating() + { + + } + + void apfc::after_updating() + { + + } + + void apfc::device_recieved_data() + { + if(_ref->samples() != _data->samples()) + return; + + if(_device) + _device->lock_ai(); + + before_updating(); + + // Вычисление , усреднение + int avdiv = _av_filled ? _av : _av_cnt + 1; + if( _av > 1 ) + _calculated_it = _buffer_out[_av_cnt]->begin(); + else + _calculated_it = begin(); + + calculate(); + + if( _av > 1 ) + { + for(int i = 0; i < _size_fft_out; i++) + { + qreal ampl = 0; + qreal phase = 0; + for(int j = 0; j < avdiv; j++) + { + ampl += _buffer_out[j]->at(i).first/avdiv; + phase += _buffer_out[j]->at(i).second/avdiv; + } + at(i).first = ampl; + at(i).second = phase; + } + } + + _av_cnt++; + _ac = 100. * ( (qreal) _av_cnt )/_av; + emit acquired(_ac); + + if(_av_cnt == _av) + { + _av_cnt = 0; + _av_filled = true; + } + + after_updating(); + + if(_device) + _device->unlock_ai(); + + emit changed(); + } + + void apfc::cleanup() + { + if(_device) + _device->lock_ai(); + + for(std::vector>* ptr:_buffer_out) + { + if( ptr ) + { + ptr->clear(); + ptr->shrink_to_fit(); + delete ptr; + } + } + _buffer_out.clear(); + _buffer_out.shrink_to_fit(); + + clear(); + shrink_to_fit(); + + if(_device) + _device->unlock_ai(); + } + + void apfc::init() + { + if(_device) + _device->lock_ai(); + + cleanup(); + + _size_fft_out = std::min(_data->size_fft_out(), _ref->size_fft_out()); + _size_fft_calc = std::min(_data->size_fft_calc(), _ref->size_fft_calc()); + + _av_cnt = 0; + _av_filled = false; + + for(int i = 0; i < _av; i++) + _buffer_out.push_back(new std::vector>(_size_fft_out)); + _calculated_it = _buffer_out[0]->begin(); + + resize(_size_fft_out); + + if(_device) + _device->unlock_ai(); + + emit initialized(); + emit acq_time_changed(); + } + + void apfc::stop_acq() + { + _is_acq = false; + } + + } // namespace math +} // namespace gtl diff --git a/math/gtl_math_apfc.h b/math/gtl_math_apfc.h new file mode 100644 index 0000000..7c97fcb --- /dev/null +++ b/math/gtl_math_apfc.h @@ -0,0 +1,145 @@ +#ifndef GTL_MATH_APFC_H +#define GTL_MATH_APFC_H + +#include +#include + +#include "core/gtl_device.h" +#include "core/gtl_analog_data.h" +#include "gtl_math_fft.h" +#include "math_global.h" + +namespace gtl { + namespace math { + + class MATH_EXPORT apfc : public QObject, public std::vector> + { + Q_OBJECT + + Q_PROPERTY(qreal frequency READ frequency WRITE set_frequency NOTIFY frequency_changed); + Q_PROPERTY(qreal resolution READ resolution WRITE set_resolution NOTIFY resolution_changed); + Q_PROPERTY(windows window READ window WRITE set_window NOTIFY window_changed); + Q_PROPERTY(int lines READ lines WRITE set_lines NOTIFY lines_changed); + Q_PROPERTY(int average READ average WRITE set_average NOTIFY average_changed); + Q_PROPERTY(int overlap READ overlap WRITE set_overlap NOTIFY overlap_changed); + Q_PROPERTY(qreal acq_time READ acq_time NOTIFY acq_time_changed); + Q_PROPERTY(afcs afc READ afc WRITE set_afc NOTIFY afc_changed); + Q_PROPERTY(pfcs pfc READ pfc WRITE set_pfc NOTIFY pfc_changed); + Q_PROPERTY(qreal threshold READ threshold WRITE set_threshold NOTIFY threshold_changed) + + public: + + typedef fft::windows windows; +// Q_ENUM(windows) + + enum afcs + { + magnitude, + db, + coherence + }; + Q_ENUM(afcs) + + enum pfcs + { + deg, + rad + }; + Q_ENUM(pfcs) + + apfc(gtl::analog_data *ref, gtl::analog_data *data); + ~apfc(); + + void set_ref(gtl::analog_data *ref); + void set_data(gtl::analog_data *data); + + qreal frequency() const; + void set_frequency(qreal value); + + qreal resolution() const; + void set_resolution(qreal value); + + windows window() const; + void set_window(windows value); + + int lines() const; + void set_lines(int value); + + uint average() const; + void set_average(int value); + + qreal overlap() const; + void set_overlap(int value); + + gtl::analog_data *ad() const; + + qreal acq_time() const; + + afcs afc() const; + void set_afc(afcs value); + + pfcs pfc() const; + void set_pfc(pfcs value); + + qreal threshold() const; + void set_threshold(qreal newThreshold); + + private: + void cleanup(); + + bool _is_acq; + int _av; // Количество усреднений + afcs _afc; // Единицы измерения АЧХ + pfcs _pfc; // Единицы измерения ФЧХ + int _av_cnt; // Счетчик усреднений + bool _av_filled; // Флаг заполнения окна усреднения + qreal _ac; // Процент завершения усреднения + qreal _threshold; // Порог % от max + +// gtl::analog_data* _ad; + + std::vector>*> _buffer_out; + std::vector>::iterator _calculated_it; // Итератор текущего буффера для вычисления спектра + + int _size_fft_calc; // Размер буфера/ 2 - до частоты Котельникова + int _size_fft_out; // Количество отображаемых линий + + math::fft* _ref; // БПФ эталона + math::fft* _data; // БПФ данных + + gtl::device* _device; + + protected: + virtual void calculate(); + virtual void before_updating(); + virtual void after_updating(); + + private slots: + virtual void device_recieved_data(); + void init(); + + public slots: + void stop_acq(); + + signals: + void frequency_changed(); + void resolution_changed(); + void window_changed(); + void lines_changed(); + void average_changed(); + void overlap_changed(); + void acq_time_changed(); + void afc_changed(); + void pfc_changed(); + void threshold_changed(); + + void initialized(); + void changed(); + void acquired(qreal); + void deleting(); + }; + + } // namespace math +} // namespace gtl + +#endif // GTL_MATH_APFC_H diff --git a/math/gtl_math_cross_spec.cpp b/math/gtl_math_cross_spec.cpp new file mode 100644 index 0000000..c3a11fa --- /dev/null +++ b/math/gtl_math_cross_spec.cpp @@ -0,0 +1,334 @@ + +#include "gtl_math_cross_spec.h" + +namespace gtl { + namespace math { + + cross_spec::cross_spec(gtl::analog_data *ref, gtl::analog_data *data) : + _is_acq(true), + _av(1), + _av_cnt(0), + _av_filled(false), + _threshold(50), + _ref(nullptr), + _data(nullptr) + { + _device = static_cast(data->root()); + + if(_device) + _device->lock_ai(); + + _ref = new gtl::math::fft(ref); + _data = new gtl::math::fft(data); + + connect(_device, >l::device::recieved_data, this, >l::math::cross_spec::device_recieved_data); + connect(_data, >l::math::fft::initialized, this, &cross_spec::init); + connect(_data, >l::math::fft::frequency_changed, this, &cross_spec::frequency_changed); + connect(_data, >l::math::fft::resolution_changed, this, &cross_spec::resolution_changed); + connect(_data, >l::math::fft::window_changed, this, &cross_spec::window_changed); + connect(_data, >l::math::fft::lines_changed, this, &cross_spec::lines_changed); + connect(_data, >l::math::fft::overlap_changed, this, &cross_spec::overlap_changed); + + init(); + + if(_device) + _device->unlock_ai(); + } + + cross_spec::~cross_spec() + { + emit deleting(); + + cleanup(); + if(_ref) + delete _ref; + if(_data) + delete _data; + } + + void cross_spec::set_ref(analog_data *ref) + { + if(_device) + _device->lock_ai(); + + if(_ref) + delete _ref; + + _ref = new gtl::math::fft(ref); + + _ref->set_frequency(_data->frequency()); + _ref->set_resolution(_data->resolution()); + _ref->set_overlap(_data->overlap()); + _ref->set_window(_data->window()); + + init(); + + if(_device) + _device->unlock_ai(); + } + + void cross_spec::set_data(analog_data *data) + { + _device = static_cast(data->root()); + + if(_device) + _device->lock_ai(); + + disconnect(_device, >l::device::recieved_data, this, >l::math::cross_spec::device_recieved_data); + disconnect(_data, >l::math::fft::initialized, this, &cross_spec::init); + disconnect(_data, >l::math::fft::frequency_changed, this, &cross_spec::frequency_changed); + disconnect(_data, >l::math::fft::resolution_changed, this, &cross_spec::resolution_changed); + disconnect(_data, >l::math::fft::window_changed, this, &cross_spec::window_changed); + disconnect(_data, >l::math::fft::lines_changed, this, &cross_spec::lines_changed); + disconnect(_data, >l::math::fft::overlap_changed, this, &cross_spec::overlap_changed); + + if(_data) + delete _data; + + _data = new gtl::math::fft(data); + + _data->set_frequency(_ref->frequency()); + _data->set_resolution(_ref->resolution()); + _data->set_overlap(_ref->overlap()); + _data->set_window(_ref->window()); + + connect(_device, >l::device::recieved_data, this, >l::math::cross_spec::device_recieved_data); + connect(_data, >l::math::fft::initialized, this, >l::math::cross_spec::init); + connect(_data, >l::math::fft::frequency_changed, this, >l::math::cross_spec::frequency_changed); + connect(_data, >l::math::fft::resolution_changed, this, >l::math::cross_spec::resolution_changed); + connect(_data, >l::math::fft::window_changed, this, >l::math::cross_spec::window_changed); + connect(_data, >l::math::fft::lines_changed, this, >l::math::cross_spec::lines_changed); + connect(_data, >l::math::fft::overlap_changed, this, >l::math::cross_spec::overlap_changed); + + init(); + + if(_device) + _device->unlock_ai(); + } + + qreal cross_spec::frequency() const + { + return _data->frequency(); + } + + void cross_spec::set_frequency(qreal value) + { + _ref->set_frequency(value); + _data->set_frequency(value); + + init(); + } + + qreal cross_spec::resolution() const + { + return _data->resolution(); + } + + void cross_spec::set_resolution(qreal value) + { + _ref->set_resolution(value); + _data->set_resolution(value); + + init(); + } + + cross_spec::windows cross_spec::window() const + { + return (cross_spec::windows) _data->window(); + } + + void cross_spec::set_window(windows value) + { + _ref->set_window((fft::windows) value); + _data->set_window((fft::windows) value); + } + + int cross_spec::lines() const + { + return _data->lines(); + } + + void cross_spec::set_lines(int value) + { + _ref->set_lines(value); + _data->set_lines(value); + + init(); + } + + uint cross_spec::average() const + { + return _av; + } + + void cross_spec::set_average(int value) + { + if( value != _av && value > 0) + { + _av = value; + init(); + emit average_changed(); + } + } + + qreal cross_spec::overlap() const + { + return _data->overlap(); + } + + void cross_spec::set_overlap(int value) + { + _ref->set_overlap(value); + _data->set_overlap(value); + } + +// units cross_spec::unit() const +// { +// return _un; +// } + +// void cross_spec::set_unit(units value) +// { +// if( value != _un ) +// { +// _un = value; +// emit unit_changed(); +// } +// } + + analog_data *cross_spec::ad() const + { + return _data->ad(); + } + + qreal cross_spec::acq_time() const + { + int nl = _data->lines(); + qreal f = _data->frequency(); + qreal ol = _data->overlap(); + + return (nl + 1)/f * ((_av - 1)* (1.0 - ol/100.0) + 1); + } + + void cross_spec::calculate() + { + for(int i = 0; i < _size_fft_out; i++) + *(_calculated_it + i) = abs(_ref->at(i)*std::conj(_data->at(i)))/_size_fft_calc/_size_fft_calc; + } + + void cross_spec::before_updating() + { + + } + + void cross_spec::after_updating() + { + + } + + void cross_spec::device_recieved_data() + { + if(_ref->samples() != _data->samples()) + return; + + if(_device) + _device->lock_ai(); + + before_updating(); + + // Вычисление , усреднение + int avdiv = _av_filled ? _av : _av_cnt + 1; + if( _av > 1 ) + _calculated_it = _buffer_out[_av_cnt]->begin(); + else + _calculated_it = begin(); + + calculate(); + + if( _av > 1 ) + { + for(int i = 0; i < _size_fft_out; i++) + { + qreal value = 0; + for(int j = 0; j < avdiv; j++) + value += _buffer_out[j]->at(i)/avdiv; + at(i) = value; + } + } + + _av_cnt++; + _ac = 100. * ( (qreal) _av_cnt )/_av; + emit acquired(_ac); + + if(_av_cnt == _av) + { + _av_cnt = 0; + _av_filled = true; + } + + after_updating(); + + if(_device) + _device->unlock_ai(); + + emit changed(); + } + + void cross_spec::cleanup() + { + if(_device) + _device->lock_ai(); + + for(std::vector* ptr:_buffer_out) + { + if( ptr ) + { + ptr->clear(); + ptr->shrink_to_fit(); + delete ptr; + } + } + _buffer_out.clear(); + _buffer_out.shrink_to_fit(); + + clear(); + shrink_to_fit(); + + if(_device) + _device->unlock_ai(); + } + + void cross_spec::init() + { + if(_device) + _device->lock_ai(); + + cleanup(); + + _size_fft_out = std::min(_data->size_fft_out(), _ref->size_fft_out()); + _size_fft_calc = std::min(_data->size_fft_calc(), _ref->size_fft_calc()); + + _av_cnt = 0; + _av_filled = false; + + for(int i = 0; i < _av; i++) + _buffer_out.push_back(new std::vector(_size_fft_out)); + _calculated_it = _buffer_out[0]->begin(); + + resize(_size_fft_out); + + if(_device) + _device->unlock_ai(); + + emit initialized(); + emit acq_time_changed(); + } + + void cross_spec::stop_acq() + { + _is_acq = false; + } + + } // namespace math +} // namespace gtl + diff --git a/math/gtl_math_cross_spec.h b/math/gtl_math_cross_spec.h new file mode 100644 index 0000000..254d413 --- /dev/null +++ b/math/gtl_math_cross_spec.h @@ -0,0 +1,122 @@ + +#ifndef GTL_MATH_CROSS_SPEC_H +#define GTL_MATH_CROSS_SPEC_H + + +#include +#include + +#include "core/gtl_device.h" +#include "core/gtl_analog_data.h" +#include "gtl_math_fft.h" +#include "math_global.h" + + +namespace gtl { + namespace math { + + class MATH_EXPORT cross_spec : public QObject, public std::vector + { + Q_OBJECT + + Q_PROPERTY(qreal frequency READ frequency WRITE set_frequency NOTIFY frequency_changed); + Q_PROPERTY(qreal resolution READ resolution WRITE set_resolution NOTIFY resolution_changed); + Q_PROPERTY(windows window READ window WRITE set_window NOTIFY window_changed); + Q_PROPERTY(int lines READ lines WRITE set_lines NOTIFY lines_changed); + Q_PROPERTY(int average READ average WRITE set_average NOTIFY average_changed); + Q_PROPERTY(int overlap READ overlap WRITE set_overlap NOTIFY overlap_changed); +// Q_PROPERTY(units unit READ unit WRITE set_unit NOTIFY unit_changed); + Q_PROPERTY(qreal acq_time READ acq_time NOTIFY acq_time_changed); + + public: + + typedef fft::windows windows; + + cross_spec(gtl::analog_data *ref, gtl::analog_data *data); + ~cross_spec(); + + void set_ref(gtl::analog_data *ref); + void set_data(gtl::analog_data *data); + + qreal frequency() const; + void set_frequency(qreal value); + + qreal resolution() const; + void set_resolution(qreal value); + + windows window() const; + void set_window(windows value); + + int lines() const; + void set_lines(int value); + + uint average() const; + void set_average(int value); + + qreal overlap() const; + void set_overlap(int value); + +// units unit() const; +// void set_unit(units value); + + gtl::analog_data *ad() const; + + qreal acq_time() const; + + private: + void cleanup(); + + bool _is_acq; + int _av; // Количество усреднений +// units _un; // Единицы измерения + int _av_cnt; // Счетчик усреднений + bool _av_filled; // Флаг заполнения окна усреднения + qreal _ac; // Процент завершения усреднения + qreal _threshold; // Порог % от max + + // gtl::analog_data* _ad; + + std::vector*> _buffer_out; + std::vector::iterator _calculated_it; // Итератор текущего буффера для вычисления спектра + + int _size_fft_calc; // Размер буфера/ 2 - до частоты Котельникова + int _size_fft_out; // Количество отображаемых линий + + math::fft* _ref; // БПФ эталона + math::fft* _data; // БПФ данных + + gtl::device* _device; + + protected: + virtual void calculate(); + virtual void before_updating(); + virtual void after_updating(); + + private slots: + virtual void device_recieved_data(); + void init(); + + public slots: + void stop_acq(); + + signals: + void frequency_changed(); + void resolution_changed(); + void window_changed(); + void lines_changed(); + void average_changed(); + void overlap_changed(); +// void unit_changed(); + void acq_time_changed(); + + void initialized(); + void changed(); + void acquired(qreal); + void deleting(); + + }; + + } // namespace math +} // namespace gtl + +#endif // GTL_MATH_CROSS_SPEC_H diff --git a/math/gtl_math_delta_phase.cpp b/math/gtl_math_delta_phase.cpp new file mode 100644 index 0000000..2ef262d --- /dev/null +++ b/math/gtl_math_delta_phase.cpp @@ -0,0 +1,71 @@ +#include "gtl_math_delta_phase.h" + +namespace gtl { + namespace math { + + delta_phase::delta_phase(gtl::analog_data *ref, gtl::analog_data *data) + : last_front(data) + , _ref(new last_front(ref)) + , _freq(new gtl::math::freq(ref)) + { + + } + + delta_phase::~delta_phase() + { + delete _ref; + delete _freq; + } + + void delta_phase::set_time(qreal value) + { + _freq->set_time(value); + set_freq(_freq->value()); + _ref->set_freq(_freq->value()); + } + + void delta_phase::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + + } + + void delta_phase::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + + } + + void delta_phase::data_changed() + { + last_front::data_changed(); + + qreal freq_coef = _freq->value()/_ref->freq(); + if(freq_coef > 1.2 || freq_coef <= 1) + { + set_freq(_freq->value()); + _ref->set_freq(_freq->value()); + return; + } + + if(front() > 0 && _ref->front() > 0) + { + qreal phase = (front() - _ref->front())*_freq->value()/_ad->get_rate(); + if(phase > 1.) + return; + phase = phase*360.; + if(phase < 0) + phase += 360.; +// if(phase >= 180.) +// phase = 360. - phase; + + _values.push_back(phase); + while(_values.size() > _avg_cnt) + _values.pop_front(); + _value = std::accumulate(_values.begin(), _values.end(), 0.0)/_values.size(); + + emit value_changed(); + } + } + + + } // namespace math +} // namespace gtl diff --git a/math/gtl_math_delta_phase.h b/math/gtl_math_delta_phase.h new file mode 100644 index 0000000..038d667 --- /dev/null +++ b/math/gtl_math_delta_phase.h @@ -0,0 +1,103 @@ +#ifndef GTL_MATH_DELTA_PHASE_H +#define GTL_MATH_DELTA_PHASE_H + +#include +#include + +#include "gtl_math_analog_value.h" +#include "math_global.h" +#include "core/gtl_analog_data.h" +#include "gtl_math_freq.h" + +namespace gtl { + namespace math { + + + class last_front : public analog_value + { + Q_OBJECT + public: + last_front(gtl::analog_data *data) + : analog_value(data) + , _front(-1) + , _freq(100) + { + + } + + int front(){return _front;} + void set_freq(qreal value) + { + if(value != _freq && value > 0) + { + _freq = value; + analog_value::set_time(1.1/_freq); + _front = -1; + } + } + qreal freq(){return _freq;} + + virtual void set_time(qreal value) override{ analog_value::set_time(value); } + + private: + virtual void before_copying_data(std::vector::iterator begin, std::vector::iterator end) override{} + virtual void after_copying_data(std::vector::iterator begin, std::vector::iterator end) override{} + + protected slots: + virtual void data_changed() override + { + _ad->lock_device(); + if(_ad->size() >= _data.size()) + { + std::copy(_ad->end() - _data.size(), _ad->end(), _data.begin()); + } + else + { + int dif = _data.size() - _ad->size(); + std::copy(_data.end() - dif, _data.end(), _data.begin()); + std::copy(_ad->begin(), _ad->end(), _data.begin() + dif); + } + _ad->unlock_device(); + + qreal dc = 0;//std::accumulate(_data.begin(), _data.end(), 0.0)/_data.size(); + for(int i = _data.size() - 1; i > 0; --i) + { + if(_data[i] >= dc && _data[i-1] < dc) + { + _front = _data.size() - i; + break; + } + } + } + private: + int _front; + qreal _freq; + + }; + + class MATH_EXPORT delta_phase : public last_front + { + Q_OBJECT + public: + delta_phase(gtl::analog_data *ref, gtl::analog_data *data); + ~delta_phase(); + + virtual void set_time(qreal value) override; + + private: + last_front* _ref = NULL; + last_front* _data = NULL; + gtl::math::freq *_freq = NULL; + + private: + virtual void before_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + virtual void after_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + + protected slots: + virtual void data_changed() override; + + }; + } // namespace math +} // namespace gtl + +#endif // GTL_MATH_DELTA_PHASE_H diff --git a/math/gtl_math_delta_phase_spec.cpp b/math/gtl_math_delta_phase_spec.cpp new file mode 100644 index 0000000..1f49f98 --- /dev/null +++ b/math/gtl_math_delta_phase_spec.cpp @@ -0,0 +1,162 @@ +#include "gtl_math_delta_phase_spec.h" + +namespace gtl +{ + namespace math + { + + delta_phase_spec::delta_phase_spec(gtl::analog_data *ref, gtl::analog_data *data) + : _ref_spec(nullptr), + _data_spec(nullptr), + _phase(0), + _freq(0), + _max_freq(0) + { + _device = static_cast(data->root()); + + if(_device) + _device->lock_ai(); + + _ref_spec = new gtl::math::spec(gtl::math::spec::types::phase, ref); + _data_spec = new gtl::math::spec(gtl::math::spec::types::phase, data); + + if(_device) + connect(_device, >l::device::recieved_data, this, &delta_phase_spec::calculate); + + if(_device) + _device->unlock_ai(); + + connect(_ref_spec, >l::math::spec::resolution_changed, this, &delta_phase_spec::resolution_changed); + connect(_ref_spec, >l::math::spec::overlap_changed, this, &delta_phase_spec::overlap_changed); + } + + delta_phase_spec::~delta_phase_spec() + { + delete _ref_spec; + delete _data_spec; + } + + qreal delta_phase_spec::frequency() const + { + return _freq; + } + + void delta_phase_spec::set_frequency(qreal value) + { + if(value != _freq && value > 0 && value < _ref_spec->frequency()) + { + _freq = value; + + if(!_max_freq) + { + if(_device) + _device->lock_ai(); + + _ref_spec->set_frequency(_freq*2); + _data_spec->set_frequency(_freq*2); + + if(_device) + _device->unlock_ai(); + } + + emit frequency_changed(); + } + } + + qreal delta_phase_spec::max_frequency() const + { + return _max_freq; + } + + void delta_phase_spec::set_max_frequency(qreal value) + { + if(value != _max_freq && value >= 0) + { + _max_freq = value; + + qreal max_freq = _max_freq ? _max_freq : _freq*2; + + if(_device) + _device->lock_ai(); + + _ref_spec->set_frequency(max_freq); + _data_spec->set_frequency(max_freq); + + if(_device) + _device->unlock_ai(); + } + } + + qreal delta_phase_spec::resolution() const + { + return _ref_spec->resolution(); + } + + void delta_phase_spec::set_resolution(qreal value) + { + if(_device) + _device->lock_ai(); + + _ref_spec->set_resolution(value); + _data_spec->set_resolution(value); + + if(_device) + _device->unlock_ai(); + } + + qreal delta_phase_spec::overlap() const + { + return _ref_spec->overlap(); + } + + void delta_phase_spec::set_overlap(int value) + { + if(_device) + _device->lock_ai(); + + _ref_spec->set_overlap(value); + _data_spec->set_overlap(value); + + if(_device) + _device->unlock_ai(); + } + + qreal delta_phase_spec::value() + { + return _phase; + } + + void delta_phase_spec::calculate() + { + if(_ref_spec->samples() != _data_spec->samples()) + return; + + if(_device) + _device->lock_ai(); + + if(_freq) + { + qreal phase = 0; + int idx = std::round(_freq/_ref_spec->resolution()); + + if(idx < _ref_spec->size() && idx < _data_spec->size()) + phase = _data_spec->at(idx) - _ref_spec->at(idx); + else + return; + + phase = 180*phase/M_PI; + if(phase < 0) + phase += 360.; +// if(phase >= 180.) +// phase = 360. - phase; + + _phase = phase; + emit value_changed(); + } + + if(_device) + _device->unlock_ai(); + } + + } // namespace math +} // namespace gtl diff --git a/math/gtl_math_delta_phase_spec.h b/math/gtl_math_delta_phase_spec.h new file mode 100644 index 0000000..cd819f8 --- /dev/null +++ b/math/gtl_math_delta_phase_spec.h @@ -0,0 +1,73 @@ +#ifndef GTL_MATH_DELTA_PHASE_SPEC_H +#define GTL_MATH_DELTA_PHASE_SPEC_H + +#include + +#include "core/gtl_device.h" +#include "core/gtl_analog_data.h" + +#include "gtl_math_spec.h" + +#include "math_global.h" + + +namespace gtl +{ + namespace math + { + + class MATH_EXPORT delta_phase_spec : public QObject + { + Q_OBJECT + + Q_PROPERTY(qreal frequency READ frequency WRITE set_frequency NOTIFY frequency_changed); + Q_PROPERTY(qreal max_frequency READ max_frequency WRITE set_max_frequency NOTIFY max_frequency_changed); + Q_PROPERTY(qreal resolution READ resolution WRITE set_resolution NOTIFY resolution_changed); + Q_PROPERTY(qreal value READ value() NOTIFY value_changed); + + public: + delta_phase_spec(gtl::analog_data *ref, gtl::analog_data *data); + ~delta_phase_spec(); + + qreal frequency() const; + void set_frequency(qreal value); + + qreal max_frequency() const; + void set_max_frequency(qreal value); + + qreal resolution() const; + void set_resolution(qreal value); + + qreal overlap() const; + void set_overlap(int value); + + + private: + int _ref_max_idx; + int _data_max_idx; + gtl::math::spec* _ref_spec = NULL; + gtl::math::spec* _data_spec = NULL; + qreal _phase; + qreal _freq; + qreal _max_freq; + + gtl::device* _device; + + private slots: + void calculate(); + + signals: + void value_changed(); + void frequency_changed(); + void max_frequency_changed(); + void resolution_changed(); + void overlap_changed(); + + public slots: + qreal value(); + }; + + } // namespace math +} // namespace gtl + +#endif // GTL_MATH_DELTA_PHASE_SPEC_H diff --git a/math/gtl_math_diff.cpp b/math/gtl_math_diff.cpp new file mode 100644 index 0000000..445443e --- /dev/null +++ b/math/gtl_math_diff.cpp @@ -0,0 +1,101 @@ +#include "gtl_math_diff.h" + +namespace gtl +{ + namespace math + { + diff::diff(gtl::analog_data* parent, bool is_hidden) + : gtl::analog_data(parent, is_hidden) + , _taps(1) +// , _delay(1) + { + if(parent == NULL) + return; + + QString name_base = tr("differentiator"); + QString name = name_base; + int idx = 0; + while(parent->is_child_name_exist(name)) + { + idx++; + name = name_base + "_" + QString::number(idx); + } + + set_name(name); + + init(); + } + + diff::~diff() + { + delete _delays; + } + + int diff::taps() const + { + return _taps; + } + + void diff::set_taps(int value) + { + if( value > 0 && value != _taps) + { + _taps = value; + init(); + } + } + +// int diff::delay() const +// { +// return _delay; +// } + +// void diff::set_delay(int value) +// { +// if( value > 0 && value != _delay) +// { +// _delay = value; +// init(); +// } +// } + + void gtl::math::diff::set_data(iterator begin, iterator end) + { + clear(); + std::copy(begin, end, std::back_inserter(*this)); //to do... + + process(this->begin(), this->end()); + + gtl::analog_data::set_data(this->begin(), this->end()); + } + + void diff::init() + { + if(_delays) + delete _delays; + _delays = new std::vector(_taps); + } + + void diff::process(iterator begin, iterator end) + { + int size = std::distance(begin, end); + qreal dt = 1./get_rate(); + + for( int i = 0; i < size; i++) + { + qreal value = *(begin + i); + + for(int t = 0; t < _taps; t++) + { + value -= _delays->at(t); + _delays->at(t) += value; + } + + *(begin + i) = value/dt; + } + return; + } + + + } +} diff --git a/math/gtl_math_diff.h b/math/gtl_math_diff.h new file mode 100644 index 0000000..b9e241d --- /dev/null +++ b/math/gtl_math_diff.h @@ -0,0 +1,48 @@ +#ifndef GTL_MATH_DIFF_H +#define GTL_MATH_DIFF_H + +#include "core/gtl_analog_data.h" + +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT diff : public gtl::analog_data + { + Q_OBJECT + Q_PROPERTY(int taps READ taps WRITE set_taps NOTIFY taps_changed) +// Q_PROPERTY(int delay READ delay WRITE set_delay NOTIFY delay_changed) + + public: + diff(gtl::analog_data* parent, bool is_hidden = false); + ~diff(); + + int taps() const; +// int delay() const; + + protected: + virtual void set_data(std::vector::iterator begin, std::vector::iterator end) override; + + private: + void init(); + void process(std::vector::iterator begin, std::vector::iterator end); + + private: + int _taps; +// int _delay; + std::vector *_delays = NULL; + + public slots: + void set_taps(int value); +// void set_delay(int value); + + signals: + void taps_changed(); +// void delay_changed(); + }; + } +} + +#endif // GTL_MATH_DIFF_H diff --git a/math/gtl_math_fft.cpp b/math/gtl_math_fft.cpp new file mode 100644 index 0000000..cff01bc --- /dev/null +++ b/math/gtl_math_fft.cpp @@ -0,0 +1,400 @@ +#include "gtl_math_fft.h" + +namespace gtl { + namespace math { + + fft::fft(gtl::analog_data *ad) + : QObject(ad) + , _ad(new gtl::math::filter_iir(ad, true)) + , _rbw(10)//6.25 + , _w(windows::rectangular) + , _nl(20) + , _ol(0) + , _done(false) + , _cn(4) + , _dr(1) + , _buffer_ptr(0) + , _buffer_ptr_offset(0) + { + _device = static_cast(_ad->root()); + + if(_device) + _device->lock_ai(); + + fftw_make_planner_thread_safe(); + + set_frequency(200); + init(); + + if(_device) + _device->unlock_ai(); + + connect(_ad, >l::analog_data::data_changed, this, &fft::data_changed); + } + + + fft::~fft() + { + emit deleting(); + cleanup(); + if(_ad) + delete _ad; + } + + qreal fft::frequency() const + { + return _f; + } + + void fft::set_frequency(qreal value) + { + if(_device) + _device->lock_ai(); + + if(value > _ad->get_rate()/2) + value = _ad->get_rate()/2; + + if(_f != value && value <= _ad->get_rate()/2 && value > 0) + { + _f = value; + _dr = (uint) ( (_ad->get_rate()/2)/_f ); + _fs = _ad->get_rate() / _dr; + _fk = _fs/2; + + int size = _fs/_rbw;//_size_r = (size_t) _fs/_rbw; + _rbw = _fs / (qreal)size;//_rbw = _fs / (qreal)_size_r; + + _nl = (uint) round(_f/_rbw); + + init(); + emit frequency_changed(); + } + + if(_device) + _device->unlock_ai(); + } + + qreal fft::resolution() const + { + return _rbw; + } + + void fft::set_resolution(qreal value) + { + if(_device) + _device->lock_ai(); + + if(_rbw != value && _fk > value && value > 0) + { + _rbw = value; + + int size = _fs/_rbw; + _rbw = _fs / (qreal)size; + + _nl = (uint) round(_f/_rbw); + + init(); + emit resolution_changed(); + } + + if(_device) + _device->unlock_ai(); + } + + fft::windows fft::window() const + { + return _w; + } + + void fft::set_window(windows value) + { + if(_w != value) + { + _w = value; + init(); + emit window_changed(); + } + } + + int fft::lines() const + { + return _nl; + } + + void fft::set_lines(int value) + { + if(_device) + _device->lock_ai(); + + if(_nl != value && value > 0) + { +// _nl = value; +// _rbw = _f/_nl; + set_resolution(_f/value); +// init(); + emit lines_changed(); + } + + if(_device) + _device->unlock_ai(); + } + + qreal fft::overlap() const + { + return _ol; + } + + void fft::set_overlap(int value) + { + if( value != _ol && value < 100 && value >= 0) + { + _ol = value; + init(); + emit overlap_changed(); + } + } + + analog_data *fft::ad() const + { + return static_cast(_ad->parent()); + } + + + int fft::samples() const + { + return _samples; + } + + int fft::size_fft_out() const + { + return _size_out; + } + + int fft::size_fft_calc() const + { + return _size_r_k; + } + + void fft::init() + { + _ad->lock_device(); + cleanup(); + + _fk = _fs/2; + if(_fk < _rbw) + _rbw = _fk; + _size = _ad->get_rate()/_rbw; + _fs = _ad->get_rate()/_dr; + _size_r = ceil(_fs/_rbw); + _size_r_k = _dr > 1 ? _size_r/2 + 1 : _size_r/2; + _size_out = _nl < _size_r_k ? _nl + 1 : _nl; + _buffer_ptr = 0; + _buffer_ptr_offset = (int) (_size * _ol) / 100 ; + + _buffer_in = new std::vector(_size); + _buffer_w = new std::vector(_size_r); + + int N = _size_r; + switch (_w) { + case windows::rectangular: + std::generate(_buffer_w->begin(), _buffer_w->end(), [] () mutable { return 1.*1.051; }); + break; + case windows::cosine: + std::generate(_buffer_w->begin(), _buffer_w->end(), [n = 0, &N] () mutable { auto v = sin(M_PI*n/N); n++; return v*1.626; }); + break; + case windows::hann: + std::generate(_buffer_w->begin(), _buffer_w->end(), [n = 0, &N] () mutable { auto v = 0.5 - 0.5*cos(2*M_PI*n/N); n++; return v*2.07; }); + break; + case windows::bartlett_hann: + std::generate(_buffer_w->begin(), _buffer_w->end(), [n = 0, &N] () mutable { auto v = 0.62 - 0.48*fabs(n/N - 1/2) - 0.38*cos(2*M_PI*n/N); n++; return v*1.669; }); + break; + case windows::hamming: + std::generate(_buffer_w->begin(), _buffer_w->end(), [n = 0, &N] () mutable { auto v = 0.54 - 0.46*cos(2*M_PI*n/N); n++; return v*1.918; }); + break; + case windows::blackman: + std::generate(_buffer_w->begin(), _buffer_w->end(), [n = 0, &N] () mutable { auto v = 0.42 - 0.5*cos(2*M_PI*n/N) + 0.08*cos(4*M_PI*n/N); n++; return v*2.464; }); + break; + case windows::blackman_harris: + std::generate(_buffer_w->begin(), _buffer_w->end(), [n = 0, &N] () mutable { auto v = 0.359 - 0.488*cos(2*M_PI*n/N) + 0.141*cos(4*M_PI*n/N) - 0.012*cos(6*M_PI*n/N); n++; return v*2.883; }); + break; + case windows::flattop: + std::generate(_buffer_w->begin(), _buffer_w->end(), [n = 0, &N] () mutable { auto v = 1 - 1.93*cos(2*M_PI*n/N) + 1.29*cos(4*M_PI*n/N) - 0.388*cos(6*M_PI*n/N) + 0.032*cos(8*M_PI*n/N); n++; return v*1.035; }); + break; + } + + _fft_in = new std::vector(_size_r); + + resize(_size_r); + + _plan = fftw_plan_dft_r2c_1d(_size_r, _fft_in->data(), reinterpret_cast(data()), FFTW_ESTIMATE); + + ////ANTIALIAS FILTER + gtl::math::filter_iir *filter = static_cast(_ad); + filter->reset(); + filter->set_kind(gtl::math::filter_iir::kinds::elliptic); + filter->set_type(gtl::math::filter_iir::types::lowpass); + filter->set_order(10); + filter->set_frequency(_rbw*_size_out); + filter->set_ripple(1); + filter->set_slope(1); + + _ad->unlock_device(); + + _samples = 0; + + emit initialized(); + } + + void fft::cleanup() + { + _ad->lock_device(); + + if(_plan) + { + fftw_destroy_plan(_plan); + _plan = nullptr; + } + + if(_buffer_in) + { + _buffer_in->clear(); + _buffer_in->shrink_to_fit(); + delete _buffer_in; + _buffer_in = nullptr; + } + + if(_buffer_w) + { + _buffer_w->clear(); + _buffer_w->shrink_to_fit(); + delete _buffer_w; + _buffer_w = nullptr; + } + + if(_fft_in) + { + _fft_in->clear(); + _fft_in->shrink_to_fit(); + delete _fft_in; + _fft_in = nullptr; + } + + clear(); + shrink_to_fit(); + + _ad->unlock_device(); + } + + void fft::decimate(std::vector::iterator in_it, std::vector::iterator out_it, int size, int N, int R) + { + for( int i = 0; i < size/R; i++) + *(out_it + i) = *(in_it + i*R); + return; + } + + void fft::set_rate(qreal value) + { + _ad->set_rate(value); + + init(); + } + + void fft::data_changed() + { + _ad->lock_device(); + + _samples += _ad->size(); + + adcopy(_ad->size()); + + _ad->unlock_device(); + } + + void fft::adcopy(int adoffset) + { + if(_buffer_ptr < (int) _size) + { + if(adoffset > _size) + adcopy(adoffset - _size); + + int adsize = _size; + + if(adoffset >= _ad->size()) + adsize = _ad->size() % _size; + + std::vector::iterator it_b = _ad->begin() + adoffset - (adoffset % _size); + std::vector::iterator it_e = it_b + adsize; + + /// Заполнение буфера данными + if( adsize >= _size) + { + if(_buffer_ptr) + { + int tmp = _buffer_ptr; + std::copy(it_b, it_e - _buffer_ptr, _buffer_in->begin() + _buffer_ptr); + _buffer_ptr = _size; + calc(); + + _buffer_ptr = tmp; + std::copy(it_e - _buffer_ptr, it_e, _buffer_in->begin()); + } + else + { + std::copy(it_b, it_e, _buffer_in->begin()); + _buffer_ptr = _size; + } + } + else + { + if( (_size - _buffer_ptr) >= adsize ) + { + std::copy(it_b, it_e, _buffer_in->begin() + _buffer_ptr); + _buffer_ptr += adsize; + } + else + { + int tmp = _buffer_ptr; + std::copy(it_b, it_b + (_size - _buffer_ptr), _buffer_in->begin() + _buffer_ptr); + _buffer_ptr = _size; + calc(); + + _buffer_ptr = std::distance(it_b, it_e) - (_size - tmp); + std::copy(it_e - _buffer_ptr, it_e, _buffer_in->begin()); + } + } + ///////////////////// + } + + calc(); + } + + void fft::calc() + { + _done = false; + + if( _buffer_ptr >= (int) _size && _plan ) + { + //////////////////////////////////////////////////////////////////////////////////////// + + decimate(_buffer_in->begin(), _fft_in->begin(), _size, _cn, _dr); + + // Весовая функция + for(int i = 0; i < _size_r; i++) + _fft_in->at(i) *= _buffer_w->at(i); + + // БПФ + if(_plan) fftw_execute(_plan); + + if( _buffer_ptr_offset ) + std::copy(_buffer_in->end() - _buffer_ptr_offset, _buffer_in->end(), _buffer_in->begin()); + _buffer_ptr = _buffer_ptr_offset; + + _done = true; + } + + if(_done) + emit changed(); + } + + } // namespace math +} // namespace gtl diff --git a/math/gtl_math_fft.h b/math/gtl_math_fft.h new file mode 100644 index 0000000..923c21b --- /dev/null +++ b/math/gtl_math_fft.h @@ -0,0 +1,122 @@ +#ifndef GTL_MATH_FFT_H +#define GTL_MATH_FFT_H + +#include +#include +#include "fftw3.h" + +#include "core/gtl_device.h" +#include "core/gtl_analog_data.h" +#include "core/gtl.h" +#include "gtl_math_filter_iir.h" +#include "math_global.h" + +namespace gtl { + namespace math { + + class MATH_EXPORT fft : public QObject, public std::vector> + { + Q_OBJECT + + public: + enum windows + { + rectangular, + cosine, + hann, + bartlett_hann, + hamming, + blackman, + blackman_harris, + flattop, + }; + Q_ENUM(windows) + + public: + fft(gtl::analog_data *ad); + ~fft(); + + qreal frequency() const; + void set_frequency(qreal value); + + qreal resolution() const; + void set_resolution(qreal value); + + windows window() const; + void set_window(windows value); + + int lines() const; + void set_lines(int value); + + qreal overlap() const; + void set_overlap(int value); + + gtl::analog_data *ad() const; + + int samples() const; + void init(); + + int size_fft_out() const; + int size_fft_calc() const; + + private: + void decimate(std::vector::iterator in_it, std::vector::iterator out_it, int size, int N, int R); + void cleanup(); + void adcopy(int adoffset); + void calc(); + + gtl::analog_data* _ad; // Без фильтра + int _samples; + + qreal _f; // Верхняя частота + qreal _rbw; // Разрешение + windows _w; // Окно + int _nl; // Кол-во линий + int _av; // Количество усреднение + int _ol; // Перекрытие + units _un; // Единицы измерения + + std::vector>* _fft_out = nullptr; + fftw_plan _plan = nullptr; + bool _done; + size_t _size_r; // Размер буфера после децимации + size_t _size_r_k; // Размер буфера после децимации / 2 - до частоты Котельникова + + std::vector*> _buffer_out; + int _size_out; // Количество отображаемых линий + + qreal _fs; // Частота дискритизации + qreal _fk; // Частота Котельникова + uint _cn; // Порядок гребенчатого фильтра + uint _dr; // Коэффициент децимации + + size_t _size; // Размер буфера + + std::vector *_buffer_w = nullptr; // Буфер оконной функции + std::vector *_buffer_in = nullptr; // Буфер входной + int _buffer_ptr; + int _buffer_ptr_offset; + std::vector* _fft_in = nullptr; + gtl::device* _device; + + private slots: + virtual void data_changed(); + virtual void set_rate(qreal value); + + signals: + void frequency_changed(); + void resolution_changed(); + void window_changed(); + void lines_changed(); + void overlap_changed(); + + void initialized(); + void changed(); + void deleting(); + + }; + + } // namespace math +} // namespace gtl + +#endif // GTL_MATH_FFT_H diff --git a/math/gtl_math_filter_iir.cpp b/math/gtl_math_filter_iir.cpp new file mode 100644 index 0000000..8b77094 --- /dev/null +++ b/math/gtl_math_filter_iir.cpp @@ -0,0 +1,529 @@ +#include "gtl_math_filter_iir.h" +namespace gtl +{ + namespace math + { + filter_iir::filter_iir(gtl::analog_data* parent, bool is_hidden) + : gtl::analog_data(parent, is_hidden) + , _filter(NULL) + , _kind(kinds::undef) + , _type(types::lowpass) + , _order(1) + , _fc(100) + , _gain(0) + , _fw(10) + , _ripple(1) + , _stop(200) + , _q(1) + , _slope(1) + { + + if(parent == NULL) + return; + + QString name_base = tr("filter"); + QString name = name_base; + int idx = 0; + while(parent->is_child_name_exist(name)) + { + idx++; + name = name_base + "_" + QString::number(idx); + } + + set_name(name); + + init(); + } + + filter_iir::kinds filter_iir::kind() const + { + return _kind; + } + + void filter_iir::set_kind(kinds kind) + { + if(_kind != kind) + { + _kind = kind; + toNull(); + init(); + } + } + + filter_iir::types filter_iir::type() const + { + return _type; + } + + void filter_iir::set_type(types type) + { + if(_type != type) + { + _type = type; + toNull(); + init(); + } + } + + int filter_iir::order() const + { + return _order; + } + + void filter_iir::set_order(int order) + { + if(_order != order) + { + _order = order; + init(); + } + } + + qreal filter_iir::frequency() const + { + return _fc; + } + + void filter_iir::set_frequency(qreal frequency) + { + if(_fc != frequency) + { + _fc = frequency; + init(); + } + } + + qreal filter_iir::gain() const + { + return _gain; + } + + void filter_iir::set_gain(qreal gain) + { + if(_gain != gain) + { + _gain = gain; + init(); + } + } + + qreal filter_iir::width() const + { + return _fw; + } + + void filter_iir::set_width(qreal width) + { + if(_fw != width) + { + _fw = width; + init(); + } + } + + qreal filter_iir::ripple() const + { + return _ripple; + } + + void filter_iir::set_ripple(qreal ripple) + { + if(_ripple != ripple) + { + _ripple = ripple; + init(); + } + } + + qreal filter_iir::stop() const + { + return _stop; + } + + void filter_iir::set_stop(qreal stop) + { + if(_stop != stop) + { + _stop = stop; + init(); + } + } + + qreal filter_iir::q() const + { + return _q; + } + + void filter_iir::set_q(qreal q) + { + if(_q != q) + { + _q = q; + init(); + } + } + + qreal filter_iir::slope() const + { + return _slope; + } + + void filter_iir::reset() const + { + if(_filter) + _filter->reset(); + } + + void filter_iir::set_slope(qreal slope) + { + if(_slope != slope) + { + _slope = slope; + init(); + } + } + + void filter_iir::save(QDomElement &root_element) + { + gtl::analog_data::save(root_element); + + root_element.setAttribute("kind", _kind); + root_element.setAttribute("type", _type); + root_element.setAttribute("order", _order); + root_element.setAttribute("fc", _fc); + root_element.setAttribute("gain", _gain); + root_element.setAttribute("ripple", _ripple); + root_element.setAttribute("stop", _stop); + root_element.setAttribute("q", _q); + root_element.setAttribute("slope", _slope); + } + + void filter_iir::load(const QDomElement &root_element) + { + gtl::analog_data::load(root_element); + + _kind = (kinds)root_element.attribute("kind", QString::number((int)kinds::undef)).toInt(); + _type = (types)root_element.attribute("type", QString::number(types::lowpass)).toInt(); + _order = root_element.attribute("order", "1").toInt(); + _fc = root_element.attribute("fc", "100").toDouble(); + _gain = root_element.attribute("gain", "0").toDouble(); + _ripple = root_element.attribute("ripple", "1").toDouble(); + _stop = root_element.attribute("stop", "200").toDouble(); + _q = root_element.attribute("q", "1").toDouble(); + _slope = root_element.attribute("slope", "1").toDouble(); + + toNull(); + init(); + } + + void filter_iir::get_state(QJsonObject &root) + { + root["kind"] = _kind; + root["type"] = _type; + root["order"] = _order; + root["fc"] = _fc; + root["gain"] = _gain; + root["ripple"] = _ripple; + root["stop"] = _stop; + root["q"] = _q; + root["slope"] = _slope; + + gtl::analog_data::get_state(root); + } + + qreal filter_iir::response(qreal value) const + { + return std::abs(_filter->response(value)); + } + + void filter_iir::toNull() + { + if(_filter) + { + delete _filter; + _filter = NULL; + } + } + + void filter_iir::init() + { + Dsp::Params params; + params[0] = _rate; + params[1] = _order; + params[2] = _fc; + + if(_kind == kinds::bessel) + { + switch (_type) { + case types::lowpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Bessel::Design::LowPass<50>, 1 > ; + break; + case types::highpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Bessel::Design::HighPass<50>, 1 > ; + break; + case types::bandpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Bessel::Design::BandPass<50>, 1 > ; + params[3] = _fw; + break; + case types::bandstop: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Bessel::Design::BandStop<50>, 1 > ; + params[3] = _fw; + break; + case types::lowshelf: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Bessel::Design::LowShelf<50>, 1 > ; + params[3] = _gain; + break; + default: + break; + } + } + + if(_kind == kinds::butterworth) + { + switch (_type) { + case types::lowpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Butterworth::Design::LowPass<50>, 1 > ; + break; + case types::highpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Butterworth::Design::HighPass<50>, 1 > ; + break; + case types::bandpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Butterworth::Design::BandPass<50>, 1 > ; + params[3] = _fw; + break; + case types::bandstop: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Butterworth::Design::BandStop<50>, 1 > ; + params[3] = _fw; + break; + case types::lowshelf: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Butterworth::Design::LowShelf<50>, 1 > ; + params[3] = _gain; + break; + case types::highshelf: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Butterworth::Design::HighShelf<50>, 1 > ; + params[3] = _gain; + break; + case types::bandshelf: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Butterworth::Design::BandShelf<50>, 1 > ; + params[3] = _fw; + params[4] = _gain; + break; + default: + break; + } + } + + if(_kind == kinds::chebyshev1) + { + switch (_type) { + case types::lowpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevI::Design::LowPass<50>, 1 > ; + params[3] = _ripple; + break; + case types::highpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevI::Design::HighPass<50>, 1 > ; + params[3] = _ripple; + break; + case types::bandpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevI::Design::BandPass<50>, 1 > ; + params[3] = _fw; + params[4] = _ripple; + break; + case types::bandstop: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevI::Design::BandStop<50>, 1 > ; + params[3] = _fw; + params[4] = _ripple; + break; +// case types::lowshelf: +// if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevI::Design::LowShelf<50>, 1 > ; +// params[3] = _gain; +// params[4] = _ripple; +// break; +// case types::highshelf: +// if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevI::Design::HighShelf<50>, 1 > ; +// params[3] = _gain; +// params[4] = _ripple; +// break; +// case types::bandshelf: +// if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevI::Design::BandShelf<50>, 1 > ; +// params[3] = _fw; +// params[4] = _gain; +// params[5] = _ripple; +// break; + default: + break; + } + } + + if(_kind == kinds::chebyshev2) + { + switch (_type) { + case types::lowpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevII::Design::LowPass<50>, 1 > ; + params[3] = _stop; + break; + case types::highpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevII::Design::HighPass<50>, 1 > ; + params[3] = _stop; + break; + case types::bandpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevII::Design::BandPass<50>, 1 > ; + params[3] = _fw; + params[4] = _stop; + break; + case types::bandstop: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevII::Design::BandStop<50>, 1 > ; + params[3] = _fw; + params[4] = _stop; + break; +// case types::lowshelf: +// if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevII::Design::LowShelf<50>, 1 > ; +// params[3] = _gain; +// params[4] = _stop; +// break; +// case types::highshelf: +// if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevII::Design::HighShelf<50>, 1 > ; +// params[3] = _gain; +// params[4] = _stop; +// break; +// case types::bandshelf: +// if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::ChebyshevII::Design::BandShelf<50>, 1 > ; +// params[3] = _fw; +// params[4] = _gain; +// params[5] = _stop; +// break; + default: + break; + } + } + + if(_kind == kinds::elliptic) + { + switch (_type) { + case types::lowpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Elliptic::Design::LowPass<50>, 1 > ; + params[3] = _ripple; + params[4] = _slope; + break; + case types::highpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Elliptic::Design::HighPass<50>, 1 > ; + params[3] = _ripple; + params[4] = _slope; + break; + case types::bandpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Elliptic::Design::BandPass<50>, 1 > ; + params[3] = _fw; + params[4] = _ripple; + params[5] = _slope; + break; + case types::bandstop: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Elliptic::Design::BandStop<50>, 1 > ; + params[3] = _fw; + params[4] = _ripple; + params[5] = _slope; + break; + default: + break; + } + } + + if(_kind == kinds::legendre) + { + switch (_type) { + case types::lowpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Legendre::Design::LowPass<50>, 1 > ; + break; + case types::highpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Legendre::Design::HighPass<50>, 1 > ; + break; + case types::bandpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Legendre::Design::BandPass<50>, 1 > ; + params[3] = _fw; + break; + case types::bandstop: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::Legendre::Design::BandStop<50>, 1 > ; + params[3] = _fw; + break; + default: + break; + } + } + + if(_kind == kinds::rbj) + { + switch (_type) { + case types::lowpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::RBJ::Design::LowPass, 1 > ; + params[3] = _q; + break; + case types::highpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::RBJ::Design::HighPass, 1 > ; + params[3] = _q; + break; + case types::bandpass1: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::RBJ::Design::BandPass1, 1 > ; + params[3] = _fw; + break; + case types::bandpass2: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::RBJ::Design::BandPass2, 1 > ; + params[3] = _fw; + break; + case types::bandstop: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::RBJ::Design::BandStop, 1 > ; + params[3] = _fw; + break; + case types::lowshelf: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::RBJ::Design::LowShelf, 1 > ; + params[3] = _gain; + params[4] = _slope; + break; + case types::highshelf: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::RBJ::Design::HighShelf, 1 > ; + params[3] = _gain; + params[4] = _slope; + break; + case types::bandshelf: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::RBJ::Design::BandShelf, 1 > ; + params[3] = _gain; + params[4] = _fw; + break; + case types::allpass: + if( _filter == NULL ) _filter = new Dsp::FilterDesign < Dsp::RBJ::Design::AllPass, 1 > ; + params[3] = _q; + break; + default: + break; + } + } + + if(_filter) + _filter->setParams(params); + + emit changed(); + } + + void filter_iir::set_data(iterator begin, iterator end) + { + clear(); + std::copy(begin, end, std::back_inserter(*this)); + + if(_filter) + { + double* ptr = this->data(); + _filter->process((int)size(), &ptr); + } + + gtl::analog_data::set_data(this->begin(), this->end()); + } + void filter_iir::set_rate(qreal value) + { + gtl::analog_data::set_rate(value); + + if(_filter) + { + _filter->setParam(0, _rate); + _filter->reset(); + } + } + } +} diff --git a/math/gtl_math_filter_iir.h b/math/gtl_math_filter_iir.h new file mode 100644 index 0000000..8d508ed --- /dev/null +++ b/math/gtl_math_filter_iir.h @@ -0,0 +1,401 @@ +#ifndef FILTER_IRR_H +#define FILTER_IRR_H + +#include "core/gtl_analog_data.h" + +#include "DspFilters/Dsp.h" + +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT filter_iir : public gtl::analog_data + { + Q_OBJECT + Q_PROPERTY(kinds kind READ kind WRITE set_kind NOTIFY kind_changed) + Q_PROPERTY(types type READ type WRITE set_type NOTIFY type_changed) + + Q_PROPERTY(int order READ order WRITE set_order NOTIFY order_changed) + Q_PROPERTY(qreal frequency READ frequency WRITE set_frequency NOTIFY frequency_changed) + Q_PROPERTY(qreal gain READ gain WRITE set_gain NOTIFY gain_changed) + Q_PROPERTY(qreal width READ width WRITE set_width NOTIFY width_changed) + Q_PROPERTY(qreal ripple READ ripple WRITE set_ripple NOTIFY ripple_changed) + Q_PROPERTY(qreal stop READ stop WRITE set_stop NOTIFY stop_changed) + Q_PROPERTY(qreal q READ q WRITE set_q NOTIFY q_changed) + Q_PROPERTY(qreal slope READ slope WRITE set_slope NOTIFY slope_changed) + + public: + enum kinds + { + bessel, + butterworth, + chebyshev1, + chebyshev2, + elliptic, + legendre, + rbj, + undef + }; + Q_ENUMS(kinds) + + enum types + { + lowpass, + highpass, + bandpass, + bandpass1, + bandpass2, + bandstop, + lowshelf, + highshelf, + bandshelf, + allpass + }; + Q_ENUMS(types) + + public: + filter_iir(gtl::analog_data* parent, bool is_hidden = false); + + kinds kind() const; + types type() const; + int order() const; + qreal frequency() const; + qreal gain() const; + qreal width() const; + qreal ripple() const; + qreal stop() const; + qreal q() const; + qreal slope() const; + void reset() const; + + virtual void save(QDomElement& root_element) override; + virtual void load(const QDomElement& root_element) override; + virtual void get_state(QJsonObject& root) override; + + qreal response(qreal value) const; + + private: + void init(); + void toNull(); + + private: + Dsp::Filter* _filter; + kinds _kind; + types _type; + int _order; + qreal _fc; + qreal _gain; // gain dB + qreal _fw; // width frequency (band) + qreal _ripple; // ripple dB + qreal _stop; // stop band dB + qreal _q; + qreal _slope; + + + protected: + virtual void set_data(std::vector::iterator begin, std::vector::iterator end); + + + public slots: + void set_kind(kinds kind); + void set_type(types type); + void set_order(int order); + void set_frequency(qreal frequency); + void set_gain(qreal gain); + void set_width(qreal width); + void set_ripple(qreal ripple); + void set_stop(qreal stop); + void set_q(qreal q); + void set_slope(qreal slope); + + private slots: + virtual void set_rate(qreal value); + + signals: + void kind_changed(); + void type_changed(); + void order_changed(); + void frequency_changed(); + void gain_changed(); + void width_changed(); + void ripple_changed(); + void stop_changed(); + void q_changed(); + void slope_changed(); + + void changed(); + }; + + class MATH_EXPORT filter_iir_info + { + public: + + filter_iir_info() + { + QDomDocument doc("FILTERS"); + _info = doc.createElement("info"); + doc.appendChild(_info); + + QDomElement kind; + QDomElement type; + QDomElement param; + + ///bessel/// + kind = doc.createElement("bessel"); + + type = doc.createElement("lowpass"); + kind.appendChild(type); + + type = doc.createElement("highpass"); + kind.appendChild(type); + + type = doc.createElement("bandpass"); + type.setAttribute("0","width"); + kind.appendChild(type); + + type = doc.createElement("bandstop"); + type.setAttribute("0","width"); + kind.appendChild(type); + + type = doc.createElement("lowshelf"); + type.setAttribute("0","gain"); + kind.appendChild(type); + + _info.appendChild(kind); + /// + + ///butterworth/// + kind = doc.createElement("butterworth"); + + type = doc.createElement("lowpass"); + kind.appendChild(type); + + type = doc.createElement("highpass"); + kind.appendChild(type); + + type = doc.createElement("bandpass"); + type.setAttribute("0","width"); + kind.appendChild(type); + + type = doc.createElement("bandstop"); + type.setAttribute("0","width"); + kind.appendChild(type); + + type = doc.createElement("lowshelf"); + type.setAttribute("0","gain"); + kind.appendChild(type); + + type = doc.createElement("highshelf"); + type.setAttribute("0","gain"); + kind.appendChild(type); + + type = doc.createElement("bandshelf"); + type.setAttribute("0","width"); + type.setAttribute("1","gain"); + kind.appendChild(type); + + _info.appendChild(kind); + /// + + ///chebyshev1// + kind = doc.createElement("chebyshev1"); + + type = doc.createElement("lowpass"); + type.setAttribute("0","ripple"); + kind.appendChild(type); + + type = doc.createElement("highpass"); + type.setAttribute("0","ripple"); + kind.appendChild(type); + + type = doc.createElement("bandpass"); + type.setAttribute("0","width"); + type.setAttribute("1","ripple"); + kind.appendChild(type); + + type = doc.createElement("bandstop"); + type.setAttribute("0","width"); + type.setAttribute("1","ripple"); + kind.appendChild(type); + + _info.appendChild(kind); + /// + + ///chebyshev2// + kind = doc.createElement("chebyshev2"); + + type = doc.createElement("lowpass"); + type.setAttribute("0","stop"); + kind.appendChild(type); + + type = doc.createElement("highpass"); + type.setAttribute("0","stop"); + kind.appendChild(type); + + type = doc.createElement("bandpass"); + type.setAttribute("0","width"); + type.setAttribute("1","stop"); + kind.appendChild(type); + + type = doc.createElement("bandstop"); + type.setAttribute("0","width"); + type.setAttribute("1","stop"); + kind.appendChild(type); + + _info.appendChild(kind); + /// + + //elliptic// + kind = doc.createElement("elliptic"); + + type = doc.createElement("lowpass"); + type.setAttribute("0","ripple"); + type.setAttribute("1","slope"); + kind.appendChild(type); + + type = doc.createElement("highpass"); + type.setAttribute("0","ripple"); + type.setAttribute("1","slope"); + kind.appendChild(type); + + type = doc.createElement("bandpass"); + type.setAttribute("0","width"); + type.setAttribute("1","ripple"); + type.setAttribute("2","slope"); + kind.appendChild(type); + + type = doc.createElement("bandstop"); + type.setAttribute("0","width"); + type.setAttribute("1","ripple"); + type.setAttribute("2","slope"); + kind.appendChild(type); + + _info.appendChild(kind); + /// + + //legendre// + kind = doc.createElement("legendre"); + + type = doc.createElement("lowpass"); + kind.appendChild(type); + + type = doc.createElement("highpass"); + kind.appendChild(type); + + type = doc.createElement("bandpass"); + type.setAttribute("0","width"); + kind.appendChild(type); + + type = doc.createElement("bandstop"); + type.setAttribute("0","width"); + kind.appendChild(type); + + _info.appendChild(kind); + /// + + ///rbj// + kind = doc.createElement("rbj"); + + type = doc.createElement("lowpass"); + type.setAttribute("0","q"); + kind.appendChild(type); + + type = doc.createElement("highpass"); + type.setAttribute("0","q"); + kind.appendChild(type); + + type = doc.createElement("bandpass1"); + type.setAttribute("0","width"); + kind.appendChild(type); + + type = doc.createElement("bandpass2"); + type.setAttribute("0","width"); + kind.appendChild(type); + + type = doc.createElement("bandstop"); + type.setAttribute("0","width"); + kind.appendChild(type); + + type = doc.createElement("lowshelf"); + type.setAttribute("0","gain"); + type.setAttribute("1","slope"); + kind.appendChild(type); + + type = doc.createElement("highshelf"); + type.setAttribute("0","gain"); + type.setAttribute("1","slope"); + kind.appendChild(type); + + type = doc.createElement("bandshelf"); + type.setAttribute("0","gain"); + type.setAttribute("1","width"); + kind.appendChild(type); + + type = doc.createElement("allpass"); + type.setAttribute("0","q"); + kind.appendChild(type); + + _info.appendChild(kind); + /// + + } + + QDomElement info() + { + return _info; + } + + gtl::math::filter_iir::kinds kind(QString name) + { + if(name == QString("bessel")) + return gtl::math::filter_iir::kinds::bessel; + if(name == QString("butterworth")) + return gtl::math::filter_iir::kinds::butterworth; + if(name == QString("chebyshev1")) + return gtl::math::filter_iir::kinds::chebyshev1; + if(name == QString("chebyshev2")) + return gtl::math::filter_iir::kinds::chebyshev2; + if(name == QString("elliptic")) + return gtl::math::filter_iir::kinds::elliptic; + if(name == QString("legendre")) + return gtl::math::filter_iir::kinds::legendre; + if(name == QString("rbj")) + return gtl::math::filter_iir::kinds::rbj; + + return gtl::math::filter_iir::kinds::undef; + } + + gtl::math::filter_iir::types type(QString name) + { + if(name == QString("highpass")) + return gtl::math::filter_iir::types::highpass; + if(name == QString("bandpass")) + return gtl::math::filter_iir::types::bandpass; + if(name == QString("bandpass1")) + return gtl::math::filter_iir::types::bandpass1; + if(name == QString("bandpass2")) + return gtl::math::filter_iir::types::bandpass2; + if(name == QString("bandstop")) + return gtl::math::filter_iir::types::bandstop; + if(name == QString("lowshelf")) + return gtl::math::filter_iir::types::lowshelf; + if(name == QString("highshelf")) + return gtl::math::filter_iir::types::highshelf; + if(name == QString("bandshelf")) + return gtl::math::filter_iir::types::bandshelf; + if(name == QString("allpass")) + return gtl::math::filter_iir::types::allpass; + + return gtl::math::filter_iir::types::lowpass; + } + + private: + QDomElement _info; + + }; + } +} + +#endif // FILTER_IRR_H diff --git a/math/gtl_math_freq.cpp b/math/gtl_math_freq.cpp new file mode 100644 index 0000000..ee0807e --- /dev/null +++ b/math/gtl_math_freq.cpp @@ -0,0 +1,102 @@ +#include "gtl_math_freq.h" + +namespace gtl +{ + namespace math + { + freq::freq(gtl::analog_data *data) + : analog_value(data) + , _sum(0) + , _dc(qInf()) + { + _name = "frequency"; + } + + void freq::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _sum -= std::accumulate(begin, end, 0.0); + } + + void freq::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _sum += std::accumulate(begin, end, 0.0); + +// qDebug() << _data_ptr; + log_fronts("before", _fronts.begin(), _fronts.end()); + + std::set::iterator iter_remove_begin = _fronts.lower_bound(std::distance(_data.begin(), begin)); + std::set::iterator iter_remove_end = _fronts.lower_bound(std::distance(_data.begin(), end)); + + log_fronts("eracing fronts", iter_remove_begin, iter_remove_end); + + _fronts.erase(iter_remove_begin, iter_remove_end); + + qreal dc = _dc == qInf() ? _sum/_data.size() : _dc; + for(auto it = begin; it != end; it++) + { + qreal value_prev = _data[(std::distance(_data.begin(), it) - 1 + _data.size()) % _data.size()]; + qreal value = *it; + + if(value_prev < dc && value >= dc) + _fronts.insert(std::distance(_data.begin(), it)); + } + + log_fronts("after", _fronts.begin(), _fronts.end()); + } + + void freq::set_time(qreal value) + { + analog_value::set_time(value); + _fronts.clear(); + _sum = 0; + } + + qreal freq::dc() const + { + return _dc; + } + + void freq::set_dc(qreal value) + { + _dc = value; + emit dc_changed(); + } + + void freq::log_fronts(QString msg, std::set::iterator begin, std::set::iterator end) + { + return; + QString str = msg + ": "; + for(auto it = begin; it != end; it++) + str += QString::number(*it) + ", "; + + qDebug() << str; + } + + void freq::data_changed() + { + analog_value::data_changed(); + + if(_fronts.size() < 2) + { + _value = 0; + return; + } + + std::set::iterator iter_ptr = _fronts.upper_bound(_data_ptr); + + if(iter_ptr == _fronts.end()) + iter_ptr = _fronts.begin(); + + qreal period = 0; + if(iter_ptr == _fronts.begin()) + period = *(--_fronts.end()) - *iter_ptr; + else + period = -(*iter_ptr) + *(--iter_ptr) + _data.size(); + + period /= _fronts.size() - 1; + _value = _ad->get_rate() / period; + + emit value_changed(); + } + } +} diff --git a/math/gtl_math_freq.h b/math/gtl_math_freq.h new file mode 100644 index 0000000..179894d --- /dev/null +++ b/math/gtl_math_freq.h @@ -0,0 +1,47 @@ +#ifndef FREQ_H +#define FREQ_H + +#include + +#include "gtl_math_analog_value.h" + +#include "math_global.h" + +#include + +namespace gtl +{ + namespace math + { + class MATH_EXPORT freq : public analog_value + { + Q_OBJECT + Q_PROPERTY(qreal dc READ dc WRITE set_dc NOTIFY dc_changed) + public: + freq(gtl::analog_data *data); + + virtual void set_time(qreal value) override; + qreal dc() const; + void set_dc(qreal value); + + private: + qreal _sum; + std::set _fronts; + qreal _dc; + + private: + virtual void before_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + virtual void after_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + + void log_fronts(QString msg, std::set::iterator begin, std::set::iterator end); + + protected slots: + virtual void data_changed() override; + + signals: + void dc_changed(); + }; + } +} + +#endif // FREQ_H diff --git a/math/gtl_math_intg.cpp b/math/gtl_math_intg.cpp new file mode 100644 index 0000000..853816d --- /dev/null +++ b/math/gtl_math_intg.cpp @@ -0,0 +1,175 @@ +#include "gtl_math_intg.h" + +namespace gtl +{ + namespace math + { + intg::intg(gtl::analog_data* parent, bool is_hidden) + : gtl::analog_data(parent, is_hidden) + , _taps(1) +// , _delay(1) + , _threshold(1e+10) + , _autonorm(false) + , _autoreset(false) + { + if(parent == NULL) + return; + + _device = static_cast(analog_data::root()); + + if(_device) + _device->lock_ai(); + + QString name_base = tr("integrator"); + QString name = name_base; + int idx = 0; + while(parent->is_child_name_exist(name)) + { + idx++; + name = name_base + "_" + QString::number(idx); + } + + set_name(name); + + init(); + + if(_device) + _device->unlock_ai(); + } + + intg::~intg() + { + delete _delays; + } + + int intg::taps() const + { + return _taps; + } + + void intg::reset() + { + if(_device) + _device->lock_ai(); + + for( int i = 0; i < _taps; i++) + _delays->at(i) = 0; + + if(_device) + _device->unlock_ai(); + } + + void intg::set_taps(int value) + { + if( value > 0 && value != _taps) + { + _taps = value; + init(); + } + } + +// int intg::delay() const +// { +// return _delay; +// } + +// void intg::set_delay(int value) +// { +// if( value > 0 && value != _delay) +// { +// _delay = value; +// init(); +// } +// } + + void intg::set_data(iterator begin, iterator end) + { + clear(); + std::copy(begin, end, std::back_inserter(*this)); //to do... + + process(this->begin(), this->end()); + + gtl::analog_data::set_data(this->begin(), this->end()); + } + + void intg::init() + { + if(_device) + _device->lock_ai(); + + if(_delays) + delete _delays; + _delays = new std::vector(_taps); + + if(_device) + _device->unlock_ai(); + } + + void intg::process(iterator begin, iterator end) + { + if(_autoreset && (abs(_delays->back()) > _threshold)) + reset(); + + int size = std::distance(begin, end); + qreal dt = 1./get_rate(); + + for( int i = 0; i < size; i++) + { + // Обратный метод Эйлера : https://docs.exponenta.ru/simulink/slref/discretetimeintegrator.html + + qreal value = *(begin + i)*dt; + + for(int t = 0; t < _taps; t++) + { + value += _delays->at(t); + + _delays->at(t) = value; + } + + *(begin + i) = value; + } + return; + } + + qreal intg::threshold() const + { + return _threshold; + } + + void intg::set_threshold(qreal newThreshold) + { + if (qFuzzyCompare(_threshold, newThreshold)) + return; + _threshold = newThreshold; + emit threshold_changed(); + } + + bool intg::autonorm() const + { + return _autonorm; + } + + void intg::set_autonorm(bool newAuto_norm) + { + if (_autonorm == newAuto_norm) + return; + _autonorm = newAuto_norm; + emit autonorm_changed(); + } + + bool intg::autoreset() const + { + return _autoreset; + } + + void intg::set_autoreset(bool newAuto_reset) + { + if (_autoreset == newAuto_reset) + return; + _autoreset = newAuto_reset; + emit autoreset_changed(); + } + + + } +} diff --git a/math/gtl_math_intg.h b/math/gtl_math_intg.h new file mode 100644 index 0000000..d1bfe5d --- /dev/null +++ b/math/gtl_math_intg.h @@ -0,0 +1,67 @@ +#ifndef INTG_H +#define INTG_H + +#include "core/gtl_analog_data.h" +#include "core/gtl_device.h" + +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT intg : public gtl::analog_data + { + Q_OBJECT + Q_PROPERTY(int taps READ taps WRITE set_taps NOTIFY taps_changed) +// Q_PROPERTY(int delay READ delay WRITE set_delay NOTIFY delay_changed) + Q_PROPERTY(qreal threshold READ threshold WRITE set_threshold NOTIFY threshold_changed) + Q_PROPERTY(bool autonorm READ autonorm WRITE set_autonorm NOTIFY autonorm_changed) + Q_PROPERTY(bool autoreset READ autoreset WRITE set_autoreset NOTIFY autoreset_changed) + + public: + intg(gtl::analog_data* parent, bool is_hidden = false); + ~intg(); + + int taps() const; +// int delay() const; + void reset(); + + qreal threshold() const; + void set_threshold(qreal newThreshold); + bool autonorm() const; + void set_autonorm(bool newAutonorm); + bool autoreset() const; + void set_autoreset(bool newAutoreset); + + protected: + virtual void set_data(std::vector::iterator begin, std::vector::iterator end) override; + + private: + void init(); + void process(std::vector::iterator begin, std::vector::iterator end); + + private: + int _taps; + qreal _threshold; + bool _autonorm; + bool _autoreset; +// int _delay; + std::vector *_delays = nullptr; + gtl::device* _device; + + public slots: + void set_taps(int value); +// void set_delay(int value); + + signals: + void taps_changed(); + // void delay_changed(); + void threshold_changed(); + void autonorm_changed(); + void autoreset_changed(); + }; + } +} + +#endif // INTG_H diff --git a/math/gtl_math_kurt.cpp b/math/gtl_math_kurt.cpp new file mode 100644 index 0000000..46af860 --- /dev/null +++ b/math/gtl_math_kurt.cpp @@ -0,0 +1,74 @@ +#include "gtl_math_kurt.h" + + + +namespace gtl +{ + namespace math + { + kurt::kurt(gtl::analog_data *data) + : analog_value(data) + , _sum(0) + , _mean(0) + , _full_ring(false) + , _prev_data_ptr(-1) + { + _name = "kurtosis"; + } + + void kurt::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _sum -= std::accumulate(begin, end, 0.0); + } + + void kurt::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _sum += std::accumulate(begin, end, 0.0); + _mean = _sum/_data.size(); + } + + void kurt::data_changed() + { + + analog_value::data_changed(); + + if(_prev_data_ptr >= _data_ptr && !_full_ring) _full_ring = true; + else _prev_data_ptr = _data_ptr; + + int n = _full_ring ? _data.size() : _data_ptr; + if(n > 3) + { + qreal variance = 0.; + for(int i=0; i + +#include + +#include "gtl_math_analog_value.h" + +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT kurt : public analog_value + { + Q_OBJECT + + + public: + kurt(gtl::analog_data *data); + + private: + qreal _sum; + qreal _mean; + bool _full_ring; + int _prev_data_ptr; + + protected: + virtual void before_copying_data(std::vector::iterator begin, std::vector::iterator end); + virtual void after_copying_data(std::vector::iterator begin, std::vector::iterator end); + + + + signals: + + + + protected slots: + virtual void data_changed(); + + }; + } +} + +#endif // EXC diff --git a/math/gtl_math_max.cpp b/math/gtl_math_max.cpp new file mode 100644 index 0000000..cec8106 --- /dev/null +++ b/math/gtl_math_max.cpp @@ -0,0 +1,52 @@ +#include "gtl_math_max.h" + +namespace gtl +{ + namespace math + { + max::max(gtl::analog_data *data) + : analog_value(data) + { + _name = "max"; + } + + void max::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + std::set::iterator>::iterator iter_remove_begin = _maxs.lower_bound(begin); + std::set::iterator>::iterator iter_remove_end = _maxs.lower_bound(end); + + _maxs.erase(iter_remove_begin, iter_remove_end); + } + + void max::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _maxs.insert(std::max_element(begin, end)); + } + + void max::set_time(qreal value) + { + analog_value::set_time(value); + _maxs.clear(); + } + + void max::data_changed() + { + analog_value::data_changed(); + + if(_maxs.size()) + { + qreal max = **_maxs.begin(); + + for(std::set::iterator>::iterator it = _maxs.begin(); it != _maxs.end(); ++it) + { + if(**it > max) + max = **it; + } + + _value = max; + + emit value_changed(); + } + } + } +} diff --git a/math/gtl_math_max.h b/math/gtl_math_max.h new file mode 100644 index 0000000..9d2a36a --- /dev/null +++ b/math/gtl_math_max.h @@ -0,0 +1,37 @@ +#ifndef GTL_MATH_MAX_H +#define GTL_MATH_MAX_H + +#include +#include + +#include "gtl_math_analog_value.h" +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT max : public analog_value + { + Q_OBJECT + + public: + max(gtl::analog_data *data); + + virtual void set_time(qreal value) override; + + private: + std::set::iterator> _maxs; + + private: + virtual void before_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + virtual void after_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + + + protected slots: + virtual void data_changed() override; + }; + } +} + +#endif // GTL_MATH_MAX_H diff --git a/math/gtl_math_min.cpp b/math/gtl_math_min.cpp new file mode 100644 index 0000000..5199850 --- /dev/null +++ b/math/gtl_math_min.cpp @@ -0,0 +1,52 @@ +#include "gtl_math_min.h" + +namespace gtl +{ + namespace math + { + min::min(gtl::analog_data *data) + : analog_value(data) + { + _name = "min"; + } + + void min::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + std::set::iterator>::iterator iter_remove_begin = _mins.lower_bound(begin); + std::set::iterator>::iterator iter_remove_end = _mins.lower_bound(end); + + _mins.erase(iter_remove_begin, iter_remove_end); + } + + void min::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _mins.insert(std::min_element(begin, end)); + } + + void min::set_time(qreal value) + { + analog_value::set_time(value); + _mins.clear(); + } + + void min::data_changed() + { + analog_value::data_changed(); + + if(_mins.size()) + { + qreal min = **_mins.begin(); + + for(std::set::iterator>::iterator it = _mins.begin(); it != _mins.end(); ++it) + { + if(**it > min) + min = **it; + } + + _value = min; + + emit value_changed(); + } + } + } +} diff --git a/math/gtl_math_min.h b/math/gtl_math_min.h new file mode 100644 index 0000000..7505e03 --- /dev/null +++ b/math/gtl_math_min.h @@ -0,0 +1,37 @@ +#ifndef GTL_MATH_MIN_H +#define GTL_MATH_MIN_H + +#include +#include + +#include "gtl_math_analog_value.h" +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT min : public analog_value + { + Q_OBJECT + + public: + min(gtl::analog_data *data); + + virtual void set_time(qreal value) override; + + private: + std::set::iterator> _mins; + + private: + virtual void before_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + virtual void after_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + + + protected slots: + virtual void data_changed() override; + }; + } +} + +#endif // GTL_MATH_MIN_H diff --git a/math/gtl_math_octv.cpp b/math/gtl_math_octv.cpp new file mode 100644 index 0000000..b74ff71 --- /dev/null +++ b/math/gtl_math_octv.cpp @@ -0,0 +1,339 @@ +#include "gtl_math_octv.h" + +namespace gtl +{ + namespace math + { + octv::octv(gtl::analog_data *ad) + : QObject(ad) + , _ad(ad) + , _min(10) + , _max(1024) + , _r(octv::ratios::two) + , _l(octv::looks::amplitude) + , _frac(octv::fractions::one) + , _r_rbw(5) + { + _frequencies = new std::vector; + + _spec = new gtl::math::spec(gtl::math::spec::types::ausp, _ad); + _spec->set_window(gtl::math::spec::windows::blackman); + _spec->set_unit(gtl::units::unit); + _spec->set_overlap(0); + _spec->set_average(1); + connect(_spec, >l::math::spec::changed, this, &octv::spec_changed); +// connect(_spec, >l::math::spec::average_changed, this, &octv::average_changed); +// connect(_spec, >l::math::spec::overlap_changed, this, &octv::overlap_changed); +// connect(_spec, >l::math::spec::unit_changed, this, &octv::unit_changed); + connect(_spec, >l::math::spec::acquired, this, &octv::acquired); + + init(); + } + + qreal octv::left(int idx) + { + if(idx < _frequencies->size()) + { + return _gfl * _frequencies->at(idx); + } + return 0.; + } + + qreal octv::right(int idx) + { + if(idx < _frequencies->size()) + { + return _gfh * _frequencies->at(idx); + } + return 0.; + } + + qreal octv::minimum() const + { + return _min; + } + + void octv::set_minimum(qreal value) + { + if(value != _min && value > 0.1) + { + _min = value; + init(); + } + } + + qreal octv::maximum() const + { + return _max; + } + + void octv::set_maximum(qreal value) + { + if(value > _ad->get_rate()) + value = _ad->get_rate(); + + if(value != _max && value >= 1) + { + _max = value; + init(); + } + } + + octv::ratios octv::ratio() const + { + return _r; + } + + void octv::set_ratio(ratios value) + { + if(value != _r) + { + _r = value; + init(); + } + } + + octv::looks octv::look() const + { + return _l; + } + + void octv::set_look(looks value) + { + if(value != _l) + { + _l = value; + } + } + + octv::fractions octv::fraction() const + { + return _frac; + } + + void octv::set_fraction(fractions value) + { + if(value != _frac) + { + _frac = value; + init(); + } + } + + units octv::unit() const + { + return _unit; + } + + void octv::set_unit(units value) + { + if( value != _unit ) + { + _unit = value; + emit unit_changed(); + } + } + +// int octv::average() const +// { +// if(_spec) +// { +// return _spec->average(); +// } +// return 1; +// } + +// void octv::set_average(int value) +// { +// if(_spec) +// { +// _spec->set_average(value); +// } +// } + +// qreal octv::overlap() const +// { +// if(_spec) +// { +// return _spec->overlap(); +// } +// return 0; +// } + +// void octv::set_overlap(int value) +// { +// if(_spec) +// { +// _spec->set_overlap(value); +// } +// } + + void octv::init() + { + _frac_num = 1; + if(_frac == gtl::math::octv::fractions::one_third) + _frac_num = 3; + else if(_frac == gtl::math::octv::fractions::one_sixth) + _frac_num = 6; + else if(_frac == gtl::math::octv::fractions::one_twelwe) + _frac_num = 12; + + qreal frac_num = (qreal) _frac_num; + + _gfl = g_pow((-1.)/(2.*frac_num), true); + _gfh = g_pow(1./(2.*frac_num), true); + + /// Calculate frequencies/// + qreal tmp_min = g_pow(_min/1000., false); + qreal tmp_max = g_pow(_max/1000., false); + if(_frac_num%2) + { + tmp_min = tmp_min*frac_num + 30.; + tmp_max = tmp_max*frac_num + 30.; + } + else + { + tmp_min = (tmp_min*frac_num*2. + 59.)/2.; + tmp_max = (tmp_max*frac_num*2. + 59.)/2.; + } + int min = std::floor(tmp_min); + int max = std::ceil(tmp_max); + + if(min > max) + max = min; + + _frequencies->clear(); + + for(int i = min; i <= max; i++) + { + qreal frequency = _frac_num%2 ? g_pow((i - 30.)/frac_num, true) : g_pow((2.*i - 59.)/(2.*frac_num), true); + frequency *= 1000.; + + if(_gfl * frequency < _max && _gfh * frequency > _min) + { + _frequencies->push_back(frequency); + } + } + ///////////////////////////// + + _spec->set_resolution(left(0)/_r_rbw); + _spec->set_frequency(right(_frequencies->size() - 1)); + + calculate(); + + emit initialized(); + } + + std::pair octv::calculate_band_indexes(qreal frequency) + { + qreal res = _spec->resolution(); + std::pair band; + band.first = (int) (_gfl * frequency / res); + band.second = (int) (_gfh * frequency / res) - 1; + if(band.first > band.second) + band.second = band.first; + + return band; + } + + qreal octv::calculate_octave(std::vector::iterator begin, std::vector::iterator end) + { + qreal result = 0; + if(_l == octv::looks::amplitude) + { + result = *std::max_element(begin, end); + } + else if(_l == octv::looks::rms) + { + qreal sum = 0; + for(auto it = begin; it < end; ++it) + { + sum += pow(*it, 2); + } + sum /= std::distance(begin, end); + result = sqrt(sum); + } + + if( _unit == gtl::db ) + { + result = 20 * log(result/_ad->reference()); + } + + return result; + } + + qreal octv::g_pow(qreal value, bool direction) + { + if(_r == octv::ratios::two) + { + if(direction) + { + return pow(2., value); + } + else + { + return log2(value); + } + } + else if(_r == octv::ratios::ten) + { + if(direction) + { + return pow(10., value * 3./10.); + } + else + { + return log(value) * 10./3.; + } + } + + return 0.; + } + + void octv::calculate() + { + clear(); + + std::pair band_indexes; + std::pair result; + + for(auto it : *_frequencies) + { + result.first = it; + band_indexes = calculate_band_indexes(it); + if(band_indexes.second >= _spec->size()) + band_indexes.second = _spec->size() - 1; + result.second = calculate_octave(_spec->begin() + band_indexes.first, _spec->begin() + band_indexes.second); + push_back(result); + } + } + + void octv::before_updating() + { + + } + + void octv::after_updating() + { + + } + + void octv::spec_changed() + { + _ad->lock_device(); + + calculate(); + + _ad->unlock_device(); + + emit changed(); + } + + void octv::set_rate(qreal value) + { + _ad->set_rate(value); + + init(); + } + } +} diff --git a/math/gtl_math_octv.h b/math/gtl_math_octv.h new file mode 100644 index 0000000..1db9752 --- /dev/null +++ b/math/gtl_math_octv.h @@ -0,0 +1,138 @@ +#ifndef OCTV_H +#define OCTV_H + +#include + +#include "core/gtl_analog_data.h" +#include "core/gtl.h" +#include "gtl_math_spec.h" + +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT octv : public QObject, public std::vector> + { + Q_OBJECT + + Q_PROPERTY(qreal minimum READ minimum WRITE set_minimum NOTIFY minimum_changed); + Q_PROPERTY(qreal maximum READ maximum WRITE set_maximum NOTIFY maximum_changed); + Q_PROPERTY(ratios ratio READ ratio WRITE set_ratio NOTIFY ratio_changed); + Q_PROPERTY(looks look READ look WRITE set_look NOTIFY look_changed); + Q_PROPERTY(fractions fraction READ fraction WRITE set_fraction NOTIFY fraction_changed); + Q_PROPERTY(units unit READ unit WRITE set_unit NOTIFY unit_changed); +// Q_PROPERTY(windows window READ window WRITE set_window NOTIFY window_changed); +// Q_PROPERTY(int average READ average WRITE set_average NOTIFY average_changed); +// Q_PROPERTY(int overlap READ overlap WRITE set_overlap NOTIFY overlap_changed); + + public: + enum ratios + { + two, + ten + }; + Q_ENUM(ratios) + + enum looks + { + amplitude, + rms + }; + Q_ENUM(looks) + + enum fractions + { + one, + one_third, + one_sixth, + one_twelwe + }; + Q_ENUM(fractions) + + + public: + octv(gtl::analog_data *ad); + + gtl::analog_data *ad() const; + + qreal left(int idx); + qreal right(int idx); + + qreal minimum() const; + void set_minimum(qreal value); + + qreal maximum() const; + void set_maximum(qreal value); + + ratios ratio() const; + void set_ratio(ratios value); + + looks look() const; + void set_look(looks value); + + fractions fraction() const; + void set_fraction(fractions value); + + units unit() const; + void set_unit(units value); + +// int average() const; +// void set_average(int value); + +// qreal overlap() const; +// void set_overlap(int value); + + private: + void init(); + std::pair calculate_band_indexes(qreal frequency); + qreal calculate_octave(std::vector::iterator begin, std::vector::iterator end); + qreal g_pow(qreal value, bool direction); + + gtl::analog_data* _ad; + + qreal _min; // Минимальная частота + qreal _max; // Максимальная частота + ratios _r; // Изменение в 2 или в 10 раз + looks _l; // Представление - амплитуда или СКЗ + fractions _frac; // Делитель полосы октавы + units _unit; + + gtl::math::spec *_spec = NULL; // Спектр + std::vector *_frequencies = NULL; // Частоты октав (нижняя, верхняя, нижняя,...) + int _r_rbw; // Делитель разрешения + qreal _gfl; // Коэффициент для вычисления нижней частоты октавы + qreal _gfh; // Коэффициент для вычисления верхней частоты октавы + int _frac_num; // Делитель полосы октавы - число + + protected: + virtual void calculate(); + virtual void before_updating(); + virtual void after_updating(); + + private slots: + virtual void spec_changed(); + virtual void set_rate(qreal value); + + signals: + void minimum_changed(); + void maximum_changed(); + void ratio_changed(); + void look_changed(); + void fraction_changed(); + void unit_changed(); + +// void window_changed(); +// void average_changed(); +// void overlap_changed(); + + void initialized(); + void changed(); + void acquired(qreal); + + }; + } +} + +#endif // OCTV_H diff --git a/math/gtl_math_offset.cpp b/math/gtl_math_offset.cpp new file mode 100644 index 0000000..65d6258 --- /dev/null +++ b/math/gtl_math_offset.cpp @@ -0,0 +1,35 @@ +#include "gtl_math_offset.h" + + + +namespace gtl +{ + namespace math + { + offset::offset(gtl::analog_data *data) + : analog_value(data) + , _sum(0) + { + _name = "offset"; + } + + void offset::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _sum -= std::accumulate(begin, end, 0.0); + } + + void offset::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _sum += std::accumulate(begin, end, 0.0); + } + + void offset::data_changed() + { + analog_value::data_changed(); + + _value = _sum/_data.size(); + + emit value_changed(); + } + } +} diff --git a/math/gtl_math_offset.h b/math/gtl_math_offset.h new file mode 100644 index 0000000..3cbd90f --- /dev/null +++ b/math/gtl_math_offset.h @@ -0,0 +1,38 @@ +#ifndef GTL_MATH_OFFSET +#define GTL_MATH_OFFSET + +#include + +#include + +#include "gtl_math_analog_value.h" + +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT offset : public analog_value + { + Q_OBJECT + + + public: + offset(gtl::analog_data *data); + + protected: + virtual void before_copying_data(std::vector::iterator begin, std::vector::iterator end); + virtual void after_copying_data(std::vector::iterator begin, std::vector::iterator end); + + protected slots: + virtual void data_changed(); + + private: + qreal _sum; + + }; + } +} + +#endif // GTL_MATH_OFFSET diff --git a/math/gtl_math_osc_meas.cpp b/math/gtl_math_osc_meas.cpp new file mode 100644 index 0000000..5a850eb --- /dev/null +++ b/math/gtl_math_osc_meas.cpp @@ -0,0 +1,83 @@ +#include "math/gtl_math_osc_meas.h" +#include "math/gtl_math.h" + +namespace gtl +{ + namespace math + { + + osc_meas::osc_meas() + { + + } + + qreal osc_meas::min(const std::vector::iterator& begin, const std::vector::iterator& end) + { + return mathFunctions::min(begin, end); + } + + qreal osc_meas::max(const std::vector::iterator& begin, const std::vector::iterator& end) + { + return mathFunctions::max(begin, end); + } + + qreal osc_meas::offset(const std::vector::iterator& begin, const std::vector::iterator& end) + { + return mathFunctions::mean(begin, end); + } + + qreal osc_meas::peak(const std::vector::iterator& begin, const std::vector::iterator& end) + { + + qreal amax = abs(mathFunctions::max(begin, end) - mathFunctions::mean(begin, end)); + qreal amin = abs(mathFunctions::min(begin, end) - mathFunctions::mean(begin, end)); + + return amax > amin ? amax : amin; + } + + qreal osc_meas::peak_to_peak(const std::vector::iterator& begin, const std::vector::iterator& end) + { + return mathFunctions::max(begin, end) - mathFunctions::min(begin, end); + } + + qreal osc_meas::rms(const std::vector::iterator& begin, const std::vector::iterator& end) + { + return mathFunctions::rms(begin, end); + } + + qreal osc_meas::freq(const std::vector::iterator& begin, const std::vector::iterator& end, qreal f_sample) + { + qreal dc = mathFunctions::mean(begin, end); + + std::vector fronts; + for(auto it = begin; it != (end - 1); ++it) + if(*it <= dc && *(it+1) > dc) + fronts.push_back(std::distance(begin, it)); + + qreal value = 0.; + + if(fronts.size()>1) + { + for(auto it = fronts.begin(); it != (fronts.end() - 1); ++it) + value += *(it+1) - *it; + value /= fronts.size() - 1; + value = f_sample/value; + } + return value; + } + + qreal osc_meas::period(const std::vector::iterator& begin, const std::vector::iterator& end, qreal f_sample) + { + qreal value = 0; + qreal frequency = osc_meas::freq(begin, end, f_sample); + if(frequency) + value = 1./frequency; + return value; + } + + qreal osc_meas::kurt(const std::vector::iterator& begin, const std::vector::iterator& end) + { + return mathFunctions::kurt(begin, end); + } + } +} diff --git a/math/gtl_math_osc_meas.h b/math/gtl_math_osc_meas.h new file mode 100644 index 0000000..b7c83dc --- /dev/null +++ b/math/gtl_math_osc_meas.h @@ -0,0 +1,70 @@ + +#ifndef GTL_MATH_OSC_MEAS_H +#define GTL_MATH_OSC_MEAS_H + +#include +#include +#include + +#include "core/gtl_analog_data.h" +#include "math_global.h" + +namespace gtl { + namespace math { + + class MATH_EXPORT osc_meas + { + Q_GADGET + public: + osc_meas(); + + enum class types + { + min, + max, + offset, + peak, + peak_to_peak, + rms, + freq, + period, + kurt + }; + Q_ENUM(types) + + struct params + { + int id; + QString chan; + osc_meas::types type; + qreal value; + gtl::analog_data* ad; + explicit params() + { + chan = ""; + type = math::osc_meas::types::min; + value = 0; + id = 0; + ad = nullptr; + } + }; + + static qreal min(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal max(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal offset(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal peak(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal peak_to_peak(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal rms(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal freq(const std::vector::iterator& begin, const std::vector::iterator& end, qreal f_sample); + static qreal period(const std::vector::iterator& begin, const std::vector::iterator& end, qreal f_sample); + static qreal kurt(const std::vector::iterator& begin, const std::vector::iterator& end); + }; + } // namespace math +} // namespace gtl + + Q_DECLARE_METATYPE(gtl::math::osc_meas::params) + typedef QList QListOscMeasParams; + typedef QSharedPointer OscMeasParamsListPtr; + Q_DECLARE_METATYPE(OscMeasParamsListPtr) + +#endif // GTL_MATH_OSC_MEAS_H diff --git a/math/gtl_math_peak.cpp b/math/gtl_math_peak.cpp new file mode 100644 index 0000000..f8a7b65 --- /dev/null +++ b/math/gtl_math_peak.cpp @@ -0,0 +1,44 @@ +#include "gtl_math_peak.h" + +namespace gtl +{ + namespace math + { + peak::peak(gtl::analog_data *data) + : analog_value(data) + { + _name = "peak"; + + _max = new gtl::math::max(data); + _min = new gtl::math::min(data); + _offset = new gtl::math::offset(data); + } + + peak::~peak() + { + delete _max; + delete _min; + delete _offset; + } + + void peak::set_time(qreal value) + { + analog_value::set_time(value); + _max->set_time(value); + _min->set_time(value); + _offset->set_time(value); + } + + void peak::data_changed() + { + analog_value::data_changed(); + + qreal amax = abs(_max->value() - _offset->value()); + qreal amin = abs(_min->value() - _offset->value()); + + _value = amax > amin ? amax : amin; + + emit value_changed(); + } + } +} diff --git a/math/gtl_math_peak.h b/math/gtl_math_peak.h new file mode 100644 index 0000000..6a87b43 --- /dev/null +++ b/math/gtl_math_peak.h @@ -0,0 +1,40 @@ +#ifndef GTL_MATH_PEAK +#define GTL_MATH_PEAK + +#include +#include + +#include "gtl_math_analog_value.h" +#include "math_global.h" +#include "gtl_math_max.h" +#include "gtl_math_min.h" +#include "gtl_math_offset.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT peak : public analog_value + { + Q_OBJECT + + + public: + peak(gtl::analog_data *data); + + ~peak(); + + virtual void set_time(qreal value) override; + + protected slots: + virtual void data_changed() override; + + private: + gtl::math::max *_max = nullptr; + gtl::math::min *_min = nullptr; + gtl::math::offset *_offset = nullptr; + }; + } +} + +#endif // GTL_MATH_PEAK diff --git a/math/gtl_math_peak_to_peak.cpp b/math/gtl_math_peak_to_peak.cpp new file mode 100644 index 0000000..449244a --- /dev/null +++ b/math/gtl_math_peak_to_peak.cpp @@ -0,0 +1,38 @@ +#include "gtl_math_peak_to_peak.h" + +namespace gtl +{ + namespace math + { + peak_to_peak::peak_to_peak(gtl::analog_data *data) + : analog_value(data) + { + _name = "peak to peak"; + + _max = new gtl::math::max(data); + _min = new gtl::math::min(data); + } + + peak_to_peak::~peak_to_peak() + { + delete _max; + delete _min; + } + + void peak_to_peak::set_time(qreal value) + { + analog_value::set_time(value); + _max->set_time(value); + _min->set_time(value); + } + + void peak_to_peak::data_changed() + { + analog_value::data_changed(); + + _value = _max->value() - _min->value(); + + emit value_changed(); + } + } +} diff --git a/math/gtl_math_peak_to_peak.h b/math/gtl_math_peak_to_peak.h new file mode 100644 index 0000000..703928b --- /dev/null +++ b/math/gtl_math_peak_to_peak.h @@ -0,0 +1,39 @@ +#ifndef GTL_MATH_PEAK_TO_PEAK +#define GTL_MATH_PEAK_TO_PEAK + +#include +#include + +#include "gtl_math_analog_value.h" +#include "math_global.h" +#include "gtl_math_max.h" +#include "gtl_math_min.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT peak_to_peak : public analog_value + { + Q_OBJECT + + + public: + peak_to_peak(gtl::analog_data *data); + + ~peak_to_peak(); + + virtual void set_time(qreal value) override; + + protected slots: + virtual void data_changed(); + + + private: + gtl::math::max *_max = nullptr; + gtl::math::min *_min = nullptr; + }; + } +} + +#endif // GTL_MATH_PEAK_TO_PEAK diff --git a/math/gtl_math_rms.cpp b/math/gtl_math_rms.cpp new file mode 100644 index 0000000..cef7327 --- /dev/null +++ b/math/gtl_math_rms.cpp @@ -0,0 +1,35 @@ +#include "gtl_math_rms.h" +#include "gtl_math.h" + +namespace gtl +{ + namespace math + { + + rms::rms(gtl::analog_data *data) + : analog_value(data) + , _squared_sum_value(0.) + { + _name = "rms"; + } + + void rms::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _squared_sum_value -= mathFunctions::squared_sum(begin, end); + } + + void rms::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _squared_sum_value += mathFunctions::squared_sum(begin, end); + } + + void rms::data_changed() + { + analog_value::data_changed(); + if(_data.size() != 0) + _value = qSqrt(_squared_sum_value/_data.size()); + + emit value_changed(); + } + } +} diff --git a/math/gtl_math_rms.h b/math/gtl_math_rms.h new file mode 100644 index 0000000..e67faf0 --- /dev/null +++ b/math/gtl_math_rms.h @@ -0,0 +1,31 @@ +#ifndef RMS +#define RMS + +#include "gtl_math_analog_value.h" +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT rms : public analog_value + { + Q_OBJECT + + public: + rms(gtl::analog_data *data); + + protected: + void before_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + void after_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + + protected slots: + void data_changed() override; + + private: + qreal _squared_sum_value; + }; + } +} + +#endif // RMS diff --git a/math/gtl_math_spec.cpp b/math/gtl_math_spec.cpp new file mode 100644 index 0000000..88f184f --- /dev/null +++ b/math/gtl_math_spec.cpp @@ -0,0 +1,382 @@ +#include "gtl_math_spec.h" + +namespace gtl +{ + namespace math + { + spec::spec(types type, gtl::analog_data *ad) + : QObject(ad) + , _is_acq(true) + , _av(1) + , _un(gtl::units::unit) + , _av_cnt(0) + , _av_filled(false) + , _ad(ad) + , _type(type) + { + + _fft = new gtl::math::fft(ad); + connect(_fft, >l::math::fft::changed, this, &spec::data_changed); + connect(_fft, >l::math::fft::initialized, this, &spec::init); + connect(_fft, >l::math::fft::frequency_changed, this, &spec::frequency_changed); + connect(_fft, >l::math::fft::resolution_changed, this, &spec::resolution_changed); + connect(_fft, >l::math::fft::window_changed, this, &spec::window_changed); + connect(_fft, >l::math::fft::lines_changed, this, &spec::lines_changed); + connect(_fft, >l::math::fft::overlap_changed, this, &spec::overlap_changed); + + init(); + } + + spec::~spec() + { + emit deleting(); + + cleanup(); + if(_fft) + delete _fft; + } + + qreal spec::frequency() const + { + return _fft->frequency(); + } + + void spec::set_frequency(qreal value) + { + _fft->set_frequency(value); + } + + qreal spec::resolution() const + { + return _fft->resolution(); + } + + void spec::set_resolution(qreal value) + { + _fft->set_resolution(value); + } + + spec::windows spec::window() const + { + return (spec::windows) _fft->window(); + } + + void spec::set_window(windows value) + { + _fft->set_window((fft::windows) value); + } + + int spec::lines() const + { + return _fft->lines(); + } + + void spec::set_lines(int value) + { + _fft->set_lines(value); + } + + uint spec::average() const + { + return _av; + } + + void spec::set_average(int value) + { + if( value != _av && value > 0) + { + _av = value; + init(); + emit average_changed(); + } + } + + qreal spec::overlap() const + { + return _fft->overlap(); + } + + void spec::set_overlap(int value) + { + _fft->set_overlap(value); + } + + units spec::unit() const + { + return _un; + } + + units spec::get_unit() const + { + return _un; + } + + void spec::set_unit(units value) + { + if( value != _un ) + { + _un = value; + emit unit_changed(); + } + } + + analog_data *spec::ad() const + { + return _fft->ad(); + } + + spec::types spec::type() const + { + return _type; + } + + void spec::set_type(types type) + { + _type = type; + init(); + } + + qreal spec::acq_time() const + { +// int nl = _fft->lines(); +// qreal f = _fft->frequency(); +// qreal ol = _fft->overlap(); +//// return (nl + 1)/f * ((_av - 1)* (1.0 - ol/100.0) + 1); +// return (nl + 1)/f * ((_av)* (1.0 - ol/100.0) + 1); + +// int size = ad()->get_rate()/_fft->resolution(); + qreal time = 1./_fft->resolution(); //size/ad()->get_rate() * 1.1; //1./_fft->resolution() + + return time * _av * (1.0 - _fft->overlap()/100.0); + } + + int spec::samples() const + { + return _fft->samples(); + } + + void spec::init() + { + _ad->lock_device(); + cleanup(); + + _size_fft_out = _fft->size_fft_out(); + _size_fft_calc = _fft->size_fft_calc(); + + _av_cnt = 0; + _av_filled = false; + + for(int i = 0; i < _av; i++) + _buffer_out.push_back(new std::vector(_size_fft_out)); + _calculated_it = _buffer_out[0]->begin(); + + resize(_size_fft_out); + + if(_type == spen) + { + _ispen_fft = new std::vector>(_fft->size()); + _envelope = new std::vector(_fft->size()); + _spen_fft = new std::vector>(_fft->size()); + + _ispen_plan = fftw_plan_dft_1d(_fft->size(), reinterpret_cast(_fft->data()), reinterpret_cast(_ispen_fft->data()), FFTW_BACKWARD, FFTW_ESTIMATE); + + _spen_plan = fftw_plan_dft_r2c_1d(_fft->size(), _envelope->data(), reinterpret_cast(_spen_fft->data()), FFTW_ESTIMATE); + } + + _ad->unlock_device(); + + emit initialized(); + emit acq_time_changed(); + } + + void spec::cleanup() + { + _ad->lock_device(); + + if(_ispen_plan) + { + fftw_destroy_plan(_ispen_plan); + _ispen_plan = NULL; + } + + if(_spen_plan) + { + fftw_destroy_plan(_spen_plan); + _spen_plan = NULL; + } + + for(std::vector* ptr:_buffer_out) + { + if( ptr ) + { + ptr->clear(); + ptr->shrink_to_fit(); + delete ptr; + } + } + _buffer_out.clear(); + _buffer_out.shrink_to_fit(); + + if(_ispen_fft) + { + _ispen_fft->clear(); + _ispen_fft->shrink_to_fit(); + delete _ispen_fft; + _ispen_fft = NULL; + } + if(_envelope) + { + _envelope->clear(); + _envelope->shrink_to_fit(); + delete _envelope; + _envelope = NULL; + } + if(_spen_fft) + { + _spen_fft->clear(); + _spen_fft->shrink_to_fit(); + delete _spen_fft; + _spen_fft = NULL; + } + + clear(); + shrink_to_fit(); + + _ad->unlock_device(); + } + + void spec::calculate_ampl(std::vector> *in_it, std::vector::iterator out_it, int size) + { + qreal value; + qreal ref = _ad->reference(); + for( int i = 0; i < size && i < spec::size(); i++) + { + value = abs(in_it->at(i))/_size_fft_calc; + + if( _un == gtl::db ) + { + value = 20 * log(value/ref); + } + + *(out_it + i) = value; + } + } + + void spec::calculate_phase(std::vector > *in_it, iterator out_it, int size) + { + for( int i = 0; i < size && i < spec::size(); i++) + { + *(out_it + i) = arg(in_it->at(i)); + } + } + + void spec::before_updating() + { + + } + + void spec::after_updating() + { + + } + + void spec::calculate() + { + if(_type == ausp) + { + calculate_ampl(_fft, _calculated_it, _size_fft_out); + } + else if(_type == spen) + { + if(_ispen_plan && _spen_plan) + { + for(size_t i = 0; i < _size_fft_calc; i++) + { + if( i > 0) + { + _fft->at(i) *= 2; + } + _fft->at(i) /= _size_fft_calc; + } + + if(_ispen_plan) fftw_execute(_ispen_plan); + for(size_t i = 0; i < _fft->size(); i++) + _envelope->at(i) = abs(_ispen_fft->at(i)); + + if(_spen_plan) fftw_execute(_spen_plan); + calculate_ampl(_spen_fft, _calculated_it, _size_fft_out); + + } + } + else if(_type == phase) + { + calculate_phase(_fft, _calculated_it, _size_fft_out); + } + } + + + void spec::set_rate(qreal value) + { + _ad->set_rate(value); + + _fft->init(); + } + + void spec::stop_acq() + { + _is_acq = false; + } + + void spec::data_changed() + { + + if(!_is_acq) + return; + + _ad->lock_device(); + + before_updating(); + + // Вычисление , усреднение + int avdiv = _av_filled ? _av : _av_cnt + 1; + if( _av > 1 ) + _calculated_it = _buffer_out[_av_cnt]->begin(); + else + _calculated_it = begin(); + + calculate(); + + if( _av > 1 ) + { + for(int i = 0; i < _size_fft_out; i++) + { + qreal value = 0; + for(int j = 0; j < avdiv; j++) + value += _buffer_out[j]->at(i)/avdiv; + at(i) = value; + } + } + + _av_cnt++; + _ac = 100. * ( (qreal) _av_cnt )/_av; + emit acquired(_ac); + + if(_av_cnt == _av) + { + _av_cnt = 0; + _av_filled = true; + } + + after_updating(); + + _ad->unlock_device(); + + emit changed(); + +// unsigned long ttt = QDateTime::currentDateTime().currentMSecsSinceEpoch(); +// qDebug() << (ttt - _ttt)/1000.; +// _ttt = ttt; +// qDebug() << acq_time(); + } + } +} diff --git a/math/gtl_math_spec.h b/math/gtl_math_spec.h new file mode 100644 index 0000000..ee6a45d --- /dev/null +++ b/math/gtl_math_spec.h @@ -0,0 +1,149 @@ +#ifndef MATH_SPEC_H +#define MATH_SPEC_H + +#include +#include +#include +#include +#include +#include "fftw3.h" + +#include "core/gtl_analog_data.h" +#include "core/gtl.h" +#include "gtl_math_fft.h" +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT spec : public QObject, public std::vector + { + Q_OBJECT + + Q_PROPERTY(qreal frequency READ frequency WRITE set_frequency NOTIFY frequency_changed); + Q_PROPERTY(qreal resolution READ resolution WRITE set_resolution NOTIFY resolution_changed); + Q_PROPERTY(windows window READ window WRITE set_window NOTIFY window_changed); + Q_PROPERTY(int lines READ lines WRITE set_lines NOTIFY lines_changed); + Q_PROPERTY(int average READ average WRITE set_average NOTIFY average_changed); + Q_PROPERTY(int overlap READ overlap WRITE set_overlap NOTIFY overlap_changed); + Q_PROPERTY(units unit READ unit WRITE set_unit NOTIFY unit_changed); + Q_PROPERTY(qreal acq_time READ acq_time NOTIFY acq_time_changed); + + public: + + typedef fft::windows windows; + + enum types + { + ausp, + spen, + phase, + + undef = 0xff + }; + Q_ENUM(types) + + spec(types type, gtl::analog_data *ad); + ~spec(); + + qreal frequency() const; + void set_frequency(qreal value); + + qreal resolution() const; + void set_resolution(qreal value); + + windows window() const; + void set_window(windows value); + + int lines() const; + void set_lines(int value); + + uint average() const; + void set_average(int value); + + qreal overlap() const; + void set_overlap(int value); + + units unit() const; + units get_unit() const; + void set_unit(units value); + + gtl::analog_data *ad() const; + + types type() const; + void set_type(types type); + + qreal acq_time() const; + int samples() const; + +// void set_measures(SpecMeasParamsListPtr m); + private: + void cleanup(); + void calculate_ampl(std::vector> *in_it, std::vector::iterator out_it, int size); + void calculate_phase(std::vector> *in_it, std::vector::iterator out_it, int size); +// void measure(); + + gtl::math::fft* _fft; + + bool _is_acq; + + int _av; // Количество усреднение + units _un; // Единицы измерения + int _av_cnt; // Счетсик усреднений + bool _av_filled; // Флаг заполнения окна усреднения + qreal _ac; // Процент завершения усреднения + + gtl::analog_data* _ad; + + std::vector*> _buffer_out; + std::vector::iterator _calculated_it; // Итератор текущего буффера для вычисления спектра + + fftw_plan _ispen_plan = NULL; // оБПФ план комплексной огибающей + fftw_plan _spen_plan = NULL; // БПФ план комплексной огибающей + std::vector>* _ispen_fft = NULL; // Сигнал комплексной огибающей + std::vector>* _spen_fft = NULL; // Спектр комплексной огибающей + + int _size_fft_calc; // Размер буфера/ 2 - до частоты Котельникова + int _size_fft_out; // Количество отображаемых линий + +// unsigned long _ttt = 0; + + protected: + std::vector* _envelope = NULL; // Комплексная огибающая + types _type; + + protected: + virtual void calculate(); + virtual void before_updating(); + virtual void after_updating(); + + private slots: + virtual void data_changed(); + virtual void set_rate(qreal value); + void init(); + + public slots: + void stop_acq(); + + signals: + void frequency_changed(); + void resolution_changed(); + void window_changed(); + void lines_changed(); + void average_changed(); + void overlap_changed(); + void unit_changed(); + void acq_time_changed(); +// void measures_changed(); + + void initialized(); + void changed(); + void acquired(qreal); + void deleting(); + + }; + } +} + +#endif // MATH_SPEC_H diff --git a/math/gtl_math_spec_meas.cpp b/math/gtl_math_spec_meas.cpp new file mode 100644 index 0000000..ac515fd --- /dev/null +++ b/math/gtl_math_spec_meas.cpp @@ -0,0 +1,29 @@ + +#include "gtl_math_spec_meas.h" +#include "gtl_math.h" + +namespace gtl { + namespace math { + + spec_meas::spec_meas() + { + + } + + qreal spec_meas::rms(const std::vector::iterator& begin, const std::vector::iterator& end) + { + return mathFunctions::rms(begin, end); + } + + qreal spec_meas::max(const std::vector::iterator& begin, const std::vector::iterator& end) + { + return mathFunctions::max(begin, end); + } + + qreal spec_meas::freq_max(const std::vector::iterator& begin, const std::vector::iterator& end, const qreal resolution) + { + return std::distance(begin, std::max_element(begin, end))*resolution; + } + } // namespace math +} // namespace gtl + diff --git a/math/gtl_math_spec_meas.h b/math/gtl_math_spec_meas.h new file mode 100644 index 0000000..6199636 --- /dev/null +++ b/math/gtl_math_spec_meas.h @@ -0,0 +1,58 @@ + +#ifndef GTL_MATH_SPEC_MEAS_H +#define GTL_MATH_SPEC_MEAS_H + +#include +#include +#include + +#include "core/gtl_analog_data.h" +#include "math_global.h" + +namespace gtl { + namespace math { + + class MATH_EXPORT spec_meas + { + Q_GADGET + public: + spec_meas(); + + enum class types + { + rms, + max, + freq_max + }; + Q_ENUM(types) + + struct params + { + int id; + QString chan; + spec_meas::types type; + qreal value; + gtl::analog_data* ad; + explicit params() + { + chan = ""; + type = math::spec_meas::types::rms; + value = 0; + id = 0; + ad = nullptr; + } + }; + + static qreal rms(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal max(const std::vector::iterator& begin, const std::vector::iterator& end); + static qreal freq_max(const std::vector::iterator& begin, const std::vector::iterator& end, const qreal resolution); + }; + } // namespace math +} // namespace gtl + + Q_DECLARE_METATYPE(gtl::math::spec_meas::params) + typedef QList QListSpecMeasParams; + typedef QSharedPointer SpecMeasParamsListPtr; + Q_DECLARE_METATYPE(SpecMeasParamsListPtr) + +#endif // GTL_MATH_SPEC_MEAS_H diff --git a/math/gtl_math_sum.cpp b/math/gtl_math_sum.cpp new file mode 100644 index 0000000..81eafd3 --- /dev/null +++ b/math/gtl_math_sum.cpp @@ -0,0 +1,37 @@ +#include "gtl_math_sum.h" + + + +namespace gtl +{ + namespace math + { + sum::sum(gtl::analog_data *data) + : analog_value(data) + { + _name = "sum"; + } + + void sum::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _value -= std::accumulate(begin, end, 0.0); + } + + void sum::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _value += std::accumulate(begin, end, 0.0); + } + + void sum::data_changed() + { + //_value = std::accumulate(_ad->begin(), _ad->end(), _value); + + analog_value::data_changed(); + +// _value = std::accumulate(_data.begin(), _data.end(), 0.0); + + emit value_changed(); + + } + } +} diff --git a/math/gtl_math_sum.h b/math/gtl_math_sum.h new file mode 100644 index 0000000..d998f0c --- /dev/null +++ b/math/gtl_math_sum.h @@ -0,0 +1,41 @@ +#ifndef SUM +#define SUM + +#include + +#include + +#include "gtl_math_analog_value.h" + +#include "math_global.h" + +namespace gtl +{ + namespace math + { + class MATH_EXPORT sum : public analog_value + { + Q_OBJECT + + + public: + sum(gtl::analog_data *data); + + protected: + virtual void before_copying_data(std::vector::iterator begin, std::vector::iterator end); + virtual void after_copying_data(std::vector::iterator begin, std::vector::iterator end); + + + + signals: + + + + protected slots: + virtual void data_changed(); + + }; + } +} + +#endif // SUM diff --git a/math/gtl_math_var.cpp b/math/gtl_math_var.cpp new file mode 100644 index 0000000..2ecce5d --- /dev/null +++ b/math/gtl_math_var.cpp @@ -0,0 +1,50 @@ + +#include "gtl_math_var.h" +#include "gtl_math.h" + +namespace gtl { + namespace math { + + var::var(gtl::analog_data *data) + : analog_value(data) + , _variance(0.) + { + _name = "var"; + } + + void var::before_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _variance -= variance(begin, end); + } + + void var::after_copying_data(std::vector::iterator begin, std::vector::iterator end) + { + _variance += variance(begin, end); + } + + void var::data_changed() + { + analog_value::data_changed(); + if(_data.size() != 0) + _value = _variance; + + emit value_changed(); + } + + qreal var::variance(std::vector::iterator begin, std::vector::iterator end) + { + int n = std::distance(begin, end); + + qreal mean = std::accumulate(begin, end, 0.)/n; + + qreal v = 0.; + for(auto it = begin; it != end; ++it) + v += qPow((*it - mean), 2); + v /= n-1; + + return v; + } + + } // namespace math +} // namespace gtl + diff --git a/math/gtl_math_var.h b/math/gtl_math_var.h new file mode 100644 index 0000000..8142588 --- /dev/null +++ b/math/gtl_math_var.h @@ -0,0 +1,33 @@ + +#ifndef GTL_MATH_VAR_H +#define GTL_MATH_VAR_H + +#include "gtl_math_analog_value.h" +#include "math_global.h" + +namespace gtl { + namespace math { + + class MATH_EXPORT var : public analog_value + { + Q_OBJECT + public: + var(gtl::analog_data *data); + + protected: + void before_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + void after_copying_data(std::vector::iterator begin, std::vector::iterator end) override; + + protected slots: + void data_changed() override; + + private: + qreal variance(std::vector::iterator begin, std::vector::iterator end); + + qreal _variance; + }; + + } // namespace math +} // namespace gtl + +#endif // GTL_MATH_VAR_H diff --git a/math/math.pro b/math/math.pro new file mode 100644 index 0000000..e4403ad --- /dev/null +++ b/math/math.pro @@ -0,0 +1,110 @@ +QT -= gui +QT += xml + +TEMPLATE = lib +DEFINES += MATH_LIBRARY +TARGET = gtl_math + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + $$(DSPFILTERSPATH)/source/Bessel.cpp \ + $$(DSPFILTERSPATH)/source/Biquad.cpp \ + $$(DSPFILTERSPATH)/source/Butterworth.cpp \ + $$(DSPFILTERSPATH)/source/Cascade.cpp \ + $$(DSPFILTERSPATH)/source/ChebyshevI.cpp \ + $$(DSPFILTERSPATH)/source/ChebyshevII.cpp \ + $$(DSPFILTERSPATH)/source/Custom.cpp \ + $$(DSPFILTERSPATH)/source/Design.cpp \ + $$(DSPFILTERSPATH)/source/Documentation.cpp \ + $$(DSPFILTERSPATH)/source/Elliptic.cpp \ + $$(DSPFILTERSPATH)/source/Filter.cpp \ + $$(DSPFILTERSPATH)/source/Legendre.cpp \ + $$(DSPFILTERSPATH)/source/Param.cpp \ + $$(DSPFILTERSPATH)/source/PoleFilter.cpp \ + $$(DSPFILTERSPATH)/source/RBJ.cpp \ + $$(DSPFILTERSPATH)/source/RootFinder.cpp \ + $$(DSPFILTERSPATH)/source/State.cpp \ + gtl_math.cpp \ + gtl_math_ampl.cpp \ + gtl_math_analog_value.cpp \ + gtl_math_apfc.cpp \ + gtl_math_cross_spec.cpp \ + gtl_math_delta_phase.cpp \ + gtl_math_delta_phase_spec.cpp \ + gtl_math_diff.cpp \ + gtl_math_fft.cpp \ + gtl_math_filter_iir.cpp \ + gtl_math_intg.cpp \ + gtl_math_kurt.cpp \ + gtl_math_max.cpp \ + gtl_math_min.cpp \ + gtl_math_octv.cpp \ + gtl_math_offset.cpp \ + gtl_math_osc_meas.cpp \ + gtl_math_peak.cpp \ + gtl_math_peak_to_peak.cpp \ + gtl_math_rms.cpp \ + gtl_math_freq.cpp \ + gtl_math_spec.cpp \ + gtl_math_spec_meas.cpp \ + gtl_math_sum.cpp \ + gtl_math_var.cpp + +HEADERS += \ + gtl_math.h \ + gtl_math_ampl.h \ + gtl_math_analog_value.h \ + gtl_math_apfc.h \ + gtl_math_cross_spec.h \ + gtl_math_delta_phase.h \ + gtl_math_delta_phase_spec.h \ + gtl_math_diff.h \ + gtl_math_fft.h \ + gtl_math_filter_iir.h \ + gtl_math_intg.h \ + gtl_math_kurt.h \ + gtl_math_max.h \ + gtl_math_min.h \ + gtl_math_octv.h \ + gtl_math_offset.h \ + gtl_math_osc_meas.h \ + gtl_math_peak.h \ + gtl_math_peak_to_peak.h \ + gtl_math_rms.h \ + gtl_math_freq.h \ + gtl_math_spec.h \ + gtl_math_spec_meas.h \ + gtl_math_sum.h \ + gtl_math_var.h \ + math_global.h + +# Default rules for deployment. +unix { + target.path = /usr/lib +} +!isEmpty(target.path): INSTALLS += target + +win32:CONFIG(release, debug|release): DESTDIR = ../.output/release +else:win32:CONFIG(debug, debug|release): DESTDIR = ../.output/debug +else:unix: DESTDIR = ../../.ouput + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_core +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_core + +INCLUDEPATH += $$PWD/../ +DEPENDPATH += $$PWD/../ + +INCLUDEPATH += $$(FFTWPATH) + +LIBS += -L$$(FFTWPATH) -llibfftw3-3 \ + -L$$(FFTWPATH) -llibfftw3f-3 \ + -L$$(FFTWPATH) -llibfftw3l-3 \ + +INCLUDEPATH += $$(DSPFILTERSPATH)/include +DEPENDPATH += $$(DSPFILTERSPATH)/include diff --git a/math/math_global.h b/math/math_global.h new file mode 100644 index 0000000..272954d --- /dev/null +++ b/math/math_global.h @@ -0,0 +1,12 @@ +#ifndef MATH_GLOBAL_H +#define MATH_GLOBAL_H + +#include + +#if defined(MATH_LIBRARY) +# define MATH_EXPORT Q_DECL_EXPORT +#else +# define MATH_EXPORT Q_DECL_IMPORT +#endif + +#endif // MATH_GLOBAL_H diff --git a/script/.gitignore b/script/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/script/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/script/gtl_scr_analog_data.cpp b/script/gtl_scr_analog_data.cpp new file mode 100644 index 0000000..bb01d82 --- /dev/null +++ b/script/gtl_scr_analog_data.cpp @@ -0,0 +1,36 @@ +#include "gtl_scr_analog_data.h" + + +namespace gtl +{ + namespace scr + { + analog_data::analog_data(qreal rate, qreal reference) + : gtl::analog_data((data_model_node*)nullptr, true) + , _reference(reference) + , _scale(1) + { + _rate = rate; + } + + qreal analog_data::reference() const + { + return _reference; + } + + void analog_data::set_scale(qreal value) + { + _scale = value; + } + + void analog_data::set_data(qreal *data, int samples, int idx, int step) + { + clear(); + + for(int i = 0; i < samples; i++) + push_back(data[idx + i*step]*_scale); + + gtl::analog_data::set_data(begin(), end()); + } + } +} diff --git a/script/gtl_scr_analog_data.h b/script/gtl_scr_analog_data.h new file mode 100644 index 0000000..22e9280 --- /dev/null +++ b/script/gtl_scr_analog_data.h @@ -0,0 +1,31 @@ +#ifndef SCR_ANALOG_DATA_H +#define SCR_ANALOG_DATA_H + +#include "core/gtl_analog_data.h" + +#include "script_global.h" + +namespace gtl +{ + namespace scr + { + class SCRIPT_EXPORT analog_data : public gtl::analog_data + { + Q_OBJECT + public: + analog_data(qreal rate, qreal reference); + + virtual qreal reference() const override; + void set_scale(qreal value); + + + void set_data(qreal* data, int samples, int idx, int step); + + private: + qreal _reference; + qreal _scale; + }; + } +} + +#endif // ANALOG_DATA_H diff --git a/script/gtl_scr_engine.cpp b/script/gtl_scr_engine.cpp new file mode 100644 index 0000000..497f8d8 --- /dev/null +++ b/script/gtl_scr_engine.cpp @@ -0,0 +1,783 @@ +#include "gtl_scr_engine.h" + +#include + +#include "core/gtl_logger.h" +#include "core/gtl_device.h" +#include "core/gtl_analog_data.h" + +#include "math/gtl_math_sum.h" +#include "math/gtl_math_rms.h" +#include "math/gtl_math_var.h" +#include "math/gtl_math_freq.h" +#include "math/gtl_math_ampl.h" +#include "math/gtl_math_kurt.h" +#include "math/gtl_math_delta_phase.h" +#include "math/gtl_math_delta_phase_spec.h" + +#include "math/gtl_math_filter_iir.h" +#include "math/gtl_math_intg.h" +#include "math/gtl_math_diff.h" +#include "math/gtl_math.h" + +#include "hw/gtl_hw_player_file_gtr.h" +#include "hw/gtl_hw_player_file_wav.h" + +namespace gtl +{ + namespace scr + { + QStringList engine::import_paths; + + engine::engine(QObject* parent) + : QObject(parent) + , _engine(nullptr) + , _player(nullptr) + , _file(nullptr) + { + _log = new log(this); + connect(_log, &log::new_message, this, &engine::log_message); + } + + engine::~engine() + { + set_file(); + } + + void gtl::scr::engine::clear() + { + clear_options(); + + clear_objects(); + + + remove_all_ad(); + } + + void engine::get_state(QJsonObject &root) const + { + QJsonArray base_indices; + for(auto obj: _objects) + { + if(is_gtl_math_analog_value(obj)) + base_indices.append(QJsonObject{{static_cast(obj)->name(), static_cast(obj)->value()}}); + } + root.insert("base_indices", base_indices); + + QJsonArray specs; + for(auto obj: _objects) + { + if(is_gtl_scr_spec(obj)) + { + QJsonObject spec_object; + static_cast(obj)->get_state(spec_object); + specs.append(spec_object); + } + } + root.insert("specs", specs); + } + + bool engine::set_file(QString path, std::vector channels) + { +// qDebug() << path; + + if(_file) + { + delete _player; + _player = nullptr; + + delete _file; + _file = nullptr; + + for(auto it : _analog_inputs) + delete it; + + _analog_inputs.clear(); + } + + clear(); + + if(path.isEmpty()) + return true; + + QFileInfo info(path); + if(info.suffix().toLower() == "gtr") + _file = new gtl::hw::player_file_gtr(this, path); + else if(info.suffix().toLower() == "wav") + _file = new gtl::hw::player_file_wav(this, path); + + if(_file == nullptr) + { + gtl::logger::error("script engine", "error opening file: " + path); + return false; + } + + if(!_file->is_ok()) + { + gtl::logger::error("script engine", "error reading file: " + path); + + delete _file; + _file = nullptr; + + return false; + } + + + if(channels.empty()) + { + for(int i = 0; i < _file->channels(); i++) + { + gtl::analog_data* ad = new analog_data(_file->rate(), _file->reference(i)); + ad->set_name(_file->channel(i)); + _analog_inputs.push_back(ad); + } + } + else + { + for(auto index: channels) + { + gtl::analog_data* ad = new analog_data(_file->rate(), _file->reference(index)); + ad->set_name(_file->channel(index)); + _analog_inputs.push_back(ad); + } + } + + emit analog_inputs_changed(); + + + _player = new player(_file, channels, this); + connect(_player, &player::data_read, this, &engine::set_data_from_file/*, Qt::DirectConnection*/); + + + return true; + } + + QQmlEngine *engine::qml_engine() const + { + return _engine; + } + + void engine::set_ad(std::vector::iterator begin, std::vector::iterator end) + { + remove_all_ad(); + for(auto it = begin; it != end; it++) + add_ad(*it); + } + + void engine::add_local_import_path(QString path) + { + _local_import_paths.push_back(path); + } + + void engine::clear_local_import_paths() + { + _local_import_paths.clear(); + } + + analog_data *engine::analog_input(int idx) + { + return qobject_cast(_analog_inputs[idx]); + } + + bool engine::evaluate(QString program) + { + clear_objects(); + + if(_engine) + delete _engine; + + _engine = new QQmlEngine(this); + + + init(); + + QJSValue result = _engine->evaluate(program); + if(result.isError()) + gtl::logger::error("script", result.toString() + " " + tr("at line") + ": " + result.property("lineNumber").toString() + " " + tr("in file") + ": " + result.property("fileName").toString()); + + + bool res = !result.isError(); + + + if(res && _player) + { + _player->stop(); + _player->start(); + } + + return res; + } + + void engine::clear_options() + { + _options = QJsonObject(); + } + + void engine::set_options(const QJsonObject &root) + { + _options = root; + + emit options_changed(); + } + + void engine::get_results(QJsonObject &root) + { + root = _results; + } + + QVariant engine::get_analog_inputs() const + { + return QVariant::fromValue(_analog_inputs); + } + + bool engine::is_type(QObject *obj, QString type) const + { + if(obj == NULL) + return false; + + const QMetaObject* meta_object = obj->metaObject(); + while(meta_object) + { + if(QString(meta_object->className()) == type) + return true; + + meta_object = meta_object->superClass(); + } + + return false; + } + + bool engine::is_gtl_analog_data(QObject *obj) + { + return is_type(obj, "gtl::analog_data"); + } + + bool engine::is_gtl_math_analog_value(QObject *obj) const + { + return is_type(obj, "gtl::math::analog_value"); + + } + + bool engine::is_gtl_scr_spec(QObject *obj) const + { + return is_type(obj, "gtl::scr::spec"); + } + + log* engine::get_log() const + { + return _log; + } + + QJsonObject engine::get_options() const + { + return _options; + } + + QJsonObject engine::get_results() const + { + //return QVariant::fromValue(_results); + return _results; + } + + void engine::clear_objects() + { + while(!_objects.empty()) + { + QObject *obj = _objects.first(); + _objects.removeFirst(); + delete obj; + } + } + + void engine::init() + { + _engine->globalObject().setProperty("gtl", _engine->newQObject(this)); + + _engine->globalObject().property("gtl").setProperty("filter_iir", _engine->newQMetaObject(>l::math::filter_iir::staticMetaObject)); + _engine->globalObject().property("gtl").setProperty("spec", _engine->newQMetaObject(>l::scr::spec::staticMetaObject)); + } + + void engine::add_ad(gtl::analog_data *ad) + { + gtl::data_model_node *device = ad->root(); + auto it = std::find_if(_analog_inputs.begin(), _analog_inputs.end(), [=](QObject* ai){return static_cast(ai)->root() == device;}); + if(it == _analog_inputs.end()) + connect(static_cast(device), >l::device::recieved_data, this, &engine::device_recieved_data); + + _analog_inputs.push_back(ad); + connect(ad, >l::data_model_node::deleting, this, &engine::deleting_ad); + + emit analog_inputs_changed(); + } + + void engine::remove_ad(gtl::analog_data *ad) + { + _analog_inputs.removeOne(ad); + + gtl::data_model_node *device = ad->root(); + auto it = std::find_if(_analog_inputs.begin(), _analog_inputs.end(), [=](QObject* ai){return static_cast(ai)->root() == device;}); + if(it == _analog_inputs.end()) + disconnect(static_cast(device), >l::device::recieved_data, this, &engine::device_recieved_data); + + emit analog_inputs_changed(); + } + + void engine::remove_all_ad() + { + for(auto it : _analog_inputs) + disconnect(static_cast(static_cast(it)->root()), >l::device::recieved_data, this, &engine::device_recieved_data); + + _analog_inputs.clear(); + + emit analog_inputs_changed(); + } + + QJSValue engine::add_value_sum(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::math::sum* sum_value = NULL; + + if(is_gtl_analog_data(obj)) + { + sum_value = new gtl::math::sum(static_cast(obj)); + _objects.push_back(sum_value); + } + else + gtl::logger::error("script", "error creating sum_value object: parent is not gtl::analog_data object"); + + emit base_index_created(sum_value); + + return _engine->newQObject(sum_value); + } + + QJSValue engine::add_value_rms(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::math::rms* rms_value = NULL; + + if(is_gtl_analog_data(obj)) + { + rms_value = new gtl::math::rms(static_cast(obj)); + _objects.push_back(rms_value); + } + else + gtl::logger::error("script", "error creating rms_value object: parent is not gtl::analog_data object"); + + emit base_index_created(rms_value); + + return _engine->newQObject(rms_value); + } + + QJSValue engine::add_value_var(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::math::var* var_value = NULL; + + if(is_gtl_analog_data(obj)) + { + var_value = new gtl::math::var(static_cast(obj)); + _objects.push_back(var_value); + } + else + gtl::logger::error("script", "error creating rms_value object: parent is not gtl::analog_data object"); + + emit base_index_created(var_value); + + return _engine->newQObject(var_value); + } + + QJSValue engine::add_value_freq(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::math::freq* freq_value = NULL; + + if(is_gtl_analog_data(obj)) + { + freq_value = new gtl::math::freq(static_cast(obj)); + _objects.push_back(freq_value); + } + else + gtl::logger::error("script", "error creating freq_value object: parent is not gtl::analog_data object"); + + emit base_index_created(freq_value); + + return _engine->newQObject(freq_value); + } + + QJSValue engine::add_value_ampl(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::math::ampl* ampl_value = NULL; + + if(is_gtl_analog_data(obj)) + { + ampl_value = new gtl::math::ampl(static_cast(obj)); + _objects.push_back(ampl_value); + } + else + gtl::logger::error("script", "error creating freq_value object: parent is not gtl::analog_data object"); + + emit base_index_created(ampl_value); + + return _engine->newQObject(ampl_value); + } + + QJSValue engine::add_value_kurt(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::math::kurt* kurt_value = NULL; + + if(is_gtl_analog_data(obj)) + { + kurt_value = new gtl::math::kurt(static_cast(obj)); + _objects.push_back(kurt_value); + } + else + gtl::logger::error("script", "error creating freq_value object: parent is not gtl::analog_data object"); + + emit base_index_created(kurt_value); + + return _engine->newQObject(kurt_value); + } + + QJSValue engine::add_delta_phase(QJSValue parent, QJSValue parent1) + { + QObject* obj = parent.toQObject(); + QObject* obj1 = parent1.toQObject(); + + gtl::math::delta_phase* delta_phase = NULL; + + if(is_gtl_analog_data(obj) && is_gtl_analog_data(obj1)) + { + delta_phase = new gtl::math::delta_phase(static_cast(obj), static_cast(obj1)); + _objects.push_back(delta_phase); + } + else + gtl::logger::error("script", "error creating delta_phase object: parent is not gtl::analog_data object"); + + return _engine->newQObject(delta_phase); + } + + QJSValue engine::add_delta_phase_spec(QJSValue parent, QJSValue parent1) + { + QObject* obj = parent.toQObject(); + QObject* obj1 = parent1.toQObject(); + + gtl::math::delta_phase_spec* delta_phase = NULL; + + if(is_gtl_analog_data(obj) && is_gtl_analog_data(obj1)) + { + delta_phase = new gtl::math::delta_phase_spec(static_cast(obj), static_cast(obj1)); + _objects.push_back(delta_phase); + } + else + gtl::logger::error("script", "error creating delta_phase object: parent is not gtl::analog_data object"); + + return _engine->newQObject(delta_phase); + } + + QJSValue engine::add_filter_iir(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::math::filter_iir* filter_iir = NULL; + + if(is_gtl_analog_data(obj)) + { + filter_iir = new gtl::math::filter_iir(static_cast(obj), true); + _objects.push_back(static_cast(filter_iir)); + connect(filter_iir, >l::data_model_node::deleting, this, &engine::deleting_object); + } + else + gtl::logger::error("script", "error creating filter object: parent is not gtl::analog_data object"); + + return _engine->newQObject(filter_iir); + } + + QJSValue engine::add_intg(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::math::intg *intg = NULL; + + if(is_gtl_analog_data(obj)) + { + intg = new gtl::math::intg(static_cast(obj), true); + _objects.push_back(static_cast(intg)); + connect(intg, >l::data_model_node::deleting, this, &engine::deleting_object); + } + else + gtl::logger::error("script", "error creating filter object: parent is not gtl::analog_data object"); + + return _engine->newQObject(intg); + } + + QJSValue engine::add_diff(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::math::diff *diff = NULL; + + if(is_gtl_analog_data(obj)) + { + diff = new gtl::math::diff(static_cast(obj), true); + _objects.push_back(static_cast(diff)); + connect(diff, >l::data_model_node::deleting, this, &engine::deleting_object); + } + else + gtl::logger::error("script", "error creating filter object: parent is not gtl::analog_data object"); + + return _engine->newQObject(diff); + } + + QJSValue engine::add_ausp(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::scr::spec* ausp = NULL; + + if(is_gtl_analog_data(obj)) + { + ausp = new gtl::scr::spec(gtl::math::spec::ausp, static_cast(obj)); + _objects.push_back(static_cast(ausp)); + connect(ausp, >l::math::spec::deleting, this, &engine::deleting_object); + connect(ausp, >l::scr::spec::create_engine_object, this, &engine::create_jsobject); + + emit spec_created(ausp); + } + else + gtl::logger::error("script", "error creating ausp object: parent is not gtl::analog_data object"); + + return _engine->newQObject(ausp); + } + + QJSValue engine::add_spen(QJSValue parent) + { + QObject* obj = parent.toQObject(); + + gtl::scr::spec* spen = NULL; + + if(is_gtl_analog_data(obj)) + { + spen = new gtl::scr::spec(gtl::math::spec::spen, static_cast(obj)); + _objects.push_back(static_cast(spen)); + connect(spen, >l::math::spec::deleting, this, &engine::deleting_object); + connect(spen, >l::scr::spec::create_engine_object, this, &engine::create_jsobject); + emit spec_created(spen); + } + else + gtl::logger::error("script", "error creating spen object: parent is not gtl::analog_data object"); + + return _engine->newQObject(spen); + } + + QJSValue engine::get_analog_input(QString name) + { + QObject *input = NULL; + auto it = std::find_if(_analog_inputs.begin(), _analog_inputs.end(), [=](QObject *obj){return static_cast(obj)->name() == name;}); + if(it != _analog_inputs.end()) + input = *it; + + return _engine->newQObject(input); + } + + QJSValue engine::import(QString file_name) + { + if(QFileInfo::exists(file_name)) + return _engine->importModule(file_name); + + for(auto it : _local_import_paths) + { + QFileInfo file(it + "/" + file_name); + if(file.exists()) + { + return _engine->importModule(file.absoluteFilePath()); + } + } + + for(auto it : import_paths) + { + QFileInfo file(it + "/" + file_name); + if(file.exists()) + { + return _engine->importModule(file.absoluteFilePath()); + } + } + + gtl::logger::error("script", tr("can't find module file: ") + file_name); + + + return QJSValue(); + } + + qreal engine::get_kurt_value(const QJsonArray &data_array) const + { + std::vector array; + for(auto it = data_array.cbegin(); it != data_array.cend(); it++) + array.push_back(it->toDouble()); + + return mathFunctions::kurt(array.begin(), array.end()); + + } + + qreal engine::get_var_value(const QJsonArray &data_array) const + { + std::vector array; + for(auto it = data_array.cbegin(); it != data_array.cend(); it++) + array.push_back(it->toDouble()); + + return mathFunctions::var(array.begin(), array.end()); + } + + void engine::device_recieved_data() + { + + } + + void engine::deleting_ad() + { + remove_ad(static_cast(sender())); + } + + void engine::deleting_object() + { + _objects.removeOne(sender()); + } + + void engine::create_jsobject(QObject *obj, QJSValue &js_object) + { + js_object = _engine->newQObject(obj); + } + + void engine::set_results(const QJsonObject &value) + { + _results = value; + emit results_changed(); + } + + void engine::set_data_from_file() + { + for(int i = 0; i < _analog_inputs.size(); i++) + static_cast(_analog_inputs[i])->set_data(_player->data(), _player->samples(), _player->channels().empty() ? i : _player->channels().at(i), /*_analog_inputs.size()*/_file->channels()); + + device_recieved_data(); + + _player->confirm(); + } + + log::log(QObject *parent) + : QObject(parent) + { + + } + + void log::info(QString tag, QString message) + { + gtl::logger::info(tag, message); + emit new_message(tag, message); + } + + void log::warning(QString tag, QString message) + { + gtl::logger::warning(tag, message); + emit new_message(tag, message); + } + + void log::error(QString tag, QString message) + { + gtl::logger::error(tag, message); + emit new_message(tag, message); + } + + void log::debug(QString tag, QString message) + { + gtl::logger::debug(tag, message); + emit new_message(tag, message); + } + + player::player(hw::player_file *file, const std::vector &channels, QObject *parent) + : QThread(parent) + , _file(file) + , _samples(qRound(0.1*file->rate())) + , _buffer(_samples * file->channels()) + , _channels(channels) + { + + } + + player::~player() + { + stop(); + } + + void player::start() + { + _is_playing = true; + QThread::start(); + } + + void player::stop() + { + if(!isRunning()) + return; + + _status = true; + _is_playing = false; + wait(); + } + + qreal player::rate() const + { + return _file->rate(); + } + + void player::confirm() + { + _status = true; + } + + int player::samples() const + { + return _samples; + } + + qreal *player::data() const + { + return (qreal*)&_buffer[0]; + } + + const std::vector &player::channels() const + { + return _channels; + } + + void player::run() + { + setPriority(QThread::LowPriority); + + int idx = 0; + + while(_is_playing) + { + _file->get_data(&_buffer[0], idx, _samples, true); + if(_samples) + { + + _status = false; + emit data_read(); +// qDebug() << "playing..."; + } + + + while((!_status) && _is_playing) + msleep(1); + } + } + } +} diff --git a/script/gtl_scr_engine.h b/script/gtl_scr_engine.h new file mode 100644 index 0000000..38bd272 --- /dev/null +++ b/script/gtl_scr_engine.h @@ -0,0 +1,191 @@ +#ifndef GTL_SCR_ENGINE_H +#define GTL_SCR_ENGINE_H + +#include +#include +#include +#include +#include + +#include "script_global.h" + +#include "core/gtl_analog_data.h" +#include "math/gtl_math_analog_value.h" + +#include "hw/gtl_hw_player_file.h" + +#include "script/gtl_scr_spec.h" +#include "script/gtl_scr_analog_data.h" + +namespace gtl +{ + namespace scr + { + class player : public QThread + { + Q_OBJECT + public: + player(gtl::hw::player_file* file, const std::vector &channesl, QObject* parent = NULL); + ~player(); + void start(); + void stop(); + qreal rate() const; + void confirm(); + int samples() const; + qreal* data() const; + const std::vector& channels() const; + + private: + gtl::hw::player_file* _file; + int _samples; + std::vector _buffer; + bool _is_playing; + bool _status; + std::vector _channels; + + private: + void run() override; + + signals: + void data_read(); + }; + + + + class log : public QObject + { + Q_OBJECT + public: + log(QObject* parent = NULL); + + public slots: + void info(QString tag, QString message); + void warning(QString tag, QString message); + void error(QString tag, QString message); + void debug(QString tag, QString message); + + signals: + void new_message(QString, QString); + + }; + + class SCRIPT_EXPORT engine : public QObject + { + Q_OBJECT + Q_PROPERTY(QVariant analog_inputs READ get_analog_inputs NOTIFY analog_inputs_changed) + Q_PROPERTY(log* log READ get_log CONSTANT) + Q_PROPERTY(QJsonObject options READ get_options NOTIFY options_changed) + Q_PROPERTY(QJsonObject results READ get_results WRITE set_results NOTIFY results_changed) + + public: + engine(QObject* parent = NULL); + ~engine(); + + virtual bool evaluate(QString program); + + void clear_options(); + void set_options(const QJsonObject &root); + void get_results(QJsonObject &root); + + virtual void clear(); + + void get_state(QJsonObject &root) const; + + bool set_file(QString path = "", std::vector = std::vector()); + + QQmlEngine *qml_engine() const; + + void set_ad(std::vector::iterator begin, std::vector::iterator end); + + void add_local_import_path(QString path); + void clear_local_import_paths(); + + analog_data* analog_input(int idx); + + public: + static QStringList import_paths; + + protected: + QQmlEngine* _engine; + QObjectList _analog_inputs; + QJsonObject _options; + QJsonObject _results; + + player* _player; + + + private: + QObjectList _objects; + log *_log; + gtl::hw::player_file* _file; + QStringList _local_import_paths; + + + private: + QVariant get_analog_inputs() const; + bool is_type(QObject* obj, QString type) const; + bool is_gtl_analog_data(QObject* obj); + bool is_gtl_math_analog_value(QObject* obj) const; + bool is_gtl_scr_spec(QObject* obj) const; + log* get_log() const; + QJsonObject get_options() const; + QJsonObject get_results() const; + + virtual void clear_objects(); + + protected: + virtual void init(); + + + public slots: + void add_ad(gtl::analog_data* ad); + void remove_ad(gtl::analog_data* ad); + void remove_all_ad(); + + QJSValue add_value_sum(QJSValue parent); + QJSValue add_value_rms(QJSValue parent); + QJSValue add_value_var(QJSValue parent); + QJSValue add_value_freq(QJSValue parent); + QJSValue add_value_ampl(QJSValue parent); + QJSValue add_value_kurt(QJSValue parent); + QJSValue add_delta_phase(QJSValue parent, QJSValue parent1); + QJSValue add_delta_phase_spec(QJSValue parent, QJSValue parent1); + + QJSValue add_filter_iir(QJSValue parent); + QJSValue add_intg(QJSValue parent); + QJSValue add_diff(QJSValue parent); + + QJSValue add_ausp(QJSValue parent); + QJSValue add_spen(QJSValue parent); + + QJSValue get_analog_input(QString name); + + QJSValue import(QString file_name); + + qreal get_kurt_value(const QJsonArray &data_array) const; + qreal get_var_value(const QJsonArray &data_array) const; + + protected slots: + virtual void device_recieved_data(); + + private slots: + void deleting_ad(); + void deleting_object(); + void create_jsobject(QObject* obj, QJSValue &js_object); + void set_results(const QJsonObject &value); + void set_data_from_file(); + + signals: + void analog_inputs_changed(); + void spec_created(spec*); + void base_index_created(gtl::math::analog_value*); + + void options_changed(); + void results_changed(); + + void log_message(QString, QString); + }; + } +} + +#endif // GTL_SCR_ENGINE_H diff --git a/script/gtl_scr_engine_diagnostic.cpp b/script/gtl_scr_engine_diagnostic.cpp new file mode 100644 index 0000000..c807a4e --- /dev/null +++ b/script/gtl_scr_engine_diagnostic.cpp @@ -0,0 +1,202 @@ +#include "gtl_scr_engine_diagnostic.h" + +#include "core/gtl_logger.h" + +namespace gtl +{ + namespace scr + { + engine_diagnostic::engine_diagnostic(QObject* parent) + : engine(parent) + { + _diagnostic = new diagnostic(this); + + connect(_diagnostic, &diagnostic::stopped, this, &engine_diagnostic::diagnostic_stopped); + connect(_diagnostic, &diagnostic::progress_, this, &engine_diagnostic::diagnostic_progress); + + connect(this, &engine::spec_created, this, &engine_diagnostic::connect_spec); + connect(this, &engine::base_index_created, this, &engine_diagnostic::connect_base_index); + } + + bool engine_diagnostic::evaluate(QString program) + { + _devices_intervals.clear(); + _diagnostic->start(); + _diagnostic->set_time(0); + _diagnose = QJSValue(); + if(program.isEmpty()) + { + gtl::logger::warning("diagnostic engine", "script is empty."); + _diagnostic->stop(); + return true; + } + else if(engine::evaluate(program)) + { + _diagnose = _engine->globalObject().property("diagnose"); + if(!_diagnose.isCallable()) + { + _diagnostic->stop(); + gtl::logger::error("engine_diagnostic", tr("can't find diagnose function")); + } + + if(_player) + connect(_diagnostic, &diagnostic::stopped, _player, &player::stop); + + return true; + } + + _diagnostic->stop(); + + return false; + } + + void engine_diagnostic::init() + { + engine::init(); + + _engine->globalObject().property("gtl").setProperty("diagnostic", _engine->newQObject(_diagnostic)); + } + + void engine_diagnostic::device_recieved_data() + { + if(!_diagnose.isCallable()) + return; + + if(!_diagnostic->is_running()) + return; + + gtl::device* device = static_cast(sender()); + qreal rate = 0; + if(_player) + { + device = nullptr; + rate = _player->rate(); + } + else + rate = device->rate(); + + qreal interval = _devices_intervals[device]; + + auto it = std::find_if(_analog_inputs.begin(), _analog_inputs.end(), [=](QObject* ai){return static_cast(ai)->root() == device;}); + if(_player) + it = _analog_inputs.begin(); + + if(it == _analog_inputs.end()) + return; + + if(device) + device->lock_ai(); + + qreal time = static_cast(*it)->size() / rate; + +// qDebug() << time << interval; + + interval += time; + _diagnostic->set_time(_diagnostic->time() + time); + + + if(qAbs(interval - _diagnostic->interval()) < /*1e-6*/1.0/rate || interval > _diagnostic->interval()) + { +// qDebug() << _diagnostic->time() << interval << _diagnostic->interval(); + QJSValue result = _diagnose.call(); + if(result.isError()) + gtl::logger::error("engine_diagnostic", result.toString() + " " + tr("at line") + ": " + result.property("lineNumber").toString()); + + interval -= _diagnostic->interval(); +// qDebug() << interval; + } + + if(device) + device->unlock_ai(); + + _devices_intervals[device] = interval; + + } + + void engine_diagnostic::connect_spec(spec *spec) + { + connect(_diagnostic, &diagnostic::stopped, spec, >l::math::spec::stop_acq); + } + + void engine_diagnostic::connect_base_index(math::analog_value *index) + { + connect(_diagnostic, &diagnostic::stopped, index, >l::math::analog_value::stop_acq); + } + + diagnostic::diagnostic(QObject *parent) + : QObject(parent) + , _interval(1) + , _time(0) + , _is_running(false) + , _progress(0) + { + + } + + qreal diagnostic::interval() const + { + return _interval; + } + + qreal diagnostic::time() const + { + return _time; + } + + void diagnostic::start() + { + _is_running = true; + } + + bool diagnostic::is_running() const + { + return _is_running; + } + + qreal diagnostic::progress() const + { + return _progress; + } + + void diagnostic::set_interval(qreal value) + { + if(_interval != value) + { + qDebug() << "set_interval: " << value; + _interval = value; + emit interval_changed(); + } + } + + void diagnostic::set_time(qreal value) + { + if(_time != value) + { + _time = value; + emit time_changed(); + } + } + + void diagnostic::stop() + { + if(!_is_running) + return; + + _is_running = false; + emit stopped(); + + } + + void diagnostic::set_progress(qreal value) + { + if(value != _progress) + { + _progress = value; + emit progress_changed(); + + emit progress_(_progress); + } + } + + } +} diff --git a/script/gtl_scr_engine_diagnostic.h b/script/gtl_scr_engine_diagnostic.h new file mode 100644 index 0000000..4ba3e16 --- /dev/null +++ b/script/gtl_scr_engine_diagnostic.h @@ -0,0 +1,85 @@ +#ifndef ENGINE_DIAGNOSTIC_H +#define ENGINE_DIAGNOSTIC_H + +#include "script_global.h" + +#include "core/gtl_device.h" +#include "script/gtl_scr_engine.h" + + + +namespace gtl +{ + namespace scr + { + class diagnostic : public QObject + { + Q_OBJECT + Q_PROPERTY(qreal interval READ interval WRITE set_interval NOTIFY interval_changed) + Q_PROPERTY(qreal time READ time WRITE set_time NOTIFY time_changed) + Q_PROPERTY(qreal progress READ progress WRITE set_progress NOTIFY progress_changed) + + public: + diagnostic(QObject* parent = NULL); + + qreal interval() const; + qreal time() const; + + void start(); + bool is_running() const; + qreal progress() const; + + private: + qreal _interval; + qreal _time; + bool _is_running; + qreal _progress; + + signals: + void interval_changed(); + void time_changed(); + void stopped(); + void progress_changed(); + void progress_(qreal); + + public slots: + void set_interval(qreal value); + void set_time(qreal value); + void stop(); + void set_progress(qreal value); + }; + + class SCRIPT_EXPORT engine_diagnostic : public engine + { + Q_OBJECT + public: + engine_diagnostic(QObject* parent = NULL); + + virtual bool evaluate(QString program) override; + + + + private: + QJSValue _diagnose; + diagnostic *_diagnostic; + std::map _devices_intervals; + + private: + virtual void init() override; + + + protected slots: + virtual void device_recieved_data() override; + + private slots: + void connect_spec(gtl::scr::spec* spec); + void connect_base_index(gtl::math::analog_value *index); + + signals: + void diagnostic_stopped(); + void diagnostic_progress(qreal); + }; + } +} + +#endif // ENGINE_DIAGNOSTIC_H diff --git a/script/gtl_scr_engine_table.cpp b/script/gtl_scr_engine_table.cpp new file mode 100644 index 0000000..ea500e0 --- /dev/null +++ b/script/gtl_scr_engine_table.cpp @@ -0,0 +1,42 @@ +#include "gtl_scr_engine_table.h" + +namespace gtl +{ + namespace scr + { + engine_table::engine_table(QObject* parent) + : engine(parent) + { + + _model = new engine_table_model(this); + + + } + + engine_table::~engine_table() + { + + } + + QAbstractItemModel *engine_table::model() + { + return _model; + } + + bool engine_table::evaluate(QString program) + { + _model->clear(); + return engine::evaluate(program); + } + + void engine_table::init() + { + engine::init(); + _table_object = _engine->newQObject(_model); + _engine->globalObject().property("gtl").setProperty("table", _table_object); + + _table_object.setProperty("vert_header", _engine->newQObject(_model->vert_header())); + _table_object.setProperty("horz_header", _engine->newQObject(_model->horz_header())); + } + } +} diff --git a/script/gtl_scr_engine_table.h b/script/gtl_scr_engine_table.h new file mode 100644 index 0000000..807b902 --- /dev/null +++ b/script/gtl_scr_engine_table.h @@ -0,0 +1,35 @@ +#ifndef ENGINGE_TABLE_H +#define ENGINGE_TABLE_H + +#include "script/gtl_scr_engine.h" + +#include "script_global.h" + +#include "gtl_scr_engine_table_model.h" + +namespace gtl +{ + namespace scr + { + class SCRIPT_EXPORT engine_table : public engine + { + Q_OBJECT + public: + engine_table(QObject* parent = NULL); + virtual ~engine_table(); + + QAbstractItemModel* model(); + + virtual bool evaluate(QString program) override; + + private: + engine_table_model* _model; + QJSValue _table_object; + + private: + virtual void init() override; + }; + } +} + +#endif // ENGINGE_TABLE_H diff --git a/script/gtl_scr_engine_table_model.cpp b/script/gtl_scr_engine_table_model.cpp new file mode 100644 index 0000000..d9a7abd --- /dev/null +++ b/script/gtl_scr_engine_table_model.cpp @@ -0,0 +1,157 @@ +#include "gtl_scr_engine_table_model.h" + +namespace gtl +{ + namespace scr + { + engine_table_model::engine_table_model(QObject *parent) + : QAbstractTableModel(parent) + , _rows(0) + , _cols(0) + { + _horz_header = new engine_table_model_header(this); + _vert_header = new engine_table_model_header(this); + } + + QVariant engine_table_model::headerData(int section, Qt::Orientation orientation, int role) const + { + // FIXME: Implement me! + + if(role == Qt::DisplayRole) + { + if(orientation == Qt::Vertical) + return _vert_header->text(section); + else + return _horz_header->text(section); + } + + return QVariant(); + } + + int engine_table_model::rowCount(const QModelIndex &parent) const + { + if (parent.isValid()) + return 0; + + + return _rows; + } + + int engine_table_model::columnCount(const QModelIndex &parent) const + { + if (parent.isValid()) + return 0; + + // FIXME: Implement me! + return _cols; + } + + QVariant engine_table_model::data(const QModelIndex &index, int role) const + { + if (!index.isValid()) + return QVariant(); + + if(role == Qt::DisplayRole) + { + QObject* value_object = _values.value(index); + if(is_gtl_math_analog_value(value_object)) + { + return static_cast(value_object)->value(); + } + } + + return QVariant(); + } + + void engine_table_model::clear() + { + _vert_header->clear(); + _horz_header->clear(); + _values.clear(); + } + + bool engine_table_model::is_gtl_math_analog_value(QObject *obj) const + { + if(obj == NULL) + return false; + + const QMetaObject* meta_object = obj->metaObject(); + while(meta_object) + { + if(QString(meta_object->className()) == "gtl::math::analog_value") + return true; + + meta_object = meta_object->superClass(); + } + + return false; + } + + engine_table_model_header *engine_table_model::vert_header() + { + return _vert_header; + } + + engine_table_model_header *engine_table_model::horz_header() + { + return _horz_header; + } + + + void engine_table_model::init(int rows, int cols) + { + beginResetModel(); + + _rows = rows; + _cols = cols; + + endResetModel(); + } + + void engine_table_model::set_cell_value(int row, int col, QJSValue value_object) + { + QObject* obj = value_object.toQObject(); + + if(is_gtl_math_analog_value(obj)) + { + QModelIndex idx = index(row, col); + _values[idx] = obj; + connect(static_cast(obj), >l::math::analog_value::value_changed, this, &engine_table_model::value_changed); + + emit dataChanged(idx, idx, QList() << Qt::DisplayRole); + } + else + { + qDebug() << "error set_cell_value : value_object is not gtl::math::analog_value object"; + } + } + + void engine_table_model::value_changed() + { + QModelIndex idx = _values.key(sender()); + emit dataChanged(idx, idx, QList() << Qt::DisplayRole); + } + + engine_table_model_header::engine_table_model_header(QObject *parent) + : QObject(parent) + { + + } + + QString engine_table_model_header::text(int idx) const + { + return _text[idx]; + } + + void engine_table_model_header::clear() + { + _text.clear(); + } + + void engine_table_model_header::set_text(int idx, QString text) + { + _text[idx] = text; + } + + } +} diff --git a/script/gtl_scr_engine_table_model.h b/script/gtl_scr_engine_table_model.h new file mode 100644 index 0000000..c5a49b9 --- /dev/null +++ b/script/gtl_scr_engine_table_model.h @@ -0,0 +1,74 @@ +#ifndef ENGINE_TABLE_MODEL_H +#define ENGINE_TABLE_MODEL_H + +#include +#include + +#include "math/gtl_math_analog_value.h" + +namespace gtl +{ + namespace scr + { + class engine_table_model_header : public QObject + { + Q_OBJECT + + private: + QHash _text; + + public: + engine_table_model_header(QObject* parent); + QString text(int idx) const; + void clear(); + + public slots: + void set_text(int idx, QString text); + + + + }; + class engine_table_model : public QAbstractTableModel + { + Q_OBJECT + + public: + explicit engine_table_model(QObject *parent = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + void clear(); + + engine_table_model_header* vert_header(); + engine_table_model_header* horz_header(); + + private: + int _rows; + int _cols; + + engine_table_model_header* _horz_header; + engine_table_model_header* _vert_header; + + QHash _values; + + private: + bool is_gtl_math_analog_value(QObject* obj) const; + + public slots: + void init(int rows, int cols); + void set_cell_value(int row, int col, QJSValue parent); + + private slots: + void value_changed(); + }; + } +} + +#endif // ENGINE_TABLE_MODEL_H diff --git a/script/gtl_scr_spec.cpp b/script/gtl_scr_spec.cpp new file mode 100644 index 0000000..228eeae --- /dev/null +++ b/script/gtl_scr_spec.cpp @@ -0,0 +1,631 @@ +#include "gtl_scr_spec.h" +#include "qvariant.h" + +#include + +namespace gtl +{ + namespace scr + { + spec::spec(gtl::math::spec::types type, gtl::analog_data *ad) + : gtl::math::spec(type, ad) + , _name(ad->name()) + , _color(ad->color()) + , _is_visible(true) + , _smoothing_factor(10) + , _smoothed_line_color(ad->color()) + , _peak_level(6) + , _harm_tolerance(1) + , _max_harm_level(0) + { + connect(this, >l::math::spec::initialized, this, >l::scr::spec::reset); + } + + QString spec::name() const + { + return _name; + } + + int spec::color() const + { + return _color; + } + + bool spec::is_visible() const + { + return _is_visible; + } + + int spec::smoothing_factor() const + { + return _smoothing_factor; + } + + int spec::smoothed_line_color() const + { + return _smoothed_line_color; + } + + qreal spec::peak_level() const + { + return _peak_level; + } + + void spec::get_smoothed_line(std::back_insert_iterator > data) const + { + std::copy(_smoothed_line.begin(), _smoothed_line.end(), data); + } + + void spec::get_peaks(std::back_insert_iterator > peaks) const + { + std::copy(_peaks.begin(), _peaks.end(), peaks); + } + + QVariant spec::get_harms_sets() const + { + return QVariant::fromValue(_harms_sets); + } + + int spec::harms_sets_count() const + { + return _harms_sets.count(); + } + + spec_harms *spec::harms_set(uint idx) const + { + if(idx < _harms_sets.count()) + return _harms_sets[idx]; + + return NULL; + } + + void spec::get_harms(std::back_insert_iterator> harms) + { + for(auto harms_set : _harms_sets) + { + if(harms_set->is_visible()) + harms_set->get_harms(harms); + } + + } + + void spec::get_humps(std::back_insert_iterator> humps) + { + std::copy(_humps_sets.begin(), _humps_sets.end(), humps); + } + + qreal spec::harm_tolerance() const + { + return _harm_tolerance; + } + + qreal spec::get_amplitude(qreal freq) const + { + return get_y(freq, *this); + } + + void spec::get_state(QJsonObject &root) const + { + root.insert("name", name()); + root.insert("type", type() == gtl::math::spec::ausp ? "ausp" : "spen"); + root.insert("unit", gtl::math::spec::unit() == gtl::unit ? "units" : "db"); + root.insert("res", gtl::math::spec::resolution()); + root.insert("avg", (int)gtl::math::spec::average()); + root.insert("overlap", (int)gtl::math::spec::overlap()); + root.insert("window", (int)gtl::math::spec::window()); + analog_data* analog_data_root = ad()->analog_data_root(); + if(analog_data_root) + root.insert("ad_name", analog_data_root->name()); + else + root.insert("ad_name", ""); + + + QJsonObject spec; + spec.insert("color", _color); + spec.insert("data", data()); + root.insert("spec", spec); + + QJsonObject base_; + base_.insert("color", _smoothed_line_color); + base_.insert("factor", _smoothing_factor); + base_.insert("data", base()); + root.insert("base", base_); + + + QJsonObject peaks; + peaks.insert("level", _peak_level); + QJsonArray peaks_indices; + for(auto it = _peaks.begin(); it != _peaks.end(); it++) + peaks_indices.append(*it); + peaks.insert("indices", peaks_indices); + root.insert("peaks", peaks); + + QJsonObject harmonics; + harmonics.insert("tolerance", _harm_tolerance); + + QJsonArray harms_sets; + for(auto harm_set : _harms_sets) + { + QJsonObject harm_set_object; + harm_set->save_state(harm_set_object); + harms_sets.append(harm_set_object); + } + + harmonics.insert("harms_sets", harms_sets); + + + root.insert("harmonics", harmonics); + } + + void spec::set_state(const QJsonObject &root) + { + _name = root["name"].toString(); + analog_data* analog_data_root = ad()->analog_data_root(); + if(analog_data_root) + analog_data_root->set_name(root["ad_name"].toString()); + + _type = (gtl::math::spec::types)(root["type"].toString() == "spen"); + set_unit((gtl::units)(root["unit"].toString() == "db")); + + set_average(root["avg"].toInt()); + set_overlap(root["overlap"].toInt()); + set_window((gtl::math::spec::windows)root["window"].toInt()); + + QJsonObject spec = root["spec"].toObject(); + QJsonArray spec_data = spec["data"].toArray(); + + set_color(spec["color"].toInt()); + set_frequency(root["res"].toDouble() * (spec_data.size() - 1)); + set_lines(spec_data.size() - 1); + + + + + for(int i = 0; i < size(); i++) + (*this)[i] = spec_data[i].toDouble(); + + QJsonObject base = root["base"].toObject(); + QJsonArray base_data = base["data"].toArray(); + set_smoothed_line_color(base["color"].toInt()); + + _smoothed_line.clear(); + for(int i = 0; i < base_data.size(); i++) + _smoothed_line.push_back(base_data[i].toDouble()); + + QJsonObject peaks = root["peaks"].toObject(); + _peak_level = peaks["level"].toDouble(); + QJsonArray peak_indices = peaks["indices"].toArray(); + + _peaks.clear(); + + for(auto it: peak_indices) + { + _peaks.push_back(it.toInt()); + _peaks_freqs.push_back(_peaks.back()*resolution()); + } + + QJsonObject harmonics = root["harmonics"].toObject(); + _harm_tolerance = harmonics["torlerance"].toDouble(); + + QJsonArray harms_set = harmonics["harms_sets"].toArray(); + for(auto it: harms_set) + { + spec_harms *harms = new spec_harms(this); + harms->set_tolerance(_harm_tolerance); + harms->restore_state(it.toObject()); + _harms_sets.push_back(harms); + + connect(harms, &spec_harms::get_peak_freq, this, &spec::get_peak_freq); + connect(harms, &spec_harms::get_harm_amplitude, this, &spec::get_ampl); + connect(harms, &spec_harms::get_harm_base, this, &spec::get_base); + connect(harms, &spec_harms::visible_changed, this, &spec::harms_set_visible_changed); + connect(harms, &spec_harms::get_max_harm_level, this, &spec::get_max_harm_level); + } + + + set_max_harm_ampl(); + + emit spec_changed(); + + } + + qreal spec::integral_index() const + { + qreal integral_index = 0; + + for(auto harms_set : _harms_sets) + integral_index += harms_set->integral_index(); + + return integral_index; + } + + void spec::get_avg(int points, const_iterator begin, const_iterator end, std::back_insert_iterator > out) + { + for(auto it = begin; it != end; it++) + { + int cnt = 0; + qreal value = 0; + for( + auto it1 = (std::distance(begin, it) < points ? begin : it - points); + it1 < (std::distance(it, end) <= points ? end : it + 1 + points); + it1++ + ) + { + value += *it1; + cnt++; + } + + out = value/cnt; + out++; + } + } + + qreal spec::get_y(qreal x, const std::vector &series) const + { + if(qIsNaN(x)) + return 0; + + if(series.size() < 2) + return 0; + + qreal x_[2]; + qreal y_[2]; + int idx = 0; + qreal dx = resolution(); + + if(x < 0) + { + //idx = 0; + return 0; + } + else if(x > (series.size() - 1)*dx) + { + //idx = size() - 2; + return 0; + } + else + { + idx = x/dx; + } + + x_[0] = idx*dx; + x_[1] = (idx + 1) * dx; + y_[0] = series[idx]; + y_[1] = series[idx + 1]; + + qreal a = (y_[0] - y_[1])/(x_[0] - x_[1]); + qreal b = y_[0] - a*x_[0]; + + return a*x + b; + } + + QJsonArray spec::base() const + { + QJsonArray base; + for(auto it = _smoothed_line.begin(); it != _smoothed_line.end(); it++) + base.append(*it); + + return base; + } + + QJsonArray spec::data() const + { + QJsonArray data; + for(auto it = begin(); it != end(); it++) + data.append(*it); + + return data; + } + + QJsonArray spec::env() const + { + QJsonArray evl; + if(_envelope) + { + for(auto it : *_envelope) + evl.append(it); + } + + return evl; + } + + void spec::before_updating() + { + _smoothed_line.clear(); + _peaks.clear(); + _peaks_freqs.clear(); + } + + void spec::after_updating() + { + get_avg(_smoothing_factor, begin(), end(), std::back_inserter(_smoothed_line)); + /* + for(int i = 0; i < size(); i++) + { + int cnt = 0; + qreal value = 0; + for(int j = qMax(0, i - _smoothing_factor); j <= qMin(i + _smoothing_factor, (int)size() - 1); j++) + { + value += at(j); + cnt++; + } + _smoothed_line.push_back(value/cnt); + } + */ + + int peak_idx = -1; + qreal peak_value = 0; + qreal df = 1; + for(int i = 0; i < size(); i++) + { + if(at(i) - _smoothed_line[i] >= _peak_level) + { + if(peak_idx == -1) + { + peak_idx = i; + peak_value = at(i); + } + else if(at(i) > peak_value) + { + peak_idx = i; + peak_value = at(i); + } + } + else if(at(i) < _smoothed_line[i] || i == (int)size() - 1) + { + if(peak_idx != -1) + { + _peaks.push_back(peak_idx); + _peaks_freqs.push_back(peak_idx*resolution()); + } + + peak_idx = -1; + } + } + + for(auto harms_set : _harms_sets) + harms_set->update(); + + for(auto humps : _humps_sets) + humps->update(); + + + set_max_harm_ampl(); + + emit spec_changed(); + + } + + qreal spec::get_freq_for_max_ampl(qreal freq, qreal tolerance) + { + qreal freq_left = freq - tolerance; + qreal freq_right = freq + tolerance; + + qreal value = freq_left; + qreal ampl_max = get_y(freq_left, *this); + + qreal ampl = get_y(freq_right, *this); + if(ampl > ampl_max) + { + value = freq_right; + ampl_max = ampl; + } + + int left = std::max(freq_left/resolution() + 1, 0); + int right = std::min(freq_right/resolution(), size() - 1); + + for(int i = left; i <= right; i++) + { + ampl = get_y(i*resolution(), *this); + if(ampl > ampl_max) + { + value = i*resolution(); + ampl_max = ampl; + } + } + + return value; + } + + void spec::set_max_harm_ampl() + { + _max_harm_level = 0; + + for(auto harms_set : _harms_sets) + harms_set->get_max_ampl(_max_harm_level); + + emit integral_index_changed(); + } + + void spec::set_name(QString value) + { + if(value != _name) + { + _name = value; + + emit name_changed(); + } + } + + void spec::set_color(int value) + { + if(value != _color) + { + _color = value; + + emit color_changed(); + } + } + + void spec::set_visible(bool value) + { + if(_is_visible != value) + { + _is_visible = value; + emit visible_changed(); + } + } + + void spec::set_smoothing_factor(int value) + { + if(_smoothing_factor != value) + { + _smoothing_factor = value; + emit smoothing_factor_changed(); + } + } + + void spec::set_smoothed_line_color(int value) + { + if(_smoothed_line_color != value) + { + _smoothed_line_color = value; + emit smoothed_line_color_changed(); + } + } + + void spec::set_peak_level(qreal value) + { + if(_peak_level != value) + { + _peak_level = value; + emit peak_level_changed(); + } + } + + QJSValue spec::add_harms_set(qreal freq, int k, int color, qreal weight) + { + spec_harms *harms = new spec_harms(this, freq, k, color, weight); + harms->set_tolerance(_harm_tolerance); + _harms_sets.push_back(harms); + + connect(harms, &spec_harms::get_peak_freq, this, &spec::get_peak_freq); + connect(harms, &spec_harms::get_harm_amplitude, this, &spec::get_ampl); + connect(harms, &spec_harms::get_harm_base, this, &spec::get_base); + connect(harms, &spec_harms::visible_changed, this, &spec::harms_set_visible_changed); + connect(harms, &spec_harms::get_max_harm_level, this, &spec::get_max_harm_level); + + set_max_harm_ampl(); + + emit harms_sets_changed(); + + QJSValue js_object; + emit create_engine_object(harms, js_object); + + return js_object; + } + + QJSValue spec::addHarmsSet(const QJsonObject args) + { + return add_harms_set( + args.value("freq").toDouble(100), + args.value("count").toInt(5), + args.value("color").toInt(0), + args.value("weight").toInt(1) + ); + } + + QJSValue spec::addHumps(const QJsonObject& args) + { + spec_humps *humps = new spec_humps( + this, + args + ); + _humps_sets.push_back(humps); + + QJSValue js_object; + emit create_engine_object(humps, js_object); + + return js_object; + } + + void spec::clear_harms_sets() + { + for(auto harms : _harms_sets) + { + delete harms; + } + + _harms_sets.clear(); + + set_max_harm_ampl(); + } + + void spec::clear_humps() + { + for(auto humps : _humps_sets) + { + delete humps; + } + + _humps_sets.clear(); + } + + void spec::set_harm_tolerance(qreal value) + { + if(value != _harm_tolerance) + { + _harm_tolerance = value; + + + for(auto it : _harms_sets) + it->set_tolerance(_harm_tolerance); + + emit harm_tolerance_changed(); + } + } + + qreal spec::max_harm_level() const + { + return _max_harm_level; + } + + void spec::get_peak_freq(qreal freq, qreal tolerance, qreal &peak_freq) + { + if(!_peaks_freqs.empty()) + { + auto it = std::lower_bound(_peaks_freqs.begin(), _peaks_freqs.end(), freq); + if(it != _peaks_freqs.begin()) + { + if(freq - *prev(it) <= tolerance) + { + peak_freq = *prev(it); + return; + } + } + if(it != _peaks_freqs.end()) + { + if(*it - freq <= tolerance) + { + peak_freq = *it; + return; + } + } + } + + peak_freq = -1; + } + + void spec::get_ampl(qreal freq, qreal &res) + { + res = get_y(freq, *this); + } + + void spec::get_base(qreal freq, qreal &res) + { + res = get_y(freq, _smoothed_line); + } + + void spec::reset() + { + _smoothed_line.clear(); + _peaks.clear(); + _peaks_freqs.clear(); + } + + void spec::get_max_harm_level(qreal &value) const + { + value = _max_harm_level; + } + } +} diff --git a/script/gtl_scr_spec.h b/script/gtl_scr_spec.h new file mode 100644 index 0000000..4162522 --- /dev/null +++ b/script/gtl_scr_spec.h @@ -0,0 +1,176 @@ +#ifndef SCR_SPEC_H +#define SCR_SPEC_H + +#include +#include +#include +#include + +#include "math/gtl_math_spec.h" + +#include "gtl_scr_spec_harms.h" +#include "gtl_scr_spec_humps.h" + +#include "script_global.h" + +namespace gtl +{ + namespace scr + { + class SCRIPT_EXPORT spec : public gtl::math::spec + { + Q_OBJECT + + Q_PROPERTY(QString name READ name WRITE set_name NOTIFY name_changed) + Q_PROPERTY(int color READ color WRITE set_color NOTIFY color_changed) + Q_PROPERTY(bool visible READ is_visible WRITE set_visible NOTIFY visible_changed) + Q_PROPERTY(int smoothing_factor READ smoothing_factor WRITE set_smoothing_factor NOTIFY smoothing_factor_changed) + Q_PROPERTY(int smoothed_line_color READ smoothed_line_color WRITE set_smoothed_line_color NOTIFY smoothed_line_color_changed) + Q_PROPERTY(qreal peak_level READ peak_level WRITE set_peak_level NOTIFY peak_level_changed) + Q_PROPERTY(QVariant harms_sets READ get_harms_sets NOTIFY harms_sets_changed) + Q_PROPERTY(qreal harm_tolerance READ harm_tolerance WRITE set_harm_tolerance NOTIFY harm_tolerance_changed) + Q_PROPERTY(qreal integral_index READ integral_index NOTIFY integral_index_changed) + Q_PROPERTY(QJsonArray base READ base NOTIFY spec_changed) + Q_PROPERTY(QJsonArray data READ data NOTIFY spec_changed) + Q_PROPERTY(QJsonArray env READ env NOTIFY spec_changed) + + public: + enum units + { + unit = gtl::unit, + db = gtl::db + }; + Q_ENUMS(units) + + enum windows + { + rectangular, + cosine, + hann, + bartlett_hann, + hamming, + blackman, + blackman_harris, + flattop, + }; + Q_ENUM(windows) + + + public: + spec(gtl::math::spec::types type, gtl::analog_data *ad); + + QString name() const; + int color() const; + bool is_visible() const; + + int smoothing_factor() const; + int smoothed_line_color() const; + + qreal peak_level() const; + + void get_smoothed_line(std::back_insert_iterator> data) const; + void get_peaks(std::back_insert_iterator> peaks) const; + + QVariant get_harms_sets() const; + + int harms_sets_count() const; + spec_harms* harms_set(uint idx) const; + + void get_harms(std::back_insert_iterator> harms); + void get_humps(std::back_insert_iterator> humps); + + qreal harm_tolerance() const; + + qreal get_amplitude(qreal freq) const; + + void get_state(QJsonObject &root) const; + void set_state(const QJsonObject &root); + + qreal integral_index() const; + + static void get_avg(int points, std::vector::const_iterator begin, std::vector::const_iterator end, std::back_insert_iterator> out); + + private: + qreal get_y(qreal x, const std::vector &series) const; + QJsonArray base() const; + QJsonArray data() const; + QJsonArray env() const; + + private: + QString _name; + int _color; + int _is_visible; + + int _smoothing_factor; + std::vector _smoothed_line; + int _smoothed_line_color; + + qreal _peak_level; + std::vector _peaks; + std::vector _peaks_freqs; + + QList _harms_sets; + QList _humps_sets; + + qreal _harm_tolerance; + qreal _max_harm_level; + + + protected: + virtual void before_updating(); + virtual void after_updating(); + + private: + qreal get_freq_for_max_ampl(qreal freq, qreal tolerance); + void set_max_harm_ampl(); + + public slots: + void set_name(QString value); + void set_color(int value); + void set_visible(bool value); + + void set_smoothing_factor(int value); + void set_smoothed_line_color(int value); + + void set_peak_level(qreal value); + + QJSValue add_harms_set(qreal freq, int k, int color = 0, qreal weight = 1); + QJSValue addHarmsSet(const QJsonObject args); + QJSValue addHumps(const QJsonObject& args); + + void clear_harms_sets(); + void clear_humps(); + + void set_harm_tolerance(qreal value); + + qreal max_harm_level() const; + + + private slots: + void get_peak_freq(qreal freq, qreal tolerance, qreal &peak_freq); + void get_ampl(qreal freq, qreal &res); + void get_base(qreal freq, qreal &res); + void reset(); + + void get_max_harm_level(qreal &value) const; + + signals: + void spec_changed(); + void name_changed(); + void color_changed(); + void visible_changed(); + void smoothing_factor_changed(); + void smoothed_line_color_changed(); + void peak_level_changed(); + void harms_sets_changed(); + void harm_tolerance_changed(); + void harms_set_visible_changed(); + void integral_index_changed(); + + void create_engine_object(QObject*, QJSValue &); + + }; + } +} + +#endif // SCR_SPEC_H diff --git a/script/gtl_scr_spec_harm.cpp b/script/gtl_scr_spec_harm.cpp new file mode 100644 index 0000000..715b503 --- /dev/null +++ b/script/gtl_scr_spec_harm.cpp @@ -0,0 +1,160 @@ +#include "gtl_scr_spec_harm.h" + +namespace gtl +{ + namespace scr + { + spec_harm::spec_harm(QObject *parent, qreal freq, int color, qreal weight) + : QObject{parent} + , _freq(freq) + , _color(color) + , _weight(weight) + , _tolerance(1) + { + + } + + spec_harm::spec_harm(QObject *parent) + : QObject{parent} + , _weight(1) + , _tolerance(1) + { + + } + + qreal spec_harm::freq() const + { + return _freq; + } + + qreal spec_harm::peak_freq() const + { + qreal peak_freq = -1; + emit get_peak_freq(_freq, _tolerance, peak_freq); + return peak_freq; + } + + int spec_harm::color() const + { + return _color; + } + + qreal spec_harm::weight() const + { + return _weight; + } + + bool spec_harm::is_present() const + { + return peak_freq() >= 0; + } + + qreal spec_harm::amplitude() const + { + qreal freq = peak_freq(); + if(freq < 0) + freq = _freq; + + qreal value = 0; + emit get_amplitude(freq, value); + + return value; + } + + qreal spec_harm::level() const + { + return amplitude() - base(); + } + + qreal spec_harm::base() const + { + qreal freq = peak_freq(); + if(freq < 0) + freq = _freq; + + qreal value = 0; + emit get_base(freq, value); + + return value; + } + + qreal spec_harm::tolerance() const + { + return _tolerance; + } + + qreal spec_harm::integral_index() const + { + qreal max_hamr_ampl = 0; + emit get_max_harm_ampl(max_hamr_ampl); + + if(max_hamr_ampl == 0) + return 0; + + qreal modulating_harms_intergal_index = 0; + emit get_modulating_harms_integral_index(modulating_harms_intergal_index); + + return level()/max_hamr_ampl*weight() + modulating_harms_intergal_index; + } + + void spec_harm::update() + { + emit amplitude_changed(); + } + + void spec_harm::save_state(QJsonObject &root) + { + root.insert("freq", freq()); + root.insert("color", color()); + root.insert("weight", weight()); + root.insert("tolerance", tolerance()); + root.insert("is_present", is_present()); + root.insert("level", level()); + root.insert("ampl", amplitude()); + root.insert("base", base()); + } + + void spec_harm::restor_state(const QJsonObject &root) + { + _freq = root["freq"].toDouble(); + _color = root["color"].toInt(); + _weight = root["weight"].toDouble(); + _tolerance = root["tolerance"].toDouble(); + } + + void spec_harm::get_max_level(qreal &max_ampl) + { + qreal ampl = level(); + if(ampl > max_ampl) + max_ampl = ampl; + } + + void spec_harm::set_color(int value) + { + if(_color != value) + { + _color = value; + emit color_changed(); + } + } + + void spec_harm::set_weight(qreal value) + { + if(_weight != value) + { + _weight = value; + emit weight_changed(); + } + } + + void spec_harm::set_tolerance(qreal value) + { + if(value != _tolerance) + { + _tolerance = value; + + emit tolerance_changed(); + } + } + } +} diff --git a/script/gtl_scr_spec_harm.h b/script/gtl_scr_spec_harm.h new file mode 100644 index 0000000..e8acca1 --- /dev/null +++ b/script/gtl_scr_spec_harm.h @@ -0,0 +1,82 @@ +#ifndef SPEC_HARM_H +#define SPEC_HARM_H + +#include +#include + +#include "script_global.h" + +namespace gtl +{ + namespace scr + { + class SCRIPT_EXPORT spec_harm : public QObject + { + Q_OBJECT + Q_PROPERTY(qreal freq READ freq CONSTANT) + Q_PROPERTY(qreal peak_freq READ peak_freq CONSTANT) + Q_PROPERTY(int color READ color WRITE set_color NOTIFY color_changed) + Q_PROPERTY(qreal weight READ weight WRITE set_weight NOTIFY weight_changed) + Q_PROPERTY(qreal amplitude READ amplitude NOTIFY amplitude_changed) + Q_PROPERTY(qreal level READ level NOTIFY amplitude_changed) + Q_PROPERTY(bool is_present READ is_present NOTIFY amplitude_changed) + Q_PROPERTY(qreal base READ base NOTIFY amplitude_changed) + Q_PROPERTY(qreal integral_index READ integral_index NOTIFY amplitude_changed) + Q_PROPERTY(qreal tolerance READ tolerance WRITE set_tolerance NOTIFY tolerance_changed) + + public: + explicit spec_harm(QObject *parent, qreal freq, int color, qreal weight); + spec_harm(QObject *parent); + + qreal freq() const; + qreal peak_freq() const; + int color() const; + qreal weight() const; + + bool is_present() const; + qreal amplitude() const; + qreal level() const; + qreal base() const; + qreal tolerance() const; + + qreal integral_index() const; + + void update(); + + void save_state(QJsonObject &root); + void restor_state(const QJsonObject &root); + + void get_max_level(qreal &max_ampl); + + private: + qreal _freq; + int _color; + qreal _weight; + qreal _tolerance; + + public slots: + void set_color(int value); + void set_weight(qreal value); + void set_tolerance(qreal value); + + + + signals: + void color_changed(); + void weight_changed(); + void tolerance_changed(); + + void get_peak_freq(qreal freq, qreal tolerance, qreal&) const; + + void amplitude_changed(); + void get_amplitude(qreal freq, qreal &value) const; + void get_base(qreal freq, qreal &value) const; + + void get_max_harm_ampl(qreal &value) const; + void get_modulating_harms_integral_index(qreal &value) const; + + }; + } +} + +#endif // SPEC_HARM_H diff --git a/script/gtl_scr_spec_harms.cpp b/script/gtl_scr_spec_harms.cpp new file mode 100644 index 0000000..7cf2886 --- /dev/null +++ b/script/gtl_scr_spec_harms.cpp @@ -0,0 +1,313 @@ +#include "gtl_scr_spec_harms.h" + +namespace gtl +{ + namespace scr + { + spec_harms::spec_harms(QObject *parent) + : QObject(parent) + , _tolerance(1) + , _is_visible(true) + { + + } + + spec_harms::spec_harms(QObject *parent, qreal freq, int k, int color, qreal weight) + : QObject(parent) + , _tolerance(1) + , _is_visible(true) + { + for(int i = 0; i < k; i++) + add_harm(new spec_harm(this, freq*(i + 1), color, weight)); + } + + spec_harms::~spec_harms() + { + clear(); + } + + QVariant spec_harms::get_harms() const + { + return QVariant::fromValue(static_cast>(*this)); + } + + QVariant spec_harms::get_modulating() const + { + return QVariant::fromValue(_modulating_harms); + } + + void spec_harms::get_harms(std::back_insert_iterator> harms) + { + for(auto harm : *this) + { + harms = harm; + harms++; + } + + for(auto modulating_harms : _modulating_harms) + modulating_harms->get_harms(harms); + } + + void spec_harms::update() + { + for(int i = 0; i < size(); i++) + at(i)->update(); + + for(int i = 0; i < _modulating_harms.size(); i++) + _modulating_harms[i]->update(); + + } + + void spec_harms::save_state(QJsonObject &root) + { + root["name"] = _name; + int idx = 0; + + QJsonArray harms; + for(auto harm : *this) + { + QJsonObject harm_object; + harm->save_state(harm_object); + + + if(idx < _modulating_harms.size()) + { + QJsonObject modulatings; + _modulating_harms[idx]->save_state(modulatings); + harm_object.insert("modulatings", modulatings); + } + + harms.append(harm_object); + idx++; + } + + root["harms"] = harms; + + } + + void spec_harms::restore_state(const QJsonObject &root) + { + set_name(root["name"].toString()); + clear(); + + QJsonArray harms = root["harms"].toArray(); + for(auto it: harms) + { + spec_harm *harm = new spec_harm(this); + add_harm(harm); + + harm->restor_state(it.toObject()); + + QJsonObject modulating = it.toObject()["modulatings"].toObject(); + + if(modulating.empty()) + continue; + + spec_harms *harms = new spec_harms(this); + + harms->set_tolerance(_tolerance); + + connect(harms, &spec_harms::get_peak_freq, this, &spec_harms::get_peak_freq); + connect(harms, &spec_harms::get_harm_amplitude, this, &spec_harms::get_harm_amplitude); + connect(harms, &spec_harms::get_harm_base, this, &spec_harms::get_harm_base); + connect(harms, &spec_harms::get_max_harm_level, this, &spec_harms::get_max_harm_level); + connect(harms, &spec_harms::integral_index_changed, this, &spec_harms::integral_index_changed); + + _modulating_harms.push_back(harms); + + harms->restore_state(modulating); + + } + } + + qreal spec_harms::tolerance() const + { + return _tolerance; + } + + QString spec_harms::name() const + { + return _name; + } + + bool spec_harms::is_visible() const + { + return _is_visible; + } + + void spec_harms::get_max_ampl(qreal &max_ampl) + { + for(int j = 0; j < size(); j++) + at(j)->get_max_level(max_ampl); + + for(auto harm : _modulating_harms) + harm->get_max_ampl(max_ampl); + } + + qreal spec_harms::integral_index() const + { + qreal value = 0; + for(int j = 0; j < size(); j++) + value += at(j)->integral_index(); + + return value; + } + + void spec_harms::add_harm(spec_harm *harm) + { + push_back(harm); + + harm->set_tolerance(_tolerance); + + connect(harm, &spec_harm::get_peak_freq, this, &spec_harms::get_peak_freq); + connect(harm, &spec_harm::get_amplitude, this, &spec_harms::get_harm_amplitude); + connect(harm, &spec_harm::get_base, this, &spec_harms::get_harm_base); + connect(harm, &spec_harm::get_max_harm_ampl, this, &spec_harms::get_max_harm_level); + connect(harm, &spec_harm::get_modulating_harms_integral_index, this, &spec_harms::get_modulating_harms_integral_index); + connect(harm, &spec_harm::amplitude_changed, this, &spec_harms::integral_index_changed); + } + + int spec_harms::get_cnt_harms(int from, int step) + { + int cnt = 0; + for(int i = from; i < size(); i += step) + { + if(at(i)->is_present()) + cnt++; + } + + return cnt; + } + + void spec_harms::modulate(qreal freq, int k, int color, qreal weight) + { + for(int j = 0; j < size(); j++) + { + spec_harms *harms = new spec_harms(this); + + harms->set_tolerance(_tolerance); + + connect(harms, &spec_harms::get_peak_freq, this, &spec_harms::get_peak_freq); + connect(harms, &spec_harms::get_harm_amplitude, this, &spec_harms::get_harm_amplitude); + connect(harms, &spec_harms::get_harm_base, this, &spec_harms::get_harm_base); + connect(harms, &spec_harms::get_max_harm_level, this, &spec_harms::get_max_harm_level); + + _modulating_harms.push_back(harms); + + for(int i = 1; i <= k; i++) + { + harms->add_harm(new spec_harm(this, at(j)->freq() + freq*i, color, weight)); + harms->add_harm(new spec_harm(this, at(j)->freq() - freq*i, color, weight)); + } + } + } + + int spec_harms::get_count(uint from, int fails, int cnt_mods, bool both_sides) const + { + int cnt = 0; + int cnt_fails = 0; + for(int i = from; i < size(); i++) + { + if(at(i)->is_present()) + { + + int mods = 0; + + if(i < _modulating_harms.size()) + { + int cnt_mods_left = _modulating_harms[i]->get_cnt_harms(1, 2); + int cnt_mods_right = _modulating_harms[i]->get_cnt_harms(0, 2); + + if(both_sides) + mods = cnt_mods_left + cnt_mods_right; + else + mods = qMax(cnt_mods_left, cnt_mods_right); + } + + if(mods >= cnt_mods) + { + cnt++; + cnt_fails = 0; + } + else + { + cnt_fails++; + } + + + } + else + { + cnt_fails++; + } + + if(cnt_fails > fails) + break; + } + + return cnt; + } + + void spec_harms::clear() + { + for(auto it : *this) + delete it; + + QList::clear(); + + for(auto it : _modulating_harms) + delete it; + + _modulating_harms.clear(); + } + + void spec_harms::set_tolerance(qreal value) + { + if(_tolerance != value) + { + _tolerance = value; + + for(int i = 0; i < size(); i++) + at(i)->set_tolerance(_tolerance); + + for(int i = 0; i < _modulating_harms.size(); i++) + _modulating_harms[i]->set_tolerance(_tolerance); + + + emit tolerance_changed(); + } + } + + void spec_harms::set_name(QString value) + { + if(_name != value) + { + _name = value; + emit name_changed(); + } + } + + void spec_harms::set_visible(bool value) + { + if(_is_visible != value) + { + _is_visible = value; + emit visible_changed(); + } + } + + void spec_harms::get_modulating_harms_integral_index(qreal &value) + { + auto it = std::find(begin(), end(), sender()); + if(it == end()) + return; + + int idx = std::distance(begin(), it); + if(idx >= _modulating_harms.size()) + return; + + value += _modulating_harms[idx]->integral_index(); + + } + } +} diff --git a/script/gtl_scr_spec_harms.h b/script/gtl_scr_spec_harms.h new file mode 100644 index 0000000..1553b3f --- /dev/null +++ b/script/gtl_scr_spec_harms.h @@ -0,0 +1,84 @@ +#ifndef SPEC_HARMS_H +#define SPEC_HARMS_H + +#include +#include +#include + +#include "gtl_scr_spec_harm.h" + +#include "script_global.h" + +namespace gtl +{ + namespace scr + { + class SCRIPT_EXPORT spec_harms : public QObject, public QList + { + Q_OBJECT + Q_PROPERTY(QVariant harms READ get_harms CONSTANT) + Q_PROPERTY(QVariant modulating READ get_modulating CONSTANT) + Q_PROPERTY(qreal tolerance READ tolerance WRITE set_tolerance NOTIFY tolerance_changed) + Q_PROPERTY(QString name READ name WRITE set_name NOTIFY name_changed) + Q_PROPERTY(bool is_visible READ is_visible WRITE set_visible NOTIFY visible_changed) + Q_PROPERTY(qreal integral_index READ integral_index NOTIFY integral_index_changed) + + public: + spec_harms(QObject *parent); + spec_harms(QObject *parent, qreal freq, int k, int color, qreal weight); + ~spec_harms(); + QVariant get_harms() const; + QVariant get_modulating() const; + + void get_harms(std::back_insert_iterator> harms); + + void update(); + + void save_state(QJsonObject &root); + void restore_state(const QJsonObject &root); + qreal tolerance() const; + QString name() const; + bool is_visible() const; + + void get_max_ampl(qreal& max_ampl); + + qreal integral_index() const; + + private: + void add_harm(spec_harm* harm); + int get_cnt_harms(int from, int step); + + private: + QList _modulating_harms; + qreal _tolerance; + QString _name; + bool _is_visible; + + public slots: + void modulate(qreal freq, int k, int color = 0, qreal weight = 1); + int get_count(uint from = 0, int fails = 1000, int cnt_mod = 0, bool both_sides = false) const; + void clear(); + void set_tolerance(qreal value); + void set_name(QString value); + void set_visible(bool value); + + + private slots: + void get_modulating_harms_integral_index(qreal &value); + + + signals: + void harms_changed(); + void get_peak_freq(qreal freq, qreal tolerance, qreal &peak_freq); + void get_harm_amplitude(qreal freq, qreal &value); + void get_harm_base(qreal freq, qreal &value); + void tolerance_changed(); + void name_changed(); + void visible_changed(); + void integral_index_changed(); + + void get_max_harm_level(qreal &value) const; + }; + } +} +#endif // SPEC_HARMS_H diff --git a/script/gtl_scr_spec_humps.cpp b/script/gtl_scr_spec_humps.cpp new file mode 100644 index 0000000..25ee252 --- /dev/null +++ b/script/gtl_scr_spec_humps.cpp @@ -0,0 +1,97 @@ +#include "gtl_scr_spec_humps.h" + +#include "gtl_scr_spec.h" + +namespace gtl +{ + namespace scr + { + spec_humps::spec_humps(QObject *parent, const QJsonObject &args) + : QObject{parent} + , _left(args.value("left").toDouble(0)) + , _right(args.value("right").toDouble(0)) + , _color(args.value("color").toInt(0)) + , _smoothing_factor(args.value("smoothing").toObject().value("factor").toInt(1)) + , _smoothing_passes(args.value("smoothing").toObject().value("passes").toInt(1)) + { + + } + + void spec_humps::update() + { + _line.clear(); + static_cast(parent())->get_smoothed_line(std::back_inserter(_line)); + + for(int i = 0; i < _smoothing_passes; i++) + { + std::vector src = _line; + _line.clear(); + spec::get_avg(_smoothing_factor, src.cbegin(), src.cend(), std::back_inserter(_line)); + } + + _dx = static_cast(parent())->resolution(); + _left_idx = qMax(0, qRound(_left/_dx)); + _right_idx = qMin((int)static_cast(parent())->size() - 1, qRound(_right/_dx)); + _x0 = _left_idx*_dx; + + _humps.clear(); + + hump hump; + hump.left_idx = -1; + + for(int i = _left_idx; i <= _right_idx; i++) + { + hump.area += _line[i]; + + if((_line[i]< _line[i + 1] && _line[i - 1] >= _line[i])) + { + if(hump.left_idx >= 0) + { + + hump.right_idx = i - _left_idx; + hump.bgnd = (hump.right_idx - hump.left_idx + 1)*(_line[hump.right_idx] + _line[hump.left_idx])*0.5; + hump.net = hump.area - hump.bgnd; + + _humps.push_back(hump); + } + + hump.left_idx = i - _left_idx; + hump.area = 0; + } + } + + + + } + + void spec_humps::get_line(std::back_insert_iterator> line) + { + std::copy(_line.begin() + _left_idx, _line.begin() + _right_idx + 1, line); + } + + qreal spec_humps::x0() const + { + return _x0; + } + + qreal spec_humps::dx() const + { + return _dx; + } + + int spec_humps::color() const + { + return _color; + } + + int spec_humps::count() const + { + return _humps.size(); + } + + const spec_humps::hump &spec_humps::operator[](int idx) const + { + return _humps[idx]; + } + } +} diff --git a/script/gtl_scr_spec_humps.h b/script/gtl_scr_spec_humps.h new file mode 100644 index 0000000..eb5b6c6 --- /dev/null +++ b/script/gtl_scr_spec_humps.h @@ -0,0 +1,62 @@ +#ifndef SPEC_HUMPS_H +#define SPEC_HUMPS_H + +#include + +#include "math/gtl_math_spec.h" + +#include "script_global.h" + +namespace gtl +{ + namespace scr + { + class SCRIPT_EXPORT spec_humps : public QObject + { + Q_OBJECT + + public: + struct hump + { + hump() : area(0){} + + qreal area; + int left_idx; + int right_idx; + qreal bgnd; + qreal net; + }; + + public: + explicit spec_humps(QObject *parent, const QJsonObject &args); + + void update(); + + void get_line(std::back_insert_iterator> line); + qreal x0() const; + qreal dx() const; + int color() const; + int count() const; + + const hump& operator[](int idx) const; + + private: + qreal _left; + qreal _right; + int _color; + int _smoothing_factor; + int _smoothing_passes; + int _left_idx; + int _right_idx; + qreal _x0; + qreal _dx; + std::vector _line; + std::vector _humps; + + signals: + + }; + } +} + +#endif // SPEC_HUMPS_H diff --git a/script/gtl_scr_specs_model.cpp b/script/gtl_scr_specs_model.cpp new file mode 100644 index 0000000..a78dee3 --- /dev/null +++ b/script/gtl_scr_specs_model.cpp @@ -0,0 +1,99 @@ +#include "gtl_scr_specs_model.h" + +namespace gtl +{ + namespace scr + { + specs_model::specs_model(QObject *parent) + : QAbstractListModel(parent) + { + } + + int specs_model::rowCount(const QModelIndex &parent) const + { + return _specs.size(); + } + + Qt::ItemFlags specs_model::flags(const QModelIndex &index) const + { + return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable; + } + + QVariant specs_model::data(const QModelIndex &index, int role) const + { + if (!index.isValid()) + return QVariant(); + + if(role == Qt::DisplayRole) + return _specs[index.row()]->name(); + else if(role == Qt::ForegroundRole) + return QBrush(_specs[index.row()]->color()); + if(role == Qt::CheckStateRole) + return _specs[index.row()]->is_visible() ? Qt::Checked : Qt::Unchecked; + + return QVariant(); + } + + bool specs_model::setData(const QModelIndex &index, const QVariant &value, int role) + { + if(role == Qt::CheckStateRole) + { + _specs[index.row()]->set_visible(value.toBool()); + + return true; + } + + return false; + } + + void specs_model::add_spec(spec *s) + { + beginInsertRows(QModelIndex(), _specs.size(), _specs.size()); + + _specs.push_back(s); + connect(s, >l::scr::spec::deleting, this, >l::scr::specs_model::deleting_spec); + connect(s, >l::scr::spec::visible_changed, this, >l::scr::specs_model::visibility_spec_chagned); + + if(s->is_visible()) + emit show_spec(s); + + endInsertRows(); + + } + + void specs_model::deleting_spec() + { + auto it = std::find(_specs.begin(), _specs.end(), sender()); + + if(it == _specs.end()) + return; + + int idx = std::distance(_specs.begin(), it); + + beginRemoveRows(QModelIndex(), idx, idx); + + _specs.erase(it); + + endRemoveRows(); + } + + void specs_model::visibility_spec_chagned() + { + auto it = std::find(_specs.begin(), _specs.end(), sender()); + + if(it == _specs.end()) + return; + + int idx = std::distance(_specs.begin(), it); + spec* s = _specs[idx]; + + if(s->is_visible()) + emit show_spec(s); + else + emit hide_spec(s); + + emit dataChanged(index(idx), index(idx), QList() << Qt::CheckStateRole); + + } + } +} diff --git a/script/gtl_scr_specs_model.h b/script/gtl_scr_specs_model.h new file mode 100644 index 0000000..8d8eb65 --- /dev/null +++ b/script/gtl_scr_specs_model.h @@ -0,0 +1,46 @@ +#ifndef SPECS_MODEL_H +#define SPECS_MODEL_H + +#include +#include + +#include "gtl_scr_spec.h" + +#include "script_global.h" + +namespace gtl +{ + namespace scr + { + class SCRIPT_EXPORT specs_model : public QAbstractListModel + { + Q_OBJECT + + public: + explicit specs_model(QObject *parent = nullptr); + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + private: + std::vector _specs; + + public slots: + void add_spec(gtl::scr::spec*); + + private slots: + void deleting_spec(); + void visibility_spec_chagned(); + + signals: + void show_spec(gtl::scr::spec*); + void hide_spec(gtl::scr::spec*); + }; + } +} + +#endif // SPECS_MODEL_H diff --git a/script/script.pro b/script/script.pro new file mode 100644 index 0000000..e3084b4 --- /dev/null +++ b/script/script.pro @@ -0,0 +1,70 @@ +QT += qml xml + +TEMPLATE = lib +DEFINES += SCRIPT_LIBRARY +TARGET = gtl_script + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + gtl_scr_analog_data.cpp \ + gtl_scr_engine.cpp \ + gtl_scr_engine_diagnostic.cpp \ + gtl_scr_engine_table.cpp \ + gtl_scr_engine_table_model.cpp \ + gtl_scr_spec.cpp \ + gtl_scr_spec_harm.cpp \ + gtl_scr_spec_harms.cpp \ + gtl_scr_spec_humps.cpp \ + gtl_scr_specs_model.cpp + +HEADERS += \ + gtl_scr_analog_data.h \ + gtl_scr_engine.h \ + gtl_scr_engine_diagnostic.h \ + gtl_scr_engine_table.h \ + gtl_scr_engine_table_model.h \ + gtl_scr_spec.h \ + gtl_scr_spec_harm.h \ + gtl_scr_spec_harms.h \ + gtl_scr_spec_humps.h \ + gtl_scr_specs_model.h \ + script_global.h + +# Default rules for deployment. +unix { + target.path = /usr/lib +} +!isEmpty(target.path): INSTALLS += target + +win32:CONFIG(release, debug|release): DESTDIR = ../.output/release +else:win32:CONFIG(debug, debug|release): DESTDIR = ../.output/debug +else:unix: DESTDIR = ../../.ouput + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_core +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_core + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_math +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_math +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_math + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_hw +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_hw +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_hw + +INCLUDEPATH += $$PWD/../ +DEPENDPATH += $$PWD/../ + +INCLUDEPATH += $$(DSPFILTERSPATH)/include +DEPENDPATH += $$(DSPFILTERSPATH)/include + +INCLUDEPATH += $$(FFTWPATH) + +LIBS += -L$$(FFTWPATH) -llibfftw3-3 + + diff --git a/script/script_global.h b/script/script_global.h new file mode 100644 index 0000000..8346801 --- /dev/null +++ b/script/script_global.h @@ -0,0 +1,12 @@ +#ifndef SCRIPT_GLOBAL_H +#define SCRIPT_GLOBAL_H + +#include + +#if defined(SCRIPT_LIBRARY) +# define SCRIPT_EXPORT Q_DECL_EXPORT +#else +# define SCRIPT_EXPORT Q_DECL_IMPORT +#endif + +#endif // SCRIPT_GLOBAL_H diff --git a/test_gtl/main.cpp b/test_gtl/main.cpp new file mode 100644 index 0000000..d96530d --- /dev/null +++ b/test_gtl/main.cpp @@ -0,0 +1,14 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication::setOrganizationName("GTLab"); + QApplication::setApplicationName("test_gtl"); + + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/test_gtl/mainwindow.cpp b/test_gtl/mainwindow.cpp new file mode 100644 index 0000000..f3d0998 --- /dev/null +++ b/test_gtl/mainwindow.cpp @@ -0,0 +1,269 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include "core/gtl_logger.h" + +#include "gui/osc/gtl_gui_osc_widget.h" + +#include "gui/gtl_gui_options_dialog.h" +#include "gui/gtl_gui_scr_options_widget.h" + + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +#ifdef QT_DEBUG + , _hw(new gtl::hw::hw("../.output/.hwplugins/debug", this)) +#else + , _hw(new gtl::hw::hw("../.output/.hwplugins/release", this)) +#endif +{ + ui->setupUi(this); + + gtl::logger::init("./", 10, gtl::logger::mb); + + _model = new gtl::data_model(this); + _selection_data_model = new gtl::selection_data_model(/*_model*/this); + _selection_data_model->setSourceModel(_model); + + ui->tree_hw->setModel(_selection_data_model); + + +// _device = _hw->create_device("ni", this); +// _device = new gtl::hw::generator(this); + +// _model->add_device(_device->gtl_device()); + + +// _device->start("Dev1", 51200); + + gtl::gui::osc::chart* chart = new gtl::gui::osc::chart(this); + gtl::gui::chart_widget *widget = new gtl::gui::chart_widget(chart, this); + + ui->cb_axis_y_mode->setChecked(chart->is_axis_y_multi()); + connect(ui->cb_axis_y_mode, &QCheckBox::clicked, chart, >l::gui::osc::chart::set_axis_y_mode); + + ui->num_time->setValue(chart->time()); + connect(ui->num_time, &QDoubleSpinBox::valueChanged, chart, >l::gui::osc::chart::set_time); + + ui->btn_run->setChecked(chart->is_updating()); + connect(ui->btn_run, &QPushButton::clicked, chart, >l::gui::chart::set_updating); + + connect(_selection_data_model, >l::selection_data_model::selected, chart, >l::gui::chart::add_ad); + connect(_selection_data_model, >l::selection_data_model::deselected, chart, >l::gui::chart::remove_ad); + + connect(_selection_data_model, >l::selection_data_model::selected, &_engine, >l::scr::engine::add_ad); + connect(_selection_data_model, >l::selection_data_model::deselected, &_engine, >l::scr::engine::remove_ad); + + //chart->add_ai(_device->ai(3)); + +// setCentralWidget(chart); + + ui->splitter->insertWidget(0, widget); + + _scr_table = new gtl::gui::scr_table(_model, this); + ui->frame->layout()->addWidget(_scr_table); + + ui->tabWidget->addTab(new gtl::gui::osc::widget(this, _model), "osc"); + + _spec = new gtl::gui::spec_widget(this, gtl::math::spec::ausp, _model); + ui->tabWidget->addTab(_spec, "spec"); + + _apfc = new gtl::gui::apfc_widget(this, _model); + ui->tabWidget->addTab(_apfc, "apfc"); + + _cross_spec = new gtl::gui::cross_spec_widget(this, _model); + ui->tabWidget->addTab(_cross_spec, "cross_spec"); + + _octv = new gtl::gui::octv_widget(this, _model); + ui->tabWidget->addTab(_octv, "octave"); + + _orbit = new gtl::gui::orbit::widget(this, _model); + ui->tabWidget->addTab(_orbit, "orbit"); + + _diagnostic_engine = new gtl::scr::engine_diagnostic(this); + _diagnostic = new gtl::gui::scr_widget(_diagnostic_engine, _model, this); + + + ui->tabWidget->addTab(_diagnostic, "diagnostic"); + connect(_diagnostic, >l::gui::scr_widget::init_editor_menu, this, &MainWindow::init_scr_editor_menu); + + show_scr_specs_action = new QAction("Show specs"); + _scr_specs_dialog = new scr_specs_dialog(_diagnostic_engine, this); + connect(show_scr_specs_action, &QAction::triggered, _scr_specs_dialog, &QWidget::show); + + evaluate_scr_action = new QAction("Evaluate"); + connect(evaluate_scr_action, &QAction::triggered, _diagnostic, >l::gui::scr_widget::evaluate); + + _get_engine_state_action = new QAction("Get state"); + connect(_get_engine_state_action, &QAction::triggered, this, &MainWindow::copy_engine_state_to_clipboard); + + _get_engine_results_action = new QAction("Get results"); + connect(_get_engine_results_action, &QAction::triggered, this, &MainWindow::copy_engine_results_to_clipboard); + + _set_engine_options_action = new QAction("Set options"); + connect(_set_engine_options_action, &QAction::triggered, this, &MainWindow::copy_engine_options_from_clipboard); + + _config_widget = new gtl::gui::config::widget( + _model, +#ifdef QT_DEBUG + "../.output/.hwplugins/debug" +#else + "../.output/.hwplugins/release" +#endif + , this + ); + ui->tabWidget->addTab(_config_widget , "config"); + + _log_viewer = new gtl::gui::log_viewer(this); + ui->tabWidget->addTab(_log_viewer, "log"); + + _recorder = new gtl::gui::recorder_widget(_model, this); + ui->tabWidget->addTab(_recorder, "recorder"); + + _qml = new gtl::gui::scr_qml_widget(_model, this); + ui->tabWidget->addTab(_qml, "qml"); + + QCoreApplication::setOrganizationName("GTLab"); + QCoreApplication::setOrganizationDomain("gtlab.pro"); + QCoreApplication::setApplicationName("test_gtl"); + + gtl::gui::options_dialog* options = new gtl::gui::options_dialog(this); + options->add_options_widget("scripts", new gtl::gui::scr_options_widget(this)); + + connect(ui->actionOptions, &QAction::triggered, [=](){options->exec();}); + + restore(); + + gtl::logger::info("test_gtl", "start"); +} + +MainWindow::~MainWindow() +{ + save(); + + delete ui; +} + +void MainWindow::save() +{ + QDomDocument doc; + QDomElement root = doc.createElement("test_gtl"); + doc.appendChild(root); + + root.setAttribute("geometry", saveGeometry().toBase64()); + root.setAttribute("state", saveState().toBase64()); + + + QDomElement config_element = doc.createElement("config"); + root.appendChild(config_element); + _config_widget->save(config_element); + + QDomElement diagnostic_element = doc.createElement("diagnostic"); + root.appendChild(diagnostic_element); + _diagnostic->save(diagnostic_element); + + QDomElement specs_diagnostic_element = doc.createElement("specs"); + diagnostic_element.appendChild(specs_diagnostic_element); + _scr_specs_dialog->save(specs_diagnostic_element); + + QDomElement spec_element = doc.createElement("spec"); + root.appendChild(spec_element); + _spec->save(spec_element); + + QDomElement orbit_element = doc.createElement("orbit"); + root.appendChild(orbit_element); + _orbit->save(orbit_element); + + QDomElement recorder_element = doc.createElement("recorder"); + root.appendChild(recorder_element); + _recorder->save(recorder_element); + + QDomElement qml_element = doc.createElement("qml"); + root.appendChild(qml_element); + _qml->save(qml_element); + + QFile file_xml("workspace.gtl"); + if (file_xml.open(QIODevice::WriteOnly)) + { + QTextStream stream(&file_xml); +// stream.setCodec(QTextCodec::codecForName("Windows-1251")); + doc.save(stream, 0, QDomNode::EncodingFromTextStream); + file_xml.close(); + } +} + +void MainWindow::restore() +{ + QFile file_xml("workspace.gtl"); + if (file_xml.open(QIODevice::ReadOnly)) + { + QDomDocument doc; + if (doc.setContent(&file_xml)) + { + QDomElement root = doc.documentElement().toElement(); + + QDomElement config_element = root.firstChildElement("config"); + _config_widget->load(config_element); + + QDomElement diagnostic_element = root.firstChildElement("diagnostic"); + _diagnostic->load(diagnostic_element); + + QDomElement specs_diagnostic_element = diagnostic_element.firstChildElement("specs"); + _scr_specs_dialog->load(specs_diagnostic_element); + + QDomElement spec_element = root.firstChildElement("spec"); + _spec->load(spec_element); + + QDomElement orbit_element = root.firstChildElement("orbit"); + _orbit->load(orbit_element); + + QDomElement recorder_element = root.firstChildElement("recorder"); + _recorder->load(recorder_element); + + _qml->load(root.firstChildElement("qml")); + + QDomElement tools_element = root.firstChildElement("tools"); + + restoreGeometry(QByteArray::fromBase64(root.attribute("geometry", "").toUtf8())); + restoreState(QByteArray::fromBase64(root.attribute("state", "").toUtf8())); + } + + } +} + +void MainWindow::init_scr_editor_menu(QMenu *menu) +{ + menu->addSeparator(); + menu->addAction(show_scr_specs_action); + menu->addAction(evaluate_scr_action); + menu->addSeparator(); + menu->addAction(_set_engine_options_action); + menu->addAction(_get_engine_state_action); + menu->addAction(_get_engine_results_action); +} + +void MainWindow::copy_engine_state_to_clipboard() +{ + QJsonObject state_object; + _diagnostic_engine->get_state(state_object); + QJsonDocument state(state_object); + QApplication::clipboard()->setText(QString(state.toJson())); +} + +void MainWindow::copy_engine_options_from_clipboard() +{ + QJsonDocument options = QJsonDocument::fromJson(QApplication::clipboard()->text().toUtf8()); + _diagnostic_engine->set_options(options.object()); +} + +void MainWindow::copy_engine_results_to_clipboard() +{ + QJsonObject results_object; + _diagnostic_engine->get_results(results_object); + QJsonDocument state(results_object); + QApplication::clipboard()->setText(QString(state.toJson())); +} + + + diff --git a/test_gtl/mainwindow.h b/test_gtl/mainwindow.h new file mode 100644 index 0000000..b3bdf7f --- /dev/null +++ b/test_gtl/mainwindow.h @@ -0,0 +1,92 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +#include "hw/gtl_hw.h" +#include "hw/gtl_hw_device.h" +#include "hw/gtl_hw_generator.h" + +#include "math/gtl_math_sum.h" + +#include "gui/osc/gtl_gui_osc_chart.h" + +#include "core/gtl_data_model.h" +#include "core/gtl_selection_data_model.h" +#include "gui/gtl_gui_chart_widget.h" +//#include "gui/gtl_gui_scr_table.h" +#include "script/gtl_scr_engine_table.h" +#include "gui/gtl_gui_scr_table.h" +#include "gui/config/gtl_gui_config_widget.h" +#include "gui/gtl_gui_log_viewer.h" +#include "gui/gtl_gui_scr_widget.h" +#include "gui/gtl_gui_spec_widget.h" +#include "gui/gtl_gui_recorder_widget.h" +#include "gui/gtl_gui_apfc_widget.h" +#include "gui/gtl_gui_cross_spec_widget.h" +#include "gui/gtl_gui_octv_widget.h" +#include "gui/orbit/gtl_gui_orbit_widget.h" + +#include "script/gtl_scr_engine_diagnostic.h" + +#include "scr_specs_dialog.h" +#include "gui/gtl_gui_scr_qml_widget.h" + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + + gtl::hw::hw* _hw; + gtl::hw::device* _device; + gtl::data_model* _model; + gtl::selection_data_model* _selection_data_model; + gtl::gui::scr_table* _scr_table; + + gtl::scr::engine_table _engine; + gtl::scr::engine_diagnostic* _diagnostic_engine; + + gtl::gui::config::widget *_config_widget; + gtl::gui::log_viewer *_log_viewer; + gtl::gui::scr_widget *_diagnostic; + gtl::gui::spec_widget *_spec; + gtl::gui::apfc_widget *_apfc; + gtl::gui::cross_spec_widget *_cross_spec; + gtl::gui::octv_widget *_octv; + gtl::gui::orbit::widget *_orbit; + + QAction *evaluate_scr_action; + QAction *show_scr_specs_action; + QAction *_get_engine_state_action; + QAction *_get_engine_results_action; + QAction *_set_engine_options_action; + scr_specs_dialog *_scr_specs_dialog; + gtl::gui::scr_qml_widget* _qml; + + gtl::gui::recorder_widget* _recorder; + +private: + void save(); + void restore(); + +private slots: + void init_scr_editor_menu(QMenu *menu); + void copy_engine_state_to_clipboard(); + void copy_engine_options_from_clipboard(); + void copy_engine_results_to_clipboard(); +}; +#endif // MAINWINDOW_H diff --git a/test_gtl/mainwindow.ui b/test_gtl/mainwindow.ui new file mode 100644 index 0000000..3394a5a --- /dev/null +++ b/test_gtl/mainwindow.ui @@ -0,0 +1,123 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + 0 + + + + misc + + + + + + Qt::Horizontal + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + false + + + + + + + run + + + true + + + + + + + + + + time + + + sec + + + 3 + + + 0.001000000000000 + + + 100.000000000000000 + + + + + + + is multi axis y + + + + + + + + + + + + + + + + + 0 + 0 + 800 + 22 + + + + + Tools + + + + + + + + + Options + + + + + + diff --git a/test_gtl/options_dialog.cpp b/test_gtl/options_dialog.cpp new file mode 100644 index 0000000..4ce1a68 --- /dev/null +++ b/test_gtl/options_dialog.cpp @@ -0,0 +1,40 @@ +#include "options_dialog.h" +#include "ui_options_dialog.h" + +#include "gui/gtl_gui_scr_options_widget.h" + +options_dialog::options_dialog(QWidget *parent) + : QDialog(parent) + , ui(new Ui::option_dialog) + , _settings("GTLab", "test_gtl") +{ + ui->setupUi(this); + setWindowTitle("Options"); + + _options = new gtl::gui::options_widget(this); + + ui->verticalLayout->insertWidget(0, _options); + + _options->add_group("scripts", new gtl::gui::scr_options_widget(this)); + _options->restore(&_settings); + + connect(ui->apply, &QPushButton::clicked, _options, >l::gui::options_widget::apply); + connect(ui->apply, &QPushButton::clicked, this, &options_dialog::save_settings); + + connect(ui->ok, &QPushButton::clicked, _options, >l::gui::options_widget::apply); + connect(ui->ok, &QPushButton::clicked, this, &QDialog::accept); + connect(ui->ok, &QPushButton::clicked, this, &options_dialog::save_settings); + + connect(ui->cancel, &QPushButton::clicked, _options, >l::gui::options_widget::discard); + connect(ui->cancel, &QPushButton::clicked, this, &QDialog::reject); +} + +options_dialog::~options_dialog() +{ + delete ui; +} + +void options_dialog::save_settings() +{ + _options->save(&_settings); +} diff --git a/test_gtl/scr_specs_dialog.cpp b/test_gtl/scr_specs_dialog.cpp new file mode 100644 index 0000000..a46715a --- /dev/null +++ b/test_gtl/scr_specs_dialog.cpp @@ -0,0 +1,30 @@ +#include "scr_specs_dialog.h" +#include "ui_scr_specs_dialog.h" + +scr_specs_dialog::scr_specs_dialog(gtl::scr::engine *engine, QWidget *parent) + : QDialog(parent) + , ui(new Ui::scr_specs_dialog) +{ + ui->setupUi(this); + + setWindowTitle("Spectrums"); + + + _widget = new gtl::gui::scr_specs_widget(engine, this); + ui->verticalLayout->addWidget(_widget); +} + +scr_specs_dialog::~scr_specs_dialog() +{ + delete ui; +} + +void scr_specs_dialog::load(const QDomElement &root_element) +{ + _widget->load(root_element); +} + +void scr_specs_dialog::save(QDomElement &root_element) +{ + _widget->save(root_element); +} diff --git a/test_gtl/scr_specs_dialog.h b/test_gtl/scr_specs_dialog.h new file mode 100644 index 0000000..facfd0f --- /dev/null +++ b/test_gtl/scr_specs_dialog.h @@ -0,0 +1,28 @@ +#ifndef SCR_SPECS_DIALOG_H +#define SCR_SPECS_DIALOG_H + +#include + +#include "gui/gtl_gui_scr_specs_widget.h" + +namespace Ui { +class scr_specs_dialog; +} + +class scr_specs_dialog : public QDialog +{ + Q_OBJECT + +public: + explicit scr_specs_dialog(gtl::scr::engine *engine, QWidget *parent = nullptr); + ~scr_specs_dialog(); + + virtual void save(QDomElement& root_element); + virtual void load(const QDomElement& root_element); + +private: + Ui::scr_specs_dialog *ui; + gtl::gui::scr_specs_widget *_widget; +}; + +#endif // SCR_SPECS_DIALOG_H diff --git a/test_gtl/scr_specs_dialog.ui b/test_gtl/scr_specs_dialog.ui new file mode 100644 index 0000000..31f4256 --- /dev/null +++ b/test_gtl/scr_specs_dialog.ui @@ -0,0 +1,36 @@ + + + scr_specs_dialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + 2 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + diff --git a/test_gtl/test_gtl.pro b/test_gtl/test_gtl.pro new file mode 100644 index 0000000..030812d --- /dev/null +++ b/test_gtl/test_gtl.pro @@ -0,0 +1,73 @@ +QT += core gui charts qml xml quickwidgets + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + scr_specs_dialog.cpp + +HEADERS += \ + mainwindow.h \ + scr_specs_dialog.h \ + version.h + +FORMS += \ + mainwindow.ui \ + scr_specs_dialog.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + + +INCLUDEPATH += $$PWD/.. +DEPENDPATH += $$PWD/.. + +INCLUDEPATH += $$(COMPONENTSPATH)/qt6 +DEPENDPATH += $$(COMPONENTSPATH)/qt6 + +INCLUDEPATH += $$(COMPONENTSPATH)/!c++ +DEPENDPATH += $$(COMPONENTSPATH)/!c++ + +INCLUDEPATH += $$(DSPFILTERSPATH)/include +DEPENDPATH += $$(DSPFILTERSPATH)/include + +INCLUDEPATH += $$(FFTWPATH) + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_core +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_core + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_math +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_math +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_core + + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_hw +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_hw +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_hw + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_gui +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_gui +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_gui + +win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../.output/release/ -lgtl_script +else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../.output/debug/ -lgtl_script +else:unix: LIBS += -L$$PWD/../.output/ -lgtl_script + +win32:CONFIG(release, debug|release): LIBS += -L$$(COMPONENTSPATH)/qt6/.output/release/ -lchart +else:win32:CONFIG(debug, debug|release): LIBS += -L$$(COMPONENTSPATH)/qt6/.output/debug/ -lchart +else:unix: LIBS += -L$$(COMPONENTSPATH)/qt6/.output/ -lchart + +LIBS += -L$$(FFTWPATH) -llibfftw3-3 \ + -L$$(FFTWPATH) -llibfftw3f-3 \ + -L$$(FFTWPATH) -llibfftw3l-3 \ diff --git a/test_gtl/version.h b/test_gtl/version.h new file mode 100644 index 0000000..44c2e07 --- /dev/null +++ b/test_gtl/version.h @@ -0,0 +1,7 @@ +#ifndef VERSION_H +#define VERSION_H + +#define version_major 0 +#define version_minor 38 + +#endif // VERSION_H