This commit is contained in:
Benedek László 2024-05-15 22:52:13 +02:00
parent 911a174006
commit 9d27db7777
14 changed files with 333 additions and 110 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
build
.cache
.cache
__pycache__

View File

@ -5,6 +5,6 @@
extern "C" {
extern plugin_t plugin;
bool init();
void destroy();
int run();
int destroy();
}

View File

@ -12,7 +12,7 @@ typedef struct {
} point_t;
typedef int (*run_method_t)();
typedef void (*destroy_t)();
typedef int (*destroy_t)();
typedef void (*update_callback_t)(unsigned int chartIndex, const char* seriesName, const point_t* points, int count);
typedef struct {
@ -20,4 +20,6 @@ typedef struct {
run_method_t run_method;
destroy_t destroy_method;
update_callback_t update_callback;
int argc;
char** argv;
} plugin_t;

View File

@ -2,12 +2,11 @@
#include <graph/server/gui/chart/chart.h>
#include <graph/server/plugin.h>
#include <qfiledialog.h>
#include <QFileDialog>
#include <QMainWindow>
#include <QMenuBar>
#include <QStatusBar>
#include <QToolBar>
#include <cstddef>
#include <vector>
namespace Ui {
@ -18,13 +17,14 @@ namespace Graph::GUI {
class MainWindow : public QMainWindow {
Q_OBJECT
private:
explicit MainWindow(QWidget* parent = nullptr);
explicit MainWindow(QWidget* parent = nullptr, int argc = 0, char** argv = nullptr);
public:
MainWindow(MainWindow& other) = delete;
void operator=(const MainWindow&) = delete;
~MainWindow();
static MainWindow* getInstance(int argc, char** argv);
static MainWindow* getInstance();
static void close();
@ -42,5 +42,8 @@ class MainWindow : public QMainWindow {
std::vector<ChartWidget*> charts;
QFileDialog* fileDialog;
int argc;
char** argv;
};
} // namespace Graph::GUI

View File

@ -22,7 +22,7 @@ class Plugin : public QObject {
void exited(int code);
public:
Plugin(const std::string& path, update_callback_t callback);
Plugin(const std::string& path, update_callback_t callback, int argc, char** argv);
~Plugin();
const std::string& getPath() const { return this->path; }

View File

@ -13,6 +13,8 @@ struct chart_spec_t {
QString title;
bool grid = false;
bool legend = false;
bool keep_all = true;
int keep_limit;
struct {
axis_spec_t x, y;
} axis;

View File

@ -1,3 +1,5 @@
file(GLOB_RECURSE HTTP_SOURCES "./*.cpp")
add_library(http SHARED ${HTTP_SOURCES})
target_link_libraries(http librum.a)
# target_link_libraries(http librum.a libflags-cpp.so)
set_target_properties(http PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins")

View File

@ -1,28 +1,72 @@
#include <graph/plugins/http/http.h>
#include <graph/plugins/plugin.h>
#include <cmath>
#include <rum/http/response.h>
#include <rum/http/server.h>
#include <rum/http/uri.h>
#include <rum/tcp/error.h>
// #include <flags-cpp/flags-cpp.h>
#include <iostream>
#include <sstream>
#define PI 3.1415
#define to_rad(x) (x / 180 / 3.1415)
using namespace Rum::HTTP;
// using namespace Flags;
Server* server;
extern "C" {
plugin_t plugin = {.version = 1, .run_method = &run, .destroy_method = &destroy};
int run() {
std::cout << "init" << std::endl;
point_t points[100];
for (int i = 0; i < 100; i++) {
points[i].x = i;
points[i].y = i;
}
// Parser parser;
// int* port = parser.add<int>("port", "http port", false, 8080);
// int* workers = parser.add<int>("workers", "number of worker threads", false, 10);
plugin.update_callback(0, "1st", points, 100);
// bool* help = parser.add<bool>("help", "print help", false, false);
// if (!parser.parse(plugin.argc, plugin.argv) || *help) {
// parser.help();
// }
// server = new Server(*port, *workers, DEFAULT_BUFFER_SIZE);
try {
server = new Server(8080, 10, DEFAULT_BUFFER_SIZE);
server->add_path<GET>("/add", [](const Request& req, Response& resp) {
URI uri = req.copy_uri();
auto params = uri.get_parameters();
int chart = 0;
if (params.contains("chart"))
chart = std::stoi(params.at("chart"));
std::string series = "1st";
if (params.contains("series"))
series = params.at("series");
if (params.contains("x") && params.contains("y")) {
point_t point;
point.x = std::stod(params.at("x"));
point.y = std::stod(params.at("y"));
plugin.update_callback(chart, series.c_str(), &point, 1);
}
});
std::cout << "starting http server" << std::endl;
server->listen();
return 0;
} catch (Rum::TCP::Error e) {
std::cerr << e.what() << std::endl;
delete server;
return 1;
}
}
int destroy() {
if (server)
server->end();
std::cout << "stopped http server" << std::endl;
return 0;
}
void destroy() {
std::cout << "destory" << std::endl;
}
plugin_t plugin = {.version = 1, .run_method = &run, .destroy_method = &destroy};
}

View File

@ -4,8 +4,7 @@
#include <graph/server/plugin.h>
#include <graph/server/spec.h>
#include <graph/server/ui_mainwindow.h>
#include <qobject.h>
#include <qthread.h>
#include <qlineseries.h>
#include <QFileDialog>
#include <QMessageBox>
#include <iostream>
@ -18,6 +17,13 @@ namespace Graph::GUI {
MainWindow* MainWindow::instance = nullptr;
MainWindow* MainWindow::getInstance(int argc, char** argv) {
if (!instance)
instance = new MainWindow(nullptr, argc, argv);
return instance;
}
MainWindow* MainWindow::getInstance() {
if (!instance)
instance = new MainWindow();
@ -46,30 +52,38 @@ void MainWindow::addData(unsigned int chartIndex, const char* seriesName, const
double minX = std::numeric_limits<double>::max(), maxX = std::numeric_limits<double>::min();
double minY = std::numeric_limits<double>::max(), maxY = std::numeric_limits<double>::min();
chart_spec_t spec = chart->getSpec();
for (QAbstractSeries* series : chart->getSeries()) {
if (series->name() == name) {
for (int i = 0; i < count; i++) {
*((QLineSeries*)series) << QPointF(points[i].x, points[i].y);
}
}
for (QPointF point : ((QLineSeries*)series)->points()) {
minX = MIN(minX, point.x());
maxX = MAX(maxX, point.x());
minY = MIN(minY, point.y());
maxY = MAX(maxY, point.y());
if (!spec.keep_all && ((QLineSeries*)series)->count() > spec.keep_limit) {
((QLineSeries*)series)->removePoints(0, ((QLineSeries*)series)->count() - spec.keep_limit);
}
for (QPointF point : ((QLineSeries*)series)->points()) {
minX = MIN(minX, point.x());
maxX = MAX(maxX, point.x());
minY = MIN(minY, point.y());
maxY = MAX(maxY, point.y());
}
if (spec.axis.x.auto_limit)
chart->getX()->setRange(minX, maxX);
if (spec.axis.y.auto_limit)
chart->getY()->setRange(minY, maxY);
break;
}
}
if (chart->getSpec().axis.x.auto_limit)
chart->getX()->setRange(minX, maxX);
if (chart->getSpec().axis.y.auto_limit)
chart->getY()->setRange(minY, maxY);
chart->getView()->update();
}
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
MainWindow::MainWindow(QWidget* parent, int argc, char** argv) : QMainWindow(parent), ui(new Ui::MainWindow), argc(argc), argv(argv) {
ui->setupUi(this);
// plugins
@ -126,8 +140,19 @@ void MainWindow::on_action_Load_triggered() {
return;
for (QString file : fileDialog->selectedFiles()) {
bool loaded = false;
for (Plugin* plugin : pluginModel->getPlugins()) {
if (plugin->getPath() == file.toStdString()) {
loaded = true;
}
}
if (loaded) {
QMessageBox::critical(this, "Error", "Plugin already loaded:\n" + file, QMessageBox::StandardButton::Ignore);
continue;
}
try {
Plugin* plugin = new Plugin(file.toStdString(), &Graph::GUI::addData);
Plugin* plugin = new Plugin(file.toStdString(), &Graph::GUI::addData, argc, argv);
pluginModel->add(plugin);
QThread* thread = new QThread();
@ -135,11 +160,15 @@ void MainWindow::on_action_Load_triggered() {
connect(plugin, &Plugin::exited, this, [](int code) { std::cout << "plugin exited: " << code << std::endl; });
connect(thread, &QThread::started, plugin, &Plugin::run);
connect(plugin, &Plugin::exited, thread, &QThread::quit);
connect(plugin, &Plugin::exited, this, [this, file](int code) {
if (code != 0)
QMessageBox::critical(this, "Error", "Plugin exited with an error!\n" + file, QMessageBox::StandardButton::Ignore);
});
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
} catch (std::runtime_error e) {
std::cout << e.what() << std::endl;
QMessageBox::critical(this, "Error", "Failed to load plugin:\n" + file, QMessageBox::StandardButton::Ignore);
QMessageBox::critical(this, "Error", "Failed to load plugin!\n" + file, QMessageBox::StandardButton::Ignore);
}
int row = pluginModel->rowCount();

View File

@ -13,7 +13,7 @@ SpecDialog* SpecDialog::getInstance() {
}
void SpecDialog::close() {
if(instance)
if (instance)
delete instance;
}
@ -41,12 +41,12 @@ SpecDialog::~SpecDialog() {
}
chart_spec_t SpecDialog::getSpec() const {
return {
.title = ui->chartTitle->text(),
.grid = (ui->grid->checkState() == Qt::Checked),
.legend = (ui->legend->checkState() == Qt::Checked),
.axis = {
.x = {.label = ui->xTitle->text(), .auto_limit = (ui->xAutoRange->checkState() == Qt::Checked), .min = ui->xMin->value(), .max = ui->xMax->value()},
.y = {.label = ui->yTitle->text(), .auto_limit = (ui->yAutoRange->checkState() == Qt::Checked), .min = ui->yMin->value(), .max = ui->yMax->value()}}};
return {.title = ui->chartTitle->text(),
.grid = ui->grid->isChecked(),
.legend = ui->legend->isChecked(),
.keep_all = ui->keepAll->isChecked(),
.keep_limit = ui->keep->value(),
.axis = {.x = {.label = ui->xTitle->text(), .auto_limit = ui->xAutoRange->isChecked(), .min = ui->xMin->value(), .max = ui->xMax->value()},
.y = {.label = ui->yTitle->text(), .auto_limit = ui->yAutoRange->isChecked(), .min = ui->yMin->value(), .max = ui->yMax->value()}}};
}
} // namespace Graph::GUI

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>302</width>
<height>372</height>
<width>266</width>
<height>334</height>
</rect>
</property>
<property name="sizePolicy">
@ -70,11 +70,17 @@
<item alignment="Qt::AlignTop">
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
@ -96,41 +102,84 @@
<height>16</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string>X</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="xTitle">
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frame">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>Axis Title</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="xAutoRange">
<property name="text">
<string>Auto-Range</string>
</property>
<property name="checked">
<bool>true</bool>
<property name="clearButtonEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="xMin">
<property name="toolTip">
<string>minimum</string>
<widget class="QFrame" name="frame_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="xMax">
<property name="toolTip">
<string>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Range</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="xAutoRange">
<property name="text">
<string>Auto</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="xMin">
<property name="toolTip">
<string>minimum</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="xMax">
<property name="toolTip">
<string>
&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;maximum&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
@ -144,6 +193,9 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
@ -178,29 +230,48 @@
</widget>
</item>
<item>
<widget class="QCheckBox" name="yAutoRange">
<property name="text">
<string>Auto-Range</string>
<widget class="QFrame" name="frame_4">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="checked">
<bool>true</bool>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="yMin">
<property name="toolTip">
<string>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Range</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="yAutoRange">
<property name="text">
<string>Auto</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="yMin">
<property name="toolTip">
<string>
&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;minimum&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="yMax">
<property name="toolTip">
<string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="yMax">
<property name="toolTip">
<string>
&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;maximum&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
@ -210,7 +281,7 @@
</widget>
<widget class="QWidget" name="displayTab">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -219,24 +290,75 @@
<string>Display</string>
</attribute>
<layout class="QVBoxLayout" name="displayVerticalLayout">
<item>
<widget class="QCheckBox" name="legend">
<property name="text">
<string>Legend</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="grid">
<property name="text">
<string>Grid</string>
</property>
<property name="checked">
<bool>true</bool>
<item alignment="Qt::AlignTop">
<widget class="QWidget" name="widget_2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QCheckBox" name="legend">
<property name="text">
<string>Legend</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="grid">
<property name="text">
<string>Grid</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_5">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Keep</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="keepAll">
<property name="text">
<string>Keep All</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="keep">
<property name="maximum">
<number>2147483647</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>

View File

@ -3,7 +3,7 @@
int main(int argc, char** argv) {
QApplication app(argc, argv);
Graph::GUI::MainWindow::getInstance()->show();
Graph::GUI::MainWindow::getInstance(argc, argv)->show();
int result = app.exec();
Graph::GUI::MainWindow::close();
return result;

View File

@ -6,7 +6,7 @@
#include <string>
namespace Graph {
Plugin::Plugin(const std::string& path, update_callback_t callback) : path(path) {
Plugin::Plugin(const std::string& path, update_callback_t callback, int argc, char** argv) : path(path) {
handle = dlopen(path.c_str(), RTLD_LAZY);
if (!handle)
throw std::runtime_error(dlerror());
@ -16,6 +16,8 @@ Plugin::Plugin(const std::string& path, update_callback_t callback) : path(path)
throw std::runtime_error(dlerror());
plugin->update_callback = callback;
plugin->argc = argc;
plugin->argv = argv;
}
void Plugin::run() {
@ -24,7 +26,7 @@ void Plugin::run() {
Plugin::~Plugin() {
if (plugin->destroy_method)
plugin->destroy_method();
emit exited(plugin->destroy_method());
if (handle)
dlclose((void*)handle);

16
test/test.py Executable file
View File

@ -0,0 +1,16 @@
#!/bin/env python3
import requests
from math import sin
from time import sleep
def main():
x = 0.0
while True:
requests.get(f"http://localhost:8080/add?chart=0&series=R1&x={x}&y={sin(x)}")
x += 0.01
sleep(0.01)
if __name__ == "__main__":
main()