The Battle for Wesnoth  1.15.2+dev
text_box.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2018 by Mark de Wever <koraq@xs4all.nl>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "gui/widgets/text_box.hpp"
18 
19 #include "font/sdl_ttf.hpp"
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 "utils/functional.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 
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, _2, _3, _5));
111  connect_signal<event::LEFT_BUTTON_DOWN>(std::bind(
113  connect_signal<event::LEFT_BUTTON_UP>(std::bind(
115  connect_signal<event::LEFT_BUTTON_DOUBLE_CLICK>(std::bind(
117 
118  const auto conf = cast_config_to<text_box_definition>();
119  assert(conf);
120 
121  set_font_size(conf->text_font_size);
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 
281  // FIXME: This should use pango-cairo code path instead of sdl_ttf code path
282  text_height_ = font::get_max_height(conf->text_font_size);
283 
284  wfl::map_formula_callable variables;
285  variables.add("height", wfl::variant(get_height()));
286  variables.add("width", wfl::variant(get_width()));
287  variables.add("text_font_height", wfl::variant(text_height_));
288 
289  text_x_offset_ = conf->text_x_offset(variables);
290  text_y_offset_ = conf->text_y_offset(variables);
291 
292  // Since this variable doesn't change set it here instead of in
293  // update_canvas().
294  for(auto & tmp : get_canvases())
295  {
296  tmp.set_variable("text_font_height", wfl::variant(text_height_));
297  }
298 
299  // Force an update of the canvas since now text_font_height is known.
300  update_canvas();
301 }
302 
304 {
305  if(!history_.get_enabled()) {
306  return false;
307  }
308 
309  const std::string str = history_.up(get_value());
310  if(!str.empty()) {
311  set_value(str);
312  }
313  return true;
314 }
315 
317 {
318  if(!history_.get_enabled()) {
319  return false;
320  }
321 
322  const std::string str = history_.down(get_value());
323  if(!str.empty()) {
324  set_value(str);
325  }
326  return true;
327 }
328 
329 void text_box::handle_key_tab(SDL_Keymod modifier, bool& handled)
330 {
331  if(modifier & KMOD_CTRL) {
332  if(!(modifier & KMOD_SHIFT)) {
333  handled = history_up();
334  } else {
335  handled = history_down();
336  }
337  }
338 }
339 
340 void text_box::handle_key_clear_line(SDL_Keymod /*modifier*/, bool& handled)
341 {
342  handled = true;
343 
344  set_value("");
345 }
346 
348  bool& handled,
349  const point& coordinate)
350 {
351  DBG_GUI_E << get_control_type() << "[" << id() << "]: " << event << ".\n";
352 
353  if(dragging_) {
354  handle_mouse_selection(coordinate, false);
355  }
356 
357  handled = true;
358 }
359 
361  bool& handled)
362 {
363  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
364 
365  /*
366  * Copied from the base class see how we can do inheritance with the new
367  * system...
368  */
369  get_window()->keyboard_capture(this);
371 
373 
374  handled = true;
375 }
376 
378  bool& handled)
379 {
380  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
381 
382  dragging_ = false;
383  handled = true;
384 }
385 
386 void
388  bool& handled)
389 {
390  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
391 
392  select_all();
393  handled = true;
394 }
395 
396 // }---------- DEFINITION ---------{
397 
400 {
401  DBG_GUI_P << "Parsing text_box " << id << '\n';
402 
403  load_resolutions<resolution>(cfg);
404 }
405 
406 /*WIKI
407  * @page = GUIWidgetDefinitionWML
408  * @order = 1_text_box
409  *
410  * == Text box ==
411  *
412  * The definition of a text box.
413  *
414  * @begin{parent}{name="gui/"}
415  * @begin{tag}{name="ext_box_definition"}{min=0}{max=-1}{super="generic/widget_definition"}
416  * The resolution for a text box also contains the following keys:
417  * @begin{tag}{name="resolution"}{min=0}{max=-1}{super=generic/widget_definition/resolution}
418  * @begin{table}{config}
419  * text_x_offset & f_unsigned & "" & The x offset of the text in the text
420  * box. This is needed for the code to
421  * determine where in the text the mouse
422  * clicks, so it can set the cursor
423  * properly. $
424  * text_y_offset & f_unsigned & "" & The y offset of the text in the text
425  * box. $
426  * @end{table}
427  *
428  * The following states exist:
429  * * state_enabled, the text box is enabled.
430  * * state_disabled, the text box is disabled.
431  * * state_focused, the text box has the focus of the keyboard.
432  * @begin{tag}{name="state_enabled"}{min=0}{max=1}{super="generic/state"}
433  * @end{tag}{name="state_enabled"}
434  * @begin{tag}{name="state_disabled"}{min=0}{max=1}{super="generic/state"}
435  * @end{tag}{name="state_disabled"}
436  * @begin{tag}{name="state_focused"}{min=0}{max=1}{super="generic/state"}
437  * @end{tag}{name="state_focused"}
438  * @end{tag}{name="resolution"}
439  * @end{tag}{name="ext_box_definition"}
440  * @end{parent}{name="gui/"}
441  */
443  : resolution_definition(cfg)
444  , text_x_offset(cfg["text_x_offset"])
445  , text_y_offset(cfg["text_y_offset"])
446 {
447  // Note the order should be the same as the enum state_t in text_box.hpp.
448  state.emplace_back(cfg.child("state_enabled"));
449  state.emplace_back(cfg.child("state_disabled"));
450  state.emplace_back(cfg.child("state_focused"));
451 }
452 
453 // }---------- BUILDER -----------{
454 
455 /*WIKI
456  * @page = GUIWidgetInstanceWML
457  * @order = 2_text_box
458  *
459  * == Text box ==
460  * @begin{parent}{name="gui/window/resolution/grid/row/column/"}
461  * @begin{tag}{name="text_box"}{min="0"}{max="-1"}{super="generic/widget_instance"}
462  * @begin{table}{config}
463  * label & t_string & "" & The initial text of the text box. $
464  * history & string & "" & The name of the history for the text
465  * box.
466  * A history saves the data entered in a
467  * text box between the games. With the up
468  * and down arrow it can be accessed. To
469  * create a new history item just add a
470  * new unique name for this field and the
471  * engine will handle the rest. $
472  * @end{table}
473  * @end{tag}{name="text_box"}
474  * @end{parent}{name="gui/window/resolution/grid/row/column/"}
475  */
476 
477 namespace implementation
478 {
479 
480 builder_text_box::builder_text_box(const config& cfg)
481  : builder_styled_widget(cfg)
482  , history(cfg["history"])
483  , max_input_length(cfg["max_input_length"])
484  , hint_text(cfg["hint_text"].t_str())
485  , hint_image(cfg["hint_image"])
486 {
487 }
488 
490 {
491  text_box* widget = new text_box(*this);
492 
493  // A textbox doesn't have a label but a text
494  widget->set_value(label_string);
495 
496  if(!history.empty()) {
497  widget->set_history(history);
498  }
499 
502 
503  DBG_GUI_G << "Window builder: placed text box '" << id
504  << "' with definition '" << definition << "'.\n";
505 
506  return widget;
507 }
508 
509 } // namespace implementation
510 
511 // }------------ END --------------
512 
513 } // 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:1282
#define DBG_GUI_P
Definition: log.hpp:68
void select_all()
Selects all text.
int get_x() const
Definition: widget.cpp:312
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:420
void set_history(const std::string &id)
Definition: text_box.hpp:136
std::vector< state_definition > state
Abstract base class for text items.
unsigned pos_
The current position in the history.
Definition: text_box.hpp:114
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:146
bool history_up()
Goes one item up in the history.
Definition: text_box.cpp:303
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:107
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:47
std::size_t max_input_length_
The maximum length of the text input.
Definition: text_box.hpp:195
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:327
STL namespace.
void set_max_input_length(const std::size_t length)
Definition: text_box.hpp:141
#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:223
size_t get_composition_length() const
Get length of composition text by IME.
unsigned get_width() const
Definition: widget.cpp:322
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:121
int x
x coordinate.
Definition: point.hpp:44
std::size_t get_selection_length() const
Generic file dialog.
Definition: field-fwd.hpp:22
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:86
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.
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:252
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:463
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:317
point get_cursor_position(const unsigned column, const unsigned line=0) const
void set_font_size(const unsigned font_size)
int get_max_height(int size)
Definition: sdl_ttf.cpp:407
unsigned text_height_
The height of the text itself.
Definition: text_box.hpp:217
#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:117
Class for text input history.
Definition: text_box.hpp:35
std::size_t get_length() const
resolution_definition_ptr config()
text_box_definition(const config &cfg)
Definition: text_box.cpp:398
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:962
#define DBG_GUI_E
Definition: log.hpp:34
std::vector< std::string > * history_
The items in the history.
Definition: text_box.hpp:111
unsigned text_y_offset_
The y offset in the widget where the text starts.
Definition: text_box.hpp:210
window * get_window()
Get the parent window.
Definition: widget.cpp:114
void signal_handler_left_button_down(const event::ui_event event, bool &handled)
Definition: text_box.cpp:360
map_location curr
Definition: astarsearch.cpp:65
Holds a 2D point.
Definition: point.hpp:23
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:329
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:104
bool history_down()
Goes one item down in the history.
Definition: text_box.cpp:316
point get_mouse_position()
Returns the current mouse position.
Definition: helper.cpp:116
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:99
void handle_key_clear_line(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
Definition: text_box.cpp:340
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:203
void mouse_capture(const bool capture=true)
Definition: window.cpp:1276
std::string hint_image_
Image (such as a magnifying glass) that accompanies the help text.
Definition: text_box.hpp:229
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
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:28
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:213
text_history history_
The history text for this widget.
Definition: text_box.hpp:192
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
std::string hint_text_
Helper text to display (such as "Search") if the text box is empty.
Definition: text_box.hpp:226
void signal_handler_mouse_motion(const event::ui_event event, bool &handled, const point &coordinate)
Definition: text_box.cpp:347
void signal_handler_left_button_double_click(const event::ui_event event, bool &handled)
Definition: text_box.cpp:387
int y
y coordinate.
Definition: point.hpp:47
#define DBG_GUI_G
Definition: log.hpp:40
void signal_handler_left_button_up(const event::ui_event event, bool &handled)
Definition: text_box.cpp:377
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:55