Commit adf77fc7 authored by Arnaud Blanchard's avatar Arnaud Blanchard
Browse files

Toward C++17 impementation

parent 15e91361
# Set the minimum version of cmake required to build this project
cmake_minimum_required(VERSION 3.3)
cmake_minimum_required(VERSION 2.6)
project(gtk_channels)
#set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "/usr/local/Cellar/include-what-you-use/0.12/bin/include-what-you-use;-Xiwyu;--verbose=3;")
find_package(blc_channel REQUIRED)
find_package(blc_image REQUIRED)
find_package(blc_image REQUIRED) #Used to detect image format in channels
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(GTK3 gtk+-3.0 REQUIRED)
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)
pkg_check_modules(VTE3 vte-2.91 REQUIRED) #libvtemm is not yet common and stable we
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 ChannelWidget.cpp ContextMenu.cpp ChannelColumns.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})
add_definitions(-std=c++17 -pedantic ${BL_DEFINITIONS})
include_directories(${BL_INCLUDE_DIRS} ${GTKMM_INCLUDE_DIRS} ${VTE3_INCLUDE_DIRS})
link_directories(${GTKMM_LIBRARY_DIRS} ${VTE3_LIBRARY_DIRS})
add_executable(gtk_channels gtk_channels.cpp ChannelWidget.cpp ContextMenu.cpp ChannelColumns.cpp Command.cpp Process.cpp parse_help.cpp)
target_link_libraries(gtk_channels ${BL_LIBRARIES} ${GTKMM_LIBRARIES} ${VTE3_LIBRARIES})
......@@ -5,20 +5,16 @@
// Created by Arnaud Blanchard on 29/09/2019.
//
#include "ChannelColumns.hpp"
#include "Process.h"
#include "ChannelWidget.hpp"
#include "blc_channel.h"
#include "blc_image.h"
#include "ChannelColumns.hpp"
#include "ChannelWidget.hpp"
#include "Process.h"
#include "Command.hpp"
#include <numeric> //Accumulate
#include <string>
#include <vector>
#include <forward_list>
#include <vte/vte.h>
#include <thread>
#include <iostream>
#include <numeric>
using namespace Gtk;
using namespace Glib; //signal_idle,RefPtr
......@@ -37,9 +33,7 @@ ChannelColumns ChannelWidget::columns;
ChannelWidget::ChannelWidget(){
char const *blaar_dir=getenv("BLAAR_DIR");
if (blaar_dir==nullptr) EXIT_ON_ERROR("Your environment variable 'BLAAR_DIR' is not defined.");
program_dir=blaar_dir;
program_dir+="/bin";
bin_dir=string(blaar_dir)+"/bin";
append_column("Name", columns.name);
append_column("Type", columns.type);
......@@ -54,29 +48,62 @@ ChannelWidget::ChannelWidget(){
col->add_attribute(terminal_cell_renderer, "icon-name", columns.terminal_icon);
append_column(*col);
col = new TreeViewColumn(" ");
col->pack_start(play_pause_cell_renderer);
col->pack_start(stop_cell_renderer);
col = new TreeViewColumn(" ", play_pause_cell_renderer);
col->add_attribute(play_pause_cell_renderer, "icon-name", columns.play_pause_icon);
col->add_attribute(stop_cell_renderer, "icon-name", columns.stop_icon);
col->add_attribute(play_pause_cell_renderer, "sensitive", columns.play_pause_sensitive);
append_column(*col);
col = new TreeViewColumn(" ", stop_cell_renderer);
col->add_attribute(stop_cell_renderer, "icon-name", columns.stop_icon);
col->add_attribute(stop_cell_renderer, "sensitive", columns.stop_sensitive);
append_column(*col);
commands.emplace("graph", Command(bin_dir, "o_gnuplot", {}, [](vector<blc_channel const*> channels ){
if (channels.size()>1) return false;
}));
commands.emplace("graph history", Command(bin_dir, "o_gnuplot", {"--history=100"}, [](vector<blc_channel const*> channels ){
int total_length=channels[0]->total_length;
return all_of(channels.begin()+1,channels.end(),[total_length](blc_channel const *channel){
if (channel->dims_nb==1 && channel->total_length!=total_length) return false;
else if (channel->dims_nb==2) return false;
return true;
});
}));
commands.emplace("keyboard", Command(bin_dir, "i_keyboard", {"--debug"}, [](vector<blc_channel const*> channels ){
if (channels.size()==1 && channels[0]->total_length<=64) return true;
else return false;
}));
signal_row_activated().connect([this](const TreeModel::Path& path, TreeViewColumn* column){
int x, y;
TreeIter iter=this->tree_store->get_iter(path);
TreeModel::Row row = *iter;
Process *process=row[columns.process];
if (tree_store->iter_depth(iter) != 0){ //It is a process
if (column->get_cell_position(terminal_cell_renderer, x, y)){//x, y are not used but it allow to chack the existanace of the cell renderer
process->display_terminal();
}
else if (column->get_cell_position(play_pause_cell_renderer, x, y)){
process->signal(SIGTSTP);
switch (process->status){
case Process::running:
process->pause();
row[columns.play_pause_icon]="media-playback-start";
break;
case Process::paused:
process->cont();
row[columns.play_pause_icon]="media-playback-pause";
break;
case Process::stopped:
process->run();
break;
}
}
else if (column->get_cell_position(stop_cell_renderer, x, y)){
process->stop();
row [columns.stop_sensitive]=false;
}
}
});
......@@ -96,8 +123,7 @@ ChannelWidget::ChannelWidget(){
}
blc_channel *ChannelWidget::find_channel(int id){
blc_channel *channel;
channel= lower_bound(channels, channels+channels_nb, id, [](blc_channel const &a, int id){
blc_channel *channel= lower_bound(channels, channels+channels_nb, id, [](blc_channel const &a, int id){
return (a.id < id);
});
return channel;
......@@ -112,27 +138,21 @@ void ChannelWidget::after_launch(Process *process, std::vector<TreeModel::Path>
for (auto const &path : rows){
TreeNodeChildren node = tree_store->get_iter(path)->children();
TreeIter row_iter=tree_store->append(node);
process_rows->emplace_back(row_iter);
process_rows->emplace_back(row_iter);
TreeRow row=*row_iter;
row[columns.name]=command;
row[columns.terminal_icon]="utilities-terminal";
row[columns.play_pause_icon]="media-playback-pause";
row[columns.play_pause_sensitive]=true;
row[columns.stop_icon]="media-playback-stop";
row[columns.stop_sensitive]=true;
row[columns.process]= process;
expand_row(path, true);
// process_rows->push_back(row_iter);
}
process->on_quit([process_rows](int status){
for (auto &row_iter : *process_rows){
TreeRow row=*row_iter;
// row[columns.name]=command;
// row[columns.terminal_icon]="utilities-terminal";
if (status==0) row[columns.terminal_icon]="process-stop";
else row[columns.terminal_icon]="dialog-error";
row[columns.play_pause_icon]="media-playback-start";
......@@ -145,31 +165,63 @@ void ChannelWidget::after_launch(Process *process, std::vector<TreeModel::Path>
void ChannelWidget::on_quit(Process *process, int status, std::vector<TreeModel::Path> rows){
for (auto const &path : rows){
TreeRow row = *tree_store->get_iter(path);
// row[columns.name]=command;
// row[columns.terminal_icon]="utilities-terminal";
// row[columns.name]=command;
// row[columns.terminal_icon]="utilities-terminal";
if (status==0) row[columns.terminal_icon]="process-stop";
else row[columns.terminal_icon]="dialog-error";
row[columns.stop_icon]="media-playback-start";
expand_row(path, true);
}
}
/*
void ChannelWidget::create_and_execute_command(string program_name, initializer_list<const char*> arguments){
commands.emplace_back(bin_dir, program_name, arguments));
auto command=&commands.back();
command->execute();
}*/
ContextMenu *ChannelWidget::create_menu(){
bool only_channels=true, can_keyboard=true, can_image=true, can_graph=true, can_history_graph=true;
menu=new ContextMenu(program_dir);
menu->row_selection=get_selection();
menu->row_selection->selected_foreach_iter ([&](TreeModel::iterator const &iter){
if (tree_store->iter_depth(iter)==0){
blc_channel *channel=find_channel((*iter)[columns.id]);
if (channel->total_length > 64) can_keyboard=false;
if (blc_image_get_bytes_per_pixel(channel)==0) can_image=false;
if (channel->dims_nb > 1) can_history_graph=false;
else if (channel->dims_nb > 2) can_graph=false;
menu->selected_channels.emplace_back(channel);
/*
void ContextMenu::add_program(char const *menu_name, std::string program_name, initializer_list<const char*> const optional_arguments, function <void(Process *process)> after_launch){
auto item=new MenuItem(menu_name);
auto args_vec=make_shared<vector<const char*>>(optional_arguments); //This will be destroyed by process
append(*item);
item->signal_activate().connect([this, args_vec, program_name, after_launch]{
args_vec->reserve(args_vec->size()+selected_channels.size());
for(auto const &channel:selected_channels){
args_vec->push_back(strdup(channel->name));
}
else only_channels=false;
auto process = new Process(program_dir, program_name, args_vec);
process->run(after_launch);
});
}*/
/*
item->signal_activate().connect([this, args_vec, program_name, after_launch]{
args_vec->reserve(args_vec->size()+selected_channels.size());
for(auto const &channel:selected_channels){
args_vec->push_back(strdup(channel->name));
}
auto process = new Process(program_dir, program_name, args_vec);
process->run(after_launch);
});
*/
ContextMenu *ChannelWidget::create_menu(){
bool only_channels=true;
vector<Command*> context_commands;
vector<blc_channel const *> selected_channels;
menu=new ContextMenu();
menu->row_selection=get_selection();
get_selection()->selected_foreach_iter ([&](TreeModel::iterator const &iter){
if (tree_store->iter_depth(iter)==0){
blc_channel *channel=find_channel((*iter)[columns.id]);
selected_channels.emplace_back(channel);
}
else only_channels=false;
});
if (only_channels){
menu->add_item("_Remove", true, [this]{
......@@ -179,15 +231,14 @@ ContextMenu *ChannelWidget::create_menu(){
});
menu->add_separator();
Glib::RefPtr<TreeModel> tree_model=tree_store;
/* This is the simpler I can do for a callback
It means this->after_launch( first_argument (i.e. process)) will be called.*/
auto after_launch_callback=bind(&ChannelWidget::after_launch, this, placeholders::_1, menu->row_selection->get_selected_rows(tree_model));//This is a way to pass the rows, without process needing to know what is row
if (can_keyboard) menu->add_program("keyboard", "i_keyboard", {"--display","--output"}, after_launch_callback);
if (can_image) menu->add_program("image", "o_gtk_image", {}, after_launch_callback);
if (can_graph) menu->add_program("graph", "o_gnuplot", {}, after_launch_callback);
if (can_history_graph) menu->add_program("history graph", "o_gnuplot", {"--history=100"}, after_launch_callback);
for ( const auto& [name, command]:commands){
Command const *command_ptr=&command; //wihout ptr, command cannot be pass to lambda
if (command.valid_channels(selected_channels));
menu->add_item(name, false, [this, command_ptr, selected_channels](){
auto command =used_commands.emplace_back(*command_ptr, selected_channels);
auto process=new Process(command.args.data(), [](Process*){});
});
}
}
menu->show_all();
return menu;
......@@ -201,16 +252,15 @@ void ChannelWidget::refresh_channel_list(){
/// We define a functor to compare blc_channel and TreeIter by id
struct compare_id{
bool operator()(blc_channel const &a, TreeIter const &b) { return a.id < (*b)[columns.id]; };
bool operator()(TreeIter const &a, blc_channel const &b) { return (*a)[columns.id]< b.id ; };
};
vector<blc_channel> news_channels;
vector<TreeIter> channels_to_remove;
bool operator()(TreeIter const &a, blc_channel const &b) { return (*a)[columns.id]< b.id ; };
};
vector<blc_channel> news_channels;
vector<TreeIter> channels_to_remove;
FREE(channels);
tie(channels, channels_nb)=blc_channel::get_all_infos();
FREE(channels);
tie(channels, channels_nb)=blc_channel::get_all_infos();
//We get children to be able to use iterator
//We get children to be able to use iterator
auto children=tree_store->children();
//We get a list of new channels
......
......@@ -12,11 +12,13 @@
#include <gtkmm.h>
#include <string>
#include <deque>
#include <set>
#include "blc_channel.h"
#include "ChannelColumns.hpp"
#include "ContextMenu.hpp"
#include "Command.hpp"
#include <unordered_map>
#include <map>
struct ChannelWidget:Gtk::TreeView{
blc_channel* channels; //This structure is coherent with blc_channel implementation
......@@ -24,13 +26,16 @@ struct ChannelWidget:Gtk::TreeView{
Gtk::CellRendererPixbuf terminal_cell_renderer, play_pause_cell_renderer, stop_cell_renderer;
Glib::RefPtr<Gtk::TreeStore> tree_store;
int channels_nb;
std::string program_dir;
std::string bin_dir;
std::map<std::string, Command> commands;
std::vector<Command> used_commands;
static ChannelColumns columns;
ChannelWidget();
blc_channel *find_channel(int id);
ContextMenu *create_menu();
void create_and_execute_command(std::string program_name, std::initializer_list<const char*> const optional_arguments);
void display_process(Gtk::TreeModel::Row &row);
void refresh_channel_list();
......
//
// Command.cpp
// gtk_channels
//
// Created by Arnaud Blanchard on 21/10/2019.
//
#include "Command.hpp"
#include <numeric> //accumulate
using namespace std;
Command::Command(string const &bin_dir, string program_name, initializer_list<char const*> default_args, function<bool(vector<blc_channel const*>)>valid_channels):binary(bin_dir+"/"+program_name), program_name(move(program_name)), valid_channels(valid_channels){
args.emplace_back(binary.data());
args.insert(args.end(), default_args);
}
Command::Command(Command const &command, vector<blc_channel const*> const channels){
*this=command;
args.reserve(args.size()+channels.size()+1);
for (auto const &channel:channels){
args.emplace_back(channel->name);
}
args.emplace_back(nullptr);
}
void Command::add_args(initializer_list<char const *> optional_args){
args.insert(args.end(), optional_args);
}
unique_ptr<Process> Command::execute(function<void(Process*)> after_launch) const{
/* for (auto &channel:channels){
process_args.emplace_back(channel->name);
}*/
}
//
// Command.hpp
// gtk_channels
//
// Created by Arnaud Blanchard on 21/10/2019.
//
#ifndef Command_hpp
#define Command_hpp
#include <string>
#include <vector>
#include "Process.h"
#include "blc_channel.h"
/* One command may have many processes */
struct Command{
std::string binary;
std::string program_name;
std::function<bool(std::vector<blc_channel const*> &channels)> valid_channels;
std::vector<const char *> args;
Command(std::string const &bin_dir, std::string program_name, std::initializer_list<char const*> default_args, std::function<bool(std::vector<blc_channel const*>)>validity);
Command(Command const &command, std::vector<blc_channel const*> const channel); //Copy command and add arguments
void add_args(std::initializer_list<char const *> optional_args);
std::unique_ptr<Process> execute(std::function<void(Process*)> after_launch) const;
};
#endif /* Command_hpp */
......@@ -4,15 +4,11 @@
//
// Created by Arnaud Blanchard on 13/10/2019.
//
#include "ChannelWidget.hpp"
#include "blc_image.h"
#include "ContextMenu.hpp"
using namespace Gtk;
using namespace std;
ContextMenu::ContextMenu(string program_dir):program_dir(move(program_dir)){}
void ContextMenu::add_item(string name, bool shortcut, function <void()> action){
MenuItem *item=new MenuItem(name, shortcut);
append(*item);
......@@ -23,7 +19,7 @@ void ContextMenu::add_separator(){
SeparatorMenuItem *separator_item = new SeparatorMenuItem();
append(*separator_item);
}
/*
void ContextMenu::add_program(char const *menu_name, std::string program_name, initializer_list<const char*> const optional_arguments, function <void(Process *process)> after_launch){
auto item=new MenuItem(menu_name);
auto args_vec=make_shared<vector<const char*>>(optional_arguments); //This will be destroyed by process
......@@ -37,4 +33,4 @@ void ContextMenu::add_program(char const *menu_name, std::string program_name, i
auto process = new Process(program_dir, program_name, args_vec);
process->run(after_launch);
});
}
}*/
......@@ -14,19 +14,13 @@
#include <string>
#include <vector>
#include "Process.h"
struct ContextMenu:Gtk::Menu{
std::string program_dir;
std::vector<blc_channel*> selected_channels;
Glib::RefPtr<Gtk::TreeSelection> row_selection;
ContextMenu(std::string program_dir);
void after_launch(Process *process);
void add_item(std::string name, bool shortcut, std::function<void ()>action);
void add_separator();
void add_program(char const *menu_name, std::string program_name, std::initializer_list<const char*> const optional_args, std::function <void(Process*)> after_launch);
// void add_program(char const *menu_name, std::string program_name, std::initializer_list<const char*> const optional_args, std::function <void(Process*)> after_launch);
};
#endif /* ContextMenu_hpp */
......@@ -6,16 +6,11 @@
//
//
#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>
#include <signal.h>
using namespace std;
using namespace Gtk;
......@@ -35,6 +30,7 @@ static void TerminalSpawnAsyncCallback (VteTerminal *terminal, GPid pid, GError
fprintf(stderr, "%s ", arg);
}
fprintf(stderr, "\n");
process->status=Process::running;
process->after_launch(process);
}
};
......@@ -50,26 +46,15 @@ void Process::on_quit(function<void(int exit_status)> on_quit){
g_signal_connect(G_OBJECT(terminal), "child-exited", G_CALLBACK(after_exectution) , this);
}
Process::Process(string const directory, string const program, shared_ptr<vector<const char*>> const optional_arguments):directory(move(directory)),program_name(move(program)){
command_path=directory+"/"+program;
arg_vector.emplace_back(command_path.data());
Process::Process(char const * const *argv, function<void(Process *)>after_launch){
this->after_launch=after_launch;
terminal=VTE_TERMINAL(vte_terminal_new());
vte_terminal_spawn_async(terminal, VTE_PTY_DEFAULT , nullptr, (char**)argv, nullptr, G_SPAWN_DEFAULT, nullptr, nullptr, nullptr, 1000, nullptr, TerminalSpawnAsyncCallback, (void*)this);
for( auto &arg : *optional_arguments){
arg_vector.emplace_back(arg);
}
};
Process::~Process(){
for (char const *&arg: arg_vector ){
free(&arg);
}
};
void Process::run(function <void(Process *process)> after_launch){
this->after_launch=after_launch;
terminal=VTE_TERMINAL(vte_terminal_new());
arg_vector.emplace_back(nullptr); //note the end of the list
void Process::run(){
vte_terminal_spawn_async(terminal, VTE_PTY_DEFAULT , nullptr, (char**)arg_vector.data(), nullptr, G_SPAWN_DEFAULT, nullptr, nullptr, nullptr, 1000, nullptr, TerminalSpawnAsyncCallback, (void*)this);
}
......@@ -87,6 +72,19 @@ void Process::display_terminal(){
terminal_window->show_all();
}
void Process::stop(){
signal(SIGTERM);
status=stopped;
};
void Process::pause(){
signal(SIGTSTP);
status=paused;
};
void Process::cont(){
signal(SIGCONT);
status=running;
};
void Process::signal(int signal) const{
kill(pid, signal);
}
......@@ -16,8 +16,8 @@
#include <vte/vte.h>
struct Process{
enum Status{paused, stopped, running} status=stopped;
pid_t pid;
std::string name;
std::string directory;
std::string program_name;
......@@ -28,11 +28,16 @@ struct Process{
std::function<void(Process *)> after_launch;
std::function<void(int exit_status)> on_quit_callback;
Process(std::string const directory, std::string const program_name, std::shared_ptr<std::vector<const char*>> const optional_arguments={});
~Process();
Process(char const* const* argv, std::function<void(Process*)> on_launch);
void run(std::function<void(Process*)> on_launch);
void run();
void stop();
void pause();
void cont();
void display_terminal();
void on_quit(std::function<void(int exit_status)> on_quit);
void signal(int signal) const;
......
#include "ChannelWidget.hpp"
#include "blc_program.h"
#include "libgen.h"
#include <gtkmm.h>
#include "ChannelWidget.hpp"
#include "thread"
using namespace std;
using namespace Gtk;
......@@ -16,11 +13,10 @@ struct App:Application{
//Overrides of default signal handlers:
void on_startup() override; //This is called for the first instance of the app
void on_activate() override;
void on_activate() override; //This is called a new instance of the app is called (i.e already existing)
};
App::App(int &argc, char **&argv):Application(argc, argv){
}
App::App(int &argc, char **&argv):Application(argc, argv){}
void App::on_startup(){
Application::on_startup();
......@@ -30,7 +26,6 @@ void App::on_startup(){
}
void App::on_activate(){
Application::on_activate();
add_window(main_window);
main_window.add(channel_widget);
......@@ -39,7 +34,7 @@ void App::on_activate(){
int main(int argc, char **argv)
{
App app(argc, argv); //AArgc argv allow to interpret option --g-fatal-warnings for debugging
App app(argc, argv); //Argc argv allow to interpret option --g-fatal-warnings for debugging
blc_program_set_description("Graphical interface for blaar programs");
blc_program_init(&argc, &argv, blc_quit);
......