diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 12a3dbf..9cb6469 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -28,10 +28,10 @@ endif() # Install internal icons if (WIN32) - install(FILES icon_cddvd.png icon_hdd.png + install(FILES icon_optical.png icon_harddisk.png DESTINATION .) else() - install(FILES icon_cddvd.png icon_hdd.png + install(FILES icon_optical.png icon_harddisk.png DESTINATION "${CMAKE_INSTALL_DATADIR}/gsmartcontrol/icons") endif() diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index eb8ae7f..63aa790 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -23,6 +23,7 @@ target_sources(gsmartcontrol PRIVATE gsc_main.cpp gsc_main_window.cpp gsc_main_window.h + gsc_main_window_iconview.cpp gsc_main_window_iconview.h gsc_preferences_window.cpp gsc_preferences_window.h diff --git a/src/gui/gsc_info_window.cpp b/src/gui/gsc_info_window.cpp index 49af717..0b78d94 100644 --- a/src/gui/gsc_info_window.cpp +++ b/src/gui/gsc_info_window.cpp @@ -240,7 +240,7 @@ GscInfoWindow::GscInfoWindow(BaseObjectType* gtkcobj, Glib::RefPtr // --------------- // Create columns of treeviews - columns = std::make_unique(); + columns_ = std::make_unique(); // Set default texts on TextView-s, because glade's "text" property doesn't work @@ -763,8 +763,8 @@ void GscInfoWindow::clear_ui_info(bool clear_tests_too) // Delete columns. We need to do this because otherwise, // the column objects store their old indexes and will break when re-added - columns.reset(); - columns = std::make_unique(); + columns_.reset(); + columns_ = std::make_unique(); } @@ -1076,36 +1076,36 @@ void GscInfoWindow::fill_ui_ata_attributes(const StoragePropertyRepository& prop // ID (int), Name, Flag (hex), Normalized Value (uint8), Worst (uint8), Thresh (uint8), Raw (int64), Type (string), // Updated (string), When Failed (string) - model_columns.add(columns->ata_attribute_table_columns.id); // we can use the column variable by value after this. - num_tree_col = app_gtkmm_create_tree_view_column(columns->ata_attribute_table_columns.id, *treeview, _("ID"), _("Attribute ID"), true); + model_columns.add(columns_->ata_attribute_table_columns.id); // we can use the column variable by value after this. + num_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.id, *treeview, _("ID"), _("Attribute ID"), true); - model_columns.add(columns->ata_attribute_table_columns.displayable_name); - num_tree_col = app_gtkmm_create_tree_view_column(columns->ata_attribute_table_columns.displayable_name, *treeview, + model_columns.add(columns_->ata_attribute_table_columns.displayable_name); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.displayable_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(columns->ata_attribute_table_columns.displayable_name.index()); + treeview->set_search_column(columns_->ata_attribute_table_columns.displayable_name.index()); - model_columns.add(columns->ata_attribute_table_columns.when_failed); - num_tree_col = app_gtkmm_create_tree_view_column(columns->ata_attribute_table_columns.when_failed, *treeview, + model_columns.add(columns_->ata_attribute_table_columns.when_failed); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.when_failed, *treeview, _("Failed"), _("When failed (that is, the normalized value became equal to or less than threshold)"), true, true); - model_columns.add(columns->ata_attribute_table_columns.normalized_value); - num_tree_col = app_gtkmm_create_tree_view_column(columns->ata_attribute_table_columns.normalized_value, *treeview, + model_columns.add(columns_->ata_attribute_table_columns.normalized_value); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.normalized_value, *treeview, C_("value", "Normalized"), _("Normalized value (highly vendor-specific; converted from Raw value by the drive's firmware)"), false); - model_columns.add(columns->ata_attribute_table_columns.worst); - num_tree_col = app_gtkmm_create_tree_view_column(columns->ata_attribute_table_columns.worst, *treeview, + model_columns.add(columns_->ata_attribute_table_columns.worst); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.worst, *treeview, C_("value", "Worst"), _("The worst normalized value recorded for this attribute during the drive's lifetime (with SMART enabled)"), false); - model_columns.add(columns->ata_attribute_table_columns.threshold); - num_tree_col = app_gtkmm_create_tree_view_column(columns->ata_attribute_table_columns.threshold, *treeview, + model_columns.add(columns_->ata_attribute_table_columns.threshold); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.threshold, *treeview, C_("value", "Threshold"), _("Threshold for normalized value. Normalized value should be greater than threshold (unless vendor thinks otherwise)."), false); - model_columns.add(columns->ata_attribute_table_columns.raw); - num_tree_col = app_gtkmm_create_tree_view_column(columns->ata_attribute_table_columns.raw, *treeview, + model_columns.add(columns_->ata_attribute_table_columns.raw); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.raw, *treeview, _("Raw value"), _("Raw value as reported by drive. May or may not be sensible."), false); - model_columns.add(columns->ata_attribute_table_columns.type); - num_tree_col = app_gtkmm_create_tree_view_column(columns->ata_attribute_table_columns.type, *treeview, + model_columns.add(columns_->ata_attribute_table_columns.type); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.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. @@ -1113,8 +1113,8 @@ void GscInfoWindow::fill_ui_ata_attributes(const StoragePropertyRepository& prop // tree_col = app_gtkmm_create_tree_view_column(attribute_table_columns.updated, *treeview, // "Updated", "The attribute is usually updated continuously, or during Offline Data Collection only. This column indicates that.", true); - model_columns.add(columns->ata_attribute_table_columns.flag_value); - num_tree_col = app_gtkmm_create_tree_view_column(columns->ata_attribute_table_columns.flag_value, *treeview, + model_columns.add(columns_->ata_attribute_table_columns.flag_value); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.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" @@ -1125,15 +1125,15 @@ void GscInfoWindow::fill_ui_ata_attributes(const StoragePropertyRepository& prop + _("K: auto-keep") + "\n" + _("+: undocumented bits present"), false); - model_columns.add(columns->ata_attribute_table_columns.tooltip); - treeview->set_tooltip_column(columns->ata_attribute_table_columns.tooltip.index()); + model_columns.add(columns_->ata_attribute_table_columns.tooltip); + treeview->set_tooltip_column(columns_->ata_attribute_table_columns.tooltip.index()); - model_columns.add(columns->ata_attribute_table_columns.storage_property); + model_columns.add(columns_->ata_attribute_table_columns.storage_property); // create a TreeModel (ListStore) Glib::RefPtr list_store = Gtk::ListStore::create(model_columns); - list_store->set_sort_column(columns->ata_attribute_table_columns.id, Gtk::SORT_ASCENDING); // default sort + list_store->set_sort_column(columns_->ata_attribute_table_columns.id, Gtk::SORT_ASCENDING); // default sort treeview->set_model(list_store); for (int i = 0; i < int(treeview->get_n_columns()); ++i) { @@ -1162,20 +1162,20 @@ void GscInfoWindow::fill_ui_ata_attributes(const StoragePropertyRepository& prop const auto& attr = p.get_value(); Gtk::TreeRow row = *(list_store->append()); - row[columns->ata_attribute_table_columns.id] = attr.id; - row[columns->ata_attribute_table_columns.displayable_name] = Glib::Markup::escape_text(p.displayable_name); - row[columns->ata_attribute_table_columns.flag_value] = Glib::Markup::escape_text(attr.flag); // it's a string, not int. - row[columns->ata_attribute_table_columns.normalized_value] = Glib::Markup::escape_text(attr.value.has_value() ? hz::number_to_string_locale(attr.value.value()) : "-"); - row[columns->ata_attribute_table_columns.worst] = Glib::Markup::escape_text(attr.worst.has_value() ? hz::number_to_string_locale(attr.worst.value()) : "-"); - row[columns->ata_attribute_table_columns.threshold] = Glib::Markup::escape_text(attr.threshold.has_value() ? hz::number_to_string_locale(attr.threshold.value()) : "-"); - row[columns->ata_attribute_table_columns.raw] = Glib::Markup::escape_text(attr.format_raw_value()); - row[columns->ata_attribute_table_columns.type] = Glib::Markup::escape_text( + row[columns_->ata_attribute_table_columns.id] = attr.id; + row[columns_->ata_attribute_table_columns.displayable_name] = Glib::Markup::escape_text(p.displayable_name); + row[columns_->ata_attribute_table_columns.flag_value] = Glib::Markup::escape_text(attr.flag); // it's a string, not int. + row[columns_->ata_attribute_table_columns.normalized_value] = Glib::Markup::escape_text(attr.value.has_value() ? hz::number_to_string_locale(attr.value.value()) : "-"); + row[columns_->ata_attribute_table_columns.worst] = Glib::Markup::escape_text(attr.worst.has_value() ? hz::number_to_string_locale(attr.worst.value()) : "-"); + row[columns_->ata_attribute_table_columns.threshold] = Glib::Markup::escape_text(attr.threshold.has_value() ? hz::number_to_string_locale(attr.threshold.value()) : "-"); + row[columns_->ata_attribute_table_columns.raw] = Glib::Markup::escape_text(attr.format_raw_value()); + row[columns_->ata_attribute_table_columns.type] = Glib::Markup::escape_text( AtaStorageAttribute::get_readable_attribute_type_name(attr.attr_type)); // row[attribute_table_columns.updated] = Glib::Markup::escape_text(AtaStorageAttribute::get_update_type_name(attr.update_type)); - row[columns->ata_attribute_table_columns.when_failed] = Glib::Markup::escape_text( + row[columns_->ata_attribute_table_columns.when_failed] = Glib::Markup::escape_text( AtaStorageAttribute::get_readable_fail_time_name(attr.when_failed)); - row[columns->ata_attribute_table_columns.tooltip] = p.get_description(); // markup - row[columns->ata_attribute_table_columns.storage_property] = &p; + row[columns_->ata_attribute_table_columns.tooltip] = p.get_description(); // markup + row[columns_->ata_attribute_table_columns.storage_property] = &p; if (int(p.warning_level) > int(max_tab_warning)) max_tab_warning = p.warning_level; @@ -1200,19 +1200,19 @@ void GscInfoWindow::fill_ui_nvme_attributes(const StoragePropertyRepository& pro Gtk::TreeModelColumnRecord model_columns; [[maybe_unused]] int num_tree_col = 0; - model_columns.add(columns->nvme_attribute_table_columns.displayable_name); - num_tree_col = app_gtkmm_create_tree_view_column(columns->nvme_attribute_table_columns.displayable_name, *treeview, + model_columns.add(columns_->nvme_attribute_table_columns.displayable_name); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->nvme_attribute_table_columns.displayable_name, *treeview, _("Description"), _("Entry description"), true); - treeview->set_search_column(columns->nvme_attribute_table_columns.displayable_name.index()); + treeview->set_search_column(columns_->nvme_attribute_table_columns.displayable_name.index()); - model_columns.add(columns->nvme_attribute_table_columns.value); - num_tree_col = app_gtkmm_create_tree_view_column(columns->nvme_attribute_table_columns.value, *treeview, + model_columns.add(columns_->nvme_attribute_table_columns.value); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->nvme_attribute_table_columns.value, *treeview, _("Value"), _("Value"), false); - model_columns.add(columns->nvme_attribute_table_columns.tooltip); - treeview->set_tooltip_column(columns->nvme_attribute_table_columns.tooltip.index()); + model_columns.add(columns_->nvme_attribute_table_columns.tooltip); + treeview->set_tooltip_column(columns_->nvme_attribute_table_columns.tooltip.index()); - model_columns.add(columns->nvme_attribute_table_columns.storage_property); + model_columns.add(columns_->nvme_attribute_table_columns.storage_property); // create a TreeModel (ListStore) @@ -1235,10 +1235,10 @@ void GscInfoWindow::fill_ui_nvme_attributes(const StoragePropertyRepository& pro Gtk::TreeRow row = *(list_store->append()); const auto& value = p.format_value(); - row[columns->nvme_attribute_table_columns.displayable_name] = Glib::Markup::escape_text(p.displayable_name); - row[columns->nvme_attribute_table_columns.value] = Glib::Markup::escape_text(value); - row[columns->nvme_attribute_table_columns.tooltip] = p.get_description(); // markup - row[columns->nvme_attribute_table_columns.storage_property] = &p; + row[columns_->nvme_attribute_table_columns.displayable_name] = Glib::Markup::escape_text(p.displayable_name); + row[columns_->nvme_attribute_table_columns.value] = Glib::Markup::escape_text(value); + row[columns_->nvme_attribute_table_columns.tooltip] = p.get_description(); // markup + row[columns_->nvme_attribute_table_columns.storage_property] = &p; if (int(p.warning_level) > int(max_tab_warning)) max_tab_warning = p.warning_level; @@ -1262,17 +1262,17 @@ void GscInfoWindow::fill_ui_statistics(const StoragePropertyRepository& property Gtk::TreeModelColumnRecord model_columns; [[maybe_unused]] int num_tree_col = 0; - model_columns.add(columns->statistics_table_columns.displayable_name); - num_tree_col = app_gtkmm_create_tree_view_column(columns->statistics_table_columns.displayable_name, *treeview, + model_columns.add(columns_->statistics_table_columns.displayable_name); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->statistics_table_columns.displayable_name, *treeview, _("Description"), _("Entry description"), true); - treeview->set_search_column(columns->statistics_table_columns.displayable_name.index()); + treeview->set_search_column(columns_->statistics_table_columns.displayable_name.index()); - model_columns.add(columns->statistics_table_columns.value); - num_tree_col = app_gtkmm_create_tree_view_column(columns->statistics_table_columns.value, *treeview, + model_columns.add(columns_->statistics_table_columns.value); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->statistics_table_columns.value, *treeview, _("Value"), Glib::ustring::compose(_("Value (can be normalized if '%1' flag is present)"), "N"), false); - model_columns.add(columns->statistics_table_columns.flags); - num_tree_col = app_gtkmm_create_tree_view_column(columns->statistics_table_columns.flags, *treeview, + model_columns.add(columns_->statistics_table_columns.flags); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->statistics_table_columns.flags, *treeview, _("Flags"), _("Flags") + "\n\n"s + _("V: valid") + "\n" + _("N: value is normalized") + "\n" @@ -1280,14 +1280,14 @@ void GscInfoWindow::fill_ui_statistics(const StoragePropertyRepository& property + _("C: monitored condition met") + "\n" // Related to DSN? From the specification, it looks like something user-controllable. + _("+: undocumented bits present"), false); - model_columns.add(columns->statistics_table_columns.page_offset); - num_tree_col = app_gtkmm_create_tree_view_column(columns->statistics_table_columns.page_offset, *treeview, + model_columns.add(columns_->statistics_table_columns.page_offset); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->statistics_table_columns.page_offset, *treeview, _("Page, Offset"), _("Page and offset of the entry"), false); - model_columns.add(columns->statistics_table_columns.tooltip); - treeview->set_tooltip_column(columns->statistics_table_columns.tooltip.index()); + model_columns.add(columns_->statistics_table_columns.tooltip); + treeview->set_tooltip_column(columns_->statistics_table_columns.tooltip.index()); - model_columns.add(columns->statistics_table_columns.storage_property); + model_columns.add(columns_->statistics_table_columns.storage_property); // create a TreeModel (ListStore) @@ -1320,13 +1320,13 @@ void GscInfoWindow::fill_ui_statistics(const StoragePropertyRepository& property Gtk::TreeRow row = *(list_store->append()); const auto& st = p.get_value(); - row[columns->statistics_table_columns.displayable_name] = Glib::Markup::escape_text(st.is_header ? p.displayable_name : (" " + p.displayable_name)); - row[columns->statistics_table_columns.value] = Glib::Markup::escape_text(st.format_value()); - row[columns->statistics_table_columns.flags] = Glib::Markup::escape_text(st.flags); // it's a string, not int. - row[columns->statistics_table_columns.page_offset] = Glib::Markup::escape_text(st.is_header ? std::string() + row[columns_->statistics_table_columns.displayable_name] = Glib::Markup::escape_text(st.is_header ? p.displayable_name : (" " + p.displayable_name)); + row[columns_->statistics_table_columns.value] = Glib::Markup::escape_text(st.format_value()); + row[columns_->statistics_table_columns.flags] = Glib::Markup::escape_text(st.flags); // it's a string, not int. + row[columns_->statistics_table_columns.page_offset] = Glib::Markup::escape_text(st.is_header ? std::string() : hz::string_sprintf("0x%02x, 0x%03x", int(st.page), int(st.offset))); - row[columns->statistics_table_columns.tooltip] = p.get_description(); // markup - row[columns->statistics_table_columns.storage_property] = &p; + row[columns_->statistics_table_columns.tooltip] = p.get_description(); // markup + row[columns_->statistics_table_columns.storage_property] = &p; if (int(p.warning_level) > int(max_tab_warning)) max_tab_warning = p.warning_level; @@ -1438,40 +1438,40 @@ void GscInfoWindow::fill_ui_self_test_log(const StoragePropertyRepository& prope // Test num., Type, Status, % Completed, Lifetime hours, LBA of the first error - model_columns.add(columns->self_test_log_table_columns.log_entry_index); // we can use the column variable by value after this. - num_tree_col = app_gtkmm_create_tree_view_column(columns->self_test_log_table_columns.log_entry_index, *treeview, + model_columns.add(columns_->self_test_log_table_columns.log_entry_index); // we can use the column variable by value after this. + num_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.log_entry_index, *treeview, _("Test #"), _("Test # (greater may mean newer or older depending on drive model)"), true); - model_columns.add(columns->self_test_log_table_columns.type); - num_tree_col = app_gtkmm_create_tree_view_column(columns->self_test_log_table_columns.type, *treeview, + model_columns.add(columns_->self_test_log_table_columns.type); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.type, *treeview, _("Type"), _("Type of the test performed"), true); - treeview->set_search_column(columns->self_test_log_table_columns.type.index()); + treeview->set_search_column(columns_->self_test_log_table_columns.type.index()); - model_columns.add(columns->self_test_log_table_columns.status); - num_tree_col = app_gtkmm_create_tree_view_column(columns->self_test_log_table_columns.status, *treeview, + model_columns.add(columns_->self_test_log_table_columns.status); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.status, *treeview, _("Status"), _("Test completion status"), true); - model_columns.add(columns->self_test_log_table_columns.percent); - num_tree_col = app_gtkmm_create_tree_view_column(columns->self_test_log_table_columns.percent, *treeview, + model_columns.add(columns_->self_test_log_table_columns.percent); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.percent, *treeview, _("% Completed"), _("Percentage of the test completed. Instantly-aborted tests have 10%, while unsupported ones may have 100%."), true, false, true); - model_columns.add(columns->self_test_log_table_columns.hours); - num_tree_col = app_gtkmm_create_tree_view_column(columns->self_test_log_table_columns.hours, *treeview, + model_columns.add(columns_->self_test_log_table_columns.hours); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.hours, *treeview, _("Lifetime hours"), _("During which hour of the drive's (powered on) lifetime did the test complete (or abort)"), true); - model_columns.add(columns->self_test_log_table_columns.lba); - num_tree_col = app_gtkmm_create_tree_view_column(columns->self_test_log_table_columns.lba, *treeview, + model_columns.add(columns_->self_test_log_table_columns.lba); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.lba, *treeview, _("LBA of the first error"), _("LBA of the first error (if an LBA-related error happened)"), true); - model_columns.add(columns->self_test_log_table_columns.tooltip); - treeview->set_tooltip_column(columns->self_test_log_table_columns.tooltip.index()); + model_columns.add(columns_->self_test_log_table_columns.tooltip); + treeview->set_tooltip_column(columns_->self_test_log_table_columns.tooltip.index()); - model_columns.add(columns->self_test_log_table_columns.storage_property); + model_columns.add(columns_->self_test_log_table_columns.storage_property); // create a TreeModel (ListStore) Glib::RefPtr list_store = Gtk::ListStore::create(model_columns); - list_store->set_sort_column(columns->self_test_log_table_columns.log_entry_index, Gtk::SORT_ASCENDING); // default sort + list_store->set_sort_column(columns_->self_test_log_table_columns.log_entry_index, Gtk::SORT_ASCENDING); // default sort treeview->set_model(list_store); for (int i = 0; i < int(treeview->get_n_columns()); ++i) { @@ -1510,25 +1510,25 @@ void GscInfoWindow::fill_ui_self_test_log(const StoragePropertyRepository& prope if (p.is_value_type()) { const auto& entry = p.get_value(); - row[columns->self_test_log_table_columns.log_entry_index] = entry.test_num; - row[columns->self_test_log_table_columns.type] = Glib::Markup::escape_text(entry.type); - row[columns->self_test_log_table_columns.status] = Glib::Markup::escape_text(entry.get_readable_status()); - row[columns->self_test_log_table_columns.percent] = Glib::Markup::escape_text(hz::number_to_string_locale(100 - entry.remaining_percent) + "%"); - row[columns->self_test_log_table_columns.hours] = Glib::Markup::escape_text(hz::number_to_string_locale(entry.lifetime_hours)); - row[columns->self_test_log_table_columns.lba] = Glib::Markup::escape_text(entry.lba_of_first_error); + row[columns_->self_test_log_table_columns.log_entry_index] = entry.test_num; + row[columns_->self_test_log_table_columns.type] = Glib::Markup::escape_text(entry.type); + row[columns_->self_test_log_table_columns.status] = Glib::Markup::escape_text(entry.get_readable_status()); + row[columns_->self_test_log_table_columns.percent] = Glib::Markup::escape_text(hz::number_to_string_locale(100 - entry.remaining_percent) + "%"); + row[columns_->self_test_log_table_columns.hours] = Glib::Markup::escape_text(hz::number_to_string_locale(entry.lifetime_hours)); + row[columns_->self_test_log_table_columns.lba] = Glib::Markup::escape_text(entry.lba_of_first_error); } else if (p.is_value_type()) { const auto& entry = p.get_value(); - row[columns->self_test_log_table_columns.log_entry_index] = entry.test_num; - row[columns->self_test_log_table_columns.type] = Glib::Markup::escape_text(NvmeSelfTestTypeExt::get_displayable_name(entry.type)); - row[columns->self_test_log_table_columns.status] = Glib::Markup::escape_text(NvmeSelfTestResultTypeExt::get_displayable_name(entry.result)); - row[columns->self_test_log_table_columns.hours] = Glib::Markup::escape_text(hz::number_to_string_locale(entry.power_on_hours)); - row[columns->self_test_log_table_columns.lba] = Glib::Markup::escape_text(entry.lba.has_value() ? hz::number_to_string_locale(entry.lba.value()) : std::string("-")); + row[columns_->self_test_log_table_columns.log_entry_index] = entry.test_num; + row[columns_->self_test_log_table_columns.type] = Glib::Markup::escape_text(NvmeSelfTestTypeExt::get_displayable_name(entry.type)); + row[columns_->self_test_log_table_columns.status] = Glib::Markup::escape_text(NvmeSelfTestResultTypeExt::get_displayable_name(entry.result)); + row[columns_->self_test_log_table_columns.hours] = Glib::Markup::escape_text(hz::number_to_string_locale(entry.power_on_hours)); + row[columns_->self_test_log_table_columns.lba] = Glib::Markup::escape_text(entry.lba.has_value() ? hz::number_to_string_locale(entry.lba.value()) : std::string("-")); } // There are no descriptions in self-test log entries, so don't display // "No description available" for all of them. - // row[columns->self_test_log_table_columns.tooltip] = p.get_description(); - row[columns->self_test_log_table_columns.storage_property] = &p; + // row[columns_->self_test_log_table_columns.tooltip] = p.get_description(); + row[columns_->self_test_log_table_columns.storage_property] = &p; if (int(p.warning_level) > int(max_tab_warning)) max_tab_warning = p.warning_level; @@ -1558,37 +1558,37 @@ void GscInfoWindow::fill_ui_ata_error_log(const StoragePropertyRepository& prope // Error Number, Lifetime Hours, State, Type, Details, [tooltips] - model_columns.add(columns->error_log_table_columns.log_entry_index); // we can use the column variable by value after this. - num_tree_col = app_gtkmm_create_tree_view_column(columns->error_log_table_columns.log_entry_index, *treeview, + model_columns.add(columns_->error_log_table_columns.log_entry_index); // we can use the column variable by value after this. + num_tree_col = app_gtkmm_create_tree_view_column(columns_->error_log_table_columns.log_entry_index, *treeview, _("Error #"), _("Error # in the error log (greater means newer)"), true); - model_columns.add(columns->error_log_table_columns.hours); - num_tree_col = app_gtkmm_create_tree_view_column(columns->error_log_table_columns.hours, *treeview, + model_columns.add(columns_->error_log_table_columns.hours); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->error_log_table_columns.hours, *treeview, _("Lifetime hours"), _("During which hour of the drive's (powered on) lifetime did the error happen."), true); - model_columns.add(columns->error_log_table_columns.state); - num_tree_col = app_gtkmm_create_tree_view_column(columns->error_log_table_columns.state, *treeview, + model_columns.add(columns_->error_log_table_columns.state); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->error_log_table_columns.state, *treeview, C_("power", "State"), _("Power state of the drive when the error occurred"), false); - model_columns.add(columns->error_log_table_columns.lba); - num_tree_col = app_gtkmm_create_tree_view_column(columns->error_log_table_columns.lba, *treeview, + model_columns.add(columns_->error_log_table_columns.lba); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->error_log_table_columns.lba, *treeview, _("LBA"), _("LBA Address"), true); - model_columns.add(columns->error_log_table_columns.details); - num_tree_col = app_gtkmm_create_tree_view_column(columns->error_log_table_columns.details, *treeview, + model_columns.add(columns_->error_log_table_columns.details); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->error_log_table_columns.details, *treeview, _("Details"), _("Additional details"), true); - model_columns.add(columns->error_log_table_columns.tooltip); - treeview->set_tooltip_column(columns->error_log_table_columns.tooltip.index()); + model_columns.add(columns_->error_log_table_columns.tooltip); + treeview->set_tooltip_column(columns_->error_log_table_columns.tooltip.index()); - model_columns.add(columns->error_log_table_columns.storage_property); + model_columns.add(columns_->error_log_table_columns.storage_property); - model_columns.add(columns->error_log_table_columns.mark_name); + model_columns.add(columns_->error_log_table_columns.mark_name); // create a TreeModel (ListStore) Glib::RefPtr list_store = Gtk::ListStore::create(model_columns); - list_store->set_sort_column(columns->error_log_table_columns.log_entry_index, Gtk::SORT_DESCENDING); // default sort + list_store->set_sort_column(columns_->error_log_table_columns.log_entry_index, Gtk::SORT_DESCENDING); // default sort treeview->set_model(list_store); for (int i = 0; i < int(treeview->get_n_columns()); ++i) { @@ -1624,7 +1624,7 @@ void GscInfoWindow::fill_ui_ata_error_log(const StoragePropertyRepository& prope // Set marks so we can scroll to them if (!error_log_row_selected_conn.connected()) { // avoid double-connect error_log_row_selected_conn = treeview->get_selection()->signal_changed().connect( - sigc::bind(sigc::bind(sigc::ptr_fun(on_error_log_treeview_row_selected), columns->error_log_table_columns.mark_name), this)); + sigc::bind(sigc::bind(sigc::ptr_fun(on_error_log_treeview_row_selected), columns_->error_log_table_columns.mark_name), this)); } Gtk::TextIter titer = buffer->begin(); @@ -1652,20 +1652,20 @@ void GscInfoWindow::fill_ui_ata_error_log(const StoragePropertyRepository& prope const auto& eb = p.get_value(); Gtk::TreeRow row = *(list_store->append()); - row[columns->error_log_table_columns.log_entry_index] = eb.error_num; - row[columns->error_log_table_columns.hours] = Glib::Markup::escape_text(hz::number_to_string_locale(eb.lifetime_hours)); - row[columns->error_log_table_columns.state] = Glib::Markup::escape_text(eb.device_state); + row[columns_->error_log_table_columns.log_entry_index] = eb.error_num; + row[columns_->error_log_table_columns.hours] = Glib::Markup::escape_text(hz::number_to_string_locale(eb.lifetime_hours)); + row[columns_->error_log_table_columns.state] = Glib::Markup::escape_text(eb.device_state); std::string details_str = eb.type_more_info; // parsed in JSON if (details_str.empty()) { details_str = AtaStorageErrorBlock::format_readable_error_types(eb.reported_types); // parsed in Text } - row[columns->error_log_table_columns.lba] = Glib::Markup::escape_text(hz::number_to_string_locale(eb.lba)); - row[columns->error_log_table_columns.details] = Glib::Markup::escape_text(details_str.empty() ? "-" : details_str); - row[columns->error_log_table_columns.tooltip] = p.get_description(); // markup - row[columns->error_log_table_columns.storage_property] = &p; - row[columns->error_log_table_columns.mark_name] = Glib::ustring::compose(_("Error %1"), eb.error_num); + row[columns_->error_log_table_columns.lba] = Glib::Markup::escape_text(hz::number_to_string_locale(eb.lba)); + row[columns_->error_log_table_columns.details] = Glib::Markup::escape_text(details_str.empty() ? "-" : details_str); + row[columns_->error_log_table_columns.tooltip] = p.get_description(); // markup + row[columns_->error_log_table_columns.storage_property] = &p; + row[columns_->error_log_table_columns.mark_name] = Glib::ustring::compose(_("Error %1"), eb.error_num); } if (int(p.warning_level) > int(max_tab_warning)) @@ -1806,32 +1806,32 @@ WarningLevel GscInfoWindow::fill_ui_capabilities(const StoragePropertyRepository // N, Name, Flag, Capabilities, [tooltips] - model_columns.add(columns->capabilities_table_columns.entry_index); // we can use the column variable by value after this. - num_tree_col = app_gtkmm_create_tree_view_column(columns->capabilities_table_columns.entry_index, *treeview, _("#"), _("Entry #"), true); + model_columns.add(columns_->capabilities_table_columns.entry_index); // we can use the column variable by value after this. + num_tree_col = app_gtkmm_create_tree_view_column(columns_->capabilities_table_columns.entry_index, *treeview, _("#"), _("Entry #"), true); - model_columns.add(columns->capabilities_table_columns.name); - num_tree_col = app_gtkmm_create_tree_view_column(columns->capabilities_table_columns.name, *treeview, _("Name"), _("Name"), true); - treeview->set_search_column(columns->capabilities_table_columns.name.index()); + model_columns.add(columns_->capabilities_table_columns.name); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->capabilities_table_columns.name, *treeview, _("Name"), _("Name"), true); + treeview->set_search_column(columns_->capabilities_table_columns.name.index()); - model_columns.add(columns->capabilities_table_columns.flag_value); - num_tree_col = app_gtkmm_create_tree_view_column(columns->capabilities_table_columns.flag_value, *treeview, _("Flags"), _("Flags"), false); + model_columns.add(columns_->capabilities_table_columns.flag_value); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->capabilities_table_columns.flag_value, *treeview, _("Flags"), _("Flags"), false); // treeview->get_column(num_tree_col - 1)->set_visible(false); // - model_columns.add(columns->capabilities_table_columns.str_values); - num_tree_col = app_gtkmm_create_tree_view_column(columns->capabilities_table_columns.str_values, *treeview, _("Capabilities"), _("Capabilities"), false); + model_columns.add(columns_->capabilities_table_columns.str_values); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->capabilities_table_columns.str_values, *treeview, _("Capabilities"), _("Capabilities"), false); - model_columns.add(columns->capabilities_table_columns.value); - num_tree_col = app_gtkmm_create_tree_view_column(columns->capabilities_table_columns.value, *treeview, _("Value"), _("Value"), false); + model_columns.add(columns_->capabilities_table_columns.value); + num_tree_col = app_gtkmm_create_tree_view_column(columns_->capabilities_table_columns.value, *treeview, _("Value"), _("Value"), false); - model_columns.add(columns->capabilities_table_columns.tooltip); - treeview->set_tooltip_column(columns->capabilities_table_columns.tooltip.index()); + model_columns.add(columns_->capabilities_table_columns.tooltip); + treeview->set_tooltip_column(columns_->capabilities_table_columns.tooltip.index()); - model_columns.add(columns->capabilities_table_columns.storage_property); + model_columns.add(columns_->capabilities_table_columns.storage_property); // create a TreeModel (ListStore) Glib::RefPtr list_store = Gtk::ListStore::create(model_columns); - list_store->set_sort_column(columns->capabilities_table_columns.entry_index, Gtk::SORT_ASCENDING); // default sort + list_store->set_sort_column(columns_->capabilities_table_columns.entry_index, Gtk::SORT_ASCENDING); // default sort treeview->set_model(list_store); for (int i = 0; i < int(treeview->get_n_columns()); ++i) { @@ -1863,13 +1863,13 @@ WarningLevel GscInfoWindow::fill_ui_capabilities(const StoragePropertyRepository } Gtk::TreeRow row = *(list_store->append()); - row[columns->capabilities_table_columns.entry_index] = index; - row[columns->capabilities_table_columns.name] = Glib::Markup::escape_text(p.displayable_name); - row[columns->capabilities_table_columns.flag_value] = Glib::Markup::escape_text(flag_value.empty() ? "-" : flag_value); - row[columns->capabilities_table_columns.str_values] = Glib::Markup::escape_text(str_value); - row[columns->capabilities_table_columns.value] = Glib::Markup::escape_text(str_value); - row[columns->capabilities_table_columns.tooltip] = p.get_description(); // markup - row[columns->capabilities_table_columns.storage_property] = &p; + row[columns_->capabilities_table_columns.entry_index] = index; + row[columns_->capabilities_table_columns.name] = Glib::Markup::escape_text(p.displayable_name); + row[columns_->capabilities_table_columns.flag_value] = Glib::Markup::escape_text(flag_value.empty() ? "-" : flag_value); + row[columns_->capabilities_table_columns.str_values] = Glib::Markup::escape_text(str_value); + row[columns_->capabilities_table_columns.value] = Glib::Markup::escape_text(str_value); + row[columns_->capabilities_table_columns.tooltip] = p.get_description(); // markup + row[columns_->capabilities_table_columns.storage_property] = &p; if (int(p.warning_level) > int(max_tab_warning)) max_tab_warning = p.warning_level; @@ -2040,7 +2040,7 @@ inline void cell_renderer_set_warning_fg_bg(Gtk::CellRendererText* crt, const St void GscInfoWindow::cell_renderer_for_ata_attributes(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter, [[maybe_unused]] int column_index) const { - const StorageProperty* prop = (*iter)[columns->ata_attribute_table_columns.storage_property]; + const StorageProperty* prop = (*iter)[columns_->ata_attribute_table_columns.storage_property]; if (!prop) { return; } @@ -2049,17 +2049,17 @@ void GscInfoWindow::cell_renderer_for_ata_attributes(Gtk::CellRenderer* cr, if (auto* crt = dynamic_cast(cr)) { cell_renderer_set_warning_fg_bg(crt, *prop); - if (column_index == columns->ata_attribute_table_columns.displayable_name.index()) { + if (column_index == columns_->ata_attribute_table_columns.displayable_name.index()) { crt->property_weight() = Pango::WEIGHT_BOLD; } - if (column_index == columns->ata_attribute_table_columns.type.index()) { + if (column_index == columns_->ata_attribute_table_columns.type.index()) { if (attribute.attr_type == AtaStorageAttribute::AttributeType::Prefail) { crt->property_weight() = Pango::WEIGHT_BOLD; } else { // reset to default value if reloading crt->property_weight().reset_value(); } } - if (column_index == columns->ata_attribute_table_columns.when_failed.index()) { + if (column_index == columns_->ata_attribute_table_columns.when_failed.index()) { if (attribute.when_failed != AtaStorageAttribute::FailTime::None) { crt->property_weight() = Pango::WEIGHT_BOLD; } else { // reset to default value if reloading @@ -2069,19 +2069,19 @@ void GscInfoWindow::cell_renderer_for_ata_attributes(Gtk::CellRenderer* cr, } // Monospace, align all numeric values - if (column_index == columns->ata_attribute_table_columns.normalized_value.index() - || column_index == columns->ata_attribute_table_columns.worst.index() - || column_index == columns->ata_attribute_table_columns.threshold.index() - || column_index == columns->ata_attribute_table_columns.raw.index() ) { + if (column_index == columns_->ata_attribute_table_columns.normalized_value.index() + || column_index == columns_->ata_attribute_table_columns.worst.index() + || column_index == columns_->ata_attribute_table_columns.threshold.index() + || column_index == columns_->ata_attribute_table_columns.raw.index() ) { crt->property_family() = "Monospace"; crt->property_xalign() = 1.; // right-align } - if (column_index == columns->ata_attribute_table_columns.id.index() - || column_index == columns->ata_attribute_table_columns.flag_value.index()) { + if (column_index == columns_->ata_attribute_table_columns.id.index() + || column_index == columns_->ata_attribute_table_columns.flag_value.index()) { crt->property_family() = "Monospace"; crt->property_xalign() = .5; // center-align } - if (column_index == columns->ata_attribute_table_columns.type.index()) { + if (column_index == columns_->ata_attribute_table_columns.type.index()) { crt->property_xalign() = .5; // center-align } } @@ -2092,7 +2092,7 @@ void GscInfoWindow::cell_renderer_for_ata_attributes(Gtk::CellRenderer* cr, void GscInfoWindow::cell_renderer_for_nvme_attributes(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter, int column_index) const { - const StorageProperty* prop = (*iter)[this->columns->nvme_attribute_table_columns.storage_property]; + const StorageProperty* prop = (*iter)[this->columns_->nvme_attribute_table_columns.storage_property]; if (!prop) { return; } @@ -2100,11 +2100,11 @@ void GscInfoWindow::cell_renderer_for_nvme_attributes(Gtk::CellRenderer* cr, if (auto* crt = dynamic_cast(cr)) { cell_renderer_set_warning_fg_bg(crt, *prop); - if (column_index == columns->nvme_attribute_table_columns.displayable_name.index()) { + if (column_index == columns_->nvme_attribute_table_columns.displayable_name.index()) { crt->property_weight() = Pango::WEIGHT_BOLD; } - if (column_index == columns->nvme_attribute_table_columns.value.index()) { + if (column_index == columns_->nvme_attribute_table_columns.value.index()) { crt->property_family() = "Monospace"; crt->property_xalign() = 1.; // right-align } @@ -2116,7 +2116,7 @@ void GscInfoWindow::cell_renderer_for_nvme_attributes(Gtk::CellRenderer* cr, void GscInfoWindow::cell_renderer_for_statistics(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter, [[maybe_unused]] int column_index) const { - const StorageProperty* prop = (*iter)[this->columns->statistics_table_columns.storage_property]; + const StorageProperty* prop = (*iter)[this->columns_->statistics_table_columns.storage_property]; if (!prop) { return; } @@ -2132,12 +2132,12 @@ void GscInfoWindow::cell_renderer_for_statistics(Gtk::CellRenderer* cr, } // Monospace, align all numeric values - if (column_index == columns->statistics_table_columns.value.index()) { + if (column_index == columns_->statistics_table_columns.value.index()) { crt->property_family() = "Monospace"; crt->property_xalign() = 1.; // right-align } - if (column_index == columns->statistics_table_columns.flags.index() - || column_index == columns->statistics_table_columns.page_offset.index() ) { + if (column_index == columns_->statistics_table_columns.flags.index() + || column_index == columns_->statistics_table_columns.page_offset.index() ) { crt->property_family() = "Monospace"; crt->property_xalign() = .5; // center-align } @@ -2149,7 +2149,7 @@ void GscInfoWindow::cell_renderer_for_statistics(Gtk::CellRenderer* cr, void GscInfoWindow::cell_renderer_for_self_test_log(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter, [[maybe_unused]] int column_index) const { - const StorageProperty* prop = (*iter)[this->columns->self_test_log_table_columns.storage_property]; + const StorageProperty* prop = (*iter)[this->columns_->self_test_log_table_columns.storage_property]; if (!prop) { return; } @@ -2157,22 +2157,22 @@ void GscInfoWindow::cell_renderer_for_self_test_log(Gtk::CellRenderer* cr, if (auto* crt = dynamic_cast(cr)) { cell_renderer_set_warning_fg_bg(crt, *prop); - if (column_index == columns->self_test_log_table_columns.log_entry_index.index()) { + if (column_index == columns_->self_test_log_table_columns.log_entry_index.index()) { crt->property_weight() = Pango::WEIGHT_BOLD; } // Monospace, align all numeric values - if (column_index == columns->self_test_log_table_columns.log_entry_index.index() - || column_index == columns->self_test_log_table_columns.percent.index() - || column_index == columns->self_test_log_table_columns.hours.index() ) { + if (column_index == columns_->self_test_log_table_columns.log_entry_index.index() + || column_index == columns_->self_test_log_table_columns.percent.index() + || column_index == columns_->self_test_log_table_columns.hours.index() ) { crt->property_family() = "Monospace"; crt->property_xalign() = 1.; // right-align } - if (column_index == columns->self_test_log_table_columns.log_entry_index.index()) { + if (column_index == columns_->self_test_log_table_columns.log_entry_index.index()) { crt->property_family() = "Monospace"; crt->property_xalign() = .5; // center-align } - if (column_index == columns->self_test_log_table_columns.lba.index()) { + if (column_index == columns_->self_test_log_table_columns.lba.index()) { crt->property_family() = "Monospace"; } } @@ -2183,7 +2183,7 @@ void GscInfoWindow::cell_renderer_for_self_test_log(Gtk::CellRenderer* cr, void GscInfoWindow::cell_renderer_for_error_log(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter, [[maybe_unused]] int column_index) const { - const StorageProperty* prop = (*iter)[this->columns->error_log_table_columns.storage_property]; + const StorageProperty* prop = (*iter)[this->columns_->error_log_table_columns.storage_property]; if (!prop) { return; } @@ -2191,16 +2191,16 @@ void GscInfoWindow::cell_renderer_for_error_log(Gtk::CellRenderer* cr, if (auto* crt = dynamic_cast(cr)) { cell_renderer_set_warning_fg_bg(crt, *prop); - if (column_index == columns->error_log_table_columns.log_entry_index.index()) { + if (column_index == columns_->error_log_table_columns.log_entry_index.index()) { crt->property_weight() = Pango::WEIGHT_BOLD; } // Monospace, align all numeric values - if (column_index == columns->error_log_table_columns.log_entry_index.index()) { + if (column_index == columns_->error_log_table_columns.log_entry_index.index()) { crt->property_family() = "Monospace"; crt->property_xalign() = .5; // center-align } - if (column_index == columns->error_log_table_columns.hours.index()) { + if (column_index == columns_->error_log_table_columns.hours.index()) { crt->property_family() = "Monospace"; crt->property_xalign() = 1.; // right-align } @@ -2212,7 +2212,7 @@ void GscInfoWindow::cell_renderer_for_error_log(Gtk::CellRenderer* cr, void GscInfoWindow::cell_renderer_for_capabilities(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter, [[maybe_unused]] int column_index) const { - const StorageProperty* prop = (*iter)[this->columns->capabilities_table_columns.storage_property]; + const StorageProperty* prop = (*iter)[this->columns_->capabilities_table_columns.storage_property]; if (!prop) { return; } @@ -2220,13 +2220,13 @@ void GscInfoWindow::cell_renderer_for_capabilities(Gtk::CellRenderer* cr, if (auto* crt = dynamic_cast(cr)) { cell_renderer_set_warning_fg_bg(crt, *prop); - if (column_index == columns->capabilities_table_columns.name.index()) { + if (column_index == columns_->capabilities_table_columns.name.index()) { crt->property_weight() = Pango::WEIGHT_BOLD; } // Monospace, align all numeric values - if (column_index == columns->capabilities_table_columns.entry_index.index() - || column_index == columns->capabilities_table_columns.flag_value.index() ) { + if (column_index == columns_->capabilities_table_columns.entry_index.index() + || column_index == columns_->capabilities_table_columns.flag_value.index() ) { crt->property_family() = "Monospace"; crt->property_xalign() = .5; // center-align } diff --git a/src/gui/gsc_info_window.h b/src/gui/gsc_info_window.h index ee45e08..342bbcb 100644 --- a/src/gui/gsc_info_window.h +++ b/src/gui/gsc_info_window.h @@ -306,7 +306,7 @@ class GscInfoWindow : public AppBuilderWidget { Glib::RefPtr test_combo_model; ///< Combobox model /// Columns of treeviews inside GscInfoWindow - std::unique_ptr columns; + std::unique_ptr columns_; }; diff --git a/src/gui/gsc_main_window.cpp b/src/gui/gsc_main_window.cpp index 60775fc..e27032d 100644 --- a/src/gui/gsc_main_window.cpp +++ b/src/gui/gsc_main_window.cpp @@ -11,6 +11,7 @@ Copyright: #include #include +#include #include #include #include @@ -42,6 +43,7 @@ Copyright: #include "gsc_add_device_window.h" #include "applib/command_executor_factory.h" #include "gsc_startup_settings.h" +#include "build_config.h" @@ -56,8 +58,8 @@ GscMainWindow::GscMainWindow(BaseObjectType* gtkcobj, Glib::RefPtr // Size { - int def_size_w = rconfig::get_data("gui/main_window/default_size_w"); - int def_size_h = rconfig::get_data("gui/main_window/default_size_h"); + const int def_size_w = rconfig::get_data("gui/main_window/default_size_w"); + const int def_size_h = rconfig::get_data("gui/main_window/default_size_h"); if (def_size_w > 0 && def_size_h > 0) { set_default_size(def_size_w, def_size_h); } @@ -68,8 +70,8 @@ GscMainWindow::GscMainWindow(BaseObjectType* gtkcobj, Glib::RefPtr // Position (after the window has been shown) { - int pos_x = rconfig::get_data("gui/main_window/default_pos_x"); - int pos_y = rconfig::get_data("gui/main_window/default_pos_y"); + const int pos_x = rconfig::get_data("gui/main_window/default_pos_x"); + const int pos_y = rconfig::get_data("gui/main_window/default_pos_y"); if (pos_x > 0 && pos_y > 0) { // to avoid situations where positions are not supported this->move(pos_x, pos_y); } @@ -85,7 +87,7 @@ GscMainWindow::GscMainWindow(BaseObjectType* gtkcobj, Glib::RefPtr bool show_output_button = true; do { - std::string smartctl_binary = hz::fs_path_to_string(get_smartctl_binary()); + const std::string smartctl_binary = hz::fs_path_to_string(get_smartctl_binary()); // Don't use default options here - they are used when invoked // with a device option. @@ -111,7 +113,7 @@ GscMainWindow::GscMainWindow(BaseObjectType* gtkcobj, Glib::RefPtr break; } - std::string output = ex.get_stdout_str(); + const std::string output = ex.get_stdout_str(); if (output.empty()) { error_msg = _("Smartctl returned an empty output."); break; @@ -137,7 +139,7 @@ GscMainWindow::GscMainWindow(BaseObjectType* gtkcobj, Glib::RefPtr } while (false); - bool smartctl_valid = error_msg.empty(); + const bool smartctl_valid = error_msg.empty(); if (!smartctl_valid) { gsc_executor_error_dialog_show(_("There was an error while executing smartctl"), error_msg + "\n\n" + _("Please specify the correct smartctl binary in Preferences.") + "", @@ -237,7 +239,7 @@ bool GscMainWindow::create_widgets() // --------------------------------- Action widgets - static Glib::ustring ui_info = + static const Glib::ustring ui_info = "" " " @@ -506,7 +508,7 @@ void GscMainWindow::on_action_activated(GscMainWindow::action_t action_type) return; } - std::string action_name = action->get_name(); + const std::string action_name = action->get_name(); // Do NOT output action->get_name() directly, it dies with unhandled conversion error // exception if used with operator <<. @@ -642,7 +644,7 @@ void GscMainWindow::on_action_enable_smart_toggled(Gtk::ToggleAction* action) if (status == StorageDevice::SmartStatus::Unsupported) // this shouldn't happen return; - bool toggle_active = action->get_active(); + const bool toggle_active = action->get_active(); if ( (toggle_active && status == StorageDevice::SmartStatus::Disabled) || (!toggle_active && status == StorageDevice::SmartStatus::Enabled) ) { @@ -653,7 +655,7 @@ void GscMainWindow::on_action_enable_smart_toggled(Gtk::ToggleAction* action) auto command_status = drive->set_smart_enabled(toggle_active, ex); // run it with GUI support if (!command_status) { - std::string error_header = (toggle_active ? _("Cannot enable SMART") : _("Cannot disable SMART")); + const std::string error_header = (toggle_active ? _("Cannot enable SMART") : _("Cannot disable SMART")); gsc_executor_error_dialog_show(error_header, command_status.error().message(), this); } @@ -715,7 +717,7 @@ void GscMainWindow::set_drive_menu_status(const StorageDevicePtr& drive) // make everything sensitive, then disable one by one actiongroup_device_->set_sensitive(true); - bool is_virtual = (drive && drive->get_is_virtual()); + const bool is_virtual = (drive && drive->get_is_virtual()); StorageDevice::SmartStatus smart_status = StorageDevice::SmartStatus::Unsupported; @@ -774,7 +776,7 @@ void GscMainWindow::update_status_widgets() // Gtk::Label* family_label = this->lookup_widget("status_family_label"); // Gtk::Statusbar* statusbar = this->lookup_widget("window_statusbar"); - StorageDevicePtr drive = iconview_->get_selected_drive(); + const StorageDevicePtr drive = iconview_->get_selected_drive(); if (!drive) { if (name_label_) name_label_->set_text(_("No drive selected")); @@ -788,17 +790,17 @@ void GscMainWindow::update_status_widgets() } /// Translators: %1 is filename - std::string device = Glib::Markup::escape_text(drive->get_is_virtual() + const std::string device = Glib::Markup::escape_text(drive->get_is_virtual() ? Glib::ustring::compose(_("Virtual: %1"), drive->get_virtual_filename()) : Glib::ustring(drive->get_device_with_type())); - std::string size = Glib::Markup::escape_text(drive->get_device_size_str()); - std::string model = Glib::Markup::escape_text(drive->get_model_name().empty() + const std::string size = Glib::Markup::escape_text(drive->get_device_size_str()); + const std::string model = Glib::Markup::escape_text(drive->get_model_name().empty() ? std::string(_("Unknown model")) : drive->get_model_name()); - std::string family = Glib::Markup::escape_text(drive->get_family_name().empty() + const std::string family = Glib::Markup::escape_text(drive->get_family_name().empty() ? C_("model_family", "Unknown") : drive->get_family_name()); - std::string family_fallback = Glib::Markup::escape_text(drive->get_family_name().empty() ? model : drive->get_family_name()); - std::string drive_letters_str = Glib::Markup::escape_text(drive->format_drive_letters(false)); + const std::string family_fallback = Glib::Markup::escape_text(drive->get_family_name().empty() ? model : drive->get_family_name()); + const std::string drive_letters_str = Glib::Markup::escape_text(drive->format_drive_letters(false)); - std::string info_str = device + const std::string info_str = device + (drive_letters_str.empty() ? "" : (" (" + drive_letters_str + ")")) + (size.empty() ? "" : (", " + size)) + (model.empty() ? "" : (", " + model)); @@ -807,7 +809,7 @@ void GscMainWindow::update_status_widgets() app_gtkmm_set_widget_tooltip(*name_label_, info_str, false); // in case it doesn't fit } - StorageProperty health_prop = drive->get_health_property(); + const StorageProperty health_prop = drive->get_health_property(); if (health_label_) { if (health_prop.generic_name == "smart_status/passed") { @@ -821,7 +823,7 @@ void GscMainWindow::update_status_widgets() // app_gtkmm_set_widget_tooltip(*health_label, health_prop.get_description(), true); if (health_prop.warning_level != WarningLevel::None) { - std::string tooltip_str = storage_property_get_warning_reason(health_prop) + const std::string tooltip_str = storage_property_get_warning_reason(health_prop) + "\n\n" + _("View details for more information."); app_gtkmm_set_widget_tooltip(*health_label_, tooltip_str, true); } @@ -900,7 +902,7 @@ void GscMainWindow::rescan_devices() // Catch permission errors. // executor errors and outputs, not reported through error_message. - std::vector fetch_outputs = sd.get_fetch_data_error_outputs(); + const std::vector fetch_outputs = sd.get_fetch_data_error_outputs(); for (const auto& fetch_output : fetch_outputs) { // debug_out_error("app", DBG_FUNC_MSG << fetch_outputs[i] << "\n"); if (app_regex_partial_match("/Smartctl open device.+Permission denied/mi", fetch_output)) { diff --git a/src/gui/gsc_main_window_iconview.cpp b/src/gui/gsc_main_window_iconview.cpp new file mode 100644 index 0000000..31d6deb --- /dev/null +++ b/src/gui/gsc_main_window_iconview.cpp @@ -0,0 +1,573 @@ +/****************************************************************************** +License: GNU General Public License v3.0 only +Copyright: + (C) 2008 - 2024 Alexander Shaduri +******************************************************************************/ +/// \file +/// \author Alexander Shaduri +/// \ingroup gsc +/// \weakgroup gsc +/// @{ + +#include +#include +#include +#include +#include +#include // std::floor +#include +#include + +#include "applib/warning_level.h" +#include "hz/string_algo.h" // string_join +#include "hz/debug.h" +#include "hz/data_file.h" // data_file_find +#include "applib/app_gtkmm_tools.h" +#include "applib/warning_colors.h" + +#include "gsc_main_window.h" +#include "rconfig/rconfig.h" +#include "build_config.h" + +#include "gsc_main_window_iconview.h" + + + + +std::string GscMainWindowIconView::get_message_string(Message type) +{ + static const std::unordered_map m { + {Message::None, _("[error - invalid message]")}, + {Message::ScanDisabled, _("Automatic scanning is disabled.\nPress Ctrl+R to scan manually.")}, + {Message::Scanning, _("Scanning system, please wait...")}, + {Message::NoDrivesFound, _("No drives found.")}, + {Message::NoSmartctl, _("Please specify the correct smartctl binary in\nPreferences and press Ctrl-R to re-scan.")}, + {Message::PleaseRescan, _("Preferences changed.\nPress Ctrl-R to re-scan.")}, + }; + if (auto iter = m.find(type); iter != m.end()) { + return iter->second; + } + return "[internal_error]"; +} + + + +GscMainWindowIconView::GscMainWindowIconView(BaseObjectType* gtkcobj, [[maybe_unused]] const Glib::RefPtr& ref_ui) + : Gtk::IconView(gtkcobj) +{ + columns_.add(col_name_); // we can use the col_name variable by value after this. + this->set_markup_column(col_name_); + + columns_.add(col_description_); + + columns_.add(col_pixbuf_); + + // For high quality rendering with GDK_SCALE=2 + this->pack_start(cell_renderer_pixbuf_, false); + this->set_cell_data_func(cell_renderer_pixbuf_, + sigc::mem_fun(this, &GscMainWindowIconView::on_cell_data_render)); + + columns_.add(col_drive_ptr_); + + columns_.add(col_populated_); + + // create a Tree Model + ref_list_model_ = Gtk::ListStore::create(columns_); +// ref_list_model->set_sort_column(col_name, Gtk::SORT_ASCENDING); + this->set_model(ref_list_model_); + + // we add it here because list model must be created already + set_tooltip_column(col_description_.index()); + + this->load_icon_pixbufs(); + + this->signal_item_activated().connect(sigc::mem_fun(*this, + &GscMainWindowIconView::on_iconview_item_activated) ); + + this->signal_selection_changed().connect(sigc::mem_fun(*this, + &GscMainWindowIconView::on_iconview_selection_changed) ); + + this->signal_button_press_event().connect(sigc::mem_fun(*this, + &GscMainWindowIconView::on_iconview_button_press_event) ); +} + + + +void GscMainWindowIconView::set_main_window(GscMainWindow* w) +{ + main_window_ = w; +} + + + +void GscMainWindowIconView::set_empty_view_message(Message message) +{ + empty_view_message_ = message; +} + + + +int GscMainWindowIconView::get_num_icons() const +{ + return num_icons_; +} + + + +bool GscMainWindowIconView::on_draw(const Cairo::RefPtr& cr) +{ + if (in_destruction()) { + return true; + } + if (empty_view_message_ != Message::None && this->num_icons_ == 0) { // no icons + Glib::RefPtr layout = this->create_pango_layout(""); + layout->set_alignment(Pango::ALIGN_CENTER); + layout->set_markup(get_message_string(empty_view_message_)); + + int layout_w = 0, layout_h = 0; + layout->get_pixel_size(layout_w, layout_h); + + const int pos_x = (get_allocation().get_width() - layout_w) / 2; + const int pos_y = (get_allocation().get_height() - layout_h) / 2; + cr->move_to(pos_x, pos_y); + + layout->show_in_cairo_context(cr); + + return true; + } + + return Gtk::IconView::on_draw(cr); +} + + + +void GscMainWindowIconView::on_cell_data_render(const Gtk::TreeModel::const_iterator& iter) +{ + Gtk::TreeRow row = *iter; + Glib::RefPtr pixbuf = row[col_pixbuf_]; + if (!pixbuf) { + return; + } + + // Gtkmm property_surface() doesn't work, so use plain C. + // https://bugzilla.gnome.org/show_bug.cgi?id=788513 + // Also, Gtkmm doesn't have gdk_cairo_surface_create_from_pixbuf() wrapper. + // https://bugzilla.gnome.org/show_bug.cgi?id=788533 + +// Cairo::Format format = Cairo::FORMAT_ARGB32; +// if (pixbuf->get_n_channels() == 3) { +// format = Cairo::FORMAT_RGB24; +// } +// Cairo::RefPtr surface = get_window()->create_similar_image_surface( +// format, pixbuf->get_width(), pixbuf->get_height(), get_scale_factor()); +// cell_renderer_pixbuf.property_surface().set_value(surface); + + // gdk_cairo_surface_create_from_pixbuf() (and create_similar_image_surface()) from gtk 3.10. + cairo_surface_t* surface = gdk_cairo_surface_create_from_pixbuf(pixbuf->gobj(), get_scale_factor(), get_window()->gobj()); + g_object_set(G_OBJECT(cell_renderer_pixbuf_.gobj()), "surface", surface, nullptr); + cairo_surface_destroy(surface); +} + + + +void GscMainWindowIconView::add_entry(StorageDevicePtr drive, bool scroll_to_it) +{ + if (!drive) + return; + + Gtk::TreeModel::Row row = *(ref_list_model_->append()); + row[col_drive_ptr_] = drive; + + this->decorate_entry(row); + + row[col_populated_] = true; // triggers rendering + + drive->signal_changed().connect(sigc::mem_fun(this, &GscMainWindowIconView::on_drive_changed)); + + if (scroll_to_it) { + const Gtk::TreeModel::Path tpath(row); + // scroll_to_path() and set/get_cursor() are since gtkmm 2.8. + + this->scroll_to_path(tpath, true, 0.5, 0.5); + // select it (keyboard & selection) + Gtk::CellRenderer* cell = nullptr; + if (this->get_cursor(cell) && cell) { + this->set_cursor(tpath, *cell, false); + } + this->select_path(tpath); // highlight it + } + + ++num_icons_; +} + + + +void GscMainWindowIconView::decorate_entry(const Gtk::TreePath& model_path) +{ + if (model_path.empty()) + return; + + Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(model_path)); + this->decorate_entry(row); +} + + + +void GscMainWindowIconView::decorate_entry(Gtk::TreeModel::Row& row) +{ + StorageDevicePtr drive = row[col_drive_ptr_]; + if (!drive) { + return; + } + + // it needs this space to be symmetric (why?); + std::string name; // = "" + drive->get_device_with_type() + " \n"; + Glib::ustring drive_letters = Glib::Markup::escape_text(drive->format_drive_letters(false)); + if (drive_letters.empty()) { + drive_letters = C_("media", "not mounted"); + } + Glib::ustring drive_letters_with_volname = Glib::Markup::escape_text(drive->format_drive_letters(true)); + if (drive_letters_with_volname.empty()) { + drive_letters_with_volname = C_("media", "not mounted"); + } + + // note: if this wraps, it becomes left-aligned in gtk <= 2.10. + name += (drive->get_model_name().empty() ? Glib::ustring("Unknown model") : Glib::Markup::escape_text(drive->get_model_name())); + if (rconfig::get_data("gui/icons_show_device_name")) { + if (!drive->get_is_virtual()) { + const std::string dev = Glib::Markup::escape_text(drive->get_device_with_type()); + if constexpr(BuildEnv::is_kernel_family_windows()) { + name += "\n" + Glib::ustring::compose(_("%1 (%2)"), dev, drive_letters); + } else { + name += "\n" + dev; + } + } else if (!drive->get_virtual_filename().empty()) { + name += "\n" + Glib::Markup::escape_text(drive->get_virtual_filename()); + } + } + if (rconfig::get_data("gui/icons_show_serial_number") && !drive->get_serial_number().empty()) { + name += "\n" + Glib::Markup::escape_text(drive->get_serial_number()); + } + StorageProperty scan_time_prop; + if (drive->get_is_virtual()) { + scan_time_prop = drive->get_property_repository().lookup_property("local_time/asctime"); + if (!scan_time_prop.empty() && !scan_time_prop.get_value().empty()) { + name += "\n" + Glib::Markup::escape_text(scan_time_prop.get_value()); + } + } + + std::vector tooltip_strs; + + if (drive->get_is_virtual()) { + const std::string vfile = drive->get_virtual_filename(); + tooltip_strs.push_back(Glib::ustring::compose(_("Loaded from: %1"), (vfile.empty() ? (Glib::ustring("[") + C_("name", "empty") + "]") : Glib::Markup::escape_text(vfile)))); + if (!scan_time_prop.empty() && !scan_time_prop.get_value().empty()) { + tooltip_strs.push_back(Glib::ustring::compose(_("Scanned on: "), Glib::Markup::escape_text(scan_time_prop.get_value()))); + } + } else { + tooltip_strs.push_back(Glib::ustring::compose(_("Device: %1"), "" + Glib::Markup::escape_text(drive->get_device_with_type()) + "")); + } + + if constexpr(BuildEnv::is_kernel_family_windows()) { + tooltip_strs.push_back(Glib::ustring::compose(_("Drive letters: %1"), "" + drive_letters_with_volname + "")); + } + + if (!drive->get_serial_number().empty()) { + tooltip_strs.push_back(Glib::ustring::compose(_("Serial number: %1"), "" + Glib::Markup::escape_text(drive->get_serial_number()) + "")); + } + tooltip_strs.push_back(Glib::ustring::compose(_("SMART status: %1"), + "" + Glib::Markup::escape_text(StorageDevice::get_status_displayable_name(drive->get_smart_status())) + "")); + + std::string tooltip_str = hz::string_join(tooltip_strs, '\n'); + + + Glib::RefPtr icon; + if (icon_pixbufs_.contains(drive->get_detected_type())) { + icon = icon_pixbufs_[drive->get_detected_type()]; + } else { + icon = default_icon_; + } + + const StorageProperty health_prop = drive->get_health_property(); + if (health_prop.warning_level != WarningLevel::None && health_prop.generic_name == "smart_status/passed") { + if (icon) { + icon = icon->copy(); // work on a copy + if (icon->get_colorspace() == Gdk::COLORSPACE_RGB && icon->get_bits_per_sample() == 8) { + const std::ptrdiff_t n_channels = icon->get_n_channels(); + const std::ptrdiff_t icon_width = icon->get_width(); + const std::ptrdiff_t icon_height = icon->get_height(); + const std::ptrdiff_t rowstride = icon->get_rowstride(); + guint8* pixels = icon->get_pixels(); + + for (std::ptrdiff_t y = 0; y < icon_height; ++y) { + for (std::ptrdiff_t x = 0; x < icon_width; ++x) { + guint8* p = pixels + y * rowstride + x * n_channels; + auto avg = static_cast(std::floor((p[0] * 0.30) + (p[1] * 0.59) + (p[2] * 0.11) + 0.001 + 0.5)); + p[0] = avg; // R + p[1] = 0; // G + p[2] = 0; // B + } + } + } + } + + tooltip_str += "\n\n" + storage_property_get_warning_reason(health_prop) + + "\n\n" + _("View details for more information."); + } + + + // we use all these if-s because changing the data (without actually changing it) + // sometimes leads to screwed up icons in iconview (blame gtk). + + if (row.get_value(col_name_) != name) { + row[col_name_] = name; // markup + } + if (row.get_value(col_description_) != tooltip_str) { + row[col_description_] = tooltip_str; // markup + } + + if (row.get_value(col_pixbuf_) != icon) { + row[col_pixbuf_] = icon; + } +} + + + +void GscMainWindowIconView::remove_entry(const Gtk::TreePath& model_path) +{ + const Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(model_path)); + ref_list_model_->erase(row); +} + + + +void GscMainWindowIconView::remove_selected_drive() +{ + const auto& selected_items = this->get_selected_items(); + if (!selected_items.empty()) { + const Gtk::TreePath model_path = *(selected_items.begin()); + this->remove_entry(model_path); + } +} + + + +void GscMainWindowIconView::clear_all() +{ + num_icons_ = 0; + ref_list_model_->clear(); + + // this is needed to update the label from "disabled" to "scanning" + if (this->get_realized()) { + const Gdk::Rectangle rect = this->get_allocation(); + Glib::RefPtr win = this->get_window(); + win->invalidate_rect(rect, true); // force expose event + win->process_updates(false); // update immediately + } +} + + + +StorageDevicePtr GscMainWindowIconView::get_selected_drive() +{ + StorageDevicePtr drive; + const auto& selected_items = this->get_selected_items(); + if (!selected_items.empty()) { + const Gtk::TreePath model_path = *(selected_items.begin()); + const Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(model_path)); + drive = row[col_drive_ptr_]; + } + return drive; +} + + + +Gtk::TreePath GscMainWindowIconView::get_path_by_drive(StorageDevice* drive) +{ + const Gtk::TreeNodeChildren children = ref_list_model_->children(); + for (const auto& row : children) { + // convert iter to row (iter is row's base, but can we cast it?) + if (drive == row.get_value(col_drive_ptr_).get()) + return ref_list_model_->get_path(row); + } + return {}; // check with .empty() +} + + + +void GscMainWindowIconView::update_menu_actions() +{ + // if there's nothing selected, disable items from "Drives" menu + if (this->get_selected_items().empty()) { + main_window_->set_drive_menu_status(nullptr); + + } else { // enable drives menu, set proper smart toggles + const Gtk::TreePath model_path = *(this->get_selected_items().begin()); + const Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(model_path)); + if (!row[col_populated_]) { // protect against using incomplete model entry + return; + } + + StorageDevicePtr drive = row[col_drive_ptr_]; + main_window_->set_drive_menu_status(drive); + } +} + + +void GscMainWindowIconView::on_iconview_item_activated(const Gtk::TreePath& model_path) +{ + debug_out_info("app", DBG_FUNC << "\n"); + if (!main_window_) + return; + + const Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(model_path)); + if (!row[col_populated_]) { // protect against using incomplete model entry + return; + } + + StorageDevicePtr drive = row[col_drive_ptr_]; + main_window_->show_device_info_window(drive); +} + + + +void GscMainWindowIconView::on_iconview_selection_changed() +{ + // Must do it here - if done during menu activation, the actions won't work + // properly before that. + this->update_menu_actions(); + + main_window_->update_status_widgets(); // status area, etc. +} + + + +bool GscMainWindowIconView::on_iconview_button_press_event(GdkEventButton* event_button) +{ + // select and show popup menu on right-click + if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) { + StorageDevicePtr drive; // clicked drive (if any) + + // don't use get_item_at_pos() - it's not available in gtkmm < 2.8. + Gtk::TreePath tpath = this->get_path_at_pos(static_cast(event_button->x), + static_cast(event_button->y)); + // if (this->get_item_at_pos(static_cast(event_button->x), static_cast(event_button->y), model_path)) { + + if (tpath.gobj() && !tpath.empty()) { // without gobj() check gtkmm 2.6 (but not 2.12) prints lots of errors + // move keyboard focus to the icon (just as left-click does) + + Gtk::CellRenderer* cell = nullptr; + if (this->get_cursor(cell) && cell) { + // gtkmm's set_cursor() is undefined (but declared) in 2.8, so use gtk variant. + gtk_icon_view_set_cursor(GTK_ICON_VIEW(this->gobj()), tpath.gobj(), cell->gobj(), FALSE); + } + + // select the icon + this->select_path(tpath); + + const Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(tpath)); + drive = row[col_drive_ptr_]; + + } else { + this->unselect_all(); // unselect on empty area right-click + } + + Gtk::Menu* menu = main_window_->get_popup_menu(drive); + if (menu) + menu->popup(event_button->button, event_button->time); + + return true; // stop handling + } + + // left click and everything else - continue handling. + // left click selects the icon by default, and allows "activated" signal on double-click. + return false; +} + + + +void GscMainWindowIconView::on_drive_changed(StorageDevice* drive) +{ + const Gtk::TreePath model_path = this->get_path_by_drive(drive); + this->decorate_entry(model_path); + this->update_menu_actions(); + main_window_->update_status_widgets(); +} + + + +void GscMainWindowIconView::load_icon_pixbufs() +{ + Glib::RefPtr default_icon_theme; + try { + default_icon_theme = Gtk::IconTheme::get_default(); + } catch (...) { + // nothing + } + + default_icon_ = load_icon_pixbuf(default_icon_theme, "drive-harddisk", "icon_harddisk.png"); + + for (auto type : StorageDeviceDetectedTypeExt::get_all_values()) { + Glib::RefPtr type_icon; + switch(type) { + case StorageDeviceDetectedType::Unknown: + case StorageDeviceDetectedType::NeedsExplicitType: + break; + case StorageDeviceDetectedType::AtaAny: + case StorageDeviceDetectedType::AtaHdd: + type_icon = load_icon_pixbuf(default_icon_theme, "drive-harddisk", "icon_harddisk.png"); + break; + case StorageDeviceDetectedType::AtaSsd: + case StorageDeviceDetectedType::Nvme: + // Not in XDG, but available in some icon themes + type_icon = load_icon_pixbuf(default_icon_theme, "drive-harddisk-solidstate", ""); + break; + case StorageDeviceDetectedType::BasicScsi: + // Most likely to be a flash drive + type_icon = load_icon_pixbuf(default_icon_theme, "drive-removable-media", ""); + break; + case StorageDeviceDetectedType::CdDvd: + type_icon = load_icon_pixbuf(default_icon_theme, "drive-optical", "icon_optical.png"); + break; + case StorageDeviceDetectedType::UnsupportedRaid: + // Not in XDG, but available in some icon themes + type_icon = load_icon_pixbuf(default_icon_theme, "drive-multidisk", ""); + break; + } + if (!type_icon) { + type_icon = default_icon_; + } + icon_pixbufs_[type] = type_icon; + } +} + + + +Glib::RefPtr GscMainWindowIconView::load_icon_pixbuf(Glib::RefPtr default_icon_theme, + const std::string& xdg_icon_name, const std::string& bundled_icon_filename) +{ + Glib::RefPtr icon; + + // Try XDG version first + try { + if (default_icon_theme && !xdg_icon_name.empty()) { + icon = default_icon_theme->load_icon(xdg_icon_name, icon_size_, get_scale_factor(), Gtk::IconLookupFlags(0)); + } + } catch (...) { } // ignore exceptions + + if (!icon && !bundled_icon_filename.empty()) { + // Still no luck, use bundled ones. + if (auto icon_file = hz::data_file_find("icons", bundled_icon_filename); !icon_file.empty()) { + try { + icon = Gdk::Pixbuf::create_from_file(hz::fs_path_to_string(icon_file)); + } catch (...) { } // ignore exceptions + } + } + + return icon; +} + + + + +/// @} diff --git a/src/gui/gsc_main_window_iconview.h b/src/gui/gsc_main_window_iconview.h index 3f6e66e..f90cff7 100644 --- a/src/gui/gsc_main_window_iconview.h +++ b/src/gui/gsc_main_window_iconview.h @@ -1,7 +1,7 @@ /****************************************************************************** License: GNU General Public License v3.0 only Copyright: - (C) 2008 - 2021 Alexander Shaduri + (C) 2008 - 2024 Alexander Shaduri ******************************************************************************/ /// \file /// \author Alexander Shaduri @@ -14,20 +14,11 @@ Copyright: #include #include -#include -#include // std::floor -#include #include - -#include "hz/string_algo.h" // string_join -#include "hz/debug.h" -#include "hz/data_file.h" // data_file_find -#include "applib/app_gtkmm_tools.h" -#include "applib/warning_colors.h" +#include #include "gsc_main_window.h" -#include "rconfig/rconfig.h" -#include "build_config.h" +#include "applib/storage_device.h" @@ -49,550 +40,123 @@ class GscMainWindowIconView : public Gtk::IconView { /// Get message string - static std::string get_message_string(Message type) - { - static const std::unordered_map m { - {Message::None, _("[error - invalid message]")}, - {Message::ScanDisabled, _("Automatic scanning is disabled.\nPress Ctrl+R to scan manually.")}, - {Message::Scanning, _("Scanning system, please wait...")}, - {Message::NoDrivesFound, _("No drives found.")}, - {Message::NoSmartctl, _("Please specify the correct smartctl binary in\nPreferences and press Ctrl-R to re-scan.")}, - {Message::PleaseRescan, _("Preferences changed.\nPress Ctrl-R to re-scan.")}, - }; - if (auto iter = m.find(type); iter != m.end()) { - return iter->second; - } - return "[internal_error]"; - } + [[nodiscard]] static std::string get_message_string(Message type); /// Constructor, GtkBuilder needs this. - [[maybe_unused]] GscMainWindowIconView(BaseObjectType* gtkcobj, [[maybe_unused]] const Glib::RefPtr& ref_ui) - : Gtk::IconView(gtkcobj) - { - columns.add(col_name); // we can use the col_name variable by value after this. - this->set_markup_column(col_name); - - columns.add(col_description); - - columns.add(col_pixbuf); - - // For high quality rendering with GDK_SCALE=2 - this->pack_start(cell_renderer_pixbuf, false); - this->set_cell_data_func(cell_renderer_pixbuf, - sigc::mem_fun(this, &GscMainWindowIconView::on_cell_data_render)); - - columns.add(col_drive_ptr); - - columns.add(col_populated); - - // create a Tree Model - ref_list_model = Gtk::ListStore::create(columns); -// ref_list_model->set_sort_column(col_name, Gtk::SORT_ASCENDING); - this->set_model(ref_list_model); - - // we add it here because list model must be created already - set_tooltip_column(col_description.index()); - - - // icons - Glib::RefPtr default_icon_theme; - try { - default_icon_theme = Gtk::IconTheme::get_default(); - } catch (...) { - // nothing - } - - // Adwaita's drive-harddisk icons are really small at 48, so 64 is better. - // Plus, it scales well to 128 and 256 (if using GDK_SCALE). - const int icon_size = 64; - - // Try XDG version first - try { - hd_icon = default_icon_theme->load_icon("drive-harddisk", icon_size, get_scale_factor(), Gtk::IconLookupFlags(0)); - } catch (...) { } // ignore exceptions - - if (!hd_icon) { - // Still no luck, use bundled ones. - auto icon_file = hz::data_file_find("icons", "icon_hdd.png"); - if (!icon_file.empty()) { - try { - hd_icon = Gdk::Pixbuf::create_from_file(hz::fs_path_to_string(icon_file)); - } catch (...) { } // ignore exceptions - } - } - - // Try XDG version first - try { - cddvd_icon = default_icon_theme->load_icon("media-optical", icon_size, get_scale_factor(), Gtk::IconLookupFlags(0)); - } catch (...) { } // ignore exceptions - - if (!cddvd_icon) { - // Still no luck, use bundled ones. - auto icon_file = hz::data_file_find("icons", "icon_cddvd.png"); - if (!icon_file.empty()) { - try { - cddvd_icon = Gdk::Pixbuf::create_from_file(hz::fs_path_to_string(icon_file)); - } catch (...) { } // ignore exceptions - } - } - - this->signal_item_activated().connect(sigc::mem_fun(*this, - &GscMainWindowIconView::on_iconview_item_activated) ); - - this->signal_selection_changed().connect(sigc::mem_fun(*this, - &GscMainWindowIconView::on_iconview_selection_changed) ); - - this->signal_button_press_event().connect(sigc::mem_fun(*this, - &GscMainWindowIconView::on_iconview_button_press_event) ); - } + [[maybe_unused]] GscMainWindowIconView(BaseObjectType* gtkcobj, [[maybe_unused]] const Glib::RefPtr& ref_ui); /// Set the parent window - void set_main_window(GscMainWindow* w) - { - main_window = w; - } + void set_main_window(GscMainWindow* w); /// Set the message type to display when there are no icons to show - void set_empty_view_message(Message message) - { - empty_view_message = message; - } + void set_empty_view_message(Message message); /// Get the number of icons currently displayed - int get_num_icons() const - { - return num_icons; - } + [[nodiscard]] int get_num_icons() const; // Overridden from Gtk::Widget - bool on_draw(const Cairo::RefPtr& cr) override - { - if (in_destruction()) { - return true; - } - if (empty_view_message != Message::None && this->num_icons == 0) { // no icons - Glib::RefPtr layout = this->create_pango_layout(""); - layout->set_alignment(Pango::ALIGN_CENTER); - layout->set_markup(get_message_string(empty_view_message)); - - int layout_w = 0, layout_h = 0; - layout->get_pixel_size(layout_w, layout_h); - - const int pos_x = (get_allocation().get_width() - layout_w) / 2; - const int pos_y = (get_allocation().get_height() - layout_h) / 2; - cr->move_to(pos_x, pos_y); - - layout->show_in_cairo_context(cr); - - return true; - } - - return Gtk::IconView::on_draw(cr); - } - + bool on_draw(const Cairo::RefPtr& cr) override; /// Cell data renderer (needed for high quality icons in GDK_SCALE=2). /// We have to use Cairo surfaces, because pixbufs are scaled by GtkIconView. - void on_cell_data_render(const Gtk::TreeModel::const_iterator& iter) - { - Gtk::TreeRow row = *iter; - Glib::RefPtr pixbuf = row[col_pixbuf]; - if (!pixbuf) { - return; - } - - // Gtkmm property_surface() doesn't work, so use plain C. - // https://bugzilla.gnome.org/show_bug.cgi?id=788513 - // Also, Gtkmm doesn't have gdk_cairo_surface_create_from_pixbuf() wrapper. - // https://bugzilla.gnome.org/show_bug.cgi?id=788533 - -// Cairo::Format format = Cairo::FORMAT_ARGB32; -// if (pixbuf->get_n_channels() == 3) { -// format = Cairo::FORMAT_RGB24; -// } -// Cairo::RefPtr surface = get_window()->create_similar_image_surface( -// format, pixbuf->get_width(), pixbuf->get_height(), get_scale_factor()); -// cell_renderer_pixbuf.property_surface().set_value(surface); - - // gdk_cairo_surface_create_from_pixbuf() (and create_similar_image_surface()) from gtk 3.10. - cairo_surface_t* surface = gdk_cairo_surface_create_from_pixbuf(pixbuf->gobj(), get_scale_factor(), get_window()->gobj()); - g_object_set(G_OBJECT(cell_renderer_pixbuf.gobj()), "surface", surface, nullptr); - cairo_surface_destroy(surface); - } + void on_cell_data_render(const Gtk::TreeModel::const_iterator& iter); /// Add a drive entry to the icon view - void add_entry(StorageDevicePtr drive, bool scroll_to_it = false) - { - if (!drive) - return; - - Gtk::TreeModel::Row row = *(ref_list_model->append()); - row[col_drive_ptr] = drive; - - this->decorate_entry(row); - - row[col_populated] = true; // triggers rendering - - drive->signal_changed().connect(sigc::mem_fun(this, &GscMainWindowIconView::on_drive_changed)); - - if (scroll_to_it) { - const Gtk::TreeModel::Path tpath(row); - // scroll_to_path() and set/get_cursor() are since gtkmm 2.8. - - this->scroll_to_path(tpath, true, 0.5, 0.5); - // select it (keyboard & selection) - Gtk::CellRenderer* cell = nullptr; - if (this->get_cursor(cell) && cell) { - this->set_cursor(tpath, *cell, false); - } - this->select_path(tpath); // highlight it - } - - ++num_icons; - } - + void add_entry(StorageDevicePtr drive, bool scroll_to_it = false); /// Decorate a drive entry (colorize it if it has errors, etc.). /// This should be called to update the icon of already refreshed drive. - void decorate_entry(const Gtk::TreePath& model_path) - { - if (model_path.empty()) - return; - - Gtk::TreeModel::Row row = *(ref_list_model->get_iter(model_path)); - this->decorate_entry(row); - } - + void decorate_entry(const Gtk::TreePath& model_path); /// Decorate a drive entry (colorize it if it has errors, etc.). /// This should be called to update the icon of already refreshed drive. - void decorate_entry(Gtk::TreeModel::Row& row) - { - StorageDevicePtr drive = row[col_drive_ptr]; - if (!drive) { - return; - } - - // it needs this space to be symmetric (why?); - std::string name; // = "" + drive->get_device_with_type() + " \n"; - Glib::ustring drive_letters = Glib::Markup::escape_text(drive->format_drive_letters(false)); - if (drive_letters.empty()) { - drive_letters = C_("media", "not mounted"); - } - Glib::ustring drive_letters_with_volname = Glib::Markup::escape_text(drive->format_drive_letters(true)); - if (drive_letters_with_volname.empty()) { - drive_letters_with_volname = C_("media", "not mounted"); - } - - // note: if this wraps, it becomes left-aligned in gtk <= 2.10. - name += (drive->get_model_name().empty() ? Glib::ustring("Unknown model") : Glib::Markup::escape_text(drive->get_model_name())); - if (rconfig::get_data("gui/icons_show_device_name")) { - if (!drive->get_is_virtual()) { - const std::string dev = Glib::Markup::escape_text(drive->get_device_with_type()); - if constexpr(BuildEnv::is_kernel_family_windows()) { - name += "\n" + Glib::ustring::compose(_("%1 (%2)"), dev, drive_letters); - } else { - name += "\n" + dev; - } - } else if (!drive->get_virtual_filename().empty()) { - name += "\n" + Glib::Markup::escape_text(drive->get_virtual_filename()); - } - } - if (rconfig::get_data("gui/icons_show_serial_number") && !drive->get_serial_number().empty()) { - name += "\n" + Glib::Markup::escape_text(drive->get_serial_number()); - } - StorageProperty scan_time_prop; - if (drive->get_is_virtual()) { - scan_time_prop = drive->get_property_repository().lookup_property("local_time/asctime"); - if (!scan_time_prop.empty() && !scan_time_prop.get_value().empty()) { - name += "\n" + Glib::Markup::escape_text(scan_time_prop.get_value()); - } - } - - std::vector tooltip_strs; - - if (drive->get_is_virtual()) { - const std::string vfile = drive->get_virtual_filename(); - tooltip_strs.push_back(Glib::ustring::compose(_("Loaded from: %1"), (vfile.empty() ? (Glib::ustring("[") + C_("name", "empty") + "]") : Glib::Markup::escape_text(vfile)))); - if (!scan_time_prop.empty() && !scan_time_prop.get_value().empty()) { - tooltip_strs.push_back(Glib::ustring::compose(_("Scanned on: "), Glib::Markup::escape_text(scan_time_prop.get_value()))); - } - } else { - tooltip_strs.push_back(Glib::ustring::compose(_("Device: %1"), "" + Glib::Markup::escape_text(drive->get_device_with_type()) + "")); - } - - if constexpr(BuildEnv::is_kernel_family_windows()) { - tooltip_strs.push_back(Glib::ustring::compose(_("Drive letters: %1"), "" + drive_letters_with_volname + "")); - } - - if (!drive->get_serial_number().empty()) { - tooltip_strs.push_back(Glib::ustring::compose(_("Serial number: %1"), "" + Glib::Markup::escape_text(drive->get_serial_number()) + "")); - } - tooltip_strs.push_back(Glib::ustring::compose(_("SMART status: %1"), - "" + Glib::Markup::escape_text(StorageDevice::get_status_displayable_name(drive->get_smart_status())) + "")); - - std::string tooltip_str = hz::string_join(tooltip_strs, '\n'); - - - Glib::RefPtr icon; - switch(drive->get_detected_type()) { - case StorageDeviceDetectedType::CdDvd: - icon = cddvd_icon; - break; - case StorageDeviceDetectedType::Unknown: // standard HD icon - case StorageDeviceDetectedType::NeedsExplicitType: - case StorageDeviceDetectedType::AtaAny: - case StorageDeviceDetectedType::AtaHdd: - case StorageDeviceDetectedType::AtaSsd: - case StorageDeviceDetectedType::Nvme: - case StorageDeviceDetectedType::BasicScsi: - case StorageDeviceDetectedType::UnsupportedRaid: // TODO a separate icon - icon = hd_icon; - break; - } - - const StorageProperty health_prop = drive->get_health_property(); - if (health_prop.warning_level != WarningLevel::None && health_prop.generic_name == "smart_status/passed") { - if (icon) { - icon = icon->copy(); // work on a copy - if (icon->get_colorspace() == Gdk::COLORSPACE_RGB && icon->get_bits_per_sample() == 8) { - const std::ptrdiff_t n_channels = icon->get_n_channels(); - const std::ptrdiff_t icon_width = icon->get_width(); - const std::ptrdiff_t icon_height = icon->get_height(); - const std::ptrdiff_t rowstride = icon->get_rowstride(); - guint8* pixels = icon->get_pixels(); - - for (std::ptrdiff_t y = 0; y < icon_height; ++y) { - for (std::ptrdiff_t x = 0; x < icon_width; ++x) { - guint8* p = pixels + y * rowstride + x * n_channels; - auto avg = static_cast(std::floor((p[0] * 0.30) + (p[1] * 0.59) + (p[2] * 0.11) + 0.001 + 0.5)); - p[0] = avg; // R - p[1] = 0; // G - p[2] = 0; // B - } - } - } - } - - tooltip_str += "\n\n" + storage_property_get_warning_reason(health_prop) - + "\n\n" + _("View details for more information."); - } - - - // we use all these if-s because changing the data (without actually changing it) - // sometimes leads to screwed up icons in iconview (blame gtk). - - if (row.get_value(col_name) != name) - row[col_name] = name; // markup - if (row.get_value(col_description) != tooltip_str) - row[col_description] = tooltip_str; // markup - - if (row.get_value(col_pixbuf) != icon) - row[col_pixbuf] = icon; - - } - + void decorate_entry(Gtk::TreeModel::Row& row); /// Remove drive entry - void remove_entry(const Gtk::TreePath& model_path) - { - const Gtk::TreeModel::Row row = *(ref_list_model->get_iter(model_path)); - ref_list_model->erase(row); - } - + void remove_entry(const Gtk::TreePath& model_path); /// Remove selected drive entry - void remove_selected_drive() - { - const auto& selected_items = this->get_selected_items(); - if (!selected_items.empty()) { - const Gtk::TreePath model_path = *(selected_items.begin()); - this->remove_entry(model_path); - } - } - + void remove_selected_drive(); /// Remove all entries - void clear_all() - { - num_icons = 0; - ref_list_model->clear(); - - // this is needed to update the label from "disabled" to "scanning" - if (this->get_realized()) { - const Gdk::Rectangle rect = this->get_allocation(); - Glib::RefPtr win = this->get_window(); - win->invalidate_rect(rect, true); // force expose event - win->process_updates(false); // update immediately - } - } - + void clear_all(); /// Get selected drive - StorageDevicePtr get_selected_drive() - { - StorageDevicePtr drive; - const auto& selected_items = this->get_selected_items(); - if (!selected_items.empty()) { - const Gtk::TreePath model_path = *(selected_items.begin()); - const Gtk::TreeModel::Row row = *(ref_list_model->get_iter(model_path)); - drive = row[col_drive_ptr]; - } - return drive; - } - + [[nodiscard]] StorageDevicePtr get_selected_drive(); /// Get tree path by a drive - Gtk::TreePath get_path_by_drive(StorageDevice* drive) - { - const Gtk::TreeNodeChildren children = ref_list_model->children(); - for (const auto& row : children) { - // convert iter to row (iter is row's base, but can we cast it?) - if (drive == row.get_value(col_drive_ptr).get()) - return ref_list_model->get_path(row); - } - return {}; // check with .empty() - } - + [[nodiscard]] Gtk::TreePath get_path_by_drive(StorageDevice* drive); /// Update menu actions in the Drives menu - void update_menu_actions() - { - // if there's nothing selected, disable items from "Drives" menu - if (this->get_selected_items().empty()) { - main_window->set_drive_menu_status(nullptr); + void update_menu_actions(); - } else { // enable drives menu, set proper smart toggles - const Gtk::TreePath model_path = *(this->get_selected_items().begin()); - const Gtk::TreeModel::Row row = *(ref_list_model->get_iter(model_path)); - if (!row[col_populated]) { // protect against using incomplete model entry - return; - } - StorageDevicePtr drive = row[col_drive_ptr]; - main_window->set_drive_menu_status(drive); - } - } + protected: + + /// Callback + void on_iconview_item_activated(const Gtk::TreePath& model_path); /// Callback - void on_iconview_item_activated(const Gtk::TreePath& model_path) - { - debug_out_info("app", DBG_FUNC << "\n"); - if (!main_window) - return; - - const Gtk::TreeModel::Row row = *(ref_list_model->get_iter(model_path)); - if (!row[col_populated]) { // protect against using incomplete model entry - return; - } - - StorageDevicePtr drive = row[col_drive_ptr]; - main_window->show_device_info_window(drive); - } + void on_iconview_selection_changed(); /// Callback - void on_iconview_selection_changed() - { - // Must do it here - if done during menu activation, the actions won't work - // properly before that. - this->update_menu_actions(); - - main_window->update_status_widgets(); // status area, etc. - } - - - /// Callback - bool on_iconview_button_press_event(GdkEventButton* event_button) - { - // select and show popup menu on right-click - if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) { - StorageDevicePtr drive; // clicked drive (if any) - - // don't use get_item_at_pos() - it's not available in gtkmm < 2.8. - Gtk::TreePath tpath = this->get_path_at_pos(static_cast(event_button->x), - static_cast(event_button->y)); - // if (this->get_item_at_pos(static_cast(event_button->x), static_cast(event_button->y), model_path)) { - - if (tpath.gobj() && !tpath.empty()) { // without gobj() check gtkmm 2.6 (but not 2.12) prints lots of errors - // move keyboard focus to the icon (just as left-click does) - - Gtk::CellRenderer* cell = nullptr; - if (this->get_cursor(cell) && cell) { - // gtkmm's set_cursor() is undefined (but declared) in 2.8, so use gtk variant. - gtk_icon_view_set_cursor(GTK_ICON_VIEW(this->gobj()), tpath.gobj(), cell->gobj(), FALSE); - } - - // select the icon - this->select_path(tpath); - - const Gtk::TreeModel::Row row = *(ref_list_model->get_iter(tpath)); - drive = row[col_drive_ptr]; - - } else { - this->unselect_all(); // unselect on empty area right-click - } - - Gtk::Menu* menu = main_window->get_popup_menu(drive); - if (menu) - menu->popup(event_button->button, event_button->time); - - return true; // stop handling - } - - // left click and everything else - continue handling. - // left click selects the icon by default, and allows "activated" signal on double-click. - return false; - } + bool on_iconview_button_press_event(GdkEventButton* event_button); /// Callback attached to StorageDevice, updates its view. - void on_drive_changed(StorageDevice* drive) - { - const Gtk::TreePath model_path = this->get_path_by_drive(drive); - this->decorate_entry(model_path); - this->update_menu_actions(); - main_window->update_status_widgets(); - } + void on_drive_changed(StorageDevice* drive); + + + /// Load drive-type-specific icons + void load_icon_pixbufs(); + + + /// Load drive-type-specific icons + [[nodiscard]] Glib::RefPtr load_icon_pixbuf(Glib::RefPtr default_icon_theme, + const std::string& xdg_icon_name, const std::string& bundled_icon_filename); private: - Gtk::TreeModel::ColumnRecord columns; ///< Model columns - Gtk::CellRendererPixbuf cell_renderer_pixbuf; ///< Cell renderer for icons. + Gtk::TreeModel::ColumnRecord columns_; ///< Model columns + Gtk::CellRendererPixbuf cell_renderer_pixbuf_; ///< Cell renderer for icons. - Gtk::TreeModelColumn col_name; ///< Model column - Gtk::TreeModelColumn col_description; ///< Model column - Gtk::TreeModelColumn > col_pixbuf; ///< Model column - Gtk::TreeModelColumn col_drive_ptr; ///< Model column - Gtk::TreeModelColumn col_populated; ///< Model column, indicates whether the model entry has been fully populated. + Gtk::TreeModelColumn col_name_; ///< Model column + Gtk::TreeModelColumn col_description_; ///< Model column + Gtk::TreeModelColumn > col_pixbuf_; ///< Model column + Gtk::TreeModelColumn col_drive_ptr_; ///< Model column + Gtk::TreeModelColumn col_populated_; ///< Model column, indicates whether the model entry has been fully populated. - Glib::RefPtr ref_list_model; ///< The icon view model - int num_icons = 0; ///< Track the number of icons, because liststore makes it difficult to count them. + Glib::RefPtr ref_list_model_; ///< The icon view model + int num_icons_ = 0; ///< Track the number of icons, because liststore makes it difficult to count them. - // available icons - Glib::RefPtr hd_icon; ///< Icon pixbuf - Glib::RefPtr cddvd_icon; ///< Icon pixbuf + /// Adwaita's drive-harddisk icons are tiny at 48, so 64 is better. + /// Plus, 64 scales well to 128 and 256 (if using GDK_SCALE). + const int icon_size_ = 64; - GscMainWindow* main_window = nullptr; ///< The main window, our parent + Glib::RefPtr default_icon_; ///< Icon pixbuf, used when type-specific icon is missing + std::unordered_map> icon_pixbufs_; ///< Icons for different drive types - Message empty_view_message = Message::None; ///< Message type to display when not showing any icons + GscMainWindow* main_window_ = nullptr; ///< The main window, our parent + + Message empty_view_message_ = Message::None; ///< Message type to display when not showing any icons };