This commit is contained in:
Nadal Gonzalo García Zavala 2024-11-13 19:07:44 +02:00 committed by GitHub
commit b9f3c69df6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 43731 additions and 3 deletions

View File

@ -1,5 +1,5 @@
qt5 = import('qt5')
qt5_dep = dependency('qt5', modules: ['DBus', 'Charts', 'Widgets'])
qt5_dep = dependency('qt5', modules: ['DBus', 'Charts', 'Widgets', 'PrintSupport'])
# signals2
boost_dep = dependency('boost')
@ -9,6 +9,8 @@ moc_files = qt5.preprocess(moc_headers: ['MainWindow.hpp',
'data/AssignableItem.hpp',
'data/AssignableProxy.hpp',
'data/DeviceModel.hpp',
'monitor/Monitor.hpp',
'monitor/qcustomplot.h',
'data/DynamicReadableConnection.hpp',
'data/DynamicReadableProxy.hpp',
'widgets/AbstractAssignableEditor.hpp',
@ -28,6 +30,10 @@ sources = ['main.cpp',
'data/DynamicReadableProxy.cpp',
'widgets/DeviceBrowser.cpp',
'widgets/DeviceTreeView.cpp',
'monitor/Monitor.cpp',
'monitor/Metric.cpp',
'monitor/QColorProvider.cpp',
'monitor/qcustomplot.cpp',
'widgets/DragChartView.cpp',
'widgets/EnumEditor.cpp',
'widgets/FlagEditor.cpp',

View File

@ -0,0 +1,92 @@
#include "Metric.hpp"
#include <QtDBus/QtDBus>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusVariant>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusArgument>
#include <QtDBus/QDBusReply>
#include <thread>
using namespace std;
struct BooleanValue
{
bool my_bool;
QDBusVariant val;
};
Q_DECLARE_METATYPE(BooleanValue);
// Marshall the MyStructure data into a D-Bus argument
QDBusArgument &operator<<(QDBusArgument &argument, const BooleanValue &mystruct)
{
argument.beginStructure();
argument << mystruct.my_bool << mystruct.val;
argument.endStructure();
return argument;
}
// Retrieve the MyStructure data from the D-Bus argument
const QDBusArgument &operator>>(const QDBusArgument &argument, BooleanValue &mystruct)
{
argument.beginStructure();
argument >> mystruct.my_bool >> mystruct.val;
argument.endStructure();
return argument;
}
Metric::Metric(QString dbus_obj_path) : dbus_obj_path(dbus_obj_path) {
qDBusRegisterMetaType<BooleanValue>();
dynamic_readable_interface = new QDBusInterface(
"org.tuxclocker",
dbus_obj_path,
"org.tuxclocker.DynamicReadable",
QDBusConnection::systemBus()
);
if(!dynamic_readable_interface->isValid()){
qDebug() << "WARNING: Metric got an invalid dynamic_readable_interface";
qDebug() << " ==> path" << dbus_obj_path;
}
fetchName();
};
void Metric::fetchName(){
QDBusInterface *node_interface = new QDBusInterface(
"org.tuxclocker",
dbus_obj_path,
"org.tuxclocker.Node",
QDBusConnection::systemBus()
);
name = node_interface->property("name").toString();
}
void Metric::fetchValue(){
if(dynamic_readable_interface->isValid()){
QDBusReply<BooleanValue> reply = dynamic_readable_interface->call("value");
if(!reply.isValid()){
qDebug() << "WARNING: fetchValue had an invalid DBus reply";
qDebug() << "error message: " << reply.error().message();
qDebug() << "error name: " << reply.error().name();
}
// TODO: actually request double precision on DBus call method signature
actualValue = reply.value().val.variant().toDouble();
}
}
void Metric::asyncFetchValue(){
// TODO: Allow cancellation. There are cases where we may be innecessary blocking
// if we delay more time that the next time we are called.
futureValue = async(launch::async, &Metric::fetchValue, this);
}
double Metric::value(){
return actualValue;
}

View File

@ -0,0 +1,24 @@
#include <QtDBus/QDBusInterface>
#include <QString>
#include <future>
#ifndef MetricHPP
#define MetricHPP
class Metric {
public:
QDBusInterface *dynamic_readable_interface;
QString dbus_obj_path;
QString name;
float actualValue = 0;
std::future<void> futureValue;
Metric(QString dbus_readable);
void fetchName();
void fetchValue();
void asyncFetchValue();
double value();
};
#endif

View File

@ -0,0 +1,130 @@
#include "Monitor.hpp"
using namespace std;
Monitor::Monitor(QWidget *parent) : QCustomPlot(parent), color_provider() {
setMinimumWidth(1700);
setMinimumHeight(400);
setLocale(QLocale(QLocale::English, QLocale::UnitedKingdom));
setOpenGl(true);
//setAttribute(Qt::WA_OpaquePaintEvent);
setAntialiasedElements(QCP::AntialiasedElement::aeNone);
setBackground(QColor(48, 56, 65));
//setAutoAddMonitortableToLegend(false);
xAxis->setRange(0, 60);
yAxis->setRange(0, 5000);
collect_metrics_timer.callOnTimeout(this, &Monitor::collect_metrics);
collect_metrics_timer.setInterval(100);
// With 100 it's low profile like gnome-system-monitor
// With 20 it looks nice but may use a bit more cpu (40% of an i7-9750h core)
replot_timer.callOnTimeout(this, &Monitor::replot);
replot_timer.setInterval(20);
setupYAxisTick();
setupLessImportant();
QObject::connect(yAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(onRangeChanged(QCPRange)));
};
void Monitor::onRangeChanged(QCPRange range){
setupYAxisTick();
}
void Monitor::setupYAxisTick(){
QSharedPointer<QCPAxisTickerText> textTicker(new QCPAxisTickerText);
yAxis->setTicker(textTicker);
double size = yAxis->range().size();
double lower = yAxis->range().lower;
textTicker->addTick(lower, "0%");
textTicker->addTick(lower + 0.2 * size, "20%");
textTicker->addTick(lower + 0.4 * size, "40%");
textTicker->addTick(lower + 0.6 * size, "60%");
textTicker->addTick(lower + 0.8 * size, "80%");
textTicker->addTick(yAxis->range().upper, "100%");
}
void Monitor::setupLessImportant(){
QSharedPointer<QCPAxisTickerTime> timeTicker(new QCPAxisTickerTime);
timeTicker->setTimeFormat("%m:%s:%z");
timeTicker->setTickCount(8);
xAxis->setTicker(timeTicker);
xAxis->setTickLabelFont(QFont(QFont().family(), 12));
yAxis->setTickLabelFont(QFont(QFont().family(), 12));
xAxis->setTickLabelColor(QColor(200, 200, 200));
yAxis->setTickLabelColor(QColor(200, 200, 200));
xAxis2->setVisible(true);
yAxis2->setVisible(true);
xAxis2->setTicks(false);
yAxis2->setTicks(false);
xAxis2->setTickLabels(false);
yAxis2->setTickLabels(false);
legend->setVisible(true);
legend->setBrush(QColor(255, 255, 255, 150));
}
double Monitor::timenow(){
return ((double) QDateTime::currentDateTime().time().msecsSinceStartOfDay()) / 1000;
}
void Monitor::collect_initial_metrics(){
for(auto metric : metrics){
metric->fetchValue();
}
}
void Monitor::collect_metrics(){
double now = timenow();
for(int m = 0; m < metrics.count(); m++){
graph(m)->data()->add(QCPGraphData (now, metrics[m]->value()));
// TODO: I dont know how to correctly tell Qt5
// to make the dbus call async, for example by using
// callWithCallback()
metrics[m]->asyncFetchValue();
}
graph()->data()->removeBefore(now - 60);
}
void Monitor::replot(){
rescaleAxes();
double now = timenow();
xAxis->setRange(now - 60, now);
QCustomPlot::replot();
}
void Monitor::setupGraphs(){
clearGraphs();
for(auto metric : metrics){
addGraph();
graph()->setName(metric->name);
graph()->setLineStyle(QCPGraph::lsLine);
QPen pen(color_provider.pick());
pen.setWidth(1);
graph()->setPen(pen);
graph()->setAdaptiveSampling(false);
}
}
void Monitor::start(){
collect_initial_metrics();
collect_metrics_timer.start();
replot_timer.start();
}

View File

@ -0,0 +1,36 @@
#include <QTimer>
#include <QVector>
#include <QWidget>
#include <QObject>
#include "qcustomplot.h"
#include "QColorProvider.hpp"
#include "Metric.hpp"
using namespace std;
class Monitor : public QCustomPlot {
Q_OBJECT
public:
QTimer collect_metrics_timer;
QTimer replot_timer;
QColorProvider color_provider;
QVector<shared_ptr<Metric>> metrics;
Monitor(QWidget *parent);
void setupLessImportant();
double timenow();
void collect_initial_metrics();
void collect_metrics();
void replot();
void setupGraphs();
void start();
void setupYAxisTick();
public slots:
void onRangeChanged(QCPRange range);
};

View File

@ -0,0 +1,11 @@
#include "QColorProvider.hpp"
QColorProvider::QColorProvider(){};
QColor QColorProvider::pick(){
if(current >= colors.count()){
current = 0;
}
return colors.at(current++);
}

View File

@ -0,0 +1,35 @@
#include <QColor>
#include <QVector>
class QColorProvider {
public:
int current = 0;
QVector<QColor> colors = {
QColor(230, 25, 75, 170), // Red
QColor(245, 130, 49, 170), // Orange
QColor(255, 255, 25, 170), // Yellow
QColor(191, 239, 69, 170), // Lime
QColor(60, 180, 75, 170), // Green
QColor(66, 212, 244, 170), // Cyan
QColor(67, 99, 216, 170), // Navy
QColor(240, 50, 230, 170), // Dark Green
QColor(145, 30, 180, 170), // Dark Blue
QColor(250, 190, 190, 170), // Peach
QColor(255, 216, 177, 170), // Tan
QColor(255, 250, 200, 170), // Light Blue
QColor(128, 0, 255, 170), // Purple
QColor(255, 128, 128, 170), // Pink
QColor(128, 255, 128, 170), // Light Green
QColor(128, 128, 255, 170), // Sky Blue
QColor(255, 255, 128, 170), // Light Yellow
QColor(255, 128, 255, 170), // Light Pink
QColor(128, 255, 255, 170), // Light Turquoise
QColor(192, 192, 192, 170) // Gray
};
QColorProvider();
QColor pick();
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
#include "DeviceBrowser.hpp"
#include "AssignableItemData.hpp"
#include "./monitor/Monitor.hpp"
#include "./monitor/Metric.hpp"
#include "qnamespace.h"
#include <Globals.hpp>
@ -11,6 +13,8 @@
#include <QVariant>
#include <Settings.hpp>
#include <Utils.hpp>
#include <QMimeData>
#include <QStandardItemModel>
#define _(String) gettext(String)
@ -20,6 +24,48 @@ using namespace TuxClocker::Device;
Q_DECLARE_METATYPE(AssignableItemData)
Q_DECLARE_METATYPE(AssignableProxy *)
class MonitorDropBox : public QLabel{
public:
Monitor monitor;
QWidget widget;
MonitorDropBox(QWidget *parent) : QLabel(parent), monitor(nullptr), widget(nullptr){
setAcceptDrops(true);
setFrameStyle(QFrame::Box);
setLineWidth(1);
setText("Monitor");
setAlignment(Qt::AlignCenter);
}
void setupMonitor(QStringList dbus_paths){
monitor.setParent(&widget);
widget.show();
monitor.metrics.clear();
for(QString dbus_path : dbus_paths){
monitor.metrics.append(std::make_shared<Metric>(dbus_path));
}
monitor.setupGraphs();
monitor.start();
}
void dropEvent(QDropEvent *event) override {
QStringList dbus_paths = event->mimeData()->text().split(",");
if(dbus_paths.count()){
setupMonitor(dbus_paths);
}
}
void dragEnterEvent(QDragEnterEvent *event) override {
event->acceptProposedAction();
}
};
DeviceBrowser::DeviceBrowser(DeviceModel &model, QWidget *parent)
: QWidget(parent), m_deviceModel(model) {
m_layout = new QGridLayout(this);
@ -75,9 +121,12 @@ DeviceBrowser::DeviceBrowser(DeviceModel &model, QWidget *parent)
Globals::g_mainStack->setCurrentWidget(m_settings);
});
m_layout->addWidget(toolButton, 0, 0);
auto monitorDropBox = new MonitorDropBox{this};
m_layout->addWidget(m_flagLabel, 0, 1, 1, 1, Qt::AlignRight);
m_layout->addWidget(toolButton, 0, 0);
m_layout->addWidget(monitorDropBox, 0, 1);
m_layout->addWidget(m_flagLabel, 0, 2, 1, 0, Qt::AlignRight);
m_layout->addWidget(m_flagEditor, 0, 2);
m_layout->addWidget(m_treeView, 1, 0, 1, 3);
m_layout->addWidget(m_apply, 2, 0, 1, 3);

View File

@ -10,6 +10,8 @@
#include <QHeaderView>
#include <QSettings>
#include <Utils.hpp>
#include <QMimeData>
#include <QDrag>
using namespace mpark::patterns;
using namespace TuxClocker::Device;
@ -26,6 +28,7 @@ DeviceTreeView::DeviceTreeView(QWidget *parent) : QTreeView(parent) {
setSortingEnabled(true);
setEditTriggers(SelectedClicked | EditKeyPressed);
setContextMenuPolicy(Qt::DefaultContextMenu);
setDragEnabled(true);
setSelectionMode(QAbstractItemView::ExtendedSelection);
@ -69,6 +72,42 @@ DeviceTreeView::DeviceTreeView(QWidget *parent) : QTreeView(parent) {
setItemDelegate(m_delegate);
}
void DeviceTreeView::mouseMoveEvent(QMouseEvent *event)
{
if (!(event->buttons() & Qt::LeftButton))
return;
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setData("text/plain", selected_dbus_readables.join(",").toUtf8());
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
}
void DeviceTreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected){
selected_dbus_readables.clear();
for(auto selection : selected){
QModelIndexList selectedIndexes = selection.indexes();
auto duplicated = std::unique(selectedIndexes.begin(), selectedIndexes.end(), [](QModelIndex &indexA, QModelIndex &indexB){
return indexA.row() == indexB.row();
});
selectedIndexes.erase(duplicated, selectedIndexes.end());
for(auto model_index : selectedIndexes){
auto dynProxyV = model_index.data(DeviceModel::DynamicReadableProxyRole);
if (dynProxyV.isValid()) {
auto proxy = qvariant_cast<DynamicReadableProxy *>(dynProxyV);
selected_dbus_readables.append(proxy->dbusPath());
}
}
}
}
void DeviceTreeView::suspendChildren(const QModelIndex &index) {
// Somehow can be true when we collapse nodes from cache
if (!Globals::g_deviceModel)

View File

@ -39,6 +39,8 @@ protected:
// TODO: allow to start editing with the keyboard
EditTriggers editTriggers() { return QAbstractItemView::AllEditTriggers; }
void dataChanged(const QModelIndex &, const QModelIndex &, const QVector<int> &) override;
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
void mouseMoveEvent(QMouseEvent *event);
private:
void saveCollapsed(QAbstractItemModel *model);
void restoreCollapsed(QAbstractItemModel *model);
@ -52,4 +54,5 @@ private:
QModelIndexList m_editSelection;
QModelIndex m_editedIndex;
DeviceModelDelegate *m_delegate;
QStringList selected_dbus_readables;
};