refactor, new features
This commit is contained in:
parent
376d0a17e8
commit
14e22cc636
@ -1,5 +1,5 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(nvme-monitor)
|
project(temp-monitor)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
||||||
@ -44,19 +44,19 @@ set(HEADERS
|
|||||||
include/config_manager.h
|
include/config_manager.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(nvme-monitor ${SOURCES} ${HEADERS})
|
add_executable(temp-monitor ${SOURCES} ${HEADERS})
|
||||||
|
|
||||||
target_link_libraries(nvme-monitor
|
target_link_libraries(temp-monitor
|
||||||
${GTK_LIBRARIES}
|
${GTK_LIBRARIES}
|
||||||
${CAIRO_LIBRARIES}
|
${CAIRO_LIBRARIES}
|
||||||
m
|
m
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_options(nvme-monitor PRIVATE ${GTK_CFLAGS_OTHER})
|
target_compile_options(temp-monitor PRIVATE ${GTK_CFLAGS_OTHER})
|
||||||
target_compile_options(nvme-monitor PRIVATE ${CAIRO_CFLAGS_OTHER})
|
target_compile_options(temp-monitor PRIVATE ${CAIRO_CFLAGS_OTHER})
|
||||||
|
|
||||||
# Install executable
|
# Install executable
|
||||||
install(TARGETS nvme-monitor
|
install(TARGETS temp-monitor
|
||||||
DESTINATION bin)
|
DESTINATION bin)
|
||||||
|
|
||||||
# Install desktop file for system integration
|
# Install desktop file for system integration
|
||||||
|
|||||||
2
build.sh
2
build.sh
@ -8,4 +8,4 @@ cd build
|
|||||||
cmake ..
|
cmake ..
|
||||||
make
|
make
|
||||||
|
|
||||||
echo "Build complete! Run with: ./nvme-monitor"
|
echo "Build complete! Run with: ./temp-monitor"
|
||||||
|
|||||||
@ -18,16 +18,20 @@ public:
|
|||||||
int getWindowWidth() const { return windowWidth; }
|
int getWindowWidth() const { return windowWidth; }
|
||||||
int getWindowHeight() const { return windowHeight; }
|
int getWindowHeight() const { return windowHeight; }
|
||||||
int getPollingTime() const { return pollingTime; }
|
int getPollingTime() const { return pollingTime; }
|
||||||
|
int getHistoryLength() const { return historyLength; }
|
||||||
|
|
||||||
void setWindowWidth(int w) { windowWidth = w; }
|
void setWindowWidth(int w) { windowWidth = w; }
|
||||||
void setWindowHeight(int h) { windowHeight = h; }
|
void setWindowHeight(int h) { windowHeight = h; }
|
||||||
void setPollingTime(int t) { pollingTime = t; }
|
void setPollingTime(int t) { pollingTime = t; }
|
||||||
|
void setHistoryLength(int m) { historyLength = m; }
|
||||||
|
|
||||||
std::map<std::string, std::string> getSensorColors() const { return sensorColors; }
|
std::map<std::string, std::string> getSensorColors() const { return sensorColors; }
|
||||||
std::map<std::string, std::string> getSensorNames() const { return sensorNames; }
|
std::map<std::string, std::string> getSensorNames() const { return sensorNames; }
|
||||||
|
std::map<std::string, bool> getSensorEnabled() const { return sensorEnabled; }
|
||||||
|
|
||||||
void setSensorColor(const std::string &id, const std::string &color) { sensorColors[id] = color; }
|
void setSensorColor(const std::string &id, const std::string &color) { sensorColors[id] = color; }
|
||||||
void setSensorName(const std::string &id, const std::string &name) { sensorNames[id] = name; }
|
void setSensorName(const std::string &id, const std::string &name) { sensorNames[id] = name; }
|
||||||
|
void setSensorEnabled(const std::string &id, bool enabled) { sensorEnabled[id] = enabled; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string getConfigFilePath() const;
|
std::string getConfigFilePath() const;
|
||||||
@ -36,9 +40,11 @@ private:
|
|||||||
int windowWidth;
|
int windowWidth;
|
||||||
int windowHeight;
|
int windowHeight;
|
||||||
int pollingTime;
|
int pollingTime;
|
||||||
|
int historyLength;
|
||||||
|
|
||||||
std::map<std::string, std::string> sensorColors;
|
std::map<std::string, std::string> sensorColors;
|
||||||
std::map<std::string, std::string> sensorNames;
|
std::map<std::string, std::string> sensorNames;
|
||||||
|
std::map<std::string, bool> sensorEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONFIG_MANAGER_H
|
#endif // CONFIG_MANAGER_H
|
||||||
|
|||||||
@ -24,14 +24,17 @@ private:
|
|||||||
static gboolean onDeleteWindow(GtkWidget *widget, gpointer userData);
|
static gboolean onDeleteWindow(GtkWidget *widget, gpointer userData);
|
||||||
static gboolean onUpdateTimer(gpointer userData);
|
static gboolean onUpdateTimer(gpointer userData);
|
||||||
static void onRefreshRateChanged(GtkSpinButton *spinButton, gpointer userData);
|
static void onRefreshRateChanged(GtkSpinButton *spinButton, gpointer userData);
|
||||||
|
static void onHistoryLengthChanged(GtkSpinButton *spinButton, gpointer userData);
|
||||||
static void onClearButtonClicked(GtkButton *button, gpointer userData);
|
static void onClearButtonClicked(GtkButton *button, gpointer userData);
|
||||||
static void onQuitButtonClicked(GtkButton *button, gpointer userData);
|
static void onQuitButtonClicked(GtkButton *button, gpointer userData);
|
||||||
static void onColorSet(GObject *object, GParamSpec *pspec, gpointer userData);
|
static void onColorSet(GObject *object, GParamSpec *pspec, gpointer userData);
|
||||||
static void onNameChanged(GtkEditable *editable, gpointer userData);
|
static void onNameChanged(GtkEditable *editable, gpointer userData);
|
||||||
|
static void onVisibilityToggled(GtkCheckButton *checkButton, gpointer userData);
|
||||||
|
|
||||||
GtkWidget *window;
|
GtkWidget *window;
|
||||||
GtkWidget *statusLabel;
|
GtkWidget *statusLabel;
|
||||||
GtkSpinButton *refreshRateSpinBox;
|
GtkSpinButton *refreshRateSpinBox;
|
||||||
|
GtkSpinButton *historyLengthSpinBox;
|
||||||
std::unique_ptr<TemperatureChart> chart;
|
std::unique_ptr<TemperatureChart> chart;
|
||||||
std::unique_ptr<TempMonitor> monitor;
|
std::unique_ptr<TempMonitor> monitor;
|
||||||
std::unique_ptr<ConfigManager> config;
|
std::unique_ptr<ConfigManager> config;
|
||||||
|
|||||||
@ -18,6 +18,7 @@ struct SeriesData {
|
|||||||
GdkRGBA color;
|
GdkRGBA color;
|
||||||
std::string id; // Unique internal ID (device + sensor)
|
std::string id; // Unique internal ID (device + sensor)
|
||||||
std::string name; // User-friendly display name
|
std::string name; // User-friendly display name
|
||||||
|
bool visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TemperatureChart {
|
class TemperatureChart {
|
||||||
@ -40,6 +41,12 @@ public:
|
|||||||
std::string getSeriesName(const std::string &seriesId) const;
|
std::string getSeriesName(const std::string &seriesId) const;
|
||||||
void setSeriesName(const std::string &seriesId, const std::string &name);
|
void setSeriesName(const std::string &seriesId, const std::string &name);
|
||||||
|
|
||||||
|
// Visibility management
|
||||||
|
bool isSeriesVisible(const std::string &seriesId) const;
|
||||||
|
void setSeriesVisible(const std::string &seriesId, bool visible);
|
||||||
|
|
||||||
|
void setHistoryLength(int minutes);
|
||||||
|
|
||||||
void drawChart(GtkDrawingArea *area, cairo_t *cr, int width, int height);
|
void drawChart(GtkDrawingArea *area, cairo_t *cr, int width, int height);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -75,7 +82,7 @@ private:
|
|||||||
// Cairo drawing state
|
// Cairo drawing state
|
||||||
double minTemp, maxTemp;
|
double minTemp, maxTemp;
|
||||||
int64_t minTime, maxTime;
|
int64_t minTime, maxTime;
|
||||||
static constexpr int MAX_TIME_MS = 600000; // 10 minutes in milliseconds
|
int historyLengthMinutes;
|
||||||
static constexpr double MIN_TEMP = 10.0;
|
static constexpr double MIN_TEMP = 10.0;
|
||||||
static constexpr double MAX_TEMP = 110.0;
|
static constexpr double MAX_TEMP = 110.0;
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
ConfigManager::ConfigManager()
|
ConfigManager::ConfigManager()
|
||||||
: windowWidth(1200), windowHeight(700), pollingTime(1)
|
: windowWidth(1200), windowHeight(700), pollingTime(1), historyLength(10)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ std::string ConfigManager::getConfigFilePath() const
|
|||||||
|
|
||||||
if (len == -1) {
|
if (len == -1) {
|
||||||
// Fallback to current directory
|
// Fallback to current directory
|
||||||
cachedConfigPath = "./nvme-monitor.conf";
|
cachedConfigPath = "./temp-monitor.conf";
|
||||||
return cachedConfigPath;
|
return cachedConfigPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,11 +34,11 @@ std::string ConfigManager::getConfigFilePath() const
|
|||||||
size_t lastSlash = fullPath.find_last_of('/');
|
size_t lastSlash = fullPath.find_last_of('/');
|
||||||
if (lastSlash != std::string::npos) {
|
if (lastSlash != std::string::npos) {
|
||||||
std::string exeDir = fullPath.substr(0, lastSlash);
|
std::string exeDir = fullPath.substr(0, lastSlash);
|
||||||
cachedConfigPath = exeDir + "/nvme-monitor.conf";
|
cachedConfigPath = exeDir + "/temp-monitor.conf";
|
||||||
return cachedConfigPath;
|
return cachedConfigPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedConfigPath = "./nvme-monitor.conf";
|
cachedConfigPath = "./temp-monitor.conf";
|
||||||
return cachedConfigPath;
|
return cachedConfigPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,10 +75,14 @@ void ConfigManager::load()
|
|||||||
windowHeight = std::stoi(value);
|
windowHeight = std::stoi(value);
|
||||||
} else if (key == "polling_time") {
|
} else if (key == "polling_time") {
|
||||||
pollingTime = std::stoi(value);
|
pollingTime = std::stoi(value);
|
||||||
|
} else if (key == "history_length") {
|
||||||
|
historyLength = std::stoi(value);
|
||||||
} else if (key.find("color_") == 0) {
|
} else if (key.find("color_") == 0) {
|
||||||
sensorColors[key.substr(6)] = value;
|
sensorColors[key.substr(6)] = value;
|
||||||
} else if (key.find("name_") == 0) {
|
} else if (key.find("name_") == 0) {
|
||||||
sensorNames[key.substr(5)] = value;
|
sensorNames[key.substr(5)] = value;
|
||||||
|
} else if (key.find("enabled_") == 0) {
|
||||||
|
sensorEnabled[key.substr(8)] = (value == "true" || value == "1");
|
||||||
}
|
}
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
std::cerr << "Config error: invalid value for '" << key << "': " << value << " (" << e.what() << ")" << std::endl;
|
std::cerr << "Config error: invalid value for '" << key << "': " << value << " (" << e.what() << ")" << std::endl;
|
||||||
@ -104,6 +108,7 @@ void ConfigManager::save()
|
|||||||
file << "window_width = " << windowWidth << "\n";
|
file << "window_width = " << windowWidth << "\n";
|
||||||
file << "window_height = " << windowHeight << "\n";
|
file << "window_height = " << windowHeight << "\n";
|
||||||
file << "polling_time = " << pollingTime << "\n";
|
file << "polling_time = " << pollingTime << "\n";
|
||||||
|
file << "history_length = " << historyLength << "\n";
|
||||||
|
|
||||||
for (auto const& [id, color] : sensorColors) {
|
for (auto const& [id, color] : sensorColors) {
|
||||||
file << "color_" << id << " = " << color << "\n";
|
file << "color_" << id << " = " << color << "\n";
|
||||||
@ -113,5 +118,9 @@ void ConfigManager::save()
|
|||||||
file << "name_" << id << " = " << name << "\n";
|
file << "name_" << id << " = " << name << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto const& [id, enabled] : sensorEnabled) {
|
||||||
|
file << "enabled_" << id << " = " << (enabled ? "true" : "false") << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,8 @@
|
|||||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||||
|
|
||||||
MainWindow::MainWindow()
|
MainWindow::MainWindow()
|
||||||
: window(nullptr), statusLabel(nullptr), refreshRateSpinBox(nullptr),
|
: window(nullptr), statusLabel(nullptr),
|
||||||
|
refreshRateSpinBox(nullptr), historyLengthSpinBox(nullptr),
|
||||||
timerID(0), refreshRateSec(3)
|
timerID(0), refreshRateSec(3)
|
||||||
{
|
{
|
||||||
monitor = std::make_unique<TempMonitor>();
|
monitor = std::make_unique<TempMonitor>();
|
||||||
@ -19,6 +20,12 @@ MainWindow::MainWindow()
|
|||||||
|
|
||||||
setupUI();
|
setupUI();
|
||||||
|
|
||||||
|
int historyLen = config->getHistoryLength();
|
||||||
|
chart->setHistoryLength(historyLen);
|
||||||
|
if (historyLengthSpinBox) {
|
||||||
|
gtk_spin_button_set_value(historyLengthSpinBox, historyLen);
|
||||||
|
}
|
||||||
|
|
||||||
// Set window size from config
|
// Set window size from config
|
||||||
gtk_window_set_default_size(GTK_WINDOW(window), config->getWindowWidth(), config->getWindowHeight());
|
gtk_window_set_default_size(GTK_WINDOW(window), config->getWindowWidth(), config->getWindowHeight());
|
||||||
|
|
||||||
@ -50,7 +57,7 @@ void MainWindow::setupUI()
|
|||||||
g_object_unref(provider);
|
g_object_unref(provider);
|
||||||
|
|
||||||
window = gtk_window_new();
|
window = gtk_window_new();
|
||||||
gtk_window_set_title(GTK_WINDOW(window), "NVMe Temperature Monitor");
|
gtk_window_set_title(GTK_WINDOW(window), "Temperature Monitor");
|
||||||
|
|
||||||
// Set window icon - in GTK4 we load pixbuf and use it for the window display
|
// Set window icon - in GTK4 we load pixbuf and use it for the window display
|
||||||
GError *error = nullptr;
|
GError *error = nullptr;
|
||||||
@ -92,6 +99,17 @@ void MainWindow::setupUI()
|
|||||||
g_signal_connect(refreshRateSpinBox, "value-changed", G_CALLBACK(onRefreshRateChanged), this);
|
g_signal_connect(refreshRateSpinBox, "value-changed", G_CALLBACK(onRefreshRateChanged), this);
|
||||||
gtk_box_append(GTK_BOX(controlBox), GTK_WIDGET(refreshRateSpinBox));
|
gtk_box_append(GTK_BOX(controlBox), GTK_WIDGET(refreshRateSpinBox));
|
||||||
|
|
||||||
|
// History length label
|
||||||
|
GtkWidget *historyLabel = gtk_label_new("History (min):");
|
||||||
|
gtk_box_append(GTK_BOX(controlBox), historyLabel);
|
||||||
|
|
||||||
|
// History length spinner (5-60 min, step 5)
|
||||||
|
GtkAdjustment *histAdjustment = gtk_adjustment_new(10, 5, 60, 5, 10, 0);
|
||||||
|
historyLengthSpinBox = GTK_SPIN_BUTTON(gtk_spin_button_new(histAdjustment, 5, 0));
|
||||||
|
gtk_widget_set_size_request(GTK_WIDGET(historyLengthSpinBox), 80, -1);
|
||||||
|
g_signal_connect(historyLengthSpinBox, "value-changed", G_CALLBACK(onHistoryLengthChanged), this);
|
||||||
|
gtk_box_append(GTK_BOX(controlBox), GTK_WIDGET(historyLengthSpinBox));
|
||||||
|
|
||||||
// Clear button
|
// Clear button
|
||||||
GtkWidget *clearButton = gtk_button_new_with_label("Clear Data");
|
GtkWidget *clearButton = gtk_button_new_with_label("Clear Data");
|
||||||
g_signal_connect(clearButton, "clicked", G_CALLBACK(onClearButtonClicked), this);
|
g_signal_connect(clearButton, "clicked", G_CALLBACK(onClearButtonClicked), this);
|
||||||
@ -161,6 +179,14 @@ void MainWindow::onRefreshRateChanged(GtkSpinButton *spinButton, gpointer userDa
|
|||||||
self->timerID = g_timeout_add(self->refreshRateSec * 1000, onUpdateTimer, self);
|
self->timerID = g_timeout_add(self->refreshRateSec * 1000, onUpdateTimer, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::onHistoryLengthChanged(GtkSpinButton *spinButton, gpointer userData)
|
||||||
|
{
|
||||||
|
MainWindow *self = static_cast<MainWindow*>(userData);
|
||||||
|
int minutes = gtk_spin_button_get_value_as_int(spinButton);
|
||||||
|
self->chart->setHistoryLength(minutes);
|
||||||
|
self->config->setHistoryLength(minutes);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::onClearButtonClicked(GtkButton *button, gpointer userData)
|
void MainWindow::onClearButtonClicked(GtkButton *button, gpointer userData)
|
||||||
{
|
{
|
||||||
MainWindow *self = static_cast<MainWindow*>(userData);
|
MainWindow *self = static_cast<MainWindow*>(userData);
|
||||||
@ -273,15 +299,30 @@ void MainWindow::updateLegend()
|
|||||||
auto seriesColors = chart->getSeriesColors();
|
auto seriesColors = chart->getSeriesColors();
|
||||||
|
|
||||||
// Add legend items
|
// Add legend items
|
||||||
|
auto sensorEnabledMap = config->getSensorEnabled();
|
||||||
|
|
||||||
for (const auto &pair : seriesColors) {
|
for (const auto &pair : seriesColors) {
|
||||||
const std::string &seriesId = pair.first;
|
const std::string &seriesId = pair.first;
|
||||||
const GdkRGBA &color = pair.second;
|
const GdkRGBA &color = pair.second;
|
||||||
std::string displayName = chart->getSeriesName(seriesId);
|
std::string displayName = chart->getSeriesName(seriesId);
|
||||||
|
|
||||||
|
bool isVisible = true;
|
||||||
|
if (sensorEnabledMap.find(seriesId) != sensorEnabledMap.end()) {
|
||||||
|
isVisible = sensorEnabledMap[seriesId];
|
||||||
|
}
|
||||||
|
chart->setSeriesVisible(seriesId, isVisible);
|
||||||
|
|
||||||
// Create container for legend item
|
// Create container for legend item
|
||||||
GtkWidget *itemBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
|
GtkWidget *itemBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
|
||||||
gtk_widget_add_css_class(itemBox, "legend-item");
|
gtk_widget_add_css_class(itemBox, "legend-item");
|
||||||
|
|
||||||
|
// Checkbox for visibility
|
||||||
|
GtkWidget *checkButton = gtk_check_button_new();
|
||||||
|
gtk_check_button_set_active(GTK_CHECK_BUTTON(checkButton), isVisible);
|
||||||
|
g_object_set_data_full(G_OBJECT(checkButton), "series-name", g_strdup(seriesId.c_str()), g_free);
|
||||||
|
g_signal_connect(checkButton, "toggled", G_CALLBACK(onVisibilityToggled), this);
|
||||||
|
gtk_box_append(GTK_BOX(itemBox), checkButton);
|
||||||
|
|
||||||
// Create color dialog and button
|
// Create color dialog and button
|
||||||
GtkColorDialog *dialog = gtk_color_dialog_new();
|
GtkColorDialog *dialog = gtk_color_dialog_new();
|
||||||
GtkWidget *colorButton = gtk_color_dialog_button_new(dialog);
|
GtkWidget *colorButton = gtk_color_dialog_button_new(dialog);
|
||||||
@ -354,3 +395,16 @@ void MainWindow::onNameChanged(GtkEditable *editable, gpointer userData)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::onVisibilityToggled(GtkCheckButton *checkButton, gpointer userData)
|
||||||
|
{
|
||||||
|
MainWindow *self = static_cast<MainWindow*>(userData);
|
||||||
|
const char *seriesId = static_cast<const char*>(g_object_get_data(G_OBJECT(checkButton), "series-name"));
|
||||||
|
|
||||||
|
if (seriesId) {
|
||||||
|
bool visible = gtk_check_button_get_active(checkButton);
|
||||||
|
self->chart->setSeriesVisible(seriesId, visible);
|
||||||
|
self->config->setSensorEnabled(seriesId, visible);
|
||||||
|
self->config->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ TemperatureChart::TemperatureChart()
|
|||||||
: drawingArea(nullptr), tooltipWindow(nullptr), tooltipLabel(nullptr),
|
: drawingArea(nullptr), tooltipWindow(nullptr), tooltipLabel(nullptr),
|
||||||
maxDataPoints(600), tickHandler(0),
|
maxDataPoints(600), tickHandler(0),
|
||||||
minTemp(MIN_TEMP), maxTemp(MAX_TEMP), minTime(0), maxTime(0),
|
minTemp(MIN_TEMP), maxTemp(MAX_TEMP), minTime(0), maxTime(0),
|
||||||
lastMouseX(-1), lastMouseY(-1)
|
lastMouseX(-1), lastMouseY(-1), historyLengthMinutes(10)
|
||||||
{
|
{
|
||||||
drawingArea = gtk_drawing_area_new();
|
drawingArea = gtk_drawing_area_new();
|
||||||
gtk_widget_set_size_request(drawingArea, 800, 400);
|
gtk_widget_set_size_request(drawingArea, 800, 400);
|
||||||
@ -200,8 +200,8 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std::
|
|||||||
|
|
||||||
seriesMap[seriesKey].points.push_back(point);
|
seriesMap[seriesKey].points.push_back(point);
|
||||||
|
|
||||||
// Keep only last 10 minutes of data
|
// Keep only historyLengthMinutes of data
|
||||||
int64_t cutoffTime = timestamp - MAX_TIME_MS;
|
int64_t cutoffTime = timestamp - (static_cast<int64_t>(historyLengthMinutes) * 60 * 1000);
|
||||||
for (auto &entry : seriesMap) {
|
for (auto &entry : seriesMap) {
|
||||||
auto &points = entry.second.points;
|
auto &points = entry.second.points;
|
||||||
auto it = points.begin();
|
auto it = points.begin();
|
||||||
@ -216,6 +216,7 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std::
|
|||||||
bool hasData = false;
|
bool hasData = false;
|
||||||
|
|
||||||
for (const auto &entry : seriesMap) {
|
for (const auto &entry : seriesMap) {
|
||||||
|
if (!entry.second.visible) continue;
|
||||||
for (const auto &p : entry.second.points) {
|
for (const auto &p : entry.second.points) {
|
||||||
if (p.temperature < currentMin) currentMin = p.temperature;
|
if (p.temperature < currentMin) currentMin = p.temperature;
|
||||||
if (p.temperature > currentMax) currentMax = p.temperature;
|
if (p.temperature > currentMax) currentMax = p.temperature;
|
||||||
@ -240,9 +241,9 @@ bool TemperatureChart::addTemperatureData(const std::string &device, const std::
|
|||||||
maxTemp = MAX_TEMP;
|
maxTemp = MAX_TEMP;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update time range - keep 10 minute window
|
// Update time range - keep dynamic window
|
||||||
maxTime = timestamp;
|
maxTime = timestamp;
|
||||||
minTime = timestamp - MAX_TIME_MS;
|
minTime = timestamp - (static_cast<int64_t>(historyLengthMinutes) * 60 * 1000);
|
||||||
|
|
||||||
// Trigger redraw
|
// Trigger redraw
|
||||||
gtk_widget_queue_draw(drawingArea);
|
gtk_widget_queue_draw(drawingArea);
|
||||||
@ -283,6 +284,67 @@ void TemperatureChart::setSeriesColor(const std::string &seriesName, const GdkRG
|
|||||||
gtk_widget_queue_draw(drawingArea);
|
gtk_widget_queue_draw(drawingArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TemperatureChart::setHistoryLength(int minutes)
|
||||||
|
{
|
||||||
|
historyLengthMinutes = minutes;
|
||||||
|
|
||||||
|
// Immediately recalculate ranges based on new history length
|
||||||
|
if (!seriesMap.empty()) {
|
||||||
|
int64_t latestTimestamp = 0;
|
||||||
|
for (const auto &entry : seriesMap) {
|
||||||
|
if (!entry.second.points.empty()) {
|
||||||
|
latestTimestamp = std::max(latestTimestamp, entry.second.points.back().timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestTimestamp > 0) {
|
||||||
|
int64_t cutoffTime = latestTimestamp - (static_cast<int64_t>(historyLengthMinutes) * 60 * 1000);
|
||||||
|
|
||||||
|
double currentMin = 1000.0;
|
||||||
|
double currentMax = -1000.0;
|
||||||
|
bool hasData = false;
|
||||||
|
|
||||||
|
for (auto &entry : seriesMap) {
|
||||||
|
auto &points = entry.second.points;
|
||||||
|
|
||||||
|
if (!entry.second.visible) continue;
|
||||||
|
|
||||||
|
// Prune points that are now outside the new history window
|
||||||
|
auto it = points.begin();
|
||||||
|
while (it != points.end() && it->timestamp < cutoffTime) {
|
||||||
|
it = points.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &p : points) {
|
||||||
|
if (p.temperature < currentMin) currentMin = p.temperature;
|
||||||
|
if (p.temperature > currentMax) currentMax = p.temperature;
|
||||||
|
hasData = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasData) {
|
||||||
|
minTemp = std::floor(currentMin / 10.0) * 10.0;
|
||||||
|
maxTemp = std::ceil(currentMax / 10.0) * 10.0;
|
||||||
|
|
||||||
|
// Ensure at least 20 degrees range
|
||||||
|
if (maxTemp - minTemp < 20.0) {
|
||||||
|
maxTemp = minTemp + 20.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minTemp > 30.0) minTemp = 30.0;
|
||||||
|
} else {
|
||||||
|
minTemp = MIN_TEMP;
|
||||||
|
maxTemp = MAX_TEMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxTime = latestTimestamp;
|
||||||
|
minTime = cutoffTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_queue_draw(drawingArea);
|
||||||
|
}
|
||||||
|
|
||||||
std::string TemperatureChart::getSeriesName(const std::string &seriesId) const
|
std::string TemperatureChart::getSeriesName(const std::string &seriesId) const
|
||||||
{
|
{
|
||||||
auto it = seriesMap.find(seriesId);
|
auto it = seriesMap.find(seriesId);
|
||||||
@ -292,6 +354,45 @@ std::string TemperatureChart::getSeriesName(const std::string &seriesId) const
|
|||||||
return seriesId;
|
return seriesId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TemperatureChart::isSeriesVisible(const std::string &seriesId) const
|
||||||
|
{
|
||||||
|
auto it = seriesMap.find(seriesId);
|
||||||
|
if (it != seriesMap.end()) {
|
||||||
|
return it->second.visible;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TemperatureChart::setSeriesVisible(const std::string &seriesId, bool visible)
|
||||||
|
{
|
||||||
|
if (seriesMap.find(seriesId) != seriesMap.end()) {
|
||||||
|
seriesMap[seriesId].visible = visible;
|
||||||
|
|
||||||
|
// Recalculate temperature range
|
||||||
|
double currentMin = 1000.0;
|
||||||
|
double currentMax = -1000.0;
|
||||||
|
bool hasVisibleData = false;
|
||||||
|
|
||||||
|
for (const auto &entry : seriesMap) {
|
||||||
|
if (!entry.second.visible) continue;
|
||||||
|
for (const auto &p : entry.second.points) {
|
||||||
|
if (p.temperature < currentMin) currentMin = p.temperature;
|
||||||
|
if (p.temperature > currentMax) currentMax = p.temperature;
|
||||||
|
hasVisibleData = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasVisibleData) {
|
||||||
|
minTemp = std::floor(currentMin / 10.0) * 10.0;
|
||||||
|
maxTemp = std::ceil(currentMax / 10.0) * 10.0;
|
||||||
|
if (maxTemp - minTemp < 20.0) maxTemp = minTemp + 20.0;
|
||||||
|
if (minTemp > 30.0) minTemp = 30.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_queue_draw(drawingArea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TemperatureChart::setSeriesName(const std::string &seriesId, const std::string &name)
|
void TemperatureChart::setSeriesName(const std::string &seriesId, const std::string &name)
|
||||||
{
|
{
|
||||||
if (seriesMap.find(seriesId) != seriesMap.end()) {
|
if (seriesMap.find(seriesId) != seriesMap.end()) {
|
||||||
@ -371,13 +472,12 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
|||||||
cairo_show_text(cr, tempStr);
|
cairo_show_text(cr, tempStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time labels on X axis (5 points for 10-minute window)
|
// Time labels on X axis (5 points for history window)
|
||||||
for (int i = 0; i <= 5; i++) {
|
for (int i = 0; i <= 5; i++) {
|
||||||
double x = marginLeft + (plotWidth * i / 5.0);
|
double x = marginLeft + (plotWidth * i / 5.0);
|
||||||
|
|
||||||
// Calculate time offset in seconds
|
// Calculate time offset in minutes
|
||||||
int secondsOffset = (5 - i) * 120; // 10 minutes / 5 = 2 minutes between points
|
int minutes = (5 - i) * historyLengthMinutes / 5;
|
||||||
int minutes = secondsOffset / 60;
|
|
||||||
|
|
||||||
char timeStr[16];
|
char timeStr[16];
|
||||||
snprintf(timeStr, sizeof(timeStr), "-%d m", minutes);
|
snprintf(timeStr, sizeof(timeStr), "-%d m", minutes);
|
||||||
@ -386,11 +486,11 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
|||||||
cairo_show_text(cr, timeStr);
|
cairo_show_text(cr, timeStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw data series (right to left, with 10-minute window)
|
// Draw data series (right to left, with history window)
|
||||||
for (auto &seriesEntry : seriesMap) {
|
for (auto &seriesEntry : seriesMap) {
|
||||||
SeriesData &series = seriesEntry.second;
|
SeriesData &series = seriesEntry.second;
|
||||||
|
|
||||||
if (series.points.empty()) continue;
|
if (!series.visible || series.points.empty()) continue;
|
||||||
|
|
||||||
// Set series color
|
// Set series color
|
||||||
cairo_set_source_rgba(cr, series.color.red, series.color.green,
|
cairo_set_source_rgba(cr, series.color.red, series.color.green,
|
||||||
@ -400,7 +500,8 @@ void TemperatureChart::drawChart(GtkDrawingArea *area, cairo_t *cr, int width, i
|
|||||||
bool firstPoint = true;
|
bool firstPoint = true;
|
||||||
for (const DataPoint &point : series.points) {
|
for (const DataPoint &point : series.points) {
|
||||||
// Reverse X axis - newer data on the right, older on the left
|
// Reverse X axis - newer data on the right, older on the left
|
||||||
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / MAX_TIME_MS);
|
int64_t historyMs = static_cast<int64_t>(historyLengthMinutes) * 60 * 1000;
|
||||||
|
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / historyMs);
|
||||||
double y = marginTop + plotHeight * (1.0 - (point.temperature - minTemp) / (maxTemp - minTemp));
|
double y = marginTop + plotHeight * (1.0 - (point.temperature - minTemp) / (maxTemp - minTemp));
|
||||||
|
|
||||||
if (firstPoint) {
|
if (firstPoint) {
|
||||||
@ -444,8 +545,11 @@ TemperatureChart::NearestPoint TemperatureChart::findNearestDataPoint(double mou
|
|||||||
for (auto &seriesEntry : seriesMap) {
|
for (auto &seriesEntry : seriesMap) {
|
||||||
SeriesData &series = seriesEntry.second;
|
SeriesData &series = seriesEntry.second;
|
||||||
|
|
||||||
|
if (!series.visible) continue;
|
||||||
|
|
||||||
for (const DataPoint &point : series.points) {
|
for (const DataPoint &point : series.points) {
|
||||||
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / MAX_TIME_MS);
|
int64_t historyMs = static_cast<int64_t>(historyLengthMinutes) * 60 * 1000;
|
||||||
|
double x = marginLeft + plotWidth * (1.0 - (double)(maxTime - point.timestamp) / historyMs);
|
||||||
double y = marginTop + plotHeight * (1.0 - (point.temperature - minTemp) / (maxTemp - minTemp));
|
double y = marginTop + plotHeight * (1.0 - (point.temperature - minTemp) / (maxTemp - minTemp));
|
||||||
|
|
||||||
double distance = std::hypot(mouseX - x, mouseY - y);
|
double distance = std::hypot(mouseX - x, mouseY - y);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user