dialog: add text-area widget

This patch moves the text-editing facility of app/text_area to a
text-area widget as part of the dialog library. This has two benefits.
First, it simplifies app/text_area by using the dialog API. Second, the
editor can now easily be reused by other dialog-API-based applications.

Fixes #5058
This commit is contained in:
Norman Feske 2023-11-15 16:12:42 +01:00 committed by Christian Helmuth
parent e326371762
commit 0f54ad8e26
12 changed files with 310 additions and 907 deletions

View File

@ -5,61 +5,40 @@
*/
/*
* 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.
*/
#ifndef _DIALOG_H_
#define _DIALOG_H_
#ifndef _INCLUDE__DIALOG__TEXT_AREA_WIDGET_H_
#define _INCLUDE__DIALOG__TEXT_AREA_WIDGET_H_
/* Genode includes */
#include <base/component.h>
#include <base/session_object.h>
#include <os/buffered_xml.h>
#include <sandbox/sandbox.h>
#include <os/dynamic_rom_session.h>
#include <input/event.h>
#include <util/reconstructible.h>
#include <util/utf8.h>
#include <dialog/widgets.h>
#include <gems/dynamic_array.h>
/* local includes */
#include <dynamic_array.h>
#include <report.h>
#include <types.h>
namespace Text_area { struct Dialog; }
namespace Dialog { struct Text_area_widget; }
struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer
struct Dialog::Text_area_widget : Widget<Vbox>
{
public:
Dynamic_rom_session rom_session;
struct Trigger_copy : Interface, Noncopyable
struct Action : Interface, Noncopyable
{
virtual void trigger_copy() = 0;
};
struct Trigger_paste : Interface, Noncopyable
{
virtual void trigger_paste() = 0;
};
struct Trigger_save : Interface, Noncopyable
{
virtual void trigger_save() = 0;
virtual void refresh_text_area() = 0;
};
private:
Allocator &_alloc;
Trigger_copy &_trigger_copy;
Trigger_paste &_trigger_paste;
Trigger_save &_trigger_save;
struct Character : Codepoint
{
Character(Codepoint &codepoint) : Codepoint(codepoint) { }
@ -85,16 +64,6 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer
Line::Index x;
Text::Index y;
Position(Line::Index x, Text::Index y) : x(x), y(y) { }
Position(Position const &other) : x(other.x), y(other.y) { }
Position &operator = (Position const &other)
{
x = other.x, y = other.y;
return *this;
}
bool operator != (Position const &other) const
{
return (x.value != other.x.value) || (y.value != other.y.value);
@ -136,18 +105,15 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer
void with_selection_at_line(Text::Index y, Line const &, FN const &) const;
/* generate dialog model */
void gen_selected_line(Xml_generator &, Text::Index, Line const &) const;
void view_selected_line(Scope<Hbox, Float, Label> &, Text::Index, Line const &) const;
};
bool _drag = false;
bool _shift = false;
bool _control = false;
bool _text_hovered = false;
bool _drag = false;
bool _shift = false;
bool _control = false;
Selection _selection { };
void produce_xml(Xml_generator &xml) override;
static bool _printable(Codepoint code)
{
if (!code.valid())
@ -192,6 +158,7 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer
- _max_lines;
}
void _sanitize_scroll_position();
void _move_characters(Line &, Line &);
void _delete_selection();
void _insert_printable(Codepoint);
@ -208,10 +175,17 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer
void _handle_home();
void _handle_end();
void _with_position_at(At const &, auto const &) const;
public:
Dialog(Entrypoint &, Ram_allocator &, Region_map &, Allocator &,
Trigger_copy &, Trigger_paste &, Trigger_save &);
Text_area_widget(Allocator &alloc) : _alloc(alloc) { clear(); }
void view(Scope<Vbox> &) const;
void click(Clicked_at const &);
void clack(Clacked_at const &, Action &);
void drag (Dragged_at const &);
void editable(bool editable) { _editable = editable; }
@ -219,25 +193,15 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer
void max_lines(unsigned max_lines) { _max_lines = max_lines; }
void handle_input_event(Input::Event const &);
void handle_event(Event const &, Action &);
void handle_hover(Xml_node const &hover);
void move_cursor_to(::Dialog::At const &);
void clear();
void append_newline()
{
_text.append(_alloc);
}
void append_newline() { _text.append(_alloc); }
void append_character(Codepoint c)
{
if (_printable(c)) {
Text::Index const y { _text.upper_bound().value - 1 };
_text.apply(y, [&] (Line &line) {
line.append(c); });
}
}
void append_character(Codepoint c);
/* insert character and advance cursor */
void insert_at_cursor_position(Codepoint);
@ -257,4 +221,4 @@ struct Text_area::Dialog : private Dynamic_rom_session::Xml_producer
}
};
#endif /* _DIALOG_H_ */
#endif /* _INCLUDE__DIALOG__TEXT_AREA_WIDGET_H_ */

View File

@ -1,4 +1,4 @@
SRC_CC += sandboxed_runtime.cc
SRC_CC += sandboxed_runtime.cc text_area_widget.cc
LIBS += sandbox
vpath %.cc $(REP_DIR)/src/lib/dialog

View File

@ -1,3 +1,10 @@
SRC_DIR := src/app/text_area
MIRROR_FROM_REP_DIR := lib/mk/dialog.mk src/lib/dialog include/dialog
content: $(MIRROR_FROM_REP_DIR)
$(MIRROR_FROM_REP_DIR):
$(mirror_from_rep_dir)
include $(GENODE_DIR)/repos/base/recipes/src/content.inc

View File

@ -8,3 +8,4 @@ timer_session
gui_session
input_session
framebuffer_session
gems

View File

@ -1,124 +0,0 @@
/*
* \brief Runtime state of a child hosted in the runtime subsystem
* \author Norman Feske
* \date 2018-09-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 _CHILD_STATE_H_
#define _CHILD_STATE_H_
/* Genode includes */
#include <util/xml_node.h>
#include <util/noncopyable.h>
#include <util/string.h>
#include <base/registry.h>
#include <base/quota_guard.h>
/* local includes */
#include "types.h"
namespace Text_area { struct Child_state; }
struct Text_area::Child_state : Noncopyable
{
private:
using Start_name = String<128>;
Registry<Child_state>::Element _element;
Start_name const _name;
Ram_quota const _initial_ram_quota;
Cap_quota const _initial_cap_quota;
Ram_quota _ram_quota = _initial_ram_quota;
Cap_quota _cap_quota = _initial_cap_quota;
struct Version { unsigned value; } _version { 0 };
public:
/**
* Constructor
*
* \param ram_quota initial RAM quota
* \param cap_quota initial capability quota
*/
Child_state(Registry<Child_state> &registry, Start_name const &name,
Ram_quota ram_quota, Cap_quota cap_quota)
:
_element(registry, *this),
_name(name),
_initial_ram_quota(ram_quota), _initial_cap_quota(cap_quota)
{ }
void trigger_restart()
{
_version.value++;
_ram_quota = _initial_ram_quota;
_cap_quota = _initial_cap_quota;
}
void gen_start_node_version(Xml_generator &xml) const
{
if (_version.value)
xml.attribute("version", _version.value);
}
void gen_start_node_content(Xml_generator &xml) const
{
xml.attribute("name", _name);
gen_start_node_version(xml);
xml.attribute("caps", _cap_quota.value);
xml.node("resource", [&] () {
xml.attribute("name", "RAM");
Number_of_bytes const bytes(_ram_quota.value);
xml.attribute("quantum", String<64>(bytes)); });
}
/**
* Adapt runtime state information to the child
*
* This method responds to RAM and cap-resource requests by increasing
* the resource quotas as needed.
*
* \param child child node of the runtime'r state report
* \return true if runtime must be reconfigured so that the changes
* can take effect
*/
bool apply_child_state_report(Xml_node child)
{
bool result = false;
if (child.attribute_value("name", Start_name()) != _name)
return false;
if (child.has_sub_node("ram") && child.sub_node("ram").has_attribute("requested")) {
_ram_quota.value *= 2;
result = true;
}
if (child.has_sub_node("caps") && child.sub_node("caps").has_attribute("requested")) {
_cap_quota.value += 100;
result = true;
}
return result;
}
Ram_quota ram_quota() const { return _ram_quota; }
Start_name name() const { return _name; }
};
#endif /* _CHILD_STATE_H_ */

View File

@ -1,122 +0,0 @@
/*
* \brief GUI wrapper for monitoring the user input of GUI components
* \author Norman Feske
* \date 2020-01-12
*/
/*
* 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 _GUI_H_
#define _GUI_H_
/* Genode includes */
#include <input/component.h>
#include <base/session_object.h>
#include <gui_session/connection.h>
/* local includes */
#include <input_event_handler.h>
namespace Gui {
using namespace Genode;
struct Session_component;
}
struct Gui::Session_component : Session_object<Gui::Session>
{
Env &_env;
Input_event_handler &_event_handler;
Gui::Connection _connection;
Input::Session_component _input_component { _env, _env.ram() };
Signal_handler<Session_component> _input_handler {
_env.ep(), *this, &Session_component::_handle_input };
void _handle_input()
{
_connection.input()->for_each_event([&] (Input::Event ev) {
/* handle event locally within the sculpt manager */
_event_handler.handle_input_event(ev);
_input_component.submit(ev);
});
}
template <typename... ARGS>
Session_component(Env &env, Input_event_handler &event_handler, ARGS &&... args)
:
Session_object(args...),
_env(env), _event_handler(event_handler),
_connection(env, _label.string())
{
_connection.input()->sigh(_input_handler);
_env.ep().manage(_input_component);
_input_component.event_queue().enabled(true);
}
~Session_component() { _env.ep().dissolve(_input_component); }
void upgrade(Session::Resources const &resources)
{
_connection.upgrade(resources);
}
Framebuffer::Session_capability framebuffer_session() override {
return _connection.framebuffer_session(); }
Input::Session_capability input_session() override {
return _input_component.cap(); }
View_handle create_view(View_handle parent) override {
return _connection.create_view(parent); }
void destroy_view(View_handle view) override {
_connection.destroy_view(view); }
View_handle view_handle(View_capability view_cap, View_handle handle) override {
return _connection.view_handle(view_cap, handle); }
View_capability view_capability(View_handle view) override {
return _connection.view_capability(view); }
void release_view_handle(View_handle view) override {
_connection.release_view_handle(view); }
Dataspace_capability command_dataspace() override {
return _connection.command_dataspace(); }
void execute() override {
_connection.execute(); }
Framebuffer::Mode mode() override {
return _connection.mode(); }
void mode_sigh(Signal_context_capability sigh) override {
_connection.mode_sigh(sigh); }
void buffer(Framebuffer::Mode mode, bool use_alpha) override
{
/*
* Do not call 'Connection::buffer' to avoid paying session quota
* from our own budget.
*/
_connection.Client::buffer(mode, use_alpha);
}
void focus(Capability<Gui::Session> session) override {
_connection.focus(session); }
};
#endif /* _GUI_H_ */

View File

@ -1,28 +0,0 @@
/*
* \brief Interface for handling input events
* \author Norman Feske
* \date 2018-05-02
*/
/*
* 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 _INPUT_EVENT_HANDLER_H_
#define _INPUT_EVENT_HANDLER_H_
/* Genode includes */
#include <util/interface.h>
#include <input/event.h>
namespace Gui { struct Input_event_handler; }
struct Gui::Input_event_handler : Genode::Interface
{
virtual void handle_input_event(Input::Event const &) = 0;
};
#endif /* _INPUT_EVENT_HANDLER_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.
@ -13,28 +13,21 @@
/* Genode includes */
#include <base/component.h>
#include <base/session_object.h>
#include <base/attached_rom_dataspace.h>
#include <base/buffered_output.h>
#include <os/buffered_xml.h>
#include <sandbox/sandbox.h>
#include <os/dynamic_rom_session.h>
#include <os/vfs.h>
#include <dialog/runtime.h>
#include <dialog/text_area_widget.h>
#include <os/reporter.h>
#include <os/vfs.h>
/* local includes */
#include <gui.h>
#include <report.h>
#include <dialog.h>
#include <child_state.h>
namespace Text_area {
namespace Text_area { struct Main; }
using namespace Dialog;
struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
Sandbox::State_handler,
Gui::Input_event_handler,
Dialog::Trigger_copy, Dialog::Trigger_paste,
Dialog::Trigger_save
struct Main;
}
struct Text_area::Main : Text_area_widget::Action
{
Env &_env;
@ -47,60 +40,69 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
unsigned _min_width = 0;
unsigned _min_height = 0;
Registry<Child_state> _children { };
Runtime _runtime { _env, _heap };
Child_state _menu_view_child_state { _children, "menu_view",
Ram_quota { 4*1024*1024 },
Cap_quota { 200 } };
/**
* Sandbox::State_handler
*/
void handle_sandbox_state() override
struct Main_dialog : Top_level_dialog
{
/* obtain current sandbox state */
Buffered_xml state(_heap, "state", [&] (Xml_generator &xml) {
_sandbox.generate_state_report(xml);
Main &_main;
Hosted<Frame, Button, Float, Text_area_widget> text { Id { "text" }, _main._heap };
Main_dialog(Main &main) : Top_level_dialog("text_area"), _main(main) { }
void view(Scope<> &s) const override
{
s.sub_scope<Frame>([&] (Scope<Frame> &s) {
s.sub_scope<Button>([&] (Scope<Frame, Button> &s) {
if (s.hovered())
s.attribute("hovered", "yes");
s.sub_scope<Float>([&] (Scope<Frame, Button, Float> &s) {
s.attribute("north", "yes");
s.attribute("east", "yes");
s.attribute("west", "yes");
s.widget(text);
});
});
});
}
void click(Clicked_at const &at) override { text.propagate(at); }
void clack(Clacked_at const &at) override { text.propagate(at, _main); }
void drag (Dragged_at const &at) override { text.propagate(at); }
} _dialog { *this };
Runtime::View _view { _runtime, _dialog };
/* handler used to respond to keyboard input */
Runtime::Event_handler<Main> _event_handler { _runtime, *this, &Main::_handle_event };
void _handle_event(::Dialog::Event const &event)
{
bool const orig_modified = _modified();
_dialog.text.handle_event(event, *this);
event.event.handle_press([&] (Input::Keycode const key, Codepoint) {
/* paste on middle mouse click */
bool const middle_click = (key == Input::BTN_MIDDLE);
if (middle_click) {
_view.if_hovered([&] (Hovered_at const &at) {
_dialog.text.move_cursor_to(at);
trigger_paste();
_view.refresh();
return true;
});
}
});
bool reconfiguration_needed = false;
state.with_xml_node([&] (Xml_node state) {
state.for_each_sub_node("child", [&] (Xml_node const &child) {
if (_menu_view_child_state.apply_child_state_report(child))
reconfiguration_needed = true; }); });
if (reconfiguration_needed)
_update_sandbox_config();
if (_modified() != orig_modified)
_generate_saved_report();
}
Sandbox _sandbox { _env, *this };
typedef Sandbox::Local_service<Gui::Session_component> Gui_service;
Gui_service _gui_service { _sandbox, *this };
typedef Sandbox::Local_service<Dynamic_rom_session> Rom_service;
Rom_service _rom_service { _sandbox, *this };
typedef Sandbox::Local_service<Report::Session_component> Report_service;
Report_service _report_service { _sandbox, *this };
void _handle_hover(Xml_node const &node)
{
if (!node.has_sub_node("dialog"))
_dialog.handle_hover(Xml_node("<empty/>"));
node.with_optional_sub_node("dialog", [&] (Xml_node const &dialog) {
_dialog.handle_hover(dialog); });
}
Report::Session_component::Xml_handler<Main>
_hover_handler { *this, &Main::_handle_hover };
Dialog _dialog { _env.ep(), _env.ram(), _env.rm(), _heap, *this, *this, *this };
Constructible<Expanding_reporter> _saved_reporter { };
struct Saved_version { unsigned value; } _saved_version { 0 };
@ -112,7 +114,7 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
bool _modified() const
{
return _dialog.modification_count() != _saved_modification_count.value;
return _dialog.text.modification_count() != _saved_modification_count.value;
}
void _generate_saved_report()
@ -127,158 +129,6 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
});
}
void _generate_sandbox_config(Xml_generator &xml) const
{
xml.node("report", [&] () {
xml.attribute("child_ram", "yes");
xml.attribute("child_caps", "yes");
xml.attribute("delay_ms", 20*1000);
});
xml.node("parent-provides", [&] () {
auto service_node = [&] (char const *name) {
xml.node("service", [&] () {
xml.attribute("name", name); }); };
service_node("ROM");
service_node("CPU");
service_node("PD");
service_node("LOG");
service_node("File_system");
service_node("Gui");
service_node("Timer");
service_node("Report");
});
xml.node("start", [&] () {
_menu_view_child_state.gen_start_node_content(xml);
xml.node("config", [&] () {
xml.attribute("xpos", "100");
xml.attribute("ypos", "50");
if (_min_width) xml.attribute("width", _min_width);
if (_min_height) xml.attribute("height", _min_height);
xml.node("report", [&] () {
xml.attribute("hover", "yes"); });
xml.node("libc", [&] () {
xml.attribute("stderr", "/dev/log"); });
xml.node("vfs", [&] () {
xml.node("tar", [&] () {
xml.attribute("name", "menu_view_styles.tar"); });
xml.node("dir", [&] () {
xml.attribute("name", "dev");
xml.node("log", [&] () { });
});
xml.node("dir", [&] () {
xml.attribute("name", "fonts");
xml.node("fs", [&] () {
xml.attribute("label", "fonts");
});
});
});
});
xml.node("route", [&] () {
xml.node("service", [&] () {
xml.attribute("name", "ROM");
xml.attribute("label", "dialog");
xml.node("local", [&] () { });
});
xml.node("service", [&] () {
xml.attribute("name", "Report");
xml.attribute("label", "hover");
xml.node("local", [&] () { });
});
xml.node("service", [&] () {
xml.attribute("name", "Gui");
xml.node("local", [&] () { });
});
xml.node("service", [&] () {
xml.attribute("name", "File_system");
xml.attribute("label", "fonts");
xml.node("parent", [&] () {
xml.attribute("label", "fonts"); });
});
xml.node("any-service", [&] () {
xml.node("parent", [&] () { }); });
});
});
}
/**
* Sandbox::Local_service_base::Wakeup interface
*/
void wakeup_local_service() override
{
_rom_service.for_each_requested_session([&] (Rom_service::Request &request) {
if (request.label == "menu_view -> dialog")
request.deliver_session(_dialog.rom_session);
else
request.deny();
});
_report_service.for_each_requested_session([&] (Report_service::Request &request) {
if (request.label == "menu_view -> hover") {
Report::Session_component &session = *new (_heap)
Report::Session_component(_env, _hover_handler,
_env.ep(),
request.resources, "", request.diag);
request.deliver_session(session);
}
});
_report_service.for_each_session_to_close([&] (Report::Session_component &session) {
destroy(_heap, &session);
return Report_service::Close_response::CLOSED;
});
_gui_service.for_each_requested_session([&] (Gui_service::Request &request) {
Gui::Session_component &session = *new (_heap)
Gui::Session_component(_env, *this, _env.ep(),
request.resources, "", request.diag);
request.deliver_session(session);
});
_gui_service.for_each_upgraded_session([&] (Gui::Session_component &session,
Session::Resources const &amount) {
session.upgrade(amount);
return Gui_service::Upgrade_response::CONFIRMED;
});
_gui_service.for_each_session_to_close([&] (Gui::Session_component &session) {
destroy(_heap, &session);
return Gui_service::Close_response::CLOSED;
});
}
/**
* Gui::Input_event_handler interface
*/
void handle_input_event(Input::Event const &event) override
{
bool const orig_modified = _modified();
_dialog.handle_input_event(event);
if (_modified() != orig_modified)
_generate_saved_report();
}
Directory::Path _path() const
{
return _config.xml().attribute_value("path", Directory::Path());
@ -301,7 +151,7 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
enum { MAX_LINE_LEN = 1000 };
typedef String<MAX_LINE_LEN + 1> Content_line;
_dialog.clear();
_dialog.text.clear();
content.for_each_line<Content_line>([&] (Content_line const &line) {
if (line.length() == Content_line::capacity()) {
@ -309,19 +159,19 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
throw Max_line_len_exceeded();
}
_dialog.append_newline();
_dialog.text.append_newline();
for (Utf8_ptr utf8(line.string()); utf8.complete(); utf8 = utf8.next())
_dialog.append_character(utf8.codepoint());
_dialog.text.append_character(utf8.codepoint());
});
}
catch (...) {
warning("failed to load file ", _path());
_dialog.clear();
_dialog.text.clear();
}
_dialog.rom_session.trigger_update();
_view.refresh();
}
Constructible<Watch_handler<Main>> _watch_handler { };
@ -335,7 +185,7 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
Constructible<Expanding_reporter> _clipboard_reporter { };
/**
* Dialog::Trigger_copy interface
* Text_area::Dialog::Action interface
*/
void trigger_copy() override
{
@ -343,7 +193,7 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
return;
_clipboard_reporter->generate([&] (Xml_generator &xml) {
_dialog.gen_clipboard_content(xml); });
_dialog.text.gen_clipboard_content(xml); });
}
/*
@ -356,7 +206,7 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
struct Paste_buffer { char buffer[PASTE_BUFFER_SIZE]; } _paste_buffer { };
/**
* Dialog::Trigger_paste interface
* Text_area::Dialog::Action interface
*/
void trigger_paste() override
{
@ -381,9 +231,9 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
}
for (Utf8_ptr utf8(_paste_buffer.buffer); utf8.complete(); utf8 = utf8.next())
_dialog.insert_at_cursor_position(utf8.codepoint());
_dialog.text.insert_at_cursor_position(utf8.codepoint());
_dialog.rom_session.trigger_update();
_view.refresh();
}
/*
@ -409,7 +259,7 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
Buffered_output<1024, decltype(write)> output(write);
_dialog.for_each_character([&] (Codepoint c) { print(output, c); });
_dialog.text.for_each_character([&] (Codepoint c) { print(output, c); });
}
catch (New_file::Create_failed) {
error("file creation failed while saving file"); }
@ -419,13 +269,13 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
return;
}
_saved_modification_count.value = _dialog.modification_count();
_saved_modification_count.value = _dialog.text.modification_count();
_generate_saved_report();
}
/**
* Dialog::Trigger_save interface
* Text_area::Dialog::Action interface
*/
void trigger_save() override
{
@ -453,11 +303,11 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
_clipboard_reporter.conditional(copy_enabled, _env, "clipboard", "clipboard");
_clipboard_rom .conditional(paste_enabled, _env, "clipboard");
_dialog.max_lines(config.attribute_value("max_lines", ~0U));
_dialog.text.max_lines(config.attribute_value("max_lines", ~0U));
_watch(config.attribute_value("watch", false));
_dialog.editable(_editable());
_dialog.text.editable(_editable());
if (_editable()) {
bool const orig_saved_reporter_enabled = _saved_reporter.constructed();
@ -493,14 +343,10 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
void _update_sandbox_config()
{
Buffered_xml const config { _heap, "config", [&] (Xml_generator &xml) {
_generate_sandbox_config(xml); } };
config.with_xml_node([&] (Xml_node const &config) {
_sandbox.apply_config(config); });
}
/**
* Text_area::Dialog::Action interface
*/
void refresh_text_area() override { _view.refresh(); }
Main(Env &env)
:
@ -515,7 +361,6 @@ struct Text_area::Main : Sandbox::Local_service_base::Wakeup,
_config.sigh(_config_handler);
_handle_config();
_update_sandbox_config();
}
};

View File

@ -1,89 +0,0 @@
/*
* \brief Report session provided to the sandbox
* \author Norman Feske
* \date 2020-01-14
*/
/*
* 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 _REPORT_H_
#define _REPORT_H_
/* Genode includes */
#include <base/session_object.h>
#include <report_session/report_session.h>
namespace Report {
using namespace Genode;
struct Session_component;
}
class Report::Session_component : public Session_object<Report::Session>
{
public:
struct Handler_base : Interface, Genode::Noncopyable
{
virtual void handle_report(char const *, size_t) = 0;
};
template <typename T>
struct Xml_handler : Handler_base
{
T &_obj;
void (T::*_member) (Xml_node const &);
Xml_handler(T &obj, void (T::*member)(Xml_node const &))
: _obj(obj), _member(member) { }
void handle_report(char const *start, size_t length) override
{
(_obj.*_member)(Xml_node(start, length));
}
};
private:
Attached_ram_dataspace _ds;
Handler_base &_handler;
/*******************************
** Report::Session interface **
*******************************/
Dataspace_capability dataspace() override { return _ds.cap(); }
void submit(size_t length) override
{
_handler.handle_report(_ds.local_addr<char const>(),
min(_ds.size(), length));
}
void response_sigh(Signal_context_capability) override { }
size_t obtain_response() override { return 0; }
public:
template <typename... ARGS>
Session_component(Env &env, Handler_base &handler,
Entrypoint &ep, Resources const &resources,
ARGS &&... args)
:
Session_object(ep, resources, args...),
_ds(env.ram(), env.rm(), resources.ram_quota.value),
_handler(handler)
{ }
};
#endif /* _REPORT_H_ */

View File

@ -1,4 +1,4 @@
TARGET = text_area
SRC_CC = main.cc dialog.cc
LIBS += base sandbox vfs
SRC_CC = main.cc
LIBS += base sandbox vfs dialog
INC_DIR += $(PRG_DIR)

View File

@ -1,21 +0,0 @@
/*
* \brief Common types
* \author Norman Feske
* \date 2020-01-14
*/
/*
* Copyright (C) 2012 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 _TYPES_H_
#define _TYPES_H_
namespace Genode { }
namespace Text_area { using namespace Genode; }
#endif /* _TYPES_H_ */

View File

@ -11,10 +11,9 @@
* under the terms of the GNU Affero General Public License version 3.
*/
/* local includes */
#include <dialog.h>
#include <dialog/text_area_widget.h>
using namespace Text_area;
using namespace Dialog;
enum {
@ -53,8 +52,33 @@ template <typename T>
static void swap(T &v1, T &v2) { auto tmp = v1; v1 = v2; v2 = tmp; };
template <typename FN>
void Dialog::Selection::for_each_selected_line(FN const &fn) const
static unsigned unsigned_from_id(Dialog::Id const &id)
{
unsigned value { };
ascii_to(id.value.string(), value);
return value;
};
void Text_area_widget::_with_position_at(::Dialog::At const &at, auto const &fn) const
{
At::Narrowed<Vbox, Hbox, void>::with_at(at, [&] (At const &at) {
Text::Index const y = { unsigned_from_id(at.id()) + _scroll.y.value };
_text.apply(y, [&] (Line const &line) {
Line::Index x = line.upper_bound();
At::Narrowed<Float, Label, void>::with_at(at, [&] (At const &at) {
x = { at._location.attribute_value("at", 0u) }; });
fn(Position { x, y });
});
});
}
void Text_area_widget::Selection::for_each_selected_line(auto const &fn) const
{
if (!defined())
return;
@ -72,9 +96,8 @@ void Dialog::Selection::for_each_selected_line(FN const &fn) const
}
template <typename FN>
void Dialog::Selection::with_selection_at_line(Text::Index y, Line const &line,
FN const &fn) const
void Text_area_widget::Selection::with_selection_at_line(Text::Index y, Line const &line,
auto const &fn) const
{
if (!defined())
return;
@ -103,74 +126,80 @@ void Dialog::Selection::with_selection_at_line(Text::Index y, Line const &line,
}
void Dialog::Selection::gen_selected_line(Xml_generator &xml,
Text::Index y, Line const &line) const
void Text_area_widget::Selection::view_selected_line(Scope<Hbox, Float, Label> &s,
Text::Index y, Line const &line) const
{
with_selection_at_line(y, line, [&] (Line::Index const start_x, const unsigned n) {
xml.node("selection", [&] () {
xml.attribute("at", start_x.value);
xml.attribute("length", n);
});
});
s.sub_node("selection", [&] () {
s.attribute("at", start_x.value);
s.attribute("length", n); }); });
}
void Dialog::produce_xml(Xml_generator &xml)
void Text_area_widget::view(Scope<Vbox> &s) const
{
auto gen_line = [&] (Text::Index at, Line const &line)
using namespace ::Dialog;
struct Line_widget : Widget<Hbox>
{
xml.node("hbox", [&] () {
xml.attribute("name", at.value - _scroll.y.value);
xml.node("float", [&] () {
xml.attribute("north", "yes");
xml.attribute("south", "yes");
xml.attribute("west", "yes");
xml.node("label", [&] () {
xml.attribute("font", "monospace/regular");
xml.attribute("text", String<512>(line));
xml.attribute("hover", "yes");
struct Attr
{
Text::Index const y;
Line const &line;
Position const &cursor;
Selection const &selection;
};
if (_cursor.y.value == at.value)
xml.node("cursor", [&] () {
xml.attribute("name", "cursor");
xml.attribute("at", _cursor.x.value); });
void view(Scope<Hbox> &s, Attr const &attr) const
{
bool const line_hovered = s.hovered();
if (_hovered_position.constructed())
if (_hovered_position->y.value == at.value)
xml.node("cursor", [&] () {
xml.attribute("name", "hover");
xml.attribute("style", "hover");
xml.attribute("at", _hovered_position->x.value); });
s.sub_scope<Float>([&] (Scope<Hbox, Float> &s) {
s.attribute("north", "yes");
s.attribute("south", "yes");
s.attribute("west", "yes");
_selection.gen_selected_line(xml, at, line);
s.sub_scope<Label>(String<512>(attr.line), [&] (Scope<Hbox, Float, Label> &s) {
s.attribute("font", "monospace/regular");
s.attribute("hover", "yes");
if (attr.cursor.y.value == attr.y.value)
s.sub_node("cursor", [&] {
s.attribute("name", "cursor");
s.attribute("at", attr.cursor.x.value); });
if (line_hovered) {
unsigned const hover_x =
s.hover._location.attribute_value("at", attr.line.upper_bound().value);
s.sub_node("cursor", [&] {
s.attribute("name", "hover");
s.attribute("style", "hover");
s.attribute("at", hover_x); });
}
attr.selection.view_selected_line(s, attr.y, attr.line);
});
});
});
}
};
xml.node("frame", [&] () {
xml.node("button", [&] () {
xml.attribute("name", "text");
if (_text_hovered)
xml.attribute("hovered", "yes");
xml.node("float", [&] () {
xml.attribute("north", "yes");
xml.attribute("east", "yes");
xml.attribute("west", "yes");
xml.node("vbox", [&] () {
Dynamic_array<Line>::Range const range { .at = _scroll.y,
.length = _max_lines };
_text.for_each(range, gen_line);
});
});
Dynamic_array<Line>::Range const range { .at = _scroll.y,
.length = _max_lines };
unsigned count = 0;
_text.for_each(range, [&] (Text::Index const &at, Line const &line) {
s.widget(Hosted<Vbox, Line_widget> { Id { { count } } }, Line_widget::Attr {
.y = at,
.line = line,
.cursor = _cursor,
.selection = _selection
});
count++;
});
}
void Dialog::_delete_selection()
void Text_area_widget::_delete_selection()
{
if (!_editable)
return;
@ -246,7 +275,7 @@ void Dialog::_delete_selection()
}
void Dialog::_insert_printable(Codepoint code)
void Text_area_widget::_insert_printable(Codepoint code)
{
_tie_cursor_to_end_of_line();
@ -257,7 +286,24 @@ void Dialog::_insert_printable(Codepoint code)
}
void Dialog::_handle_printable(Codepoint code)
void Text_area_widget::_sanitize_scroll_position()
{
/* ensure that the cursor remains visible */
if (_cursor.y.value > 0)
if (_scroll.y.value > _cursor.y.value - 1)
_scroll.y.value = _cursor.y.value - 1;
if (_cursor.y.value == 0)
_scroll.y.value = 0;
if (_scroll.y.value + _max_lines < _cursor.y.value + 2)
_scroll.y.value = _cursor.y.value - _max_lines + 2;
_clamp_scroll_position_to_upper_bound();
}
void Text_area_widget::_handle_printable(Codepoint code)
{
if (!_editable)
return;
@ -269,7 +315,7 @@ void Dialog::_handle_printable(Codepoint code)
}
void Dialog::_move_characters(Line &from, Line &to)
void Text_area_widget::_move_characters(Line &from, Line &to)
{
/* move all characters of line 'from' to the end of line 'to' */
Line::Index const first { 0 };
@ -281,7 +327,7 @@ void Dialog::_move_characters(Line &from, Line &to)
}
void Dialog::_handle_backspace()
void Text_area_widget::_handle_backspace()
{
if (!_editable)
return;
@ -323,7 +369,7 @@ void Dialog::_handle_backspace()
}
void Dialog::_handle_delete()
void Text_area_widget::_handle_delete()
{
if (!_editable)
return;
@ -344,7 +390,7 @@ void Dialog::_handle_delete()
}
void Dialog::_handle_newline()
void Text_area_widget::_handle_newline()
{
if (!_editable)
return;
@ -373,11 +419,11 @@ void Dialog::_handle_newline()
}
void Dialog::_handle_left()
void Text_area_widget::_handle_left()
{
_tie_cursor_to_end_of_line();
if (_cursor.x.value == 0) {
if (_cursor.x.value == 0) {
if (_cursor.y.value > 0) {
_cursor.y.value--;
_text.apply(_cursor.y, [&] (Line &line) {
@ -389,7 +435,7 @@ if (_cursor.x.value == 0) {
}
void Dialog::_handle_right()
void Text_area_widget::_handle_right()
{
if (!_cursor_at_end_of_line()) {
_cursor.x.value++;
@ -403,21 +449,21 @@ void Dialog::_handle_right()
}
void Dialog::_handle_up()
void Text_area_widget::_handle_up()
{
if (_cursor.y.value > 0)
_cursor.y.value--;
}
void Dialog::_handle_down()
void Text_area_widget::_handle_down()
{
if (_cursor.y.value + 1 < _text.upper_bound().value)
_cursor.y.value++;
}
void Dialog::_handle_pageup()
void Text_area_widget::_handle_pageup()
{
if (_max_lines != ~0U) {
for (unsigned i = 0; i < _max_lines; i++)
@ -428,7 +474,7 @@ void Dialog::_handle_pageup()
}
void Dialog::_handle_pagedown()
void Text_area_widget::_handle_pagedown()
{
if (_max_lines != ~0U) {
for (unsigned i = 0; i < _max_lines; i++)
@ -439,35 +485,59 @@ void Dialog::_handle_pagedown()
}
void Dialog::_handle_home()
void Text_area_widget::_handle_home()
{
_cursor.x.value = 0;
}
void Dialog::_handle_end()
void Text_area_widget::_handle_end()
{
_text.apply(_cursor.y, [&] (Line &line) {
_cursor.x = line.upper_bound(); });
}
void Dialog::handle_input_event(Input::Event const &event)
void Text_area_widget::click(Clicked_at const &at)
{
_with_position_at(at, [&] (Position const pos) {
if (_shift) {
_selection.end.construct(pos);
} else {
_selection.start.construct(pos);
_selection.end.destruct();
}
_drag = true;
});
}
void Text_area_widget::clack(Clacked_at const &at, Action &action)
{
_with_position_at(at, [&] (Position const pos) {
_cursor = pos; });
_drag = false;
if (_selection.defined())
action.trigger_copy();
}
void Text_area_widget::drag(Dragged_at const &at)
{
_with_position_at(at, [&] (Position const pos) {
_selection.end.construct(pos); });
}
void Text_area_widget::handle_event(Event const &event, Action &action)
{
bool update_dialog = false;
Position const orig_cursor = _cursor;
auto cursor_to_hovered_position = [&] ()
{
if (_hovered_position.constructed()) {
_cursor.x = _hovered_position->x;
_cursor.y = _hovered_position->y;
update_dialog = true;
}
};
event.handle_press([&] (Input::Keycode key, Codepoint code) {
event.event.handle_press([&] (Input::Keycode key, Codepoint code) {
bool key_has_visible_effect = true;
@ -501,7 +571,7 @@ void Dialog::handle_input_event(Input::Event const &event)
else if (code.value == CODEPOINT_PAGEUP) { _handle_pageup(); }
else if (code.value == CODEPOINT_HOME) { _handle_home(); }
else if (code.value == CODEPOINT_END) { _handle_end(); }
else if (code.value == CODEPOINT_INSERT) { _trigger_paste.trigger_paste(); }
else if (code.value == CODEPOINT_INSERT) { action.trigger_paste(); }
else {
key_has_visible_effect = false;
}
@ -513,56 +583,25 @@ void Dialog::handle_input_event(Input::Event const &event)
if (_control) {
if (code.value == 'c')
_trigger_copy.trigger_copy();
action.trigger_copy();
if (code.value == 'x') {
_trigger_copy.trigger_copy();
action.trigger_copy();
_delete_selection();
}
if (code.value == 'v')
_trigger_paste.trigger_paste();
action.trigger_paste();
if (code.value == 's')
_trigger_save.trigger_save();
action.trigger_save();
}
if (key_has_visible_effect)
update_dialog = true;
bool const click = (key == Input::BTN_LEFT);
if (click && _hovered_position.constructed()) {
if (_shift)
_selection.end.construct(*_hovered_position);
else
_selection.start.construct(*_hovered_position);
_drag = true;
}
bool const middle_click = (key == Input::BTN_MIDDLE);
if (middle_click) {
cursor_to_hovered_position();
_trigger_paste.trigger_paste();
}
});
if (_drag && _hovered_position.constructed()) {
_selection.end.construct(*_hovered_position);
update_dialog = true;
}
bool const clack = event.key_release(Input::BTN_LEFT);
if (clack) {
cursor_to_hovered_position();
_drag = false;
if (_selection.defined())
_trigger_copy.trigger_copy();
}
event.handle_release([&] (Input::Keycode key) {
event.event.handle_release([&] (Input::Keycode key) {
if (shift_key(key)) _shift = false;
if (control_key(key)) _control = false;
});
@ -571,7 +610,7 @@ void Dialog::handle_input_event(Input::Event const &event)
(_max_lines == ~0U) || (_text.upper_bound().value <= _max_lines);
if (!all_lines_visible) {
event.handle_wheel([&] (int, int y) {
event.event.handle_wheel([&] (int, int y) {
/* scroll at granulatory of 1/5th of vertical view size */
y *= max(1U, _max_lines / 5);
@ -587,105 +626,26 @@ void Dialog::handle_input_event(Input::Event const &event)
}
/* adjust scroll position */
if (all_lines_visible) {
if (all_lines_visible)
_scroll.y.value = 0;
} else if (orig_cursor != _cursor) {
/* ensure that the cursor remains visible */
if (_cursor.y.value > 0)
if (_scroll.y.value > _cursor.y.value - 1)
_scroll.y.value = _cursor.y.value - 1;
if (_cursor.y.value == 0)
_scroll.y.value = 0;
if (_scroll.y.value + _max_lines < _cursor.y.value + 2)
_scroll.y.value = _cursor.y.value - _max_lines + 2;
}
_clamp_scroll_position_to_upper_bound();
_sanitize_scroll_position();
if (update_dialog)
rom_session.trigger_update();
action.refresh_text_area();
}
void Dialog::handle_hover(Xml_node const &hover)
void Text_area_widget::move_cursor_to(::Dialog::At const &at)
{
Constructible<Position> orig_pos { };
_with_position_at(at, [&] (Position pos) {
_cursor = pos; });
if (_hovered_position.constructed())
orig_pos.construct(*_hovered_position);
_hovered_position.destruct();
auto with_hovered_line = [&] (Xml_node node)
{
Text::Index const y {
node.attribute_value("name", _text.upper_bound().value)
+ _scroll.y.value };
_text.apply(y, [&] (Line const &line) {
Line::Index const max_x = line.upper_bound();
_hovered_position.construct(max_x, y);
node.with_optional_sub_node("float", [&] (Xml_node node) {
node.with_optional_sub_node("label", [&] (Xml_node node) {
Line::Index const x {
node.attribute_value("at", max_x.value) };
_hovered_position.construct(x, y);
});
});
});
};
bool const hover_changed =
(orig_pos.constructed() != _hovered_position.constructed());
bool const position_changed = orig_pos.constructed()
&& _hovered_position.constructed()
&& (*orig_pos != *_hovered_position);
bool const orig_text_hovered = _text_hovered;
_text_hovered = false;
hover.with_optional_sub_node("frame", [&] (Xml_node node) {
node.with_optional_sub_node("button", [&] (Xml_node node) {
_text_hovered = true;
node.with_optional_sub_node("float", [&] (Xml_node node) {
node.with_optional_sub_node("vbox", [&] (Xml_node node) {
node.with_optional_sub_node("hbox", [&] (Xml_node node) {
with_hovered_line(node); }); }); }); }); });
if (hover_changed || position_changed || (_text_hovered != orig_text_hovered))
rom_session.trigger_update();
_sanitize_scroll_position();
}
Dialog::Dialog(Entrypoint &ep, Ram_allocator &ram, Region_map &rm,
Allocator &alloc, Trigger_copy &trigger_copy,
Trigger_paste &trigger_paste, Trigger_save &trigger_save)
:
Xml_producer("dialog"),
rom_session(ep, ram, rm, *this),
_alloc(alloc),
_trigger_copy(trigger_copy),
_trigger_paste(trigger_paste),
_trigger_save(trigger_save)
{
clear();
}
void Dialog::clear()
void Text_area_widget::clear()
{
Text::Index const first { 0 };
@ -697,7 +657,17 @@ void Dialog::clear()
}
void Dialog::insert_at_cursor_position(Codepoint c)
void Text_area_widget::append_character(Codepoint c)
{
if (_printable(c)) {
Text::Index const y { _text.upper_bound().value - 1 };
_text.apply(y, [&] (Line &line) {
line.append(c); });
}
}
void Text_area_widget::insert_at_cursor_position(Codepoint c)
{
if (_printable(c)) {
_insert_printable(c);
@ -710,7 +680,7 @@ void Dialog::insert_at_cursor_position(Codepoint c)
}
void Dialog::gen_clipboard_content(Xml_generator &xml) const
void Text_area_widget::gen_clipboard_content(Xml_generator &xml) const
{
if (!_selection.defined())
return;