2018-07-05 16:57:43 +02:00
|
|
|
/*
|
|
|
|
* \brief Graph view of runtime state
|
|
|
|
* \author Norman Feske
|
|
|
|
* \date 2018-07-05
|
|
|
|
*
|
|
|
|
* The GUI is based on a dynamically configured init component, which hosts
|
|
|
|
* one menu-view component for each dialog.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2018 Genode Labs GmbH
|
|
|
|
*
|
|
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef _GRAPH_H_
|
|
|
|
#define _GRAPH_H_
|
|
|
|
|
|
|
|
/* Genode includes */
|
|
|
|
#include <os/reporter.h>
|
|
|
|
|
|
|
|
/* local includes */
|
|
|
|
#include <types.h>
|
|
|
|
#include <xml.h>
|
2018-08-23 13:39:21 +02:00
|
|
|
#include <model/capacity.h>
|
2018-09-11 15:17:17 +02:00
|
|
|
#include <model/popup.h>
|
2018-07-05 16:57:43 +02:00
|
|
|
|
|
|
|
namespace Sculpt { struct Graph; }
|
|
|
|
|
|
|
|
|
|
|
|
struct Sculpt::Graph
|
|
|
|
{
|
|
|
|
Env &_env;
|
|
|
|
|
2018-08-23 13:39:21 +02:00
|
|
|
Runtime_state &_runtime_state;
|
|
|
|
|
2018-07-05 16:57:43 +02:00
|
|
|
Storage_target const &_sculpt_partition;
|
|
|
|
|
2018-09-11 15:17:17 +02:00
|
|
|
Popup::State const &_popup_state;
|
|
|
|
|
|
|
|
Depot_deploy::Children const &_deploy_children;
|
|
|
|
|
2018-07-05 16:57:43 +02:00
|
|
|
Expanding_reporter _graph_dialog_reporter { _env, "dialog", "runtime_view_dialog" };
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Even though the runtime configuration is generate by the sculpt
|
|
|
|
* manager, we still obtain it as a separate ROM session to keep both
|
|
|
|
* parts decoupled.
|
|
|
|
*/
|
|
|
|
Attached_rom_dataspace _runtime_config_rom { _env, "config -> managed/runtime" };
|
|
|
|
|
2018-08-23 13:39:21 +02:00
|
|
|
Attached_rom_dataspace _hover_rom { _env, "runtime_view_hover" };
|
|
|
|
|
2018-07-05 16:57:43 +02:00
|
|
|
Signal_handler<Graph> _runtime_config_handler {
|
|
|
|
_env.ep(), *this, &Graph::_handle_runtime_config };
|
|
|
|
|
2018-08-23 13:39:21 +02:00
|
|
|
Signal_handler<Graph> _hover_handler {
|
|
|
|
_env.ep(), *this, &Graph::_handle_hover };
|
|
|
|
|
2018-09-11 15:17:17 +02:00
|
|
|
Hoverable_item _node_button_item { };
|
|
|
|
Hoverable_item _add_button_item { };
|
|
|
|
Activatable_item _remove_item { };
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Defined when '+' button is hovered
|
|
|
|
*/
|
|
|
|
Rect _popup_anchor { };
|
2018-08-23 13:39:21 +02:00
|
|
|
|
|
|
|
bool _hovered = false;
|
|
|
|
|
2018-07-05 16:57:43 +02:00
|
|
|
typedef Start_name Node_name;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return component name targeted by the first route of the start node
|
|
|
|
*/
|
|
|
|
static Node_name _primary_dependency(Xml_node const start)
|
|
|
|
{
|
|
|
|
if (!start.has_sub_node("route"))
|
|
|
|
return Node_name();
|
|
|
|
|
|
|
|
Xml_node const route = start.sub_node("route");
|
|
|
|
|
|
|
|
if (!route.has_sub_node("service"))
|
|
|
|
return Node_name();
|
|
|
|
|
|
|
|
Xml_node const service = route.sub_node("service");
|
|
|
|
|
|
|
|
if (service.has_sub_node("child")) {
|
|
|
|
Xml_node const child = service.sub_node("child");
|
|
|
|
return child.attribute_value("name", Node_name());
|
|
|
|
}
|
|
|
|
|
|
|
|
return Node_name();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename FN>
|
|
|
|
static void _for_each_secondary_dep(Xml_node const start, FN const &fn)
|
|
|
|
{
|
|
|
|
if (!start.has_sub_node("route"))
|
|
|
|
return;
|
|
|
|
|
|
|
|
Xml_node const route = start.sub_node("route");
|
|
|
|
|
|
|
|
bool first_route = true;
|
|
|
|
route.for_each_sub_node("service", [&] (Xml_node service) {
|
|
|
|
|
2018-09-11 15:17:17 +02:00
|
|
|
if (!first_route && service.has_sub_node("child")) {
|
2018-07-05 16:57:43 +02:00
|
|
|
Xml_node const child = service.sub_node("child");
|
|
|
|
fn(child.attribute_value("name", Start_name()));
|
|
|
|
}
|
|
|
|
|
|
|
|
first_route = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-09-11 15:17:17 +02:00
|
|
|
void _gen_selected_node_content(Xml_generator &xml, Start_name const &name,
|
|
|
|
Runtime_state::Info const &info) const
|
|
|
|
{
|
|
|
|
bool const removable = _deploy_children.exists(name);
|
|
|
|
|
|
|
|
if (removable) {
|
|
|
|
gen_named_node(xml, "frame", "operations", [&] () {
|
|
|
|
xml.node("vbox", [&] () {
|
|
|
|
gen_named_node(xml, "button", "remove", [&] () {
|
|
|
|
_remove_item.gen_button_attr(xml, "remove");
|
|
|
|
xml.node("label", [&] () {
|
|
|
|
xml.attribute("text", "Remove");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
String<100> const
|
|
|
|
ram (Capacity{info.assigned_ram - info.avail_ram}, " / ",
|
|
|
|
Capacity{info.assigned_ram}),
|
|
|
|
caps(info.assigned_caps - info.avail_caps, " / ",
|
|
|
|
info.assigned_caps, " caps");
|
|
|
|
|
|
|
|
gen_named_node(xml, "label", "ram", [&] () {
|
|
|
|
xml.attribute("text", ram); });
|
|
|
|
|
|
|
|
gen_named_node(xml, "label", "caps", [&] () {
|
|
|
|
xml.attribute("text", caps); });
|
|
|
|
}
|
|
|
|
|
2018-08-23 13:39:21 +02:00
|
|
|
void _gen_graph_dialog()
|
2018-07-05 16:57:43 +02:00
|
|
|
{
|
|
|
|
Xml_node const config = _runtime_config_rom.xml();
|
|
|
|
|
|
|
|
_graph_dialog_reporter.generate([&] (Xml_generator &xml) {
|
|
|
|
|
|
|
|
xml.node("depgraph", [&] () {
|
|
|
|
|
2019-02-19 16:31:00 +01:00
|
|
|
if (_sculpt_partition.valid()) {
|
|
|
|
gen_named_node(xml, "button", "global+", [&] () {
|
|
|
|
_add_button_item.gen_button_attr(xml, "global+");
|
2018-09-11 15:17:17 +02:00
|
|
|
|
2019-02-19 16:31:00 +01:00
|
|
|
if (_popup_state == Popup::VISIBLE)
|
|
|
|
xml.attribute("selected", "yes");
|
2018-09-11 15:17:17 +02:00
|
|
|
|
2019-02-19 16:31:00 +01:00
|
|
|
xml.node("label", [&] () {
|
|
|
|
xml.attribute("text", "+"); }); });
|
|
|
|
}
|
2018-09-11 15:17:17 +02:00
|
|
|
|
2018-07-05 16:57:43 +02:00
|
|
|
config.for_each_sub_node("start", [&] (Xml_node start) {
|
|
|
|
|
|
|
|
Start_name const name = start.attribute_value("name", Start_name());
|
|
|
|
|
2018-08-23 13:39:21 +02:00
|
|
|
Runtime_state::Info const info = _runtime_state.info(name);
|
|
|
|
|
2018-07-05 16:57:43 +02:00
|
|
|
gen_named_node(xml, "frame", name, [&] () {
|
|
|
|
|
|
|
|
Node_name primary_dep = _primary_dependency(start);
|
|
|
|
|
|
|
|
if (primary_dep == "default_fs_rw")
|
|
|
|
primary_dep = _sculpt_partition.fs();
|
|
|
|
|
|
|
|
if (primary_dep.valid())
|
|
|
|
xml.attribute("dep", primary_dep);
|
|
|
|
|
2018-08-23 13:39:21 +02:00
|
|
|
xml.node("vbox", [&] () {
|
|
|
|
|
|
|
|
gen_named_node(xml, "button", name, [&] () {
|
|
|
|
_node_button_item.gen_button_attr(xml, name);
|
|
|
|
|
|
|
|
if (info.selected)
|
|
|
|
xml.attribute("selected", "yes");
|
|
|
|
|
|
|
|
xml.node("label", [&] () {
|
|
|
|
xml.attribute("text", name);
|
|
|
|
});
|
2018-07-05 16:57:43 +02:00
|
|
|
});
|
2018-08-23 13:39:21 +02:00
|
|
|
|
2018-09-11 15:17:17 +02:00
|
|
|
if (info.selected)
|
|
|
|
_gen_selected_node_content(xml, name, info);
|
2018-07-05 16:57:43 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
config.for_each_sub_node("start", [&] (Xml_node start) {
|
|
|
|
|
|
|
|
Start_name const name = start.attribute_value("name", Start_name());
|
|
|
|
|
2018-08-23 13:39:21 +02:00
|
|
|
Runtime_state::Info const info = _runtime_state.info(name);
|
|
|
|
|
|
|
|
bool const show_details = info.selected;
|
2018-07-05 16:57:43 +02:00
|
|
|
|
|
|
|
if (show_details) {
|
|
|
|
_for_each_secondary_dep(start, [&] (Start_name const &dep) {
|
|
|
|
xml.node("dep", [&] () {
|
|
|
|
xml.attribute("node", name);
|
|
|
|
xml.attribute("on", dep);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-23 13:39:21 +02:00
|
|
|
void _handle_runtime_config()
|
|
|
|
{
|
|
|
|
_runtime_config_rom.update();
|
|
|
|
|
|
|
|
_gen_graph_dialog();
|
|
|
|
}
|
|
|
|
|
|
|
|
void _handle_hover()
|
|
|
|
{
|
|
|
|
_hover_rom.update();
|
|
|
|
|
|
|
|
Xml_node const hover = _hover_rom.xml();
|
|
|
|
|
|
|
|
_hovered = (hover.num_sub_nodes() != 0);
|
|
|
|
|
|
|
|
bool const changed =
|
2018-09-11 15:17:17 +02:00
|
|
|
_node_button_item.match(hover, "dialog", "depgraph", "frame", "vbox", "button", "name") |
|
|
|
|
_add_button_item .match(hover, "dialog", "depgraph", "button", "name") |
|
|
|
|
_remove_item .match(hover, "dialog", "depgraph", "frame", "vbox",
|
|
|
|
"frame", "vbox", "button", "name");
|
|
|
|
|
|
|
|
if (_add_button_item.hovered("global+")) {
|
|
|
|
|
|
|
|
/* update anchor geometry of popup menu */
|
|
|
|
auto hovered_rect = [] (Xml_node const hover) {
|
|
|
|
|
|
|
|
auto point_from_xml = [] (Xml_node node) {
|
|
|
|
return Point(node.attribute_value("xpos", 0L),
|
|
|
|
node.attribute_value("ypos", 0L)); };
|
|
|
|
|
|
|
|
auto area_from_xml = [] (Xml_node node) {
|
|
|
|
return Area(node.attribute_value("width", 0UL),
|
|
|
|
node.attribute_value("height", 0UL)); };
|
|
|
|
|
|
|
|
if (!hover.has_sub_node("dialog")) return Rect();
|
|
|
|
Xml_node const dialog = hover.sub_node("dialog");
|
|
|
|
|
|
|
|
if (!dialog.has_sub_node("depgraph")) return Rect();
|
|
|
|
Xml_node const depgraph = dialog.sub_node("depgraph");
|
|
|
|
|
|
|
|
if (!depgraph.has_sub_node("button")) return Rect();
|
|
|
|
Xml_node const button = depgraph.sub_node("button");
|
|
|
|
|
|
|
|
return Rect(point_from_xml(dialog) + point_from_xml(depgraph) +
|
|
|
|
point_from_xml(button),
|
|
|
|
area_from_xml(button));
|
|
|
|
};
|
|
|
|
|
|
|
|
_popup_anchor = hovered_rect(hover);
|
|
|
|
}
|
2018-08-23 13:39:21 +02:00
|
|
|
|
|
|
|
if (changed)
|
|
|
|
_gen_graph_dialog();
|
|
|
|
}
|
|
|
|
|
2018-09-11 15:17:17 +02:00
|
|
|
Graph(Env &env,
|
|
|
|
Runtime_state &runtime_state,
|
|
|
|
Storage_target const &sculpt_partition,
|
|
|
|
Popup::State const &popup_state,
|
|
|
|
Depot_deploy::Children const &deploy_children)
|
2018-07-05 16:57:43 +02:00
|
|
|
:
|
2018-09-11 15:17:17 +02:00
|
|
|
_env(env), _runtime_state(runtime_state),
|
|
|
|
_sculpt_partition(sculpt_partition),
|
|
|
|
_popup_state(popup_state), _deploy_children(deploy_children)
|
2018-07-05 16:57:43 +02:00
|
|
|
{
|
|
|
|
_runtime_config_rom.sigh(_runtime_config_handler);
|
2018-08-23 13:39:21 +02:00
|
|
|
_hover_rom.sigh(_hover_handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool hovered() const { return _hovered; }
|
|
|
|
|
2018-09-11 15:17:17 +02:00
|
|
|
bool add_button_hovered() const { return _add_button_item._hovered.valid(); }
|
|
|
|
|
|
|
|
struct Action : Interface
|
|
|
|
{
|
|
|
|
virtual void remove_deployed_component(Start_name const &) = 0;
|
|
|
|
virtual void toggle_launcher_selector(Rect) = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
void click(Action &action)
|
2018-08-23 13:39:21 +02:00
|
|
|
{
|
2018-09-11 15:17:17 +02:00
|
|
|
if (_add_button_item._hovered.valid()) {
|
|
|
|
action.toggle_launcher_selector(_popup_anchor);
|
|
|
|
}
|
|
|
|
|
2018-08-23 13:39:21 +02:00
|
|
|
if (_node_button_item._hovered.valid()) {
|
|
|
|
_runtime_state.toggle_selection(_node_button_item._hovered);
|
2018-09-11 15:17:17 +02:00
|
|
|
_remove_item.reset();
|
2018-08-23 13:39:21 +02:00
|
|
|
_gen_graph_dialog();
|
|
|
|
}
|
2018-09-11 15:17:17 +02:00
|
|
|
|
|
|
|
if (_remove_item.hovered("remove")) {
|
|
|
|
_remove_item.propose_activation_on_click();
|
|
|
|
_gen_graph_dialog();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void clack(Action &action)
|
|
|
|
{
|
|
|
|
if (_remove_item.hovered("remove")) {
|
|
|
|
|
|
|
|
_remove_item.confirm_activation_on_clack();
|
|
|
|
|
|
|
|
if (_remove_item.activated("remove"))
|
|
|
|
action.remove_deployed_component(_runtime_state.selected());
|
|
|
|
|
|
|
|
} else {
|
|
|
|
_remove_item.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
_gen_graph_dialog();
|
2018-07-05 16:57:43 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#endif /* _GRAPH_H_ */
|