From bedd8f362b5a4453005dd0cebf46c927f15cb41c Mon Sep 17 00:00:00 2001 From: Alexander Shaduri Date: Mon, 15 Mar 2021 17:55:22 +0400 Subject: [PATCH] Refactored gtkmm utils and app builder to simply the code and document it better. --- src/applib/app_builder_widget.h | 209 +++++++++++++++++++----------- src/applib/app_gtkmm_features.h | 4 +- src/applib/app_gtkmm_utils.cpp | 26 ++-- src/applib/app_gtkmm_utils.h | 90 ++++++++----- src/gsc_about_dialog.cpp | 2 +- src/gsc_add_device_window.cpp | 4 +- src/gsc_executor_error_dialog.cpp | 2 +- src/gsc_executor_log_window.cpp | 4 +- src/gsc_info_window.cpp | 92 ++++++------- src/gsc_init.cpp | 4 +- src/gsc_preferences_window.cpp | 6 +- src/gsc_text_window.h | 45 ++++++- src/hz/instance_manager.h | 105 +++++---------- 13 files changed, 329 insertions(+), 264 deletions(-) diff --git a/src/applib/app_builder_widget.h b/src/applib/app_builder_widget.h index 62135a7..730ab25 100644 --- a/src/applib/app_builder_widget.h +++ b/src/applib/app_builder_widget.h @@ -24,10 +24,9 @@ Copyright: -// These allow easy attaching of gtkbuilder widget signals to member functions - -/// Connect member function (callback) to signal \c signal_name on widget -/// \c ui_element, where \c ui_element is the widget's gtkbuilder name. +/// Connect member function (callback) to signal \ref signal_name on widget +/// \ref ui_element, where \ref ui_element is the widget's gtkbuilder name. +/// This allows easy attaching of gtkbuilder widget signals to member functions. #define APP_BUILDER_CONNECT(ui_element, signal_name, callback) \ if (true) { \ if (!(ui_element)) \ @@ -38,6 +37,7 @@ Copyright: } else (void)0 + /// Connect member function (callback) with a name of \c on__ /// to signal \c signal_name on widget \c ui_element, where \c ui_element is the /// widget's gtkbuilder name. @@ -46,6 +46,7 @@ Copyright: + /// Inherit this when using GtkBuilder-enabled windows (or any other GtkBuilder-enabled objects). /// \c Child is the child class that inherits all the functionality of having instance lifetime /// management and other benefits. @@ -54,106 +55,62 @@ template class AppBuilderWidget : public WidgetType, public hz::InstanceManager { public: - /// Instance class type, which is also the parent class. - friend class Gtk::Builder; // allow construction through gtkbuilder - friend class hz::InstanceManager; // allow construction through instance class + friend class Gtk::Builder; // allow construction via GtkBuilder + // friend class hz::InstanceManager; // allow construction through instance class - /// Override parent hz::InstanceManager's function because of non-trivial constructor - static Child* create() - { - if constexpr(!MultiInstance) { // for single-instance objects - if (hz::InstanceManager::get_single_instance()) { - return hz::InstanceManager::get_single_instance(); - } - } + /// Disallow + AppBuilderWidget(const AppBuilderWidget& other) = delete; - std::string error_msg; + /// Disallow + AppBuilderWidget(const AppBuilderWidget&& other) = delete; - auto ui_path = hz::data_file_find("ui", std::string(Child::ui_name) + ".glade"); - try { - auto ui = Gtk::Builder::create_from_file(ui_path.u8string()); // may throw + /// Disallow + AppBuilderWidget& operator=(const AppBuilderWidget& other) = delete; - Child* o = nullptr; - ui->get_widget_derived({Child::ui_name.data(), Child::ui_name.size()}, o); // Calls Child's constructor + /// Disallow + AppBuilderWidget& operator=(const AppBuilderWidget&& other) = delete; - if (!o) { - debug_out_fatal("app", "Fatal error: Cannot get root widget from UI-resource-created hierarchy.\n"); - gui_show_error_dialog(_("Fatal error: Cannot get root widget from UI-resource-created hierarchy.")); - return nullptr; - } + /// Default + ~AppBuilderWidget() = default; - if constexpr(!MultiInstance) { - hz::InstanceManager::set_single_instance(o); // for single-instance objects - } - return o; - } - catch (Glib::Exception& ex) { - error_msg = ex.what(); - } - if (!error_msg.empty()) { - debug_out_fatal("app", "Fatal error: Cannot create UI-resource widgets: " << error_msg << "\n"); - gui_show_error_dialog(Glib::ustring::compose(_("Fatal error: Cannot create UI-resource widgets: %1"), error_msg)); - } - return nullptr; - } + + /// Create an instance of this class, returning an existing instance if not MultiInstance. + /// A glade file in "ui" data domain is loaded with Child::ui_name filename base and is available as + /// `get_ui()` in child object. + /// \return nullptr if widget could not be loaded. + static Child* create(); /// Get UI resource - Glib::RefPtr get_ui() - { - return ui_; - } + Glib::RefPtr get_ui(); /// Find a widget in UI and return it. - Gtk::Widget* lookup_widget(const Glib::ustring& name) - { - return lookup_widget(name); - } + /// \return nullptr if widget was not found. + Gtk::Widget* lookup_widget(const Glib::ustring& name); /// Find a widget in UI and return it. + /// \return nullptr if widget was not found. template - WidgetPtr lookup_widget(const Glib::ustring& name) - { - WidgetPtr w = nullptr; - return lookup_widget(name, w); - } + WidgetPtr lookup_widget(const Glib::ustring& name); - /// Find a widget in UI and return it. + /// Find a widget in UI and return it in \ref w. + /// \return false if widget was not found. template - Widget* lookup_widget(const Glib::ustring& name, Widget*& w) - { - ui_->get_widget(name, w); - return w; - } + bool lookup_widget(const Glib::ustring& name, Widget*& w); protected: - // protected constructor / destructor, use create() / destroy() instead of new / delete. + /// Protected constructor, use `create()` instead. /// GtkBuilder needs this constructor in a child. /// BaseObjectType is a C type, defined in specific Gtk:: widget class. - AppBuilderWidget(typename WidgetType::BaseObjectType* gtkcobj, Glib::RefPtr ui) - : WidgetType(gtkcobj), ui_(std::move(ui)) - { - // manually connecting signals: - // this->signal_delete_event().connect(sigc::mem_fun(*this, &MainWindow::on_main_window_delete)); - - // signals of GtkBuilder-created objects: - // Gtk::ToolButton* rescan_devices_toolbutton = 0; - // APP_BUILDER_AUTO_CONNECT(rescan_devices_toolbutton, clicked); - - // show(); - } - - - /// Virtual destructor - ~AppBuilderWidget() = default; + AppBuilderWidget(typename WidgetType::BaseObjectType* gtkcobj, Glib::RefPtr ui); private: @@ -166,6 +123,106 @@ class AppBuilderWidget : public WidgetType, public hz::InstanceManager +Child* AppBuilderWidget::create() +{ + if constexpr(!MultiInstance) { // for single-instance objects + if (auto* inst = hz::InstanceManager::instance()) { + return inst; + } + } + + std::string error_msg; + + auto ui_path = hz::data_file_find("ui", std::string(Child::ui_name) + ".glade"); + try { + auto ui = Gtk::Builder::create_from_file(ui_path.u8string()); // may throw + + Child* o = nullptr; + ui->get_widget_derived({Child::ui_name.data(), Child::ui_name.size()}, o); // Calls Child's constructor + + if (!o) { + debug_out_fatal("app", "Fatal error: Cannot get root widget from UI-resource-created hierarchy.\n"); + gui_show_error_dialog(_("Fatal error: Cannot get root widget from UI-resource-created hierarchy.")); + return nullptr; + } + + if constexpr(!MultiInstance) { + hz::InstanceManager::set_single_instance(o); // for single-instance objects + } + return o; + } + catch (Glib::Exception& ex) { + error_msg = ex.what(); + } + + if (!error_msg.empty()) { + debug_out_fatal("app", "Fatal error: Cannot create UI-resource widgets: " << error_msg << "\n"); + gui_show_error_dialog(Glib::ustring::compose(_("Fatal error: Cannot create UI-resource widgets: %1"), error_msg)); + } + return nullptr; +} + + + +template +Glib::RefPtr AppBuilderWidget::get_ui() +{ + return ui_; +} + + + +template +template +WidgetPtr AppBuilderWidget::lookup_widget(const Glib::ustring& name) +{ + WidgetPtr w = nullptr; + lookup_widget(name, w); + return w; +} + + + +template +Gtk::Widget* AppBuilderWidget::lookup_widget(const Glib::ustring& name) +{ + return lookup_widget(name); +} + + + +template +template +bool AppBuilderWidget::lookup_widget(const Glib::ustring& name, Widget*& w) +{ + ui_->get_widget(name, w); + return w != nullptr; +} + + + +template +AppBuilderWidget::AppBuilderWidget(typename WidgetType::BaseObjectType* gtkcobj, Glib::RefPtr ui) + : WidgetType(gtkcobj), ui_(std::move(ui)) +{ + // An example of Child's constructor: + + // Manually connect signals: + // this->signal_delete_event().connect(sigc::mem_fun(*this, &MainWindow::on_main_window_delete)); + + // Automatically connect signals of GtkBuilder-created objects to member functions: + // Gtk::ToolButton* rescan_devices_toolbutton = 0; + // APP_BUILDER_AUTO_CONNECT(rescan_devices_toolbutton, clicked); + + // show(); +} + + + #endif diff --git a/src/applib/app_gtkmm_features.h b/src/applib/app_gtkmm_features.h index ce3fb06..3869be9 100644 --- a/src/applib/app_gtkmm_features.h +++ b/src/applib/app_gtkmm_features.h @@ -16,8 +16,10 @@ Copyright: #include + /// \def APP_GTKMM_CHECK_VERSION(major, minor, micro) -/// Similar to GTK_CHECK_VERSION, but for gtkmm, which lacks this for some reason. +/// Similar to GTK_CHECK_VERSION, but for Gtkmm, which lacks this before gtkmm4. +/// This is useful as Gtk and Gtkmm versions may differ. #ifndef APP_GTKMM_CHECK_VERSION #define APP_GTKMM_CHECK_VERSION(major, minor, micro) \ (GTKMM_MAJOR_VERSION > (major) \ diff --git a/src/applib/app_gtkmm_utils.cpp b/src/applib/app_gtkmm_utils.cpp index 1c6f340..ecf71ed 100644 --- a/src/applib/app_gtkmm_utils.cpp +++ b/src/applib/app_gtkmm_utils.cpp @@ -20,7 +20,6 @@ Copyright: -// Note: This works only if the column has custom widget set. Gtk::Widget* app_gtkmm_get_column_header(Gtk::TreeViewColumn& column) { Gtk::Widget* w = column.get_widget(); @@ -37,19 +36,16 @@ Gtk::Widget* app_gtkmm_get_column_header(Gtk::TreeViewColumn& column) -// Read column header text and create a label with that text, set it as column's custom widget. -Gtk::Widget* app_gtkmm_labelize_column(Gtk::TreeViewColumn& column) +Gtk::Label& app_gtkmm_labelize_column(Gtk::TreeViewColumn& column) { Gtk::Label* label = Gtk::manage(new Gtk::Label(column.get_title())); label->show(); column.set_widget(*label); - return label; + return *label; } - -// A wrapper around set_tooltip_*() for portability across different gtkmm versions. void app_gtkmm_set_widget_tooltip(Gtk::Widget& widget, const Glib::ustring& tooltip_text, bool use_markup) { @@ -67,7 +63,7 @@ namespace { /// This has been copied from _g_utf8_make_valid() (glib-2.20.4). /// _g_utf8_make_valid() is GLib's private function for auto-correcting /// the potentially invalid utf-8 data. - inline gchar* gsc_g_utf8_make_valid (const gchar* name) + inline gchar* app_make_valid_utf_c (const gchar* name) { GString* str = nullptr; const gchar* remainder = nullptr; @@ -123,29 +119,23 @@ Glib::ustring app_ustring_from_gchar(gchar* str) -Glib::ustring app_utf8_make_valid(const Glib::ustring& str) +Glib::ustring app_make_valid_utf8(const Glib::ustring& str) { - char* s = gsc_g_utf8_make_valid(str.c_str()); - if (!s) { - return Glib::ustring(); - } - Glib::ustring res(s); - g_free(s); - return res; + return app_ustring_from_gchar(app_make_valid_utf_c(str.c_str())); } -Glib::ustring app_output_make_valid(const Glib::ustring& str) +Glib::ustring app_make_valid_utf8_from_command_output(const std::string& str) { #ifdef _WIN32 try { - return app_utf8_make_valid(Glib::locale_to_utf8(str)); + return Glib::locale_to_utf8(str); // detects invalid utf-8 sequences } catch (Glib::ConvertError& e) { // nothing, try to fix as it is } #endif - return app_utf8_make_valid(str); + return app_ustring_from_gchar(app_make_valid_utf_c(str.c_str())); } diff --git a/src/applib/app_gtkmm_utils.h b/src/applib/app_gtkmm_utils.h index fb4c28f..1d9a035 100644 --- a/src/applib/app_gtkmm_utils.h +++ b/src/applib/app_gtkmm_utils.h @@ -19,49 +19,26 @@ Copyright: /// Get column header widget of a tree view column. /// Note: This works only if the column has custom widget set. +/// \return nullptr on failure. Gtk::Widget* app_gtkmm_get_column_header(Gtk::TreeViewColumn& column); /// Read column header text and create a label with that text. Set the label as /// column's custom widget and return it. -Gtk::Widget* app_gtkmm_labelize_column(Gtk::TreeViewColumn& column); +Gtk::Label& app_gtkmm_labelize_column(Gtk::TreeViewColumn& column); -/// A wrapper around set_tooltip_*() for portability across different gtkmm versions. +/// A wrapper around set_tooltip_*(), calling appropriate method depending on `use_markup`. void app_gtkmm_set_widget_tooltip(Gtk::Widget& widget, const Glib::ustring& tooltip_text, bool use_markup = false); - -/// Convenience function for creating a TreeViewColumn . -template -int app_gtkmm_create_tree_view_column(Gtk::TreeModelColumn& mcol, Gtk::TreeView& treeview, - const Glib::ustring& title, const Glib::ustring& tooltip_text, bool sortable = false, bool cell_markup = false, bool tooltip_markup = false) -{ - int num_tree_cols = treeview.append_column(title, mcol); - Gtk::TreeViewColumn* tcol = treeview.get_column(num_tree_cols - 1); - if (tcol) { - if (sortable) - tcol->set_sort_column(mcol); - - app_gtkmm_labelize_column(*tcol); - tcol->set_reorderable(true); - tcol->set_resizable(true); - - Gtk::Widget* header = app_gtkmm_get_column_header(*tcol); - if (header) - app_gtkmm_set_widget_tooltip(*header, tooltip_text, tooltip_markup); - } - - if (cell_markup) { - if (auto cr_type = dynamic_cast(treeview.get_column_cell_renderer(num_tree_cols - 1))) { - treeview.get_column(num_tree_cols - 1)->clear_attributes(*cr_type); // clear "text" attribute. "markup" won't work without this. - treeview.get_column(num_tree_cols - 1)->add_attribute(cr_type->property_markup(), mcol); // render col_type as markup. - } - } - - return num_tree_cols; -} +/// Convenience function for creating a TreeViewColumn from model column. +/// \return tree column index +template +int app_gtkmm_create_tree_view_column(const Gtk::TreeModelColumn& model_column, + Gtk::TreeView& treeview, const Glib::ustring& header_title, const Glib::ustring& header_tooltip_text, + bool sortable = false, bool use_cell_markup = false, bool header_tooltip_is_markup = false); @@ -71,14 +48,55 @@ Glib::ustring app_ustring_from_gchar(gchar* str); /// Convert a possibly invalid utf-8 string to valid utf-8. /// \param str string to test and fix. -Glib::ustring app_utf8_make_valid(const Glib::ustring& str); +Glib::ustring app_make_valid_utf8(const Glib::ustring& str); -/// Make command output a valid utf-8 string. Essentially, this calls app_utf8_make_valid(), -/// supplying true for \c in_locale under Win32, and false under other systems. +/// Make command output a valid utf-8 string. This function takes command output +/// (in locale encoding under Windows, utf-8 encoding under other OSes), and converts +/// it to valid utf-8. /// The reason for this is that in Win32 we can't execute commands under C locale, /// but we do execute them under C in other systems. -Glib::ustring app_output_make_valid(const Glib::ustring& str); +Glib::ustring app_make_valid_utf8_from_command_output(const std::string& str); + + + + +// ------------------------------------------- Implementation + + + + +template +int app_gtkmm_create_tree_view_column(const Gtk::TreeModelColumn& model_column, + Gtk::TreeView& treeview, const Glib::ustring& header_title, const Glib::ustring& header_tooltip_text, + bool sortable, bool use_cell_markup, bool header_tooltip_is_markup) +{ + int num_tree_cols = treeview.append_column(header_title, model_column); + Gtk::TreeViewColumn* tcol = treeview.get_column(num_tree_cols - 1); + if (tcol) { + if (sortable) { + tcol->set_sort_column(model_column); + } + + app_gtkmm_labelize_column(*tcol); + tcol->set_reorderable(true); + tcol->set_resizable(true); + + Gtk::Widget* header = app_gtkmm_get_column_header(*tcol); + if (header) { + app_gtkmm_set_widget_tooltip(*header, header_tooltip_text, header_tooltip_is_markup); + } + } + + if (use_cell_markup) { + if (auto* cr_type = dynamic_cast(treeview.get_column_cell_renderer(num_tree_cols - 1))) { + treeview.get_column(num_tree_cols - 1)->clear_attributes(*cr_type); // clear "text" attribute. "markup" won't work without this. + treeview.get_column(num_tree_cols - 1)->add_attribute(cr_type->property_markup(), model_column); // render col_type as markup. + } + } + + return num_tree_cols - 1; +} diff --git a/src/gsc_about_dialog.cpp b/src/gsc_about_dialog.cpp index 24eb762..802325c 100644 --- a/src/gsc_about_dialog.cpp +++ b/src/gsc_about_dialog.cpp @@ -79,7 +79,7 @@ void GscAboutDialog::on_response(int response_id) if (response_id == Gtk::RESPONSE_NONE || response_id == Gtk::RESPONSE_DELETE_EVENT || response_id == Gtk::RESPONSE_CANCEL || response_id == Gtk::RESPONSE_CLOSE) { debug_out_info("app", DBG_FUNC_MSG << "Closing the dialog.\n"); - destroy(); // close the window and delete the object + delete this; // close the window and delete the object } } diff --git a/src/gsc_add_device_window.cpp b/src/gsc_add_device_window.cpp index a0cd972..0a74421 100644 --- a/src/gsc_add_device_window.cpp +++ b/src/gsc_add_device_window.cpp @@ -138,7 +138,7 @@ bool GscAddDeviceWindow::on_delete_event([[maybe_unused]] GdkEventAny* e) void GscAddDeviceWindow::on_window_cancel_button_clicked() { - destroy(this); + delete this; } @@ -159,7 +159,7 @@ void GscAddDeviceWindow::on_window_ok_button_clicked() main_window_->add_device(dev, type, params); } - destroy(this); + delete this; } diff --git a/src/gsc_executor_error_dialog.cpp b/src/gsc_executor_error_dialog.cpp index 6e3fd77..0e2adf8 100644 --- a/src/gsc_executor_error_dialog.cpp +++ b/src/gsc_executor_error_dialog.cpp @@ -92,7 +92,7 @@ void gsc_no_info_dialog_show(const std::string& message, const std::string& sec_ if (response == Gtk::RESPONSE_HELP) { GscTextWindow* win = GscTextWindow::create(); - win->set_text(output_window_title, output, true, true); + win->set_text_from_command(output_window_title, output); if (!default_save_filename.empty()) win->set_save_filename(default_save_filename); diff --git a/src/gsc_executor_log_window.cpp b/src/gsc_executor_log_window.cpp index 17ff125..94d5a0a 100644 --- a/src/gsc_executor_log_window.cpp +++ b/src/gsc_executor_log_window.cpp @@ -421,7 +421,7 @@ void GscExecutorLogWindow::on_tree_selection_changed() if (auto* output_textview = this->lookup_widget("output_textview")) { Glib::RefPtr buffer = output_textview->get_buffer(); if (buffer) { - buffer->set_text(app_output_make_valid(entry->std_output)); + buffer->set_text(app_make_valid_utf8_from_command_output(entry->std_output)); Glib::RefPtr tag; Glib::RefPtr table = buffer->get_tag_table(); @@ -437,7 +437,7 @@ void GscExecutorLogWindow::on_tree_selection_changed() if (auto* command_entry = this->lookup_widget("command_entry")) { std::string cmd_text = entry->command + " " + entry->parameters; - command_entry->set_text(app_output_make_valid(cmd_text)); + command_entry->set_text(app_make_valid_utf8_from_command_output(cmd_text)); } if (auto* window_save_current_button = this->lookup_widget("window_save_current_button")) diff --git a/src/gsc_info_window.cpp b/src/gsc_info_window.cpp index a3bd4da..77dede7 100644 --- a/src/gsc_info_window.cpp +++ b/src/gsc_info_window.cpp @@ -722,7 +722,7 @@ void GscInfoWindow::on_view_output_button_clicked() output = this->drive->get_info_output(); } - win->set_text(_("Smartctl Output"), output, true, true); + win->set_text_from_command(_("Smartctl Output"), output); std::string filename = drive->get_save_filename(); if (!filename.empty()) @@ -838,7 +838,7 @@ void GscInfoWindow::on_close_window_button_clicked() if (drive && drive->get_test_is_active()) { // disallow close if test is active. gui_show_warn_dialog(_("Please wait until all tests are finished."), this); } else { - destroy(this); // deletes this object and nullifies instance + delete this; // deletes this object and nullifies instance } } @@ -963,63 +963,63 @@ void GscInfoWindow::fill_ui_attributes(const std::vector& props auto* treeview = lookup_widget("attributes_treeview"); Gtk::TreeModelColumnRecord model_columns; - int num_tree_cols = 0; + [[maybe_unused]] int num_tree_col = 0; // ID (int), Name, Flag (hex), Normalized Value (uint8), Worst (uint8), Thresh (uint8), Raw (int64), Type (string), // Updated (string), When Failed (string) Gtk::TreeModelColumn col_id; model_columns.add(col_id); // we can use the column variable by value after this. - num_tree_cols = app_gtkmm_create_tree_view_column(col_id, *treeview, _("ID"), _("Attribute ID"), true); + num_tree_col = app_gtkmm_create_tree_view_column(col_id, *treeview, _("ID"), _("Attribute ID"), true); Gtk::TreeModelColumn col_name; model_columns.add(col_name); - num_tree_cols = app_gtkmm_create_tree_view_column(col_name, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_name, *treeview, _("Name"), _("Attribute name (this is deduced from ID by smartctl and may be incorrect, as it's highly vendor-specific)"), true); treeview->set_search_column(col_name.index()); - auto* cr_name = dynamic_cast(treeview->get_column_cell_renderer(num_tree_cols - 1)); + auto* cr_name = dynamic_cast(treeview->get_column_cell_renderer(num_tree_col)); if (cr_name) cr_name->property_weight() = Pango::WEIGHT_BOLD; Gtk::TreeModelColumn col_failed; model_columns.add(col_failed); - num_tree_cols = app_gtkmm_create_tree_view_column(col_failed, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_failed, *treeview, _("Failed"), _("When failed (that is, the normalized value became equal to or less than threshold)"), true, true); Gtk::TreeModelColumn col_value; model_columns.add(col_value); - num_tree_cols = app_gtkmm_create_tree_view_column(col_value, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_value, *treeview, C_("value", "Normalized"), _("Normalized value (highly vendor-specific; converted from Raw value by the drive's firmware)"), false); Gtk::TreeModelColumn col_worst; model_columns.add(col_worst); - num_tree_cols = app_gtkmm_create_tree_view_column(col_worst, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_worst, *treeview, C_("value", "Worst"), _("The worst normalized value recorded for this attribute during the drive's lifetime (with SMART enabled)"), false); Gtk::TreeModelColumn col_threshold; model_columns.add(col_threshold); - num_tree_cols = app_gtkmm_create_tree_view_column(col_threshold, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_threshold, *treeview, C_("value", "Threshold"), _("Threshold for normalized value. Normalized value should be greater than threshold (unless vendor thinks otherwise)."), false); Gtk::TreeModelColumn col_raw; model_columns.add(col_raw); - num_tree_cols = app_gtkmm_create_tree_view_column(col_raw, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_raw, *treeview, _("Raw value"), _("Raw value as reported by drive. May or may not be sensible."), false); Gtk::TreeModelColumn col_type; model_columns.add(col_type); - num_tree_cols = app_gtkmm_create_tree_view_column(col_type, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_type, *treeview, _("Type"), _("Alarm condition is reached when normalized value becomes less than or equal to threshold. Type indicates whether it's a signal of drive's pre-failure time or just an old age."), false, true); // Doesn't carry that much info. Advanced users can look at the flags. // Gtk::TreeModelColumn col_updated; // model_columns.add(col_updated); -// num_tree_cols = app_gtkmm_create_tree_view_column(col_updated, *treeview, +// tree_col = app_gtkmm_create_tree_view_column(col_updated, *treeview, // "Updated", "The attribute is usually updated continuously, or during Offline Data Collection only. This column indicates that.", true); Gtk::TreeModelColumn col_flag_value; model_columns.add(col_flag_value); - num_tree_cols = app_gtkmm_create_tree_view_column(col_flag_value, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_flag_value, *treeview, _("Flags"), _("Flags") + "\n\n"s + Glib::ustring::compose(_("If given in %1 format, the presence of each letter indicates that the flag is on."), "POSRCK+") + "\n" + _("P: pre-failure attribute (if the attribute failed, the drive is failing)") + "\n" @@ -1044,7 +1044,7 @@ void GscInfoWindow::fill_ui_attributes(const std::vector& props list_store->set_sort_column(col_id, Gtk::SORT_ASCENDING); // default sort treeview->set_model(list_store); - for (int i = 0; i < num_tree_cols; ++i) { + for (int i = 0; i < int(treeview->get_n_columns()); ++i) { Gtk::TreeViewColumn* tcol = treeview->get_column(i); tcol->set_cell_data_func(*(tcol->get_first_cell()), sigc::bind(sigc::ptr_fun(app_list_cell_renderer_func), col_storage)); @@ -1111,25 +1111,25 @@ void GscInfoWindow::fill_ui_statistics(const std::vector& props auto* treeview = lookup_widget("statistics_treeview"); Gtk::TreeModelColumnRecord model_columns; - int num_tree_cols = 0; + [[maybe_unused]] int num_tree_col = 0; Gtk::TreeModelColumn col_description; model_columns.add(col_description); - num_tree_cols = app_gtkmm_create_tree_view_column(col_description, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_description, *treeview, _("Description"), _("Entry description"), true); treeview->set_search_column(col_description.index()); -// Gtk::CellRendererText* cr_name = dynamic_cast(treeview->get_column_cell_renderer(num_tree_cols - 1)); +// Gtk::CellRendererText* cr_name = dynamic_cast(treeview->get_column_cell_renderer(num_tree_col)); // if (cr_name) // cr_name->property_weight() = Pango::WEIGHT_BOLD ; Gtk::TreeModelColumn col_value; model_columns.add(col_value); - num_tree_cols = app_gtkmm_create_tree_view_column(col_value, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_value, *treeview, _("Value"), Glib::ustring::compose(_("Value (can be normalized if '%1' flag is present)"), "N"), false); Gtk::TreeModelColumn col_flags; model_columns.add(col_flags); - num_tree_cols = app_gtkmm_create_tree_view_column(col_flags, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_flags, *treeview, _("Flags"), _("Flags") + "\n\n"s + _("N: value is normalized") + "\n" + _("D: supports Device Statistics Notification (DSN)") + "\n" @@ -1138,7 +1138,7 @@ void GscInfoWindow::fill_ui_statistics(const std::vector& props Gtk::TreeModelColumn col_page_offset; model_columns.add(col_page_offset); - num_tree_cols = app_gtkmm_create_tree_view_column(col_page_offset, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_page_offset, *treeview, _("Page, Offset"), _("Page and offset of the entry"), false); Gtk::TreeModelColumn col_tooltip; @@ -1154,7 +1154,7 @@ void GscInfoWindow::fill_ui_statistics(const std::vector& props treeview->set_model(list_store); // No sorting (we don't want to screw up the headers). - for (int i = 0; i < num_tree_cols; ++i) { + for (int i = 0; i < int(treeview->get_n_columns()); ++i) { Gtk::TreeViewColumn* tcol = treeview->get_column(i); tcol->set_cell_data_func(*(tcol->get_first_cell()), sigc::bind(sigc::ptr_fun(app_list_cell_renderer_func), col_storage)); @@ -1291,42 +1291,42 @@ void GscInfoWindow::fill_ui_self_test_log(const std::vector& pr auto* treeview = lookup_widget("selftest_log_treeview"); Gtk::TreeModelColumnRecord model_columns; - int num_tree_cols = 0; + [[maybe_unused]] int num_tree_col = 0; // Test num., Type, Status, % Completed, Lifetime hours, LBA of the first error Gtk::TreeModelColumn col_num; model_columns.add(col_num); // we can use the column variable by value after this. - num_tree_cols = app_gtkmm_create_tree_view_column(col_num, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_num, *treeview, _("Test #"), _("Test # (greater may mean newer or older depending on drive model)"), true); - auto* cr_test_num = dynamic_cast(treeview->get_column_cell_renderer(num_tree_cols - 1)); + auto* cr_test_num = dynamic_cast(treeview->get_column_cell_renderer(num_tree_col)); if (cr_test_num) cr_test_num->property_weight() = Pango::WEIGHT_BOLD ; Gtk::TreeModelColumn col_type; model_columns.add(col_type); - num_tree_cols = app_gtkmm_create_tree_view_column(col_type, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_type, *treeview, _("Type"), _("Type of the test performed"), true); treeview->set_search_column(col_type.index()); Gtk::TreeModelColumn col_status; model_columns.add(col_status); - num_tree_cols = app_gtkmm_create_tree_view_column(col_status, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_status, *treeview, _("Status"), _("Test completion status"), true); Gtk::TreeModelColumn col_percent; model_columns.add(col_percent); - num_tree_cols = app_gtkmm_create_tree_view_column(col_percent, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_percent, *treeview, _("% Completed"), _("Percentage of the test completed. Instantly-aborted tests have 10%, while unsupported ones may have 100%."), true, false, true); Gtk::TreeModelColumn col_hours; model_columns.add(col_hours); - num_tree_cols = app_gtkmm_create_tree_view_column(col_hours, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_hours, *treeview, _("Lifetime hours"), _("During which hour of the drive's (powered on) lifetime did the test complete (or abort)"), true); Gtk::TreeModelColumn col_lba; model_columns.add(col_lba); - num_tree_cols = app_gtkmm_create_tree_view_column(col_lba, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_lba, *treeview, _("LBA of the first error"), _("LBA of the first error (if an LBA-related error happened)"), true); Gtk::TreeModelColumn col_tooltip; @@ -1342,7 +1342,7 @@ void GscInfoWindow::fill_ui_self_test_log(const std::vector& pr list_store->set_sort_column(col_num, Gtk::SORT_ASCENDING); // default sort treeview->set_model(list_store); - for (int i = 0; i < num_tree_cols; ++i) { + for (int i = 0; i < int(treeview->get_n_columns()); ++i) { Gtk::TreeViewColumn* tcol = treeview->get_column(i); tcol->set_cell_data_func(*(tcol->get_first_cell()), sigc::bind(sigc::ptr_fun(app_list_cell_renderer_func), col_storage)); @@ -1402,35 +1402,35 @@ void GscInfoWindow::fill_ui_error_log(const std::vector& props) auto* treeview = lookup_widget("error_log_treeview"); Gtk::TreeModelColumnRecord model_columns; - int num_tree_cols = 0; + [[maybe_unused]] int num_tree_col = 0; // Error Number, Lifetime Hours, State, Type, Details, [tooltips] Gtk::TreeModelColumn col_num; model_columns.add(col_num); // we can use the column variable by value after this. - num_tree_cols = app_gtkmm_create_tree_view_column(col_num, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_num, *treeview, _("Error #"), _("Error # in the error log (greater means newer)"), true); - if (auto* cr_name = dynamic_cast(treeview->get_column_cell_renderer(num_tree_cols - 1))) + if (auto* cr_name = dynamic_cast(treeview->get_column_cell_renderer(num_tree_col))) cr_name->property_weight() = Pango::WEIGHT_BOLD ; Gtk::TreeModelColumn col_hours; model_columns.add(col_hours); - num_tree_cols = app_gtkmm_create_tree_view_column(col_hours, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_hours, *treeview, _("Lifetime hours"), _("During which hour of the drive's (powered on) lifetime did the error happen."), true); Gtk::TreeModelColumn col_state; model_columns.add(col_state); - num_tree_cols = app_gtkmm_create_tree_view_column(col_state, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_state, *treeview, C_("power", "State"), _("Power state of the drive when the error occurred"), false); Gtk::TreeModelColumn col_type; model_columns.add(col_type); - num_tree_cols = app_gtkmm_create_tree_view_column(col_type, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_type, *treeview, _("Type"), _("Type of error"), true); Gtk::TreeModelColumn col_details; model_columns.add(col_details); - num_tree_cols = app_gtkmm_create_tree_view_column(col_details, *treeview, + num_tree_col = app_gtkmm_create_tree_view_column(col_details, *treeview, _("Details"), _("Additional details (e.g. LBA where the error occurred, etc...)"), true); Gtk::TreeModelColumn col_tooltip; @@ -1449,7 +1449,7 @@ void GscInfoWindow::fill_ui_error_log(const std::vector& props) list_store->set_sort_column(col_num, Gtk::SORT_DESCENDING); // default sort treeview->set_model(list_store); - for (int i = 0; i < num_tree_cols; ++i) { + for (int i = 0; i < int(treeview->get_n_columns()); ++i) { Gtk::TreeViewColumn* tcol = treeview->get_column(i); tcol->set_cell_data_func(*(tcol->get_first_cell()), sigc::bind(sigc::ptr_fun(app_list_cell_renderer_func), col_storage)); @@ -1607,29 +1607,29 @@ WarningLevel GscInfoWindow::fill_ui_capabilities(const std::vector("capabilities_treeview"); Gtk::TreeModelColumnRecord model_columns; - int num_tree_cols = 0; + [[maybe_unused]] int num_tree_col = 0; // N, Name, Flag, Capabilities, [tooltips] Gtk::TreeModelColumn col_index; model_columns.add(col_index); // we can use the column variable by value after this. - num_tree_cols = app_gtkmm_create_tree_view_column(col_index, *treeview, _("#"), _("Entry #"), true); + num_tree_col = app_gtkmm_create_tree_view_column(col_index, *treeview, _("#"), _("Entry #"), true); Gtk::TreeModelColumn col_name; model_columns.add(col_name); - num_tree_cols = app_gtkmm_create_tree_view_column(col_name, *treeview, _("Name"), _("Name"), true); + num_tree_col = app_gtkmm_create_tree_view_column(col_name, *treeview, _("Name"), _("Name"), true); treeview->set_search_column(col_name.index()); - auto* cr_name = dynamic_cast(treeview->get_column_cell_renderer(num_tree_cols - 1)); + auto* cr_name = dynamic_cast(treeview->get_column_cell_renderer(num_tree_col)); if (cr_name) cr_name->property_weight() = Pango::WEIGHT_BOLD ; Gtk::TreeModelColumn col_flag_value; model_columns.add(col_flag_value); - num_tree_cols = app_gtkmm_create_tree_view_column(col_flag_value, *treeview, _("Flags"), _("Flags"), false); + num_tree_col = app_gtkmm_create_tree_view_column(col_flag_value, *treeview, _("Flags"), _("Flags"), false); Gtk::TreeModelColumn col_str_values; model_columns.add(col_str_values); - num_tree_cols = app_gtkmm_create_tree_view_column(col_str_values, *treeview, _("Capabilities"), _("Capabilities"), false); + num_tree_col = app_gtkmm_create_tree_view_column(col_str_values, *treeview, _("Capabilities"), _("Capabilities"), false); Gtk::TreeModelColumn col_tooltip; model_columns.add(col_tooltip); @@ -1644,7 +1644,7 @@ WarningLevel GscInfoWindow::fill_ui_capabilities(const std::vectorset_sort_column(col_index, Gtk::SORT_ASCENDING); // default sort treeview->set_model(list_store); - for (int i = 0; i < num_tree_cols; ++i) { + for (int i = 0; i < int(treeview->get_n_columns()); ++i) { Gtk::TreeViewColumn* tcol = treeview->get_column(i); tcol->set_cell_data_func(*(tcol->get_first_cell()), sigc::bind(sigc::ptr_fun(app_list_cell_renderer_func), col_storage)); diff --git a/src/gsc_init.cpp b/src/gsc_init.cpp index 925a1e6..9b78fdd 100644 --- a/src/gsc_init.cpp +++ b/src/gsc_init.cpp @@ -543,9 +543,9 @@ bool app_init_and_loop(int& argc, char**& argv) debug_out_info("app", "Main loop exited.\n"); // close the main window and delete its object - GscMainWindow::destroy(); + delete GscMainWindow::instance(); - GscExecutorLogWindow::destroy(); + delete GscExecutorLogWindow::instance(); // std::cerr << app_get_debug_buffer_str(); // this will output everything that went through libdebug. diff --git a/src/gsc_preferences_window.cpp b/src/gsc_preferences_window.cpp index 1dfde6e..e1fd820 100644 --- a/src/gsc_preferences_window.cpp +++ b/src/gsc_preferences_window.cpp @@ -458,7 +458,7 @@ bool GscPreferencesWindow::on_delete_event([[maybe_unused]] GdkEventAny* e) void GscPreferencesWindow::on_window_cancel_button_clicked() { - destroy(this); + delete this; } @@ -491,7 +491,7 @@ void GscPreferencesWindow::on_window_ok_button_clicked() main_window_->show_prefs_updated_message(); } - destroy(this); + delete this; } @@ -506,7 +506,7 @@ void GscPreferencesWindow::on_window_reset_all_button_clicked() rconfig::clear_config(); import_config(); // close the window, because the user might get the impression that "Cancel" will revert. - destroy(this); + delete this; } } diff --git a/src/gsc_text_window.h b/src/gsc_text_window.h index 2b68c78..75cc98f 100644 --- a/src/gsc_text_window.h +++ b/src/gsc_text_window.h @@ -16,6 +16,7 @@ Copyright: #include #include // GDK_KEY_Escape #include +#include #include "hz/debug.h" #include "hz/fs.h" @@ -72,18 +73,36 @@ class GscTextWindow : public AppBuilderWidget, Ins } - /// Set the text to display + void set_text_from_command(const Glib::ustring& title, const std::string& contents) + { + this->contents_ = contents; // we save it to prevent its mangling through the widget + this->set_text_helper(title, true, true); + } + + void set_text(const Glib::ustring& title, const Glib::ustring& contents, bool save_visible = false, bool use_monospace = false) + { + this->contents_ = contents; + this->set_text_helper(title, save_visible, use_monospace); + } + + + /// Set the text to display + void set_text_helper(const Glib::ustring& title, + bool save_visible = false, bool use_monospace = false) { this->set_title(title + " - " + default_title_); // something - gsmartcontrol - this->contents_ = contents; // we save it to prevent its mangling through the widget - Gtk::TextView* textview = this->template lookup_widget("main_textview"); if (textview) { Glib::RefPtr buffer = textview->get_buffer(); - buffer->set_text(app_output_make_valid(contents)); + + if (std::holds_alternative(contents_)) { + buffer->set_text(app_make_valid_utf8_from_command_output(std::get(contents_))); + } else { + buffer->set_text(std::get(contents_)); + } if (use_monospace) { Glib::RefPtr tag = buffer->create_tag(); @@ -207,7 +226,13 @@ class GscTextWindow : public AppBuilderWidget, Ins file += ".txt"; } - auto ec = hz::fs_file_put_contents(hz::fs::u8path(file), this->contents_.c_str()); + std::string text; + if (std::holds_alternative(contents_)) { + text = std::get(contents_); + } else { + text = std::get(contents_); + } + auto ec = hz::fs_file_put_contents(hz::fs::u8path(file), text); if (ec) { gui_show_error_dialog(_("Cannot save data to file"), ec.message(), this); } @@ -228,14 +253,20 @@ class GscTextWindow : public AppBuilderWidget, Ins /// Button click callback void on_close_window_button_clicked() { - this->destroy(this); + delete this; } private: Glib::ustring default_title_; ///< Window title - Glib::ustring contents_; ///< The text to display + + /// The text to display + std::variant< + std::string, // command output + Glib::ustring // utf-8 text + > contents_; + std::string save_filename_; ///< Default filename for Save As }; diff --git a/src/hz/instance_manager.h b/src/hz/instance_manager.h index a40b8b6..63b8278 100644 --- a/src/hz/instance_manager.h +++ b/src/hz/instance_manager.h @@ -12,15 +12,15 @@ Copyright: #ifndef HZ_INSTANCE_MANAGER_H #define HZ_INSTANCE_MANAGER_H +#include + namespace hz { -/** -Inherit this class to have a single- or multi-instance objects, e.g. windows. -This is a multi-instance specialization. -*/ +/// Inherit this class template to have a single- or multi-instance objects, e.g. windows. +/// This is a multi-instance implementation. template class InstanceManager { protected: @@ -28,49 +28,27 @@ class InstanceManager { /// Can't construct / delete this directly! use create() and destroy() InstanceManager() = default; - /// Can't construct / delete this directly! use create() and destroy() - ~InstanceManager() = default; - public: - /// Non-copyable + /// Deleted InstanceManager(const InstanceManager& other) = delete; - /// Non-copyable + /// Deleted + InstanceManager(const InstanceManager&& other) = delete; + + /// Deleted InstanceManager& operator=(const InstanceManager&) = delete; + /// Deleted + InstanceManager& operator=(const InstanceManager&&) = delete; - /// Create a new instance or return an already created one if single-instance. - /// If single-instance, the call will be serialized. - static Child* create() - { - return new Child(); - } + /// Default + ~InstanceManager() = default; - /// Destroy an instance. \c instance must be passed if using - /// multi-instance object. If single-instance, \c instance has no effect. - /// If single-instance, the call will be serialized. - static void destroy(Child* instance) - { - if (instance) { - delete instance; - } - } - - - protected: - - // We have these functions for multi-instance variant too to - // support transparently switching between them. - - /// Returns true if there is a valid single-instance object. - /// In multi-instance version this always returns false. - static constexpr bool has_single_instance() - { - return false; - } + /// The default multi-instance implementation doesn't support `instance()` + static Child* instance() = delete; }; @@ -82,59 +60,48 @@ template class InstanceManager { protected: + /// Can't construct / delete this directly! use create() and destroy() InstanceManager() = default; - ~InstanceManager() = default; - public: - /// Non-construction-copyable + /// Deleted InstanceManager(const InstanceManager& other) = delete; - /// Non-copyable + /// Deleted + InstanceManager(const InstanceManager&& other) = delete; + + /// Deleted InstanceManager& operator=(const InstanceManager&) = delete; + /// Deleted + InstanceManager& operator=(const InstanceManager&&) = delete; - static Child* create() + /// Default + ~InstanceManager() = default; + + + /// Return a single existing instance of this template instantiation. + /// \return nullptr if no instances were created yet. + static Child* instance() { - if (instance_) // for single-instance objects - return instance_; - - instance_ = new Child(); - return instance_; - } - - - static void destroy() - { - if (instance_) { - delete instance_; - instance_ = nullptr; - } + return instance_.get(); } protected: - static constexpr bool has_single_instance() - { - return (bool)instance_; - } - - static Child* get_single_instance() - { - return instance_; - } - + /// Set the instance. static void set_single_instance(Child* instance) { - instance_ = instance; + instance_.reset(instance); } - // if an object is allowed to have a single instance only, these are needed. - static inline Child* instance_ = nullptr; ///< Single instance pointer + private: + + static inline std::unique_ptr instance_; ///< Single instance pointer };