The Battle for Wesnoth  1.19.1+dev
multiline_text.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2023 - 2024
3  by babaissarkar(Subhraman Sarkar) <suvrax@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
19 
20 #include "gui/core/log.hpp"
22 #include "gui/widgets/window.hpp"
24 #include "font/text.hpp"
25 #include "wml_exception.hpp"
26 #include "gettext.hpp"
27 
28 #include <functional>
29 
30 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
31 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
32 
33 namespace gui2
34 {
35 
36 // ------------ WIDGET -----------{
37 
38 REGISTER_WIDGET(multiline_text)
39 
40 multiline_text::multiline_text(const implementation::builder_styled_widget& builder)
41  : text_box_base(builder, type())
42  , history_()
43  , max_input_length_(0)
44  , text_x_offset_(0)
45  , text_y_offset_(0)
46  , text_height_(0)
47  , dragging_(false)
48  , line_num_(0)
49 {
50  set_wants_mouse_left_double_click();
51 
52  connect_signal<event::MOUSE_MOTION>(std::bind(
53  &multiline_text::signal_handler_mouse_motion, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
54  connect_signal<event::LEFT_BUTTON_DOWN>(std::bind(
55  &multiline_text::signal_handler_left_button_down, this, std::placeholders::_2, std::placeholders::_3));
56  connect_signal<event::LEFT_BUTTON_UP>(std::bind(
57  &multiline_text::signal_handler_left_button_up, this, std::placeholders::_2, std::placeholders::_3));
58  connect_signal<event::LEFT_BUTTON_DOUBLE_CLICK>(std::bind(
59  &multiline_text::signal_handler_left_button_double_click, this, std::placeholders::_2, std::placeholders::_3));
60 
61  const auto conf = cast_config_to<multiline_text_definition>();
62  assert(conf);
63 
64  set_font_size(get_text_font_size());
65  set_font_style(conf->text_font_style);
66 
67  update_offsets();
68 }
69 
70 void multiline_text::place(const point& origin, const point& size)
71 {
72  // Inherited.
73  styled_widget::place(origin, size);
74 
77 
79 
81 }
82 
84 {
85  /***** Gather the info *****/
86 
87  // Set the cursor info.
88  const unsigned start = get_selection_start();
89  const int length = static_cast<int>(get_selection_length());
90 
91  // Set the cursor info.
92  const unsigned edit_start = get_composition_start();
93  const int edit_length = get_composition_length();
94 
96 
97  // Set the composition info
98  unsigned comp_start_offset = 0;
99  unsigned comp_end_offset = 0;
100  if(edit_length == 0) {
101  // No nothing.
102  } else if(edit_length > 0) {
103  comp_start_offset = get_cursor_position(edit_start).x;
104  comp_end_offset = get_cursor_position(edit_start + edit_length).x;
105  } else {
106  comp_start_offset = get_cursor_position(edit_start + edit_length).x;
107  comp_end_offset = get_cursor_position(edit_start).x;
108  }
109 
111 
112  /***** Set in all canvases *****/
113 
114  const int max_width = get_text_maximum_width();
115  const int max_height = get_text_maximum_height();
116 
117  for(auto & tmp : get_canvases())
118  {
119 
120  tmp.set_variable("text", wfl::variant(get_value()));
121  tmp.set_variable("text_x_offset", wfl::variant(text_x_offset_));
122  tmp.set_variable("text_y_offset", wfl::variant(text_y_offset_));
123  tmp.set_variable("text_maximum_width", wfl::variant(max_width));
124  tmp.set_variable("text_maximum_height", wfl::variant(max_height));
125  tmp.set_variable("text_wrap_mode", wfl::variant(PANGO_ELLIPSIZE_NONE));
126 
127  tmp.set_variable("editable", wfl::variant(is_editable()));
128 
129  if (length < 0) {
130  tmp.set_variable("highlight_start", wfl::variant(get_byte_offset(start+length)));
131  tmp.set_variable("highlight_end", wfl::variant(get_byte_offset(start)));
132  } else {
133  tmp.set_variable("highlight_start", wfl::variant(get_byte_offset(start)));
134  tmp.set_variable("highlight_end", wfl::variant(get_byte_offset(start+length)));
135  }
136 
137  tmp.set_variable("cursor_offset_x",
138  wfl::variant(get_cursor_position(start + length).x));
139  tmp.set_variable("cursor_offset_y",
140  wfl::variant(get_cursor_position(start + length).y));
141 
142  tmp.set_variable("composition_offset", wfl::variant(comp_start_offset));
143  tmp.set_variable("composition_width", wfl::variant(comp_end_offset - comp_start_offset));
144 
145  tmp.set_variable("hint_text", wfl::variant(hint_text_));
146  tmp.set_variable("hint_image", wfl::variant(hint_image_));
147  }
148 }
149 
150 void multiline_text::delete_char(const bool before_cursor)
151 {
152  if(!is_editable()) {
153  return;
154  }
155 
156  if(before_cursor) {
157  set_cursor(get_selection_start() - 1, false);
158  }
159 
161 
163 }
164 
166 {
167  if(get_selection_length() == 0 || (!is_editable()) ) {
168  return;
169  }
170 
171  // If we have a negative range change it to a positive range.
172  // This makes the rest of the algorithms easier.
173  int len = get_selection_length();
174  unsigned start = get_selection_start();
175  if(len < 0) {
176  len = -len;
177  start -= len;
178  }
179 
180  std::string tmp = get_value();
181  set_value(utf8::erase(tmp, start, len));
182  set_cursor(start, false);
183 
184  update_layout();
185 }
186 
187 void multiline_text::handle_mouse_selection(point mouse, const bool start_selection)
188 {
189  mouse.x -= get_x();
190  mouse.y -= get_y();
191  // FIXME we don't test for overflow in width
192  if(mouse.x < static_cast<int>(text_x_offset_)
193  || mouse.y < static_cast<int>(text_y_offset_)
194  || mouse.y >= static_cast<int>(text_y_offset_ + get_lines_count() * font::get_line_spacing_factor() * text_height_)) {
195  return;
196  }
197 
198  point cursor_pos = get_column_line(point(mouse.x - text_x_offset_, mouse.y - text_y_offset_));
199  int offset = cursor_pos.x;
200  int line = cursor_pos.y;
201 
202  if(offset < 0) {
203  return;
204  }
205 
206  offset += get_line_start_offset(line);
207 
209 
210  set_cursor(offset, !start_selection);
211 
212  update_canvas();
213  queue_redraw();
214  dragging_ |= start_selection;
215 }
216 
217 unsigned multiline_text::get_line_end_offset(unsigned line_no) {
218  // Should be cached if needed
219  std::string line = get_lines().at(line_no);
220  // Get correct number of characters to move for multibyte utf8 string.
221  int line_size = utf8::size(line);
222  return get_line_start_offset(line_no) + line_size;
223 }
224 
225 unsigned multiline_text::get_line_start_offset(unsigned line_no) {
226  if (line_no > 0) {
227  return get_line_end_offset(line_no-1) + 1;
228  } else {
229  return 0;
230  }
231 }
232 
233 unsigned multiline_text::get_line_num_from_offset(unsigned offset) {
234  unsigned line_start = 0, line_end = 0, line_no = 0;
235  for(unsigned i = 0; i < get_lines_count(); i++) {
236  line_start = get_line_start_offset(i);
237  line_end = get_line_end_offset(i);
238  if ((offset >= line_start) && (offset <= line_end)) {
239  line_no = i;
240  break;
241  }
242  }
243  return line_no;
244 }
245 
247 {
249 }
250 
252 {
253  const auto conf = cast_config_to<multiline_text_definition>();
254  assert(conf);
255 
257 
258  wfl::map_formula_callable variables;
259  variables.add("height", wfl::variant(get_height()));
260  variables.add("width", wfl::variant(get_width()));
261  variables.add("text_font_height", wfl::variant(text_height_));
262 
263  text_x_offset_ = conf->text_x_offset(variables);
264  text_y_offset_ = conf->text_y_offset(variables);
265 
266  // Since this variable doesn't change set it here instead of in
267  // update_canvas().
268  for(auto & tmp : get_canvases())
269  {
270  tmp.set_variable("text_font_height", wfl::variant(text_height_));
271  }
272 
273  // Force an update of the canvas since now text_font_height is known.
274  update_canvas();
275 }
276 
278 {
279  if(!history_.get_enabled()) {
280  return false;
281  }
282 
283  const std::string str = history_.up(get_value());
284  if(!str.empty()) {
285  set_value(str);
286  }
287  return true;
288 }
289 
291 {
292  if(!history_.get_enabled()) {
293  return false;
294  }
295 
296  const std::string str = history_.down(get_value());
297  if(!str.empty()) {
298  set_value(str);
299  }
300  return true;
301 }
302 
303 void multiline_text::handle_key_tab(SDL_Keymod modifier, bool& handled)
304 {
305  if(!is_editable())
306  {
307  return;
308  }
309 
310  if(modifier & KMOD_CTRL) {
311  if(!(modifier & KMOD_SHIFT)) {
312  handled = history_up();
313  } else {
314  handled = history_down();
315  }
316  } else {
317  handled = true;
318  insert_char("\t");
319  }
320 }
321 
322 void multiline_text::handle_key_enter(SDL_Keymod modifier, bool& handled)
323 {
324  if (is_editable() && !(modifier & (KMOD_CTRL | KMOD_ALT | KMOD_GUI))) {
325  insert_char("\n");
326  handled = true;
327  }
328 }
329 
330 
331 void multiline_text::handle_key_clear_line(SDL_Keymod /*modifier*/, bool& handled)
332 {
333  handled = true;
334 
335  set_value("");
336 }
337 
338 void multiline_text::handle_key_down_arrow(SDL_Keymod modifier, bool& handled)
339 {
341 
342  handled = true;
343 
345  size_t offset = get_selection_start();
346 
347  if (line_num_ < get_lines_count()-1) {
348  offset = offset
351 
352  if (offset > get_line_end_offset(line_num_+1)) {
353  offset = get_line_end_offset(line_num_+1);
354  }
355  }
356 
357  offset += get_selection_length();
358 
359  if (offset <= get_length()) {
360  set_cursor(offset, (modifier & KMOD_SHIFT) != 0);
361  }
362 
363  update_canvas();
364  queue_redraw();
365 }
366 
367 void multiline_text::handle_key_up_arrow(SDL_Keymod modifier, bool& handled)
368 {
370 
371  handled = true;
372 
374  size_t offset = get_selection_start();
375 
376  if (line_num_ > 0) {
377  offset = offset
380 
381  if (offset > get_line_end_offset(line_num_-1)) {
382  offset = get_line_end_offset(line_num_-1);
383  }
384  }
385 
386  offset += get_selection_length();
387 
388  /* offset is unsigned int */
389  if (offset <= get_length()) {
390  set_cursor(offset, (modifier & KMOD_SHIFT) != 0);
391  }
392 
393  update_canvas();
394  queue_redraw();
395 }
396 
398  bool& handled,
399  const point& coordinate)
400 {
401  DBG_GUI_E << get_control_type() << "[" << id() << "]: " << event << ".";
402 
403  if(dragging_) {
405  }
406 
407  handled = true;
408 }
409 
411  bool& handled)
412 {
413  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
414 
415  /*
416  * Copied from the base class see how we can do inheritance with the new
417  * system...
418  */
419  get_window()->keyboard_capture(this);
421 
423 
424  handled = true;
425 }
426 
428  bool& handled)
429 {
430  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
431 
432  dragging_ = false;
433  handled = true;
434 }
435 
436 void
438  bool& handled)
439 {
440  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
441 
442  select_all();
443  handled = true;
444 }
445 
446 // }---------- DEFINITION ---------{
447 
450 {
451  DBG_GUI_P << "Parsing multiline_text " << id;
452 
453  load_resolutions<resolution>(cfg);
454 }
455 
457  : resolution_definition(cfg)
458  , text_x_offset(cfg["text_x_offset"])
459  , text_y_offset(cfg["text_y_offset"])
460 {
461  // Note the order should be the same as the enum state_t in multiline_text.hpp.
462  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", missing_mandatory_wml_tag("multiline_text_definition][resolution", "state_enabled")));
463  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", missing_mandatory_wml_tag("multiline_text_definition][resolution", "state_disabled")));
464  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_focused", missing_mandatory_wml_tag("multiline_text_definition][resolution", "state_focused")));
465  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_hovered", missing_mandatory_wml_tag("multiline_text_definition][resolution", "state_hovered")));
466 }
467 
468 // }---------- BUILDER -----------{
469 
470 namespace implementation
471 {
472 
473 builder_multiline_text::builder_multiline_text(const config& cfg)
474  : builder_styled_widget(cfg)
475  , history(cfg["history"])
476  , max_input_length(cfg["max_input_length"])
477  , hint_text(cfg["hint_text"].t_str())
478  , hint_image(cfg["hint_image"])
479  , editable(cfg["editable"].to_bool(true))
480  , wrap(cfg["wrap"].to_bool(true))
481 {
482 }
483 
484 std::unique_ptr<widget> builder_multiline_text::build() const
485 {
486  auto widget = std::make_unique<multiline_text>(*this);
487 
488  widget->set_editable(editable);
489  // A textbox doesn't have a label but a text
490  widget->set_value(label_string);
491 
492  if(!history.empty()) {
493  widget->set_history(history);
494  }
495 
496  widget->set_max_input_length(max_input_length);
497  widget->set_hint_data(hint_text, hint_image);
498 
499  DBG_GUI_G << "Window builder: placed text box '" << id
500  << "' with definition '" << definition << "'.";
501 
502  return widget;
503 }
504 
505 } // namespace implementation
506 
507 // }------------ END --------------
508 
509 } // namespace gui2
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
void handle_key_down_arrow(SDL_Keymod, bool &handled) override
Inherited from text_box_base.
bool dragging_
Is the mouse in dragging mode, this affects selection in mouse move.
unsigned line_num_
Line number of text.
unsigned text_y_offset_
The y offset in the widget where the text starts.
unsigned text_x_offset_
The x offset in the widget where the text starts.
virtual void place(const point &origin, const point &size) override
See widget::place.
bool history_up()
Goes one item up in the history.
void delete_selection() override
Inherited from text_box_base.
unsigned text_height_
The height of the text itself.
void signal_handler_mouse_motion(const event::ui_event event, bool &handled, const point &coordinate)
void update_offsets()
Updates text_x_offset_ and text_y_offset_.
void handle_key_enter(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
virtual const std::string & get_control_type() const override
Inherited from styled_widget, implemented by REGISTER_WIDGET.
bool history_down()
Goes one item down in the history.
unsigned get_line_end_offset(unsigned line_no)
Utility function to calculate the offset of the end of the line.
void handle_mouse_selection(point mouse, const bool start_selection)
std::string hint_text_
Helper text to display (such as "Search") if the text box is empty.
unsigned get_line_num_from_offset(unsigned offset)
void signal_handler_left_button_down(const event::ui_event event, bool &handled)
void delete_char(const bool before_cursor) override
Inherited from text_box_base.
virtual void update_canvas() override
See styled_widget::update_canvas.
std::string hint_image_
Image (such as a magnifying glass) that accompanies the help text.
void handle_key_tab(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
unsigned get_line_start_offset(unsigned line_no)
Utility function to calculate the offset of the end of the line.
void handle_key_clear_line(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
std::size_t max_input_length_
The maximum length of the text input.
void signal_handler_left_button_double_click(const event::ui_event event, bool &handled)
void update_layout()
Update layout.
void set_cursor(const std::size_t offset, const bool select) override
Inherited from text_box_base.
void handle_key_up_arrow(SDL_Keymod, bool &handled) override
Inherited from text_box_base.
void insert_char(const std::string &unicode) override
Inherited from text_box_base.
text_history history_
The history text for this widget.
void set_line_num_from_offset()
utility function to calculate and set line_num_ from offset
void signal_handler_left_button_up(const event::ui_event event, bool &handled)
std::vector< canvas > & get_canvases()
int get_text_maximum_width() const
Returns the maximum width available for the text.
int get_text_maximum_height() const
Returns the maximum height available for the text.
virtual void place(const point &origin, const point &size) override
See widget::place.
unsigned int get_text_font_size() const
Resolves and returns the text_font_size.
Abstract base class for text items.
std::size_t get_length() const
Wrapper function, returns length of the text in pango column offsets.
point get_column_line(const point &position) const
std::string get_value() const
std::size_t get_composition_start() const
size_t get_composition_length() const
Get length of composition text by IME.
point get_cursor_position(const unsigned column, const unsigned line=0) const
unsigned get_lines_count() const
Wrapper function, return number of lines.
virtual void set_value(const std::string &text)
The set_value is virtual for the password_box class.
std::size_t get_selection_length() const
void set_maximum_height(const int height, const bool multiline)
font::family_class get_font_family()
std::vector< std::string > get_lines()
Wrapper function, returns a vector with the lines.
void set_maximum_width(const int width)
bool is_editable()
Check whether text can be edited or not.
std::size_t get_selection_start() const
void set_selection_length(const int selection_length)
void set_maximum_length(const std::size_t maximum_length)
void select_all()
Selects all text.
int get_byte_offset(const unsigned column) const
Wrapper function, returns corrected column offset from pango.
std::string down(const std::string &text="")
One step down in the history.
Definition: text_box.cpp:78
std::string up(const std::string &text="")
One step up in the history.
Definition: text_box.cpp:60
bool get_enabled() const
Definition: text_box.hpp:100
Base class for all widgets.
Definition: widget.hpp:53
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:454
int get_x() const
Definition: widget.cpp:316
unsigned get_width() const
Definition: widget.cpp:326
int get_y() const
Definition: widget.cpp:321
unsigned get_height() const
Definition: widget.cpp:331
const std::string & id() const
Definition: widget.cpp:110
window * get_window()
Get the parent window.
Definition: widget.cpp:117
void keyboard_capture(widget *widget)
Definition: window.cpp:1221
void mouse_capture(const bool capture=true)
Definition: window.cpp:1215
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:253
std::size_t i
Definition: function.cpp:968
Define the common log macros for the gui toolkit.
#define DBG_GUI_G
Definition: log.hpp:41
#define DBG_GUI_P
Definition: log.hpp:66
#define DBG_GUI_E
Definition: log.hpp:35
This file contains the window object, this object is a top level container which has the event manage...
#define LOG_HEADER
#define LOG_SCOPE_HEADER
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:202
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:180
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
int get_max_height(unsigned size, font::family_class fclass, pango_text::FONT_STYLE style)
Returns the maximum glyph height of a font, in pixels.
Definition: text.cpp:1180
constexpr float get_line_spacing_factor()
Definition: text.hpp:545
ui_event
The event sent to the dispatcher.
Definition: handler.hpp:115
Generic file dialog.
point get_mouse_position()
Returns the current mouse position.
Definition: helper.cpp:148
Contains the implementation details for lexical_cast and shouldn't be used directly.
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
std::string & erase(std::string &str, const std::size_t start, const std::size_t len)
Erases a portion of a UTF-8 string.
Definition: unicode.cpp:103
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
virtual std::unique_ptr< widget > build() const override
std::string definition
Parameters for the styled_widget.
multiline_text_definition(const config &cfg)
std::vector< state_definition > state
Holds a 2D point.
Definition: point.hpp:25
std::string missing_mandatory_wml_tag(const std::string &section, const std::string &tag)
Returns a standard message for a missing wml child (tag).
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE_WML_CHILD(cfg, key, message)