Compare commits

...
Author SHA1 Message Date
anthropic-code-agent[bot]andashaduri 893dfc75c8 Improve format detection: check output content for JSON vs text
- When smartctl/output property exists: both JSON and text available
- When no property and output starts with '{': JSON only (loaded JSON virtual drive)
- When no property and output doesn't start with '{': text only (loaded text virtual drive)
- Ensures correct filters shown for each scenario per requirements

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
Agent-Logs-Url: https://github.com/ashaduri/gsmartcontrol/sessions/a0167009-9a8b-4835-9c5d-83a6f7ff903d
2026-03-22 17:32:06 +00:00
anthropic-code-agent[bot]andashaduri 4823af1d23 Fix data format detection logic for JSON vs Text
- Correctly detect when output is JSON (has smartctl/output property) vs text
- When smartctl/output property exists, output is JSON format
- When no property exists, output is text format (loaded virtual drive)
- Ensures filters are shown appropriately for available formats

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
Agent-Logs-Url: https://github.com/ashaduri/gsmartcontrol/sessions/a0167009-9a8b-4835-9c5d-83a6f7ff903d
2026-03-22 17:31:00 +00:00
anthropic-code-agent[bot]andashaduri 81f21c9218 Implement JSON/Text save filters in View Output window
- Add separate JSON and Text file filters to save dialog
- Detect available data formats (JSON/Text) from storage device
- Handle user-selected filter to determine save format
- Extract text output from smartctl/output property when saving as txt
- Add set_storage_device() method to pass device context
- Default to JSON when both formats available (per requirements)

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
Agent-Logs-Url: https://github.com/ashaduri/gsmartcontrol/sessions/a0167009-9a8b-4835-9c5d-83a6f7ff903d
2026-03-22 17:29:25 +00:00
anthropic-code-agent[bot] 4642c9bb13 Initial plan 2026-03-22 17:24:54 +00:00
Alexander Shaduri 57a16f6971 Fix compilation. 2026-03-08 19:08:51 +01:00
Alexander Shaduri 79f1c7d5b0 Merge pull request #106 from ashaduri/claude/fix-red-text-legibility
Red text is barely legible when using a dark theme
2026-03-08 19:05:19 +01:00
Alexander Shaduri a7f0cb78cd Merge remote-tracking branch 'origin/claude/fix-red-text-legibility' into claude/fix-red-text-legibility
# Conflicts:
#	src/applib/warning_colors.h
#	src/gui/gsc_info_window.cpp
2026-03-08 19:04:00 +01:00
Alexander Shaduri 3642afc46f Use readable text highlight colors in dark mode. 2026-03-08 18:52:27 +01:00
Alexander ShaduriandCopilot e4c9afeed0 Update src/applib/warning_colors.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-08 18:52:27 +01:00
Alexander ShaduriandCopilot 06a0a75349 Update src/gui/gsc_info_window.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-08 18:52:27 +01:00
anthropic-code-agent[bot]andashaduri c322e96741 Revert to umbrella gtkmm.h include per maintainer request
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-08 18:52:27 +01:00
anthropic-code-agent[bot]andashaduri a6e2dd0cc6 Add GTK init guard and optimize theme detection
- Guard theme detection with Gtk::Main::level() check to avoid crashes in non-GUI executables
- Use narrow headers (gtkmm/settings.h, gtkmm/main.h) instead of umbrella gtkmm.h
- Return early from app_property_get_label_highlight_color() for WarningLevel::None

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-08 18:52:27 +01:00
anthropic-code-agent[bot]andashaduri 6de9bb897d Implement theme-aware red text colors for dark theme legibility
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-08 18:52:27 +01:00
anthropic-code-agent[bot] ef30450c5f Initial plan 2026-03-08 18:52:27 +01:00
Claude 083936e99b Add Windows dark mode detection and automatic GTK theme switching (#111) 2026-03-08 18:31:58 +01:00
Alexander ShaduriandCopilot 68032c4f3c Update src/applib/warning_colors.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-07 17:39:35 +01:00
Alexander ShaduriandCopilot 7e626460c0 Update src/gui/gsc_info_window.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-07 16:15:24 +01:00
anthropic-code-agent[bot]andashaduri 258d06b755 Revert to umbrella gtkmm.h include per maintainer request
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-06 19:08:06 +00:00
anthropic-code-agent[bot]andashaduri 525fcfe3af Add GTK init guard and optimize theme detection
- Guard theme detection with Gtk::Main::level() check to avoid crashes in non-GUI executables
- Use narrow headers (gtkmm/settings.h, gtkmm/main.h) instead of umbrella gtkmm.h
- Return early from app_property_get_label_highlight_color() for WarningLevel::None

Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-06 14:36:54 +00:00
anthropic-code-agent[bot]andashaduri f692e2879c Implement theme-aware red text colors for dark theme legibility
Co-authored-by: ashaduri <2302268+ashaduri@users.noreply.github.com>
2026-03-04 21:56:40 +00:00
anthropic-code-agent[bot] d110c7abbc Initial plan 2026-03-04 21:50:41 +00:00
11 changed files with 342 additions and 81 deletions
+1
View File
@@ -72,6 +72,7 @@ target_sources(applib PRIVATE
storage_property_repository.cpp
storage_property_repository.h
storage_settings.h
warning_colors.cpp
warning_colors.h
warning_level.h
window_instance_manager.h
+23
View File
@@ -165,6 +165,29 @@ bool gui_show_text_entry_dialog(const std::string& title, const std::string& mes
bool gui_is_dark_theme_active()
{
// Try to get the GTK settings to check for dark theme preference.
// If GTK is not available or not initialized, get_default() will return null.
const Glib::RefPtr<Gtk::Settings> settings = Gtk::Settings::get_default();
if (settings) {
// Check if the application prefers dark theme
if (settings->property_gtk_application_prefer_dark_theme().get_value()) {
return true;
}
// Check theme name for common dark theme identifiers
Glib::ustring theme_name;
settings->get_property("gtk-theme-name", theme_name);
const std::string theme_str = theme_name.lowercase();
if (theme_str.find("dark") != std::string::npos ||
theme_str.find("black") != std::string::npos) {
return true;
}
}
return false;
}
+2 -2
View File
@@ -58,8 +58,8 @@ bool gui_show_text_entry_dialog(const std::string& title, const std::string& mes
std::string& result, const std::string& default_str, Gtk::Window* parent = nullptr, bool sec_msg_markup = false);
/// Check if a dark GTK theme is currently active
bool gui_is_dark_theme_active();
#endif
+88
View File
@@ -0,0 +1,88 @@
/******************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2008 - 2026 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
/// \file
/// \author Alexander Shaduri
/// \ingroup applib
/// \weakgroup applib
/// @{
#include <glibmm.h>
#include "warning_colors.h"
#include "gui_utils.h"
bool app_property_get_row_highlight_colors(bool dark_mode, WarningLevel warning, std::string& fg, std::string& bg)
{
// Note: we're setting both fg and bg, to avoid theme conflicts.
if (warning == WarningLevel::Notice) {
fg = dark_mode ? "#FFFFFF" : "#000000"; // white for dark themes, black for light themes
bg = dark_mode ? "#6B2050" : "#FFD5EE"; // dark pinkish for dark themes, pinkish for light themes
} else if (warning == WarningLevel::Warning) {
fg = dark_mode ? "#FFFFFF" : "#000000"; // white for dark themes, black for light themes
bg = dark_mode ? "#802020" : "#FFA0A0"; // dark red for dark themes, light red for light themes
} else if (warning == WarningLevel::Alert) {
fg = dark_mode ? "#FFFFFF" : "#000000"; // white for dark themes, black for light themes
bg = dark_mode ? "#AA0000" : "#FF0000"; // darker red for dark themes, bright red for light themes
}
return !(fg.empty());
}
bool app_property_get_label_highlight_color(bool dark_mode, WarningLevel warning, std::string& fg)
{
if (warning == WarningLevel::None) {
return false;
}
if (warning == WarningLevel::Notice) {
fg = dark_mode ? "#FF9999" : "#770000"; // lighter red for dark themes, very dark red for light themes
} else if (warning == WarningLevel::Warning) {
fg = dark_mode ? "#FF6666" : "#C00000"; // lighter red for dark themes, dark red for light themes
} else if (warning == WarningLevel::Alert) {
fg = dark_mode ? "#FF4444" : "#FF0000"; // lighter/pink red for dark themes, bright red for light themes
}
return !(fg.empty());
}
std::string storage_property_get_warning_reason(const StorageProperty& p)
{
std::string fg, start = "<b>", stop = "</b>";
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), p.warning_level, fg)) {
start += "<span color=\"" + fg + "\">";
stop = "</span>" + stop;
}
switch (p.warning_level) {
case WarningLevel::None:
// nothing
break;
case WarningLevel::Notice:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Notice:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Warning:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Warning:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Alert:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1ALERT:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
}
return {};
}
/// @}
+6 -65
View File
@@ -1,7 +1,7 @@
/******************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>
(C) 2008 - 2026 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
/// \file
/// \author Alexander Shaduri
@@ -12,83 +12,24 @@ Copyright:
#ifndef WARNING_COLORS_H
#define WARNING_COLORS_H
#include <glibmm.h>
#include <string>
#include "storage_property.h"
#include "warning_level.h"
/// Get colors for tree rows according to warning severity.
/// \return true if the colors were changed.
inline bool app_property_get_row_highlight_colors(WarningLevel warning, std::string& fg, std::string& bg)
{
// Note: we're setting both fg and bg, to avoid theme conflicts.
if (warning == WarningLevel::Notice) {
fg = "#000000"; // black
bg = "#FFD5EE"; // pinkish
} else if (warning == WarningLevel::Warning) {
fg = "#000000"; // black
bg = "#FFA0A0"; // even more pinkish
} else if (warning == WarningLevel::Alert) {
fg = "#000000"; // black
bg = "#FF0000"; // red
}
return !(fg.empty());
}
bool app_property_get_row_highlight_colors(bool dark_mode, WarningLevel warning, std::string& fg, std::string& bg);
/// Get color for labels according to warning severity.
/// \return true if the color was changed.
inline bool app_property_get_label_highlight_color(WarningLevel warning, std::string& fg)
{
if (warning == WarningLevel::Notice) {
fg = "#770000"; // very dark red
} else if (warning == WarningLevel::Warning) {
fg = "#C00000"; // dark red
} else if (warning == WarningLevel::Alert) {
fg = "#FF0000"; // red
}
return !(fg.empty());
}
bool app_property_get_label_highlight_color(bool dark_mode, WarningLevel warning, std::string& fg);
/// Format warning text, but without description
inline std::string storage_property_get_warning_reason(const StorageProperty& p)
{
std::string fg, start = "<b>", stop = "</b>";
if (app_property_get_label_highlight_color(p.warning_level, fg)) {
start += "<span color=\"" + fg + "\">";
stop = "</span>" + stop;
}
switch (p.warning_level) {
case WarningLevel::None:
// nothing
break;
case WarningLevel::Notice:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Notice:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Warning:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1Warning:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
case WarningLevel::Alert:
/// Translators: %1 and %2 are HTML tags, %3 is a message.
return Glib::ustring::compose(_("%1ALERT:%2 %3"), start, stop, Glib::Markup::escape_text(p.warning_reason));
}
return {};
}
std::string storage_property_get_warning_reason(const StorageProperty& p);
+14 -5
View File
@@ -81,6 +81,7 @@ namespace {
// vbox->pack_start(*label, false, false);
} else {
const bool dark_mode = gui_is_dark_theme_active();
// add one label per element
for (const auto& label_string : label_strings) {
@@ -95,7 +96,7 @@ namespace {
label->set_can_focus(false);
std::string fg;
if (app_property_get_label_highlight_color(label_string.property->warning_level, fg)) {
if (app_property_get_label_highlight_color(dark_mode, label_string.property->warning_level, fg)) {
label->set_markup(
std::string("<span color=\"").append(fg).append("\">")
.append(label_text).append("</span>") );
@@ -129,7 +130,7 @@ namespace {
}
std::string fg;
if (app_property_get_label_highlight_color(warning, fg))
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), warning, fg))
label->set_markup_with_mnemonic("<span color=\"" + fg + "\">" + original_label + "</span>");
}
@@ -828,6 +829,9 @@ void GscInfoWindow::on_view_output_button_clicked()
if (!filename.empty())
win->set_save_filename(filename);
// Pass the storage device so the text window can access text/JSON data
win->set_storage_device(drive_);
win->show();
}
@@ -1038,6 +1042,7 @@ void GscInfoWindow::fill_ui_general(const StoragePropertyRepository& property_re
identity_table->hide();
WarningLevel max_tab_warning = WarningLevel::None;
const bool dark_mode = gui_is_dark_theme_active();
int row = 0;
for (auto&& p : general_props) {
@@ -1070,7 +1075,7 @@ void GscInfoWindow::fill_ui_general(const StoragePropertyRepository& property_re
value->set_markup(Glib::Markup::escape_text(p.format_value()));
std::string fg;
if (app_property_get_label_highlight_color(p.warning_level, fg)) {
if (app_property_get_label_highlight_color(dark_mode, p.warning_level, fg)) {
name->set_markup("<span color=\"" + fg + "\">" + name->get_label() + "</span>");
value->set_markup("<span color=\"" + fg + "\">" + value->get_label() + "</span>");
}
@@ -2067,7 +2072,7 @@ WarningLevel GscInfoWindow::fill_ui_directory(const StoragePropertyRepository& p
inline void cell_renderer_set_warning_fg_bg(Gtk::CellRendererText* crt, const StorageProperty& p)
{
std::string fg, bg;
if (app_property_get_row_highlight_colors(p.warning_level, fg, bg)) {
if (app_property_get_row_highlight_colors(gui_is_dark_theme_active(), p.warning_level, fg, bg)) {
// Note: property_cell_background makes horizontal tree lines disappear around it,
// but property_background doesn't play nice with sorted column color.
crt->property_cell_background() = bg;
@@ -2444,7 +2449,11 @@ gboolean GscInfoWindow::test_idle_callback(void* data)
break;
case SelfTestStatusSeverity::Error:
if (!result_main_msg.empty()) { // Highlight in red
result_main_msg = "<span color=\"#FF0000\">"s + result_main_msg + "</span>";
std::string alert_color;
// Use the same color as Alert level warnings for consistency
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), WarningLevel::Alert, alert_color) && !alert_color.empty()) {
result_main_msg = "<span color=\"" + alert_color + "\">"s + result_main_msg + "</span>";
}
}
result_details_msg += "\n"s + _("Check the Self-Test Log for more information.");
break;
+26
View File
@@ -505,6 +505,32 @@ bool app_init_and_loop(int& argc, char**& argv)
}
*/
// Detect Windows dark mode and set GTK theme preference accordingly
if constexpr(BuildEnv::is_kernel_family_windows()) {
Glib::RefPtr<Gtk::Settings> gtk_settings = Gtk::Settings::get_default();
if (gtk_settings) {
bool use_dark_theme = false;
#ifdef _WIN32
// Check Windows registry for dark mode preference
// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize
// AppsUseLightTheme = 0 means dark mode, 1 means light mode
DWORD apps_use_light_theme = 1; // Default to light mode
if (hz::win32_get_registry_value_dword(HKEY_CURRENT_USER,
R"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)",
"AppsUseLightTheme", apps_use_light_theme)) {
use_dark_theme = (apps_use_light_theme == 0);
debug_out_dump("app", "Windows theme detected: " << (use_dark_theme ? "dark" : "light") << "\n");
} else {
debug_out_dump("app", "Could not read Windows theme preference, defaulting to light mode.\n");
}
#endif
// Apply the dark theme preference to GTK
gtk_settings->property_gtk_application_prefer_dark_theme().set_value(use_dark_theme);
debug_out_dump("app", "GTK dark theme preference set to: " << (use_dark_theme ? "dark" : "light") << "\n");
}
}
// The application is dpi-aware in Windows.
// However, Gtk3 does not support fractional scaling, so at 250% scaling in system settings, the UI will use 200%.
//
+1 -1
View File
@@ -774,7 +774,7 @@ void GscMainWindow::update_status_widgets()
if (health_prop.generic_name == "smart_status/passed") {
health_label_->set_text(health_prop.format_value());
std::string fg;
if (app_property_get_label_highlight_color(health_prop.warning_level, fg)) {
if (app_property_get_label_highlight_color(gui_is_dark_theme_active(), health_prop.warning_level, fg)) {
health_label_->set_markup("<span color=\"" + fg + "\">"+ Glib::Markup::escape_text(health_label_->get_text()) + "</span>");
}
// don't set description tooltip - we already have the basic one.
+7 -1
View File
@@ -120,7 +120,7 @@ bool GscMainWindowIconView::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
return true;
}
if (empty_view_message_ != Message::None && this->num_icons_ == 0) { // no icons
Glib::RefPtr<Pango::Layout> layout = this->create_pango_layout("");
const Glib::RefPtr<Pango::Layout> layout = this->create_pango_layout("");
layout->set_alignment(Pango::ALIGN_CENTER);
layout->set_markup(get_message_string(empty_view_message_));
@@ -131,6 +131,12 @@ bool GscMainWindowIconView::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
const int pos_y = (get_allocation().get_height() - layout_h) / 2;
cr->move_to(pos_x, pos_y);
// Use the foreground color from the widget's style context so
// the text is visible in both light and dark themes.
const auto style_context = get_style_context();
const Gdk::RGBA fg_color = style_context->get_color(style_context->get_state());
cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), fg_color.get_alpha());
layout->show_in_cairo_context(cr);
return true;
+123 -7
View File
@@ -24,6 +24,7 @@ Copyright:
#include "applib/app_builder_widget.h"
#include "applib/app_gtkmm_tools.h"
#include "applib/storage_device.h"
@@ -130,6 +131,13 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
}
/// Set the storage device to enable text/JSON format detection
void set_storage_device(std::shared_ptr<StorageDevice> device)
{
storage_device_ = std::move(device);
}
protected:
@@ -158,11 +166,59 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
}
int result = 0;
// Determine if we have JSON and/or text data available
bool has_json = false;
bool has_text = false;
if (storage_device_) {
// Check if we have text output in the property repository
bool has_text_property = false;
if (auto p = storage_device_->get_property_repository().lookup_property("smartctl/output"); !p.empty()) {
const std::string text_output = p.get_value<std::string>();
if (!text_output.empty()) {
has_text_property = true;
}
}
// Get the output - it could be JSON or text format
std::string output = storage_device_->get_full_output();
if (output.empty()) {
output = storage_device_->get_basic_output();
}
if (!output.empty()) {
// If we have text in property repo, the output is JSON with embedded text
if (has_text_property) {
has_json = true;
has_text = true;
} else {
// No text in property means either:
// 1. Output is JSON without embedded text, or
// 2. Output is text format (loaded text virtual drive)
// We check if output starts with '{' to detect JSON
if (!output.empty() && output[0] == '{') {
has_json = true;
} else {
has_text = true;
}
}
}
} else {
// If no storage device is set, we only have the contents (assume JSON for backward compatibility)
has_json = true;
}
Glib::RefPtr<Gtk::FileFilter> specific_filter = Gtk::FileFilter::create();
specific_filter->set_name(_("JSON and Text Files"));
specific_filter->add_pattern("*.json");
specific_filter->add_pattern("*.txt");
Glib::RefPtr<Gtk::FileFilter> json_filter = Gtk::FileFilter::create();
json_filter->set_name(_("JSON Files"));
json_filter->add_pattern("*.json");
Glib::RefPtr<Gtk::FileFilter> txt_filter = Gtk::FileFilter::create();
txt_filter->set_name(_("Text Files"));
txt_filter->add_pattern("*.txt");
Glib::RefPtr<Gtk::FileFilter> all_filter = Gtk::FileFilter::create();
all_filter->set_name(_("All Files"));
all_filter->add_pattern("*");
@@ -174,7 +230,16 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog.get()), TRUE);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());
// Add filters based on what data is available
if (has_json && has_text) {
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), json_filter->gobj());
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), txt_filter->gobj());
} else if (has_json) {
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), json_filter->gobj());
} else if (has_text) {
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), txt_filter->gobj());
}
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj());
if (!last_dir.empty())
@@ -195,7 +260,16 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
dialog.set_do_overwrite_confirmation(true);
dialog.add_filter(specific_filter);
// Add filters based on what data is available
if (has_json && has_text) {
dialog.add_filter(specific_filter);
dialog.add_filter(json_filter);
dialog.add_filter(txt_filter);
} else if (has_json) {
dialog.add_filter(json_filter);
} else if (has_text) {
dialog.add_filter(txt_filter);
}
dialog.add_filter(all_filter);
if (!last_dir.empty())
@@ -216,22 +290,62 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
#if GTK_CHECK_VERSION(3, 20, 0)
file = hz::fs_path_from_string(app_string_from_gchar(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog.get()))));
last_dir = hz::fs_path_to_string(file.parent_path());
// Detect which filter was selected
bool txt_selected = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog.get())) == txt_filter->gobj();
bool json_selected = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog.get())) == json_filter->gobj();
#else
file = hz::fs_path_from_string(dialog.get_filename()); // in fs encoding
last_dir = dialog.get_current_folder(); // save for the future
// Detect which filter was selected
bool txt_selected = dialog.get_filter() == txt_filter;
bool json_selected = dialog.get_filter() == json_filter;
#endif
rconfig::set_data("gui/drive_data_open_save_dir", last_dir);
// Add extension if not present, based on selected filter
if (file.extension() != ".json" && file.extension() != ".txt") {
file += ".json";
if (txt_selected) {
file += ".txt";
} else if (json_selected) {
file += ".json";
} else {
// "JSON and Text Files" or "All Files" selected - use user-provided extension or default
if (has_json && !has_text) {
file += ".json";
} else if (has_text && !has_json) {
file += ".txt";
} else {
// Both available, default to JSON (per requirements)
file += ".json";
}
}
}
// Determine if we should save as text based on extension or filter
bool save_txt = txt_selected || file.extension() == ".txt";
std::string text;
if (std::holds_alternative<std::string>(contents_)) {
text = std::get<std::string>(contents_);
} else {
text = std::get<Glib::ustring>(contents_);
if (save_txt && storage_device_) {
// Try to get text output from property repository
if (auto p = storage_device_->get_property_repository().lookup_property("smartctl/output"); !p.empty()) {
const std::string text_output = p.get_value<std::string>();
if (!text_output.empty()) {
text = text_output;
}
}
}
// If we didn't get text output or not saving as text, use the contents
if (text.empty()) {
if (std::holds_alternative<std::string>(contents_)) {
text = std::get<std::string>(contents_);
} else {
text = std::get<Glib::ustring>(contents_);
}
}
auto ec = hz::fs_file_put_contents(file, text);
if (ec) {
gui_show_error_dialog(_("Cannot save data to file"), ec.message(), this);
@@ -269,6 +383,8 @@ class GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, Ins
std::string save_filename_; ///< Default filename for Save As
std::shared_ptr<StorageDevice> storage_device_; ///< Storage device for accessing text/JSON data
};
+51
View File
@@ -79,6 +79,14 @@ inline bool win32_set_registry_value_string(HKEY base,
const std::string& keydir, const std::string& key, const std::string& value);
/// Get registry value as a DWORD.
/// Base may be e.g. HKEY_CURRENT_USER.
/// Note that this works only with REG_DWORD types.
/// False is returned for all other types.
inline bool win32_get_registry_value_dword(HKEY base,
const std::string& keydir, const std::string& key, DWORD& put_here);
/// Redirect stdout and stderr to console window (if open). Requires winxp (at compile-time).
/// \param create_if_none if true, create a new console if none was found and attach to it.
/// \return false if failed or unsupported.
@@ -340,6 +348,49 @@ inline bool win32_set_registry_value_string(HKEY base,
// Get registry value as a DWORD.
// Note that this works only with REG_DWORD types.
inline bool win32_get_registry_value_dword(HKEY base,
const std::string& keydir, const std::string& key, DWORD& put_here)
{
std::wstring wkeydir = win32_utf8_to_utf16(keydir);
if (wkeydir.empty())
return false;
HKEY reg_key = nullptr;
bool open_status = (RegOpenKeyExW(base, wkeydir.c_str(), 0, KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS);
if (!open_status)
return false;
bool ok = false;
std::wstring wkey = win32_utf8_to_utf16(key, &ok);
if (!ok) { // conversion error. Note that an empty string is not an error.
if (reg_key)
RegCloseKey(reg_key);
return false;
}
DWORD type = 0;
DWORD value = 0;
DWORD nbytes = sizeof(DWORD);
bool status = (RegQueryValueExW(reg_key, wkey.c_str(), nullptr, &type,
reinterpret_cast<BYTE*>(&value), &nbytes) == ERROR_SUCCESS);
if (status && type == REG_DWORD) {
put_here = value;
} else {
status = false;
}
if (reg_key)
RegCloseKey(reg_key);
return status;
}
// Redirect stdout and stderr to console window (if open).
inline bool win32_redirect_stdio_to_console(bool create_if_none)
{