Files
gsmartcontrol/src/gsc_executor_log_window.cpp

455 lines
13 KiB
C++

/******************************************************************************
License: GNU General Public License v3.0 only
Copyright:
(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>
******************************************************************************/
/// \file
/// \author Alexander Shaduri
/// \ingroup gsc
/// \weakgroup gsc
/// @{
#include "local_glibmm.h"
#include <gtkmm.h>
#include <gdk/gdk.h> // GDK_KEY_Escape
#include <sstream>
#include <cstddef> // std::size_t
#include <memory>
#include "applib/app_gtkmm_tools.h" // app_gtkmm_create_tree_view_column
#include "hz/fs.h"
#include "rconfig/rconfig.h"
#include "gsc_executor_log_window.h"
#include "gsc_init.h" // app_get_debug_buffer_str()
GscExecutorLogWindow::GscExecutorLogWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)
: AppBuilderWidget<GscExecutorLogWindow, false>(gtkcobj, std::move(ui))
{
// Connect callbacks
Gtk::Button* window_close_button = nullptr;
APP_BUILDER_AUTO_CONNECT(window_close_button, clicked);
Gtk::Button* window_save_current_button = nullptr;
APP_BUILDER_AUTO_CONNECT(window_save_current_button, clicked);
Gtk::Button* window_save_all_button = nullptr;
APP_BUILDER_AUTO_CONNECT(window_save_all_button, clicked);
Gtk::Button* clear_command_list_button = nullptr;
APP_BUILDER_AUTO_CONNECT(clear_command_list_button, clicked);
// Accelerators
Glib::RefPtr<Gtk::AccelGroup> accel_group = this->get_accel_group();
if (window_close_button) {
window_close_button->add_accelerator("clicked", accel_group, GDK_KEY_Escape,
Gdk::ModifierType(0), Gtk::AccelFlags(0));
}
// --------------- Make a treeview
auto* treeview = this->lookup_widget<Gtk::TreeView*>("command_list_treeview");
if (treeview) {
Gtk::TreeModelColumnRecord model_columns;
// #, Command + parameters, [EntryPtr]
model_columns.add(col_num);
app_gtkmm_create_tree_view_column(col_num, *treeview,
"#", _("# of executed command"), true); // sortable
model_columns.add(col_command);
app_gtkmm_create_tree_view_column(col_command, *treeview,
_("Command"), _("Command with parameters"), true); // sortable
model_columns.add(col_entry);
// create a TreeModel (ListStore)
list_store = Gtk::ListStore::create(model_columns);
// list_store->set_sort_column(col_num, Gtk::SORT_DESCENDING); // default sort
treeview->set_model(list_store);
selection = treeview->get_selection();
selection->signal_changed().connect(sigc::mem_fun(*this,
&GscExecutorLogWindow::on_tree_selection_changed) );
}
// Hide command text entry in win32.
// Setting text on this entry segfaults under win32, (utf8 conversion doesn't
// help, not that it should). Seems to be connected to non-english locale.
// Surprisingly, the treeview column text still works.
// The problem seems to have disappeared (new compiler/runtime?)
// if constexpr(BuildEnv::is_kernel_family_windows()) {
// Gtk::Box* command_hbox = this->lookup_widget<Gtk::Box*>("command_hbox");
// if (command_hbox)
// command_hbox->hide();
// }
// ---------------
// Connect to CommandExecutor signal
cmdex_sync_signal_execute_finish().connect(sigc::mem_fun(*this,
&GscExecutorLogWindow::on_command_output_received));
// show();
}
void GscExecutorLogWindow::show_last()
{
auto* treeview = this->lookup_widget<Gtk::TreeView*>("command_list_treeview");
if (treeview != nullptr && !list_store->children().empty()) {
// Gtk::TreeRow row = *(list_store->children().rbegin()); // this causes invalid read error in valgrind
Gtk::TreeRow row = *(--(list_store->children().end()));
selection->select(row);
// you would think that scroll_to_row would accept a TreeRow for a change (shock!)
treeview->scroll_to_row(list_store->get_path(row));
}
show();
}
void GscExecutorLogWindow::clear_view_widgets()
{
auto* window_save_current_button = this->lookup_widget<Gtk::Button*>("window_save_current_button");
if (window_save_current_button)
window_save_current_button->set_sensitive(false);
auto* output_textview = this->lookup_widget<Gtk::TextView*>("output_textview");
if (output_textview) {
Glib::RefPtr<Gtk::TextBuffer> buffer = output_textview->get_buffer();
buffer->set_text("");
}
auto* command_entry = this->lookup_widget<Gtk::Entry*>("command_entry");
if (command_entry)
command_entry->set_text("");
}
void GscExecutorLogWindow::on_command_output_received(const CommandExecutorResult& info)
{
auto entry = std::make_shared<CommandExecutorResult>(info);
entries.push_back(entry);
// update tree model
Gtk::TreeRow row = *(list_store->append());
row[col_num] = entries.size();
row[col_command] = info.command + " " + info.parameters;
row[col_entry] = entry;
// if visible, set the selection to it
if (auto* treeview = this->lookup_widget<Gtk::TreeView*>("command_list_treeview")) {
selection->select(row);
treeview->scroll_to_row(list_store->get_path(row));
}
}
bool GscExecutorLogWindow::on_delete_event([[maybe_unused]] GdkEventAny* e)
{
on_window_close_button_clicked();
return true; // event handled
}
void GscExecutorLogWindow::on_window_close_button_clicked()
{
this->hide(); // hide only, don't destroy
}
void GscExecutorLogWindow::on_window_save_current_button_clicked()
{
if (selection->count_selected_rows() == 0)
return;
Gtk::TreeIter iter = selection->get_selected();
std::shared_ptr<CommandExecutorResult> entry = (*iter)[col_entry];
static std::string last_dir;
if (last_dir.empty()) {
last_dir = rconfig::get_data<std::string>("gui/drive_data_open_save_dir");
}
int result = 0;
Glib::RefPtr<Gtk::FileFilter> specific_filter = Gtk::FileFilter::create();
specific_filter->set_name(_("Text Files"));
specific_filter->add_pattern("*.txt");
Glib::RefPtr<Gtk::FileFilter> all_filter = Gtk::FileFilter::create();
all_filter->set_name(_("All Files"));
all_filter->add_pattern("*");
#if GTK_CHECK_VERSION(3, 20, 0)
std::unique_ptr<GtkFileChooserNative, decltype(&g_object_unref)> dialog(gtk_file_chooser_native_new(
_("Save Data As..."), this->gobj(), GTK_FILE_CHOOSER_ACTION_SAVE, nullptr, nullptr),
&g_object_unref);
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog.get()), TRUE);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj());
if (!last_dir.empty())
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog.get()), last_dir.c_str());
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog.get()), ".txt");
result = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.get()));
#else
Gtk::FileChooserDialog dialog(*this, _("Save Data As..."),
Gtk::FILE_CHOOSER_ACTION_SAVE);
// Add response buttons the the dialog
dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
dialog.set_do_overwrite_confirmation(true);
dialog.add_filter(specific_filter);
dialog.add_filter(all_filter);
if (!last_dir.empty())
dialog.set_current_folder(last_dir);
dialog.set_current_name(".txt");
// Show the dialog and wait for a user response
result = dialog.run(); // the main cycle blocks here
#endif
// Handle the response
switch (result) {
case Gtk::RESPONSE_ACCEPT:
{
std::string file;
#if GTK_CHECK_VERSION(3, 20, 0)
file = app_ustring_from_gchar(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog.get())));
last_dir = hz::fs::u8path(file).parent_path().u8string();
#else
file = dialog.get_filename(); // in fs encoding
last_dir = dialog.get_current_folder(); // save for the future
#endif
rconfig::set_data("gui/drive_data_open_save_dir", last_dir);
if (file.rfind(".txt") != (file.size() - std::strlen(".txt"))) {
file += ".txt";
}
auto ec = hz::fs_file_put_contents(hz::fs::u8path(file), entry->std_output);
if (ec) {
gui_show_error_dialog(_("Cannot save data to file"), ec.message(), this);
}
break;
}
case Gtk::RESPONSE_CANCEL: case Gtk::RESPONSE_DELETE_EVENT:
// nothing, the dialog is closed already
break;
default:
debug_out_error("app", DBG_FUNC_MSG << "Unknown dialog response code: " << result << ".\n");
break;
}
}
void GscExecutorLogWindow::on_window_save_all_button_clicked()
{
// complete libdebug output + execution logs
std::ostringstream exss;
exss << "\n------------------------- LIBDEBUG LOG -------------------------\n\n\n";
exss << app_get_debug_buffer_str() << "\n\n\n";
exss << "\n\n\n------------------------- EXECUTION LOG -------------------------\n\n\n";
for (std::size_t i = 0; i < entries.size(); ++i) {
exss << "\n\n\n------------------------- EXECUTED COMMAND " << (i+1) << " -------------------------\n\n";
exss << "\n---------------" << "Command" << "---------------\n";
exss << entries[i]->command << "\n";
exss << "\n---------------" << "Parameters" << "---------------\n";
exss << entries[i]->parameters << "\n";
exss << "\n---------------" << "STDOUT" << "---------------\n";
exss << entries[i]->std_output << "\n\n";
exss << "\n---------------" << "STDERR" << "---------------\n";
exss << entries[i]->std_error << "\n\n";
exss << "\n---------------" << "Error Message" << "---------------\n";
exss << entries[i]->error_message << "\n\n";
}
static std::string last_dir;
if (last_dir.empty()) {
last_dir = rconfig::get_data<std::string>("gui/drive_data_open_save_dir");
}
int result = 0;
Glib::RefPtr<Gtk::FileFilter> specific_filter = Gtk::FileFilter::create();
specific_filter->set_name(_("Text Files"));
specific_filter->add_pattern("*.txt");
Glib::RefPtr<Gtk::FileFilter> all_filter = Gtk::FileFilter::create();
all_filter->set_name(_("All Files"));
all_filter->add_pattern("*");
#if GTK_CHECK_VERSION(3, 20, 0)
std::unique_ptr<GtkFileChooserNative, decltype(&g_object_unref)> dialog(gtk_file_chooser_native_new(
_("Save Data As..."), this->gobj(), GTK_FILE_CHOOSER_ACTION_SAVE, nullptr, nullptr),
&g_object_unref);
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog.get()), TRUE);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj());
if (!last_dir.empty())
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog.get()), last_dir.c_str());
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog.get()), ".txt");
result = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.get()));
#else
Gtk::FileChooserDialog dialog(*this, _("Save Data As..."),
Gtk::FILE_CHOOSER_ACTION_SAVE);
// Add response buttons the the dialog
dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
dialog.set_do_overwrite_confirmation(true);
dialog.add_filter(specific_filter);
dialog.add_filter(all_filter);
if (!last_dir.empty())
dialog.set_current_folder(last_dir);
dialog.set_current_name(".txt");
// Show the dialog and wait for a user response
result = dialog.run(); // the main cycle blocks here
#endif
// Handle the response
switch (result) {
case Gtk::RESPONSE_ACCEPT:
{
std::string file;
#if GTK_CHECK_VERSION(3, 20, 0)
file = app_ustring_from_gchar(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog.get())));
last_dir = hz::fs::u8path(file).parent_path().u8string();
#else
file = dialog.get_filename(); // in fs encoding
last_dir = dialog.get_current_folder(); // save for the future
#endif
rconfig::set_data("gui/drive_data_open_save_dir", last_dir);
if (file.rfind(".txt") != (file.size() - std::strlen(".txt"))) {
file += ".txt";
}
auto ec = hz::fs_file_put_contents(hz::fs::u8path(file), exss.str());
if (ec) {
gui_show_error_dialog(_("Cannot save data to file"), ec.message(), this);
}
break;
}
case Gtk::RESPONSE_CANCEL: case Gtk::RESPONSE_DELETE_EVENT:
// nothing, the dialog is closed already
break;
default:
debug_out_error("app", DBG_FUNC_MSG << "Unknown dialog response code: " << result << ".\n");
break;
}
}
void GscExecutorLogWindow::on_clear_command_list_button_clicked()
{
entries.clear();
list_store->clear(); // this will unselect & clear widgets too.
}
void GscExecutorLogWindow::on_tree_selection_changed()
{
this->clear_view_widgets();
if (selection->count_selected_rows() > 0) {
Gtk::TreeIter iter = selection->get_selected();
Gtk::TreeRow row = *iter;
std::shared_ptr<CommandExecutorResult> entry = row[col_entry];
if (auto* output_textview = this->lookup_widget<Gtk::TextView*>("output_textview")) {
Glib::RefPtr<Gtk::TextBuffer> buffer = output_textview->get_buffer();
if (buffer) {
buffer->set_text(app_make_valid_utf8_from_command_output(entry->std_output));
Glib::RefPtr<Gtk::TextTag> tag;
Glib::RefPtr<Gtk::TextTagTable> table = buffer->get_tag_table();
if (table)
tag = table->lookup("font");
if (!tag)
tag = buffer->create_tag("font");
tag->property_family() = "Monospace";
buffer->apply_tag(tag, buffer->begin(), buffer->end());
}
}
if (auto* command_entry = this->lookup_widget<Gtk::Entry*>("command_entry")) {
std::string cmd_text = entry->command + " " + entry->parameters;
command_entry->set_text(app_make_valid_utf8_from_command_output(cmd_text));
}
if (auto* window_save_current_button = this->lookup_widget<Gtk::Button*>("window_save_current_button"))
window_save_current_button->set_sensitive(true);
}
}
/// @}