sculpt: redesigned popup dialog

The new popup dialog mirrors the concept of the software add and option
dialogs of the phone version.

Fixes #5168
This commit is contained in:
Norman Feske 2024-03-28 17:03:46 +01:00 committed by Christian Helmuth
parent 9ea99a896a
commit 268a77add1
14 changed files with 353 additions and 918 deletions

View File

@ -915,9 +915,14 @@ struct Sculpt::Main : Input_event_handler,
&& _software_tabs_widget.hosted.options_selected()
&& _storage._selected_target.valid());
s.widget(_software_add_widget, _software_title_bar.selected()
&& _software_tabs_widget.hosted.add_selected()
&& _storage._selected_target.valid());
{
using Attr = Software_add_widget::Attr;
s.widget(_software_add_widget, _software_title_bar.selected()
&& _software_tabs_widget.hosted.add_selected()
&& _storage._selected_target.valid(),
Attr { .visible_frames = true,
.left_aligned_items = false });
}
_image_index_rom.with_xml([&] (Xml_node const &image_index) {
s.widget(_software_update_widget, _software_title_bar.selected()

View File

@ -18,17 +18,19 @@
#include <os/reporter.h>
/* local includes */
#include <model/wpa_passphrase.h>
#include <types.h>
#include <model/popup.h>
#include <model/wpa_passphrase.h>
#include <view/network_widget.h>
#include <view/panel_dialog.h>
#include <view/system_dialog.h>
#include <view/popup_dialog.h>
namespace Sculpt { struct Keyboard_focus; }
struct Sculpt::Keyboard_focus
{
enum Target { INITIAL, WPA_PASSPHRASE, SYSTEM_DIALOG, WM } target { INITIAL };
enum Target { UNDEFINED, WPA_PASSPHRASE, SYSTEM_DIALOG, POPUP, WM } target { UNDEFINED };
Expanding_reporter _focus_reporter;
@ -36,13 +38,21 @@ struct Sculpt::Keyboard_focus
Wpa_passphrase &_wpa_passphrase;
Panel_dialog::State const &_panel;
System_dialog const &_system_dialog;
Popup_dialog const &_popup_dialog;
bool const &_system_visible;
Popup const &_popup;
void update()
{
Target const orig_target = target;
target = WM;
target = UNDEFINED;
if (_panel.inspect_tab_visible())
target = WM;
if ((_popup.state == Popup::VISIBLE) && _popup_dialog.keyboard_needed())
target = POPUP;
if (_panel.network_visible() && _network_widget.need_keyboard_focus_for_passphrase())
target = WPA_PASSPHRASE;
@ -61,10 +71,13 @@ struct Sculpt::Keyboard_focus
case WPA_PASSPHRASE:
case SYSTEM_DIALOG:
case POPUP:
xml.attribute("label", "manager -> input");
break;
case INITIAL:
case UNDEFINED:
break;
case WM:
xml.attribute("label", "wm -> ");
break;
@ -77,14 +90,18 @@ struct Sculpt::Keyboard_focus
Wpa_passphrase &wpa_passphrase,
Panel_dialog::State const &panel,
System_dialog const &system_dialog,
bool const &system_visible)
bool const &system_visible,
Popup_dialog const &popup_dialog,
Popup const &popup)
:
_focus_reporter(env, "focus", "focus"),
_network_widget(network_widget),
_wpa_passphrase(wpa_passphrase),
_panel(panel),
_system_dialog(system_dialog),
_system_visible(system_visible)
_popup_dialog(popup_dialog),
_system_visible(system_visible),
_popup(popup)
{
update();
}

View File

@ -64,16 +64,15 @@ struct Sculpt::Main : Input_event_handler,
Network::Info,
Graph::Action,
Panel_dialog::Action,
Popup_dialog::Action,
Network_widget::Action,
Settings_widget::Action,
Software_presets_widget::Action,
Software_update_widget::Action,
File_browser_dialog::Action,
Popup_dialog::Construction_info,
Popup_dialog::Action,
Component::Construction_info,
Depot_query,
Panel_dialog::State,
Popup_dialog::Refresh,
Screensaver::Action,
Drivers::Info,
Drivers::Action
@ -414,6 +413,8 @@ struct Sculpt::Main : Input_event_handler,
Depot::Archive::User _image_index_user = _build_info.depot_user;
Depot::Archive::User _index_user = _build_info.depot_user;
Expanding_reporter _depot_query_reporter { _env, "query", "depot_query"};
/**
@ -442,13 +443,19 @@ struct Sculpt::Main : Input_event_handler,
xml.attribute("arch", _deploy._arch);
xml.attribute("version", _query_version.value);
if (_popup_dialog.depot_query_needs_users())
bool const query_users = _popup_dialog.watches_depot()
|| _system_dialog_watches_depot()
|| !_scan_rom.valid();
if (query_users)
xml.node("scan", [&] {
xml.attribute("users", "yes"); });
if (_system_dialog_watches_depot() || !_scan_rom.valid())
xml.node("scan", [&] {
xml.attribute("users", "yes"); });
if (_popup_dialog.watches_depot() || !_image_index_rom.valid())
xml.node("index", [&] {
xml.attribute("user", _index_user);
xml.attribute("version", _sculpt_version);
xml.attribute("content", "yes");
});
if (_system_dialog_watches_depot() || !_image_index_rom.valid())
xml.node("image_index", [&] {
@ -457,8 +464,9 @@ struct Sculpt::Main : Input_event_handler,
xml.attribute("user", _image_index_user);
});
_scan_rom.with_xml([&] (Xml_node const &scan) {
_popup_dialog.gen_depot_query(xml, scan); });
_runtime_state.with_construction([&] (Component const &component) {
xml.node("blueprint", [&] {
xml.attribute("pkg", component.path); }); });
/* update query for blueprints of all unconfigured start nodes */
_deploy.gen_depot_query(xml);
@ -482,6 +490,29 @@ struct Sculpt::Main : Input_event_handler,
}
/******************
** Browse index **
******************/
Rom_handler<Main> _index_rom {
_env, "report -> runtime/depot_query/index", *this, &Main::_handle_index };
void _handle_index(Xml_node const &)
{
if (_popup_dialog.watches_depot())
_popup_dialog.refresh();
}
/**
* Software_add_widget::Action interface
*/
void query_index(Depot::Archive::User const &user) override
{
_index_user = user;
trigger_depot_query();
}
/*********************
** Blueprint query **
*********************/
@ -502,9 +533,10 @@ struct Sculpt::Main : Input_event_handler,
return;
_runtime_state.apply_to_construction([&] (Component &component) {
_popup_dialog.apply_blueprint(component, blueprint); });
component.try_apply_blueprint(blueprint); });
_deploy.handle_deploy();
_popup_dialog.refresh();
}
@ -519,8 +551,9 @@ struct Sculpt::Main : Input_event_handler,
void _handle_scan(Xml_node const &)
{
_popup_dialog.depot_users_scan_updated();
_system_dialog.sanitize_user_selection();
_popup_dialog.sanitize_user_selection();
_popup_dialog.refresh();
}
Rom_handler<Main> _image_index_rom {
@ -734,7 +767,8 @@ struct Sculpt::Main : Input_event_handler,
****************************/
Keyboard_focus _keyboard_focus { _env, _network.dialog, _network.wpa_passphrase,
*this, _system_dialog, _system_visible };
*this, _system_dialog, _system_visible,
_popup_dialog, _popup };
struct Keyboard_focus_guard
{
@ -786,14 +820,18 @@ struct Sculpt::Main : Input_event_handler,
ev.handle_press([&] (Input::Keycode, Codepoint code) {
if (_keyboard_focus.target == Keyboard_focus::WPA_PASSPHRASE)
_network.handle_key_press(code);
if (_keyboard_focus.target == Keyboard_focus::POPUP)
_popup_dialog.handle_key(code, *this);
else if (_system_visible && _system_dialog.keyboard_needed())
_system_dialog.handle_key(code, *this);
need_generate_dialog = true;
});
if (need_generate_dialog)
if (need_generate_dialog) {
_generate_dialog();
_popup_dialog.refresh();
}
}
/*
@ -996,6 +1034,41 @@ struct Sculpt::Main : Input_event_handler,
generate_runtime_config();
}
/**
* Software_add_dialog::Action interface
*/
void update_sculpt_index(Depot::Archive::User const &user, Verify verify) override
{
_download_queue.remove_inactive_downloads();
_index_update_queue.remove_inactive_updates();
_index_update_queue.add(Path(user, "/index/", _sculpt_version), verify);
generate_runtime_config();
}
/**
* Popup_options_widget::Action interface
*/
void enable_optional_component(Path const &launcher) override
{
_runtime_state.launch(launcher, launcher);
/* trigger change of the deployment */
_deploy.update_managed_deploy_config();
_download_queue.remove_inactive_downloads();
}
/**
* Popup_options_widget::Action interface
*/
void disable_optional_component(Path const &launcher) override
{
_runtime_state.abandon(launcher);
/* update config/managed/deploy with the component 'name' removed */
_deploy.update_managed_deploy_config();
_download_queue.remove_inactive_downloads();
}
/*
* Panel::Action interface
*/
@ -1254,7 +1327,6 @@ struct Sculpt::Main : Input_event_handler,
{
/* close popup menu */
_popup.state = Popup::OFF;
_popup_dialog.reset();
_popup_dialog.refresh();
/* remove popup window from window layout */
@ -1264,31 +1336,32 @@ struct Sculpt::Main : Input_event_handler,
_graph_view.refresh();
}
/*
* Popup_dialog::Action interface
*/
void launch_global(Path const &launcher) override
void new_construction(Component::Path const &pkg, Verify verify,
Component::Info const &info) override
{
_runtime_state.launch(launcher, launcher);
_close_popup_dialog();
/* trigger change of the deployment */
_download_queue.remove_inactive_downloads();
_deploy.update_managed_deploy_config();
_runtime_state.new_construction(pkg, verify, info, _affinity_space);
trigger_depot_query();
}
Start_name new_construction(Component::Path const &pkg, Verify verify,
Component::Info const &info) override
{
return _runtime_state.new_construction(pkg, verify, info, _affinity_space);
}
void _apply_to_construction(Popup_dialog::Action::Apply_to &fn) override
void _apply_to_construction(Component::Construction_action::Apply_to &fn) override
{
_runtime_state.apply_to_construction([&] (Component &c) { fn.apply_to(c); });
}
/**
* Component::Construction_action interface
*/
void trigger_pkg_download() override
{
_runtime_state.apply_to_construction([&] (Component &c) {
_download_queue.add(c.path, c.verify); });
/* incorporate new download-queue content into update */
_deploy.update_installation();
generate_runtime_config();
}
void discard_construction() override { _runtime_state.discard_construction(); }
void launch_construction() override
@ -1301,37 +1374,10 @@ struct Sculpt::Main : Input_event_handler,
_deploy.update_managed_deploy_config();
}
void trigger_download(Path const &path, Verify verify) override
{
_download_queue.remove_inactive_downloads();
_download_queue.add(path, verify);
/* incorporate new download-queue content into update */
_deploy.update_installation();
generate_runtime_config();
_generate_dialog();
}
void remove_index(Depot::Archive::User const &user) override
{
auto remove = [&] (Path const &path) {
_file_operation_queue.remove_file(path); };
remove(Path("/rw/depot/", user, "/index/", _sculpt_version));
remove(Path("/rw/public/", user, "/index/", _sculpt_version, ".xz"));
remove(Path("/rw/public/", user, "/index/", _sculpt_version, ".xz.sig"));
if (!_file_operation_queue.any_operation_in_progress())
_file_operation_queue.schedule_next_operations();
generate_runtime_config();
}
/**
* Popup_dialog::Construction_info interface
* Component::Construction_info interface
*/
void _with_construction(Popup_dialog::Construction_info::With const &fn) const override
void _with_construction(Component::Construction_info::With const &fn) const override
{
_runtime_state.with_construction([&] (Component const &c) { fn.with(c); });
}
@ -1346,21 +1392,18 @@ struct Sculpt::Main : Input_event_handler,
Dialog_view<Diag_dialog> _diag_dialog { _dialog_runtime, *this, _heap };
Dialog_view<Popup_dialog> _popup_dialog { _dialog_runtime, _env, *this, *this,
Dialog_view<Popup_dialog> _popup_dialog { _dialog_runtime, *this,
_build_info, _sculpt_version,
_launchers, _network._nic_state,
_network._nic_target, _runtime_state,
_cached_runtime_config, _download_queue,
_scan_rom, *this, *this };
_index_update_queue, _index_rom,
_download_queue, _runtime_state,
_cached_runtime_config, _scan_rom,
*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_dialog.refresh(); }
Managed_config<Main> _fb_drv_config {
_env, "config", "fb_drv", *this, &Main::_handle_fb_drv_config };
@ -1958,14 +2001,7 @@ void Sculpt::Main::_handle_runtime_state(Xml_node const &state)
_deploy.update_installation();
/* update depot-user selection after adding new depot URL */
if (_system_visible)
trigger_depot_query();
/*
* The removal of an index file may have completed, re-query index
* files to reflect this change at the depot selection menu.
*/
if (_popup_dialog.interested_in_file_operations())
if (_system_visible || (_popup.state == Popup::VISIBLE))
trigger_depot_query();
}
}

View File

@ -15,6 +15,7 @@
#define _TYPES_H_
#include <util/list_model.h>
#include <util/xml_generator.h>
#include <base/env.h>
#include <base/attached_rom_dataspace.h>
#include <platform_session/platform_session.h>

View File

@ -14,6 +14,7 @@
#ifndef _VIEW__COMPONENT_ADD_WIDGET_H_
#define _VIEW__COMPONENT_ADD_WIDGET_H_
#include <model/capacity.h>
#include <view/index_menu_widget.h>
#include <view/pd_route_widget.h>
#include <view/resource_widget.h>
@ -167,6 +168,10 @@ struct Sculpt::Component_add_widget : Widget<Vbox>
_launch.propagate(at);
if (at.matching_id<Vbox, Frame, Debug_widget>() == Id { "debug" })
action.apply_to_construction([&] (Component &component) {
_debug.propagate(at, component); });
Id const route_id = at.matching_id<Vbox, Frame, Vbox, Menu_entry>();
/* select route to present routing options */

View File

@ -17,6 +17,7 @@
#include <view/dialog.h>
#include <view/text_entry_field.h>
#include <model/depot_url.h>
#include <xml.h>
namespace Sculpt { struct Depot_users_widget; }

View File

@ -1,532 +0,0 @@
/*
* \brief Popup dialog
* \author Norman Feske
* \date 2018-09-12
*/
/*
* 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 <view/popup_dialog.h>
#include <string.h>
using namespace Sculpt;
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) {
s.named_sub_node("label", "info", [&] {
s.attribute("text", Component::Info(" ", component.info, " ")); });
s.template sub_scope<Annotation>("");
}
s.template sub_scope<Annotation>(component.path);
}
void Popup_dialog::_view_pkg_elements(Scope<Frame, Vbox> &s,
Component const &component) const
{
using Info = Component::Info;
s.widget(_back, Index_menu::Name("Add ", Pretty(_construction_name)));
view_component_info(s, component);
s.sub_scope<Annotation>(Info(Capacity{component.ram}, " ",
component.caps, " caps"));
s.sub_scope<Vgap>();
unsigned count = 0;
component.routes.for_each([&] (Route const &route) {
Id const id { Id::Value { count++ } };
s.sub_scope<Frame>([&] (Scope<Frame, Vbox, Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Frame, Vbox, Frame, Vbox> &s) {
bool const selected = _route_selected(id.value);
bool const defined = route.selected_service.constructed();
if (!selected) {
Route_entry entry { id };
s.widget(entry, defined,
defined ? Info(route.selected_service->info)
: Info(route));
}
/*
* List of routing options
*/
if (selected) {
Route_entry back { Id { "back" } };
s.widget(back, true, Info(route), "back");
unsigned count = 0;
_runtime_config.for_each_service([&] (Service const &service) {
Id const service_id { Id::Value("service.", count++) };
bool const service_selected =
route.selected_service.constructed() &&
service_id.value == route.selected_service_id;
if (service.type == route.required) {
Service_entry entry { service_id };
s.widget(entry, service_selected, service.info);
}
});
}
});
});
});
/* 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); });
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");
if (!selected) {
Route_entry entry { Id { "resources" } };
s.widget(entry, false, "Resource assignment ...", "enter");
}
if (selected) {
Route_entry entry { Id { "back" } };
s.widget(entry, true, "Resource assignment ...", "back");
s.widget(_resources, component);
}
});
});
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())
s.widget(_launch);
}
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;
for_each_viewed_launcher([&] (Launchers::Info const &info) {
Hosted<Frame, Vbox, Menu_entry> menu_entry { launcher_id(count++) };
s.widget(menu_entry, false, String<100>(Pretty(info.path)));
});
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) {
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());
bool const selected = (_selected_user == name);
Id const id = user_id(count++);
if (_index_avail(name)) {
Hosted<Frame, Vbox, Menu_entry> menu_entry { id };
s.widget(menu_entry, selected, User(name, " ..."));
}
});
/*
* Depot selection menu item
*/
if (_nic_ready()) {
Hosted<Frame, Vbox, Menu_entry> menu_entry { Id { "selection" } };
s.widget(menu_entry, false, "Selection ...");
}
}
/*
* List of depot users for removing/adding indices
*/
if (_state == DEPOT_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... " : " ";
Hosted<Frame, Vbox, Menu_entry> user_entry { id };
s.widget(user_entry, selected, User(name, suffix), "checkbox");
});
}
/*
* Title of index
*/
if (_state >= INDEX_SHOWN && _state < PKG_SHOWN) {
Index_menu::Name title("Depot ", _selected_user);
if (_menu._level)
title = Index_menu::Name(title, " ", _menu, " ");
s.widget(_back, title);
}
/*
* Index menu
*/
if (_state >= INDEX_SHOWN && _state < PKG_SHOWN) {
unsigned count = 0;
_for_each_menu_item([&] (Xml_node item) {
Id const id { Id::Value(count) };
if (item.has_type("index")) {
auto const name = item.attribute_value("name", Index_menu::Name());
Hosted<Frame, Vbox, Menu_entry> entry { id };
s.widget(entry, false, Index_menu::Name(name, " ..."));
}
if (item.has_type("pkg")) {
auto const path = item.attribute_value("path", Depot::Archive::Path());
auto const name = Depot::Archive::name(path);
auto const version = Depot::Archive::version(path);
bool selected = false;
bool const installing = _download_queue.in_progress(path);
_construction_info.with_construction([&] (Component const &component) {
if (component.path == path)
selected = true;
});
String<100> const text(Pretty(name), " " "(", version, ")",
installing ? " installing... " : "... ");
Hosted<Frame, Vbox, Menu_entry> entry { id };
s.widget(entry, selected, text);
if (selected && !installing) {
_construction_info.with_construction([&] (Component const &component) {
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
*
* This can happen when the pkg's runtime is
* inconsistent with the content contained in
* the pkg's archives.
*/
if (_blueprint_info.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()) {
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>();
}
/*
* Package is missing but can be installed
*/
else if (_blueprint_info.uninstalled() && _nic_ready()) {
view_component_info(s, component);
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>();
}
/*
* Package is missing and we cannot do anything
* about it
*/
else if (_blueprint_info.uninstalled()) {
s.sub_scope<Vgap>();
s.sub_scope<Annotation>(component.path);
s.sub_scope<Vgap>();
s.sub_scope<Label>("not installed");
s.sub_scope<Vgap>();
}
});
});
});
}
}
count++;
});
}
/*
* Pkg configuration
*/
if (_state >= PKG_SHOWN)
_construction_info.with_construction([&] (Component const &component) {
_view_pkg_elements(s, component); });
}
void Popup_dialog::click(Clicked_at const &at)
{
bool clicked_on_back_button = false;
_back.propagate(at, [&] {
clicked_on_back_button = true;
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;
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();
} else {
_state = DEPOT_SHOWN;
_selected_user = User();
}
break;
case PKG_SHOWN:
case ROUTE_SELECTED:
_state = INDEX_SHOWN;
_action.discard_construction();
_selected_route = { };
break;
}
});
if (clicked_on_back_button)
return;
_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.with_xml([&] (Xml_node const &users) {
users.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 {
unsigned count = 0;
for_each_viewed_launcher([&] (Launchers::Info const &info) {
if (id == launcher_id(count++))
_action.launch_global(info.path); });
}
}
else if (orig_state == DEPOT_SHOWN) {
Id const id = at.matching_id<Frame, Vbox, Menu_entry>();
if (id.value == "selection")
_state = DEPOT_SELECTION;
/* enter depot users menu */
with_matching_user(id, [&] (User const &user) {
_selected_user = user;
_state = INDEX_REQUESTED;
_depot_query.trigger_depot_query();
});
}
else if (orig_state == DEPOT_SELECTION) {
Id const id = at.matching_id<Frame, Vbox, Menu_entry>();
with_matching_user(id, [&] (User const &user) {
if (!_index_avail(user))
_action.trigger_download(_index_path(user), Verify{true});
else
_action.remove_index(user);
});
}
else if (orig_state >= INDEX_SHOWN && orig_state < PKG_SHOWN) {
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 {
_action.apply_to_construction([&] (Component &component) {
_resources.propagate(at, component); });
}
} else {
bool clicked_on_selected_route = false;
_apply_to_selected_route(_action, [&] (Route &route) {
unsigned count = 0;
_runtime_config.for_each_service([&] (Service const &service) {
Id const id { Id::Value("service.", count++) };
if (route_id == id) {
bool const clicked_service_already_selected =
route.selected_service.constructed() &&
id.value == route.selected_service_id;
if (clicked_service_already_selected) {
/* clear selection */
route.selected_service.destruct();
route.selected_service_id = { };
} else {
/* select different service */
route.selected_service.construct(service);
route.selected_service_id = id.value;
}
_state = PKG_SHOWN;
_selected_route = { };
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 (orig_state == PKG_SHOWN || orig_state == ROUTE_SELECTED)
_action.apply_to_construction([&] (Component &component) {
_debug.propagate(at, component); });
}

View File

@ -17,330 +17,88 @@
/* local includes */
#include <types.h>
#include <model/launchers.h>
#include <model/sculpt_version.h>
#include <model/component.h>
#include <model/runtime_config.h>
#include <model/download_queue.h>
#include <model/nic_state.h>
#include <model/index_menu.h>
#include <view/dialog.h>
#include <view/pd_route_widget.h>
#include <view/resource_widget.h>
#include <view/debug_widget.h>
#include <depot_query.h>
#include <view/popup_tabs_widget.h>
#include <view/popup_options_widget.h>
#include <view/software_add_widget.h>
namespace Sculpt { struct Popup_dialog; }
struct Sculpt::Popup_dialog : Dialog::Top_level_dialog
{
using Depot_users = Rom_data;
using Blueprint_info = Component::Blueprint_info;
using Depot_users = Rom_data;
using Construction_info = Component::Construction_info;
using Index = Software_add_widget::Index;
Env &_env;
Sculpt_version const _sculpt_version { _env };
Launchers const &_launchers;
Nic_state const &_nic_state;
Nic_target const &_nic_target;
bool _nic_ready() const { return _nic_target.ready() && _nic_state.ready(); }
Runtime_info const &_runtime_info;
Runtime_config const &_runtime_config;
Download_queue const &_download_queue;
Depot_users const &_depot_users;
Depot_query &_depot_query;
struct Construction_info : Interface
{
struct With : Interface { virtual void with(Component const &) const = 0; };
virtual void _with_construction(With const &) const = 0;
template <typename FN>
void with_construction(FN const &fn) const
{
struct _With : With {
FN const &_fn;
_With(FN const &fn) : _fn(fn) { }
void with(Component const &c) const override { _fn(c); }
};
_with_construction(_With(fn));
}
};
struct Refresh : Interface, Noncopyable
{
virtual void refresh_popup_dialog() = 0;
};
Refresh &_refresh;
struct Action : Interface
{
virtual void launch_global(Path const &launcher) = 0;
virtual Start_name new_construction(Component::Path const &pkg, Verify,
Component::Info const &info) = 0;
struct Apply_to : Interface { virtual void apply_to(Component &) = 0; };
virtual void _apply_to_construction(Apply_to &) = 0;
template <typename FN>
void apply_to_construction(FN const &fn)
{
struct _Apply_to : Apply_to {
FN const &_fn;
_Apply_to(FN const &fn) : _fn(fn) { }
void apply_to(Component &c) override { _fn(c); }
} apply_fn(fn);
_apply_to_construction(apply_fn);
}
virtual void discard_construction() = 0;
virtual void launch_construction() = 0;
virtual void trigger_download(Path const &, Verify) = 0;
virtual void remove_index(Depot::Archive::User const &) = 0;
};
struct Action : virtual Software_add_widget::Action,
virtual Popup_options_widget::Action { };
Action &_action;
Construction_info const &_construction_info;
using Route_entry = Hosted<Frame, Vbox, Frame, Vbox, Menu_entry>;
using Service_entry = Hosted<Frame, Vbox, Frame, Vbox, Menu_entry>;
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());
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,
PKG_REQUESTED, PKG_SHOWN, ROUTE_SELECTED };
State _state { TOP_LEVEL };
typedef Depot::Archive::User User;
User _selected_user { };
Blueprint_info _blueprint_info { };
Component::Name _construction_name { };
Id _selected_route { };
bool _route_selected(Route::Id const &id) const
{
return _selected_route.valid() && id == _selected_route.value;
}
bool _resource_dialog_selected() const
{
return _route_selected("resources");
}
void _apply_to_selected_route(Action &action, auto const &fn)
{
unsigned cnt = 0;
action.apply_to_construction([&] (Component &component) {
component.routes.for_each([&] (Route &route) {
if (_route_selected(Route::Id(cnt++)))
fn(route); }); });
}
Index_menu _menu { };
void depot_users_scan_updated()
{
if (_state == DEPOT_REQUESTED)
_state = DEPOT_SHOWN;
if (_state != TOP_LEVEL)
_refresh.refresh_popup_dialog();
}
Attached_rom_dataspace _index_rom { _env, "report -> runtime/depot_query/index" };
Signal_handler<Popup_dialog> _index_handler {
_env.ep(), *this, &Popup_dialog::_handle_index };
void _handle_index()
{
/* prevent modifications of index while browsing it */
if (_state >= INDEX_SHOWN)
return;
_index_rom.update();
if (_state == INDEX_REQUESTED)
_state = INDEX_SHOWN;
_refresh.refresh_popup_dialog();
}
bool _index_avail(User const &user) const
{
bool result = false;
_index_rom.xml().for_each_sub_node("index", [&] (Xml_node index) {
if (index.attribute_value("user", User()) == user)
result = true; });
return result;
};
Path _index_path(User const &user) const
{
return Path(user, "/index/", _sculpt_version);
}
void _for_each_menu_item(auto const &fn) const
{
_menu.for_each_item(_index_rom.xml(), _selected_user, fn);
}
void _view_pkg_elements (Scope<Frame, Vbox> &, Component const &) const;
void _view_menu_elements(Scope<Frame, Vbox> &, Xml_node const &depot_users) const;
Hosted<Frame, Vbox, Popup_tabs_widget> _tabs { Id { "tabs" } };
Hosted<Frame, Vbox, Software_add_widget> _add;
Hosted<Frame, Vbox, Popup_options_widget> _options;
void view(Scope<> &s) const override
{
s.sub_scope<Frame>([&] (Scope<Frame> &s) {
s.sub_scope<Vbox>([&] (Scope<Frame, Vbox> &s) {
_depot_users.with_xml([&] (Xml_node const &users) {
_view_menu_elements(s, users); }); }); });
s.widget(_tabs);
if (_tabs.add_selected()) {
using Attr = Software_add_widget::Attr;
s.widget(_add, Attr { .visible_frames = false,
.left_aligned_items = true });
}
if (_tabs.options_selected())
s.widget(_options);
});
});
}
void click(Clicked_at const &) override;
void click(Clicked_at const &at) override
{
_tabs.propagate(at, [&] { });
_add.propagate(at, _action);
_options.propagate(at, _action);
}
void clack(Clacked_at const &at) override
{
_launch.propagate(at, [&] {
_action.launch_construction();
reset();
});
_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();
});
}
});
_add.propagate(at, _action);
}
void drag(Dragged_at const &) override { }
void reset()
void handle_key(Codepoint c, Action &action)
{
_state = TOP_LEVEL;
_selected_user = User();
_selected_route = { };
_menu._level = 0;
if (_tabs.add_selected())
_add.handle_key(c, action);
}
Popup_dialog(Env &env, Refresh &refresh, Action &action,
Launchers const &launchers,
Nic_state const &nic_state,
Nic_target const &nic_target,
Runtime_info const &runtime_info,
Runtime_config const &runtime_config,
Download_queue const &download_queue,
Depot_users const &depot_users,
Depot_query &depot_query,
Construction_info const &construction_info)
Popup_dialog(Action &action,
Build_info const &build_info,
Sculpt_version const &sculpt_version,
Launchers const &launchers,
Nic_state const &nic_state,
Index_update_queue const &index_update_queue,
Index const &index,
Download_queue const &download_queue,
Runtime_info const &runtime_info,
Runtime_config const &runtime_config,
Depot_users const &depot_users,
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), _action(action),
_construction_info(construction_info)
{
_index_rom.sigh(_index_handler);
}
Top_level_dialog("popup"), _action(action),
_add(Id { "add" }, build_info, sculpt_version, nic_state,
index_update_queue, index, download_queue, runtime_config,
construction_info, depot_users),
_options(Id { "options" }, runtime_info, launchers)
{ }
bool depot_query_needs_users() const { return _state >= TOP_LEVEL; }
bool watches_depot() const { return _tabs.add_selected(); }
void gen_depot_query(Xml_generator &xml, Xml_node const &depot_users) const
{
if (_state >= TOP_LEVEL)
depot_users.for_each_sub_node("user", [&] (Xml_node user) {
xml.node("index", [&] {
User const name = user.attribute_value("name", User());
xml.attribute("user", name);
xml.attribute("version", _sculpt_version);
if (_state >= INDEX_REQUESTED && _selected_user == name)
xml.attribute("content", "yes");
});
});
bool keyboard_needed() const { return _tabs.add_selected()
&& _add.keyboard_needed(); }
if (_state >= PKG_REQUESTED)
_construction_info.with_construction([&] (Component const &component) {
xml.node("blueprint", [&] {
xml.attribute("pkg", component.path); }); });
}
void for_each_viewed_launcher(auto const &fn) const
{
_launchers.for_each([&] (Launchers::Info const &info) {
if (_runtime_info.present_in_runtime(info.path))
return;
fn(info);
});
}
void apply_blueprint(Component &construction, Xml_node blueprint)
{
if (_state < PKG_REQUESTED)
return;
construction.try_apply_blueprint(blueprint);
_blueprint_info = construction.blueprint_info;
if (_blueprint_info.ready_to_deploy() && _state == PKG_REQUESTED)
_state = PKG_SHOWN;
_refresh.refresh_popup_dialog();
}
bool interested_in_file_operations() const
{
return _state == DEPOT_SELECTION;
}
void sanitize_user_selection() { _add.sanitize_user_selection(); }
};
#endif /* _VIEW__POPUP_DIALOG_H_ */

View File

@ -0,0 +1,81 @@
/*
* \brief Widget for the software options
* \author Norman Feske
* \date 2024-04-02
*/
/*
* Copyright (C) 2024 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__POPUP_OPTIONS_WIDGET_H_
#define _VIEW__POPUP_OPTIONS_WIDGET_H_
#include <model/launchers.h>
#include <view/dialog.h>
#include <string.h>
namespace Sculpt { struct Popup_options_widget; }
struct Sculpt::Popup_options_widget : Widget<Vbox>
{
Runtime_info const &_runtime_info;
Launchers const &_launchers;
Popup_options_widget(Runtime_info const &runtime_info,
Launchers const &launchers)
:
_runtime_info(runtime_info), _launchers(launchers)
{ }
struct Option : Menu_entry
{
void view(Scope<Left_floating_hbox> &s, auto const &text, bool enabled) const
{
Menu_entry::view(s, enabled, Pretty(text), "checkbox");
}
};
using Hosted_option = Hosted<Vbox, Option>;
void view(Scope<Vbox> &s) const
{
unsigned count = 0;
_launchers.for_each([&] (Launchers::Info const &info) {
Hosted_option option { { count++ } };
s.widget(option, info.path, _runtime_info.present_in_runtime(info.path));
});
}
struct Action : Interface
{
virtual void enable_optional_component (Path const &launcher) = 0;
virtual void disable_optional_component(Path const &launcher) = 0;
};
void click(Clicked_at const &at, Action &action) const
{
Id const clicked_id = at.matching_id<Vbox, Option>();
unsigned count = 0;
_launchers.for_each([&] (Launchers::Info const &info) {
Id const id { { count++ } };
if (clicked_id != id)
return;
Hosted_option const option { id };
option.propagate(at, [&] {
if (_runtime_info.present_in_runtime(info.path))
action.disable_optional_component(info.path);
else
action.enable_optional_component(info.path);
});
});
}
};
#endif /* _VIEW__POPUP_OPTIONS_WIDGET_H_ */

View File

@ -0,0 +1,50 @@
/*
* \brief Widget for the tabs displayed in the popup dialog
* \author Norman Feske
* \date 2024-03-28
*/
/*
* Copyright (C) 2024 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__POPUP_TABS_WIDGET_H_
#define _VIEW__POPUP_TABS_WIDGET_H_
#include <view/dialog.h>
namespace Sculpt { struct Popup_tabs_widget; }
struct Sculpt::Popup_tabs_widget : Widget<Hbox>
{
enum class Tab { ADD, OPTIONS };
Tab _selected = Tab::OPTIONS;
Hosted<Hbox, Select_button<Tab>> const
_add { Id { "Add" }, Tab::ADD },
_options { Id { "Options" }, Tab::OPTIONS };
void view(Scope<Hbox> &s) const
{
s.widget(_add, _selected);
s.widget(_options, _selected);
}
void click(Clicked_at const &at, auto const &fn)
{
_add .propagate(at, [&] (Tab t) { _selected = t; });
_options.propagate(at, [&] (Tab t) { _selected = t; });
fn();
}
bool options_selected() const { return _selected == Tab::OPTIONS; }
bool add_selected() const { return _selected == Tab::ADD; }
};
#endif /* _VIEW__POPUP_TABS_WIDGET_H_ */

View File

@ -18,6 +18,7 @@
#include <model/nic_state.h>
#include <model/index_update_queue.h>
#include <model/index_menu.h>
#include <model/sculpt_version.h>
#include <view/depot_users_widget.h>
#include <view/index_menu_widget.h>
#include <view/index_pkg_widget.h>
@ -109,7 +110,7 @@ struct Sculpt::Software_add_widget : Widget_interface<Vbox>
if (component.path == pkg_path)
pkg_selected = true; });
label = { Pretty(label), "(", Depot::Archive::version(pkg_path), ")",
label = { Pretty(label), " (", Depot::Archive::version(pkg_path), ")",
pkg_installing ? " installing... " : "... " };
}
@ -134,9 +135,14 @@ struct Sculpt::Software_add_widget : Widget_interface<Vbox>
Hosted<Vbox, Frame, Vbox, Float, Operation_button> _check { Id { "check" } };
void view(Scope<Vbox> &s) const
struct Attr { bool visible_frames, left_aligned_items; };
void view(Scope<Vbox> &s, Attr const attr) const
{
s.sub_scope<Frame>([&] (Scope<Vbox, Frame> &s) {
if (!attr.visible_frames)
s.attribute("style", "invisible");
s.sub_scope<Vbox>([&] (Scope<Vbox, Frame, Vbox> &s) {
s.widget(_users);
@ -163,18 +169,25 @@ struct Sculpt::Software_add_widget : Widget_interface<Vbox>
if (_users.unfolded())
return;
s.sub_scope<Vgap>();
if (attr.visible_frames)
s.sub_scope<Vgap>();
User const user = _users.selected();
if (!_component_add_widget_visible() && !_menu.anything_visible(user))
return;
bool const resource_dialog = _component_add_widget_visible();
s.sub_scope<Float>([&] (Scope<Vbox, Float> &s) {
if (attr.left_aligned_items && !resource_dialog)
s.attribute("west", "yes");
s.sub_scope<Frame>([&] (Scope<Vbox, Float, Frame> &s) {
if (!attr.visible_frames)
s.attribute("style", "invisible");
s.sub_scope<Vbox>([&] (Scope<Vbox, Float, Frame, Vbox> &s) {
s.sub_scope<Min_ex>(35);
if (_component_add_widget_visible())
if (resource_dialog)
_construction_info.with_construction([&] (Component const &component) {
s.widget(_component_add, component); });