The Battle for Wesnoth  1.17.23+dev
text_box.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2023
3  by Mark de Wever <koraq@xs4all.nl>
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 
18 #include "gui/widgets/text_box.hpp"
19 
20 #include "gui/core/log.hpp"
22 #include "gui/widgets/settings.hpp"
23 #include "gui/widgets/window.hpp"
24 #include "preferences/game.hpp"
26 #include <functional>
27 #include "wml_exception.hpp"
28 #include "gettext.hpp"
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(text_box)
39 
40 text_history text_history::get_history(const std::string& id,
41  const bool enabled)
42 {
43  std::vector<std::string>* vec = preferences::get_history(id);
44  return text_history(vec, enabled);
45 }
46 
47 void text_history::push(const std::string& text)
48 {
49  if(!enabled_) {
50  return;
51  } else {
52  if(!text.empty() && (history_->empty() || text != history_->back())) {
53  history_->push_back(text);
54  }
55 
56  pos_ = history_->size();
57  }
58 }
59 
60 std::string text_history::up(const std::string& text)
61 {
62 
63  if(!enabled_) {
64  return "";
65  } else if(pos_ == history_->size()) {
66  unsigned curr = pos_;
67  push(text);
68  pos_ = curr;
69  }
70 
71  if(pos_ != 0) {
72  --pos_;
73  }
74 
75  return get_value();
76 }
77 
78 std::string text_history::down(const std::string& text)
79 {
80  if(!enabled_) {
81  return "";
82  } else if(pos_ == history_->size()) {
83  push(text);
84  } else {
85  pos_++;
86  }
87 
88  return get_value();
89 }
90 
91 std::string text_history::get_value() const
92 {
93  if(!enabled_ || pos_ == history_->size()) {
94  return "";
95  } else {
96  return history_->at(pos_);
97  }
98 }
99 
101  : text_box_base(builder, type())
102  , history_()
103  , max_input_length_(0)
104  , text_x_offset_(0)
105  , text_y_offset_(0)
106  , text_height_(0)
107  , dragging_(false)
108 {
110 
111  connect_signal<event::MOUSE_MOTION>(std::bind(
112  &text_box::signal_handler_mouse_motion, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
113  connect_signal<event::LEFT_BUTTON_DOWN>(std::bind(
114  &text_box::signal_handler_left_button_down, this, std::placeholders::_2, std::placeholders::_3));
115  connect_signal<event::LEFT_BUTTON_UP>(std::bind(
116  &text_box::signal_handler_left_button_up, this, std::placeholders::_2, std::placeholders::_3));
117  connect_signal<event::LEFT_BUTTON_DOUBLE_CLICK>(std::bind(
118  &text_box::signal_handler_left_button_double_click, this, std::placeholders::_2, std::placeholders::_3));
119 
120  const auto conf = cast_config_to<text_box_definition>();
121  assert(conf);
122 
124  set_font_style(conf->text_font_style);
125 
126  update_offsets();
127 }
128 
129 void text_box::place(const point& origin, const point& size)
130 {
131  // Inherited.
132  styled_widget::place(origin, size);
133 
136 
138 
139  update_offsets();
140 }
141 
143 {
144  /***** Gather the info *****/
145 
146  // Set the cursor info.
147  const unsigned start = get_selection_start();
148  const int length = get_selection_length();
149 
150  // Set the cursor info.
151  const unsigned edit_start = get_composition_start();
152  const int edit_length = get_composition_length();
153 
155 
156  PangoEllipsizeMode ellipse_mode = PANGO_ELLIPSIZE_NONE;
157  if(!can_wrap()) {
158  if((start + length) > (get_length() / 2)) {
159  ellipse_mode = PANGO_ELLIPSIZE_START;
160  } else {
161  ellipse_mode = PANGO_ELLIPSIZE_END;
162  }
163  }
164  set_ellipse_mode(ellipse_mode);
165 
166  // Set the selection info
167  unsigned start_offset = 0;
168  unsigned end_offset = 0;
169  if(length == 0) {
170  // No nothing.
171  } else if(length > 0) {
172  start_offset = get_cursor_position(start).x;
173  end_offset = get_cursor_position(start + length).x;
174  } else {
175  start_offset = get_cursor_position(start + length).x;
176  end_offset = get_cursor_position(start).x;
177  }
178 
179  // Set the composition info
180  unsigned comp_start_offset = 0;
181  unsigned comp_end_offset = 0;
182  if(edit_length == 0) {
183  // No nothing.
184  } else if(edit_length > 0) {
185  comp_start_offset = get_cursor_position(edit_start).x;
186  comp_end_offset = get_cursor_position(edit_start + edit_length).x;
187  } else {
188  comp_start_offset = get_cursor_position(edit_start + edit_length).x;
189  comp_end_offset = get_cursor_position(edit_start).x;
190  }
191 
192  /***** Set in all canvases *****/
193 
194  const int max_width = get_text_maximum_width();
195  const int max_height = get_text_maximum_height();
196 
197  for(auto & tmp : get_canvases())
198  {
199 
200  tmp.set_variable("text", wfl::variant(get_value()));
201  tmp.set_variable("text_x_offset", wfl::variant(text_x_offset_));
202  tmp.set_variable("text_y_offset", wfl::variant(text_y_offset_));
203  tmp.set_variable("text_maximum_width", wfl::variant(max_width));
204  tmp.set_variable("text_maximum_height", wfl::variant(max_height));
205 
206  tmp.set_variable("cursor_offset",
207  wfl::variant(get_cursor_position(start + length).x));
208 
209  tmp.set_variable("selection_offset", wfl::variant(start_offset));
210  tmp.set_variable("selection_width", wfl::variant(end_offset - start_offset));
211  tmp.set_variable("text_wrap_mode", wfl::variant(ellipse_mode));
212 
213  tmp.set_variable("composition_offset", wfl::variant(comp_start_offset));
214  tmp.set_variable("composition_width", wfl::variant(comp_end_offset - comp_start_offset));
215 
216  tmp.set_variable("hint_text", wfl::variant(hint_text_));
217  tmp.set_variable("hint_image", wfl::variant(hint_image_));
218  }
219 }
220 
221 void text_box::delete_char(const bool before_cursor)
222 {
223  if(before_cursor) {
224  set_cursor(get_selection_start() - 1, false);
225  }
226 
228 
230 }
231 
233 {
234  if(get_selection_length() == 0) {
235  return;
236  }
237 
238  // If we have a negative range change it to a positive range.
239  // This makes the rest of the algorithms easier.
240  int len = get_selection_length();
241  unsigned start = get_selection_start();
242  if(len < 0) {
243  len = -len;
244  start -= len;
245  }
246 
247  std::string tmp = get_value();
248  set_value(utf8::erase(tmp, start, len));
249  set_cursor(start, false);
250 }
251 
252 void text_box::handle_mouse_selection(point mouse, const bool start_selection)
253 {
254  mouse.x -= get_x();
255  mouse.y -= get_y();
256  // FIXME we don't test for overflow in width
257  if(mouse.x < static_cast<int>(text_x_offset_)
258  || mouse.y < static_cast<int>(text_y_offset_)
259  || mouse.y >= static_cast<int>(text_y_offset_ + text_height_)) {
260  return;
261  }
262 
263  int offset = get_column_line(point(mouse.x - text_x_offset_, mouse.y - text_y_offset_)).x;
264 
265  if(offset < 0) {
266  return;
267  }
268 
269 
270  set_cursor(offset, !start_selection);
271  update_canvas();
272  queue_redraw();
273  dragging_ |= start_selection;
274 }
275 
277 {
278  const auto conf = cast_config_to<text_box_definition>();
279  assert(conf);
280 
282 
283  wfl::map_formula_callable variables;
284  variables.add("height", wfl::variant(get_height()));
285  variables.add("width", wfl::variant(get_width()));
286  variables.add("text_font_height", wfl::variant(text_height_));
287 
288  text_x_offset_ = conf->text_x_offset(variables);
289  text_y_offset_ = conf->text_y_offset(variables);
290 
291  // Since this variable doesn't change set it here instead of in
292  // update_canvas().
293  for(auto & tmp : get_canvases())
294  {
295  tmp.set_variable("text_font_height", wfl::variant(text_height_));
296  }
297 
298  // Force an update of the canvas since now text_font_height is known.
299  update_canvas();
300 }
301 
303 {
304  if(!history_.get_enabled()) {
305  return false;
306  }
307 
308  const std::string str = history_.up(get_value());
309  if(!str.empty()) {
310  set_value(str);
311  }
312  return true;
313 }
314 
316 {
317  if(!history_.get_enabled()) {
318  return false;
319  }
320 
321  const std::string str = history_.down(get_value());
322  if(!str.empty()) {
323  set_value(str);
324  }
325  return true;
326 }
327 
328 void text_box::handle_key_tab(SDL_Keymod modifier, bool& handled)
329 {
330  if(modifier & KMOD_CTRL) {
331  if(!(modifier & KMOD_SHIFT)) {
332  handled = history_up();
333  } else {
334  handled = history_down();
335  }
336  }
337 }
338 
339 void text_box::handle_key_clear_line(SDL_Keymod /*modifier*/, bool& handled)
340 {
341  handled = true;
342 
343  set_value("");
344 }
345 
347  bool& handled,
348  const point& coordinate)
349 {
350  DBG_GUI_E << get_control_type() << "[" << id() << "]: " << event << ".";
351 
352  if(dragging_) {
354  }
355 
356  handled = true;
357 }
358 
360  bool& handled)
361 {
362  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
363 
364  /*
365  * Copied from the base class see how we can do inheritance with the new
366  * system...
367  */
368  get_window()->keyboard_capture(this);
370 
372 
373  handled = true;
374 }
375 
377  bool& handled)
378 {
379  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
380 
381  dragging_ = false;
382  handled = true;
383 }
384 
385 void
387  bool& handled)
388 {
389  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
390 
391  select_all();
392  handled = true;
393 }
394 
395 // }---------- DEFINITION ---------{
396 
399 {
400  DBG_GUI_P << "Parsing text_box " << id;
401 
402  load_resolutions<resolution>(cfg);
403 }
404 
406  : resolution_definition(cfg)
407  , text_x_offset(cfg["text_x_offset"])
408  , text_y_offset(cfg["text_y_offset"])
409 {
410  // Note the order should be the same as the enum state_t in text_box.hpp.
411  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", _("Missing required state for editable text box")));
412  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", _("Missing required state for editable text box")));
413  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_focused", _("Missing required state for editable text box")));
414  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_hovered", _("Missing required state for editable text box")));
415 }
416 
417 // }---------- BUILDER -----------{
418 
419 namespace implementation
420 {
421 
422 builder_text_box::builder_text_box(const config& cfg)
423  : builder_styled_widget(cfg)
424  , history(cfg["history"])
425  , max_input_length(cfg["max_input_length"])
426  , hint_text(cfg["hint_text"].t_str())
427  , hint_image(cfg["hint_image"])
428 {
429 }
430 
431 std::unique_ptr<widget> builder_text_box::build() const
432 {
433  auto widget = std::make_unique<text_box>(*this);
434 
435  // A textbox doesn't have a label but a text
436  widget->set_value(label_string);
437 
438  if(!history.empty()) {
439  widget->set_history(history);
440  }
441 
442  widget->set_max_input_length(max_input_length);
443  widget->set_hint_data(hint_text, hint_image);
444 
445  DBG_GUI_G << "Window builder: placed text box '" << id
446  << "' with definition '" << definition << "'.";
447 
448  return widget;
449 }
450 
451 } // namespace implementation
452 
453 // }------------ END --------------
454 
455 } // namespace gui2
map_location curr
Definition: astarsearch.cpp:66
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
void set_wants_mouse_left_double_click(const bool click=true)
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
void set_font_size(const unsigned font_size)
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
void set_font_style(const font::pango_text::FONT_STYLE font_style)
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)
void set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
void set_cursor(const std::size_t offset, const bool select)
Moves the cursor at the wanted position.
void set_maximum_width(const int width)
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.
virtual void place(const point &origin, const point &size) override
See widget::place.
Definition: text_box.cpp:129
std::size_t max_input_length_
The maximum length of the text input.
Definition: text_box.hpp:215
void handle_key_clear_line(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
Definition: text_box.cpp:339
text_history history_
The history text for this widget.
Definition: text_box.hpp:212
bool history_down()
Goes one item down in the history.
Definition: text_box.cpp:315
std::string hint_image_
Image (such as a magnifying glass) that accompanies the help text.
Definition: text_box.hpp:249
virtual const std::string & get_control_type() const override
Inherited from styled_widget, implemented by REGISTER_WIDGET.
void signal_handler_mouse_motion(const event::ui_event event, bool &handled, const point &coordinate)
Definition: text_box.cpp:346
void delete_char(const bool before_cursor) override
Inherited from text_box_base.
Definition: text_box.cpp:221
void signal_handler_left_button_down(const event::ui_event event, bool &handled)
Definition: text_box.cpp:359
unsigned text_height_
The height of the text itself.
Definition: text_box.hpp:237
std::string hint_text_
Helper text to display (such as "Search") if the text box is empty.
Definition: text_box.hpp:246
bool history_up()
Goes one item up in the history.
Definition: text_box.cpp:302
void handle_key_tab(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
Definition: text_box.cpp:328
void handle_mouse_selection(point mouse, const bool start_selection)
Definition: text_box.cpp:252
void signal_handler_left_button_double_click(const event::ui_event event, bool &handled)
Definition: text_box.cpp:386
bool dragging_
Is the mouse in dragging mode, this affects selection in mouse move.
Definition: text_box.hpp:243
void update_offsets()
Updates text_x_offset_ and text_y_offset_.
Definition: text_box.cpp:276
text_box(const implementation::builder_styled_widget &builder)
Definition: text_box.cpp:100
void delete_selection() override
Inherited from text_box_base.
Definition: text_box.cpp:232
void signal_handler_left_button_up(const event::ui_event event, bool &handled)
Definition: text_box.cpp:376
unsigned text_y_offset_
The y offset in the widget where the text starts.
Definition: text_box.hpp:230
virtual void update_canvas() override
See styled_widget::update_canvas.
Definition: text_box.cpp:142
unsigned text_x_offset_
The x offset in the widget where the text starts.
Definition: text_box.hpp:223
Class for text input history.
Definition: text_box.hpp:37
unsigned pos_
The current position in the history.
Definition: text_box.hpp:115
std::string get_value() const
Gets the current history value.
Definition: text_box.cpp:91
std::string down(const std::string &text="")
One step down in the history.
Definition: text_box.cpp:78
bool enabled_
Is the history enabled.
Definition: text_box.hpp:118
std::vector< std::string > * history_
The items in the history.
Definition: text_box.hpp:112
void push(const std::string &text)
Push string into the history.
Definition: text_box.cpp:47
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:54
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:456
int get_x() const
Definition: widget.cpp:318
unsigned get_width() const
Definition: widget.cpp:328
int get_y() const
Definition: widget.cpp:323
unsigned get_height() const
Definition: widget.cpp:333
const std::string & id() const
Definition: widget.cpp:111
window * get_window()
Get the parent window.
Definition: widget.cpp:118
virtual bool can_wrap() const
Can the widget wrap.
Definition: widget.cpp:217
void keyboard_capture(widget *widget)
Definition: window.cpp:1224
void mouse_capture(const bool capture=true)
Definition: window.cpp:1218
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:253
static std::string _(const char *str)
Definition: gettext.hpp:93
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...
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:203
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:967
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:114
Contains the implementation details for lexical_cast and shouldn't be used directly.
std::vector< std::string > * get_history(const std::string &id)
Returns a pointer to the history vector associated with given id making a new one if it doesn't exist...
Definition: game.cpp:942
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:105
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
This file contains the settings handling of the widget library.
std::string definition
Parameters for the styled_widget.
virtual std::unique_ptr< widget > build() const override
Definition: text_box.cpp:431
Base class of a resolution, contains the common keys for a resolution.
std::vector< state_definition > state
text_box_definition(const config &cfg)
Definition: text_box.cpp:397
Holds a 2D point.
Definition: point.hpp:25
#define LOG_HEADER
Definition: text_box.cpp:31
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)