The Battle for Wesnoth  1.17.0-dev
text_box.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2021
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 
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 
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  set_is_dirty(true);
271  dragging_ |= start_selection;
272 }
273 
275 {
276  assert(config());
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 << ".\n";
351 
352  if(dragging_) {
353  handle_mouse_selection(coordinate, false);
354  }
355 
356  handled = true;
357 }
358 
360  bool& handled)
361 {
362  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
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 << ".\n";
380 
381  dragging_ = false;
382  handled = true;
383 }
384 
385 void
387  bool& handled)
388 {
389  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
390 
391  select_all();
392  handled = true;
393 }
394 
395 // }---------- DEFINITION ---------{
396 
399 {
400  DBG_GUI_P << "Parsing text_box " << id << '\n';
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(cfg.child("state_enabled"));
412  state.emplace_back(cfg.child("state_disabled"));
413  state.emplace_back(cfg.child("state_focused"));
414  state.emplace_back(cfg.child("state_hovered"));
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 
432 {
433  text_box* widget = new 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 
444 
445  DBG_GUI_G << "Window builder: placed text box '" << id
446  << "' with definition '" << definition << "'.\n";
447 
448  return widget;
449 }
450 
451 } // namespace implementation
452 
453 // }------------ END --------------
454 
455 } // namespace gui2
Define the common log macros for the gui toolkit.
Base class of a resolution, contains the common keys for a resolution.
void keyboard_capture(widget *widget)
Definition: window.cpp:1276
#define DBG_GUI_P
Definition: log.hpp:66
void select_all()
Selects all text.
int get_x() const
Definition: widget.cpp:315
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:402
void set_history(const std::string &id)
Definition: text_box.hpp:156
std::vector< state_definition > state
Abstract base class for text items.
unsigned pos_
The current position in the history.
Definition: text_box.hpp:115
virtual void place(const point &origin, const point &size) override
See widget::place.
void set_hint_data(const std::string &text, const std::string &image)
Definition: text_box.hpp:166
bool history_up()
Goes one item up in the history.
Definition: text_box.cpp:302
void set_maximum_length(const std::size_t maximum_length)
void set_selection_length(const int selection_length)
const std::string & id() const
Definition: widget.cpp:110
point get_column_line(const point &position) const
std::string get_value() const
This file contains the window object, this object is a top level container which has the event manage...
Base class for all widgets.
Definition: widget.hpp:49
unsigned int get_text_font_size() const
Resolves and returns the text_font_size.
std::size_t max_input_length_
The maximum length of the text input.
Definition: text_box.hpp:215
std::string down(const std::string &text="")
One step down in the history.
Definition: text_box.cpp:76
std::string get_value() const
Gets the current history value.
Definition: text_box.cpp:89
unsigned get_height() const
Definition: widget.cpp:330
STL namespace.
void set_max_input_length(const std::size_t length)
Definition: text_box.hpp:161
#define LOG_HEADER
Definition: text_box.cpp:29
virtual void place(const point &origin, const point &size) override
See widget::place.
Definition: text_box.cpp:127
void delete_selection() override
Inherited from text_box_base.
Definition: text_box.cpp:230
bool dragging_
Is the mouse in dragging mode, this affects selection in mouse move.
Definition: text_box.hpp:243
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:897
size_t get_composition_length() const
Get length of composition text by IME.
unsigned get_width() const
Definition: widget.cpp:325
void handle_mouse_selection(point mouse, const bool start_selection)
Definition: text_box.cpp:250
Class for a single line text area.
Definition: text_box.hpp:141
int x
x coordinate.
Definition: point.hpp:45
std::size_t get_selection_length() const
Generic file dialog.
Definition: field-fwd.hpp:23
void update_offsets()
Updates text_x_offset_ and text_y_offset_.
Definition: text_box.cpp:274
void set_font_style(const font::pango_text::FONT_STYLE font_style)
void set_maximum_width(const int width)
std::string definition
Parameters for the styled_widget.
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
std::string up(const std::string &text="")
One step up in the history.
Definition: text_box.cpp:58
This file contains the settings handling of the widget library.
EXIT_STATUS start(const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
Definition: editor_main.cpp:30
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:253
virtual const std::string & get_control_type() const override
Inherited from styled_widget, implemented by REGISTER_WIDGET.
void set_is_dirty(const bool is_dirty)
Definition: widget.cpp:466
std::size_t get_composition_start() const
std::vector< canvas > & get_canvases()
void set_wants_mouse_left_double_click(const bool click=true)
int get_text_maximum_width() const
Returns the maximum width available for the text.
int get_y() const
Definition: widget.cpp:320
point get_cursor_position(const unsigned column, const unsigned line=0) const
void set_font_size(const unsigned font_size)
unsigned text_height_
The height of the text itself.
Definition: text_box.hpp:237
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
void set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
bool enabled_
Is the history enabled.
Definition: text_box.hpp:118
Class for text input history.
Definition: text_box.hpp:36
std::size_t get_length() const
resolution_definition_ptr config()
text_box_definition(const config &cfg)
Definition: text_box.cpp:397
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&#39;t exist...
Definition: game.cpp:956
#define DBG_GUI_E
Definition: log.hpp:35
std::vector< std::string > * history_
The items in the history.
Definition: text_box.hpp:112
unsigned text_y_offset_
The y offset in the widget where the text starts.
Definition: text_box.hpp:230
window * get_window()
Get the parent window.
Definition: widget.cpp:117
void signal_handler_left_button_down(const event::ui_event event, bool &handled)
Definition: text_box.cpp:359
map_location curr
Definition: astarsearch.cpp:66
Holds a 2D point.
Definition: point.hpp:24
int get_text_maximum_height() const
Returns the maximum height available for the text.
void handle_key_tab(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
Definition: text_box.cpp:328
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
bool history_down()
Goes one item down in the history.
Definition: text_box.cpp:315
point get_mouse_position()
Returns the current mouse position.
Definition: helper.cpp:118
virtual void set_value(const std::string &text)
The set_value is virtual for the password_box class.
bool get_enabled() const
Definition: text_box.hpp:100
void handle_key_clear_line(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
Definition: text_box.cpp:339
void delete_char(const bool before_cursor) override
Inherited from text_box_base.
Definition: text_box.cpp:219
unsigned text_x_offset_
The x offset in the widget where the text starts.
Definition: text_box.hpp:223
void mouse_capture(const bool capture=true)
Definition: window.cpp:1270
std::string hint_image_
Image (such as a magnifying glass) that accompanies the help text.
Definition: text_box.hpp:249
void set_maximum_height(const int height, const bool multiline)
void push(const std::string &text)
Push string into the history.
Definition: text_box.cpp:45
text_box(const implementation::builder_styled_widget &builder)
Definition: text_box.cpp:98
std::size_t get_selection_start() const
void set_cursor(const std::size_t offset, const bool select)
Moves the cursor at the wanted position.
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
virtual bool can_wrap() const
Can the widget wrap.
Definition: widget.cpp:216
text_history history_
The history text for this widget.
Definition: text_box.hpp:212
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
std::string hint_text_
Helper text to display (such as "Search") if the text box is empty.
Definition: text_box.hpp:246
void signal_handler_mouse_motion(const event::ui_event event, bool &handled, const point &coordinate)
Definition: text_box.cpp:346
void signal_handler_left_button_double_click(const event::ui_event event, bool &handled)
Definition: text_box.cpp:386
int y
y coordinate.
Definition: point.hpp:48
#define DBG_GUI_G
Definition: log.hpp:41
void signal_handler_left_button_up(const event::ui_event event, bool &handled)
Definition: text_box.cpp:376
virtual widget * build() const override
Definition: text_box.cpp:431
virtual void update_canvas() override
See styled_widget::update_canvas.
Definition: text_box.cpp:140
Contains the implementation details for lexical_cast and shouldn&#39;t be used directly.
ui_event
The event send to the dispatcher.
Definition: handler.hpp:48