sculpt_manager: use dialog API

Issue #5053
This commit is contained in:
Norman Feske 2023-10-30 12:43:19 +01:00 committed by Christian Helmuth
parent 1490c58f8b
commit 0a71c8f3e1
61 changed files with 2967 additions and 4312 deletions

View File

@ -72,6 +72,7 @@ static inline bool Sculpt::blueprint_rom_missing(Xml_node blueprint, Path const
pkg.for_each_sub_node("missing_rom", [&] (Xml_node missing_rom) {
/* ld.lib.so is always taken from the base system */
using Label = String<64>;
Label const label = missing_rom.attribute_value("label", Label());
if (label == "ld.lib.so")
return;

View File

@ -125,8 +125,7 @@ class Dialog::Distant_runtime::View : private Views::Element
Event::Seq_number _hover_seq_number { };
template <typename FN>
void _with_dialog_hover(FN const &fn) const
void _with_dialog_hover(auto const &fn) const
{
bool done = false;
@ -254,6 +253,15 @@ class Dialog::Distant_runtime::View : private Views::Element
{ }
void refresh() { _refresh_handler.local_submit(); }
bool if_hovered(auto const &fn) const
{
bool result = false;
if (_dialog_hovered)
_with_dialog_hover([&] (Xml_node const &location) {
result = fn(Hovered_at { Event::Seq_number { }, location }); });
return result;
}
};
#endif /* _DIALOG__DISTANT_RUNTIME_H_ */

View File

@ -29,9 +29,6 @@ struct Sculpt::Feature
/* allow the browsing of file systems via the inspect view */
static constexpr bool INSPECT_VIEW = true;
/* highlight hovered buttons, not possible on touch-screen devices */
static constexpr bool VISUAL_HOVER = true;
};
#endif /* _FEATURE_H_ */

View File

@ -70,7 +70,7 @@ struct Dialog::Selectable_node
if (s.hovered()) s.attribute("hovered", "yes");
if (attr.selected) s.attribute("selected", "yes");
s.sub_scope<Dialog::Label>(attr.pretty_name);
s.sub_scope<Label>(attr.pretty_name);
});
if (attr.selected)
@ -101,7 +101,7 @@ void Graph::_view_selected_node_content(Scope<Depgraph, Frame, Vbox> &s,
}
if (name == "ram_fs")
s.widget(_ram_fs_dialog, _sculpt_partition, _ram_fs_state);
s.widget(_ram_fs_widget, _sculpt_partition, _ram_fs_state);
String<100> const
ram (Capacity{info.assigned_ram - info.avail_ram}, " / ",
@ -110,8 +110,8 @@ void Graph::_view_selected_node_content(Scope<Depgraph, Frame, Vbox> &s,
info.assigned_caps, " caps");
s.sub_scope<Min_ex>(25);
s.sub_scope<Dialog::Label>(ram);
s.sub_scope<Dialog::Label>(caps);
s.sub_scope<Label>(ram);
s.sub_scope<Label>(caps);
}
@ -128,7 +128,7 @@ void Graph::_view_storage_node(Scope<Depgraph> &s) const
},
[&] (Scope<Depgraph, Frame, Vbox> &s) {
s.sub_scope<Frame>([&] (Scope<Depgraph, Frame, Vbox, Frame> &s) {
s.widget(_block_devices_dialog); });
s.widget(_block_devices_widget); });
}
);
}
@ -147,7 +147,7 @@ void Graph::_view_usb_node(Scope<Depgraph> &s) const
},
[&] (Scope<Depgraph, Frame, Vbox> &s) {
s.sub_scope<Frame>([&] (Scope<Depgraph, Frame, Vbox, Frame> &s) {
s.widget(_usb_devices_dialog); });
s.widget(_usb_devices_widget); });
}
);
}
@ -283,8 +283,8 @@ void Graph::click(Clicked_at const &at, Action &action)
_storage_selected = !_storage_selected && (id.value == "storage");
_usb_selected = !_usb_selected && (id.value == "usb");
if (_usb_selected) _usb_devices_dialog .reset();
if (_storage_selected) _block_devices_dialog.reset();
if (_usb_selected) _usb_devices_widget .reset();
if (_storage_selected) _block_devices_widget.reset();
_runtime_config.with_start_name(id, [&] (Start_name const &name) {
_runtime_state.toggle_selection(name, _runtime_config); });
@ -307,20 +307,20 @@ void Graph::click(Clicked_at const &at, Action &action)
action.open_popup_dialog(popup_anchor(at._location));
});
_ram_fs_dialog .propagate(at, _sculpt_partition, action);
_block_devices_dialog.propagate(at, action);
_usb_devices_dialog .propagate(at, action);
_ram_fs_widget .propagate(at, _sculpt_partition, action);
_block_devices_widget.propagate(at, action);
_usb_devices_widget .propagate(at, action);
_remove .propagate(at);
_restart.propagate(at);
}
void Graph::clack(Clacked_at const &at, Action &action, Ram_fs_dialog::Action &ram_fs_action)
void Graph::clack(Clacked_at const &at, Action &action, Ram_fs_widget::Action &ram_fs_action)
{
_ram_fs_dialog .propagate(at, ram_fs_action);
_block_devices_dialog.propagate(at, action);
_usb_devices_dialog .propagate(at, action);
_ram_fs_widget .propagate(at, ram_fs_action);
_block_devices_widget.propagate(at, action);
_usb_devices_widget .propagate(at, action);
_remove.propagate(at, [&] {
action.remove_deployed_component(_runtime_state.selected());

View File

@ -23,10 +23,8 @@
/* local includes */
#include <types.h>
#include <xml.h>
#include <view/activatable_item.h>
#include <view/storage_dialog.h>
#include <view/ram_fs_dialog.h>
#include <view/storage_widget.h>
#include <view/ram_fs_widget.h>
#include <model/capacity.h>
#include <model/popup.h>
#include <model/runtime_config.h>
@ -49,19 +47,19 @@ struct Sculpt::Graph : Widget<Depgraph>
Hosted<Depgraph, Toggle_button> _plus { Id { "+" } };
Hosted<Depgraph, Frame, Vbox, Ram_fs_dialog>
_ram_fs_dialog { Id { "ram_fs_dialog" } };
Hosted<Depgraph, Frame, Vbox, Ram_fs_widget>
_ram_fs_widget { Id { "ram_fs" } };
Hosted<Depgraph, Frame, Vbox, Frame, Hbox, Deferred_action_button>
_remove { Id { "Remove" } },
_restart { Id { "Restart" } };
Hosted<Depgraph, Frame, Vbox, Frame, Block_devices_dialog>
_block_devices_dialog { Id { "block_devices" },
Hosted<Depgraph, Frame, Vbox, Frame, Block_devices_widget>
_block_devices_widget { Id { "block_devices" },
_storage_devices, _sculpt_partition };
Hosted<Depgraph, Frame, Vbox, Frame, Usb_devices_dialog>
_usb_devices_dialog { Id { "usb_devices" },
Hosted<Depgraph, Frame, Vbox, Frame, Usb_devices_widget>
_usb_devices_widget { Id { "usb_devices" },
_storage_devices, _sculpt_partition };
bool _storage_selected = false;
@ -90,7 +88,7 @@ struct Sculpt::Graph : Widget<Depgraph>
void view(Scope<Depgraph> &) const;
struct Action : Storage_device_dialog::Action
struct Action : Storage_device_widget::Action
{
virtual void remove_deployed_component(Start_name const &) = 0;
virtual void restart_deployed_component(Start_name const &) = 0;
@ -98,12 +96,12 @@ struct Sculpt::Graph : Widget<Depgraph>
};
void click(Clicked_at const &, Action &);
void clack(Clacked_at const &, Action &, Ram_fs_dialog::Action &);
void clack(Clacked_at const &, Action &, Ram_fs_widget::Action &);
void reset_storage_operation()
{
_block_devices_dialog.reset_operation();
_usb_devices_dialog.reset_operation();
_block_devices_widget.reset_operation();
_usb_devices_widget.reset_operation();
}
};

View File

@ -20,7 +20,7 @@
/* local includes */
#include <model/wpa_passphrase.h>
#include <types.h>
#include <view/network_dialog.h>
#include <view/network_widget.h>
#include <view/panel_dialog.h>
#include <view/system_dialog.h>
@ -32,7 +32,7 @@ struct Sculpt::Keyboard_focus
Expanding_reporter _focus_reporter;
Network_dialog const &_network_dialog;
Network_widget const &_network_widget;
Wpa_passphrase &_wpa_passphrase;
Panel_dialog::State const &_panel;
System_dialog const &_system_dialog;
@ -44,7 +44,7 @@ struct Sculpt::Keyboard_focus
target = WM;
if (_panel.network_visible() && _network_dialog.need_keyboard_focus_for_passphrase())
if (_panel.network_visible() && _network_widget.need_keyboard_focus_for_passphrase())
target = WPA_PASSPHRASE;
if (_system_dialog.keyboard_needed() && _system_visible)
@ -73,14 +73,14 @@ struct Sculpt::Keyboard_focus
}
Keyboard_focus(Env &env,
Network_dialog const &network_dialog,
Network_widget const &network_widget,
Wpa_passphrase &wpa_passphrase,
Panel_dialog::State const &panel,
System_dialog const &system_dialog,
bool const &system_visible)
:
_focus_reporter(env, "focus", "focus"),
_network_dialog(network_dialog),
_network_widget(network_widget),
_wpa_passphrase(wpa_passphrase),
_panel(panel),
_system_dialog(system_dialog),

View File

@ -38,13 +38,12 @@
#include <model/settings.h>
#include <model/presets.h>
#include <model/screensaver.h>
#include <view/download_status_dialog.h>
#include <view/download_status_widget.h>
#include <view/popup_dialog.h>
#include <view/panel_dialog.h>
#include <view/settings_dialog.h>
#include <view/settings_widget.h>
#include <view/system_dialog.h>
#include <view/file_browser_dialog.h>
#include <menu_view.h>
#include <gui.h>
#include <keyboard_focus.h>
#include <network.h>
@ -60,19 +59,18 @@ struct Sculpt::Main : Input_event_handler,
Deploy::Action,
Storage::Action,
Network::Action,
Network::Info,
Graph::Action,
Panel_dialog::Action,
Popup_dialog::Action,
Settings_dialog::Action,
Software_presets_dialog::Action,
Depot_users_dialog::Action,
Software_update_dialog::Action,
Settings_widget::Action,
Software_presets_widget::Action,
Software_update_widget::Action,
File_browser_dialog::Action,
Popup_dialog::Construction_info,
Depot_query,
Panel_dialog::State,
Popup_dialog::Refresh,
Menu_view::Hover_update_handler,
Screensaver::Action
{
Env &_env;
@ -150,7 +148,7 @@ struct Sculpt::Main : Input_event_handler,
_handle_gui_mode();
/* visibility of fonts section of settings dialog may have changed */
_settings_menu_view.generate();
_settings_dialog.refresh();
/* visibility of settings button may have changed */
_refresh_panel_and_window_layout();
@ -177,7 +175,7 @@ struct Sculpt::Main : Input_event_handler,
_event_filter_config.generate([&] (Xml_generator &xml) {
_generate_event_filter_config(xml); });
_settings_menu_view.generate();
_settings_dialog.refresh();
/* visibility of the settings dialog may have changed */
if (orig_settings_available != _settings.interactive_settings_available()) {
@ -186,6 +184,20 @@ struct Sculpt::Main : Input_event_handler,
}
}
Dialog::Distant_runtime _dialog_runtime { _env };
template <typename TOP_LEVEL_DIALOG>
struct Dialog_view : TOP_LEVEL_DIALOG, private Distant_runtime::View
{
template <typename... ARGS>
Dialog_view(Distant_runtime &runtime, ARGS &&... args)
: TOP_LEVEL_DIALOG(args...), Distant_runtime::View(runtime, *this) { }
using Distant_runtime::View::refresh;
using Distant_runtime::View::min_width;
using Distant_runtime::View::if_hovered;
};
/**********************
** Device discovery **
@ -229,6 +241,11 @@ struct Sculpt::Main : Input_event_handler,
return _prepare_version.value != _prepare_completed.value;
}
/*************
** Storage **
*************/
Storage _storage { _env, _heap, _child_states, *this, *this };
/**
@ -253,20 +270,53 @@ struct Sculpt::Main : Input_event_handler,
*/
void refresh_storage_dialog() override { _generate_dialog(); }
Network _network { _env, _heap, *this, _child_states, *this, _runtime_state, _pci_info };
Menu_view _network_menu_view { _env, _child_states, _network.dialog, "network_view",
Ram_quota{4*1024*1024}, Cap_quota{150},
"network_dialog", "network_view_hover",
*this };
/*************
** Network **
*************/
Network _network { _env, _heap, *this, *this, _child_states, *this, _runtime_state, _pci_info };
struct Network_top_level_dialog : Top_level_dialog
{
Main &_main;
Network_top_level_dialog(Main &main)
: Top_level_dialog("network"), _main(main) { }
void view(Scope<> &s) const override
{
s.sub_scope<Frame>([&] (Scope<Frame> &s) {
_main._network.dialog.view(s); });
}
void click(Clicked_at const &at) override
{
_main._network.dialog.click(at, _main._network);
}
void clack(Clacked_at const &) override { }
void drag (Dragged_at const &) override { }
};
Dialog_view<Network_top_level_dialog> _network_dialog { _dialog_runtime, *this };
/**
* Network::Action interface
*/
void update_network_dialog() override
{
_network_menu_view.generate();
_system_menu_view.generate();
_network_dialog.refresh();
_system_dialog.refresh();
}
/**
* Network::Info interface
*/
bool ap_list_hovered() const override
{
return _network_dialog.if_hovered([&] (Hovered_at const &at) {
return _network.dialog.ap_list_hovered(at); });
}
@ -434,7 +484,7 @@ struct Sculpt::Main : Input_event_handler,
void _handle_image_index()
{
_image_index_rom.update();
_system_menu_view.generate();
_system_dialog.refresh();
}
Attached_rom_dataspace _launcher_listing_rom {
@ -462,7 +512,7 @@ struct Sculpt::Main : Input_event_handler,
_presets.update_from_xml(dir); /* iterate over <file> nodes */
});
_popup_menu_view.generate();
_popup_dialog.refresh();
_deploy._handle_managed_deploy();
}
@ -516,14 +566,10 @@ struct Sculpt::Main : Input_event_handler,
/**
* Panel_dialog::State interface
*/
bool log_visible() const override { return _log_visible; }
bool network_visible() const override { return _network_visible; }
bool settings_visible() const override { return _settings_visible; }
bool system_visible() const override { return _system_visible; }
bool log_visible() const override { return _log_visible; }
bool network_visible() const override { return _network_visible; }
bool settings_visible() const override { return _settings_visible; }
bool system_visible() const override { return _system_visible; }
bool inspect_tab_visible() const override { return _storage.any_file_system_inspected(); }
Panel_dialog::Tab selected_tab() const override { return _selected_tab; }
@ -575,7 +621,7 @@ struct Sculpt::Main : Input_event_handler,
if (download_in_progress || _main._download_queue.any_failed_download()) {
Hosted<Vbox, Download_status_dialog> download_status { Id { "Download" } };
Hosted<Vbox, Download_status_widget> download_status { Id { "Download" } };
s.widget(download_status, state, _main._download_queue);
}
@ -589,7 +635,7 @@ struct Sculpt::Main : Input_event_handler,
_graph_view.refresh();
if (_system_visible)
_system_menu_view.generate();
_system_dialog.refresh();
}
Attached_rom_dataspace _runtime_state_rom { _env, "report -> runtime/state" };
@ -651,7 +697,7 @@ struct Sculpt::Main : Input_event_handler,
_graph_view.refresh();
if (_selected_tab == Panel_dialog::Tab::FILES)
_file_browser_menu_view.generate();
_file_browser_dialog.refresh();
}
@ -659,83 +705,9 @@ struct Sculpt::Main : Input_event_handler,
** Interactive operations **
****************************/
Dialog::Distant_runtime _dialog_runtime { _env };
Keyboard_focus _keyboard_focus { _env, _network.dialog, _network.wpa_passphrase,
*this, _system_dialog, _system_visible };
Constructible<Input::Seq_number> _clicked_seq_number { };
Constructible<Input::Seq_number> _clacked_seq_number { };
void _try_handle_click()
{
if (!_clicked_seq_number.constructed())
return;
Input::Seq_number const seq = *_clicked_seq_number;
/* used to detect clicks outside the popup dialog (for closing it) */
bool popup_dialog_clicked = false;
bool const popup_opened = (_popup_opened_seq_number.value == seq.value);
bool click_consumed = false;
if (_popup_menu_view.hovered(seq)) {
_popup_dialog.click(*this);
click_consumed = true;
_popup_menu_view.generate();
popup_dialog_clicked = true;
}
else if (_settings_menu_view.hovered(seq)) {
_settings_dialog.click(*this);
click_consumed = true;
_settings_menu_view.generate();
}
else if (_system_menu_view.hovered(seq)) {
_system_dialog.click();
click_consumed = true;
_system_menu_view.generate();
}
else if (_network_menu_view.hovered(seq)) {
_network.dialog.click(_network);
click_consumed = true;
_network_menu_view.generate();
}
else if (_file_browser_menu_view.hovered(seq)) {
_file_browser_dialog.click(*this);
click_consumed = true;
_file_browser_menu_view.generate();
}
/* remove popup dialog when clicking somewhere outside */
if (!popup_dialog_clicked && !_popup_menu_view._hovered && !popup_opened) {
if (_popup.state == Popup::VISIBLE) {
_close_popup_dialog();
discard_construction();
}
}
if (click_consumed)
_clicked_seq_number.destruct();
}
void _try_handle_clack()
{
if (!_clacked_seq_number.constructed())
return;
Input::Seq_number const seq = *_clacked_seq_number;
if (_system_menu_view.hovered(seq)) {
_system_dialog.clack();
_system_menu_view.generate();
_clacked_seq_number.destruct();
}
else if (_popup_menu_view.hovered(seq)) {
_popup_dialog.clack(*this);
_clacked_seq_number.destruct();
}
}
struct Keyboard_focus_guard
{
Main &_main;
@ -745,19 +717,9 @@ struct Sculpt::Main : Input_event_handler,
~Keyboard_focus_guard() { _main._keyboard_focus.update(); }
};
/**
* Menu_view::Hover_update_handler interface
*/
void menu_view_hover_updated() override
{
Keyboard_focus_guard focus_guard { *this };
if (_clicked_seq_number.constructed())
_try_handle_click();
if (_clacked_seq_number.constructed())
_try_handle_clack();
}
/* used to prevent closing the popup immediatedly after opened */
Input::Seq_number _popup_opened_seq_number { };
Input::Seq_number _clicked_seq_number { };
/**
* Input_event_handler interface
@ -770,14 +732,25 @@ struct Sculpt::Main : Input_event_handler,
_dialog_runtime.route_input_event(seq_number, ev);
/*
* Detect clicks outside the popup dialog (for closing it)
*/
if (ev.key_press(Input::BTN_LEFT) || ev.touch()) {
_clicked_seq_number.construct(_global_input_seq_number);
_try_handle_click();
}
if (ev.key_release(Input::BTN_LEFT)) {
_clacked_seq_number.construct(_global_input_seq_number);
_try_handle_clack();
_clicked_seq_number = _global_input_seq_number;
bool const popup_opened =
(_popup_opened_seq_number.value == _clicked_seq_number.value);
bool const popup_hovered =
_popup_dialog.if_hovered([&] (Hovered_at const &) { return true; });
if (!popup_hovered && !popup_opened) {
if (_popup.state == Popup::VISIBLE) {
_close_popup_dialog();
discard_construction();
}
}
}
bool need_generate_dialog = false;
@ -786,7 +759,7 @@ struct Sculpt::Main : Input_event_handler,
if (_keyboard_focus.target == Keyboard_focus::WPA_PASSPHRASE)
_network.handle_key_press(code);
else if (_system_visible && _system_dialog.keyboard_needed())
_system_dialog.handle_key(code);
_system_dialog.handle_key(code, *this);
need_generate_dialog = true;
});
@ -808,13 +781,13 @@ struct Sculpt::Main : Input_event_handler,
void use(Storage_target const &target) override
{
_system_dialog.reset_update_dialog();
_system_dialog.reset_update_widget();
_download_queue.reset();
_storage.use(target);
/* hide system panel button and system dialog when "un-using" */
_panel_dialog.refresh();
_system_menu_view.generate();
_system_dialog.refresh();
_handle_window_layout();
}
@ -824,7 +797,7 @@ struct Sculpt::Main : Input_event_handler,
}
/*
* Storage_dialog::Action interface
* Storage_widget::Action interface
*/
void format(Storage_target const &target) override
{
@ -898,9 +871,6 @@ struct Sculpt::Main : Input_event_handler,
}
}
/* used to prevent closing the popup immediatedly after opened */
Input::Seq_number _popup_opened_seq_number { };
/*
* Graph::Action interface
*/
@ -909,10 +879,9 @@ struct Sculpt::Main : Input_event_handler,
if (_popup.state == Popup::VISIBLE)
return;
if (_clicked_seq_number.constructed())
_popup_opened_seq_number = *_clicked_seq_number;
_popup_opened_seq_number = _clicked_seq_number;
_popup_menu_view.generate();
_popup_dialog.refresh();
_popup.anchor = anchor;
_popup.state = Popup::VISIBLE;
_graph_view.refresh();
@ -942,7 +911,7 @@ struct Sculpt::Main : Input_event_handler,
}
/**
* Software_update_dialog::Action interface
* Software_update_widget::Action interface
*/
void query_image_index(Depot::Archive::User const &user) override
{
@ -951,7 +920,7 @@ struct Sculpt::Main : Input_event_handler,
}
/**
* Software_update_dialog::Action interface
* Software_update_widget::Action interface
*/
void trigger_image_download(Path const &path, Verify verify) override
{
@ -962,7 +931,7 @@ struct Sculpt::Main : Input_event_handler,
}
/**
* Software_update_dialog::Action interface
* Software_update_widget::Action interface
*/
void update_image_index(Depot::Archive::User const &user, Verify verify) override
{
@ -973,7 +942,7 @@ struct Sculpt::Main : Input_event_handler,
}
/**
* Software_update_dialog::Action interface
* Software_update_widget::Action interface
*/
void install_boot_image(Path const &path) override
{
@ -993,7 +962,7 @@ struct Sculpt::Main : Input_event_handler,
_selected_tab = tab;
if (_selected_tab == Panel_dialog::Tab::FILES)
_file_browser_menu_view.generate();
_file_browser_dialog.refresh();
_refresh_panel_and_window_layout();
}
@ -1050,14 +1019,30 @@ struct Sculpt::Main : Input_event_handler,
file.with_optional_sub_node("config", [&] (Xml_node const &config) {
_runtime_state.reset_abandoned_and_launched_children();
_deploy.use_as_deploy_template(config);
_deploy.update_managed_deploy_config();
});
}
});
}
});
_deploy.update_managed_deploy_config(); }); } }); } });
}
struct Settings_top_level_dialog : Top_level_dialog
{
Main &_main;
Hosted<Frame, Settings_widget> _hosted { Id { "hosted" }, _main._settings };
Settings_top_level_dialog(Main &main)
: Top_level_dialog("settings"), _main(main) { }
void view(Scope<> &s) const override
{
s.sub_scope<Frame>([&] (Scope<Frame> &s) { s.widget(_hosted); });
}
void click(Clicked_at const &at) override { _hosted.propagate(at, _main); }
void clack(Clacked_at const &) override { }
void drag (Dragged_at const &) override { }
};
Dialog_view<Settings_top_level_dialog> _settings_dialog { _dialog_runtime, *this };
/*
* Settings_dialog::Action interface
*/
@ -1089,7 +1074,7 @@ struct Sculpt::Main : Input_event_handler,
void _handle_fs_query_result()
{
_file_browser_state.update_query_results();
_file_browser_menu_view.generate();
_file_browser_dialog.refresh();
}
Signal_handler<Main> _editor_saved_handler {
@ -1107,7 +1092,7 @@ struct Sculpt::Main : Input_event_handler,
_file_browser_state.last_saved_version = saved.attribute_value("version", 0U);
if (orig_modified != _file_browser_state.modified)
_file_browser_menu_view.generate();
_file_browser_dialog.refresh();
}
void _close_edited_file()
@ -1139,7 +1124,7 @@ struct Sculpt::Main : Input_event_handler,
Priority::LEITZENTRALE,
Ram_quota{8*1024*1024}, Cap_quota{200});
Label const rom_label("report -> /runtime/", start_name, "/listing");
Service::Label const rom_label("report -> /runtime/", start_name, "/listing");
_file_browser_state.query_result.construct(_env, rom_label.string());
_file_browser_state.query_result->sigh(_fs_query_result_handler);
@ -1148,7 +1133,7 @@ struct Sculpt::Main : Input_event_handler,
generate_runtime_config();
_file_browser_menu_view.generate();
_file_browser_dialog.refresh();
}
void browse_sub_directory(File_browser_state::Sub_dir const &sub_dir) override
@ -1236,7 +1221,7 @@ struct Sculpt::Main : Input_event_handler,
/* close popup menu */
_popup.state = Popup::OFF;
_popup_dialog.reset();
_popup_menu_view.generate();
_popup_dialog.refresh();
/* remove popup window from window layout */
_handle_window_layout();
@ -1317,55 +1302,30 @@ struct Sculpt::Main : Input_event_handler,
_runtime_state.with_construction([&] (Component const &c) { fn.with(c); });
}
template <typename TOP_LEVEL_DIALOG>
struct Dialog_view : TOP_LEVEL_DIALOG, private Distant_runtime::View
{
template <typename... ARGS>
Dialog_view(Distant_runtime &runtime, ARGS &&... args)
: TOP_LEVEL_DIALOG(args...), Distant_runtime::View(runtime, *this) { }
using Distant_runtime::View::refresh;
using Distant_runtime::View::min_width;
};
Dialog_view<Panel_dialog> _panel_dialog { _dialog_runtime, *this, *this };
Settings_dialog _settings_dialog { _settings };
Menu_view _settings_menu_view { _env, _child_states, _settings_dialog, "settings_view",
Ram_quota{4*1024*1024}, Cap_quota{150},
"settings_dialog", "settings_view_hover", *this };
System_dialog _system_dialog { _presets, _build_info, _network._nic_state,
_download_queue, _index_update_queue,
_file_operation_queue, _scan_rom,
_image_index_rom, *this, *this, *this };
Menu_view _system_menu_view { _env, _child_states, _system_dialog, "system_view",
Ram_quota{4*1024*1024}, Cap_quota{150},
"system_dialog", "system_view_hover", *this };
Dialog_view<System_dialog> _system_dialog { _dialog_runtime,
_presets, _build_info, _network._nic_state,
_download_queue, _index_update_queue,
_file_operation_queue, _scan_rom,
_image_index_rom, *this, *this };
Dialog_view<Diag_dialog> _diag_dialog { _dialog_runtime, *this, _heap };
Popup_dialog _popup_dialog { _env, *this, _launchers,
_network._nic_state, _network._nic_target,
_runtime_state, _cached_runtime_config,
_download_queue, _scan_rom, *this, *this };
Dialog_view<Popup_dialog> _popup_dialog { _dialog_runtime, _env, *this, *this,
_launchers, _network._nic_state,
_network._nic_target, _runtime_state,
_cached_runtime_config, _download_queue,
_scan_rom, *this, *this };
Menu_view _popup_menu_view { _env, _child_states, _popup_dialog, "popup_view",
Ram_quota{4*1024*1024}, Cap_quota{150},
"popup_dialog", "popup_view_hover", *this };
File_browser_dialog _file_browser_dialog { _cached_runtime_config, _file_browser_state };
Menu_view _file_browser_menu_view { _env, _child_states, _file_browser_dialog, "file_browser_view",
Ram_quota{8*1024*1024}, Cap_quota{150},
"file_browser_dialog", "file_browser_view_hover", *this };
Dialog_view<File_browser_dialog> _file_browser_dialog { _dialog_runtime,
_cached_runtime_config,
_file_browser_state, *this };
/**
* Popup_dialog::Refresh interface
*/
void refresh_popup_dialog() override { _popup_menu_view.generate(); }
void refresh_popup_dialog() override { _popup_dialog.refresh(); }
Managed_config<Main> _fb_drv_config {
_env, "config", "fb_drv", *this, &Main::_handle_fb_drv_config };
@ -1412,27 +1372,21 @@ struct Sculpt::Main : Input_event_handler,
struct Graph_dialog : Dialog::Top_level_dialog
{
Graph &_graph;
Graph::Action &_action;
Ram_fs_dialog::Action &_ram_fs_action;
Main &_main;
Graph_dialog(Graph &graph, Graph::Action &action, Ram_fs_dialog::Action &ram_fs_action)
:
Top_level_dialog("runtime"),
_graph(graph), _action(action), _ram_fs_action(ram_fs_action)
{ }
Graph_dialog(Main &main) : Top_level_dialog("runtime"), _main(main) { }
void view(Scope<> &s) const override
{
s.sub_scope<Depgraph>([&] (Scope<Depgraph> &s) {
_graph.view(s); });
_main._graph.view(s); });
}
void click(Clicked_at const &at) override { _graph.click(at, _action); }
void clack(Clacked_at const &at) override { _graph.clack(at, _action, _ram_fs_action); }
void click(Clicked_at const &at) override { _main._graph.click(at, _main); }
void clack(Clacked_at const &at) override { _main._graph.clack(at, _main, _main._storage); }
void drag (Dragged_at const &) override { }
} _graph_dialog { _graph, *this, _storage };
} _graph_dialog { *this };
Dialog::Distant_runtime::View
_graph_view { _dialog_runtime, _graph_dialog,
@ -1808,7 +1762,7 @@ void Sculpt::Main::_handle_gui_mode()
_panel_dialog.min_width = _screen_size.w();
unsigned const menu_width = max((unsigned)(_font_size_px*21.0), 320u);
_diag_dialog.min_width = menu_width;
_network_menu_view.min_width = menu_width;
_network_dialog.min_width = menu_width;
/* font size may has changed, propagate fonts config of runtime view */
generate_runtime_config();
@ -2098,11 +2052,6 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
});
_dialog_runtime.gen_start_nodes(xml);
_settings_menu_view.gen_start_node(xml);
_system_menu_view.gen_start_node(xml);
_network_menu_view.gen_start_node(xml);
_popup_menu_view.gen_start_node(xml);
_file_browser_menu_view.gen_start_node(xml);
_storage.gen_runtime_start_nodes(xml);
_file_browser_state.gen_start_nodes(xml);

View File

@ -29,8 +29,9 @@ struct Sculpt::Managed_config
{
Env &_env;
typedef String<20> Xml_node_name;
typedef String<32> Rom_name;
using Xml_node_name = String<20>;
using Rom_name = String<32>;
using Label = Session_label;
enum Mode { MANAGED, MANUAL } _mode { MANAGED };

View File

@ -1,172 +0,0 @@
/*
* \brief Menu-view dialog handling
* \author Norman Feske
* \date 2018-05-18
*/
/*
* 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.
*/
#include <menu_view.h>
using namespace Sculpt;
void Menu_view::_handle_hover()
{
using Hover_result = Deprecated_dialog::Hover_result;
_hover_rom.update();
bool const orig_hovered = _hovered;
_hovered = false;
Hover_result hover_result = Hover_result::UNMODIFIED;
Xml_node hover = _hover_rom.xml();
if (hover.has_attribute("seq_number")) {
Input::Seq_number const seq { hover.attribute_value("seq_number", 0U) };
_seq_number.construct(seq);
}
hover.with_optional_sub_node("dialog", [&] (Xml_node hover) {
_hovered = true;
hover_result = _dialog.hover(hover);
});
if (!_hovered)
_dialog.hover(Xml_node("<empty/>"));
bool const dialog_hover_changed = (_hovered != orig_hovered),
widget_hover_changed = (hover_result == Hover_result::CHANGED);
if (dialog_hover_changed || widget_hover_changed)
generate();
/* trigger handling of pending click/clack actions */
_hover_update_handler.menu_view_hover_updated();
}
Menu_view::Menu_view(Env &env, Registry<Child_state> &registry,
Deprecated_dialog &dialog, Start_name const &name,
Ram_quota ram_quota, Cap_quota cap_quota,
Session_label const &dialog_report_name,
Session_label const &hover_rom_name,
Hover_update_handler &hover_update_handler,
Alpha alpha, Color background_color)
:
_dialog(dialog),
_hover_update_handler(hover_update_handler),
_child_state(registry, name, Priority::LEITZENTRALE, ram_quota, cap_quota),
_dialog_reporter(env, "dialog", dialog_report_name.string()),
_hover_rom(env, hover_rom_name.string()),
_hover_handler(env.ep(), *this, &Menu_view::_handle_hover),
_opaque(alpha == Alpha::OPAQUE), _background_color(background_color)
{
_hover_rom.sigh(_hover_handler);
generate();
}
void Menu_view::generate()
{
_dialog_reporter.generate([&] (Xml_generator &xml) {
_dialog.generate(xml); });
}
void Menu_view::reset()
{
_hovered = false;
_dialog.hover(Xml_node("<empty/>"));
_dialog.reset();
}
void Menu_view::gen_start_node(Xml_generator &xml) const
{
xml.node("start", [&] () {
_gen_start_node_content(xml); });
}
void Menu_view::_gen_start_node_content(Xml_generator &xml) const
{
_child_state.gen_start_node_content(xml);
gen_named_node(xml, "resource", "CPU", [&] () {
xml.attribute("quantum", 10); });
gen_named_node(xml, "binary", "menu_view");
xml.node("config", [&] () {
if (min_width) xml.attribute("width", min_width);
if (min_height) xml.attribute("height", min_height);
if (_opaque) xml.attribute("opaque", "yes");
xml.attribute("background", String<20>(_background_color));
xml.node("libc", [&] () { xml.attribute("stderr", "/dev/log"); });
xml.node("report", [&] () { xml.attribute("hover", "yes"); });
xml.node("vfs", [&] () {
gen_named_node(xml, "tar", "menu_view_styles.tar");
gen_named_node(xml, "dir", "fonts", [&] () {
xml.node("fs", [&] () {
xml.attribute("label", "fonts"); }); });
gen_named_node(xml, "dir", "dev", [&] () {
xml.node("log", [&] () { }); });
});
});
xml.node("route", [&] () {
gen_parent_rom_route(xml, "menu_view");
gen_parent_rom_route(xml, "ld.lib.so");
gen_parent_rom_route(xml, "vfs.lib.so");
gen_parent_rom_route(xml, "libc.lib.so");
gen_parent_rom_route(xml, "libm.lib.so");
gen_parent_rom_route(xml, "libpng.lib.so");
gen_parent_rom_route(xml, "zlib.lib.so");
gen_parent_rom_route(xml, "menu_view_styles.tar");
gen_parent_route<Cpu_session> (xml);
gen_parent_route<Pd_session> (xml);
gen_parent_route<Log_session> (xml);
gen_parent_route<Timer::Session> (xml);
using Label = String<128>;
Label const label = _child_state.name();
gen_service_node<Gui::Session>(xml, [&] () {
xml.node("parent", [&] () {
xml.attribute("label", Label("leitzentrale -> ", label)); }); });
gen_service_node<Rom_session>(xml, [&] () {
xml.attribute("label", "dialog");
xml.node("parent", [&] () {
xml.attribute("label", Label("leitzentrale -> ", label, " -> dialog"));
});
});
gen_service_node<Report::Session>(xml, [&] () {
xml.attribute("label", "hover");
xml.node("parent", [&] () {
xml.attribute("label", Label("leitzentrale -> ", label, " -> hover"));
});
});
gen_service_node<::File_system::Session>(xml, [&] () {
xml.attribute("label", "fonts");
xml.node("parent", [&] () {
xml.attribute("label", "leitzentrale -> fonts"); }); });
});
}

View File

@ -1,96 +0,0 @@
/*
* \brief Menu-view dialog handling
* \author Norman Feske
* \date 2018-05-18
*/
/*
* 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 _MENU_VIEW_H_
#define _MENU_VIEW_H_
/* Genode includes */
#include <base/session_label.h>
#include <os/reporter.h>
#include <base/attached_rom_dataspace.h>
/* local includes */
#include "types.h"
#include <view/dialog.h>
#include <model/child_state.h>
namespace Sculpt { struct Menu_view; }
struct Sculpt::Menu_view : Noncopyable
{
struct Hover_update_handler : Interface, Noncopyable
{
virtual void menu_view_hover_updated() = 0;
};
Deprecated_dialog &_dialog;
Hover_update_handler &_hover_update_handler;
Child_state _child_state;
Expanding_reporter _dialog_reporter;
Attached_rom_dataspace _hover_rom;
Signal_handler<Menu_view> _hover_handler;
bool const _opaque;
Color const _background_color;
bool _hovered = false;
Constructible<Input::Seq_number> _seq_number { };
unsigned min_width = 0;
unsigned min_height = 0;
void _handle_hover();
void _gen_start_node_content(Xml_generator &) const;
enum class Alpha { OPAQUE, ALPHA };
Menu_view(Env &, Registry<Child_state> &registry,
Deprecated_dialog &, Start_name const &, Ram_quota, Cap_quota,
Session_label const &dialog_report_name,
Session_label const &hover_rom_name,
Hover_update_handler &,
Alpha alpha = Alpha::ALPHA,
Color background = Color { 127, 127, 127, 255 });
void generate();
bool hovered(Input::Seq_number const &seq_number) const
{
if (!_seq_number.constructed() || !_hovered)
return false;
return (_seq_number->value >= seq_number.value);
}
void reset();
void gen_start_node(Xml_generator &) const;
bool apply_child_state_report(Xml_node report)
{
return _child_state.apply_child_state_report(report);
}
void trigger_restart() { _child_state.trigger_restart(); }
};
#endif /* _MENU_VIEW_H_ */

View File

@ -20,6 +20,7 @@
/* local includes */
#include "types.h"
#include <xml.h>
namespace Sculpt { struct Child_state; }

View File

@ -61,16 +61,14 @@ struct Sculpt::File_browser_state : Noncopyable
fn(query_result->xml());
}
using Index = Label;
template <typename FN>
void with_entry_at_index(Index index, FN const &fn) const
void with_entry_at_index(unsigned const index, FN const &fn) const
{
unsigned cnt = 0;
unsigned count = 0;
with_query_result([&] (Xml_node node) {
node.with_optional_sub_node("dir", [&] (Xml_node listing) {
listing.for_each_sub_node([&] (Xml_node entry) {
if (Index(cnt++) == index)
if (count++ == index)
fn(entry); }); }); });
}
@ -187,7 +185,7 @@ struct Sculpt::File_browser_state : Noncopyable
gen_service_node<Gui::Session>(xml, [&] () {
xml.node("parent", [&] () {
xml.attribute("label", Label("leitzentrale -> editor")); }); });
xml.attribute("label", "leitzentrale -> editor"); }); });
gen_service_node<::File_system::Session>(xml, [&] () {
xml.attribute("label", "fonts");

View File

@ -14,6 +14,7 @@
#ifndef _MODEL__ROUTE_H_
#define _MODEL__ROUTE_H_
#include <xml.h>
#include <types.h>
#include <model/service.h>
@ -22,8 +23,9 @@ namespace Sculpt { struct Route; }
struct Sculpt::Route : List_model<Route>::Element
{
typedef String<32> Id;
typedef String<80> Info;
using Id = String<32>;
using Info = String<80>;
using Label = Service::Label;
static char const *xml_type(Service::Type type)
{

View File

@ -60,8 +60,8 @@ class Sculpt::Runtime_config
Service::Type_name const service =
node.attribute_value("name", Service::Type_name());
Label const dst_label =
parent.attribute_value("label", Label());
Service::Label const dst_label =
parent.attribute_value("label", Service::Label());
bool const ignored_service = (service == "CPU")
|| (service == "PD")
@ -167,7 +167,7 @@ class Sculpt::Runtime_config
}
Child_service(Start_name server, Xml_node provides)
: Service(server, type_from_xml(provides), Label()) { }
: Service(server, type_from_xml(provides), { }) { }
static bool type_matches(Xml_node const &node)
{
@ -356,7 +356,7 @@ class Sculpt::Runtime_config
Service const _used_fs_service { "default_fs_rw",
Service::Type::FILE_SYSTEM,
Label(), "used file system" };
{ }, "used file system" };
public:
@ -428,6 +428,15 @@ class Sculpt::Runtime_config
_components.for_each([&] (Component const &component) {
component.for_each_service(fn); });
}
unsigned num_service_options(Service::Type const type) const
{
unsigned count = 0;
for_each_service([&] (Service const &service) {
if (service.type == type)
count++; });
return count;
}
};
#endif /* _MODEL__RUNTIME_CONFIG_H_ */

View File

@ -21,8 +21,9 @@ namespace Sculpt { struct Service; }
struct Sculpt::Service
{
typedef String<16> Type_name;
typedef String<32> Info;
using Type_name = String<16>;
using Info = String<32>;
using Label = String<64>;
enum class Type {
AUDIO_IN, AUDIO_OUT, BLOCK, EVENT, CAPTURE, FILE_SYSTEM, NIC, GUI, GPU,

View File

@ -40,7 +40,7 @@ void Sculpt::Network::handle_key_press(Codepoint code)
wpa_passphrase.remove_last_character();
else if (code.value == ENTER) {
if (wpa_passphrase.suitable_for_connect())
wifi_connect(dialog.selected_ap());
wifi_connect(dialog._ap_selector._selected);
}
else if (code.valid())
wpa_passphrase.append_character(code);
@ -124,7 +124,7 @@ void Sculpt::Network::_handle_wlan_accesspoints()
_wlan_accesspoints_rom.update();
/* suppress updating the list while the access-point list is hovered */
if (!initial_scan && dialog.ap_list_hovered())
if (!initial_scan && _info.ap_list_hovered())
return;
_access_points.update_from_xml(_wlan_accesspoints_rom.xml(),

View File

@ -21,8 +21,7 @@
/* local includes */
#include <model/child_exit_state.h>
#include <model/pci_info.h>
#include <view/network_dialog.h>
#include <menu_view.h>
#include <view/network_widget.h>
#include <runtime.h>
#include <keyboard_focus.h>
#include <managed_config.h>
@ -30,7 +29,7 @@
namespace Sculpt { struct Network; }
struct Sculpt::Network : Network_dialog::Action
struct Sculpt::Network : Network_widget::Action
{
Env &_env;
@ -41,7 +40,13 @@ struct Sculpt::Network : Network_dialog::Action
virtual void update_network_dialog() = 0;
};
Action &_action;
struct Info : Interface
{
virtual bool ap_list_hovered() const = 0;
};
Action &_action;
Info const &_info;
Registry<Child_state> &_child_states;
@ -50,9 +55,13 @@ struct Sculpt::Network : Network_dialog::Action
Runtime_info const &_runtime_info;
Pci_info const &_pci_info;
using Wlan_config_policy = Network_widget::Wlan_config_policy;
Nic_target _nic_target { };
Nic_state _nic_state { };
Access_point::Bssid _selected_ap { };
Wpa_passphrase wpa_passphrase { };
unsigned _nic_drv_version = 0;
@ -100,10 +109,9 @@ struct Sculpt::Network : Network_dialog::Action
Signal_handler<Network> _nic_router_state_handler {
_env.ep(), *this, &Network::_handle_nic_router_state };
Network_dialog::Wlan_config_policy _wlan_config_policy =
Network_dialog::WLAN_CONFIG_MANAGED;
Wlan_config_policy _wlan_config_policy = Wlan_config_policy::MANAGED;
Network_dialog dialog {
Network_widget dialog {
_nic_target, _access_points,
_wifi_connection, _nic_state, wpa_passphrase, _wlan_config_policy,
_pci_info };
@ -114,12 +122,12 @@ struct Sculpt::Network : Network_dialog::Action
void _handle_wlan_config(Xml_node)
{
if (_wlan_config.try_generate_manually_managed()) {
_wlan_config_policy = Network_dialog::WLAN_CONFIG_MANUAL;
_wlan_config_policy = Wlan_config_policy::MANUAL;
_action.update_network_dialog();
return;
}
_wlan_config_policy = Network_dialog::WLAN_CONFIG_MANAGED;;
_wlan_config_policy = Wlan_config_policy::MANAGED;;
if (_wifi_connection.connected())
wifi_connect(_wifi_connection.bssid);
@ -130,7 +138,7 @@ struct Sculpt::Network : Network_dialog::Action
void _update_nic_target_from_config(Xml_node const &);
/**
* Network_dialog::Action interface
* Network_widget::Action interface
*/
void nic_target(Nic_target::Type const type) override
{
@ -207,12 +215,13 @@ struct Sculpt::Network : Network_dialog::Action
_runtime_config_generator.generate_runtime_config();
}
Network(Env &env, Allocator &alloc, Action &action,
Network(Env &env, Allocator &alloc, Action &action, Info const &info,
Registry<Child_state> &child_states,
Runtime_config_generator &runtime_config_generator,
Runtime_info const &runtime_info, Pci_info const &pci_info)
:
_env(env), _alloc(alloc), _action(action), _child_states(child_states),
_env(env), _alloc(alloc), _action(action), _info(info),
_child_states(child_states),
_runtime_config_generator(runtime_config_generator),
_runtime_info(runtime_info), _pci_info(pci_info)
{

View File

@ -20,14 +20,14 @@
/* local includes */
#include <model/discovery_state.h>
#include <view/storage_dialog.h>
#include <view/ram_fs_dialog.h>
#include <view/storage_widget.h>
#include <view/ram_fs_widget.h>
#include <runtime.h>
namespace Sculpt { struct Storage; }
struct Sculpt::Storage : Storage_device_dialog::Action, Ram_fs_dialog::Action
struct Sculpt::Storage : Storage_device_widget::Action, Ram_fs_widget::Action
{
Env &_env;
@ -101,7 +101,7 @@ struct Sculpt::Storage : Storage_device_dialog::Action, Ram_fs_dialog::Action
}
/**
* Storage_dialog::Action interface
* Storage_widget::Action interface
*/
void format(Storage_target const &target) override
{

View File

@ -40,7 +40,6 @@ namespace Sculpt {
typedef String<64> Rom_name;
typedef String<128> Path;
typedef String<36> Start_name;
typedef String<64> Label;
typedef Gui::Point Point;
typedef Gui::Rect Rect;

View File

@ -1,65 +0,0 @@
/*
* \brief GUI element that can be activated on clack
* \author Norman Feske
* \date 2018-05-03
*/
/*
* 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 _VIEW__ACTIVATABLE_ITEM_H_
#define _VIEW__ACTIVATABLE_ITEM_H_
#include "hoverable_item.h"
namespace Sculpt { struct Activatable_item; }
struct Sculpt::Activatable_item : Hoverable_item
{
typedef Hoverable_item::Id Id;
Id _selected { };
Id _activated { };
/**
* Apply click - if item is hovered, the click selects the item but
* does not activate it yet
*/
void propose_activation_on_click()
{
_selected = _hovered;
}
void confirm_activation_on_clack()
{
if (_hovered.valid() && (_hovered == _selected))
_activated = _selected;
}
void reset() { _selected = Id{}, _activated = Id{}; }
/**
* Return true if item is currently activated
*/
bool activated(Id const &id) const { return id == _activated; }
/**
* Generate button attributes depending on the item state
*/
void gen_button_attr(Xml_generator &xml, Id const &id) const
{
/* hover only as long as the button is not activated */
if (!_selected.valid() || !_activated.valid())
Hoverable_item::gen_button_attr(xml, id);
if (_selected.valid() && _selected == _hovered && _selected == id)
xml.attribute("selected", "yes");
}
};
#endif /* _VIEW__ACTIVATABLE_ITEM_H_ */

View File

@ -0,0 +1,231 @@
/*
* \brief Access-point selector
* \author Norman Feske
* \date 2023-11-06
*/
/*
* Copyright (C) 2023 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 _VIEW__AP_SELECTOR_WIDGET_H_
#define _VIEW__AP_SELECTOR_WIDGET_H_
/* local includes */
#include <types.h>
#include <model/wifi_connection.h>
#include <model/wpa_passphrase.h>
#include <view/dialog.h>
namespace Sculpt { struct Ap_selector_widget; }
struct Sculpt::Ap_selector_widget : Widget<Vbox>
{
enum class Wlan_config_policy { MANAGED, MANUAL };
Access_points const &_access_points;
Wifi_connection const &_wifi_connection;
Wlan_config_policy const &_wlan_config_policy;
Blind_wpa_passphrase const &_wpa_passphrase;
struct Action : Interface
{
virtual void wifi_connect(Access_point::Ssid) = 0;
virtual void wifi_disconnect() = 0;
};
/* limit view to highest-quality access points */
unsigned const _max_visible_aps = 20;
Access_point::Bssid _selected { };
Hosted<Vbox, Action_button> _connect { Id { "Connect" } };
struct Item : Widget<Hbox>
{
struct Attr { bool selected; };
void view(Scope<Hbox> &s, Access_point const &ap, Attr attr) const
{
bool const hovered = s.hovered();
s.sub_scope<Left_floating_hbox>([&] (Scope<Hbox, Left_floating_hbox> &s) {
s.sub_scope<Icon>("radio", Icon::Attr { .hovered = hovered,
.selected = attr.selected });
s.sub_scope<Label>(String<20>(" ", ap.ssid));
s.sub_scope<Annotation>((ap.protection == Access_point::WPA_PSK)
? " (WPA) " : " ");
});
s.sub_scope<Float>([&] (Scope<Hbox, Float> &s) {
s.attribute("east", "yes");
s.sub_scope<Label>(String<8>(ap.quality, "%"));
});
}
};
/*
* \return true if at least one access point fulfils the condition 'cond_fn'
*/
bool _for_each_ap(auto const &cond_fn) const
{
bool result = false;
_access_points.for_each([&] (Access_point const &ap) {
result |= cond_fn(ap); });
return result;
}
bool _selected_ap_visible() const
{
unsigned count = 0;
return _for_each_ap([&] (Access_point const &ap) {
return (count++ < _max_visible_aps) && (_selected == ap.bssid); });
}
void _with_selected_ap(auto const &fn) const
{
bool done = false;
_access_points.for_each([&] (Access_point const &ap) {
if (!done && (ap.bssid == _selected)) {
fn(ap);
done = true; } });
/*
* If access point is not present in the list, fall back to the information
* given in the 'state' report.
*/
if (!done)
fn(Access_point { _wifi_connection.bssid,
_wifi_connection.ssid,
Access_point::UNKNOWN });
}
Ap_selector_widget(Access_points const &aps,
Wifi_connection const &wifi_connection,
Wlan_config_policy const &wlan_config_policy,
Blind_wpa_passphrase const &wpa_passphrase)
:
_access_points(aps), _wifi_connection(wifi_connection),
_wlan_config_policy(wlan_config_policy), _wpa_passphrase(wpa_passphrase)
{ }
void view(Scope<Vbox> &s) const
{
if (_wlan_config_policy == Wlan_config_policy::MANUAL)
return;
if (_wifi_connection.connecting() || _wifi_connection.connected()) {
Hosted<Vbox, Item> item { Id { _selected } };
_with_selected_ap([&] (Access_point const &ap) {
s.widget(item, ap, Item::Attr { .selected = true }); });
s.sub_scope<Label>(_wifi_connection.connecting()
? "connecting" : "associated" );
return;
}
bool const selected_ap_visible = _selected_ap_visible();
unsigned count = 0;
_access_points.for_each([&] (Access_point const &ap) {
if (count++ >= _max_visible_aps)
return;
/*
* Whenever the user has selected an access point, hide all others.
* Should the selected AP disappear from the list, show all others.
*/
bool const selected = (_selected == ap.bssid);
if (selected_ap_visible && !selected)
return;
Hosted<Vbox, Item> item { Id { ap.bssid } };
s.widget(item, ap, Item::Attr { .selected = selected });
if (!selected)
return;
bool const connected_to_selected_ap =
(selected && _wifi_connection.ssid == ap.ssid)
&& _wifi_connection.state == Wifi_connection::CONNECTED;
if (connected_to_selected_ap)
return;
if (ap.protection == Access_point::WPA_PSK) {
s.sub_scope<Label>(_wifi_connection.auth_failure()
? "Enter passphrase (auth failure):"
: "Enter passphrase:");
s.sub_scope<Frame>([&] (Scope<Vbox, Frame> &s) {
s.sub_scope<Float>([&] (Scope<Vbox, Frame, Float> &s) {
s.attribute("west", "yes");
String<3*64> const passphrase(" ", _wpa_passphrase);
s.sub_scope<Label>(passphrase, [&] (auto &s) {
s.attribute("font", "title/regular");
s.sub_node("cursor", [&] {
s.attribute("at", passphrase.length() - 1); });
});
});
});
if (_wpa_passphrase.suitable_for_connect())
s.widget(_connect);
}
});
/*
* Present motivational message until we get the first 'accesspoints'
* report.
*/
if (count == 0)
s.sub_scope<Label>("Scanning...");
}
bool need_keyboard_focus_for_passphrase() const
{
if (_wifi_connection.state == Wifi_connection::CONNECTED
|| _wifi_connection.state == Wifi_connection::CONNECTING)
return false;
return _for_each_ap([&] (Access_point const &ap) {
return (_selected == ap.bssid) && ap.wpa_protected(); });
}
void click(Clicked_at const &at, Action &action)
{
Id const ap_id = at.matching_id<Vbox, Item>();
if (ap_id.valid()) {
if (ap_id.value == _selected) {
action.wifi_disconnect();
_selected = { };
} else {
_selected = ap_id.value;
auto selected_ap_unprotected = [&] {
return _for_each_ap([&] (Access_point const &ap) {
return (_selected == ap.bssid) && ap.unprotected(); }); };
/* immediately connect to unprotected access point when selected */
if (selected_ap_unprotected())
action.wifi_connect(_selected);
}
}
_connect.propagate(at, [&] { action.wifi_connect(_selected); });
}
bool ap_list_shown() const { return !_selected.valid(); }
};
#endif /* _VIEW__AP_SELECTOR_WIDGET_H_ */

View File

@ -1,107 +0,0 @@
/*
* \brief Debug options dialog
* \author Christian Prochaska
* \date 2022-09-06
*/
/*
* Copyright (C) 2023 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 _VIEW__DEBUG_DIALOG_H_
#define _VIEW__DEBUG_DIALOG_H_
/* local includes */
#include <xml.h>
#include <model/component.h>
#include <view/dialog.h>
namespace Sculpt { struct Debug_dialog; }
struct Sculpt::Debug_dialog : Noncopyable, Deprecated_dialog
{
bool _monitor = false;
bool _wait = false;
bool _wx = false;
Hoverable_item _item { };
Hover_result hover(Xml_node hover) override
{
return Deprecated_dialog::any_hover_changed(
_item.match(hover, "vbox", "hbox", "name"));
}
void click(Component &component)
{
Hoverable_item::Id const clicked = _item._hovered;
if (!clicked.valid())
return;
if (clicked == "monitor") _monitor = !_monitor;
if (clicked == "wx") _wx = !_wx;
if (clicked == "wait") _wait = !_wait;
/* "wx" depends on "monitor", "wait" depends on "wx" */
_wx &= _monitor;
_wait &= _wx;
component.wx = _wx;
component.monitor = _monitor;
component.wait = _wait;
}
void _gen_checkbox(Xml_generator &xml, Start_name const &name,
Component::Info const &text, bool selected) const
{
gen_named_node(xml, "hbox", name, [&] {
gen_named_node(xml, "float", "left", [&] {
xml.attribute("west", "yes");
xml.node("hbox", [&] {
gen_named_node(xml, "button", "button", [&] {
if (selected)
xml.attribute("selected", "yes");
xml.attribute("style", "checkbox");
_item.gen_hovered_attr(xml, name);
xml.node("hbox", [&] { });
});
gen_named_node(xml, "label", "name", [&] {
xml.attribute("text", Path(" ", text)); });
});
});
gen_named_node(xml, "hbox", "right", [&] { });
});
}
void generate(Xml_generator &xml) const override
{
xml.node("vbox", [&] {
_gen_checkbox(xml, "monitor", "Debug", _monitor);
if (_monitor)
_gen_checkbox(xml, "wx", "Allow code patching", _wx);
if (_wx)
_gen_checkbox(xml, "wait", "Wait for GDB", _wait);
});
}
void reset() override
{
_item._hovered = Hoverable_item::Id();
_monitor = _wait = _wx = false;
}
};
#endif /* _VIEW__DEBUG_DIALOG_H_ */

View File

@ -0,0 +1,53 @@
/*
* \brief Debug-options widget
* \author Norman Feske
* \date 2023-10-30
*/
/*
* Copyright (C) 2023 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 _VIEW__DEBUG_WIDGET_H_
#define _VIEW__DEBUG_WIDGET_H_
/* local includes */
#include <model/component.h>
#include <view/dialog.h>
namespace Sculpt { struct Debug_widget; }
struct Sculpt::Debug_widget : Widget<Vbox>
{
Hosted<Vbox, Menu_entry> _monitor { Id { "monitor" } },
_wx { Id { "wx" } },
_wait { Id { "wait" } };
void click(Clicked_at const &at, Component &component)
{
_monitor.propagate(at, [&] { component.monitor = !component.monitor; });
_wx .propagate(at, [&] { component.wx = !component.wx; });
_wait .propagate(at, [&] { component.wait = !component.wait; });
/* "wx" depends on "monitor", "wait" depends on "wx" */
component.wx &= component.monitor;
component.wait &= component.wx;
}
void view(Scope<Vbox> &s, Component const &component) const
{
s.widget(_monitor, component.monitor, "Debug", "checkbox");
if (component.monitor)
s.widget(_wx, component.wx, "Allow code patching", "checkbox");
if (component.wx)
s.widget(_wait, component.wait, "Wait for GDB", "checkbox");
}
};
#endif /* _VIEW__DEBUG_WIDGET_H_ */

View File

@ -1,364 +0,0 @@
/*
* \brief Dialog for selecting a depot user
* \author Norman Feske
* \date 2023-03-17
*/
/*
* Copyright (C) 2023 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 _VIEW__DEPOT_USERS_DIALOG_H_
#define _VIEW__DEPOT_USERS_DIALOG_H_
#include <view/dialog.h>
#include <view/text_entry_field.h>
#include <model/depot_url.h>
namespace Sculpt { struct Depot_users_dialog; }
struct Sculpt::Depot_users_dialog
{
public:
using Depot_users = Attached_rom_dataspace;
using User = Depot::Archive::User;
using Url = Depot_url::Url;
using Hover_result = Hoverable_item::Hover_result;
struct Action : Interface
{
virtual void add_depot_url(Depot_url const &depot_url) = 0;
};
private:
using Url_edit_field = Text_entry_field<50>;
User const _default_user;
Depot_users const &_depot_users;
Action &_action;
User _selected;
bool _unfolded = false;
bool _selected_user_exists = false;
Hoverable_item _user { };
Hoverable_item _button { };
Url const _orig_edit_url { "https://" };
Url_edit_field _url_edit_field { _orig_edit_url };
Url _url(Xml_node const &user) const
{
if (!user.has_sub_node("url"))
return { };
Url const url = user.sub_node("url").decoded_content<Url>();
/*
* Ensure that the URL does not contain any '"' character because
* it will be taken as an XML attribute value.
*/
for (char const *s = url.string(); *s; s++)
if (*s == '"')
return { };
User const name = user.attribute_value("name", User());
return Url(url, "/", name);
}
static void _gen_vspacer(Xml_generator &xml, char const *name)
{
gen_named_node(xml, "label", name, [&] () {
xml.attribute("text", " ");
xml.attribute("font", "annotation/regular");
});
}
static inline char const *_add_id() { return "/add"; }
template <typename GEN_LABEL_FN, typename RIGHT_FN>
void _gen_item(Xml_generator &xml, User const &name,
GEN_LABEL_FN const &gen_label_fn,
RIGHT_FN const &right_fn) const
{
bool const selected = (name == _selected);
gen_named_node(xml, "hbox", name, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "float", "button", [&] () {
gen_named_node(xml, "button", "button", [&] () {
_user.gen_hovered_attr(xml, name);
if (selected)
xml.attribute("selected", "yes");
xml.attribute("style", "radio");
xml.node("hbox", [&] () { });
});
});
gen_named_node(xml, "label", "name", [&] () {
gen_label_fn(); });
});
});
gen_named_node(xml, "hbox", "right", [&] () {
right_fn(); });
});
}
void _gen_entry(Xml_generator &xml, Xml_node const user, bool /* last */) const
{
User const name = user.attribute_value("name", User());
bool const selected = (name == _selected);
Url const url = _url(user);
Url const label = Depot_url::from_string(url).valid() ? url : Url(name);
bool const show_all = _unfolded || !_selected_user_exists;
if (!selected && !show_all)
return;
_gen_item(xml, name,
[&] /* label */ { xml.attribute("text", Path(" ", label)); },
[&] /* right */ { }
);
}
Depot_url _depot_url(Xml_node const &depot_users) const
{
Depot_url const result =
Depot_url::from_string(Depot_url::Url { _url_edit_field });
/* check for duplicated user name */
bool unique = true;
depot_users.for_each_sub_node("user", [&] (Xml_node user) {
User const name = user.attribute_value("name", User());
if (name == result.user)
unique = false;
});
return unique ? result : Depot_url { };
}
void _gen_add_entry(Xml_generator &xml, Xml_node const &depot_users) const
{
_gen_item(xml, _add_id(),
[&] /* label */ {
xml.attribute("text", Depot_url::Url(" ", _url_edit_field));
xml.attribute("min_ex", 30);
xml.node("cursor", [&] () {
xml.attribute("at", _url_edit_field.cursor_pos + 1); });
},
[&] /* right */ {
gen_named_node(xml, "float", "actions", [&] {
xml.attribute("east", "yes");
bool const editing = (_selected == _add_id());
if (editing) {
bool const url_valid = _depot_url(depot_users).valid();
gen_named_node(xml, "button", "add", [&] {
if (!url_valid)
xml.attribute("style", "unimportant");
else
_button.gen_hovered_attr(xml, "add");
xml.node("label", [&] {
if (!url_valid)
xml.attribute("style", "unimportant");
xml.attribute("text", "Add"); });
});
} else {
gen_named_node(xml, "button", "edit", [&] {
_button.gen_hovered_attr(xml, "edit");
xml.node("label", [&] {
xml.attribute("text", "Edit"); }); });
}
});
}
);
}
void _gen_selection(Xml_generator &xml) const
{
Xml_node const depot_users = _depot_users.xml();
size_t remain_count = depot_users.num_sub_nodes();
remain_count++; /* account for '_gen_add_entry' */
bool known_pubkey = false;
gen_named_node(xml, "frame", "user_selection", [&] () {
xml.node("vbox", [&] () {
depot_users.for_each_sub_node("user", [&] (Xml_node user) {
if (_selected == user.attribute_value("name", User()))
known_pubkey = user.attribute_value("known_pubkey", false);
bool const last = (--remain_count == 0);
_gen_entry(xml, user, last); });
if (_unfolded || !_selected_user_exists)
_gen_add_entry(xml, depot_users);
});
});
if (!_unfolded && !known_pubkey && _selected_user_exists) {
gen_named_node(xml, "button", "pubkey warning", [&] {
xml.attribute("style", "invisible");
xml.node("label", [&] {
xml.attribute("font", "annotation/regular");
xml.attribute("text", "missing public key for verification"); });
});
}
}
public:
Depot_users_dialog(Depot_users const &depot_users,
User const &default_user,
Action &action)
:
_default_user(default_user), _depot_users(depot_users),
_action(action), _selected(default_user)
{ }
User selected() const
{
return (_selected == _add_id()) ? User() : _selected;
}
void generate(Xml_generator &xml) const { _gen_selection(xml); }
bool unfolded() const { return _unfolded || !_selected_user_exists; }
struct User_properties
{
bool exists;
bool download_url;
bool public_key;
};
User_properties selected_user_properties() const
{
User_properties result { };
_depot_users.xml().for_each_sub_node([&] (Xml_node const &user) {
if (_selected == user.attribute_value("name", User())) {
result = {
.exists = true,
.download_url = Depot_url::from_string(_url(user)).valid(),
.public_key = user.attribute_value("known_pubkey", false)
};
}
});
return result;
}
template <typename SELECT_FN>
void click(SELECT_FN const &select_fn)
{
/* unfold depot users */
if (!_unfolded) {
_unfolded = true;
return;
}
/* handle click on unfolded depot-user selection */
auto select_depot_user = [&] (User const &user)
{
_selected = user;
select_fn(user);
_unfolded = false;
_selected_user_exists = true;
_url_edit_field = _orig_edit_url;
};
if (_user._hovered.length() <= 1)
return;
if (_user.hovered(_add_id())) {
if (_button.hovered("add")) {
Depot_url const depot_url = _depot_url(_depot_users.xml());
if (depot_url.valid()) {
_action.add_depot_url(depot_url);
select_depot_user(depot_url.user);
}
} else {
_selected = _add_id();
}
} else {
select_depot_user(_user._hovered);
}
}
Hover_result hover(Xml_node const &hover)
{
return Deprecated_dialog::any_hover_changed(
_user .match(hover, "frame", "vbox", "hbox", "name"),
_button.match(hover, "frame", "vbox", "hbox", "hbox", "float", "button", "name")
);
}
void reset_hover() { _user._hovered = { }; }
bool hovered() const { return _user._hovered.valid(); }
bool keyboard_needed() const { return _selected == _add_id(); }
void handle_key(Codepoint c)
{
if (_selected != _add_id())
return;
/* prevent input of printable yet risky characters as URL */
if (c.value == ' ' || c.value == '"')
return;
/* respond to enter key */
if (c.value == 10) {
Depot_url const depot_url = _depot_url(_depot_users.xml());
if (depot_url.valid()) {
_action.add_depot_url(depot_url);
_selected = depot_url.user;
_selected_user_exists = true;
_unfolded = false;
_url_edit_field = _orig_edit_url;
}
return;
}
_url_edit_field.apply(c);
}
bool one_selected() const { return !_unfolded && _selected.length() > 1; }
void sanitize_unfold_state()
{
/*
* If the selected depot user does not exist in the depot, show
* list of available users.
*/
_selected_user_exists = false;
_depot_users.xml().for_each_sub_node([&] (Xml_node const &user) {
if (_selected == user.attribute_value("name", User()))
_selected_user_exists = true; });
}
};
#endif /* _VIEW__DEPOT_USERS_DIALOG_H_ */

View File

@ -0,0 +1,346 @@
/*
* \brief Widget for selecting a depot user
* \author Norman Feske
* \date 2023-03-17
*/
/*
* Copyright (C) 2023 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 _VIEW__DEPOT_USERS_WIDGET_H_
#define _VIEW__DEPOT_USERS_WIDGET_H_
#include <view/dialog.h>
#include <view/text_entry_field.h>
#include <model/depot_url.h>
namespace Sculpt { struct Depot_users_widget; }
struct Sculpt::Depot_users_widget : Widget<Vbox>
{
public:
using Depot_users = Attached_rom_dataspace;
using User = Depot::Archive::User;
using Url = Depot_url::Url;
struct Action : Interface
{
virtual void add_depot_url(Depot_url const &depot_url) = 0;
};
private:
using Url_edit_field = Text_entry_field<50>;
User const _default_user;
Depot_users const &_depot_users;
User _selected { _default_user };
bool _unfolded = false;
bool _selected_user_exists = false;
Url _url(Xml_node const &user) const
{
if (!user.has_sub_node("url"))
return { };
Url const url = user.sub_node("url").decoded_content<Url>();
/*
* Ensure that the URL does not contain any '"' character because
* it will be taken as an XML attribute value.
*/
for (char const *s = url.string(); *s; s++)
if (*s == '"')
return { };
User const name = user.attribute_value("name", User());
return Url(url, "/", name);
}
static inline char const *_add_id() { return "/add"; }
struct Conditional_button : Widget<Button>
{
Dialog::Event::Seq_number _seq_number { };
void view(Scope<Button> &s, bool ready, auto const &text) const
{
bool const selected = _seq_number == s.hover.seq_number;
if (!ready)
s.attribute("style", "unimportant");
if (selected)
s.attribute("selected", "yes");
if (s.hovered() && !s.dragged() && !selected && ready)
s.attribute("hovered", "yes");
s.sub_scope<Label>(text, [&] (auto &s) {
if (!ready)
s.attribute("style", "unimportant"); });
}
void view(Scope<Button> &s, bool ready) const
{
view(s, ready, s.id.value);
}
void click(Clicked_at const &at, auto const &fn)
{
_seq_number = at.seq_number;
fn();
}
};
struct Item : Widget<Hbox>
{
void view(Scope<Hbox> &s, bool selected, auto const &text) const
{
bool const hovered = s.hovered();
s.sub_scope<Left_floating_hbox>([&] (Scope<Hbox, Left_floating_hbox> &s) {
s.sub_scope<Icon>("radio", Icon::Attr { .hovered = hovered,
.selected = selected });
s.sub_scope<Label>(text);
});
}
};
struct Edit_item : Widget<Hbox>
{
Url const _orig_edit_url { "https://" };
Url_edit_field _url_edit_field { _orig_edit_url };
Depot_url depot_url(Xml_node const &depot_users) const
{
Depot_url const result =
Depot_url::from_string(Depot_url::Url { _url_edit_field });
/* check for duplicated user name */
bool unique = true;
depot_users.for_each_sub_node("user", [&] (Xml_node user) {
User const name = user.attribute_value("name", User());
if (name == result.user)
unique = false; });
return unique ? result : Depot_url { };
}
Hosted<Hbox, Hbox, Float, Conditional_button> _add { Id { "Add" } };
Hosted<Hbox, Hbox, Float, Action_button> _edit { Id { "Edit" } };
bool _ready_to_add(Xml_node const &depot_users) const
{
return depot_url(depot_users).valid();
}
void view(Scope<Hbox> &s, bool selected, Xml_node const &depot_users) const
{
bool const hovered = s.hovered() && !s.dragged() && !selected;
s.sub_scope<Left_floating_hbox>([&] (Scope<Hbox, Left_floating_hbox> &s) {
s.sub_scope<Icon>("radio", Icon::Attr { .hovered = hovered,
.selected = selected });
auto const text = Depot_url::Url(" ", _url_edit_field);
s.sub_scope<Label>(text, [&] (auto &s) {
s.attribute("min_ex", 30);
s.template sub_node("cursor", [&] {
s.attribute("at", _url_edit_field.cursor_pos + 1); });
});
});
s.sub_scope<Hbox>([&] (Scope<Hbox, Hbox> &s) {
s.sub_scope<Float>([&] (Scope<Hbox, Hbox, Float> &s) {
s.attribute("east", "yes");
if (selected)
s.widget(_add, _ready_to_add(depot_users));
else
s.widget(_edit);
});
});
}
void reset()
{
_url_edit_field = _orig_edit_url;
}
void handle_key(Codepoint c, auto const &enter_fn)
{
/* prevent input of printable yet risky characters as URL */
if (c.value == ' ' || c.value == '"')
return;
/* respond to enter key */
if (c.value == 10)
enter_fn();
_url_edit_field.apply(c);
}
void click(Clicked_at const &, Xml_node const &depot_users, auto const add_fn)
{
if (_ready_to_add(depot_users))
add_fn();
}
};
Hosted<Vbox, Frame, Vbox, Edit_item> _edit_item { Id { _add_id() } };
public:
Depot_users_widget(Depot_users const &depot_users,
User const &default_user)
:
_default_user(default_user), _depot_users(depot_users)
{ }
User selected() const
{
return (_selected == _add_id()) ? User() : _selected;
}
void view(Scope<Vbox> &s) const
{
Xml_node const depot_users = _depot_users.xml();
bool known_pubkey = false;
s.sub_scope<Frame>([&] (Scope<Vbox, Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Vbox, Frame, Vbox> &s) {
depot_users.for_each_sub_node("user", [&] (Xml_node const &user) {
if (_selected == user.attribute_value("name", User()))
known_pubkey = user.attribute_value("known_pubkey", false);
User const name = user.attribute_value("name", User());
bool const selected = (name == _selected);
Url const url = _url(user);
Url const label = Depot_url::from_string(url).valid() ? url : Url(name);
bool const show_all = _unfolded || !_selected_user_exists;
if (!selected && !show_all)
return;
Hosted<Vbox, Frame, Vbox, Item> item { Id { name } };
s.widget(item, selected, Path(" ", label));
});
if (_unfolded || !_selected_user_exists)
s.widget(_edit_item, (_selected == _add_id()), depot_users);
});
});
if (!_unfolded && !known_pubkey && _selected_user_exists) {
s.sub_scope<Button>([&] (Scope<Vbox, Button> &s) {
s.attribute("style", "invisible");
s.sub_scope<Annotation>("missing public key for verification");
});
}
}
bool unfolded() const { return _unfolded || !_selected_user_exists; }
struct User_properties
{
bool exists;
bool download_url;
bool public_key;
};
User_properties selected_user_properties() const
{
User_properties result { };
_depot_users.xml().for_each_sub_node([&] (Xml_node const &user) {
if (_selected == user.attribute_value("name", User())) {
result = {
.exists = true,
.download_url = Depot_url::from_string(_url(user)).valid(),
.public_key = user.attribute_value("known_pubkey", false)
};
}
});
return result;
}
void _select_depot_user(User const &user)
{
_selected = user;
_unfolded = false;
_selected_user_exists = true;
_edit_item.reset();
}
void _add_and_select_new_depot_user(Action &action)
{
Depot_url const depot_url = _edit_item.depot_url(_depot_users.xml());
if (depot_url.valid()) {
action.add_depot_url(depot_url);
_select_depot_user(depot_url.user);
}
}
void click(Clicked_at const &at, Action &action, auto const &select_fn)
{
/* unfold depot users */
if (!_unfolded) {
_unfolded = true;
return;
}
Id const item = at.matching_id<Vbox, Frame, Vbox, Item>();
if (item.valid()) {
if (item.value == _add_id()) {
_selected = item.value;
} else {
_select_depot_user(item.value);
select_fn(item.value);
}
}
_edit_item.propagate(at, _depot_users.xml(), [&] {
_add_and_select_new_depot_user(action); });
}
bool keyboard_needed() const { return _selected == _add_id(); }
void handle_key(Codepoint c, Action &action)
{
if (_selected == _add_id())
_edit_item.handle_key(c, [&] {
_add_and_select_new_depot_user(action); });
}
bool one_selected() const { return !_unfolded && _selected.length() > 1; }
void sanitize_unfold_state()
{
/*
* If the selected depot user does not exist in the depot, show
* list of available users.
*/
_selected_user_exists = false;
_depot_users.xml().for_each_sub_node([&] (Xml_node const &user) {
if (_selected == user.attribute_value("name", User()))
_selected_user_exists = true; });
}
};
#endif /* _VIEW__DEPOT_USERS_WIDGET_H_ */

View File

@ -1,136 +0,0 @@
/*
* \brief Menu-view dialog handling
* \author Norman Feske
* \date 2018-05-18
*/
/*
* 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 _VIEW__DEPRECATED_DIALOG_H_
#define _VIEW__DEPRECATED_DIALOG_H_
/* Genode includes */
#include <input/event.h>
#include <util/xml_generator.h>
/* local includes */
#include "types.h"
#include <view/hoverable_item.h>
namespace Sculpt { struct Deprecated_dialog; }
struct Sculpt::Deprecated_dialog : Interface
{
bool hovered = false;
enum class Click_result { CONSUMED, IGNORED };
enum class Clack_result { CONSUMED, IGNORED };
using Hover_result = Hoverable_item::Hover_result;
virtual void reset() = 0;
virtual Hover_result hover(Xml_node hover) = 0;
virtual void generate(Xml_generator &xml) const = 0;
static Hover_result any_hover_changed(Hover_result head)
{
return head;
}
template <typename... TAIL>
static Hover_result any_hover_changed(Hover_result head, TAIL... args)
{
if (head == Hover_result::CHANGED)
return Hover_result::CHANGED;
return any_hover_changed(args...);
}
Hover_result _match_sub_dialog(Xml_node node)
{
hovered = true;
return hover(node);
}
template <typename... TAIL>
Hover_result _match_sub_dialog(Xml_node hover, char const *sub_node,
TAIL &&... tail)
{
Hover_result result = Hover_result::UNMODIFIED;
hover.with_optional_sub_node(sub_node, [&] (Xml_node sub_hover) {
if (_match_sub_dialog(sub_hover, tail...) == Hover_result::CHANGED)
result = Hover_result::CHANGED; });
return result;
}
template <typename... ARGS>
Hover_result match_sub_dialog(ARGS &&... args)
{
bool const orig_hovered = hovered;
hovered = false;
Hover_result const result = _match_sub_dialog(args...);
/* reset internal hover state of the dialog if no longer hovered */
if (orig_hovered && !hovered)
(void)this->hover(Xml_node("<empty/>"));
return result;
}
/*
* Helpe for the 'Sculpt::match_sub_dialog' implementation below
*/
template <typename DIALOG>
struct _Recurse
{
DIALOG &dialog;
Hover_result result = Hover_result::UNMODIFIED;
_Recurse(DIALOG &dialog) : dialog(dialog) { }
template <typename... TAIL>
void match(Xml_node const &hover, char const *head, TAIL &&... tail)
{
hover.with_optional_sub_node(head, [&] (Xml_node const &sub_node) {
match(sub_node, tail...); });
}
void match(Xml_node const &hover) { result = dialog.hover(hover); }
};
};
namespace Sculpt {
template <typename DIALOG, typename... ARGS>
static inline Hoverable_item::Hover_result
match_sub_dialog(Xml_node const &hover, DIALOG &dialog, ARGS &&... args)
{
Deprecated_dialog::_Recurse recurse { dialog };
recurse.match(hover, args...);
static Xml_node const unhovered { "<empty/>" };
if (recurse.result == Hoverable_item::Hover_result::UNMODIFIED)
dialog.hover(unhovered);
return recurse.result;
}
}
#endif /* _VIEW__DEPRECATED_DIALOG_H_ */

View File

@ -26,34 +26,47 @@ namespace Dialog {
struct Left_right_annotation;
struct Left_floating_text;
struct Left_floating_hbox;
struct Top_left_floating_hbox;
struct Right_floating_hbox;
struct Vgap;
struct Small_vgap;
struct Button_vgap;
struct Centered_info_vbox;
struct Centered_dialog_vbox;
struct Icon;
struct Titled_frame;
struct Pin_button;
struct Pin_row;
struct Menu_entry;
struct Operation_button;
struct Right_floating_off_on;
struct Doublechecked_action_button;
template <typename> struct Radio_select_button;
template <typename> struct Choice;
}
struct Dialog::Annotation : Sub_scope
{
template <typename SCOPE, typename TEXT>
static void sub_node(SCOPE &s, TEXT const &text)
static void sub_node(auto &scope, auto const &text)
{
s.sub_node("label", [&] {
s.attribute("text", text);
s.attribute("font", "annotation/regular"); });
scope.sub_node("label", [&] {
scope.attribute("text", text);
scope.attribute("font", "annotation/regular"); });
}
static void view_sub_scope(auto &scope, auto const &text)
{
sub_node(scope, text);
}
static void with_narrowed_at(auto const &, auto const &) { }
};
struct Dialog::Left_annotation : Sub_scope
{
template <typename SCOPE, typename TEXT>
static void view_sub_scope(SCOPE &s, TEXT const &text)
static void view_sub_scope(auto &s, auto const &text)
{
s.node("hbox", [&] {
s.sub_node("float", [&] () {
@ -61,15 +74,13 @@ struct Dialog::Left_annotation : Sub_scope
Annotation::sub_node(s, text); }); });
}
template <typename AT, typename FN>
static void with_narrowed_at(AT const &, FN const &) { }
static void with_narrowed_at(auto const &, auto const &) { }
};
struct Dialog::Left_right_annotation : Sub_scope
{
template <typename SCOPE, typename LEFT_TEXT, typename RIGHT_TEXT>
static void view_sub_scope(SCOPE &s, LEFT_TEXT const &left, RIGHT_TEXT const &right)
static void view_sub_scope(auto &s, auto const &left, auto const &right)
{
s.node("hbox", [&] {
s.named_sub_node("float", "left", [&] () {
@ -82,15 +93,13 @@ struct Dialog::Left_right_annotation : Sub_scope
});
}
template <typename AT, typename FN>
static void with_narrowed_at(AT const &, FN const &) { }
static void with_narrowed_at(auto const &, auto const &) { }
};
struct Dialog::Left_floating_text : Sub_scope
{
template <typename SCOPE, typename TEXT>
static void view_sub_scope(SCOPE &s, TEXT const &text)
static void view_sub_scope(auto &s, auto const &text)
{
s.node("float", [&] {
s.attribute("west", "yes");
@ -99,15 +108,13 @@ struct Dialog::Left_floating_text : Sub_scope
s.attribute("min_ex", "15"); }); });
}
template <typename AT, typename FN>
static void with_narrowed_at(AT const &, FN const &) { }
static void with_narrowed_at(auto const &, auto const &) { }
};
struct Dialog::Left_floating_hbox : Sub_scope
{
template <typename SCOPE, typename FN>
static void view_sub_scope(SCOPE &s, FN const &fn)
static void view_sub_scope(auto &s, auto const &fn)
{
s.node("float", [&] {
s.attribute("west", "yes");
@ -115,10 +122,28 @@ struct Dialog::Left_floating_hbox : Sub_scope
fn(s); }); });
}
template <typename AT, typename FN>
static void with_narrowed_at(AT const &at, FN const &fn)
static void with_narrowed_at(auto const &at, auto const &fn)
{
with_narrowed_xml(at, "float", [&] (AT const &at) {
with_narrowed_xml(at, "float", [&] (auto const &at) {
with_narrowed_xml(at, "hbox", fn); });
}
};
struct Dialog::Top_left_floating_hbox : Sub_scope
{
static void view_sub_scope(auto &s, auto const &fn)
{
s.node("float", [&] {
s.attribute("west", "yes");
s.attribute("north", "yes");
s.named_sub_node("hbox", s.id.value, [&] {
fn(s); }); });
}
static void with_narrowed_at(auto const &at, auto const &fn)
{
with_narrowed_xml(at, "float", [&] (auto const &at) {
with_narrowed_xml(at, "hbox", fn); });
}
};
@ -126,8 +151,7 @@ struct Dialog::Left_floating_hbox : Sub_scope
struct Dialog::Right_floating_hbox : Sub_scope
{
template <typename SCOPE, typename FN>
static void view_sub_scope(SCOPE &s, FN const &fn)
static void view_sub_scope(auto &s, auto const &fn)
{
s.node("float", [&] {
s.attribute("east", "yes");
@ -135,10 +159,9 @@ struct Dialog::Right_floating_hbox : Sub_scope
fn(s); }); });
}
template <typename AT, typename FN>
static void with_narrowed_at(AT const &at, FN const &fn)
static void with_narrowed_at(auto const &at, auto const &fn)
{
with_narrowed_xml(at, "float", [&] (AT const &at) {
with_narrowed_xml(at, "float", [&] (auto const &at) {
with_narrowed_xml(at, "hbox", fn); });
}
};
@ -146,21 +169,45 @@ struct Dialog::Right_floating_hbox : Sub_scope
struct Dialog::Vgap : Sub_scope
{
template <typename SCOPE>
static void view_sub_scope(SCOPE &s)
static void view_sub_scope(auto &s)
{
s.node("label", [&] { s.attribute("text", " "); });
}
template <typename AT, typename FN>
static void with_narrowed_at(AT const &, FN const &) { }
static void with_narrowed_at(auto const &, auto const &) { }
};
struct Dialog::Small_vgap : Sub_scope
{
static void view_sub_scope(auto &s)
{
s.node("label", [&] {
s.attribute("text", "");
s.attribute("font", "annotation/regular"); });
}
static void with_narrowed_at(auto const &, auto const &) { }
};
struct Dialog::Button_vgap : Sub_scope
{
static void view_sub_scope(auto &s)
{
/* inflate vertical space to button size */
s.node("button", [&] {
s.attribute("style", "invisible");
s.sub_node("label", [&] { s.attribute("text", ""); }); });
}
static void with_narrowed_at(auto const &, auto const &) { }
};
struct Dialog::Centered_info_vbox : Sub_scope
{
template <typename SCOPE, typename FN>
static void view_sub_scope(SCOPE &s, FN const &fn)
static void view_sub_scope(auto &s, auto const &fn)
{
s.node("float", [&] {
s.sub_node("frame", [&] {
@ -169,15 +216,13 @@ struct Dialog::Centered_info_vbox : Sub_scope
fn(s); }); }); });
}
template <typename AT, typename FN>
static void with_narrowed_at(AT const &, FN const &) { }
static void with_narrowed_at(auto const &, auto const &) { }
};
struct Dialog::Centered_dialog_vbox : Sub_scope
{
template <typename SCOPE, typename FN>
static void view_sub_scope(SCOPE &s, FN const &fn)
static void view_sub_scope(auto &s, auto const &fn)
{
s.node("float", [&] {
s.sub_node("frame", [&] {
@ -186,22 +231,50 @@ struct Dialog::Centered_dialog_vbox : Sub_scope
fn(s); }); }); });
}
template <typename AT, typename FN>
static void with_narrowed_at(AT const &at, FN const &fn)
static void with_narrowed_at(auto const &at, auto const &fn)
{
with_narrowed_xml(at, "float", [&] (AT const &at) {
with_narrowed_xml(at, "frame", [&] (AT const &at) {
with_narrowed_xml(at, "float", [&] (auto const &at) {
with_narrowed_xml(at, "frame", [&] (auto const &at) {
with_narrowed_xml(at, "vbox", fn); }); });
}
};
struct Dialog::Icon : Sub_scope
{
struct Attr { bool hovered, selected; };
/* used whenever the icon's hover sensitivity is larger than the icon */
static void view_sub_scope(auto &s, auto const &style, Attr attr)
{
s.node("float", [&] {
s.sub_node("button", [&] {
s.attribute("style", style);
if (attr.selected) s.attribute("selected", "yes");
if (attr.hovered) s.attribute("hovered", "yes");
s.sub_node("hbox", [&] { }); }); });
}
/* used when hovering responds only to the icon's boundaries */
static void view_sub_scope(auto &s, auto const &style, bool selected)
{
view_sub_scope(s, style, Attr { .hovered = s.hovered(),
.selected = selected });
}
static void with_narrowed_at(auto const &at, auto const &fn)
{
with_narrowed_xml(at, "float", [&] (auto const &at) {
with_narrowed_xml(at, "button", fn); });
}
};
struct Dialog::Titled_frame : Widget<Frame>
{
struct Attr { unsigned min_ex; };
template <typename FN>
static void view(Scope<Frame> &s, Attr const attr, FN const &fn)
static void view(Scope<Frame> &s, Attr const attr, auto const &fn)
{
s.sub_node("vbox", [&] {
if (attr.min_ex)
@ -213,8 +286,7 @@ struct Dialog::Titled_frame : Widget<Frame>
fn(); }); }); });
}
template <typename FN>
static void view(Scope<Frame> &s, FN const &fn)
static void view(Scope<Frame> &s, auto const &fn)
{
view(s, Attr { }, fn);
}
@ -228,31 +300,23 @@ struct Dialog::Radio_select_button : Widget<Left_floating_hbox>
Radio_select_button(ENUM value) : value(value) { }
template <typename TEXT>
void view(Scope<Left_floating_hbox> &s, ENUM const &selected_value, TEXT const &text) const
void view(Scope<Left_floating_hbox> &s, ENUM const &selected_value, auto const &text) const
{
bool const selected = (selected_value == value),
hovered = (s.hovered() && !s.dragged() && !selected);
s.sub_scope<Float>([&] (Scope<Left_floating_hbox, Float> &s) {
s.sub_scope<Button>([&] (Scope<Left_floating_hbox, Float, Button> &s) {
s.attribute("style", "radio");
if (selected) s.attribute("selected", "yes");
if (hovered) s.attribute("hovered", "yes");
s.sub_scope<Hbox>();
});
});
/* inflate vertical space to button size */
s.sub_scope<Button>([&] (Scope<Left_floating_hbox, Button> &s) {
s.attribute("style", "invisible");
s.sub_scope<Label>(text); });
s.sub_scope<Icon>("radio", Icon::Attr { .hovered = hovered,
.selected = selected });
s.sub_scope<Label>(String<100>(" ", text));
s.sub_scope<Button_vgap>();
}
template <typename FN>
void click(Clicked_at const &, FN const &fn) { fn(value); }
void view(Scope<Left_floating_hbox> &s, ENUM const &selected_value) const
{
view(s, selected_value, s.id.value);
}
void click(Clicked_at const &, auto const &fn) const { fn(value); }
};
@ -275,7 +339,7 @@ struct Dialog::Pin_button : Action_button
s.sub_scope<Vbox>([&] (Scope<Button, Vbox> &s) {
s.sub_scope<Min_ex>(10);
s.sub_scope<Vgap>();
s.sub_scope<Dialog::Label>(text, [&] (auto &s) {
s.sub_scope<Label>(text, [&] (auto &s) {
if (!attr.visible)
s.attribute("style", "invisible");
s.attribute("font", "title/regular"); });
@ -289,8 +353,7 @@ struct Dialog::Pin_row : Widget<Hbox>
{
Hosted<Hbox, Pin_button> _buttons[3];
template <typename S1, typename S2, typename S3>
Pin_row(S1 const &left, S2 const &middle, S3 const &right)
Pin_row(auto const &left, auto const &middle, auto const &right)
:
_buttons { Id { left }, Id { middle }, Id { right } }
{ }
@ -304,8 +367,7 @@ struct Dialog::Pin_row : Widget<Hbox>
s.widget(_buttons[2], Pin_button::Attr { visible.right });
}
template <typename FN>
void click(Clicked_at const &at, FN const &fn)
void click(Clicked_at const &at, auto const &fn)
{
for (auto &button : _buttons)
button.propagate(at, [&] { fn(button.id.value); });
@ -313,6 +375,83 @@ struct Dialog::Pin_row : Widget<Hbox>
};
struct Dialog::Menu_entry : Widget<Left_floating_hbox>
{
void view(Scope<Left_floating_hbox> &s, bool selected, auto const &text,
char const * const style = "radio") const
{
bool const hovered = (s.hovered() && !s.dragged());
s.sub_scope<Icon>(style, Icon::Attr { .hovered = hovered,
.selected = selected });
s.sub_scope<Label>(String<100>(" ", text));
s.sub_scope<Button_vgap>();
}
void click(Clicked_at const &, auto const &fn) const { fn(); }
};
struct Dialog::Operation_button : Widget<Button>
{
void view(Scope<Button> &s, bool selected, auto const &text) const
{
if (selected) {
s.attribute("selected", "yes");
s.attribute("style", "unimportant");
}
if (s.hovered() && !s.dragged() && !selected)
s.attribute("hovered", "yes");
s.sub_scope<Label>(String<50>(" ", text, " "));
}
void view(Scope<Button> &s, bool selected) const
{
view(s, selected, s.id.value);
}
template <typename FN>
void click(Clicked_at const &, FN const &fn) const { fn(); }
};
struct Dialog::Right_floating_off_on : Widget<Right_floating_hbox>
{
struct Attr { bool on, transient; };
Hosted<Right_floating_hbox, Select_button<bool>> const
_off { Id { " Off " }, false },
_on { Id { " On " }, true };
void view(Scope<Right_floating_hbox> &s, Attr attr) const
{
auto transient_attr_fn = [&] (Scope<Button> &s)
{
if (attr.transient)
s.attribute("style", "unimportant");
s.sub_scope<Label>(s.id.value);
};
s.widget(_off, attr.on, transient_attr_fn);
s.widget(_on, attr.on, transient_attr_fn);
}
void view(Scope<Right_floating_hbox> &s, bool on) const
{
view(s, Attr { .on = on, .transient = false });
}
void click(Clicked_at const &at, auto const &fn) const
{
_off.propagate(at, [&] (bool) { fn(false); });
_on .propagate(at, [&] (bool) { fn(true); });
}
};
struct Dialog::Doublechecked_action_button
{
bool selected = false;
@ -329,16 +468,15 @@ struct Dialog::Doublechecked_action_button
void reset() { selected = false, confirmed = false; }
template <typename TEXT>
void view(Scope<Vbox> &s, TEXT const &text) const
void view(Scope<Vbox> &s, auto const &text) const
{
s.widget(_operation, selected, [&] (Scope<Button> &s) {
s.sub_scope<Dialog::Label>(text); });
s.sub_scope<Label>(text); });
if (selected)
s.widget(_confirm_or_cancel, [&] (auto &s) {
s.template sub_scope<Dialog::Label>(confirmed ? " Cancel "
: " Confirm "); });
s.template sub_scope<Label>(confirmed ? " Cancel "
: " Confirm "); });
}
void click(Clicked_at const &at)
@ -350,27 +488,84 @@ struct Dialog::Doublechecked_action_button
_confirm_or_cancel.propagate(at);
}
template <typename FN>
void clack(Clacked_at const &at, FN const &activate_fn)
void clack(Clacked_at const &at, auto const &activate_fn)
{
_confirm_or_cancel.propagate(at, activate_fn);
}
};
/* local includes */
#include <view/deprecated_dialog.h>
template <typename ENUM>
struct Dialog::Choice : Widget<Hbox>
{
ENUM const _unfold_value;
namespace Dialog {
Choice(ENUM const unfold_value) : _unfold_value(unfold_value) { }
template <typename FN>
static inline void with_dummy_scope(Xml_generator &xml, FN const &fn)
struct Attr
{
static Xml_node const hover("<hover/>");
At const no_hover(Dialog::Event::Seq_number { }, hover);
Scope<> scope(xml, no_hover, Dialog::Event::Dragged { }, Id { });
fn(scope);
unsigned left_ex, right_ex;
ENUM unfolded;
Id selected_item;
};
struct Sub_scope
{
Scope<> &_scope;
bool const _unfolded;
Id const _selected_item;
template <typename HOSTED, typename... ARGS>
void widget(HOSTED const &hosted, ARGS &&... args)
{
if (_unfolded || (hosted.id == _selected_item))
hosted._view_hosted(_scope, args...);
}
};
void view(Scope<Hbox> &s, Attr attr, auto const &fn) const
{
Id::Value const text = s.id.value;
bool const unfolded = (attr.unfolded == _unfold_value);
s.sub_scope<Vbox>([&] (Scope<Hbox, Vbox> &s) {
s.sub_scope<Min_ex>(attr.left_ex);
s.sub_scope<Float>([&] (Scope<Hbox, Vbox, Float> &s) {
s.attribute("north", true);
s.attribute("west", true);
s.sub_scope<Frame>([&] (Scope<Hbox, Vbox, Float, Frame> &s) {
s.attribute("style", "invisible");
s.sub_scope<Hbox>([&] (Scope<Hbox, Vbox, Float, Frame, Hbox> &s) {
s.sub_scope<Label>(text);
s.sub_scope<Button_vgap>(); }); }); }); });
s.sub_scope<Frame>([&] (Scope<Hbox, Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Hbox, Frame, Vbox> &s) {
s.sub_scope<Min_ex>(attr.right_ex);
s.as_new_scope([&] (Scope<> &s) {
Sub_scope sub_scope { s, unfolded, attr.selected_item };
fn(sub_scope); }); }); });
}
}
void click(Clicked_at const &at, ENUM &unfolded,
auto const &fold_all_fn, auto const &item_fn) const
{
if (unfolded != _unfold_value) {
unfolded = _unfold_value;
return;
}
bool clicked_at_item = false;
Hbox::with_narrowed_at(at, [&] (Clicked_at const &at) {
Frame::with_narrowed_at(at, [&] (Clicked_at const &at) {
Vbox::with_narrowed_at(at, [&] (Clicked_at const &at) {
clicked_at_item = true;
item_fn(at); }); }); });
if (!clicked_at_item)
fold_all_fn();
}
};
#endif /* _VIEW__DIALOG_H_ */

View File

@ -1,5 +1,5 @@
/*
* \brief Generate download-status view
* \brief Generate download-status widget
* \author Norman Feske
* \date 2018-05-18
*/
@ -11,17 +11,17 @@
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _VIEW__DOWNLOAD_STATUS_DIALOG_H_
#define _VIEW__DOWNLOAD_STATUS_DIALOG_H_
#ifndef _VIEW__DOWNLOAD_STATUS_WIDGET_H_
#define _VIEW__DOWNLOAD_STATUS_WIDGET_H_
/* local includes */
#include <model/download_queue.h>
#include <view/dialog.h>
namespace Sculpt { struct Download_status_dialog; }
namespace Sculpt { struct Download_status_widget; }
struct Sculpt::Download_status_dialog : Titled_frame
struct Sculpt::Download_status_widget : Titled_frame
{
void view(Scope<Frame> &s, Xml_node const &state, Download_queue const &download_queue) const
{
@ -62,4 +62,4 @@ struct Sculpt::Download_status_dialog : Titled_frame
}
};
#endif /* _VIEW__DOWNLOAD_STATUS_DIALOG_H_ */
#endif /* _VIEW__DOWNLOAD_STATUS_WIDGET_H_ */

View File

@ -5,7 +5,7 @@
*/
/*
* Copyright (C) 2020 Genode Labs GmbH
* Copyright (C) 2020-2023 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.
@ -21,49 +21,35 @@
namespace Sculpt { struct File_browser_dialog; }
struct Sculpt::File_browser_dialog : Noncopyable, Deprecated_dialog
struct Sculpt::File_browser_dialog : Top_level_dialog
{
Runtime_config const &_runtime_config;
File_browser_state const &_state;
Hoverable_item _fs_button { };
Hoverable_item _entry { };
Hoverable_item _path_elem { };
Hoverable_item _action_button { };
void reset() override { }
using Fs_name = File_browser_state::Fs_name;
using Index = File_browser_state::Index;
using Sub_dir = File_browser_state::Path;
using Path = File_browser_state::Path;
using File = File_browser_state::Path;
using Name = String<128>;
struct Action : Interface, Noncopyable
{
virtual void browse_file_system(Fs_name const &) = 0;
virtual void browse_sub_directory(Sub_dir const &) = 0;
virtual void browse_abs_directory(Path const &) = 0;
virtual void browse_parent_directory() = 0;
virtual void view_file(File const &) = 0;
virtual void edit_file(File const &) = 0;
virtual void revert_edited_file() = 0;
virtual void save_edited_file() = 0;
};
using Name = String<128>;
Action &_action;
template <typename FN>
void _for_each_path_elem(FN const &fn) const
static void _for_each_path_elem(Path const path, FN const &fn)
{
char const *curr = _state.path.string();
char const *curr = path.string();
fn(Path("/"));
@ -84,213 +70,246 @@ struct Sculpt::File_browser_dialog : Noncopyable, Deprecated_dialog
}
}
Path _path_elem_at_index(unsigned index) const
static Path _path_elem_at_index(Path const path, unsigned index)
{
Path result { };
unsigned i = 0;
_for_each_path_elem([&] (Path const &elem) {
_for_each_path_elem(path, [&] (Path const &elem) {
if (i++ == index)
result = elem; });
return result;
}
void _gen_path_elements(Xml_generator &xml) const
static void _with_matching_entry(At const &at, auto const fn)
{
gen_named_node(xml, "hbox", "back", [&] () {
Id const entry_id = at.matching_id<Vbox, Frame, Vbox, Entry>();
unsigned index = ~0U;
if (ascii_to(entry_id.value.string(), index)) {
Hosted_entry entry { entry_id, index };
fn(entry);
}
}
struct Navigation_entry : Widget<Left_floating_hbox>
{
struct Back : Widget<Float>
{
void view(Scope<Float> &s) const
{
s.sub_scope<Button>([&] (Scope<Float, Button> &s) {
if (s.hovered()) s.attribute("hovered", "yes");
s.attribute("style", "back");
s.sub_scope<Hbox>();
});
}
void click(Clicked_at const &, auto const &fn) { fn(); }
};
Hosted<Left_floating_hbox, Back> _back { Id { "back" } };
void view(Scope<Left_floating_hbox> &s, Path const &path, bool allow_back) const
{
if (allow_back)
s.widget(_back);
/* don't hover last path element */
unsigned count = 0;
_for_each_path_elem(path, [&] (Path const &) { count++; });
unsigned i = 0;
_for_each_path_elem([&] (Path const elem) {
_for_each_path_elem(path, [&] (Path const elem) {
Id const id { { i++ } };
bool const last = (i == count);
s.sub_scope<Button>(id, [&] (Scope<Left_floating_hbox, Button> &s) {
gen_named_node(xml, "button", Index(i), [&] () {
if (!_state.modified)
_path_elem.gen_hovered_attr(xml, Index(i));
xml.node("label", [&] () {
xml.attribute("text", elem);
});
if (allow_back && s.hovered() && !last)
s.attribute("hovered", "yes");
if (last)
s.attribute("style", "unimportant");
s.sub_scope<Label>(elem);
});
i++;
});
});
}
}
void _gen_back_entry(Xml_generator &xml) const
void click(Clicked_at const &at, Path const &path, Action &action)
{
Id const elem_id = at.matching_id<Left_floating_hbox, Button>();
if (elem_id.valid()) {
Genode::Path<256> abs_path { "/" };
for (unsigned i = 0; i < 20; i++) {
abs_path.append_element(_path_elem_at_index(path, i).string());
if (Id { { i } } == elem_id)
break;
}
action.browse_abs_directory(Path(abs_path));
}
_back.propagate(at, [&] { action.browse_parent_directory(); });
}
};
struct Conditional_button : Widget<Button>
{
gen_named_node(xml, "hbox", "back", [&] () {
void view(Scope<Button> &s, bool condition, bool selected) const
{
if (s.hovered()) s.attribute("hovered", "yes");
if (selected) s.attribute("selected", "yes");
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
if (!condition)
s.attribute("style", "invisible");
xml.node("hbox", [&] () {
if (!_state.modified) {
gen_named_node(xml, "float", "button", [&] () {
gen_named_node(xml, "button", "button", [&] () {
xml.attribute("style", "back");
_entry.gen_hovered_attr(xml, "back");
xml.node("hbox", [&] () { });
});
});
}
s.sub_scope<Label>(s.id.value, [&] (auto &s) {
if (!condition) s.attribute("style", "invisible"); });
}
_gen_path_elements(xml);
});
});
void click(Clicked_at const &, auto const &fn) { fn(); }
};
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
void _gen_entry(Xml_generator &xml, Xml_node node, Index const &index,
char const *style) const
/*
* Use button instances accross entries to keep their internal state
* independent from the lifetime of 'Entry' objects, which exist only
* temporarily.
*/
struct Entry_buttons
{
Name const name = node.attribute_value("name", Name());
/* scope hierarchy relative to 'Entry' */
Hosted<Hbox, Float, Hbox, Conditional_button>
edit { Id { "Edit" } },
view { Id { "View" } };
bool const entry_edited = (name == _state.edited_file);
Hosted<Hbox, Float, Hbox, Deferred_action_button>
revert { Id { "Revert" } },
save { Id { "Save" } };
/* while editing one file, hide all others */
if (!entry_edited && _state.modified)
return;
} _entry_buttons { };
gen_named_node(xml, "hbox", index, [&] () {
struct Entry : Widget<Hbox>
{
unsigned const index;
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
Entry(unsigned index) : index(index) { }
xml.node("hbox", [&] () {
gen_named_node(xml, "button", "button", [&] () {
void view(Scope<Hbox> &s, File_browser_state const &state,
Xml_node const &node, auto const &style,
Entry_buttons const &buttons) const
{
Name const name = node.attribute_value("name", Name());
bool const hovered = (s.hovered() && !state.modified);
bool const selected = (name == state.edited_file);
bool const selected = (name == _state.edited_file);
if (selected)
xml.attribute("selected", "yes");
/* while editing one file, hide all others */
if (!selected && state.modified)
return;
xml.attribute("style", style);
s.sub_scope<Float>([&] (Scope<Hbox, Float> &s) {
s.attribute("west", "yes");
s.sub_scope<Hbox>([&] (Scope<Hbox, Float, Hbox> &s) {
s.sub_scope<Icon>(style, Icon::Attr { .hovered = hovered,
.selected = selected });
s.sub_scope<Label>(Path(" ", name)); }); });
if (!_state.modified)
_entry.gen_hovered_attr(xml, index);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", Path(" ", name)); });
});
});
gen_named_node(xml, "hbox", "middle", [&] () { });
gen_named_node(xml, "float", "right", [&] () {
xml.attribute("east", "yes");
s.sub_scope<Float>([&] (Scope<Hbox, Float> &s) {
s.attribute("east", "yes");
/* show no operation buttons for directories */
if (node.type() == "dir")
return;
gen_named_node(xml, "hbox", "right", [&] () {
s.sub_scope<Hbox>([&] (Scope<Hbox, Float, Hbox> &s) {
bool const entry_hovered = _entry.hovered(index);
bool const interesting = entry_hovered || entry_edited;
bool const writeable = node.attribute_value("writeable", false);
bool const interesting = hovered || selected;
bool const writeable = node.attribute_value("writeable", false);
if (writeable) {
if (!state.modified)
s.widget(buttons.edit, interesting, selected);
if (!_state.modified) {
gen_named_node(xml, "button", "edit", [&] () {
if (interesting) {
_action_button.gen_hovered_attr(xml, "edit");
if (entry_edited)
xml.attribute("selected", "yes");
} else {
xml.attribute("style", "invisible");
}
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", "Edit");
if (!interesting)
xml.attribute("style", "invisible");
});
});
if (selected && state.modified) {
s.widget(buttons.revert);
s.widget(buttons.save);
}
if (entry_edited && _state.modified) {
auto gen_action_button = [&] (auto id, auto text)
{
gen_named_node(xml, "button", id, [&] () {
_action_button.gen_hovered_attr(xml, id);
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", text); }); });
};
gen_action_button("revert", "Revert");
gen_action_button("save", "Save");
}
} else {
gen_named_node(xml, "button", "view", [&] () {
if (interesting) {
_action_button.gen_hovered_attr(xml, "view");
if (entry_edited)
xml.attribute("selected", "yes");
} else {
xml.attribute("style", "invisible");
}
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", "View");
if (!interesting)
xml.attribute("style", "invisible");
});
});
s.widget(buttons.view, interesting, selected);
}
});
});
});
}
}
void _gen_file_system(Xml_generator &xml, Service const &service) const
void click(Clicked_at const &at, Xml_node const &node,
Entry_buttons &buttons, Action &action)
{
Name const name = node.attribute_value("name", Name());
if (node.has_type("dir")) {
action.browse_sub_directory(name);
return;
}
buttons.edit .propagate(at, [&] { action.edit_file(name); });
buttons.view .propagate(at, [&] { action.view_file(name); });
buttons.revert.propagate(at);
buttons.save .propagate(at);
}
void clack(Clacked_at const &at, Entry_buttons &buttons, Action &action)
{
buttons.revert.propagate(at, [&] { action.revert_edited_file(); });
buttons.save .propagate(at, [&] { action.save_edited_file(); });
}
};
Hosted<Vbox, Frame, Vbox, Navigation_entry> _nav_entry { Id { "nav" } };
using Hosted_entry = Hosted<Vbox, Frame, Vbox, Entry>;
void _view_file_system(Scope<Vbox> &s, Service const &service) const
{
bool const parent = !service.server.valid();
Label const name = parent ? Start_name(service.label) : service.server;
bool const parent = !service.server.valid();
Start_name const name = parent ? Start_name(service.label) : service.server;
Start_name const pretty_name { Pretty(name) };
bool const selected = (_state.browsed_fs == name);
bool const selected = (_state.browsed_fs == name);
if (_state.text_area.constructed() && _state.modified && !selected)
return;
gen_named_node(xml, "frame", name, [&] () {
xml.node("vbox", [&] () {
xml.node("label", [&] () {
xml.attribute("min_ex", "50"); });
s.sub_scope<Frame>([&] (Scope<Vbox, Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Vbox, Frame, Vbox> &s) {
s.sub_scope<Min_ex>(50);
s.sub_scope<Button>(Id { name }, [&] (Scope<Vbox, Frame, Vbox, Button> &s) {
gen_named_node(xml, "button", name, [&] () {
if (!_state.modified)
_fs_button.gen_hovered_attr(xml, name);
if (!_state.modified && s.hovered())
s.attribute("hovered", "yes");
if (selected)
xml.attribute("selected", true);
s.attribute("selected", true);
xml.node("label", [&] () {
xml.attribute("text", pretty_name);
xml.attribute("style", "title");
});
s.sub_scope<Label>(pretty_name, [&] (auto &s) {
s.attribute("style", "title"); });
});
if (selected) {
unsigned cnt = 0;
unsigned count = 0;
_state.with_query_result([&] (Xml_node node) {
node.with_optional_sub_node("dir", [&] (Xml_node listing) {
_state.with_query_result([&] (Xml_node const &node) {
node.with_optional_sub_node("dir", [&] (Xml_node const &listing) {
if (_state.path != "/")
_gen_back_entry(xml);
s.widget(_nav_entry, _state.path, !_state.modified);
listing.for_each_sub_node("dir", [&] (Xml_node dir) {
_gen_entry(xml, dir, Index(cnt++), "enter"); });
listing.for_each_sub_node("dir", [&] (Xml_node const &dir) {
unsigned const index = count++;
Hosted_entry entry { Id { { index } }, index };
s.widget(entry, _state, dir, "enter", _entry_buttons); });
listing.for_each_sub_node("file", [&] (Xml_node file) {
_gen_entry(xml, file, Index(cnt++), "radio"); });
listing.for_each_sub_node("file", [&] (Xml_node const &file) {
unsigned const index = count++;
Hosted_entry entry { Id { { index } }, index };
s.widget(entry, _state, file, "radio", _entry_buttons); });
});
});
}
@ -298,86 +317,46 @@ struct Sculpt::File_browser_dialog : Noncopyable, Deprecated_dialog
});
}
void generate(Xml_generator &xml) const override
void view(Scope<> &s) const override
{
xml.node("vbox", [&] () {
s.sub_scope<Vbox>([&] (Scope<Vbox> &s) {
_runtime_config.for_each_service([&] (Service const &service) {
if (service.type == Service::Type::FILE_SYSTEM)
_gen_file_system(xml, service); }); });
}
Hover_result hover(Xml_node hover) override
{
return any_hover_changed(
_entry.match(hover, "vbox", "frame", "vbox", "hbox", "name"),
_path_elem.match(hover, "vbox", "frame", "vbox", "hbox", "float", "hbox", "hbox", "button", "name"),
_fs_button.match(hover, "vbox", "frame", "vbox", "button", "name"),
_action_button.match(hover, "vbox", "frame", "vbox", "hbox", "float", "hbox", "button", "name"));
}
Click_result click(Action &action)
{
Click_result result = Click_result::IGNORED;
Fs_name const fs_name = _fs_button._hovered;
if (fs_name.length() > 1) {
if (!_state.modified)
action.browse_file_system(fs_name);
return Click_result::CONSUMED;
}
Index const path_elem_idx = _path_elem._hovered;
if (path_elem_idx.length() > 1) {
Genode::Path<256> abs_path { "/" };
for (unsigned i = 0; i < 20; i++) {
abs_path.append_element(_path_elem_at_index(i).string());
if (Index(i) == path_elem_idx)
break;
}
if (!_state.modified)
action.browse_abs_directory(Path(abs_path));
return Click_result::CONSUMED;
}
Index const index = _entry._hovered;
if (index == "back") {
if (!_state.modified)
action.browse_parent_directory();
}
else if (index.length() > 1) {
_state.with_entry_at_index(index, [&] (Xml_node entry) {
if (entry.has_type("dir"))
action.browse_sub_directory(entry.attribute_value("name", Sub_dir()));
if (entry.has_type("file")) {
if (_action_button.hovered("edit"))
action.edit_file(entry.attribute_value("name", Name()));
if (_action_button.hovered("view"))
action.view_file(entry.attribute_value("name", Name()));
if (_action_button.hovered("revert"))
action.revert_edited_file();
if (_action_button.hovered("save"))
action.save_edited_file();
}
});
return Click_result::CONSUMED;
}
return result;
_view_file_system(s, service); }); });
}
File_browser_dialog(Runtime_config const &runtime_config,
File_browser_state const &state)
File_browser_state const &state,
Action &action)
:
_runtime_config(runtime_config), _state(state)
Top_level_dialog("file_browser"),
_runtime_config(runtime_config), _state(state), _action(action)
{ }
void click(Clicked_at const &at) override
{
Id const fs_id = at.matching_id<Vbox, Frame, Vbox, Button>();
if (fs_id.valid()) {
if (!_state.modified)
_action.browse_file_system(fs_id.value);
return;
}
if (!_state.modified)
_nav_entry.propagate(at, _state.path, _action);
_with_matching_entry(at, [&] (Hosted_entry &entry) {
_state.with_entry_at_index(entry.index, [&] (Xml_node const &node) {
entry.propagate(at, node, _entry_buttons, _action); }); });
}
void clack(Clacked_at const &at) override
{
_with_matching_entry(at, [&] (Hosted_entry &entry) {
entry.propagate(at, _entry_buttons, _action); });
}
void drag(Dragged_at const &) override { }
};
#endif /* _VIEW__FILE_BROWSER_H_ */

View File

@ -1,69 +0,0 @@
/*
* \brief GUI element that shows hovering feedback
* \author Norman Feske
* \date 2018-05-03
*/
/*
* 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 _VIEW__HOVERABLE_ITEM_H_
#define _VIEW__HOVERABLE_ITEM_H_
#include <feature.h>
#include "types.h"
#include "xml.h"
namespace Sculpt { struct Hoverable_item; }
struct Sculpt::Hoverable_item
{
typedef String<64> Id;
Id _hovered { };
enum class Hover_result { CHANGED, UNMODIFIED };
/**
* Set ID to matching hover sub node name
*
* \return true if hovering changed
*/
template <typename... ARGS>
Hover_result match(Xml_node hover, ARGS &&... args)
{
Id const orig = _hovered;
_hovered = query_attribute<Id>(hover, args...);
return (_hovered != orig) ? Hover_result::CHANGED : Hover_result::UNMODIFIED;
}
/**
* Return true if item is currently hovered
*/
bool hovered(Id const &id) const { return id == _hovered; }
/**
* Generate hovered attribute depending on the item state
*/
void gen_hovered_attr(Xml_generator &xml, Id const &id) const
{
if (Feature::VISUAL_HOVER && hovered(id))
xml.attribute("hovered", "yes");
}
/**
* Generate button attributes depending on the item state
*/
void gen_button_attr(Xml_generator &xml, Id const &id) const
{
xml.attribute("name", id);
gen_hovered_attr(xml, id);
}
};
#endif /* _VIEW__HOVERABLE_ITEM_H_ */

View File

@ -1,38 +0,0 @@
/*
* \brief GUI layout helper
* \author Norman Feske
* \date 2022-05-20
*/
/*
* Copyright (C) 2022 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 _VIEW__LAYOUT_HELPER_H_
#define _VIEW__LAYOUT_HELPER_H_
#include <view/dialog.h>
/**
* Inflate vertical spacing using an invisble button
*/
template <typename ID>
static void gen_item_vspace(Genode::Xml_generator &xml, ID const &id)
{
using namespace Sculpt;
gen_named_node(xml, "button", id, [&] () {
xml.attribute("style", "invisible");
xml.node("label", [&] () {
xml.attribute("text", " ");
xml.attribute("font", "title/regular");
});
});
}
#endif /* _VIEW__LAYOUT_HELPER_H_ */

View File

@ -1,317 +0,0 @@
/*
* \brief Network management dialog
* \author Norman Feske
* \date 2018-05-07
*/
/*
* 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.
*/
/* Genode includes */
#include <base/log.h>
/* local includes */
#include "network_dialog.h"
using namespace Sculpt;
void Network_dialog::_gen_access_point(Xml_generator &xml,
Access_point const &ap) const
{
gen_named_node(xml, "hbox", ap.bssid, [&] {
gen_named_node(xml, "float", "left", [&] {
xml.attribute("west", "yes");
xml.node("hbox", [&] {
gen_named_node(xml, "float", "left", [&] {
gen_named_node(xml, "button", "button", [&] {
xml.attribute("style", "radio");
if (_wifi_connection.connected())
xml.attribute("selected", "yes");
else
_ap_item.gen_button_attr(xml, ap.bssid);
xml.node("hbox", [&] { });
});
});
gen_named_node(xml, "label", "ssid", [&] {
xml.attribute("text", String<20>(" ", ap.ssid)); });
gen_named_node(xml, "label", "protection", [&] {
xml.attribute("font", "annotation/regular");
if (ap.protection == Access_point::WPA_PSK)
xml.attribute("text", " (WPA) ");
else
xml.attribute("text", " ");
});
});
});
gen_named_node(xml, "float", "right", [&] {
xml.attribute("east", "yes");
xml.node("label", [&] {
xml.attribute("text", String<8>(ap.quality, "%")); });
});
});
}
bool Network_dialog::_selected_ap_visible() const
{
unsigned cnt = 0;
return _for_each_ap([&] (Access_point const &ap) {
return (cnt++ < _max_visible_aps) && _ap_item.selected(ap.bssid); });
}
bool Network_dialog::_selected_ap_unprotected() const
{
return _for_each_ap([&] (Access_point const &ap) {
return _ap_item.selected(ap.bssid) && ap.unprotected(); });
}
bool Network_dialog::need_keyboard_focus_for_passphrase() const
{
if ( _wifi_connection.state == Wifi_connection::CONNECTED
|| _wifi_connection.state == Wifi_connection::CONNECTING)
return false;
if (!_nic_target.wifi())
return false;
return _for_each_ap([&] (Access_point const &ap) {
return _ap_item.selected(ap.bssid) && ap.wpa_protected(); });
}
void Network_dialog::_gen_access_point_list(Xml_generator &xml,
bool auth_failure) const
{
if (_wlan_config_policy == WLAN_CONFIG_MANUAL)
return;
bool const selected_ap_visible = _selected_ap_visible();
unsigned cnt = 0;
_access_points.for_each([&] (Access_point const &ap) {
if (cnt++ >= _max_visible_aps)
return;
/*
* Whenever the user has selected an access point, hide all others.
* Should the selected AP disappear from the list, show all others.
*/
bool const selected = _ap_item.selected(ap.bssid);
if (selected_ap_visible && !selected)
return;
_gen_access_point(xml, ap);
if (!selected)
return;
bool const connected_to_selected_ap =
(selected && _wifi_connection.bssid == ap.bssid)
&& _wifi_connection.state == Wifi_connection::CONNECTED;
if (connected_to_selected_ap)
return;
if (ap.protection == Access_point::WPA_PSK) {
gen_named_node(xml, "label", "passphrase msg", [&] () {
xml.attribute("text", auth_failure ? "Enter passphrase (auth failure):"
: "Enter passphrase:");
});
gen_named_node(xml, "frame", "passphrase", [&] () {
xml.node("float", [&] () {
xml.attribute("west", "yes");
xml.node("label", [&] () {
xml.attribute("font", "title/regular");
String<3*64> const passphrase(" ", _wpa_passphrase);
xml.attribute("text", passphrase);
xml.node("cursor", [&] () {
xml.attribute("at", passphrase.length() - 1);
});
});
});
});
if (_wpa_passphrase.suitable_for_connect()) {
xml.node("button", [&] () {
if (_wifi_connection.state == Wifi_connection::CONNECTING)
xml.attribute("selected", "yes");
/* suppress hover while connecting */
else
_connect_item.gen_button_attr(xml, "connect");
xml.node("label", [&] () {
xml.attribute("text", "Connect"); });
});
}
}
});
/*
* Present motivational message until we get the first 'accesspoints'
* report.
*/
if (cnt == 0)
xml.node("label", [&] () {
xml.attribute("text", "Scanning..."); });
}
void Network_dialog::_gen_connected_ap(Xml_generator &xml, bool connected) const
{
bool done = false;
/*
* Try to present complete info including the quality from access-point
* list.
*/
_access_points.for_each([&] (Access_point const &ap) {
if (!done && _wifi_connection.bssid == ap.bssid) {
_gen_access_point(xml, ap);
done = true;
}
});
/*
* If access point is not present in the list, fall back to the information
* given in the 'state' report.
*/
if (!done)
_gen_access_point(xml, Access_point { _wifi_connection.bssid,
_wifi_connection.ssid,
Access_point::UNKNOWN });
gen_named_node(xml, "label", "associated", [&] () {
xml.attribute("text", connected ? "associated"
: "connecting");
});
}
void Network_dialog::generate(Xml_generator &xml) const
{
gen_named_node(xml, "frame", "network", [&] () {
xml.node("vbox", [&] () {
gen_named_node(xml, "hbox", "type", [&] () {
auto gen_nic_button = [&] (Hoverable_item::Id const &id,
Nic_target::Type const type,
String<20> const &label) {
gen_named_node(xml, "button", id, [&] () {
_nic_item.gen_button_attr(xml, id);
if (_nic_target.type() == type)
xml.attribute("selected", "yes");
xml.node("label", [&] () { xml.attribute("text", label); });
});
};
gen_nic_button("off", Nic_target::OFF, "Off");
/*
* Allow interactive selection only if NIC-router configuration
* is not manually maintained.
*/
if (_nic_target.managed() || _nic_target.manual_type == Nic_target::DISCONNECTED)
gen_nic_button("disconnected", Nic_target::DISCONNECTED, "Disconnected");
if (_nic_target.managed() || _nic_target.manual_type == Nic_target::WIRED)
if (_pci_info.lan_present)
gen_nic_button("wired", Nic_target::WIRED, "Wired");
if (_nic_target.managed() || _nic_target.manual_type == Nic_target::WIFI)
if (_pci_info.wifi_present)
gen_nic_button("wifi", Nic_target::WIFI, "Wifi");
if (_nic_target.managed() || _nic_target.manual_type == Nic_target::MODEM)
if (_pci_info.modem_present)
gen_nic_button("modem", Nic_target::MODEM, "Mobile data");
});
if (_nic_target.wifi() || _nic_target.wired() || _nic_target.modem()) {
gen_named_node(xml, "frame", "nic_info", [&] () {
xml.node("vbox", [&] () {
/*
* If connected via Wifi, show the information of the
* connected access point. If not connected, present
* the complete list of access points with the option
* to select one.
*/
if (_nic_target.wifi()) {
if (_wifi_connection.connected())
_gen_connected_ap(xml, true);
else if (_wifi_connection.connecting())
_gen_connected_ap(xml, false);
else
_gen_access_point_list(xml,
_wifi_connection.auth_failure());
}
/* append display of uplink IP address */
if (_nic_state.ready())
gen_named_node(xml, "label", "ip", [&] () {
xml.attribute("text", _nic_state.ipv4); });
});
});
}
});
});
}
Deprecated_dialog::Hover_result Network_dialog::hover(Xml_node hover)
{
Deprecated_dialog::Hover_result const hover_result = Deprecated_dialog::any_hover_changed(
_nic_item .match(hover, "frame", "vbox", "hbox", "button", "name"),
_ap_item .match(hover, "frame", "vbox", "frame", "vbox", "hbox", "name"),
_connect_item.match(hover, "frame", "vbox", "frame", "vbox", "button", "name"));
_nic_info.match(hover, "vbox", "frame", "name");
return hover_result;
}
void Network_dialog::click(Action &action)
{
if (_nic_item.hovered("off")) action.nic_target(Nic_target::OFF);
if (_nic_item.hovered("disconnected")) action.nic_target(Nic_target::DISCONNECTED);
if (_nic_item.hovered("wired")) action.nic_target(Nic_target::WIRED);
if (_nic_item.hovered("wifi")) action.nic_target(Nic_target::WIFI);
if (_nic_item.hovered("modem")) action.nic_target(Nic_target::MODEM);
if (_wifi_connection.connected() && _ap_item.hovered(_wifi_connection.bssid)) {
action.wifi_disconnect();
_ap_item.reset();
} else {
_ap_item.toggle_selection_on_click();
/* immediately connect to unprotected access point when selected */
if (_ap_item.any_selected() && _selected_ap_unprotected())
action.wifi_connect(selected_ap());
}
if (_connect_item.hovered("connect"))
action.wifi_connect(selected_ap());
}

View File

@ -1,110 +0,0 @@
/*
* \brief Network management dialog
* \author Norman Feske
* \date 2018-05-07
*/
/*
* 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 _VIEW__NETWORK_DIALOG_H_
#define _VIEW__NETWORK_DIALOG_H_
/* local includes */
#include <types.h>
#include <model/nic_target.h>
#include <model/nic_state.h>
#include <model/wifi_connection.h>
#include <model/wpa_passphrase.h>
#include <model/pci_info.h>
#include <view/selectable_item.h>
#include <view/dialog.h>
namespace Sculpt { struct Network_dialog; }
struct Sculpt::Network_dialog : Deprecated_dialog
{
enum Wlan_config_policy { WLAN_CONFIG_MANAGED, WLAN_CONFIG_MANUAL };
Nic_target const &_nic_target;
Access_points const &_access_points;
Wifi_connection const &_wifi_connection;
Nic_state const &_nic_state;
Blind_wpa_passphrase const &_wpa_passphrase;
Wlan_config_policy const &_wlan_config_policy;
Pci_info const &_pci_info;
Hoverable_item _nic_item { };
Selectable_item _ap_item { };
Hoverable_item _nic_info { };
Hoverable_item _connect_item { }; /* confirm WPA passphrase */
bool ap_list_hovered() const { return _nic_target.wifi()
&& _nic_info.hovered("nic_info"); }
/*
* \return true if at least one access point fulfils the condition 'COND_FN'
*/
template <typename COND_FN>
bool _for_each_ap(COND_FN const &cond_fn) const
{
bool result = false;
_access_points.for_each([&] (Access_point const &ap) {
result |= cond_fn(ap); });
return result;
}
Access_point::Bssid selected_ap() const { return _ap_item._selected; }
/* limit view to highest-quality access points */
unsigned const _max_visible_aps = 20;
/* determine whether the selected AP is present in access-point list */
bool _selected_ap_visible() const;
bool _selected_ap_unprotected() const;
void _gen_access_point(Xml_generator &, Access_point const &) const;
void _gen_connected_ap(Xml_generator &, bool) const;
void _gen_access_point_list(Xml_generator &, bool) const;
Hover_result hover(Xml_node) override;
void generate(Xml_generator &) const override;
void reset() override { }
bool need_keyboard_focus_for_passphrase() const;
struct Action : Interface
{
virtual void nic_target(Nic_target::Type) = 0;
virtual void wifi_connect(Access_point::Ssid) = 0;
virtual void wifi_disconnect() = 0;
};
void click(Action &action);
Network_dialog(Nic_target const &nic_target,
Access_points const &access_points,
Wifi_connection const &wifi_connection,
Nic_state const &nic_state,
Blind_wpa_passphrase const &wpa_passphrase,
Wlan_config_policy const &wlan_config_policy,
Pci_info const &pci_info)
:
_nic_target(nic_target), _access_points(access_points),
_wifi_connection(wifi_connection), _nic_state(nic_state),
_wpa_passphrase(wpa_passphrase), _wlan_config_policy(wlan_config_policy),
_pci_info(pci_info)
{ }
};
#endif /* _VIEW__NETWORK_DIALOG_H_ */

View File

@ -0,0 +1,148 @@
/*
* \brief Network management widget
* \author Norman Feske
* \date 2018-05-07
*/
/*
* 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 _VIEW__NETWORK_WIDGET_H_
#define _VIEW__NETWORK_WIDGET_H_
/* local includes */
#include <model/nic_target.h>
#include <model/nic_state.h>
#include <model/pci_info.h>
#include <view/ap_selector_widget.h>
namespace Sculpt { struct Network_widget; }
struct Sculpt::Network_widget : Widget<Frame>
{
using Wlan_config_policy = Ap_selector_widget::Wlan_config_policy;
Nic_target const &_nic_target;
Nic_state const &_nic_state;
Pci_info const &_pci_info;
struct Action : Ap_selector_widget::Action
{
virtual void nic_target(Nic_target::Type) = 0;
};
struct Target_selector : Widget<Hbox>
{
using Type = Nic_target::Type;
Hosted<Hbox, Select_button<Type>>
_off { Id { "Off" }, Type::OFF },
_local { Id { "Disconnected" }, Type::DISCONNECTED },
_wired { Id { "Wired" }, Type::WIRED },
_wifi { Id { "Wifi" }, Type::WIFI },
_modem { Id { "Mobile data" }, Type::MODEM };
void view(Scope<Hbox> &s, Nic_target const &target, Pci_info const &pci_info) const
{
Type const selected = target.type();
s.widget(_off, selected);
/*
* Allow interactive selection only if NIC-router configuration
* is not manually maintained.
*/
if (target.managed() || target.manual_type == Nic_target::DISCONNECTED)
s.widget(_local, selected);
if (target.managed() || target.manual_type == Nic_target::WIRED)
if (pci_info.lan_present)
s.widget(_wired, selected);
if (target.managed() || target.manual_type == Nic_target::WIFI)
if (pci_info.wifi_present)
s.widget(_wifi, selected);
if (target.managed() || target.manual_type == Nic_target::MODEM)
if (pci_info.modem_present)
s.widget(_modem, selected);
}
void click(Clicked_at const &at, Action &action)
{
_off .propagate(at, [&] (Type t) { action.nic_target(t); });
_local.propagate(at, [&] (Type t) { action.nic_target(t); });
_wired.propagate(at, [&] (Type t) { action.nic_target(t); });
_wifi .propagate(at, [&] (Type t) { action.nic_target(t); });
_modem.propagate(at, [&] (Type t) { action.nic_target(t); });
}
};
Hosted<Frame, Vbox, Target_selector> _target_selector { Id { "target" } };
Hosted<Frame, Vbox, Frame, Vbox, Ap_selector_widget> _ap_selector;
void _gen_connected_ap(Xml_generator &, bool) const;
Network_widget(Nic_target const &nic_target,
Access_points const &access_points,
Wifi_connection const &wifi_connection,
Nic_state const &nic_state,
Blind_wpa_passphrase const &wpa_passphrase,
Wlan_config_policy const &wlan_config_policy,
Pci_info const &pci_info)
:
_nic_target(nic_target), _nic_state(nic_state), _pci_info(pci_info),
_ap_selector(Id { "aps" },
access_points, wifi_connection,
wlan_config_policy, wpa_passphrase)
{ }
void view(Scope<Frame> &s) const
{
s.sub_scope<Vbox>([&] (Scope<Frame, Vbox> &s) {
s.sub_scope<Min_ex>(35);
s.widget(_target_selector, _nic_target, _pci_info);
if (_nic_target.wifi() || _nic_target.wired() || _nic_target.modem()) {
s.sub_scope<Frame>([&] (Scope<Frame, Vbox, Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Frame, Vbox, Frame, Vbox> &s) {
if (_nic_target.wifi())
s.widget(_ap_selector);
if (_nic_state.ready())
s.sub_scope<Label>(_nic_state.ipv4);
});
});
}
});
}
void click(Clicked_at const &at, Action &action)
{
_target_selector.propagate(at, action);
_ap_selector.propagate(at, action);
}
bool need_keyboard_focus_for_passphrase() const
{
return _nic_target.wifi()
&& _ap_selector.need_keyboard_focus_for_passphrase();
}
bool ap_list_hovered(Hovered_at const &at) const
{
return _ap_selector.if_hovered(at, [&] (Hovered_at const &) {
return _ap_selector.ap_list_shown(); });
}
};
#endif /* _VIEW__NETWORK_WIDGET_H_ */

View File

@ -14,13 +14,9 @@
#ifndef _VIEW__PANEL_DIALOG_H_
#define _VIEW__PANEL_DIALOG_H_
/* Genode includes */
#include <os/reporter.h>
/* local includes */
#include <types.h>
#include <view/dialog.h>
#include <view/activatable_item.h>
namespace Sculpt { struct Panel_dialog; }

View File

@ -25,8 +25,6 @@ void Partition_operations::view(Scope<Vbox> &s,
Partition const &partition,
Storage_target const &used_target) const
{
using Label = Dialog::Label;
String<16> const version(device.label, ".", partition.number);
bool const whole_device = !partition.number.valid();

View File

@ -1,107 +0,0 @@
/*
* \brief PD/CPU route assignment dialog
* \author Alexander Boettcher
* \date 2021-02-26
*/
/*
* Copyright (C) 2021 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.
*/
#include <view/pd_route_dialog.h>
using namespace Sculpt;
void Pd_route_dialog::generate(Xml_generator &xml) const
{
/* find out number of available PD services */
unsigned count_pd_services = 0;
_runtime_config.for_each_service([&] (Service const &service) {
if (service.type == Service::Type::PD)
count_pd_services ++;
});
/* don't show the PD menu if just the system PD service is available */
if (count_pd_services <= 1)
return;
{
Route::Id const pd_id("pd_route");
gen_named_node(xml, "frame", pd_id, [&] () {
xml.node("vbox", [&] () {
bool const defined = _route.selected_service.constructed();
if (!_menu_selected) {
_gen_route_entry(xml, pd_id,
defined ? Component::Info(_route.selected_service->info)
: Component::Info(_route),
defined);
}
/*
* List of routing options
*/
if (_menu_selected) {
_gen_route_entry(xml, "back", Component::Info(_route), true, "back");
unsigned cnt = 0;
_runtime_config.for_each_service([&] (Service const &service) {
Hoverable_item::Id const id("service.", cnt++);
bool const service_selected =
_route.selected_service.constructed() &&
id == _route.selected_service_id;
if (service.type == _route.required)
_gen_route_entry(xml, id, service.info, service_selected);
});
}
});
});
}
}
void Pd_route_dialog::click(Component &component)
{
if (_route_item.hovered("pd_route")) {
_menu_selected = true;
}
if (!_menu_selected)
return;
unsigned cnt = 0;
_runtime_config.for_each_service([&] (Service const &service) {
Hoverable_item::Id const id("service.", cnt++);
if (!_route_item.hovered(id))
return;
if (_route.selected_service.constructed()) {
if (component.pd_route.selected_service.constructed())
component.pd_route.selected_service.destruct();
_route.selected_service.destruct();
if (_route_item.hovered(_route.selected_service_id)) {
_route.selected_service_id = Hoverable_item::Id();
return;
}
}
component.pd_route.selected_service.construct(service);
_route.selected_service.construct(service);
_route.selected_service_id = id;
_menu_selected = false;
});
}

View File

@ -1,107 +0,0 @@
/*
* \brief PD/CPU route assignment dialog
* \author Alexander Boettcher
* \date 2021-02-26
*/
/*
* Copyright (C) 2021 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 _VIEW__PD_ROUTE_DIALOG_H_
#define _VIEW__PD_ROUTE_DIALOG_H_
/* Genode includes */
#include <os/reporter.h>
#include <depot/archive.h>
/* local includes */
#include <xml.h>
#include <model/component.h>
#include <model/runtime_config.h>
#include <view/dialog.h>
namespace Sculpt { struct Pd_route_dialog; }
struct Sculpt::Pd_route_dialog : Noncopyable, Deprecated_dialog
{
Route _route { "<pd/>" };
Hoverable_item _route_item { };
bool _menu_selected { false };
Runtime_config const &_runtime_config;
Pd_route_dialog(Runtime_config const &runtime_config)
:
_runtime_config(runtime_config)
{ }
Hover_result hover(Xml_node hover_node) override
{
Deprecated_dialog::Hover_result const hover_result = hover(hover_node);
return hover_result;
}
template <typename... ARGS>
Hover_result hover(Xml_node hover, ARGS &&... args)
{
Deprecated_dialog::Hover_result const hover_result = Deprecated_dialog::any_hover_changed(
_route_item.match(hover, args...));
return hover_result;
}
void click(Component &);
void generate(Xml_generator &xml) const override;
void reset() override
{
if (_route.selected_service.constructed())
_route.selected_service.destruct();
_route_item._hovered = Hoverable_item::Id();
_menu_selected = false;
}
void click()
{
if (_route_item.hovered("pd_route"))
_menu_selected = true;
}
void _gen_route_entry(Xml_generator &xml,
Start_name const &name,
Start_name const &text,
bool selected, char const *style = "radio") const
{
gen_named_node(xml, "hbox", name, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "button", "button", [&] () {
if (selected)
xml.attribute("selected", "yes");
xml.attribute("style", style);
_route_item.gen_hovered_attr(xml, name);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", Path(" ", text)); });
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
};
#endif /* _VIEW__PD_ROUTE_DIALOG_H_ */

View File

@ -0,0 +1,99 @@
/*
* \brief PD/CPU route assignment widget
* \author Norman Feske
* \date 2023-10-30
*/
/*
* Copyright (C) 2023 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 _VIEW__PD_ROUTE_WIDGET_H_
#define _VIEW__PD_ROUTE_WIDGET_H_
/* local includes */
#include <model/component.h>
#include <model/runtime_config.h>
#include <view/dialog.h>
namespace Sculpt { struct Pd_route_widget; }
struct Sculpt::Pd_route_widget : Widget<Vbox>
{
Runtime_config const &_runtime_config;
Pd_route_widget(Runtime_config const &runtime_config)
:
_runtime_config(runtime_config)
{ }
void view(Scope<Vbox> &s, Id const &selected_route, Component const &component) const
{
using Route_entry = Hosted<Vbox, Menu_entry>;
using Service_entry = Hosted<Vbox, Menu_entry>;
using Info = Component::Info;
Id const pd_route_id = s.id;
bool const selected = (selected_route == pd_route_id);
if (!selected) {
bool const defined = component.pd_route.selected_service.constructed();
Route_entry entry { pd_route_id };
s.widget(entry, defined,
defined ? Info(component.pd_route.selected_service->info)
: Info("PD"));
return;
}
/*
* List of routing options
*/
Route_entry back { Id { "back" } };
s.widget(back, true, "PD", "back");
unsigned count = 0;
_runtime_config.for_each_service([&] (Service const &service) {
Id const service_id { Id::Value("service.", count++) };
bool const service_selected =
component.pd_route.selected_service.constructed() &&
service_id.value == component.pd_route.selected_service_id;
if (service.type == Service::Type::PD) {
Service_entry entry { service_id };
s.widget(entry, service_selected, service.info);
}
});
}
void click(Clicked_at const &at, Component &component)
{
Id const id = at.matching_id<Vbox, Menu_entry>();
unsigned count = 0;
_runtime_config.for_each_service([&] (Service const &service) {
Id const service_id { Id::Value("service.", count++) };
if (id == service_id) {
Route &route = component.pd_route;
if (route.selected_service_id == service_id.value) {
route.selected_service.destruct();
route.selected_service_id = { };
} else {
route.selected_service.construct(service);
route.selected_service_id = service_id.value;
}
}
});
}
};
#endif /* _VIEW__PD_ROUTE_WIDGET_H_ */

View File

@ -17,153 +17,160 @@
using namespace Sculpt;
void Popup_dialog::_gen_pkg_info(Xml_generator &xml,
Component const &component) const
static Dialog::Id launcher_id(unsigned n) { return { Dialog::Id::Value("launcher ", n) }; }
static Dialog::Id user_id (unsigned n) { return { Dialog::Id::Value("user ", n) }; }
static void view_component_info(auto &s, Component const &component)
{
if (component.info.length() > 1) {
gen_named_node(xml, "label", "info", [&] () {
xml.attribute("text", Component::Info(" ", component.info, " ")); });
s.named_sub_node("label", "info", [&] {
s.attribute("text", Component::Info(" ", component.info, " ")); });
_gen_info_label(xml, "pad1", "");
s.template sub_scope<Annotation>("");
}
_gen_info_label(xml, "path", component.path);
s.template sub_scope<Annotation>(component.path);
}
void Popup_dialog::_gen_pkg_elements(Xml_generator &xml,
Component const &component) const
void Popup_dialog::_view_pkg_elements(Scope<Frame, Vbox> &s,
Component const &component) const
{
typedef Component::Info Info;
using Info = Component::Info;
_gen_sub_menu_title(xml, "back", Index_menu::Name("Add ", Pretty(_construction_name)));
s.widget(_back, Index_menu::Name("Add ", Pretty(_construction_name)));
_gen_pkg_info(xml, component);
view_component_info(s, component);
_gen_info_label(xml, "resources", Info(Capacity{component.ram}, " ",
component.caps, " caps"));
_gen_info_label(xml, "pad2", "");
s.sub_scope<Annotation>(Info(Capacity{component.ram}, " ",
component.caps, " caps"));
s.sub_scope<Vgap>();
unsigned cnt = 0;
unsigned count = 0;
component.routes.for_each([&] (Route const &route) {
Route::Id const id(cnt++);
Id const id { Id::Value { count++ } };
gen_named_node(xml, "frame", id, [&] () {
s.sub_scope<Frame>([&] (Scope<Frame, Vbox, Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Frame, Vbox, Frame, Vbox> &s) {
xml.node("vbox", [&] () {
bool const selected = _route_selected(id);
bool const selected = _route_selected(id.value);
bool const defined = route.selected_service.constructed();
if (!selected) {
_gen_route_entry(xml, id,
defined ? Info(route.selected_service->info)
: Info(route),
defined);
Route_entry entry { id };
s.widget(entry, defined,
defined ? Info(route.selected_service->info)
: Info(route));
}
/*
* List of routing options
*/
if (selected) {
_gen_route_entry(xml, "back", Info(route), true, "back");
Route_entry back { Id { "back" } };
s.widget(back, true, Info(route), "back");
unsigned cnt = 0;
unsigned count = 0;
_runtime_config.for_each_service([&] (Service const &service) {
Hoverable_item::Id const id("service.", cnt++);
Id const service_id { Id::Value("service.", count++) };
bool const service_selected =
route.selected_service.constructed() &&
id == route.selected_service_id;
service_id.value == route.selected_service_id;
if (service.type == route.required)
_gen_route_entry(xml, id, service.info, service_selected);
if (service.type == route.required) {
Service_entry entry { service_id };
s.widget(entry, service_selected, service.info);
}
});
}
});
});
});
_pd_route.generate(xml);
/* don't show the PD menu if only the system PD service is available */
if (_runtime_config.num_service_options(Service::Type::PD) > 1)
s.sub_scope<Frame>([&] (Scope<Frame, Vbox, Frame> &s) {
s.widget(_pd_route, _selected_route, component); });
if (_resources.constructed()) {
gen_named_node(xml, "frame", "resources", [&] {
xml.node("vbox", [&] () {
s.sub_scope<Frame>(Id { "resources" }, [&] (Scope<Frame, Vbox, Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Frame, Vbox, Frame, Vbox> &s) {
bool const selected = _route_selected("resources");
bool const selected = _route_selected("resources");
if (!selected)
_gen_route_entry(xml, "resources",
"Resource assignment ...", false, "enter");
if (!selected) {
Route_entry entry { Id { "resources" } };
s.widget(entry, false, "Resource assignment ...", "enter");
}
if (selected) {
_gen_route_entry(xml, "back", "Resource assignment ...",
true, "back");
if (selected) {
Route_entry entry { Id { "back" } };
s.widget(entry, true, "Resource assignment ...", "back");
_resources->generate(xml);
}
});
s.widget(_resources, component);
}
});
}
});
gen_named_node(xml, "frame", "debug", [&] {
xml.node("vbox", [&] {
_debug.generate(xml); }); });
s.sub_scope<Frame>([&] (Scope<Frame, Vbox, Frame> &s) {
s.widget(_debug, component); });
/*
* Display "Add component" button once all routes are defined
*/
if (component.all_routes_defined()) {
gen_named_node(xml, "button", "launch", [&] () {
_action_item.gen_button_attr(xml, "launch");
xml.node("label", [&] () {
xml.attribute("text", "Add component"); });
});
}
if (component.all_routes_defined())
s.widget(_launch);
}
void Popup_dialog::_gen_menu_elements(Xml_generator &xml, Xml_node const &depot_users) const
void Popup_dialog::_view_menu_elements(Scope<Frame, Vbox> &s, Xml_node const &depot_users) const
{
/*
* Lauchers
*/
if (_state == TOP_LEVEL || _state < DEPOT_SHOWN) {
unsigned count = 0;
_launchers.for_each([&] (Launchers::Info const &info) {
/* allow each launcher to be used only once */
if (_runtime_info.present_in_runtime(info.path))
return;
_gen_menu_entry(xml, info.path, Pretty(info.path), false);
Hosted<Frame, Vbox, Menu_entry> menu_entry { launcher_id(count++) };
s.widget(menu_entry, false, String<100>(Pretty(info.path)));
});
_gen_menu_entry(xml, "depot", "Depot ...", false);
Hosted<Frame, Vbox, Menu_entry> depot_menu_entry { Id { "depot" } };
s.widget(depot_menu_entry, false, "Depot ...");
}
/*
* Depot users with an available index
*/
if (_state == DEPOT_SHOWN || _state == INDEX_REQUESTED) {
_gen_sub_menu_title(xml, "back", "Depot");
s.widget(_back, "Depot");
unsigned count = 0;
depot_users.for_each_sub_node("user", [&] (Xml_node user) {
User const name = user.attribute_value("name", User());
User const name = user.attribute_value("name", User());
bool const selected = (_selected_user == name);
Id const id = user_id(count++);
if (_index_avail(name))
_gen_menu_entry(xml, name, User(name, " ..."), selected);
if (_index_avail(name)) {
Hosted<Frame, Vbox, Menu_entry> menu_entry { id };
s.widget(menu_entry, selected, User(name, " ..."));
}
});
/*
* Depot selection menu item
*/
if (_state == DEPOT_SHOWN || _state == INDEX_REQUESTED) {
if (_nic_ready())
_gen_menu_entry(xml, "selection", "Selection ...", false);
if (_nic_ready()) {
Hosted<Frame, Vbox, Menu_entry> menu_entry { Id { "selection" } };
s.widget(menu_entry, false, "Selection ...");
}
}
@ -171,17 +178,20 @@ void Popup_dialog::_gen_menu_elements(Xml_generator &xml, Xml_node const &depot_
* List of depot users for removing/adding indices
*/
if (_state == DEPOT_SELECTION) {
_gen_sub_menu_title(xml, "back", "Selection");
s.widget(_back, "Selection");
unsigned count = 0;
depot_users.for_each_sub_node("user", [&] (Xml_node user) {
User const name = user.attribute_value("name", User());
bool const selected = _index_avail(name);
Id const id = user_id(count++);
String<32> const suffix = _download_queue.in_progress(_index_path(name))
? " fetch... " : " ";
_gen_menu_entry(xml, name, User(name, suffix), selected, "checkbox");
Hosted<Frame, Vbox, Menu_entry> user_entry { id };
s.widget(user_entry, selected, User(name, suffix), "checkbox");
});
}
@ -193,7 +203,7 @@ void Popup_dialog::_gen_menu_elements(Xml_generator &xml, Xml_node const &depot_
if (_menu._level)
title = Index_menu::Name(title, " ", _menu, " ");
_gen_sub_menu_title(xml, "back", title);
s.widget(_back, title);
}
/*
@ -201,14 +211,15 @@ void Popup_dialog::_gen_menu_elements(Xml_generator &xml, Xml_node const &depot_
*/
if (_state >= INDEX_SHOWN && _state < PKG_SHOWN) {
unsigned cnt = 0;
unsigned count = 0;
_for_each_menu_item([&] (Xml_node item) {
Hoverable_item::Id const id(cnt);
Id const id { Id::Value(count) };
if (item.has_type("index")) {
auto const name = item.attribute_value("name", Index_menu::Name());
_gen_menu_entry(xml, id, Index_menu::Name(name, " ..."), false);
Hosted<Frame, Vbox, Menu_entry> entry { id };
s.widget(entry, false, Index_menu::Name(name, " ..."));
}
if (item.has_type("pkg")) {
@ -229,15 +240,15 @@ void Popup_dialog::_gen_menu_elements(Xml_generator &xml, Xml_node const &depot_
String<100> const text(Pretty(name), " " "(", version, ")",
installing ? " installing... " : "... ");
_gen_menu_entry(xml, id, text, selected);
Hosted<Frame, Vbox, Menu_entry> entry { id };
s.widget(entry, selected, text);
if (selected && !installing) {
_construction_info.with_construction([&] (Component const &component) {
gen_named_node(xml, "float", "install", [&] () {
gen_named_node(xml, "vbox", "vbox", [&] () {
s.sub_scope<Float>(Id { "install" }, [&] (Scope<Frame, Vbox, Float> &s) {
s.sub_scope<Vbox>([&] (Scope<Frame, Vbox, Float, Vbox> &s) {
/*
* Package is installed but content is missing
@ -247,24 +258,18 @@ void Popup_dialog::_gen_menu_elements(Xml_generator &xml, Xml_node const &depot_
* the pkg's archives.
*/
if (_blueprint_info.incomplete()) {
_gen_info_label(xml, "pad2", "");
_gen_info_label(xml, "path", component.path);
_gen_info_label(xml, "pad3", "");
xml.node("label", [&] () {
xml.attribute("text", "installed but incomplete"); });
s.sub_scope<Vgap>();
s.sub_scope<Annotation>(component.path);
s.sub_scope<Vgap>();
s.sub_scope<Label>("installed but incomplete");
if (_nic_ready()) {
_gen_info_label(xml, "pad4", "");
gen_named_node(xml, "float", "install", [&] () {
xml.node("button", [&] () {
_install_item.gen_button_attr(xml, "install");
xml.node("label", [&] () {
xml.attribute("text", "Reattempt Install");
});
});
});
s.sub_scope<Vgap>();
s.sub_scope<Float>([&] (Scope<Frame, Vbox, Float, Vbox, Float> &s) {
s.widget(_install, [&] (Scope<Button> &s) {
s.sub_scope<Label>("Reattempt Install"); }); });
}
s.sub_scope<Vgap>();
}
/*
@ -272,17 +277,13 @@ void Popup_dialog::_gen_menu_elements(Xml_generator &xml, Xml_node const &depot_
*/
else if (_blueprint_info.uninstalled() && _nic_ready()) {
_gen_pkg_info(xml, component);
_gen_info_label(xml, "pad2", "");
view_component_info(s, component);
gen_named_node(xml, "float", "install", [&] () {
xml.node("button", [&] () {
_install_item.gen_button_attr(xml, "install");
xml.node("label", [&] () {
xml.attribute("text", "Install");
});
});
});
s.sub_scope<Vgap>();
s.sub_scope<Float>([&] (Scope<Frame, Vbox, Float, Vbox, Float> &s) {
s.widget(_install, [&] (Scope<Button> &s) {
s.sub_scope<Label>("Install"); }); });
s.sub_scope<Vgap>();
}
/*
@ -290,20 +291,18 @@ void Popup_dialog::_gen_menu_elements(Xml_generator &xml, Xml_node const &depot_
* about it
*/
else if (_blueprint_info.uninstalled()) {
_gen_info_label(xml, "pad2", "");
_gen_info_label(xml, "path", component.path);
_gen_info_label(xml, "pad3", "");
xml.node("label", [&] () {
xml.attribute("text", "not installed"); });
s.sub_scope<Vgap>();
s.sub_scope<Annotation>(component.path);
s.sub_scope<Vgap>();
s.sub_scope<Label>("not installed");
s.sub_scope<Vgap>();
}
_gen_info_label(xml, "pad4", "");
});
});
});
}
}
cnt++;
count++;
});
}
@ -312,227 +311,225 @@ void Popup_dialog::_gen_menu_elements(Xml_generator &xml, Xml_node const &depot_
*/
if (_state >= PKG_SHOWN)
_construction_info.with_construction([&] (Component const &component) {
_gen_pkg_elements(xml, component); });
_view_pkg_elements(s, component); });
}
void Popup_dialog::click(Action &action)
void Popup_dialog::click(Clicked_at const &at)
{
Hoverable_item::Id const clicked = _item._hovered;
bool clicked_on_back_button = false;
_action_item .propose_activation_on_click();
_install_item.propose_activation_on_click();
_pd_route.click();
_back.propagate(at, [&] {
clicked_on_back_button = true;
Route::Id const clicked_route = _route_item._hovered;
switch (_state) {
case TOP_LEVEL: break;
case DEPOT_REQUESTED: break;
case DEPOT_SHOWN: _state = TOP_LEVEL; break;
case DEPOT_SELECTION: _state = DEPOT_SHOWN; break;
case INDEX_REQUESTED: break;
auto back_to_index = [&] ()
{
_state = INDEX_SHOWN;
action.discard_construction();
_selected_route.destruct();
};
if (_state == TOP_LEVEL) {
if (clicked == "depot") {
_state = DEPOT_REQUESTED;
_depot_query.trigger_depot_query();
} else {
action.launch_global(clicked);
}
}
else if (_state == DEPOT_SHOWN) {
/* back to top-level menu */
if (clicked == "back") {
_state = TOP_LEVEL;
} else if (clicked == "selection") {
_state = DEPOT_SELECTION;
} else {
/* enter depot users menu */
_selected_user = clicked;
_state = INDEX_REQUESTED;
_depot_query.trigger_depot_query();
}
}
else if (_state == DEPOT_SELECTION) {
/* back to depot users */
if (clicked == "back") {
_state = DEPOT_SHOWN;
} else {
if (!_index_avail(clicked))
action.trigger_download(_index_path(clicked), Verify{true});
else
action.remove_index(clicked);
}
}
else if (_state >= INDEX_SHOWN && _state < PKG_SHOWN) {
/* back to depot users */
if (_menu._level == 0 && clicked == "back") {
_state = DEPOT_SHOWN;
_selected_user = User();
} else {
/* go one menu up */
if (clicked == "back") {
case INDEX_SHOWN:
case PKG_REQUESTED:
if (_menu._level > 0) {
/* go one menu up */
_menu._selected[_menu._level] = Index_menu::Name();
_menu._level--;
action.discard_construction();
_action.discard_construction();
} else {
/* enter sub menu of index */
if (_menu._level < Index_menu::MAX_LEVELS - 1) {
unsigned cnt = 0;
_for_each_menu_item([&] (Xml_node item) {
if (clicked == Hoverable_item::Id(cnt)) {
if (item.has_type("index")) {
Index_menu::Name const name =
item.attribute_value("name", Index_menu::Name());
_menu._selected[_menu._level] = name;
_menu._level++;
} else if (item.has_type("pkg")) {
auto path = item.attribute_value("path", Component::Path());
auto info = item.attribute_value("info", Component::Info());
_construction_name =
action.new_construction(path, Verify{true}, info);
_state = PKG_REQUESTED;
_depot_query.trigger_depot_query();
}
}
cnt++;
});
}
_state = DEPOT_SHOWN;
_selected_user = User();
}
break;
case PKG_SHOWN:
case ROUTE_SELECTED:
_state = INDEX_SHOWN;
_action.discard_construction();
_selected_route = { };
break;
}
}
});
else if (_state == PKG_SHOWN) {
if (clicked_on_back_button)
return;
/* back to index */
if (clicked == "back") {
back_to_index();
_launch.propagate(at);
_install.propagate(at);
Id const route_id = at.matching_id<Frame, Vbox, Frame, Vbox, Menu_entry>();
auto with_matching_user = [&] (Id const &id, auto const &fn)
{
unsigned count = 0;
_depot_users.xml().for_each_sub_node("user", [&] (Xml_node const &user) {
if (id == user_id(count++))
fn(user.attribute_value("name", User())); });
};
State const orig_state = _state;
if (orig_state == TOP_LEVEL) {
Id const id = at.matching_id<Frame, Vbox, Menu_entry>();
if (id.value == "depot") {
_state = DEPOT_REQUESTED;
_depot_query.trigger_depot_query();
} else {
/* select route to present routing options */
if (clicked_route.valid()) {
_state = ROUTE_SELECTED;
_selected_route.construct(clicked_route);
}
unsigned count = 0;
_launchers.for_each([&] (Launchers::Info const &info) {
if (id == launcher_id(count++))
_action.launch_global(info.path); });
}
}
else if (_state == ROUTE_SELECTED || _dialog_item.hovered("debug")) {
else if (orig_state == DEPOT_SHOWN) {
/*
* Keep the routing selection open when clicking on the "Add component"
* button. Otherwise, the change of the dialog size (while folding the
* route selection) would result in the unhovering of the operaton
* button. So the clack would go elsewhere.
*/
bool const click_on_operation = _action_item.hovered("launch");
Id const id = at.matching_id<Frame, Vbox, Menu_entry>();
/* back to index */
if (clicked == "back") {
back_to_index();
if (id.value == "selection")
_state = DEPOT_SELECTION;
} else if (!click_on_operation) {
/* enter depot users menu */
with_matching_user(id, [&] (User const &user) {
_selected_user = user;
_state = INDEX_REQUESTED;
_depot_query.trigger_depot_query();
});
}
/* close selected route */
if (clicked_route == "back") {
_state = PKG_SHOWN;
_selected_route.destruct();
_pd_route.reset();
else if (orig_state == DEPOT_SELECTION) {
} else if (_resource_dialog_selected()) {
Id const id = at.matching_id<Frame, Vbox, Menu_entry>();
bool const clicked_on_different_route = clicked_route.valid()
&& (clicked_route != "");
if (clicked_on_different_route) {
with_matching_user(id, [&] (User const &user) {
/* close resource dialog */
_selected_route.construct(clicked_route);
if (!_index_avail(user))
_action.trigger_download(_index_path(user), Verify{true});
else
_action.remove_index(user);
});
}
} else {
else if (orig_state >= INDEX_SHOWN && orig_state < PKG_SHOWN) {
if (_resources.constructed())
action.apply_to_construction([&] (Component &component) {
_resources->click(component); });
Id const id = at.matching_id<Frame, Vbox, Menu_entry>();
/* enter sub menu of index */
if (_menu._level < Index_menu::MAX_LEVELS - 1) {
unsigned count = 0;
_for_each_menu_item([&] (Xml_node item) {
if (id.value == Id::Value(count)) {
if (item.has_type("index")) {
Index_menu::Name const name =
item.attribute_value("name", Index_menu::Name());
_menu._selected[_menu._level] = name;
_menu._level++;
} else if (item.has_type("pkg")) {
auto path = item.attribute_value("path", Component::Path());
auto info = item.attribute_value("info", Component::Info());
_construction_name =
_action.new_construction(path, Verify{true}, info);
_state = PKG_REQUESTED;
_depot_query.trigger_depot_query();
}
}
count++;
});
}
}
else if (orig_state == PKG_SHOWN) {
/* select route to present routing options */
if (route_id.valid()) {
_state = ROUTE_SELECTED;
_selected_route = route_id;
}
}
else if (orig_state == ROUTE_SELECTED) {
/* close selected route */
if (route_id.value == "back") {
_state = PKG_SHOWN;
_selected_route = { };
} else if (_resource_dialog_selected()) {
bool const clicked_on_different_route = route_id.valid();
if (clicked_on_different_route) {
_selected_route = route_id;
} else {
bool clicked_on_selected_route = false;
_action.apply_to_construction([&] (Component &component) {
_resources.propagate(at, component); });
}
_apply_to_selected_route(action, [&] (Route &route) {
} else {
unsigned cnt = 0;
_runtime_config.for_each_service([&] (Service const &service) {
bool clicked_on_selected_route = false;
Hoverable_item::Id const id("service.", cnt++);
_apply_to_selected_route(_action, [&] (Route &route) {
if (clicked_route == id) {
unsigned count = 0;
_runtime_config.for_each_service([&] (Service const &service) {
bool const clicked_service_already_selected =
route.selected_service.constructed() &&
id == route.selected_service_id;
Id const id { Id::Value("service.", count++) };
if (clicked_service_already_selected) {
if (route_id == id) {
/* clear selection */
route.selected_service.destruct();
route.selected_service_id = Hoverable_item::Id();
bool const clicked_service_already_selected =
route.selected_service.constructed() &&
id.value == route.selected_service_id;
} else {
if (clicked_service_already_selected) {
/* select different service */
route.selected_service.construct(service);
route.selected_service_id = id;
}
/* clear selection */
route.selected_service.destruct();
route.selected_service_id = { };
_state = PKG_SHOWN;
_selected_route.destruct();
} else {
clicked_on_selected_route = true;
/* select different service */
route.selected_service.construct(service);
route.selected_service_id = id.value;
}
});
});
/* select different route */
if (!clicked_on_selected_route && clicked_route.valid()) {
_state = ROUTE_SELECTED;
_selected_route.construct(clicked_route);
}
_state = PKG_SHOWN;
_selected_route = { };
action.apply_to_construction([&] (Component &component) {
_pd_route.click(component);
clicked_on_selected_route = true;
}
});
});
if (_selected_route == _pd_route.id)
_action.apply_to_construction([&] (Component &component) {
_pd_route.propagate(at, component); });
/* select different route */
if (!clicked_on_selected_route && route_id.valid()) {
_state = ROUTE_SELECTED;
_selected_route = route_id;
}
}
}
if (_state == PKG_SHOWN || _state == ROUTE_SELECTED) {
if (_dialog_item.hovered("debug"))
action.apply_to_construction([&] (Component &component) {
_debug.click(component); });
}
if (orig_state == PKG_SHOWN || orig_state == ROUTE_SELECTED)
_action.apply_to_construction([&] (Component &component) {
_debug.propagate(at, component); });
}

View File

@ -14,10 +14,6 @@
#ifndef _VIEW__POPUP_DIALOG_H_
#define _VIEW__POPUP_DIALOG_H_
/* Genode includes */
#include <os/reporter.h>
#include <depot/archive.h>
/* local includes */
#include <types.h>
#include <model/launchers.h>
@ -28,17 +24,15 @@
#include <model/nic_state.h>
#include <model/index_menu.h>
#include <view/dialog.h>
#include <view/activatable_item.h>
#include <view/pd_route_widget.h>
#include <view/resource_widget.h>
#include <view/debug_widget.h>
#include <depot_query.h>
#include <view/pd_route_dialog.h>
#include <view/resource_dialog.h>
#include <view/debug_dialog.h>
namespace Sculpt { struct Popup_dialog; }
struct Sculpt::Popup_dialog : Deprecated_dialog
struct Sculpt::Popup_dialog : Dialog::Top_level_dialog
{
using Depot_users = Attached_rom_dataspace;
using Blueprint_info = Component::Blueprint_info;
@ -115,18 +109,40 @@ struct Sculpt::Popup_dialog : Deprecated_dialog
virtual void remove_index(Depot::Archive::User const &) = 0;
};
Action &_action;
Construction_info const &_construction_info;
Hoverable_item _item { };
Activatable_item _action_item { };
Activatable_item _install_item { };
Hoverable_item _route_item { };
Hoverable_item _dialog_item { }; /* for detecting clicks into debug dialog */
Pd_route_dialog _pd_route { _runtime_config };
using Route_entry = Hosted<Frame, Vbox, Frame, Vbox, Menu_entry>;
using Service_entry = Hosted<Frame, Vbox, Frame, Vbox, Menu_entry>;
Constructible<Resource_dialog> _resources { };
struct Sub_menu_title : Widget<Left_floating_hbox>
{
void view(Scope<Left_floating_hbox> &s, auto const &text) const
{
bool const hovered = (s.hovered() && !s.dragged());
Debug_dialog _debug { };
s.sub_scope<Icon>("back", Icon::Attr { .hovered = hovered,
.selected = true });
s.sub_scope<Label>(" ");
s.sub_scope<Label>(text, [&] (auto &s) {
s.attribute("font", "title/regular"); });
/* inflate vertical space to button size */
s.sub_scope<Button>([&] (Scope<Left_floating_hbox, Button> &s) {
s.attribute("style", "invisible");
s.sub_scope<Label>(""); });
}
void click(Clicked_at const &, auto const &fn) { fn(); }
};
Hosted<Frame, Vbox, Sub_menu_title> _back { Id { "back" } };
Hosted<Frame, Vbox, Deferred_action_button> _launch { Id { "Add component" } };
Hosted<Frame, Vbox, Float, Vbox, Float, Deferred_action_button> _install { Id { "install" } };
Hosted<Frame, Vbox, Frame, Vbox, Resource_widget> _resources { Id { "resources" } };
Hosted<Frame, Vbox, Frame, Pd_route_widget> _pd_route { Id { "pd_route" }, _runtime_config };
Hosted<Frame, Vbox, Frame, Debug_widget> _debug { Id { "debug" } };
enum State { TOP_LEVEL, DEPOT_REQUESTED, DEPOT_SHOWN, DEPOT_SELECTION,
INDEX_REQUESTED, INDEX_SHOWN,
@ -141,11 +157,11 @@ struct Sculpt::Popup_dialog : Deprecated_dialog
Component::Name _construction_name { };
Constructible<Route::Id> _selected_route { };
Id _selected_route { };
bool _route_selected(Route::Id const &id) const
{
return _selected_route.constructed() && id == _selected_route->string();
return _selected_route.valid() && id == _selected_route.value;
}
bool _resource_dialog_selected() const
@ -153,8 +169,7 @@ struct Sculpt::Popup_dialog : Deprecated_dialog
return _route_selected("resources");
}
template <typename FN>
void _apply_to_selected_route(Action &action, FN const &fn)
void _apply_to_selected_route(Action &action, auto const &fn)
{
unsigned cnt = 0;
action.apply_to_construction([&] (Component &component) {
@ -165,29 +180,6 @@ struct Sculpt::Popup_dialog : Deprecated_dialog
Index_menu _menu { };
Hover_result hover(Xml_node hover) override
{
Deprecated_dialog::Hover_result hover_result = Deprecated_dialog::any_hover_changed(
_item .match(hover, "frame", "vbox", "hbox", "name"),
_action_item .match(hover, "frame", "vbox", "button", "name"),
_install_item.match(hover, "frame", "vbox", "float", "vbox", "float", "button", "name"),
_route_item .match(hover, "frame", "vbox", "frame", "vbox", "hbox", "name"),
_dialog_item .match(hover, "frame", "vbox", "frame", "name"));
_pd_route.hover(hover, "frame", "vbox", "frame", "vbox", "hbox", "name");
if (_resources.constructed())
hover_result = Deprecated_dialog::any_hover_changed(
hover_result,
_resources->match_sub_dialog(hover, "frame", "vbox", "frame", "vbox"));
hover_result = Deprecated_dialog::any_hover_changed(
hover_result,
_debug.match_sub_dialog(hover, "frame", "vbox", "frame", "vbox"));
return hover_result;
}
void depot_users_scan_updated()
{
if (_state == DEPOT_REQUESTED)
@ -230,153 +222,54 @@ struct Sculpt::Popup_dialog : Deprecated_dialog
return Path(user, "/index/", _sculpt_version);
}
void _gen_sub_menu_title(Xml_generator &xml,
Start_name const &name,
Start_name const &text) const
{
gen_named_node(xml, "hbox", name, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "button", "back", [&] () {
xml.attribute("selected", "yes");
xml.attribute("style", "back");
_item.gen_hovered_attr(xml, name);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "label", [&] () {
xml.attribute("font", "title/regular");
xml.attribute("text", Path(" ", text));
});
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
void _gen_menu_entry(Xml_generator &xml, Start_name const &name,
Component::Info const &text, bool selected,
char const *style = "radio") const
{
gen_named_node(xml, "hbox", name, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "button", "button", [&] () {
if (selected)
xml.attribute("selected", "yes");
xml.attribute("style", style);
_item.gen_hovered_attr(xml, name);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", Path(" ", text)); });
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
void _gen_route_entry(Xml_generator &xml,
Start_name const &name,
Start_name const &text,
bool selected, char const *style = "radio") const
{
gen_named_node(xml, "hbox", name, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "button", "button", [&] () {
if (selected)
xml.attribute("selected", "yes");
xml.attribute("style", style);
_route_item.gen_hovered_attr(xml, name);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", Path(" ", text)); });
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
template <typename FN>
void _for_each_menu_item(FN const &fn) const
{
_menu.for_each_item(_index_rom.xml(), _selected_user, fn);
}
static void _gen_info_label(Xml_generator &xml, char const *name,
Component::Info const &info)
void _view_pkg_elements (Scope<Frame, Vbox> &, Component const &) const;
void _view_menu_elements(Scope<Frame, Vbox> &, Xml_node const &depot_users) const;
void view(Scope<> &s) const override
{
gen_named_node(xml, "label", name, [&] () {
xml.attribute("font", "annotation/regular");
xml.attribute("text", Component::Info(" ", info, " ")); });
s.sub_scope<Frame>([&] (Scope<Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Frame, Vbox> &s) {
_view_menu_elements(s, _depot_users.xml()); }); });
}
void _gen_pkg_info (Xml_generator &, Component const &) const;
void _gen_pkg_elements (Xml_generator &, Component const &) const;
void _gen_menu_elements(Xml_generator &, Xml_node const &depot_users) const;
void click(Clicked_at const &) override;
void generate(Xml_generator &xml) const override
void clack(Clacked_at const &at) override
{
xml.node("frame", [&] () {
xml.node("vbox", [&] () {
_gen_menu_elements(xml, _depot_users.xml()); }); });
}
void click(Action &action);
void clack(Action &action)
{
_action_item.confirm_activation_on_clack();
_install_item.confirm_activation_on_clack();
if (_action_item.activated("launch")) {
action.launch_construction();
_launch.propagate(at, [&] {
_action.launch_construction();
reset();
}
});
bool const pkg_need_install = !_blueprint_info.pkg_avail
|| _blueprint_info.incomplete();
if (pkg_need_install && _install_item.activated("install")) {
_construction_info.with_construction([&] (Component const &component) {
action.trigger_download(component.path, component.verify);
_install_item.reset();
_refresh.refresh_popup_dialog();
});
}
_install.propagate(at, [&] {
bool const pkg_need_install = !_blueprint_info.pkg_avail
|| _blueprint_info.incomplete();
if (pkg_need_install) {
_construction_info.with_construction([&] (Component const &component) {
_action.trigger_download(component.path, component.verify);
_refresh.refresh_popup_dialog();
});
}
});
}
void reset() override
void drag(Dragged_at const &) override { }
void reset()
{
_item._hovered = Hoverable_item::Id();
_route_item._hovered = Hoverable_item::Id();
_dialog_item._hovered = Hoverable_item::Id();
_action_item.reset();
_install_item.reset();
_state = TOP_LEVEL;
_selected_user = User();
_selected_route.destruct();
_selected_route = { };
_menu._level = 0;
_resources.destruct();
_debug.reset();
_pd_route.reset();
}
Popup_dialog(Env &env, Refresh &refresh,
Popup_dialog(Env &env, Refresh &refresh, Action &action,
Launchers const &launchers,
Nic_state const &nic_state,
Nic_target const &nic_target,
@ -387,12 +280,13 @@ struct Sculpt::Popup_dialog : Deprecated_dialog
Depot_query &depot_query,
Construction_info const &construction_info)
:
Top_level_dialog("popup"),
_env(env), _launchers(launchers),
_nic_state(nic_state), _nic_target(nic_target),
_runtime_info(runtime_info), _runtime_config(runtime_config),
_download_queue(download_queue), _depot_users(depot_users),
_depot_query(depot_query),
_refresh(refresh), _construction_info(construction_info)
_depot_query(depot_query), _refresh(refresh), _action(action),
_construction_info(construction_info)
{
_index_rom.sigh(_index_handler);
}
@ -423,12 +317,6 @@ struct Sculpt::Popup_dialog : Deprecated_dialog
if (_state < PKG_REQUESTED)
return;
_resources.construct(construction.affinity_space,
construction.affinity_location,
construction.priority);
_debug.reset();
construction.try_apply_blueprint(blueprint);
_blueprint_info = construction.blueprint_info;

View File

@ -1,158 +0,0 @@
/*
* \brief Radio-button dialog
* \author Norman Feske
* \date 2021-03-19
*/
/*
* Copyright (C) 2021 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 _VIEW__RADIO_CHOICE_DIALOG_H_
#define _VIEW__RADIO_CHOICE_DIALOG_H_
#include <view/dialog.h>
namespace Sculpt { struct Radio_choice_dialog; }
struct Sculpt::Radio_choice_dialog : Noncopyable, Deprecated_dialog
{
typedef Hoverable_item::Id Id;
Id const _id;
struct Min_ex { unsigned left; unsigned right; };
Min_ex const _min_ex;
Hoverable_item _choice_item { };
bool _unfolded = false;
Hover_result hover(Xml_node hover) override
{
return any_hover_changed(_choice_item.match(hover, "hbox", "frame", "vbox", "hbox", "name"));
}
void reset() override
{
_unfolded = false;
}
struct Choice : Interface, Noncopyable
{
virtual void generate(Id const &option_id) const = 0;
};
void generate(Xml_generator &) const override { };
template <typename FN>
void generate(Xml_generator &xml, Id const &selected_id, FN const &fn) const
{
struct Choice_generator : Choice
{
Xml_generator &xml;
Radio_choice_dialog const &dialog;
Id const selected_id;
Choice_generator(Xml_generator &xml, Id const &selected_id,
Radio_choice_dialog const &dialog)
:
xml(xml), dialog(dialog), selected_id(selected_id)
{ }
void generate(Id const &option_id) const override
{
bool const selected = (option_id == selected_id);
if (!selected && !dialog._unfolded)
return;
gen_named_node(xml, "hbox", option_id, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "button", "button", [&] () {
if (selected)
xml.attribute("selected", "yes");
xml.attribute("style", "radio");
dialog._choice_item.gen_hovered_attr(xml, option_id);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", Path(" ", option_id)); });
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
} choice_generator { xml, selected_id, *this };
gen_named_node(xml, "hbox", _id, [&] () {
gen_named_node(xml, "vbox", "left", [&] () {
gen_named_node(xml, "float", "title", [&] () {
xml.attribute("north", true);
xml.attribute("west", true);
xml.node("frame", [&] () {
xml.attribute("style", "invisible");
xml.node("hbox", [&] () {
xml.node("label", [&] () {
xml.attribute("text", _id); }); });
/* used for consistent vertical text alignment */
gen_named_node(xml, "button", "vspace", [&] () {
xml.attribute("style", "invisible");
xml.node("hbox", [&] () { });
});
});
});
gen_named_node(xml, "label", "hspace", [&] () {
xml.attribute("min_ex", _min_ex.left); });
});
gen_named_node(xml, "frame", "right", [&] () {
xml.node("vbox", [&] () {
fn(choice_generator);
gen_named_node(xml, "label", "hspace", [&] () {
xml.attribute("min_ex", _min_ex.right); });
});
});
});
}
Click_result click()
{
/* unfold choice on click */
if (_choice_item._hovered == "")
return Click_result::IGNORED;
_unfolded = true;
return Click_result::CONSUMED;
}
Id hovered_choice() const { return _choice_item._hovered; }
Radio_choice_dialog(Id const &id, Min_ex min_ex)
:
_id(id), _min_ex(min_ex)
{ }
};
#endif /* _VIEW__RADIO_CHOICE_DIALOG_H_ */

View File

@ -1,5 +1,5 @@
/*
* \brief RAM file-system management dialog
* \brief RAM file-system management widget
* \author Norman Feske
* \date 2020-01-28
*/
@ -11,16 +11,16 @@
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _VIEW__RAM_FS_DIALOG_H_
#define _VIEW__RAM_FS_DIALOG_H_
#ifndef _VIEW__RAM_FS_WIDGET_H_
#define _VIEW__RAM_FS_WIDGET_H_
#include <view/fs_operations.h>
#include <model/ram_fs_state.h>
namespace Sculpt { struct Ram_fs_dialog; }
namespace Sculpt { struct Ram_fs_widget; }
struct Sculpt::Ram_fs_dialog : Widget<Vbox>
struct Sculpt::Ram_fs_widget : Widget<Vbox>
{
Storage_target const _target { "ram_fs", Partition::Number() };
@ -59,4 +59,4 @@ struct Sculpt::Ram_fs_dialog : Widget<Vbox>
}
};
#endif /* _VIEW__RAM_FS_DIALOG_H_ */
#endif /* _VIEW__RAM_FS_WIDGET_H_ */

View File

@ -1,290 +0,0 @@
/*
* \brief Resource assignment dialog
* \author Alexander Boettcher
* \author Norman Feske
* \date 2020-07-22
*/
/*
* Copyright (C) 2020-2021 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.
*/
#include <view/resource_dialog.h>
using namespace Sculpt;
void Resource_dialog::_gen_affinity_section(Xml_generator &xml) const
{
unsigned const CELL_WIDTH_EX = 4;
using Id = Hoverable_item::Id;
auto gen_small_label = [] (Xml_generator &xml, auto id, auto fn) {
gen_named_node(xml, "label", id, [&] () {
xml.attribute("font", "annotation/regular");
fn(); }); };
auto gen_cell_hspacer = [&] (Xml_generator &xml, auto id) {
gen_small_label(xml, id, [&] () {
xml.attribute("min_ex", CELL_WIDTH_EX); }); };
auto gen_cell_cpu = [&] (Xml_generator &xml, auto name, bool selected)
{
gen_named_node(xml, "vbox", name, [&] () {
gen_named_node(xml, "button", name, [&] () {
xml.attribute("style", "checkbox");
_space_item.gen_hovered_attr(xml, name);
if (selected)
xml.attribute("selected", "yes");
xml.node("hbox", [&] () { });
});
gen_cell_hspacer(xml, "below");
});
};
auto gen_cell_number = [&] (Xml_generator &xml, Id n)
{
gen_named_node(xml, "vbox", n, [&] () {
gen_cell_hspacer(xml, "above");
gen_named_node(xml, "float", "number", [&] () {
gen_small_label(xml, "number", [&] () {
xml.attribute("text", n); }); });
gen_cell_hspacer(xml, "below");
});
};
auto cpu_selected = [] (Affinity::Location location, unsigned x, unsigned y)
{
return (unsigned(location.xpos()) <= x)
&& (x < location.xpos() + location.width())
&& (unsigned(location.ypos()) <= y)
&& (y < location.ypos() + location.height());
};
_gen_dialog_section(xml, "affinity", "Affinity", [&] () {
gen_named_node(xml, "vbox", "selection", [&] () {
bool const have_hyperthreads = (_space.height() > 1);
gen_named_node(xml, "hbox", "labeledcores", [&] () {
gen_named_node(xml, "vbox", "cores", [&] () {
for (unsigned y = 0; y < _space.height(); y++) {
gen_named_node(xml, "hbox", Id("row", y), [&] () {
for (unsigned x = 0; x < _space.width(); x++)
gen_cell_cpu(xml, _cpu_id(x, y),
cpu_selected(_location, x, y));
if (have_hyperthreads)
gen_named_node(xml, "float", "number", [&] () {
gen_small_label(xml, "number", [&] () {
xml.attribute("min_ex", 2);
xml.attribute("text", y); }); });
});
}
});
if (have_hyperthreads) {
gen_named_node(xml, "float", "hyperthreadslabel", [&] () {
xml.node("vbox", [&] () {
unsigned line = 0;
auto gen_leftaligned = [&] (auto text) {
gen_named_node(xml, "float", Id(line++), [&] () {
xml.attribute("west", "yes");
gen_small_label(xml, "label", [&] () {
xml.attribute("text", text); }); }); };
gen_leftaligned("Hyper");
gen_leftaligned("threads");
});
});
}
});
gen_named_node(xml, "float", "corelabels", [&] () {
xml.attribute("west", "yes");
xml.node("vbox", [&] () {
xml.node("hbox", [&] () {
for (unsigned x = 0; x < _space.width(); x++)
gen_cell_number(xml, x); });
gen_small_label(xml, "cores", [&] () {
xml.attribute("text", "Cores"); });
});
});
});
});
}
void Resource_dialog::_gen_priority_section(Xml_generator &xml) const
{
_gen_dialog_section(xml, "priority", "Priority", [&] () {
gen_named_node(xml, "vbox", "selection", [&] () {
auto gen_radiobutton = [&] (auto id, auto text)
{
gen_named_node(xml, "hbox", id, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
gen_named_node(xml, "hbox", id, [&] () {
gen_named_node(xml, "button", "button", [&] () {
xml.attribute("style", "radio");
_priority_item.gen_button_attr(xml, id);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", text);
xml.attribute("min_ex", 13);
});
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
};
gen_radiobutton("driver", "Driver");
gen_radiobutton("multimedia", "Multimedia");
gen_radiobutton("default", "Default");
gen_radiobutton("background", "Background");
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
void Resource_dialog::click(Component &component)
{
if (component.affinity_space.total() > 1) {
Hoverable_item::Id const clicked_space = _space_item._hovered;
if (clicked_space.valid()) {
_click_space(component, clicked_space);
return;
}
}
Hoverable_item::Id const clicked_priority = _priority_item._hovered;
if (clicked_priority.valid()) {
_click_priority(component, clicked_priority);
return;
}
if (_option_item.hovered("system_control")) {
_system_control = !_system_control;
component.system_control = _system_control;
}
}
void Resource_dialog::_click_space(Component &component, Hoverable_item::Id clicked_space)
{
for (unsigned y = 0; y < _space.height(); y++) {
for (unsigned x = 0; x < _space.width(); x++) {
if (_cpu_id(x, y) != clicked_space)
continue;
unsigned loc_x = _location.xpos();
unsigned loc_y = _location.ypos();
unsigned loc_w = _location.width();
unsigned loc_h = _location.height();
bool handled_x = false;
bool handled_y = false;
if (x < loc_x) {
loc_w += loc_x - x;
loc_x = x;
handled_x = true;
} else if (x >= loc_x + loc_w) {
loc_w = x - loc_x + 1;
handled_x = true;
}
if (y < loc_y) {
loc_h += loc_y - y;
loc_y = y;
handled_y = true;
} else if (y >= loc_y + loc_h) {
loc_h = y - loc_y + 1;
handled_y = true;
}
if (handled_x && !handled_y) {
handled_y = true;
} else
if (handled_y && !handled_x) {
handled_x = true;
}
if (!handled_x) {
if ((x - loc_x) < (loc_x + loc_w - x)) {
if (x - loc_x + 1 < loc_w) {
loc_w -= x - loc_x + 1;
loc_x = x + 1;
} else {
loc_w = loc_x + loc_w - x;
loc_x = x;
}
} else {
loc_w = x - loc_x;
}
}
if (!handled_y) {
if ((y - loc_y) < (loc_y + loc_h - y)) {
if (y - loc_y + 1 < loc_h) {
loc_h -= y - loc_y + 1;
loc_y = y + 1;
} else {
loc_h = loc_y + loc_h - y;
loc_y = y;
}
} else {
loc_h = y - loc_y;
}
}
_location = Affinity::Location(loc_x, loc_y,
loc_w, loc_h);
component.affinity_location = _location;
}
}
}
void Resource_dialog::_click_priority(Component &component, Hoverable_item::Id priority)
{
_priority_item.select(priority);
/* propagate priority change to component */
auto priority_value = [] (Hoverable_item::Id string)
{
if (string == "background") return Priority::BACKGROUND;
if (string == "default") return Priority::DEFAULT;
if (string == "multimedia") return Priority::MULTIMEDIA;
if (string == "driver") return Priority::DRIVER;
return Priority::BACKGROUND;
};
component.priority = priority_value(priority);
}

View File

@ -1,190 +0,0 @@
/*
* \brief Resource assignment dialog
* \author Alexander Boettcher
* \author Norman Feske
* \date 2020-07-22
*/
/*
* Copyright (C) 2020-2021 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 _VIEW__RESOURCE_DIALOG_H_
#define _VIEW__RESOURCE_DIALOG_H_
/* Genode includes */
#include <os/reporter.h>
#include <depot/archive.h>
/* local includes */
#include <xml.h>
#include <model/component.h>
#include <view/dialog.h>
#include <view/selectable_item.h>
namespace Sculpt { struct Resource_dialog; }
struct Sculpt::Resource_dialog : Noncopyable, Deprecated_dialog
{
Affinity::Space const _space;
Affinity::Location _location;
Hoverable_item _space_item { };
Selectable_item _priority_item { };
Hoverable_item _option_item { };
bool _system_control = false;
static char const *_priority_id(Priority priority)
{
switch (priority) {
case Priority::DRIVER: return "driver";
case Priority::MULTIMEDIA: return "multimedia";
case Priority::BACKGROUND: return "background";
default: break;
};
return "default";
}
static Hoverable_item::Id _cpu_id(unsigned x, unsigned y)
{
return Hoverable_item::Id("cpu", x, "x", y);
}
Resource_dialog(Affinity::Space space, Affinity::Location location,
Priority priority)
:
_space(space), _location(location)
{
_priority_item.select(_priority_id(priority));
}
void _gen_affinity_section(Xml_generator &) const;
void _gen_priority_section(Xml_generator &) const;
Hover_result hover(Xml_node hover) override
{
return Deprecated_dialog::any_hover_changed(
_space_item.match(hover,
"vbox", "float", "hbox", /* _gen_dialog_section */
"vbox", "hbox", "vbox", "hbox", /* _gen_affinity_section */
"vbox", "button", "name" /* gen_cell_cpu */
),
_priority_item.match(hover,
"vbox", "float", "hbox", /* _gen_dialog_section */
"vbox", "hbox", "float", "hbox", "name"),
_option_item.match(hover, "vbox", "hbox", "name")
);
}
void click(Component &);
void _click_space (Component &, Hoverable_item::Id);
void _click_priority(Component &, Hoverable_item::Id);
template <typename FN>
void _gen_dialog_section(Xml_generator &xml, Hoverable_item::Id id,
char const *text, FN const &gen_content_fn) const
{
gen_named_node(xml, "float", id, [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "vbox", "title", [&] () {
xml.node("float", [&] () {
xml.attribute("north", "yes");
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
/*
* The button is used to vertically align the "Priority"
* label with the text of the first radio button.
* The leading space is used to horizontally align
* the label with the "Resource assignment ..." dialog
* title.
*/
gen_named_node(xml, "button", "spacer", [&] () {
xml.attribute("style", "invisible");
xml.node("hbox", [&] () { }); });
gen_named_node(xml, "label", "label", [&] () {
xml.attribute("text", String<32>(" ", text)); });
});
});
gen_named_node(xml, "label", "spacer", [&] () {
xml.attribute("min_ex", 11); });
});
gen_content_fn();
});
});
}
void _gen_option(Xml_generator &xml, auto const &name, auto const &text, bool selected) const
{
gen_named_node(xml, "hbox", name, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
/* align with the "Resource assignment ..." dialog */
gen_named_node(xml, "button", "left", [&] () {
xml.attribute("style", "invisible");
xml.node("hbox", [&] () { }); });
gen_named_node(xml, "button", "button", [&] () {
if (selected)
xml.attribute("selected", "yes");
xml.attribute("style", "checkbox");
_option_item.gen_hovered_attr(xml, name);
xml.node("hbox", [&] () { });
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", Path(" ", text)); });
});
});
gen_named_node(xml, "hbox", "right", [&] () { });
});
}
void generate(Xml_generator &xml) const override
{
auto gen_vspacer = [&] (auto id) {
gen_named_node(xml, "label", id, [&] () {
xml.attribute("text", "");
xml.attribute("font", "annotation/regular"); }); };
xml.node("vbox", [&] () {
gen_vspacer("spacer1");
_gen_affinity_section(xml);
gen_vspacer("spacer2");
_gen_priority_section(xml);
gen_vspacer("spacer3");
_gen_option(xml, "system_control", "System control", _system_control);
gen_vspacer("spacer4");
});
}
void reset() override
{
_space_item._hovered = Hoverable_item::Id();
_priority_item.reset();
_location = Affinity::Location();
_system_control = false;
_option_item._hovered = Hoverable_item::Id();
}
};
#endif /* _VIEW__RESOURCE_DIALOG_H_ */

View File

@ -0,0 +1,173 @@
/*
* \brief Resource-assignment widget
* \author Alexander Boettcher
* \author Norman Feske
* \date 2020-07-22
*/
/*
* Copyright (C) 2020-2021 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.
*/
#include <view/resource_widget.h>
using namespace Sculpt;
void Resource_widget::Affinity_selector::view(Scope<Vbox> &s,
Affinity::Space const &space,
Affinity::Location const &location) const
{
auto view_hyperthread_index = [] (auto &s, unsigned index) {
s.template sub_scope<Label>(String<8>(index), [&] (auto &s) {
s.attribute("font", "annotation/regular");
s.attribute("min_ex", 2); }); };
auto view_cell_hspacer = [] (auto &s) {
s.template sub_scope<Min_ex>(5); };
auto view_cell_cpu = [&] (auto &s, Id const &id, bool selected) {
s.template sub_scope<Vbox>(id, [&] (auto &s) {
view_cell_hspacer(s);
s.template sub_scope<Float>(id, [&] (auto &s) {
s.template sub_scope<Button>([&] (auto &s) {
s.attribute("style", "checkbox");
s.attribute("selected", selected);
s.template sub_scope<Hbox>(); }); }); }); };
auto view_cpu_index = [&] (auto &s, unsigned index) {
s.template sub_scope<Vbox>([&] (auto &s) {
s.template sub_scope<Annotation>(String<8>(index));
view_cell_hspacer(s); }); };
auto view_leftaligned = [] (auto &s, auto text) {
s.template sub_scope<Float>([&] (auto &s) {
s.attribute("west", "yes");
s.template sub_scope<Annotation>(text); }); };
auto selected = [] (Affinity::Location location, unsigned x, unsigned y)
{
return (unsigned(location.xpos()) <= x)
&& (x < location.xpos() + location.width())
&& (unsigned(location.ypos()) <= y)
&& (y < location.ypos() + location.height());
};
bool const have_hyperthreads = (space.height() > 1);
s.sub_scope<Hbox>([&] (Scope<Vbox, Hbox> &s) {
s.sub_scope<Vbox>([&] (Scope<Vbox, Hbox, Vbox> &s) {
for (unsigned y = 0; y < space.height(); y++) {
s.sub_scope<Hbox>(Id { { y } }, [&] (Scope<Vbox, Hbox, Vbox, Hbox> &s) {
for (unsigned x = 0; x < space.width(); x++)
view_cell_cpu(s, Id { Id::Value(x) }, selected(location, x, y));
if (have_hyperthreads)
s.sub_scope<Float>([&] (Scope<Vbox, Hbox, Vbox, Hbox, Float> &s) {
view_hyperthread_index(s, y ); });
});
}
});
if (have_hyperthreads)
s.sub_scope<Float>([&] (Scope<Vbox, Hbox, Float> &s) {
s.sub_scope<Vbox>([&] (Scope<Vbox, Hbox, Float, Vbox> &s) {
view_leftaligned(s, "Hyper");
view_leftaligned(s, "threads"); }); });
});
s.sub_scope<Float>([&] (Scope<Vbox, Float> &s) {
s.attribute("west", "yes");
s.sub_scope<Vbox>([&] (Scope<Vbox, Float, Vbox> &s) {
s.sub_scope<Hbox>([&] (Scope<Vbox, Float, Vbox, Hbox> &s) {
for (unsigned x = 0; x < space.width(); x++)
view_cpu_index(s, x);
});
s.sub_scope<Annotation>("Cores");
});
});
}
void Resource_widget::Affinity_selector::click(Clicked_at const &at,
Affinity::Space const &space,
Affinity::Location &location)
{
Id const x_id = at.matching_id<Vbox, Hbox, Vbox, Hbox, Vbox>(),
y_id = at.matching_id<Vbox, Hbox, Vbox, Hbox>();
unsigned x = ~0, y = ~0;
ascii_to(x_id.value.string(), x);
ascii_to(y_id.value.string(), y);
if (x >= space.width() || y >= space.height())
return;
unsigned loc_x = location.xpos();
unsigned loc_y = location.ypos();
unsigned loc_w = location.width();
unsigned loc_h = location.height();
bool handled_x = false;
bool handled_y = false;
if (x < loc_x) {
loc_w += loc_x - x;
loc_x = x;
handled_x = true;
} else if (x >= loc_x + loc_w) {
loc_w = x - loc_x + 1;
handled_x = true;
}
if (y < loc_y) {
loc_h += loc_y - y;
loc_y = y;
handled_y = true;
} else if (y >= loc_y + loc_h) {
loc_h = y - loc_y + 1;
handled_y = true;
}
if (handled_x && !handled_y) {
handled_y = true;
} else
if (handled_y && !handled_x) {
handled_x = true;
}
if (!handled_x) {
if ((x - loc_x) < (loc_x + loc_w - x)) {
if (x - loc_x + 1 < loc_w) {
loc_w -= x - loc_x + 1;
loc_x = x + 1;
} else {
loc_w = loc_x + loc_w - x;
loc_x = x;
}
} else {
loc_w = x - loc_x;
}
}
if (!handled_y) {
if ((y - loc_y) < (loc_y + loc_h - y)) {
if (y - loc_y + 1 < loc_h) {
loc_h -= y - loc_y + 1;
loc_y = y + 1;
} else {
loc_h = loc_y + loc_h - y;
loc_y = y;
}
} else {
loc_h = y - loc_y;
}
}
location = Affinity::Location(loc_x, loc_y, loc_w, loc_h);
}

View File

@ -0,0 +1,123 @@
/*
* \brief Resource assignment widget
* \author Norman Feske
* \date 2023-11-01
*/
/*
* Copyright (C) 2023 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 _VIEW__RESOURCE_WIDGET_H_
#define _VIEW__RESOURCE_WIDGET_H_
/* local includes */
#include <model/component.h>
#include <view/dialog.h>
namespace Sculpt { struct Resource_widget; }
struct Sculpt::Resource_widget : Widget<Vbox>
{
template <typename WIDGET>
struct Titled_widget : Widget<Left_floating_hbox>
{
Hosted<Left_floating_hbox, Vbox, WIDGET> _hosted;
Titled_widget(auto &&... args) : _hosted(Id { "hosted" }, args...) { }
void view(Scope<Left_floating_hbox> &s, auto const &text, auto &&... args) const
{
/* title */
s.sub_scope<Vbox>([&] (Scope<Left_floating_hbox, Vbox> &s) {
s.sub_scope<Top_left_floating_hbox>([&] (auto &s) {
/*
* The button is used to vertically align the "Priority"
* label with the text of the first radio button.
* The leading space is used to horizontally align
* the label with the "Resource assignment ..." dialog
* title.
*/
s.template sub_scope<Button>([&] (auto &s) {
s.attribute("style", "invisible");
s.sub_node("hbox", [&] () { }); });
s.template sub_scope<Label>(String<32>(" ", text));
});
s.sub_scope<Min_ex>(11);
});
s.sub_scope<Vbox>([&] (Scope<Left_floating_hbox, Vbox> &s) {
s.widget(_hosted, args...); });
s.sub_scope<Hbox>();
}
void click(auto &&... args) { _hosted.propagate(args...); }
};
struct Priority_selector : Widget<Vbox>
{
Hosted<Vbox, Radio_select_button<Priority>>
_buttons[4] { { Id { "Driver" }, Priority::DRIVER },
{ Id { "Multimedia" }, Priority::MULTIMEDIA },
{ Id { "Default" }, Priority::DEFAULT },
{ Id { "Background" }, Priority::BACKGROUND } };
void view(Scope<Vbox> &s, Priority const &priority) const
{
for (auto const &button : _buttons)
s.widget(button, priority);
}
void click(Clicked_at const &at, Priority &priority)
{
for (auto &button : _buttons)
button.propagate(at, [&] (Priority value) { priority = value; });
}
};
struct Affinity_selector : Widget<Vbox>
{
void view(Scope<Vbox> &, Affinity::Space const &, Affinity::Location const &) const;
void click(Clicked_at const &, Affinity::Space const &, Affinity::Location &);
};
Hosted<Vbox, Titled_widget<Affinity_selector>> _affinity { Id { "affinity" } };
Hosted<Vbox, Titled_widget<Priority_selector>> _priority { Id { "priority" } };
Hosted<Vbox, Menu_entry> _system { Id { "system control" } };
void view(Scope<Vbox> &s, Component const &component) const
{
s.sub_scope<Small_vgap>();
if (component.affinity_space.total() > 1) {
s.widget(_affinity, "Affinity", component.affinity_space, component.affinity_location);
s.sub_scope<Small_vgap>();
}
s.widget(_priority, "Priority", component.priority);
s.sub_scope<Small_vgap>();
s.widget(_system, component.system_control, "System control", "checkbox");
}
void click(Clicked_at const &at, Component &component)
{
if (component.affinity_space.total() > 1)
_affinity.propagate(at, component.affinity_space, component.affinity_location);
_priority.propagate(at, component.priority),
_system.propagate(at, [&] {
component.system_control = !component.system_control; });
}
};
#endif /* _VIEW__RESOURCE_WIDGET_H_ */

View File

@ -1,59 +0,0 @@
/*
* \brief GUI element that has a hovered and selected state
* \author Norman Feske
* \date 2018-05-03
*/
/*
* 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 _VIEW__SELECTABLE_ITEM_H_
#define _VIEW__SELECTABLE_ITEM_H_
#include "hoverable_item.h"
namespace Sculpt { struct Selectable_item; }
struct Sculpt::Selectable_item : Hoverable_item
{
typedef Hoverable_item::Id Id;
Id _selected { };
/**
* Apply click - if item is hovered, the click toggles the selection
*/
void toggle_selection_on_click()
{
if (_hovered.valid())
_selected = (_hovered == _selected) ? Id() : _hovered;
}
void select(Id const &id) { _selected = id; }
void reset() { _selected = Id{}; }
/**
* Return true if item is currently selected
*/
bool selected(Id const &id) const { return id == _selected; }
bool any_selected() const { return _selected.valid(); }
/**
* Generate button attributes depending on the item state
*/
void gen_button_attr(Xml_generator &xml, Id const &id) const
{
Hoverable_item::gen_button_attr(xml, id);
if (selected(id)) xml.attribute("selected", "yes");
}
};
#endif /* _SELECTABLE_ITEM_H_ */

View File

@ -1,138 +0,0 @@
/*
* \brief Settings dialog
* \author Norman Feske
* \date 2020-01-30
*/
/*
* Copyright (C) 2020 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 _VIEW__SETTINGS_DIALOG_H_
#define _VIEW__SETTINGS_DIALOG_H_
#include <view/dialog.h>
#include <view/radio_choice_dialog.h>
#include <model/settings.h>
namespace Sculpt { struct Settings_dialog; }
struct Sculpt::Settings_dialog : Noncopyable, Deprecated_dialog
{
Settings const &_settings;
Hoverable_item _section { };
Radio_choice_dialog::Min_ex const _ratio { .left = 10, .right = 24 };
Radio_choice_dialog _font_size_choice { "Font size", _ratio };
Radio_choice_dialog _keyboard_layout_choice { "Keyboard", _ratio };
static Radio_choice_dialog::Id _font_size_id(Settings::Font_size font_size)
{
switch (font_size) {
case Settings::Font_size::SMALL: return "Small";
case Settings::Font_size::MEDIUM: return "Medium";
case Settings::Font_size::LARGE: return "Large";
}
return Radio_choice_dialog::Id();
}
static Settings::Font_size _font_size(Radio_choice_dialog::Id id)
{
if (id == "Small") return Settings::Font_size::SMALL;
if (id == "Medium") return Settings::Font_size::MEDIUM;
if (id == "Large") return Settings::Font_size::LARGE;
return Settings::Font_size::MEDIUM;
}
Hover_result hover(Xml_node hover) override
{
return any_hover_changed(
_section.match(hover, "frame", "vbox", "hbox", "name"),
_font_size_choice.match_sub_dialog(hover, "frame", "vbox"),
_keyboard_layout_choice.match_sub_dialog(hover, "frame", "vbox"));
}
void reset() override { }
struct Action : Interface, Noncopyable
{
virtual void select_font_size(Settings::Font_size) = 0;
virtual void select_keyboard_layout(Settings::Keyboard_layout::Name const &) = 0;
};
void generate(Xml_generator &xml) const override
{
gen_named_node(xml, "frame", "settings", [&] () {
xml.node("vbox", [&] () {
using Choice = Radio_choice_dialog::Choice;
if (!_settings.manual_fonts_config) {
_font_size_choice.generate(xml, _font_size_id(_settings.font_size),
[&] (Choice const &choice) {
choice.generate("Small");
choice.generate("Medium");
choice.generate("Large");
});
}
if (!_settings.manual_event_filter_config) {
_keyboard_layout_choice.generate(xml, _settings.keyboard_layout,
[&] (Choice const &choice) {
using Keyboard_layout = Settings::Keyboard_layout;
Keyboard_layout::for_each([&] (Keyboard_layout const &layout) {
choice.generate(layout.name); });
});
}
});
});
}
Click_result click(Action &action)
{
_font_size_choice.reset();
_keyboard_layout_choice.reset();
Click_result result = Click_result::IGNORED;
auto handle_section = [&] (Radio_choice_dialog &dialog, auto fn_clicked)
{
if (result == Click_result::CONSUMED || !_section.hovered(dialog._id))
return;
/* unfold radio choice */
dialog.click();
auto selection = dialog.hovered_choice();
if (selection == "")
return;
fn_clicked(selection);
result = Click_result::CONSUMED;
};
handle_section(_font_size_choice, [&] (auto selection) {
action.select_font_size(_font_size(selection)); });
handle_section(_keyboard_layout_choice, [&] (auto selection) {
using Keyboard_layout = Settings::Keyboard_layout;
Keyboard_layout::for_each([&] (Keyboard_layout const &layout) {
if (selection == layout.name)
action.select_keyboard_layout(selection); }); });
return result;
}
Settings_dialog(Settings const &settings) : _settings(settings) { }
};
#endif /* _VIEW__SETTINGS_DIALOG_H_ */

View File

@ -0,0 +1,122 @@
/*
* \brief Settings widget
* \author Norman Feske
* \date 2020-01-30
*/
/*
* Copyright (C) 2020-2023 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 _VIEW__SETTINGS_WIDGET_H_
#define _VIEW__SETTINGS_WIDGET_H_
#include <view/dialog.h>
#include <model/settings.h>
namespace Sculpt { struct Settings_widget; }
struct Sculpt::Settings_widget : Widget<Vbox>
{
Settings const &_settings;
enum class Selected_section { NONE, FONT_SIZE, KEYBAORD };
Selected_section _selected_section = Selected_section::NONE;
using Font_size = Settings::Font_size;
using Keyboard_layout = Settings::Keyboard_layout;
static Id _font_size_id(Font_size font_size)
{
switch (font_size) {
case Font_size::SMALL: return { "Small" };
case Font_size::MEDIUM: return { "Medium" };
case Font_size::LARGE: return { "Large" };
}
return { };
}
struct Font_size_radio : Hosted<Radio_select_button<Font_size>>
{
Font_size_radio(Font_size s)
: Hosted<Radio_select_button<Font_size>>(_font_size_id(s), s) { };
};
Font_size_radio const _font_size_items[3] {
Font_size::SMALL, Font_size::MEDIUM, Font_size::LARGE };
using Keyboard_radio = Hosted<Radio_select_button<Keyboard_layout::Name>>;
using Hosted_choice = Hosted<Vbox, Choice<Selected_section>>;
Hosted_choice const
_font_size_choice { Id { "Font size" }, Selected_section::FONT_SIZE },
_keyboard_layout_choice { Id { "Keyboard" }, Selected_section::KEYBAORD };
Settings_widget(Settings const &settings) : _settings(settings) { }
void view(Scope<Vbox> &s) const
{
unsigned const left_ex = 10, right_ex = 24;
if (!_settings.manual_fonts_config) {
Font_size const selected = _settings.font_size;
s.widget(_font_size_choice,
Hosted_choice::Attr {
.left_ex = left_ex, .right_ex = right_ex,
.unfolded = _selected_section,
.selected_item = _font_size_id(selected)
},
[&] (Hosted_choice::Sub_scope &s) {
for (auto const &item : _font_size_items)
s.widget(item, selected);
});
}
if (!_settings.manual_event_filter_config) {
s.widget(_keyboard_layout_choice,
Hosted_choice::Attr {
.left_ex = left_ex, .right_ex = right_ex,
.unfolded = _selected_section,
.selected_item = _settings.keyboard_layout
},
[&] (Hosted_choice::Sub_scope &s) {
Keyboard_layout::for_each([&] (Keyboard_layout const &layout) {
s.widget(Keyboard_radio { Id { layout.name }, layout.name },
_settings.keyboard_layout);
});
});
}
}
struct Action : Interface, Noncopyable
{
virtual void select_font_size(Font_size) = 0;
virtual void select_keyboard_layout(Keyboard_layout::Name const &) = 0;
};
void click(Clicked_at const &at, Action &action)
{
_font_size_choice.propagate(at, _selected_section,
[&] { _selected_section = Selected_section::NONE; },
[&] (Clicked_at const &at) {
for (auto &item : _font_size_items)
item.propagate(at, [&] (Font_size s) {
action.select_font_size(s); });
});
_keyboard_layout_choice.propagate(at, _selected_section,
[&] { _selected_section = Selected_section::NONE; },
[&] (Clicked_at const &at) {
Id const id = at.matching_id<Keyboard_radio>();
action.select_keyboard_layout(id.value);
});
}
};
#endif /* _VIEW__SETTINGS_WIDGET_H_ */

View File

@ -1,162 +0,0 @@
/*
* \brief Dialog for the deploy presets
* \author Norman Feske
* \date 2023-01-11
*/
/*
* Copyright (C) 2023 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 _VIEW__SOFTWARE_PRESETS_DIALOG_H_
#define _VIEW__SOFTWARE_PRESETS_DIALOG_H_
#include <model/presets.h>
#include <view/dialog.h>
#include <string.h>
namespace Sculpt { struct Software_presets_dialog; }
struct Sculpt::Software_presets_dialog : Widget<Float>
{
Presets const &_presets;
struct Action : Interface
{
virtual void load_deploy_preset(Presets::Info::Name const &) = 0;
};
Action &_action;
using Hover_result = Hoverable_item::Hover_result;
Presets::Info::Name _selected { };
Hoverable_item _item { };
Activatable_item _operation { };
Software_presets_dialog(Presets const &presets, Action &action)
:
_presets(presets), _action(action)
{ }
using Name = Presets::Info::Name;
void _gen_horizontal_spacer(Xml_generator &xml) const
{
gen_named_node(xml, "label", "spacer", [&] {
xml.attribute("min_ex", 35); });
}
void _gen_preset(Xml_generator &xml, Presets::Info const &preset) const
{
gen_named_node(xml, "vbox", preset.name, [&] () {
gen_named_node(xml, "hbox", preset.name, [&] () {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", "yes");
xml.node("hbox", [&] () {
gen_named_node(xml, "float", "radio", [&] () {
gen_named_node(xml, "button", "button", [&] () {
_item.gen_hovered_attr(xml, preset.name);
if (_selected == preset.name)
xml.attribute("selected", "yes");
xml.attribute("style", "radio");
xml.node("hbox", [&] () { });
});
});
gen_named_node(xml, "label", "name", [&] () {
xml.attribute("text", Name(" ", Pretty(preset.name))); });
gen_item_vspace(xml, "vspace");
});
});
});
if (_selected != preset.name)
return;
auto vspacer = [&] (auto name)
{
gen_named_node(xml, "label", name, [&] () {
xml.attribute("text", " "); });
};
vspacer("spacer1");
gen_named_node(xml, "float", "info", [&] () {
gen_named_node(xml, "label", "text", [&] () {
xml.attribute("text", preset.text); }); });
vspacer("spacer2");
gen_named_node(xml, "float", "operations", [&] () {
gen_named_node(xml, "button", "load", [&] () {
_operation.gen_button_attr(xml, "load");
gen_named_node(xml, "label", "text", [&] () {
xml.attribute("text", " Load "); });
});
});
vspacer("spacer3");
});
}
void generate(Xml_generator &xml) const
{
if (_presets.available())
gen_named_node(xml, "float", "presets", [&] {
xml.node("frame", [&] {
xml.node("vbox", [&] {
_gen_horizontal_spacer(xml);
_presets.for_each([&] (Presets::Info const &info) {
_gen_preset(xml, info); }); }); }); });
}
Hover_result hover(Xml_node hover)
{
return Deprecated_dialog::any_hover_changed(
_item.match (hover, "float", "frame", "vbox", "vbox", "hbox", "name"),
_operation.match(hover, "float", "frame", "vbox", "vbox", "float", "button", "name")
);
}
bool hovered() const { return _item._hovered.valid(); }
void click()
{
if (_item._hovered.length() > 1)
_selected = _item._hovered;
if (_operation.hovered("load"))
_operation.propose_activation_on_click();
}
void clack()
{
if (_selected.length() <= 1)
return;
_operation.confirm_activation_on_clack();
if (_operation.activated("load")) {
_action.load_deploy_preset(_selected);
_selected = { };
}
_operation.reset();
}
};
#endif /* _VIEW__SOFTWARE_PRESETS_DIALOG_H_ */

View File

@ -0,0 +1,116 @@
/*
* \brief Widget for the deploy presets
* \author Norman Feske
* \date 2023-01-11
*/
/*
* Copyright (C) 2023 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 _VIEW__SOFTWARE_PRESETS_WIDGET_H_
#define _VIEW__SOFTWARE_PRESETS_WIDGET_H_
#include <model/presets.h>
#include <view/dialog.h>
#include <string.h>
namespace Sculpt { struct Software_presets_widget; }
struct Sculpt::Software_presets_widget : Widget<Float>
{
using Name = Presets::Info::Name;
Name _selected { };
struct Preset : Widget<Vbox>
{
using Radio = Hosted<Vbox, Radio_select_button<Name>>;
using Load = Hosted<Vbox, Float, Deferred_action_button>;
void view(Scope<Vbox> &s, Presets::Info const &preset, Load const &load,
Name const &selected) const
{
s.widget(Radio { Id { preset.name }, preset.name },
selected, Name { " ", Pretty(preset.name) });
if (selected != preset.name)
return;
s.sub_scope<Vgap>();
s.sub_scope<Float>([&] (Scope<Vbox, Float> &s) {
s.sub_scope<Label>(preset.text); });
s.sub_scope<Vgap>();
s.sub_scope<Float>([&] (Scope<Vbox, Float> &s) {
s.widget(load); });
s.sub_scope<Vgap>();
}
void click(Clicked_at const &at, Load &load) { load.propagate(at); }
void clack(Clacked_at const &at, Load &load, auto const &fn)
{
load.propagate(at, fn);
}
};
/* use one button to preserve 'Deferred_action_button' state across presets */
Preset::Load _load { Id { " Load " } };
using Hosted_preset = Hosted<Float, Frame, Vbox, Preset>;
void view(Scope<Float> &s, Presets const &presets) const
{
s.sub_scope<Frame>([&] (Scope<Float, Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Float, Frame, Vbox> &s) {
s.sub_scope<Min_ex>(35);
presets.for_each([&] (Presets::Info const &info) {
Hosted_preset const hosted { Id { info.name } };
s.widget(hosted, info, _load, _selected); }); }); });
}
void _with_selected_preset(Presets const &presets, auto const &fn)
{
presets.for_each([&] (Presets::Info const &info) {
if (info.name == _selected)
fn(info); });
}
void click(Clicked_at const &at, Presets const &presets)
{
/* unfold preset info */
Id const id = at.matching_id<Float, Frame, Vbox, Vbox>();
if (id.valid())
_selected = id.value;
_with_selected_preset(presets, [&] (Presets::Info const &info) {
Hosted_preset hosted { Id { info.name } };
hosted.propagate(at, _load); });
}
struct Action : Interface
{
virtual void load_deploy_preset(Presets::Info::Name const &) = 0;
};
void clack(Clacked_at const &at, Presets const &presets, Action &action)
{
_with_selected_preset(presets, [&] (Presets::Info const &info) {
Hosted_preset hosted { Id { info.name } };
hosted.propagate(at, _load, [&] {
action.load_deploy_preset(_selected);
_selected = { };
});
});
}
};
#endif /* _VIEW__SOFTWARE_PRESETS_WIDGET_H_ */

View File

@ -1,369 +0,0 @@
/*
* \brief Dialog for software update
* \author Norman Feske
* \date 2023-01-23
*/
/*
* Copyright (C) 2023 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 _VIEW__SOFTWARE_UPDATE_DIALOG_H_
#define _VIEW__SOFTWARE_UPDATE_DIALOG_H_
#include <model/nic_state.h>
#include <model/build_info.h>
#include <model/download_queue.h>
#include <model/index_update_queue.h>
#include <view/depot_users_dialog.h>
namespace Sculpt { struct Software_update_dialog; }
struct Sculpt::Software_update_dialog : Widget<Vbox>
{
using Depot_users = Depot_users_dialog::Depot_users;
using User = Depot_users_dialog::User;
using Image_index = Attached_rom_dataspace;
using Url = Depot_users_dialog::Url;
using Version = String<16>;
using User_properties = Depot_users_dialog::User_properties;
Build_info const _build_info;
Nic_state const &_nic_state;
Download_queue const &_download_queue;
Index_update_queue const &_index_update_queue;
File_operation_queue const &_file_operation_queue;
Image_index const &_image_index;
struct Action : Interface
{
virtual void query_image_index (User const &) = 0;
virtual void trigger_image_download(Path const &, Verify) = 0;
virtual void update_image_index (User const &, Verify) = 0;
virtual void install_boot_image (Path const &) = 0;
};
Action &_action;
Depot_users_dialog _users;
Path _last_installed { };
Path _last_selected { };
Path _index_path() const { return Path(_users.selected(), "/image/index"); }
bool _index_update_in_progress() const
{
using Update = Index_update_queue::Update;
bool result = false;
_index_update_queue.with_update(_index_path(), [&] (Update const &update) {
if (update.active())
result = true; });
return result;
}
Path _image_path(Version const &version) const
{
return Path(_users.selected(), "/image/sculpt-", _build_info.board, "-", version);
}
bool _installing() const
{
return _file_operation_queue.copying_to_path("/rw/boot");
};
Hoverable_item _check { };
Hoverable_item _version { };
Hoverable_item _operation { };
using Hover_result = Hoverable_item::Hover_result;
Software_update_dialog(Build_info const &build_info,
Nic_state const &nic_state,
Download_queue const &download_queue,
Index_update_queue const &index_update_queue,
File_operation_queue const &file_operation_queue,
Depot_users const &depot_users,
Image_index const &image_index,
Depot_users_dialog::Action &depot_users_action,
Action &action)
:
_build_info(build_info), _nic_state(nic_state),
_download_queue(download_queue),
_index_update_queue(index_update_queue),
_file_operation_queue(file_operation_queue),
_image_index(image_index),
_action(action),
_users(depot_users, _build_info.depot_user, depot_users_action)
{ }
static void _gen_vspacer(Xml_generator &xml, char const *name)
{
gen_named_node(xml, "label", name, [&] () {
xml.attribute("text", " ");
xml.attribute("font", "annotation/regular");
});
}
void _gen_image_main(Xml_generator &xml, Xml_node const &image) const
{
Version const version = image.attribute_value("version", Version());
bool const present = image.attribute_value("present", false);
Path const path = _image_path(version);
struct Download_state
{
bool in_progress;
bool failed;
unsigned percent;
};
using Download = Download_queue::Download;
auto state_from_download_queue = [&]
{
Download_state result { };
_download_queue.with_download(path, [&] (Download const &download) {
if (download.state == Download::State::DOWNLOADING)
result.in_progress = true;
if (download.state == Download::State::FAILED)
result.failed = true;
result.percent = download.percent;
});
return result;
};
Download_state const download_state = state_from_download_queue();
gen_named_node(xml, "float", "label", [&] {
xml.attribute("west", "yes");
gen_named_node(xml, "label", "label", [&] {
xml.attribute("text", String<50>(" ", version));
xml.attribute("min_ex", "15");
});
});
auto gen_status = [&] (auto message)
{
gen_named_node(xml, "float", "status", [&] {
xml.node("label", [&] {
xml.attribute("font", "annotation/regular");
xml.attribute("text", message); }); });
};
if (image.has_sub_node("info")) {
if (_last_selected == path)
gen_status("Changes");
else
gen_status("...");
}
if (download_state.in_progress && download_state.percent)
gen_status(String<16>(download_state.percent, "%"));
if (download_state.failed)
gen_status("unavailable");
if (_last_installed == path) {
if (_installing())
gen_status("installing...");
else
gen_status("reboot to activate");
}
gen_named_node(xml, "float", "buttons", [&] {
xml.attribute("east", "yes");
xml.node("hbox", [&] {
auto gen_button = [&] (auto id, bool selected, auto text)
{
gen_named_node(xml, "button", id, [&] {
if (version == _version._hovered)
_operation.gen_hovered_attr(xml, id);
if (selected) {
xml.attribute("selected", "yes");
xml.attribute("style", "unimportant");
}
xml.node("label", [&] {
xml.attribute("text", text); });
});
};
if (present)
gen_button("install", _installing(), " Install ");
if (!present)
gen_button("download", download_state.in_progress, " Download ");
});
});
}
void _gen_image_info(Xml_generator &xml, Xml_node const &image) const
{
gen_named_node(xml, "vbox", "main", [&] {
unsigned line = 0;
image.for_each_sub_node("info", [&] (Xml_node const &info) {
/* limit changelog to a sensible maximum of lines */
if (++line > 8)
return;
using Text = String<80>;
Text const text = info.attribute_value("text", Text());
gen_named_node(xml, "float", String<16>(line), [&] {
xml.attribute("west", "yes");
xml.node("label", [&] {
xml.attribute("text", text);
xml.attribute("font", "annotation/regular");
});
});
});
});
}
void _gen_image_entry(Xml_generator &xml, Xml_node const &image) const
{
Version const version = image.attribute_value("version", Version());
Path const path = _image_path(version);
gen_named_node(xml, "frame", version, [&] {
xml.attribute("style", "important");
xml.node("vbox", [&] {
gen_named_node(xml, "float", "main", [&] {
xml.attribute("east", "yes");
xml.attribute("west", "yes");
_gen_image_main(xml, image);
});
if (path == _last_selected && image.has_sub_node("info")) {
_gen_vspacer(xml, "above");
gen_named_node(xml, "float", "info", [&] {
_gen_image_info(xml, image); });
_gen_vspacer(xml, "below");
}
});
});
}
void _gen_image_list(Xml_generator &xml) const
{
Xml_node const index = _image_index.xml();
index.for_each_sub_node("user", [&] (Xml_node const &user) {
if (user.attribute_value("name", User()) == _users.selected())
user.for_each_sub_node("image", [&] (Xml_node const &image) {
_gen_image_entry(xml, image); }); });
}
void _gen_update_dialog(Xml_generator &xml) const
{
gen_named_node(xml, "frame", "update_dialog", [&] {
xml.node("vbox", [&] {
_users.generate(xml);
User_properties const properties = _users.selected_user_properties();
bool const offer_index_update = _users.one_selected()
&& _nic_state.ready()
&& properties.download_url;
if (offer_index_update) {
_gen_vspacer(xml, "above check");
gen_named_node(xml, "float", "check", [&] {
gen_named_node(xml, "button", "check", [&] {
_check.gen_hovered_attr(xml, "check");
if (_index_update_in_progress()) {
xml.attribute("selected", "yes");
xml.attribute("style", "unimportant");
}
xml.node("label", [&] {
auto const text = properties.public_key
? " Check for Updates "
: " Check for unverified Updates ";
xml.attribute("text", text); });
});
});
_gen_vspacer(xml, "below check");
}
});
});
_gen_image_list(xml);
}
void generate(Xml_generator &xml) const
{
gen_named_node(xml, "vbox", "update", [&] {
_gen_update_dialog(xml); });
}
Hover_result hover(Xml_node const &hover)
{
_users.reset_hover();
return Deprecated_dialog::any_hover_changed(
match_sub_dialog(hover, _users, "vbox", "frame", "vbox"),
_check .match(hover, "vbox", "frame", "vbox", "float", "button", "name"),
_version .match(hover, "vbox", "frame", "name"),
_operation.match(hover, "vbox", "frame", "vbox", "float", "float", "hbox", "button", "name")
);
}
bool hovered() const { return _users.hovered(); }
void click()
{
Verify const verify { _users.selected_user_properties().public_key };
if (_users.hovered())
_users.click([&] (User const &selected_user) {
_action.query_image_index(selected_user); });
if (_check.hovered("check") && !_index_update_in_progress())
_action.update_image_index(_users.selected(), verify);
if (_operation.hovered("download"))
_action.trigger_image_download(_image_path(_version._hovered), verify);
if (_version._hovered.length() > 1)
_last_selected = _image_path(_version._hovered);
if (_operation.hovered("install") && !_installing()) {
_last_installed = _image_path(_version._hovered);
_action.install_boot_image(_last_installed);
}
}
void clack() { }
bool keyboard_needed() const { return _users.keyboard_needed(); }
void handle_key(Codepoint c) { _users.handle_key(c); }
void sanitize_user_selection() { _users.sanitize_unfold_state(); }
void reset()
{
_last_installed = { };
_last_selected = { };
}
};
#endif /* _VIEW__SOFTWARE_UPDATE_DIALOG_H_ */

View File

@ -0,0 +1,307 @@
/*
* \brief Widget for software update
* \author Norman Feske
* \date 2023-01-23
*/
/*
* Copyright (C) 2023 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 _VIEW__SOFTWARE_UPDATE_WIDGET_H_
#define _VIEW__SOFTWARE_UPDATE_WIDGET_H_
#include <model/nic_state.h>
#include <model/build_info.h>
#include <model/download_queue.h>
#include <model/index_update_queue.h>
#include <view/depot_users_widget.h>
namespace Sculpt { struct Software_update_widget; }
struct Sculpt::Software_update_widget : Widget<Vbox>
{
using Depot_users = Depot_users_widget::Depot_users;
using User = Depot_users_widget::User;
using Image_index = Attached_rom_dataspace;
using Version = String<16>;
Build_info const _build_info;
Nic_state const &_nic_state;
Download_queue const &_download_queue;
Index_update_queue const &_index_update_queue;
File_operation_queue const &_file_operation_queue;
Image_index const &_image_index;
Path _last_installed { };
Path _last_selected { };
Hosted<Vbox, Frame, Vbox, Depot_users_widget> _users;
Path _index_path() const { return Path(_users.selected(), "/image/index"); }
bool _index_update_in_progress() const
{
using Update = Index_update_queue::Update;
bool result = false;
_index_update_queue.with_update(_index_path(), [&] (Update const &update) {
if (update.active())
result = true; });
return result;
}
Path _image_path(Version const &version) const
{
return Path(_users.selected(), "/image/sculpt-", _build_info.board, "-", version);
}
bool _installing() const
{
return _file_operation_queue.copying_to_path("/rw/boot");
}
struct Download_state
{
bool in_progress;
bool failed;
unsigned percent;
static Download_state from_queue(Download_queue const &queue, Path const &path)
{
using Download = Download_queue::Download;
Download_state result { };
queue.with_download(path, [&] (Download const &download) {
result = {
.in_progress = (download.state == Download::State::DOWNLOADING),
.failed = (download.state == Download::State::FAILED),
.percent = download.percent
};
});
return result;
}
};
struct Image_main : Widget<Float>
{
struct Attr
{
Version version;
Path path;
bool present;
bool changelog;
Download_state download_state;
bool selected;
bool last_installed;
bool installing;
};
Hosted<Float, Right_floating_hbox, Operation_button> const
_install { Id { "Install" } },
_download { Id { "Download" } };
void view(Scope<Float> &s, Attr const &attr) const
{
s.attribute("east", "yes");
s.attribute("west", "yes");
s.sub_scope<Left_floating_text>(attr.version);
auto status_message = [] (Attr const &attr) -> String<50>
{
if (attr.last_installed)
return { attr.installing ? "installing..." : "reboot to activate" };
if (attr.download_state.failed)
return { "unavailable" };
if (attr.download_state.in_progress && attr.download_state.percent)
return { attr.download_state.percent, "%" };
if (attr.changelog)
return { attr.selected ? "Changes" : "..." };
return { };
};
s.sub_scope<Float>([&] (Scope<Float, Float> &s) {
s.sub_scope<Annotation>(status_message(attr)); });
s.sub_scope<Right_floating_hbox>([&] (Scope<Float, Right_floating_hbox> &s) {
if (attr.present)
s.widget(_install, attr.installing);
else
s.widget(_download, attr.download_state.in_progress);
});
}
void click(Clicked_at const &at,
auto const &install_fn, auto const &download_fn) const
{
_install .propagate(at, install_fn);
_download.propagate(at, download_fn);
}
};
struct Changelog : Widget<Float>
{
void view(Scope<Float> &s, Xml_node const &image) const
{
using Text = String<80>;
unsigned line = 0; /* limit changelog to a sensible max. of lines */
s.sub_scope<Vbox>([&] (Scope<Float, Vbox> &s) {
s.sub_scope<Small_vgap>();
image.for_each_sub_node("info", [&] (Xml_node const &info) {
if (++line < 9)
s.sub_scope<Left_annotation>(info.attribute_value("text", Text()));
});
s.sub_scope<Small_vgap>();
});
}
};
/* used accross images */
Hosted<Vbox, Frame, Vbox, Image_main> const _hosted_image_main { Id { "main" } };
void _view_image_entry(Scope<Vbox> &s, Xml_node const &image) const
{
Version const version = image.attribute_value("version", Version());
Path const path = _image_path(version);
Image_main::Attr const attr {
.version = version,
.path = path,
.present = image.attribute_value("present", false),
.changelog = image.has_sub_node("info"),
.download_state = Download_state::from_queue(_download_queue, path),
.selected = (_last_selected == path),
.last_installed = (_last_installed == path),
.installing = _installing(),
};
s.sub_scope<Frame>(Id { version }, [&] (Scope<Vbox, Frame> &s) {
s.attribute("style", "important");
s.sub_scope<Vbox>([&] (Scope<Vbox, Frame, Vbox> &s) {
s.widget(_hosted_image_main, attr);
if (path == _last_selected && attr.changelog)
s.widget(Hosted<Vbox, Frame, Vbox, Changelog> { Id { "changes" } },
image);
});
});
}
Hosted<Vbox, Frame, Vbox, Float, Operation_button> _check { Id { "check" } };
Software_update_widget(Build_info const &build_info,
Nic_state const &nic_state,
Download_queue const &download_queue,
Index_update_queue const &index_update_queue,
File_operation_queue const &file_operation_queue,
Depot_users const &depot_users,
Image_index const &image_index)
:
_build_info(build_info), _nic_state(nic_state),
_download_queue(download_queue),
_index_update_queue(index_update_queue),
_file_operation_queue(file_operation_queue),
_image_index(image_index),
_users(Id { "users" }, depot_users, _build_info.depot_user)
{ }
void view(Scope<Vbox> &s, Xml_node const &image_index) const
{
/* use empty ID to not interfere with matching the version in 'click'*/
s.sub_scope<Frame>(Id { }, [&] (Scope<Vbox, Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Vbox, Frame, Vbox> &s) {
s.widget(_users);
Depot_users_widget::User_properties const
properties = _users.selected_user_properties();
bool const offer_index_update = _users.one_selected()
&& _nic_state.ready()
&& properties.download_url;
if (!offer_index_update)
return;
s.sub_scope<Small_vgap>();
s.sub_scope<Float>([&] (Scope<Vbox, Frame, Vbox, Float> &s) {
s.widget(_check, _index_update_in_progress(),
properties.public_key ? "Check for Updates"
: "Check for unverified Updates");
});
s.sub_scope<Small_vgap>();
});
});
image_index.for_each_sub_node("user", [&] (Xml_node const &user) {
if (user.attribute_value("name", User()) == _users.selected())
user.for_each_sub_node("image", [&] (Xml_node const &image) {
_view_image_entry(s, image); }); });
}
struct Action : virtual Depot_users_widget::Action
{
virtual void query_image_index (User const &) = 0;
virtual void trigger_image_download(Path const &, Verify) = 0;
virtual void update_image_index (User const &, Verify) = 0;
virtual void install_boot_image (Path const &) = 0;
};
void click(Clicked_at const &at, Action &action)
{
_users.propagate(at, action, [&] (User const &selected_user) {
action.query_image_index(selected_user); });
Verify const verify { _users.selected_user_properties().public_key };
if (!_index_update_in_progress())
_check.propagate(at, [&] {
action.update_image_index(_users.selected(), verify); });
Id const version = at.matching_id<Vbox, Frame>();
Path const path = _image_path(version.value);
if (version.valid()) {
_last_selected = path;
_hosted_image_main.propagate(at,
[&] /* install */ {
if (!_installing()) {
action.install_boot_image(path);
_last_installed = path;
}
},
[&] /* download */ {
action.trigger_image_download(path, verify);
});
}
}
bool keyboard_needed() const { return _users.keyboard_needed(); }
void handle_key(Codepoint c, Action &action) { _users.handle_key(c, action); }
void sanitize_user_selection() { _users.sanitize_unfold_state(); }
void reset()
{
_last_installed = { };
_last_selected = { };
}
};
#endif /* _VIEW__SOFTWARE_UPDATE_WIDGET_H_ */

View File

@ -1,47 +0,0 @@
/*
* \brief Dialog for showing the system version
* \author Norman Feske
* \date 2023-01-23
*/
/*
* Copyright (C) 2023 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 _VIEW__SOFTWARE_VERSION_DIALOG_H_
#define _VIEW__SOFTWARE_VERSION_DIALOG_H_
#include <model/build_info.h>
#include <view/dialog.h>
namespace Sculpt { struct Software_version_dialog; }
struct Sculpt::Software_version_dialog : Widget<Frame>
{
Build_info const _build_info;
Software_version_dialog(Build_info const &info) : _build_info(info) { }
void generate(Xml_generator &xml) const
{
using Version = Build_info::Version;
auto padded = [] (Version const &v) { return Version(" ", v, " "); };
gen_named_node(xml, "frame", "version", [&] {
xml.node("vbox", [&] {
gen_named_node(xml, "label", "image", [&] {
xml.attribute("text", padded(_build_info.image_version())); });
gen_named_node(xml, "label", "genode", [&] {
xml.attribute("text", padded(_build_info.genode_version()));
xml.attribute("font", "annotation/regular");
});
});
});
}
};
#endif /* _VIEW__SOFTWARE_VERSION_DIALOG_H_ */

View File

@ -0,0 +1,38 @@
/*
* \brief Widget for showing the system version
* \author Norman Feske
* \date 2023-01-23
*/
/*
* Copyright (C) 2023 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 _VIEW__SOFTWARE_VERSION_WIDGET_H_
#define _VIEW__SOFTWARE_VERSION_WIDGET_H_
#include <model/build_info.h>
#include <view/dialog.h>
namespace Sculpt { struct Software_version_widget; }
struct Sculpt::Software_version_widget : Widget<Frame>
{
void view(Scope<Frame> &s, Build_info const &info) const
{
using Version = Build_info::Version;
auto padded = [] (Version const &v) { return Version(" ", v, " "); };
s.sub_scope<Vbox>([&] (Scope<Frame, Vbox> &s) {
s.sub_scope<Label> (padded(info.image_version()));
s.sub_scope<Annotation>(padded(info.genode_version()));
});
}
};
#endif /* _VIEW__SOFTWARE_VERSION_WIDGET_H_ */

View File

@ -1,21 +1,18 @@
/*
* \brief Storage-device management dialog
* \brief Storage-device management widget
* \author Norman Feske
* \date 2018-04-30
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2018-2023 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.
*/
/* Genode includes */
#include <base/log.h>
/* local includes */
#include "storage_device_dialog.h"
#include <view/storage_device_widget.h>
using namespace Sculpt;
@ -27,8 +24,6 @@ struct Dialog::Partition_button : Widget<Hbox>
void view(Scope<Hbox> &s, bool selected, Storage_target const &used_target,
Storage_device const &device, Partition const &partition) const
{
using Label = Dialog::Label;
bool const hovered = s.hovered();
s.sub_scope<Left_floating_hbox>([&] (Scope<Hbox, Left_floating_hbox> &s) {
@ -53,7 +48,7 @@ struct Dialog::Partition_button : Widget<Hbox>
};
void Storage_device_dialog::view(Scope<Vbox> &s, Storage_device const &dev,
void Storage_device_widget::view(Scope<Vbox> &s, Storage_device const &dev,
Storage_target const &used_target) const
{
dev.partitions.for_each([&] (Partition const &partition) {

View File

@ -1,5 +1,5 @@
/*
* \brief Storage-device management dialog
* \brief Storage-device management widget
* \author Norman Feske
* \date 2020-01-29
*/
@ -11,16 +11,16 @@
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _VIEW__STORAGE_DEVICE_DIALOG_H_
#define _VIEW__STORAGE_DEVICE_DIALOG_H_
#ifndef _VIEW__STORAGE_DEVICE_WIDGET_H_
#define _VIEW__STORAGE_DEVICE_WIDGET_H_
#include <types.h>
#include <view/partition_operations.h>
namespace Sculpt { struct Storage_device_dialog; }
namespace Sculpt { struct Storage_device_widget; }
struct Sculpt::Storage_device_dialog : Widget<Vbox>
struct Sculpt::Storage_device_widget : Widget<Vbox>
{
Partition::Number _selected_partition { };
@ -63,4 +63,4 @@ struct Sculpt::Storage_device_dialog : Widget<Vbox>
}
};
#endif /* _VIEW__STORAGE_DEVICE_DIALOG_H_ */
#endif /* _VIEW__STORAGE_DEVICE_WIDGET_H_ */

View File

@ -1,5 +1,5 @@
/*
* \brief Storage management dialog
* \brief Storage management widget
* \author Norman Feske
* \date 2018-04-30
*/
@ -11,25 +11,26 @@
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _VIEW__STORAGE_DIALOG_H_
#define _VIEW__STORAGE_DIALOG_H_
#ifndef _VIEW__STORAGE_WIDGET_H_
#define _VIEW__STORAGE_WIDGET_H_
#include <types.h>
#include <model/storage_devices.h>
#include <view/storage_device_dialog.h>
#include <view/storage_device_widget.h>
namespace Sculpt { struct Storage_devices_dialog_base; }
namespace Sculpt { struct Storage_devices_widget_base; }
struct Sculpt::Storage_devices_dialog_base : Widget<Vbox>
struct Sculpt::Storage_devices_widget_base : Widget<Vbox>
{
Storage_devices const &_storage_devices;
Storage_target const &_used_target;
Constructible<Hosted<Vbox, Frame, Storage_device_dialog>> _storage_device_dialog { };
Constructible<Hosted<Vbox, Frame, Storage_device_widget>> _storage_device_widget { };
Block_device::Label _selected_device { };
Storage_devices_dialog_base(Storage_devices const &storage_devices,
Storage_devices_widget_base(Storage_devices const &storage_devices,
Storage_target const &used_target)
:
_storage_devices(storage_devices), _used_target(used_target)
@ -42,48 +43,48 @@ struct Sculpt::Storage_devices_dialog_base : Widget<Vbox>
s.widget(button, dev, selected, _used_target);
if (_storage_device_dialog.constructed() && selected) {
if (_storage_device_widget.constructed() && selected) {
s.sub_scope<Frame>([&] (Scope<Vbox, Frame> &s) {
s.attribute("style", "invisible");
s.widget(*_storage_device_dialog, dev, _used_target);
s.widget(*_storage_device_widget, dev, _used_target);
});
}
}
template <typename BUTTON>
void _click_device(Clicked_at const &at, Storage_device_dialog::Action &action)
void _click_device(Clicked_at const &at, Storage_device_widget::Action &action)
{
/* select device */
Id const id = at.matching_id<Vbox, BUTTON>();
if (id.valid()) {
if (id.value == _selected_device) {
_selected_device = { };
_storage_device_dialog.destruct();
_storage_device_widget.destruct();
} else {
_selected_device = id.value;
_storage_device_dialog.construct(Id { id });
_storage_device_widget.construct(Id { id });
}
}
if (_selected_device.valid() && _storage_device_dialog.constructed())
_storage_device_dialog->propagate(at, _used_target, action);
if (_selected_device.valid() && _storage_device_widget.constructed())
_storage_device_widget->propagate(at, _used_target, action);
}
void _clack_device(Clacked_at const &at, Storage_device_dialog::Action &action)
void _clack_device(Clacked_at const &at, Storage_device_widget::Action &action)
{
if (_selected_device.valid() && _storage_device_dialog.constructed())
_storage_device_dialog->propagate(at, action);
if (_selected_device.valid() && _storage_device_widget.constructed())
_storage_device_widget->propagate(at, action);
}
void reset_operation()
{
if (_storage_device_dialog.constructed())
_storage_device_dialog->reset_operation();
if (_storage_device_widget.constructed())
_storage_device_widget->reset_operation();
}
void reset()
{
_storage_device_dialog.destruct();
_storage_device_widget.destruct();
_selected_device = { };
}
};
@ -99,8 +100,6 @@ struct Sculpt::Block_device_button : Widget<Button>
if (s.hovered()) s.attribute("hovered", "yes");
if (selected) s.attribute("selected", "yes");
using Label = Dialog::Label;
s.sub_scope<Hbox>([&] (Scope<Button, Hbox> &s) {
s.sub_scope<Left_floating_hbox>(
[&] (Scope<Button, Hbox, Left_floating_hbox> &s) {
@ -118,11 +117,11 @@ struct Sculpt::Block_device_button : Widget<Button>
};
namespace Sculpt { struct Block_devices_dialog; }
namespace Sculpt { struct Block_devices_widget; }
struct Sculpt::Block_devices_dialog : Storage_devices_dialog_base
struct Sculpt::Block_devices_widget : Storage_devices_widget_base
{
using Storage_devices_dialog_base::Storage_devices_dialog_base;
using Storage_devices_widget_base::Storage_devices_widget_base;
void view(Scope<Vbox> &s) const
{
@ -133,12 +132,12 @@ struct Sculpt::Block_devices_dialog : Storage_devices_dialog_base
});
}
void click(Clicked_at const &at, Storage_device_dialog::Action &action)
void click(Clicked_at const &at, Storage_device_widget::Action &action)
{
_click_device<Block_device_button>(at, action);
}
void clack(Clacked_at const &at, Storage_device_dialog::Action &action)
void clack(Clacked_at const &at, Storage_device_widget::Action &action)
{
_clack_device(at, action);
}
@ -157,8 +156,6 @@ struct Sculpt::Usb_storage_device_button : Widget<Button>
if (s.hovered() && !discarded) s.attribute("hovered", "yes");
if (selected) s.attribute("selected", "yes");
using Label = Dialog::Label;
s.sub_scope<Hbox>([&] (Scope<Button, Hbox> &s) {
s.sub_scope<Left_floating_hbox>(
[&] (Scope<Button, Hbox, Left_floating_hbox> &s) {
@ -183,11 +180,11 @@ struct Sculpt::Usb_storage_device_button : Widget<Button>
};
namespace Sculpt { struct Usb_devices_dialog; }
namespace Sculpt { struct Usb_devices_widget; }
struct Sculpt::Usb_devices_dialog : Storage_devices_dialog_base
struct Sculpt::Usb_devices_widget : Storage_devices_widget_base
{
using Storage_devices_dialog_base::Storage_devices_dialog_base;
using Storage_devices_widget_base::Storage_devices_widget_base;
void view(Scope<Vbox> &s) const
{
@ -198,15 +195,15 @@ struct Sculpt::Usb_devices_dialog : Storage_devices_dialog_base
});
}
void click(Clicked_at const &at, Storage_device_dialog::Action &action)
void click(Clicked_at const &at, Storage_device_widget::Action &action)
{
_click_device<Usb_storage_device_button>(at, action);
}
void clack(Clacked_at const &at, Storage_device_dialog::Action &action)
void clack(Clacked_at const &at, Storage_device_widget::Action &action)
{
_clack_device(at, action);
}
};
#endif /* _VIEW__STORAGE_DIALOG_H_ */
#endif /* _VIEW__STORAGE_WIDGET_H_ */

View File

@ -14,105 +14,83 @@
#ifndef _VIEW__SYSTEM_DIALOG_H_
#define _VIEW__SYSTEM_DIALOG_H_
#include <view/layout_helper.h>
#include <view/dialog.h>
#include <view/software_presets_dialog.h>
#include <view/software_update_dialog.h>
#include <view/software_version_dialog.h>
#include <view/software_presets_widget.h>
#include <view/software_update_widget.h>
#include <view/software_version_widget.h>
#include <model/settings.h>
namespace Sculpt { struct System_dialog; }
struct Sculpt::System_dialog : Noncopyable, Deprecated_dialog
struct Sculpt::System_dialog : Top_level_dialog
{
using Depot_users = Depot_users_dialog::Depot_users;
using Depot_users = Depot_users_widget::Depot_users;
using Image_index = Attached_rom_dataspace;
Hoverable_item _tab_item { };
Presets const &_presets;
Image_index const &_image_index;
Build_info const &_build_info;
enum Tab { PRESETS, UPDATE } _selected_tab = Tab::PRESETS;
Software_presets_widget::Action &_presets_action;
Software_update_widget::Action &_update_action;
Software_presets_dialog _presets_dialog;
Software_update_dialog _update_dialog;
Software_version_dialog _version_dialog;
enum Tab { PRESET, UPDATE } _selected_tab = Tab::PRESET;
Hover_result hover(Xml_node hover) override
Hosted<Frame, Vbox, Software_presets_widget> _presets_widget { Id { "presets" } };
Hosted<Frame, Vbox, Software_update_widget> _update_widget;
Hosted<Frame, Vbox, Software_version_widget> _version_widget { Id { "version" } };
Hosted<Frame, Vbox, Hbox, Select_button<Tab>>
_preset_tab { Id { " Presets " }, Tab::PRESET },
_update_tab { Id { " Update " }, Tab::UPDATE };
void view(Scope<> &s) const override
{
Hover_result dialog_hover_result = Hover_result::UNMODIFIED;
s.sub_scope<Frame>([&] (Scope<Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Frame, Vbox> &s) {
hover.with_optional_sub_node("frame", [&] (Xml_node const &frame) {
frame.with_optional_sub_node("vbox", [&] (Xml_node const &vbox) {
switch (_selected_tab) {
case Tab::PRESETS: dialog_hover_result = _presets_dialog.hover(vbox); break;
case Tab::UPDATE: dialog_hover_result = _update_dialog.hover(vbox); break;
}
});
});
return any_hover_changed(
dialog_hover_result,
_tab_item.match(hover, "frame", "vbox", "hbox", "button", "name"));
}
void reset() override { }
void generate(Xml_generator &xml) const override
{
gen_named_node(xml, "frame", "system", [&] {
xml.node("vbox", [&] {
gen_named_node(xml, "hbox", "tabs", [&] {
auto gen_tab = [&] (auto const &id, auto tab, auto const &text)
{
gen_named_node(xml, "button", id, [&] {
_tab_item.gen_hovered_attr(xml, id);
if (_selected_tab == tab)
xml.attribute("selected", "yes");
xml.node("label", [&] {
xml.attribute("text", text);
});
});
};
gen_tab("presets", Tab::PRESETS, " Presets ");
gen_tab("update", Tab::UPDATE, " Update ");
/* tabs */
s.sub_scope<Hbox>([&] (Scope<Frame, Vbox, Hbox> &s) {
s.widget(_preset_tab, _selected_tab, [&] (auto &s) {
if (!_presets.available())
s.attribute("style", "unimportant");
s.template sub_scope<Label>(_preset_tab.id.value);
});
s.widget(_update_tab, _selected_tab);
});
switch (_selected_tab) {
case Tab::PRESETS:
_presets_dialog.generate(xml);
case Tab::PRESET:
s.widget(_presets_widget, _presets);
break;
case Tab::UPDATE:
_update_dialog .generate(xml);
_version_dialog.generate(xml);
case Tab::UPDATE:
s.widget(_update_widget, _image_index.xml());
s.widget(_version_widget, _build_info);
break;
};
});
});
}
Click_result click()
void click(Clicked_at const &at) override
{
if (_tab_item._hovered.valid()) {
if (_tab_item._hovered == "presets") _selected_tab = Tab::PRESETS;
if (_tab_item._hovered == "update") _selected_tab = Tab::UPDATE;
return Click_result::CONSUMED;
};
_preset_tab.propagate(at, [&] (Tab t) { _selected_tab = t; });
_update_tab.propagate(at, [&] (Tab t) { _selected_tab = t; });
switch (_selected_tab) {
case Tab::PRESETS: _presets_dialog.click(); break;
case Tab::UPDATE: _update_dialog .click(); break;
}
return Click_result::CONSUMED;
_presets_widget.propagate(at, _presets);
if (_selected_tab == Tab::UPDATE)
_update_widget.propagate(at, _update_action);
}
Click_result clack()
void clack(Clacked_at const &at) override
{
switch (_selected_tab) {
case Tab::PRESETS: _presets_dialog.clack(); break;
case Tab::UPDATE: _update_dialog .clack(); break;
};
return Click_result::CONSUMED;
_presets_widget.propagate(at, _presets, _presets_action);
}
void drag(Dragged_at const &) override { }
System_dialog(Presets const &presets,
Build_info const &build_info,
Nic_state const &nic_state,
@ -121,26 +99,30 @@ struct Sculpt::System_dialog : Noncopyable, Deprecated_dialog
File_operation_queue const &file_operation_queue,
Depot_users const &depot_users,
Image_index const &image_index,
Software_presets_dialog::Action &presets_action,
Depot_users_dialog::Action &depot_users_action,
Software_update_dialog::Action &update_action)
Software_presets_widget::Action &presets_action,
Software_update_widget::Action &update_action)
:
_presets_dialog(presets, presets_action),
_update_dialog(build_info, nic_state, download_queue,
Top_level_dialog("system"),
_presets(presets), _image_index(image_index), _build_info(build_info),
_presets_action(presets_action), _update_action(update_action),
_update_widget(Id { "update" },
build_info, nic_state, download_queue,
index_update_queue, file_operation_queue, depot_users,
image_index, depot_users_action, update_action),
_version_dialog(build_info)
image_index)
{ }
bool update_tab_selected() const { return _selected_tab == Tab::UPDATE; }
bool keyboard_needed() const { return _update_dialog.keyboard_needed(); }
bool keyboard_needed() const { return _update_widget.keyboard_needed(); }
void handle_key(Codepoint c) { _update_dialog.handle_key(c); }
void handle_key(Codepoint c, Software_update_widget::Action &action)
{
_update_widget.handle_key(c, action);
}
void sanitize_user_selection() { _update_dialog.sanitize_user_selection(); }
void sanitize_user_selection() { _update_widget.sanitize_user_selection(); }
void reset_update_dialog() { _update_dialog.reset(); }
void reset_update_widget() { _update_widget.reset(); }
};
#endif /* _VIEW__SYSTEM_DIALOG_H_ */

View File

@ -72,7 +72,7 @@ namespace Sculpt {
static inline void gen_parent_rom_route(Xml_generator &xml,
Rom_name const &name,
Label const &label)
auto const &label)
{
gen_service_node<Rom_session>(xml, [&] () {
xml.attribute("label_last", name);