The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
text_box.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2008 - 2025
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
19
20#include "gui/core/log.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
33namespace gui2
34{
35
36// ------------ WIDGET -----------{
37
38REGISTER_WIDGET(text_box)
39
40text_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
47void 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
60std::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
78std::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
91std::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
127}
128
129void text_box::place(const point& origin, const point& size)
130{
131 // Inherited.
132 styled_widget::place(origin, size);
133
136
138
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
223void 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
254void 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);
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
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.
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
330void text_box::handle_key_tab(SDL_Keymod modifier, bool& handled)
331{
332 if(modifier & SDL_KMOD_CTRL) {
333 if(!(modifier & SDL_KMOD_SHIFT)) {
334 handled = history_up();
335 } else {
336 handled = history_down();
337 }
338 }
339}
340
341void 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_) {
355 handle_mouse_selection(coordinate, false);
356 }
357
358 handled = true;
359}
360
362 bool& handled)
363{
364 DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
365
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
383void
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
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
417namespace implementation
418{
419
420builder_text_box::builder_text_box(const config& 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
430std::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
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
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_text_maximum_height() const
Returns the maximum height available for the text.
std::vector< canvas > & get_canvases()
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_composition_length() const
Get length of composition text by IME.
std::size_t get_length() const
Wrapper function, see font::pango_text::get_length.
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
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
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 const std::string & get_control_type() const override
Inherited from styled_widget, implemented by REGISTER_WIDGET.
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 capture_and_show_keyboard(widget *widget)
Definition window.cpp:1205
void mouse_capture(const bool capture=true)
Definition window.cpp:1191
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:249
#define LOG_HEADER
const config * cfg
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...
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:958
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:167
Contains the implementation details for lexical_cast and shouldn't be used directly.
STL namespace.
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
#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
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)