The Battle for Wesnoth  1.19.0-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/window.hpp"
23 #include "preferences/game.hpp"
25 #include <functional>
26 #include "wml_exception.hpp"
27 
28 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
29 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
30 
31 namespace gui2
32 {
33 
34 // ------------ WIDGET -----------{
35 
36 REGISTER_WIDGET(text_box)
37 
38 text_history text_history::get_history(const std::string& id,
39  const bool enabled)
40 {
41  std::vector<std::string>* vec = preferences::get_history(id);
42  return text_history(vec, enabled);
43 }
44 
45 void text_history::push(const std::string& text)
46 {
47  if(!enabled_) {
48  return;
49  } else {
50  if(!text.empty() && (history_->empty() || text != history_->back())) {
51  history_->push_back(text);
52  }
53 
54  pos_ = history_->size();
55  }
56 }
57 
58 std::string text_history::up(const std::string& text)
59 {
60 
61  if(!enabled_) {
62  return "";
63  } else if(pos_ == history_->size()) {
64  unsigned curr = pos_;
65  push(text);
66  pos_ = curr;
67  }
68 
69  if(pos_ != 0) {
70  --pos_;
71  }
72 
73  return get_value();
74 }
75 
76 std::string text_history::down(const std::string& text)
77 {
78  if(!enabled_) {
79  return "";
80  } else if(pos_ == history_->size()) {
81  push(text);
82  } else {
83  pos_++;
84  }
85 
86  return get_value();
87 }
88 
89 std::string text_history::get_value() const
90 {
91  if(!enabled_ || pos_ == history_->size()) {
92  return "";
93  } else {
94  return history_->at(pos_);
95  }
96 }
97 
99  : text_box_base(builder, type())
100  , history_()
101  , max_input_length_(0)
102  , text_x_offset_(0)
103  , text_y_offset_(0)
104  , text_height_(0)
105  , dragging_(false)
106 {
108 
109  connect_signal<event::MOUSE_MOTION>(std::bind(
110  &text_box::signal_handler_mouse_motion, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
111  connect_signal<event::LEFT_BUTTON_DOWN>(std::bind(
112  &text_box::signal_handler_left_button_down, this, std::placeholders::_2, std::placeholders::_3));
113  connect_signal<event::LEFT_BUTTON_UP>(std::bind(
114  &text_box::signal_handler_left_button_up, this, std::placeholders::_2, std::placeholders::_3));
115  connect_signal<event::LEFT_BUTTON_DOUBLE_CLICK>(std::bind(
116  &text_box::signal_handler_left_button_double_click, this, std::placeholders::_2, std::placeholders::_3));
117 
118  const auto conf = cast_config_to<text_box_definition>();
119  assert(conf);
120 
122  set_font_style(conf->text_font_style);
123 
124  update_offsets();
125 }
126 
127 void text_box::place(const point& origin, const point& size)
128 {
129  // Inherited.
130  styled_widget::place(origin, size);
131 
134 
136 
137  update_offsets();
138 }
139 
141 {
142  /***** Gather the info *****/
143 
144  // Set the cursor info.
145  const unsigned start = get_selection_start();
146  const int length = get_selection_length();
147 
148  // Set the cursor info.
149  const unsigned edit_start = get_composition_start();
150  const int edit_length = get_composition_length();
151 
153 
154  PangoEllipsizeMode ellipse_mode = PANGO_ELLIPSIZE_NONE;
155  if(!can_wrap()) {
156  if((start + length) > (get_length() / 2)) {
157  ellipse_mode = PANGO_ELLIPSIZE_START;
158  } else {
159  ellipse_mode = PANGO_ELLIPSIZE_END;
160  }
161  }
162  set_ellipse_mode(ellipse_mode);
163 
164  // Set the selection info
165  unsigned start_offset = 0;
166  unsigned end_offset = 0;
167  if(length == 0) {
168  // No nothing.
169  } else if(length > 0) {
170  start_offset = get_cursor_position(start).x;
171  end_offset = get_cursor_position(start + length).x;
172  } else {
173  start_offset = get_cursor_position(start + length).x;
174  end_offset = get_cursor_position(start).x;
175  }
176 
177  // Set the composition info
178  unsigned comp_start_offset = 0;
179  unsigned comp_end_offset = 0;
180  if(edit_length == 0) {
181  // No nothing.
182  } else if(edit_length > 0) {
183  comp_start_offset = get_cursor_position(edit_start).x;
184  comp_end_offset = get_cursor_position(edit_start + edit_length).x;
185  } else {
186  comp_start_offset = get_cursor_position(edit_start + edit_length).x;
187  comp_end_offset = get_cursor_position(edit_start).x;
188  }
189 
190  /***** Set in all canvases *****/
191 
192  const int max_width = get_text_maximum_width();
193  const int max_height = get_text_maximum_height();
194 
195  for(auto & tmp : get_canvases())
196  {
197 
198  tmp.set_variable("text", wfl::variant(get_value()));
199  tmp.set_variable("text_x_offset", wfl::variant(text_x_offset_));
200  tmp.set_variable("text_y_offset", wfl::variant(text_y_offset_));
201  tmp.set_variable("text_maximum_width", wfl::variant(max_width));
202  tmp.set_variable("text_maximum_height", wfl::variant(max_height));
203 
204  tmp.set_variable("cursor_offset",
205  wfl::variant(get_cursor_position(start + length).x));
206 
207  tmp.set_variable("selection_offset", wfl::variant(start_offset));
208  tmp.set_variable("selection_width", wfl::variant(end_offset - start_offset));
209  tmp.set_variable("text_wrap_mode", wfl::variant(ellipse_mode));
210 
211  tmp.set_variable("composition_offset", wfl::variant(comp_start_offset));
212  tmp.set_variable("composition_width", wfl::variant(comp_end_offset - comp_start_offset));
213 
214  tmp.set_variable("hint_text", wfl::variant(hint_text_));
215  tmp.set_variable("hint_image", wfl::variant(hint_image_));
216  }
217 }
218 
219 void text_box::delete_char(const bool before_cursor)
220 {
221  if(before_cursor) {
222  set_cursor(get_selection_start() - 1, false);
223  }
224 
226 
228 }
229 
231 {
232  if(get_selection_length() == 0) {
233  return;
234  }
235 
236  // If we have a negative range change it to a positive range.
237  // This makes the rest of the algorithms easier.
238  int len = get_selection_length();
239  unsigned start = get_selection_start();
240  if(len < 0) {
241  len = -len;
242  start -= len;
243  }
244 
245  std::string tmp = get_value();
246  set_value(utf8::erase(tmp, start, len));
247  set_cursor(start, false);
248 }
249 
250 void text_box::handle_mouse_selection(point mouse, const bool start_selection)
251 {
252  mouse.x -= get_x();
253  mouse.y -= get_y();
254  // FIXME we don't test for overflow in width
255  if(mouse.x < static_cast<int>(text_x_offset_)
256  || mouse.y < static_cast<int>(text_y_offset_)
257  || mouse.y >= static_cast<int>(text_y_offset_ + text_height_)) {
258  return;
259  }
260 
261  int offset = get_column_line(point(mouse.x - text_x_offset_, mouse.y - text_y_offset_)).x;
262 
263  if(offset < 0) {
264  return;
265  }
266 
267 
268  set_cursor(offset, !start_selection);
269  update_canvas();
270  queue_redraw();
271  dragging_ |= start_selection;
272 }
273 
275 {
276  const auto conf = cast_config_to<text_box_definition>();
277  assert(conf);
278 
280 
281  wfl::map_formula_callable variables;
282  variables.add("height", wfl::variant(get_height()));
283  variables.add("width", wfl::variant(get_width()));
284  variables.add("text_font_height", wfl::variant(text_height_));
285 
286  text_x_offset_ = conf->text_x_offset(variables);
287  text_y_offset_ = conf->text_y_offset(variables);
288 
289  // Since this variable doesn't change set it here instead of in
290  // update_canvas().
291  for(auto & tmp : get_canvases())
292  {
293  tmp.set_variable("text_font_height", wfl::variant(text_height_));
294  }
295 
296  // Force an update of the canvas since now text_font_height is known.
297  update_canvas();
298 }
299 
301 {
302  if(!history_.get_enabled()) {
303  return false;
304  }
305 
306  const std::string str = history_.up(get_value());
307  if(!str.empty()) {
308  set_value(str);
309  }
310  return true;
311 }
312 
314 {
315  if(!history_.get_enabled()) {
316  return false;
317  }
318 
319  const std::string str = history_.down(get_value());
320  if(!str.empty()) {
321  set_value(str);
322  }
323  return true;
324 }
325 
326 void text_box::handle_key_tab(SDL_Keymod modifier, bool& handled)
327 {
328  if(modifier & KMOD_CTRL) {
329  if(!(modifier & KMOD_SHIFT)) {
330  handled = history_up();
331  } else {
332  handled = history_down();
333  }
334  }
335 }
336 
337 void text_box::handle_key_clear_line(SDL_Keymod /*modifier*/, bool& handled)
338 {
339  handled = true;
340 
341  set_value("");
342 }
343 
345  bool& handled,
346  const point& coordinate)
347 {
348  DBG_GUI_E << get_control_type() << "[" << id() << "]: " << event << ".";
349 
350  if(dragging_) {
352  }
353 
354  handled = true;
355 }
356 
358  bool& handled)
359 {
360  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
361 
362  /*
363  * Copied from the base class see how we can do inheritance with the new
364  * system...
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"])
424  , hint_text(cfg["hint_text"].t_str())
425  , hint_image(cfg["hint_image"])
426 {
427 }
428 
429 std::unique_ptr<widget> builder_text_box::build() const
430 {
431  auto widget = std::make_unique<text_box>(*this);
432 
433  // A textbox doesn't have a label but a text
434  widget->set_value(label_string);
435 
436  if(!history.empty()) {
437  widget->set_history(history);
438  }
439 
440  widget->set_max_input_length(max_input_length);
441  widget->set_hint_data(hint_text, hint_image);
442 
443  DBG_GUI_G << "Window builder: placed text box '" << id
444  << "' with definition '" << definition << "'.";
445 
446  return widget;
447 }
448 
449 } // namespace implementation
450 
451 // }------------ END --------------
452 
453 } // 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:159
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)
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:127
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:337
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:313
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:344
void delete_char(const bool before_cursor) override
Inherited from text_box_base.
Definition: text_box.cpp:219
void signal_handler_left_button_down(const event::ui_event event, bool &handled)
Definition: text_box.cpp:357
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:300
void handle_key_tab(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
Definition: text_box.cpp:326
void handle_mouse_selection(point mouse, const bool start_selection)
Definition: text_box.cpp:250
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:243
void update_offsets()
Updates text_x_offset_ and text_y_offset_.
Definition: text_box.cpp:274
text_box(const implementation::builder_styled_widget &builder)
Definition: text_box.cpp:98
void delete_selection() override
Inherited from text_box_base.
Definition: text_box.cpp:230
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:230
virtual void update_canvas() override
See styled_widget::update_canvas.
Definition: text_box.cpp:140
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:89
std::string down(const std::string &text="")
One step down in the history.
Definition: text_box.cpp:76
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:45
std::string up(const std::string &text="")
One step up in the history.
Definition: text_box.cpp:58
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:455
int get_x() const
Definition: widget.cpp:317
unsigned get_width() const
Definition: widget.cpp:327
int get_y() const
Definition: widget.cpp:322
unsigned get_height() const
Definition: widget.cpp:332
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:216
void keyboard_capture(widget *widget)
Definition: window.cpp:1215
void mouse_capture(const bool capture=true)
Definition: window.cpp:1209
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:1007
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:112
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:939
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.
std::string definition
Parameters for the styled_widget.
virtual std::unique_ptr< widget > build() const override
Definition: text_box.cpp:429
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:395
Holds a 2D point.
Definition: point.hpp:25
#define LOG_HEADER
Definition: text_box.cpp:29
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)