mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-08 11:55:24 +00:00
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:
parent
e326371762
commit
0f54ad8e26
@ -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_ */
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -8,3 +8,4 @@ timer_session
|
||||
gui_session
|
||||
input_session
|
||||
framebuffer_session
|
||||
gems
|
||||
|
@ -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> ®istry, 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_ */
|
@ -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_ */
|
@ -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_ */
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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_ */
|
@ -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)
|
||||
|
@ -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_ */
|
@ -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;
|
Loading…
x
Reference in New Issue
Block a user