The Battle for Wesnoth  1.19.7+dev
text_box.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
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"
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 = prefs::get().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("editable", wfl::variant(is_editable()));
207 
208  tmp.set_variable("cursor_offset",
209  wfl::variant(get_cursor_position(start + length).x));
210 
211  tmp.set_variable("selection_offset", wfl::variant(start_offset));
212  tmp.set_variable("selection_width", wfl::variant(end_offset - start_offset));
213  tmp.set_variable("text_wrap_mode", wfl::variant(ellipse_mode));
214 
215  tmp.set_variable("composition_offset", wfl::variant(comp_start_offset));
216  tmp.set_variable("composition_width", wfl::variant(comp_end_offset - comp_start_offset));
217 
218  tmp.set_variable("hint_text", wfl::variant(hint_text_));
219  tmp.set_variable("hint_image", wfl::variant(hint_image_));
220  }
221 }
222 
223 void text_box::delete_char(const bool before_cursor)
224 {
225  if(before_cursor) {
226  set_cursor(get_selection_start() - 1, false);
227  }
228 
230 
232 }
233 
235 {
236  if(get_selection_length() == 0) {
237  return;
238  }
239 
240  // If we have a negative range change it to a positive range.
241  // This makes the rest of the algorithms easier.
242  int len = get_selection_length();
243  unsigned start = get_selection_start();
244  if(len < 0) {
245  len = -len;
246  start -= len;
247  }
248 
249  std::string tmp = get_value();
250  set_value(utf8::erase(tmp, start, len));
251  set_cursor(start, false);
252 }
253 
254 void text_box::handle_mouse_selection(point mouse, const bool start_selection)
255 {
256  mouse.x -= get_x();
257  mouse.y -= get_y();
258  // FIXME we don't test for overflow in width
259  if(mouse.x < static_cast<int>(text_x_offset_)
260  || mouse.y < static_cast<int>(text_y_offset_)
261  || mouse.y >= static_cast<int>(text_y_offset_ + text_height_)) {
262  return;
263  }
264 
265  int offset = get_column_line(point(mouse.x - text_x_offset_, mouse.y - text_y_offset_)).x;
266 
267  if(offset < 0) {
268  return;
269  }
270 
271 
272  set_cursor(offset, !start_selection);
273  update_canvas();
274  queue_redraw();
275  dragging_ |= start_selection;
276 }
277 
279 {
280  const auto conf = cast_config_to<text_box_definition>();
281  assert(conf);
282 
284 
285  wfl::map_formula_callable variables;
286  variables.add("height", wfl::variant(get_height()));
287  variables.add("width", wfl::variant(get_width()));
288  variables.add("text_font_height", wfl::variant(text_height_));
289 
290  text_x_offset_ = conf->text_x_offset(variables);
291  text_y_offset_ = conf->text_y_offset(variables);
292 
293  // Since this variable doesn't change set it here instead of in
294  // update_canvas().
295  for(auto & tmp : get_canvases())
296  {
297  tmp.set_variable("text_font_height", wfl::variant(text_height_));
298  }
299 
300  // Force an update of the canvas since now text_font_height is known.
301  update_canvas();
302 }
303 
305 {
306  if(!history_.get_enabled()) {
307  return false;
308  }
309 
310  const std::string str = history_.up(get_value());
311  if(!str.empty()) {
312  set_value(str);
313  }
314  return true;
315 }
316 
318 {
319  if(!history_.get_enabled()) {
320  return false;
321  }
322 
323  const std::string str = history_.down(get_value());
324  if(!str.empty()) {
325  set_value(str);
326  }
327  return true;
328 }
329 
330 void text_box::handle_key_tab(SDL_Keymod modifier, bool& handled)
331 {
332  if(modifier & KMOD_CTRL) {
333  if(!(modifier & KMOD_SHIFT)) {
334  handled = history_up();
335  } else {
336  handled = history_down();
337  }
338  }
339 }
340 
341 void text_box::handle_key_clear_line(SDL_Keymod /*modifier*/, bool& handled)
342 {
343  handled = true;
344 
345  set_value("");
346 }
347 
349  bool& handled,
350  const point& coordinate)
351 {
352  DBG_GUI_E << get_control_type() << "[" << id() << "]: " << event << ".";
353 
354  if(dragging_) {
356  }
357 
358  handled = true;
359 }
360 
362  bool& handled)
363 {
364  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
365 
366  get_window()->keyboard_capture(this);
368 
370 
371  handled = true;
372 }
373 
375  bool& handled)
376 {
377  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
378 
379  dragging_ = false;
380  handled = true;
381 }
382 
383 void
385  bool& handled)
386 {
387  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
388 
389  select_all();
390  handled = true;
391 }
392 
393 // }---------- DEFINITION ---------{
394 
397 {
398  DBG_GUI_P << "Parsing text_box " << id;
399 
400  load_resolutions<resolution>(cfg);
401 }
402 
404  : resolution_definition(cfg)
405  , text_x_offset(cfg["text_x_offset"])
406  , text_y_offset(cfg["text_y_offset"])
407 {
408  // Note the order should be the same as the enum state_t in text_box.hpp.
409  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", missing_mandatory_wml_tag("text_box_definition][resolution", "state_enabled")));
410  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", missing_mandatory_wml_tag("text_box_definition][resolution", "state_disabled")));
411  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_focused", missing_mandatory_wml_tag("text_box_definition][resolution", "state_focused")));
412  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_hovered", missing_mandatory_wml_tag("text_box_definition][resolution", "state_hovered")));
413 }
414 
415 // }---------- BUILDER -----------{
416 
417 namespace implementation
418 {
419 
420 builder_text_box::builder_text_box(const config& cfg)
421  : builder_styled_widget(cfg)
422  , history(cfg["history"])
423  , max_input_length(cfg["max_input_length"].to_size_t())
424  , hint_text(cfg["hint_text"].t_str())
425  , hint_image(cfg["hint_image"])
426  , editable(cfg["editable"].to_bool(true))
427 {
428 }
429 
430 std::unique_ptr<widget> builder_text_box::build() const
431 {
432  auto widget = std::make_unique<text_box>(*this);
433 
434  // A textbox doesn't have a label but a text
435  widget->set_value(label_string);
436 
437  if(!history.empty()) {
438  widget->set_history(history);
439  }
440 
441  widget->set_max_input_length(max_input_length);
442  widget->set_hint_data(hint_text, hint_image);
443  widget->set_editable(editable);
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:64
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
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
Wrapper function, returns length of the text in pango column offsets.
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)
virtual 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)
bool is_editable() const
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.
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:225
void handle_key_clear_line(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
Definition: text_box.cpp:341
text_history history_
The history text for this widget.
Definition: text_box.hpp:222
bool history_down()
Goes one item down in the history.
Definition: text_box.cpp:317
std::string hint_image_
Image (such as a magnifying glass) that accompanies the help text.
Definition: text_box.hpp:259
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:348
void delete_char(const bool before_cursor) override
Inherited from text_box_base.
Definition: text_box.cpp:223
void signal_handler_left_button_down(const event::ui_event event, bool &handled)
Definition: text_box.cpp:361
unsigned text_height_
The height of the text itself.
Definition: text_box.hpp:247
std::string hint_text_
Helper text to display (such as "Search") if the text box is empty.
Definition: text_box.hpp:256
bool history_up()
Goes one item up in the history.
Definition: text_box.cpp:304
void handle_key_tab(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
Definition: text_box.cpp:330
void handle_mouse_selection(point mouse, const bool start_selection)
Definition: text_box.cpp:254
void signal_handler_left_button_double_click(const event::ui_event event, bool &handled)
Definition: text_box.cpp:384
bool dragging_
Is the mouse in dragging mode, this affects selection in mouse move.
Definition: text_box.hpp:253
void update_offsets()
Updates text_x_offset_ and text_y_offset_.
Definition: text_box.cpp:278
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:234
void signal_handler_left_button_up(const event::ui_event event, bool &handled)
Definition: text_box.cpp:374
unsigned text_y_offset_
The y offset in the widget where the text starts.
Definition: text_box.hpp:240
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:233
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:55
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:464
int get_x() const
Definition: widget.cpp:326
unsigned get_width() const
Definition: widget.cpp:336
int get_y() const
Definition: widget.cpp:331
unsigned get_height() const
Definition: widget.cpp:341
const std::string & id() const
Definition: widget.cpp:110
window * get_window()
Get the parent window.
Definition: widget.cpp:117
virtual bool can_wrap() const
Can the widget wrap.
Definition: widget.cpp:225
void keyboard_capture(widget *widget)
Definition: window.cpp:1207
void mouse_capture(const bool capture=true)
Definition: window.cpp:1201
static prefs & get()
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...
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:253
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:202
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:1142
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:173
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(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
#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:430
std::vector< state_definition > state
text_box_definition(const config &cfg)
Definition: text_box.cpp:395
Holds a 2D point.
Definition: point.hpp:25
#define LOG_HEADER
Definition: text_box.cpp:31
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)