sculpt: keyboard-layout selection dialog

This patch extends the settings dialog with the ability to select the
keyboard layout between the options that are included in the sculpt
image. The manual configuration is of course still possible by editing
the /config/event_filter directly.

If both the fonts configuration and the event-filter configuration are
managed manually, the settings button and window are not displayed.

Fixes #4055
This commit is contained in:
Norman Feske 2021-03-19 17:47:53 +01:00
parent e9ac14ed49
commit 36ef41626a
8 changed files with 440 additions and 64 deletions

View File

@ -209,7 +209,7 @@
</route>
</start>
<start name="event_filter" caps="90">
<start name="event_filter" caps="120">
<resource name="RAM" quantum="2M"/>
<provides> <service name="Event"/> </provides>
<route>

View File

@ -105,7 +105,6 @@ install_config {
<rom name="event_filter" label="event_filter.config"/>
<inline name="depot_query"><query/></inline>
</dir>
<rom name="event_filter" label="event_filter.config"/>
<rom name="fb_drv" label="fb_drv.config"/>
<rom name="nitpicker" label="nitpicker.config"/>
<rom name="numlock_remap" label="numlock_remap.config"/>

View File

@ -109,14 +109,42 @@ struct Sculpt::Main : Input_event_handler,
_font_size_px = px; }); } }); } }); });
_handle_gui_mode();
/* visibility of fonts section of settings dialog may have changed */
_settings_menu_view.generate();
/* visibility of settings button may have changed */
_refresh_panel_and_window_layout();
}
Managed_config<Main> _event_filter_config {
_env, "config", "event_filter", *this, &Main::_handle_event_filter_config };
void _generate_event_filter_config(Xml_generator &);
void _handle_event_filter_config(Xml_node)
{
_event_filter_config.try_generate_manually_managed();
_update_event_filter_config();
}
void _update_event_filter_config()
{
bool const orig_settings_available = _settings.interactive_settings_available();
_settings.manual_event_filter_config =
_event_filter_config.try_generate_manually_managed();
if (!_settings.manual_event_filter_config)
_event_filter_config.generate([&] (Xml_generator &xml) {
_generate_event_filter_config(xml); });
_settings_menu_view.generate();
/* visibility of the settings dialog may have changed */
if (orig_settings_available != _settings.interactive_settings_available()) {
_refresh_panel_and_window_layout();
_handle_gui_mode();
}
}
@ -347,7 +375,7 @@ struct Sculpt::Main : Input_event_handler,
** Global **
************/
Font_size _font_size = Font_size::MEDIUM;
Settings _settings { };
float _font_size_px = 14;
@ -378,6 +406,8 @@ struct Sculpt::Main : Input_event_handler,
Panel_dialog::Tab selected_tab() const override { return _selected_tab; }
bool settings_available() const override { return _settings.interactive_settings_available(); }
/**
* Dialog interface
*/
@ -734,12 +764,28 @@ struct Sculpt::Main : Input_event_handler,
/*
* Settings_dialog::Action interface
*/
void select_font_size(Font_size font_size) override
void select_font_size(Settings::Font_size font_size) override
{
_font_size = font_size;
if (_settings.font_size == font_size)
return;
_settings.font_size = font_size;
_handle_gui_mode();
}
/*
* Settings_dialog::Action interface
*/
void select_keyboard_layout(Settings::Keyboard_layout::Name const &keyboard_layout) override
{
if (_settings.keyboard_layout == keyboard_layout)
return;
_settings.keyboard_layout = keyboard_layout;
_update_event_filter_config();
}
Signal_handler<Main> _fs_query_result_handler {
_env.ep(), *this, &Main::_handle_fs_query_result };
@ -974,7 +1020,7 @@ struct Sculpt::Main : Input_event_handler,
Ram_quota{4*1024*1024}, Cap_quota{150},
"panel_dialog", "panel_view_hover" };
Settings_dialog _settings_dialog { _font_size };
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},
@ -1073,6 +1119,7 @@ struct Sculpt::Main : Input_event_handler,
* Generate initial configurations
*/
_network.wifi_disconnect();
_update_event_filter_config();
/*
* Import initial report content
@ -1219,7 +1266,9 @@ void Sculpt::Main::_handle_window_layout()
Point const pos = _settings_visible
? Point(0, avail.y1())
: Point(-size.w(), avail.y1());
gen_window(win, Rect(pos, size));
if (_settings.interactive_settings_available())
gen_window(win, Rect(pos, size));
});
_with_window(window_list, network_view_label, [&] (Xml_node win) {
@ -1330,7 +1379,9 @@ void Sculpt::Main::_handle_gui_mode()
_handle_window_layout();
if (!_fonts_config.try_generate_manually_managed()) {
_settings.manual_fonts_config = _fonts_config.try_generate_manually_managed();
if (!_settings.manual_fonts_config) {
_font_size_px = (float)mode.area.h() / 60.0;
@ -1340,8 +1391,8 @@ void Sculpt::Main::_handle_gui_mode()
*/
_font_size_px = max(_font_size_px, 2.0);
if (_font_size == Font_size::SMALL) _font_size_px *= 0.85;
if (_font_size == Font_size::LARGE) _font_size_px *= 1.35;
if (_settings.font_size == Settings::Font_size::SMALL) _font_size_px *= 0.85;
if (_settings.font_size == Settings::Font_size::LARGE) _font_size_px *= 1.35;
_fonts_config.generate([&] (Xml_generator &xml) {
xml.attribute("copy", true);
@ -1743,6 +1794,97 @@ void Sculpt::Main::_generate_runtime_config(Xml_generator &xml) const
}
void Sculpt::Main::_generate_event_filter_config(Xml_generator &xml)
{
auto gen_include = [&] (auto rom) {
xml.node("include", [&] () {
xml.attribute("rom", rom); }); };
xml.node("output", [&] () {
xml.node("chargen", [&] () {
xml.node("remap", [&] () {
auto gen_key = [&] (auto from, auto to) {
xml.node("key", [&] () {
xml.attribute("name", from);
xml.attribute("to", to); }); };
gen_key("KEY_CAPSLOCK", "KEY_CAPSLOCK");
gen_key("KEY_F12", "KEY_DASHBOARD");
gen_key("KEY_LEFTMETA", "KEY_SCREEN");
gen_include("numlock.remap");
xml.node("merge", [&] () {
auto gen_input = [&] (auto name) {
xml.node("input", [&] () {
xml.attribute("name", name); }); };
xml.node("accelerate", [&] () {
xml.attribute("max", 50);
xml.attribute("sensitivity_percent", 1000);
xml.attribute("curve", 127);
xml.node("button-scroll", [&] () {
gen_input("ps2");
xml.node("vertical", [&] () {
xml.attribute("button", "BTN_MIDDLE");
xml.attribute("speed_percent", -10); });
xml.node("horizontal", [&] () {
xml.attribute("button", "BTN_MIDDLE");
xml.attribute("speed_percent", -10); });
});
});
gen_input("usb");
gen_input("touch");
});
});
auto gen_key = [&] (auto key) {
gen_named_node(xml, "key", key, [&] () {}); };
xml.node("mod1", [&] () {
gen_key("KEY_LEFTSHIFT");
gen_key("KEY_RIGHTSHIFT"); });
xml.node("mod2", [&] () {
gen_key("KEY_LEFTCTRL");
gen_key("KEY_RIGHTCTRL"); });
xml.node("mod3", [&] () {
gen_key("KEY_RIGHTALT"); /* AltGr */ });
xml.node("mod4", [&] () {
xml.node("rom", [&] () {
xml.attribute("name", "capslock"); }); });
xml.node("repeat", [&] () {
xml.attribute("delay_ms", 230);
xml.attribute("rate_ms", 40); });
using Keyboard_layout = Settings::Keyboard_layout;
Keyboard_layout::for_each([&] (Keyboard_layout const &layout) {
if (layout.name == _settings.keyboard_layout)
gen_include(layout.chargen_file); });
gen_include("special.chargen");
});
});
auto gen_policy = [&] (auto label) {
xml.node("policy", [&] () {
xml.attribute("label", label);
xml.attribute("input", label); }); };
gen_policy("ps2");
gen_policy("usb");
gen_policy("touch");
}
void Component::construct(Genode::Env &env)
{
static Sculpt::Main main(env);

View File

@ -16,8 +16,51 @@
#include "types.h"
namespace Sculpt { enum class Font_size; }
namespace Sculpt { struct Settings; }
enum class Sculpt::Font_size { SMALL, MEDIUM, LARGE };
struct Sculpt::Settings
{
enum class Font_size;
enum class Font_size { SMALL, MEDIUM, LARGE };
Font_size font_size = Font_size::MEDIUM;
bool manual_fonts_config = false;
struct Keyboard_layout
{
using Name = String<32>;
Name name;
Path chargen_file;
template <typename FN>
static void for_each(FN const &fn)
{
static Keyboard_layout layouts[] = {
{ .name = "French", .chargen_file = "fr_fr.chargen" },
{ .name = "German", .chargen_file = "de_de.chargen" },
{ .name = "Swiss French", .chargen_file = "fr_ch.chargen" },
{ .name = "Swiss German", .chargen_file = "de_ch.chargen" },
{ .name = "US English", .chargen_file = "en_us.chargen" },
};
for (auto layout : layouts)
fn(layout);
}
};
Keyboard_layout::Name keyboard_layout { "US English" };
bool manual_event_filter_config = false;
bool interactive_settings_available() const
{
return !manual_event_filter_config
|| !manual_fonts_config;
}
};
#endif /* _MODEL__SETTINGS_H_ */

View File

@ -21,19 +21,21 @@ void Panel_dialog::generate(Xml_generator &xml) const
xml.node("frame", [&] () {
xml.attribute("style", "unimportant");
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", true);
xml.node("hbox", [&] () {
xml.node("button", [&] () {
_item.gen_button_attr(xml, "settings");
if (_state.settings_visible())
xml.attribute("selected", true);
xml.node("label", [&] () {
xml.attribute("text", "Settings");
if (_state.settings_available()) {
gen_named_node(xml, "float", "left", [&] () {
xml.attribute("west", true);
xml.node("hbox", [&] () {
xml.node("button", [&] () {
_item.gen_button_attr(xml, "settings");
if (_state.settings_visible())
xml.attribute("selected", true);
xml.node("label", [&] () {
xml.attribute("text", "Settings");
});
});
});
});
});
}
gen_named_node(xml, "float", "center", [&] () {
xml.node("hbox", [&] () {

View File

@ -36,6 +36,7 @@ struct Sculpt::Panel_dialog : Dialog
virtual bool settings_visible() const = 0;
virtual bool network_visible() const = 0;
virtual bool inspect_tab_visible() const = 0;
virtual bool settings_available() const = 0;
};
State const &_state;

View File

@ -0,0 +1,158 @@
/*
* \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, 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

@ -15,6 +15,7 @@
#define _VIEW__SETTINGS_DIALOG_H_
#include <view/dialog.h>
#include <view/radio_choice_dialog.h>
#include <model/settings.h>
namespace Sculpt { struct Settings_dialog; }
@ -22,86 +23,116 @@ namespace Sculpt { struct Settings_dialog; }
struct Sculpt::Settings_dialog : Noncopyable, Dialog
{
Font_size const &_current_font_size;
Settings const &_settings;
Hoverable_item _item { };
Hoverable_item _section { };
static Hoverable_item::Id _font_size_id(Font_size font_size)
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 Font_size::SMALL: return "small";
case Font_size::MEDIUM: return "medium";
case Font_size::LARGE: return "large";
case Settings::Font_size::SMALL: return "Small";
case Settings::Font_size::MEDIUM: return "Medium";
case Settings::Font_size::LARGE: return "Large";
}
return Hoverable_item::Id();
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(
_item.match(hover, "frame", "vbox", "hbox", "button", "name"));
_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(Font_size) = 0;
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", "network", [&] () {
gen_named_node(xml, "frame", "settings", [&] () {
xml.node("vbox", [&] () {
gen_named_node(xml, "hbox", "font_size", [&] () {
gen_named_node(xml, "label", "label", [&] () {
xml.attribute("text", " Font size "); });
auto gen_font_size_button = [&] (Font_size font_size)
{
Label const label = _font_size_id(font_size);
gen_named_node(xml, "button", label, [&] () {
_item.gen_hovered_attr(xml, label);
using Choice = Radio_choice_dialog::Choice;
if (font_size == _current_font_size)
xml.attribute("selected", true);
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");
});
}
xml.node("label", [&] () {
xml.attribute("text", label); });
});
};
gen_font_size_button(Font_size::SMALL);
gen_font_size_button(Font_size::MEDIUM);
gen_font_size_button(Font_size::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 apply_font_size = [&] (Font_size font_size)
auto handle_section = [&] (Radio_choice_dialog &dialog, auto fn_clicked)
{
if (_item.hovered(_font_size_id(font_size))) {
action.select_font_size(font_size);
result = Click_result::CONSUMED;
}
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;
};
apply_font_size(Font_size::SMALL);
apply_font_size(Font_size::MEDIUM);
apply_font_size(Font_size::LARGE);
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(Font_size const &current_font_size)
:
_current_font_size(current_font_size)
{ }
Settings_dialog(Settings const &settings) : _settings(settings) { }
};
#endif /* _VIEW__RAM_FS_DIALOG_H_ */