diff --git a/gsmartcontrol/TODO b/gsmartcontrol/TODO index 7bede0b..5623be8 100644 --- a/gsmartcontrol/TODO +++ b/gsmartcontrol/TODO @@ -26,18 +26,12 @@ Don't rely on smartctl return code (2), parse the output instead. Need usage cases. -Add ability (through ctrl-C?) to copy selected rows from attributes and statistics pages, - in tab-separated format (for easy pasting into libreoffice). - - Testing: If ETA time has elapsed, but it's still only at 10% completion, ETA 0 is displayed. Fix. Detect running tests on launch (maybe ask the user too? some tests may be stuck due to bad firmware, e.g. 3ware/windows). - - -Parse all test data. + If smartctl outputs gibberish while testing, the GUI hangs. Support RAID for these controllers: diff --git a/gsmartcontrol/src/applib/storage_property_descr.cpp b/gsmartcontrol/src/applib/storage_property_descr.cpp index 44ab21f..de1ac88 100644 --- a/gsmartcontrol/src/applib/storage_property_descr.cpp +++ b/gsmartcontrol/src/applib/storage_property_descr.cpp @@ -1224,7 +1224,7 @@ namespace { add("Number of Reallocated Logical Sectors", "", "", "The number of logical sectors that have been reallocated after device manufacture.\n\n" "If the value is normalized, this is the whole number percentage of the available logical sector reallocation " - "resources that have been used (i.e., 0..100)." + "resources that have been used (i.e., 0-100)." "\n\n" + s_unc_text); add("Read Recovery Attempts", "", "", diff --git a/gsmartcontrol/src/gsc_info_window.cpp b/gsmartcontrol/src/gsc_info_window.cpp index a75683a..406c033 100644 --- a/gsmartcontrol/src/gsc_info_window.cpp +++ b/gsmartcontrol/src/gsc_info_window.cpp @@ -237,6 +237,29 @@ GscInfoWindow::GscInfoWindow(BaseObjectType* gtkcobj, const app_ui_res_ref_t& re Gdk::ModifierType(0), Gtk::AccelFlags(0)); } + // Context menu in treeviews + { + std::vector treeview_names; + treeview_names.push_back("attributes_treeview"); + treeview_names.push_back("statistics_treeview"); + treeview_names.push_back("selftest_log_treeview"); + + for (std::size_t i = 0; i < treeview_names.size(); ++i) { + std::string treeview_name = treeview_names[i]; + Gtk::TreeView* treeview = lookup_widget(treeview_name); + treeview->signal_button_press_event().connect( + sigc::bind(sigc::bind(sigc::mem_fun(*this, &GscInfoWindow::on_treeview_button_press_event), treeview), &treeview_menus[treeview_name]), false); // before + + Gtk::MenuItem* item = Gtk::manage(new Gtk::MenuItem("Copy Selected Data", true)); + item->signal_activate().connect( + sigc::bind(sigc::mem_fun(*this, &GscInfoWindow::on_treeview_menu_copy_clicked), treeview) ); + treeview_menus[treeview_name].append(*item); + + treeview_menus[treeview_name].show_all(); // Show all menu items when the menu pops up + } + } + + // --------------- @@ -647,7 +670,6 @@ void GscInfoWindow::fill_ui_with_info(bool scan, bool clear_ui, bool clear_tests } - StorageProperty::warning_t max_tab_warning = StorageProperty::warning_none; label_list_t label_strings; // outside-of-tree properties @@ -1197,7 +1219,7 @@ void GscInfoWindow::fill_ui_with_info(bool scan, bool clear_ui, bool clear_tests continue; if (iter->generic_name == "sct_unsupported" && iter->value_bool) { // only show if unsupported - label_strings.push_back(PropertyLabel("SCT commands unsupported.", &(*iter))); + label_strings.push_back(PropertyLabel("SCT temperature commands not supported.", &(*iter))); if (int(iter->warning) > int(max_tab_warning)) max_tab_warning = iter->warning; continue; @@ -2194,6 +2216,64 @@ void GscInfoWindow::on_drive_changed(StorageDevice* pdrive) +bool GscInfoWindow::on_treeview_button_press_event(GdkEventButton* button_event, Gtk::Menu* menu, Gtk::TreeView* treeview) +{ + if (button_event->type == GDK_BUTTON_PRESS && button_event->button == 3) { + bool selection_empty = treeview->get_selection()->get_selected_rows().empty(); + std::vector children = menu->get_children(); + for (std::size_t i = 0; i < children.size(); ++i) { + children[i]->set_sensitive(!selection_empty); + } + menu->popup(button_event->button, button_event->time); + return true; + } + return false; +} + + + +void GscInfoWindow::on_treeview_menu_copy_clicked(Gtk::TreeView* treeview) +{ + std::string text; + + guint num_cols = treeview->get_n_columns(); + std::vector col_texts; + for (guint i = 0; i < num_cols; ++i) { + Gtk::TreeViewColumn* tcol = treeview->get_column(i); + col_texts.push_back("\"" + hz::string_replace_copy(tcol->get_title(), "\"", "\"\"") + "\""); + } + text += hz::string_join(col_texts, ',') + "\n"; + + std::vector selection = treeview->get_selection()->get_selected_rows(); + Glib::RefPtr list_store = Glib::RefPtr::cast_dynamic(treeview->get_model()); + for (std::size_t i = 0; i < selection.size(); ++i) { + std::vector cell_texts; + Gtk::TreeModel::Path path = selection[i]; + Gtk::TreeRow row = *(list_store->get_iter(path)); + + for (guint j = 0; j < num_cols; ++j) { // gather data only from tree columns, not model columns (like tooltips and helper data) + GType type = list_store->get_column_type(j); + if (type == G_TYPE_INT) { + int32_t value = 0; + row.get_value(j, value); + cell_texts.push_back(hz::number_to_string(value)); + } else if (type == G_TYPE_STRING) { + std::string value; + row.get_value(j, value); + cell_texts.push_back("\"" + hz::string_replace_copy(value, "\"", "\"\"") + "\""); + } + } + text += hz::string_join(cell_texts, ',') + "\n"; + } + + Glib::RefPtr clipboard = Gtk::Clipboard::get(); + if (clipboard) { + clipboard->set_text(text); + } +} + + + diff --git a/gsmartcontrol/src/gsc_info_window.h b/gsmartcontrol/src/gsc_info_window.h index 624e44b..694b995 100644 --- a/gsmartcontrol/src/gsc_info_window.h +++ b/gsmartcontrol/src/gsc_info_window.h @@ -13,6 +13,7 @@ #define GSC_INFO_WINDOW_H #include +#include #include "applib/app_ui_res_utils.h" #include "applib/storage_device.h" @@ -100,9 +101,15 @@ class GscInfoWindow : public AppUIResWidget { void on_test_stop_button_clicked(); - // Callback attached to StorageDevice change signal. + /// Callback attached to StorageDevice change signal. void on_drive_changed(StorageDevice* pdrive); + /// Callback + bool on_treeview_button_press_event(GdkEventButton* button_event, Gtk::Menu* menu, Gtk::TreeView* treeview); + + /// Callback + void on_treeview_menu_copy_clicked(Gtk::TreeView* treeview); + private: @@ -117,6 +124,8 @@ class GscInfoWindow : public AppUIResWidget { // --------- Data members + std::map treeview_menus; ///< Context menus + // tab headers, to perform their coloration Glib::ustring tab_identity_name; ///< Tab header name Glib::ustring tab_attributes_name; ///< Tab header name