Commit c2172c87 authored by Arnaud Blanchard's avatar Arnaud Blanchard

Graphic tool to monitors blc_channels.

parent b3453aa7
# Set the minimum version of cmake required to build this project
cmake_minimum_required(VERSION 2.6)
project(gtk_channels)
find_package(blc_channel REQUIRED)
find_package(blc_program REQUIRED)
find_package(blc_process REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
pkg_check_modules(GTK3_SOURCE_VIEW REQUIRED gtksourceview-4)
pkg_check_modules(GTKMM gtkmm-3.0 REQUIRED)
pkg_check_modules(VTE3 vte-2.91 REQUIRED) #libvtemm is not yet common
#pkg_check_modules(GTKMMPLPLOT REQUIRED gtkmm-plplot-2.0)
add_definitions(-std=c++14 ${BL_DEFINITIONS} ${GTK3_CFLAGS_OTHER} )
include_directories(${BL_INCLUDE_DIRS} ${GTK3_INCLUDE_DIRS} ${GTK3_SOURCE_VIEW_INCLUDE_DIRS} ${VTE_INCLUDE_DIRS} ${GTKMM_INCLUDE_DIRS} ${VTE3_INCLUDE_DIRS})
link_directories(${GTK3_LIBRARY_DIRS} ${VTE_LIBRARY_DIRS} /usr/local/lib)
add_executable(gtk_channels gtk_channels.cpp Channel.cpp Process.cpp parse_help.cpp)
target_link_libraries(gtk_channels ${BL_LIBRARIES} ${GTK3_LIBRARIES} ${GTK3_SOURCE_VIEW_LIBRARIES} ${VTE_LIBRARIES} ${GTKMM_LIBRARIES} ${VTE3_LIBRARIES})
//
// Channel.cpp
// e_bash
//
// Created by Arnaud Blanchard on 29/09/2019.
//
#include "Process.h"
#include "Channel.hpp"
#include "blc_channel.h"
#include "string"
#include <vector>
#include <forward_list>
#include <vte/vte.h>
#include <thread>
#include <iostream>
using namespace Gtk;
using namespace Glib; //signal_idle,RefPtr
using namespace std;
vector <char*> argv_vec;
struct ChannelMenu:Gtk::Menu{
ChannelMenu(Gtk::TreeView &tree_view);
};
struct ModelColumns : public TreeModel::ColumnRecord{
TreeModelColumn<int> id, pid;
TreeModelColumn<string> name, type, format, dimensions, icon;
TreeModelColumn<Process*> process;
ModelColumns(){
add(id);
add(name);
add(type);
add(format);
add(dimensions);
add(icon);
add(pid);
add(process);
}
void add_channel(RefPtr<TreeStore> &tree, blc_channel *channel){
uint32_t type_str, format_str;
char dims_str[256];
TreeModel::Row childrow;
auto row = *(tree->append()); //* is because it is an iterator and we want the content of the ref.
//row[id] = channel->id;
row[name] = channel->name;
row[type] = string(UINT32_TO_STRING(type_str, channel->type), 4);
row[format] = string(UINT32_TO_STRING(format_str, channel->format), 4);
channel->sprint_dims(dims_str, 256);
row[dimensions] = dims_str;
row[process]=nullptr;
row[id]=channel->id;
// row[icon] = Gtk::Stock::YES.id; //dialog-information";
}
}m_Columns;
static void refresh_channels(void* user_data){
auto channel_widget = (ChannelWidget *)user_data;
signal_idle().connect([channel_widget](){ //important to use an idle otherwise Thread problems
channel_widget->refresh_channel_list();
return false;
});
}
ChannelMenu::ChannelMenu(TreeView &tree_view){
MenuItem *item = new MenuItem("_Remove", true);
append(*item);
accelerate(*this);
show_all();
item->signal_activate().connect([&tree_view](){
tree_view.get_selection()->selected_foreach_iter([](const TreeModel::iterator& iter){
TreeModel::Row row = *iter;
string name= row[m_Columns.name];
blc_remove_channel_with_name(name.data());
});;
});
}
void ChannelWidget::add_process( TreeModel::Row &row){
TreeModel::Row childrow;
string command;
Process *process = new Process(blaar_dir+"/bin/", "o_gnuplot");
process->add_arg(((string)row[m_Columns.name]).data());
process->run();
command=blc_program_name;
for (auto arg = process->arg_vector.begin()+1; arg!=process->arg_vector.end()-1; arg++){
command+=" ";
command+=*arg;
}
childrow = *(tree_store->append(row.children()));
childrow[m_Columns.name]=command;
childrow[m_Columns.icon]="utilities-terminal";
childrow[m_Columns.process]= process;
show_all_children();
}
void ChannelWidget::display_process( TreeModel::Row &row){
Process *process=row[m_Columns.process];
process->display_terminal();
}
ChannelWidget::ChannelWidget(){
CellRendererPixbuf cellRendererPixBuf;
this->set_enable_search();
// this->m
blaar_dir=getenv("BLAAR_DIR");
if (blaar_dir.empty()) EXIT_ON_ERROR("Your environment variable 'BLAAR_DIR' is not defined.");
tree_store = TreeStore::create(m_Columns);
set_model(tree_store);
append_column("Name", m_Columns.name);
append_column("Type", m_Columns.type);
append_column("Format", m_Columns.format);
append_column("Dimensions", m_Columns.dimensions);
int numcols = append_column("image ", cellRendererPixBuf);
get_column(numcols-1)->add_attribute(cellRendererPixBuf, "icon-name", m_Columns.icon);
refresh_channel_list();
blc_channel_check_for_event(refresh_channels, this);
get_selection()->set_mode(SELECTION_MULTIPLE);
show_all_children();
// set_events(Gdk::BUTTON_PRESS_MASK);
signal_row_activated().connect([this](const TreeModel::Path& path, TreeViewColumn* column){
TreeIter iter=this->tree_store->get_iter(path);
TreeModel::Row row = *iter;
if (tree_store->iter_depth(iter) == 0) this->add_process(row);
else display_process(row);
});
get_selection()->signal_changed().connect([](){
return true;
});
signal_button_release_event().connect([this](GdkEventButton const *event){
if (event->button == 3){
ChannelMenu *m_Menu_Popup = new ChannelMenu(*this);
m_Menu_Popup->popup_at_pointer((GdkEvent*)event);
}
return true;
});
}
/*
void ChannelWidget::menu_add_program_item(Menu &menu, string name, string program){
MenuItem *item = new MenuItem(name);
item->signal_activate().connect([this](){
auto refSelection = get_selection();
if (refSelection) {
TreeModel::iterator iter = refSelection->get_selected();
TreeModel::Row row = *iter;
for (TreeModel::iterator iter = refSelection-> (); ){
string name= row[m_Columns.name];
blc_remove_channel_with_name(name.data());
}
}
});
menu.append(*item);
};*/
void ChannelWidget::refresh_channel_list(){
bool found;
int channels_nb;
blc_channel *channel_infos=nullptr;
channels_nb = blc_channel_get_all_infos(&channel_infos);
list<TreeModel::iterator> list_iters_without_channels;
/* Generate a list of iter on row to be able to remove the row if the channel does not exist anymore*/
tree_store->foreach_iter([&list_iters_without_channels, this](TreeModel::iterator const &iter){
if (this->tree_store->iter_depth(iter)==0) list_iters_without_channels.push_back(iter); //copy iter
return false;
});
for(blc_channel *channel=channel_infos; channel != channel_infos+channels_nb; channel++){
found=false;
auto list_iter=list_iters_without_channels.begin();
while(list_iter != list_iters_without_channels.end()){
TreeModel::Row row = *(*list_iter);
if (row[m_Columns.id]==channel->id){
found=true;
list_iter=list_iters_without_channels.erase(list_iter);
}
else list_iter++;
}
if (found==false) m_Columns.add_channel(tree_store, channel);
}
for(auto list_iter: list_iters_without_channels){
tree_store->erase(*list_iter);
}
this->show_all_children();
free(channel_infos);
}
//
// Channel.hpp
// e_bash
//
// Created by Arnaud Blanchard on 29/09/2019.
//
#ifndef Channel_hpp
#define Channel_hpp
#include <gtkmm.h>
struct ChannelWidget:Gtk::TreeView{
std::string blaar_dir;
Glib::RefPtr<Gtk::TreeStore> tree_store;
ChannelWidget();
void add_process(Gtk::TreeModel::Row &row);
void display_process(Gtk::TreeModel::Row &row);
void refresh_channel_list();
};
#endif /* Channel_hpp */
//
// Graph.cpp
// e_bash
//
// Created by Arnaud Blanchard on 25/08/2017.
//
//
#include "Graph.h"
//
// Link.cpp
// e_bash
//
// Created by Arnaud Blanchard on 30/08/2017.
//
//
#include "common.h"
#include "Link.h"
#include "Process_old.h"
#include <blc_core.h> //APPEND_ITEM
#include <gtk/gtk.h>
Link::Link(Script *script, Process_old *input_process, char *output_channel, Process_old *output_process, char *input_channel, int sync):sync(sync), script(script), selected(0), input_process(input_process), output_process(output_process){
Link *link=this;
// printf("Input program: %s nodes:%d:%s\n", input_process->program->name, output_process->program->output_channels_nb, output_channel);
// printf("Out program: %s nodes:%d:%s\n", output_process->program->name, output_process->program->input_channels_nb, input_channel);
asprintf(&name, "link_%d", script->links_nb);
// edge=agedge(current_script->main_graph, input_process->node, output_process->node, name, 1);
// agsafeset(edge, (char*)"label", (char*)name, (char*)"");
if (sync==0) {
// agsafeset(edge, (char*)"style", (char*)"dashed", (char*)"");
// agsafeset(edge, (char*)"constraint", (char*)"false", (char*)"");
}
/* agsafeset(edge,(char*)"tailport",output_channel, (char*)"");
agsafeset(edge,(char*)"headport",input_channel, (char*)"");*/
APPEND_ITEM(&script->links, &script->links_nb, &link);
if (input_process->output_links_nb==0 && output_process->input_links_nb==0) output_process->input_process=input_process;
APPEND_ITEM(&input_process->output_links, &input_process->output_links_nb, &link);
APPEND_ITEM(&output_process->input_links, &output_process->input_links_nb, &link);
}
Link::~Link(){
Link *tmp_link=this;
REMOVE_ITEM(&input_process->output_links, &input_process->output_links_nb, &tmp_link);
REMOVE_ITEM(&output_process->input_links, &output_process->input_links_nb, &tmp_link);
if (input_process->output_links_nb==0 && output_process->input_links_nb==0) output_process->input_process=NULL;
if (selected) REMOVE_ITEM(&script->selected_links,&script->selected_links_nb, &tmp_link);
REMOVE_ITEM(&script->links,&script->links_nb, &tmp_link);
// agdelete(script->main_graph, edge);
free(name);
}
Link *Link::dialog(Script *script, Process_old *input_process, Process_old *output_process){
char **channel;
Link *link;
GtkWidget *dialog, *box, *in_combobox, *out_combobox, *sync_checkbox;
GtkWidget *grid;
dialog=gtk_dialog_new_with_buttons("New link", NULL, GTK_DIALOG_MODAL, "_OK", GTK_RESPONSE_ACCEPT, "_Cancel", GTK_RESPONSE_REJECT, NULL);
box=gtk_dialog_get_content_area(GTK_DIALOG(dialog));
grid=gtk_grid_new();
in_combobox=gtk_combo_box_text_new();
out_combobox=gtk_combo_box_text_new();
sync_checkbox=gtk_check_button_new();
gtk_grid_attach(GTK_GRID(grid), gtk_label_new(input_process->program->name), 0, 0, 1, 1);
FOR_EACH(channel, input_process->program->output_channels, input_process->program->output_channels_nb) gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(in_combobox), NULL, *channel);
gtk_grid_attach(GTK_GRID(grid), gtk_label_new(output_process->program->name), 0, 1, 1, 1);
FOR_EACH(channel, output_process->program->input_channels, output_process->program->input_channels_nb) gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(out_combobox), NULL, *channel);
gtk_grid_attach(GTK_GRID(grid), gtk_label_new("Synchronize"), 0, 2, 1, 1);
gtk_combo_box_set_active(GTK_COMBO_BOX(in_combobox), 0);
gtk_combo_box_set_active(GTK_COMBO_BOX(out_combobox), 0);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sync_checkbox), 1);
gtk_grid_attach(GTK_GRID(grid), in_combobox, 1, 0, 1, 1);
gtk_grid_attach(GTK_GRID(grid), out_combobox, 1, 1, 1, 1);
gtk_grid_attach(GTK_GRID(grid), sync_checkbox, 1, 2, 1, 1);
gtk_container_add(GTK_CONTAINER(box), grid);
gtk_widget_show_all(dialog);
switch (gtk_dialog_run(GTK_DIALOG(dialog)))
{
case GTK_RESPONSE_ACCEPT:
link=new Link(script, input_process, gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(in_combobox)),
output_process, gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(out_combobox)),
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sync_checkbox)));
break;
case GTK_RESPONSE_CANCEL:
link=NULL;
break;
}
gtk_widget_destroy(dialog);
return link;
}
void Link::edit(){
dialog(script, input_process, output_process);
}
void Link::select(){
Link *tmp_link;
if (selected==0){
// agsafeset( edge, (char*) "penwidth", (char*) "3", (char*)"1");
// agsafeset( edge, (char*) "color", (char*) "red", "black");
tmp_link=this;
APPEND_ITEM(&script->selected_links, &script->selected_links_nb, &tmp_link);
selected=1;
}
}
void Link::unselect(){
Link *tmp_link;
if (selected){
/* if (sync) agset((void*) edge, (char*) "style", (char*) "dashed");
else agset((void*) edge, (char*) "style", (char*) "solid");*/
// agsafeset((void*) edge, (char*) "color", (char*) "black", "black");
// agsafeset( edge, (char*) "penwidth", (char*) "1", (char*)"1");
tmp_link=this;
REMOVE_ITEM(&script->selected_links, &script->selected_links_nb, &tmp_link);
selected=0;
}
}
//
// Link.hpp
// e_bash
//
// Created by Arnaud Blanchard on 30/08/2017.
//
//
#ifndef LINK_H
#define LINK_H
#include "Process_old.h"
#include "Script.h"
class Script;
class Process_old;
class Link{
int sync;
Script *script;
public:
char *name;
int selected;
Process_old *input_process, *output_process;
Link(Script *script, Process_old *input_process, char *output_channel, Process_old *output_process, char *input_channel, int sync);
~Link();
void edit();
void select();
void unselect();
static Link *dialog(Script *script, Process_old *input_process, Process_old *output_process);
};
#endif /* Link_hpp */
//
// menu.cpp
// e_bash
//
// Created by Arnaud Blanchard on 23/08/2017.
//
//
#include "Script.h" //current_script
#include "Menu.h"
#include "common.h"
#include "blc_program.h"
#include "parse_help.h"
#include <stdio.h>
#include <gtk/gtk.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <libgen.h> //basename
void action_input_cb(GtkWidget *widget, Program *menu_action)
{
char *argv[2];
char path[PATH_MAX+1];
(void)widget;
// SPRINTF(path, "%s/%s", blaar_bin_dir, menu_action->program_name);
argv[0]=basename(path);
argv[1]=NULL;
// execute_process_with_iter(create_command_line(dirname(path), argv));
}
static void add_process_cb(GtkWidget *widget, Program *program){
(void)widget;
current_script->add_process(program, NULL);
}
static void process_dialog_cb(GtkWidget *widget, Program *program){
(void)widget;
current_script->add_process(program, program->argv_dialog());
}
static int possible_default(int positional_arguments_nb, PROGRAM_TYPE type){
return ((positional_arguments_nb==0 && type==INPUT_PROGRAM) || (positional_arguments_nb==1 && type==FUNCTION_PROGRAM));
}
/**Read the list of programs in directory_str and add it to the menu if the type (INPUT_PROGRAM, FUNCTION_PROGRAM) match.
*/
static void add_directory_programs(GtkWidget *menu_shell, char const *directory_str, PROGRAM_TYPE type){
GtkWidget *default_item, *action_button, *menu, *menu_item;
DIR *directory;
struct stat status;
struct dirent program_dirent, *result;
char const *description;
char token[NAME_MAX+1];
int positional_arguments_nb, optional_arguments_nb;
Program *program;
struct blc_optional_argument *optional_arguments;
struct blc_positional_argument *positional_arguments;
SYSTEM_ERROR_CHECK(directory = opendir(directory_str), NULL, "Opening dir '%s'.", directory_str);
do{
if (readdir_r(directory, &program_dirent, &result) != 0) EXIT_ON_ERROR("readdir_r error parsing dir.");
else if (result){
if (result->d_type==DT_REG){
if (((type==INPUT_PROGRAM) && ( strncmp(result->d_name, "i_", 2)==0)) || ((type==FUNCTION_PROGRAM) && (( strncmp(result->d_name, "f_", 2)==0) || ( strncmp(result->d_name, "o_", 2)==0)))){
fstat(result->d_reclen, &status);
description=blc_parse_help(directory_str, result->d_name, &optional_arguments, &optional_arguments_nb, &positional_arguments, &positional_arguments_nb, NULL);
if (positional_arguments_nb || optional_arguments_nb>1){
program=new Program(result->d_name, type, description, optional_arguments, optional_arguments_nb, positional_arguments, positional_arguments_nb);
if (possible_default(positional_arguments_nb, type))
{
menu=gtk_menu_new();
menu_item = gtk_menu_item_new_with_label(result->d_name);
gtk_container_add(GTK_CONTAINER(menu_shell), menu_item);
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu);
default_item = gtk_menu_item_new_with_label("default");
action_button = gtk_menu_item_new_with_label("options ...");
gtk_container_add(GTK_CONTAINER(menu), default_item);
gtk_container_add(GTK_CONTAINER(menu), action_button);
g_signal_connect(G_OBJECT(default_item), "activate", G_CALLBACK(add_process_cb), program);
}
else{
SPRINTF(token, "%s ...",result->d_name);
action_button = gtk_menu_item_new_with_label(token);
gtk_container_add(GTK_CONTAINER(menu_shell), action_button);
}
g_signal_connect(G_OBJECT(action_button), "activate", G_CALLBACK(process_dialog_cb), program);
}
else{
if (possible_default(positional_arguments_nb, type)){
default_item = gtk_menu_item_new_with_label(result->d_name);
program=new Program(result->d_name, type, description);
gtk_container_add(GTK_CONTAINER(menu_shell), default_item);
g_signal_connect(G_OBJECT(default_item), "activate", G_CALLBACK(add_process_cb), program);
}
}
}
}
}
}while(result);
closedir(directory);
}
BlaarMenu::BlaarMenu(PROGRAM_TYPE menu_type){
widget=gtk_menu_new();
if (menu_type==INPUT_PROGRAM){
add_directory_programs(widget, blaar_bin_dir, INPUT_PROGRAM);
add_directory_programs(widget, blaar_scripts_dir, INPUT_PROGRAM);
}
else if (menu_type==FUNCTION_PROGRAM){
add_directory_programs(widget, blaar_bin_dir, FUNCTION_PROGRAM);
add_directory_programs(widget, blaar_scripts_dir, FUNCTION_PROGRAM);
}
else EXIT_ON_ERROR("Wrong option '%d'", menu_type);
gtk_widget_show_all(widget);
}
//
// Menu.h
// e_bash
//
// Created by Arnaud Blanchard on 25/08/2017.
//
//
#ifndef MENU_H
#define MENU_H
#include "common.h"
#include <gtk/gtk.h>
struct BlaarMenu{
BlaarMenu(PROGRAM_TYPE menu_type);
GtkWidget *widget;
};
#endif
//
// Plot.cpp
// e_bash
//
// Created by Arnaud Blanchard on 30/09/2019.
//
#include "Plot.hpp"
Plot::Plot(){
}
//
// Plot.hpp
// e_bash
//
// Created by Arnaud Blanchard on 30/09/2019.
//
#ifndef Plot_hpp
#define Plot_hpp
#include <stdio.h>
#include <gtkmm-plplot.h>
struct Plot{
Gtk::PLplot::Plot2D plot;
Gtk::PLplot::Canvas canvas(plot);
Plot();
// plot.
};
#endif /* Plot_hpp */
//
// Process.cpp
// e_bash
//
// Created by Arnaud Blanchard on 30/08/2017.
//
//
#include "Process.h"
#include "blc_core.h"
#include <errno.h>
#include <unistd.h> //execve
#include <thread>
#include <vte/vte.h>
#include <sys/pipe.h>
#include <gtkmm.h>
using namespace std;
using namespace Gtk;
void TerminalSpawnAsyncCallback (VteTerminal *terminal, GPid pid, GError *error, gpointer user_data){
char **argv=(char**)user_data;
if (error){
fprintf(stderr, "Process error: '%s'\n", error->message);
for (auto arg=argv; *arg!=nullptr; arg++){
fprintf(stderr, "%s ", *arg);
}
fprintf(stderr, "\n");
}
else fprintf(stderr, "Success pid %d\n", pid);
};
Process::Process(string const &directory, string const &program):directory(directory),program_name(program){
command_path=directory+program;
arg_vector.emplace_back(command_path.data());
};
Process::~Process(){
for (char const *&arg: arg_vector ){
free(&arg);
}
};
void Process::add_arg(string const &arg){
arg_vector.emplace_back(strdup(arg.data()));
}
void Process::run(){
terminal=VTE_TERMINAL(vte_terminal_new());
arg_vector.emplace_back(nullptr);
vte_terminal_spawn_async(terminal, VTE_PTY_DEFAULT , nullptr, (char**)arg_vector.data(), nullptr, G_SPAWN_DEFAULT, nullptr, nullptr, nullptr, 1000, nullptr, TerminalSpawnAsyncCallback, arg_vector.data());
}
void Process::display_terminal(){
Widget *mm_terminal=Glib::wrap(GTK_WIDGET(terminal));