mirror of
https://github.com/genodelabs/genode.git
synced 2025-06-23 17:17:38 +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:
committed by
Christian Helmuth
parent
e326371762
commit
0f54ad8e26
224
repos/gems/include/dialog/text_area_widget.h
Normal file
224
repos/gems/include/dialog/text_area_widget.h
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* \brief Text dialog
|
||||
* \author Norman Feske
|
||||
* \date 2020-01-14
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 _INCLUDE__DIALOG__TEXT_AREA_WIDGET_H_
|
||||
#define _INCLUDE__DIALOG__TEXT_AREA_WIDGET_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <util/reconstructible.h>
|
||||
#include <util/utf8.h>
|
||||
#include <dialog/widgets.h>
|
||||
#include <gems/dynamic_array.h>
|
||||
|
||||
namespace Dialog { struct Text_area_widget; }
|
||||
|
||||
|
||||
struct Dialog::Text_area_widget : Widget<Vbox>
|
||||
{
|
||||
public:
|
||||
|
||||
struct Action : Interface, Noncopyable
|
||||
{
|
||||
virtual void trigger_copy() = 0;
|
||||
virtual void trigger_paste() = 0;
|
||||
virtual void trigger_save() = 0;
|
||||
virtual void refresh_text_area() = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
Allocator &_alloc;
|
||||
|
||||
struct Character : Codepoint
|
||||
{
|
||||
Character(Codepoint &codepoint) : Codepoint(codepoint) { }
|
||||
|
||||
void print(Output &out) const
|
||||
{
|
||||
if (value == '"')
|
||||
Genode::print(out, """);
|
||||
else if (value == 9)
|
||||
Genode::print(out, " ");
|
||||
else
|
||||
Codepoint::print(out);
|
||||
}
|
||||
};
|
||||
|
||||
using Line = Dynamic_array<Character>;
|
||||
using Text = Dynamic_array<Line>;
|
||||
|
||||
Text _text { _alloc };
|
||||
|
||||
struct Position
|
||||
{
|
||||
Line::Index x;
|
||||
Text::Index y;
|
||||
|
||||
bool operator != (Position const &other) const
|
||||
{
|
||||
return (x.value != other.x.value) || (y.value != other.y.value);
|
||||
}
|
||||
};
|
||||
|
||||
Position _cursor { { 0 }, { 0 } };
|
||||
|
||||
Position _scroll { { 0 }, { 0 } };
|
||||
|
||||
Constructible<Position> _hovered_position { };
|
||||
|
||||
unsigned _max_lines = ~0U;
|
||||
|
||||
bool _editable = false;
|
||||
|
||||
unsigned _modification_count = 0;
|
||||
|
||||
struct Selection
|
||||
{
|
||||
Constructible<Position> start;
|
||||
Constructible<Position> end;
|
||||
|
||||
void clear()
|
||||
{
|
||||
start.destruct();
|
||||
end .destruct();
|
||||
}
|
||||
|
||||
bool defined() const
|
||||
{
|
||||
return start.constructed() && end.constructed() && (*start != *end);
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void for_each_selected_line(FN const &) const;
|
||||
|
||||
template <typename FN>
|
||||
void with_selection_at_line(Text::Index y, Line const &, FN const &) const;
|
||||
|
||||
/* generate dialog model */
|
||||
void view_selected_line(Scope<Hbox, Float, Label> &, Text::Index, Line const &) const;
|
||||
};
|
||||
|
||||
bool _drag = false;
|
||||
bool _shift = false;
|
||||
bool _control = false;
|
||||
|
||||
Selection _selection { };
|
||||
|
||||
static bool _printable(Codepoint code)
|
||||
{
|
||||
if (!code.valid())
|
||||
return false;
|
||||
|
||||
if (code.value == '\t')
|
||||
return true;
|
||||
|
||||
return (code.value >= 0x20 && code.value < 0xf000);
|
||||
}
|
||||
|
||||
bool _cursor_at_last_line() const
|
||||
{
|
||||
return (_cursor.y.value + 1 >= _text.upper_bound().value);
|
||||
}
|
||||
|
||||
bool _cursor_at_end_of_line() const
|
||||
{
|
||||
bool result = false;
|
||||
_text.apply(_cursor.y, [&] (Line &line) {
|
||||
result = (_cursor.x.value >= line.upper_bound().value); });
|
||||
return result;
|
||||
}
|
||||
|
||||
void _tie_cursor_to_end_of_line()
|
||||
{
|
||||
_text.apply(_cursor.y, [&] (Line &line) {
|
||||
if (_cursor.x.value > line.upper_bound().value)
|
||||
_cursor.x = line.upper_bound(); });
|
||||
}
|
||||
|
||||
bool _end_of_text() const
|
||||
{
|
||||
return _cursor_at_last_line() && _cursor_at_end_of_line();
|
||||
}
|
||||
|
||||
void _clamp_scroll_position_to_upper_bound()
|
||||
{
|
||||
if (_max_lines != ~0U)
|
||||
if (_scroll.y.value + _max_lines > _text.upper_bound().value)
|
||||
_scroll.y.value = max(_text.upper_bound().value, _max_lines)
|
||||
- _max_lines;
|
||||
}
|
||||
|
||||
void _sanitize_scroll_position();
|
||||
void _move_characters(Line &, Line &);
|
||||
void _delete_selection();
|
||||
void _insert_printable(Codepoint);
|
||||
void _handle_printable(Codepoint);
|
||||
void _handle_backspace();
|
||||
void _handle_delete();
|
||||
void _handle_newline();
|
||||
void _handle_left();
|
||||
void _handle_right();
|
||||
void _handle_up();
|
||||
void _handle_down();
|
||||
void _handle_pageup();
|
||||
void _handle_pagedown();
|
||||
void _handle_home();
|
||||
void _handle_end();
|
||||
|
||||
void _with_position_at(At const &, auto const &) const;
|
||||
|
||||
public:
|
||||
|
||||
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; }
|
||||
|
||||
unsigned modification_count() const { return _modification_count; }
|
||||
|
||||
void max_lines(unsigned max_lines) { _max_lines = max_lines; }
|
||||
|
||||
void handle_event(Event const &, Action &);
|
||||
|
||||
void move_cursor_to(::Dialog::At const &);
|
||||
|
||||
void clear();
|
||||
|
||||
void append_newline() { _text.append(_alloc); }
|
||||
|
||||
void append_character(Codepoint c);
|
||||
|
||||
/* insert character and advance cursor */
|
||||
void insert_at_cursor_position(Codepoint);
|
||||
|
||||
void gen_clipboard_content(Xml_generator &xml) const;
|
||||
|
||||
template <typename FN>
|
||||
void for_each_character(FN const &fn) const
|
||||
{
|
||||
_text.for_each([&] (Text::Index at, Line const &line) {
|
||||
line.for_each([&] (Line::Index, Character c) {
|
||||
fn(c); });
|
||||
|
||||
if (at.value + 1 < _text.upper_bound().value)
|
||||
fn(Codepoint{'\n'});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _INCLUDE__DIALOG__TEXT_AREA_WIDGET_H_ */
|
Reference in New Issue
Block a user