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