The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
combobox.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2024 - 2025
3 by Subhraman Sarkar (babaissarkar) <sbmskmm@protonmail.com>
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 "cursor.hpp"
21#include "gui/core/log.hpp"
25#include "wml_exception.hpp"
26
27#include <functional>
28
29#define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
30#define LOG_HEADER LOG_SCOPE_HEADER + ':'
31
32
33namespace gui2
34{
35
36// ------------ WIDGET -----------{
37
38REGISTER_WIDGET(combobox)
39
40combobox::combobox(const implementation::builder_combobox& builder)
41 : text_box_base(builder, type())
42 , max_input_length_(builder.max_input_length)
43 , text_offset_{0, 0}
44 , text_height_(0)
45 , dragging_(false)
46 , hint_text_(builder.hint_text)
47 , hint_image_(builder.hint_image)
48 , values_()
49 , selected_(0)
50{
51 values_.emplace_back("label", this->get_label());
52
53 set_wants_mouse_left_double_click();
54
55 connect_signal<event::MOUSE_MOTION>(std::bind(
56 &combobox::signal_handler_mouse_motion, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
57 connect_signal<event::LEFT_BUTTON_DOWN>(std::bind(
58 &combobox::signal_handler_left_button_down, this, std::placeholders::_2, std::placeholders::_3));
59 connect_signal<event::LEFT_BUTTON_UP>(std::bind(
60 &combobox::signal_handler_left_button_up, this, std::placeholders::_2, std::placeholders::_3));
61 connect_signal<event::LEFT_BUTTON_DOUBLE_CLICK>(std::bind(
62 &combobox::signal_handler_left_button_double_click, this, std::placeholders::_2, std::placeholders::_3));
63 connect_signal<event::MOUSE_ENTER>(
64 std::bind(&combobox::signal_handler_mouse_enter, this, std::placeholders::_2, std::placeholders::_3));
65
66 const auto conf = cast_config_to<combobox_definition>();
67 assert(conf);
68
69 set_font_size(get_text_font_size());
70 set_font_style(conf->text_font_style);
71
72 update_offsets();
73}
74
75void combobox::place(const point& origin, const point& size)
76{
77 // Inherited.
78 styled_widget::place(origin, size);
79
82
84
86}
87
89{
90 // Gather the info
91
92 // Set the cursor info.
93 const unsigned start = get_selection_start();
94 const int length = get_selection_length();
95
96 // Set the cursor info.
97 const unsigned edit_start = get_composition_start();
98 const int edit_length = get_composition_length();
99
101
102 PangoEllipsizeMode ellipse_mode = PANGO_ELLIPSIZE_NONE;
103 if(!can_wrap()) {
104 if((start + length) > (get_length() / 2)) {
105 ellipse_mode = PANGO_ELLIPSIZE_START;
106 } else {
107 ellipse_mode = PANGO_ELLIPSIZE_END;
108 }
109 }
110 set_ellipse_mode(ellipse_mode);
111
112 // Set the selection info
113 unsigned start_offset = 0;
114 unsigned end_offset = 0;
115 if(length == 0) {
116 // Do nothing.
117 } else if(length > 0) {
118 start_offset = get_cursor_position(start).x;
119 end_offset = get_cursor_position(start + length).x;
120 } else {
121 start_offset = get_cursor_position(start + length).x;
122 end_offset = get_cursor_position(start).x;
123 }
124
125 // Set the composition info
126 unsigned comp_start_offset = 0;
127 unsigned comp_end_offset = 0;
128 if(edit_length == 0) {
129 // Do nothing.
130 } else if(edit_length > 0) {
131 comp_start_offset = get_cursor_position(edit_start).x;
132 comp_end_offset = get_cursor_position(edit_start + edit_length).x;
133 } else {
134 comp_start_offset = get_cursor_position(edit_start + edit_length).x;
135 comp_end_offset = get_cursor_position(edit_start).x;
136 }
137
138 // Set in all canvases
139
140 const int max_width = get_text_maximum_width() - ICON_SIZE;
141 const int max_height = get_text_maximum_height();
142
143 for(auto & tmp : get_canvases())
144 {
145
146 tmp.set_variable("text", wfl::variant(get_value()));
147 tmp.set_variable("text_x_offset", wfl::variant(text_offset_.x));
148 tmp.set_variable("text_y_offset", wfl::variant(text_offset_.y));
149 tmp.set_variable("text_maximum_width", wfl::variant(max_width));
150 tmp.set_variable("text_maximum_height", wfl::variant(max_height));
151
152 tmp.set_variable("cursor_offset",
153 wfl::variant(get_cursor_position(start + length).x));
154
155 tmp.set_variable("selection_offset", wfl::variant(start_offset));
156 tmp.set_variable("selection_width", wfl::variant(end_offset - start_offset));
157 tmp.set_variable("text_wrap_mode", wfl::variant(ellipse_mode));
158
159 tmp.set_variable("composition_offset", wfl::variant(comp_start_offset));
160 tmp.set_variable("composition_width", wfl::variant(comp_end_offset - comp_start_offset));
161
162 tmp.set_variable("hint_text", wfl::variant(hint_text_));
163 tmp.set_variable("hint_image", wfl::variant(hint_image_));
164 }
165}
166
167void combobox::delete_char(const bool before_cursor)
168{
169 if(before_cursor) {
170 set_cursor(get_selection_start() - 1, false);
171 }
172
174
176}
177
179{
180 if(get_selection_length() == 0) {
181 return;
182 }
183
184 // If we have a negative range change it to a positive range.
185 // This makes the rest of the algorithms easier.
186 int len = get_selection_length();
187 unsigned start = get_selection_start();
188 if(len < 0) {
189 len = -len;
190 start -= len;
191 }
192
193 std::string tmp = get_value();
194 set_value(utf8::erase(tmp, start, len));
195 set_cursor(start, false);
196}
197
198void combobox::handle_mouse_selection(point mouse, const bool start_selection)
199{
200 mouse.x -= get_x();
201 mouse.y -= get_y();
202 // FIXME we don't test for overflow in width
203 if(mouse.x < text_offset_.x
204 || mouse.y < text_offset_.y
205 || mouse.y >= text_offset_.y + static_cast<int>(text_height_)) {
206 return;
207 }
208
209 int offset = get_column_line(mouse - text_offset_).x;
210
211 if(offset < 0) {
212 return;
213 }
214
215 set_cursor(offset, !start_selection);
217 queue_redraw();
218 dragging_ |= start_selection;
219}
220
222{
223 const auto conf = cast_config_to<combobox_definition>();
224 assert(conf);
225
227
229 variables.add("height", wfl::variant(get_height()));
230 variables.add("width", wfl::variant(get_width()));
231 variables.add("text_font_height", wfl::variant(text_height_));
232
233 text_offset_ = {
234 static_cast<int>(conf->text_x_offset(variables)),
235 static_cast<int>(conf->text_y_offset(variables))
236 };
237
238 // Since this variable doesn't change set it here instead of in update_canvas().
239 for(auto & tmp : get_canvases())
240 {
241 tmp.set_variable("text_font_height", wfl::variant(text_height_));
242 }
243
244 // Force an update of the canvas since now text_font_height is known.
246}
247
248void combobox::handle_key_clear_line(SDL_Keymod /*modifier*/, bool& handled)
249{
250 handled = true;
251 set_value("");
252}
253
254void combobox::handle_key_up_arrow(SDL_Keymod /*modifier*/, bool& handled)
255{
257 handled = true;
258 if (selected_ > 0) {
259 set_selected(selected_ - 1, true);
260 }
261}
262
263void combobox::handle_key_down_arrow(SDL_Keymod /*modifier*/, bool& handled)
264{
266 handled = true;
267 if (selected_ < values_.size()-1) {
268 set_selected(selected_ + 1, true);
269 }
270}
271
272std::string combobox::get_preset_value(const size_t index) const
273{
274 auto row_cfg = values_[index];
275 return row_cfg.has_attribute("value") ? row_cfg["value"] : row_cfg["label"];
276}
277
278void combobox::set_values(const std::vector<::config>& values, unsigned selected)
279{
280 assert(selected < values.size());
281 assert(selected_ < values_.size());
282
283 values_ = values;
284 selected_ = selected;
285
286 std::string new_value = get_preset_value(selected_);
287 if(text_box_base::get_value() != new_value) {
288 text_box_base::set_value(new_value);
289 queue_redraw();
290 }
291}
292
294{
295 std::string value = text_box_base::get_value();
296 std::string last_selected = get_preset_value(selected_);
297 return value == last_selected ? selected_ : -1;
298}
299
300void combobox::set_selected(unsigned selected, bool fire_event)
301{
302 assert(selected < values_.size());
303 assert(selected_ < values_.size());
304
305 if(selected != selected_) {
306 queue_redraw();
307 }
308
309 selected_ = selected;
310
312 if (fire_event) {
313 fire(event::NOTIFY_MODIFIED, *this, nullptr);
314 }
315}
316
318{
319 unsigned right_border = get_x() + this->get_size().x;
320 unsigned mouse_x = get_mouse_position().x;
321
322 if ((mouse_x <= right_border) && (mouse_x >= right_border-ICON_SIZE)) {
324 } else {
326 }
327}
328
330 bool& /*handled*/)
331{
333}
334
336 bool& handled,
337 const point& coordinate)
338{
339 DBG_GUI_E << get_control_type() << "[" << id() << "]: " << event << ".";
340
341 if(dragging_) {
342 handle_mouse_selection(coordinate, false);
343 } else {
345 }
346
347 handled = true;
348}
349
351 bool& handled)
352{
353 DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
354
355 /* get_x() is the left border
356 * this->get_size().x is the size of this widget
357 * so get_x() + this->get_size().x is the right border
358 * ICON_SIZE is the size of the icon.*/
359
360 unsigned right_border = get_x() + this->get_size().x;
361 unsigned mouse_x = get_mouse_position().x;
362
363 if ((mouse_x <= right_border) && (mouse_x >= right_border-ICON_SIZE)) {
364 // If a button has a retval do the default handling.
365 dialogs::drop_down_menu droplist(this, values_, selected_, false);
366
367 if(droplist.show()) {
368 const int selected = droplist.selected_item();
369
370 // Safety check. If the user clicks a selection in the dropdown and moves their mouse away too
371 // quickly, selected_ could be set to -1. This returns in that case, preventing crashes.
372 if(selected < 0) {
373 return;
374 }
375
376 set_selected(selected, true);
377 }
378 } else {
381
383 }
384
385 handled = true;
386}
387
389 bool& handled)
390{
391 DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
392
393 dragging_ = false;
394 handled = true;
395}
396
397void
399 bool& handled)
400{
401 DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
402
403 select_all();
404 handled = true;
405}
406
407// }---------- DEFINITION ---------{
408
411{
412 DBG_GUI_P << "Parsing combobox " << id;
413
414 load_resolutions<resolution>(cfg);
415}
416
419 , text_x_offset(cfg["text_x_offset"])
420 , text_y_offset(cfg["text_y_offset"])
421{
422 // Note the order should be the same as the enum state_t in combobox.hpp.
423 state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", missing_mandatory_wml_tag("combobox_definition][resolution", "state_enabled")));
424 state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", missing_mandatory_wml_tag("combobox_definition][resolution", "state_disabled")));
425 state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_focused", missing_mandatory_wml_tag("combobox_definition][resolution", "state_focused")));
426 state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_hovered", missing_mandatory_wml_tag("combobox_definition][resolution", "state_hovered")));
427}
428
429// }---------- BUILDER -----------{
430
431namespace implementation
432{
433
434builder_combobox::builder_combobox(const config& cfg)
436 , max_input_length(cfg["max_input_length"].to_size_t())
437 , hint_text(cfg["hint_text"].t_str())
438 , hint_image(cfg["hint_image"])
439 , options_()
440{
441 for(const auto& option : cfg.child_range("option")) {
442 options_.push_back(option);
443 }
444}
445
446std::unique_ptr<widget> builder_combobox::build() const
447{
448 auto widget = std::make_unique<combobox>(*this);
449
450 // A combobox doesn't have a label but a text
451 widget->set_value(label_string);
452
453 if(!options_.empty()) {
454 widget->set_values(options_);
455 }
456
457 DBG_GUI_G << "Window builder: placed text box '" << id
458 << "' with definition '" << definition << "'.";
459
460 return widget;
461}
462
463} // namespace implementation
464
465// }------------ END --------------
466
467} // namespace gui2
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
child_itors child_range(std::string_view key)
Definition config.cpp:268
Class for a combobox.
Definition combobox.hpp:37
void signal_handler_mouse_enter(const event::ui_event, bool &)
Definition combobox.cpp:329
void signal_handler_left_button_up(const event::ui_event event, bool &handled)
Definition combobox.cpp:388
std::size_t max_input_length_
The maximum length of the text input.
Definition combobox.hpp:130
std::string get_preset_value(const size_t index) const
Get the index-th value from the preset values that area shown in the dropdown.
Definition combobox.cpp:272
void update_mouse_cursor()
Update the mouse cursor based on whether it is over button area or text area.
Definition combobox.cpp:317
unsigned selected_
Definition combobox.hpp:166
void delete_selection() override
Deletes the current selection.
Definition combobox.cpp:178
void update_offsets()
Updates text_x_offset_ and text_y_offset_.
Definition combobox.cpp:221
bool dragging_
Is the mouse in dragging mode, this affects selection in mouse move.
Definition combobox.hpp:156
void signal_handler_mouse_motion(const event::ui_event event, bool &handled, const point &coordinate)
Definition combobox.cpp:335
void handle_mouse_selection(point mouse, const bool start_selection)
Definition combobox.cpp:198
virtual void update_canvas() override
Updates the canvas(ses).
Definition combobox.cpp:88
unsigned const ICON_SIZE
Size of the dropdown icon TODO : Should be dynamically loaded from image.
Definition combobox.hpp:135
void handle_key_clear_line(SDL_Keymod modifier, bool &handled) override
Inherited from text_box_base.
Definition combobox.cpp:248
std::string hint_image_
Image (such as a magnifying glass) that accompanies the help text.
Definition combobox.hpp:162
void handle_key_down_arrow(SDL_Keymod, bool &handled) override
Inherited from text_box_base.
Definition combobox.cpp:263
void set_values(const std::vector<::config > &values, unsigned selected=0)
Definition combobox.cpp:278
int get_selected() const
Returned last selected entry's index or -1 if typed value that does not matches any preset value.
Definition combobox.cpp:293
void signal_handler_left_button_down(const event::ui_event event, bool &handled)
Definition combobox.cpp:350
void delete_char(const bool before_cursor) override
Deletes the character.
Definition combobox.cpp:167
std::vector<::config > values_
Definition combobox.hpp:164
point text_offset_
The x, y offset in the widget where the text starts.
Definition combobox.hpp:143
void signal_handler_left_button_double_click(const event::ui_event event, bool &handled)
Definition combobox.cpp:398
void set_selected(unsigned selected, bool fire_event=true)
Definition combobox.cpp:300
void handle_key_up_arrow(SDL_Keymod, bool &handled) override
Inherited from text_box_base.
Definition combobox.cpp:254
std::string hint_text_
Helper text to display (such as "Search") if the combo box is empty.
Definition combobox.hpp:159
unsigned text_height_
The height of the text itself.
Definition combobox.hpp:150
virtual void place(const point &origin, const point &size) override
See widget::place.
Definition combobox.cpp:75
virtual const std::string & get_control_type() const override
Inherited from styled_widget, implemented by REGISTER_WIDGET.
Used by the menu_button widget.
bool show(const unsigned auto_close_time=0)
Shows the window.
bool fire(const ui_event event, widget &target)
Fires an event which has no extra parameters.
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.
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
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.
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
point get_size() const
Returns the size of the widget.
Definition widget.cpp:316
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 keyboard_capture(widget *widget)
Definition window.cpp:1197
void mouse_capture(const bool capture=true)
Definition window.cpp:1191
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
#define LOG_SCOPE_HEADER
Definition button.cpp:34
This file contains the window object, this object is a top level container which has the event manage...
@ NORMAL
Definition cursor.hpp:28
@ IBEAM
Definition cursor.hpp:28
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition cursor.cpp:151
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.
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.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
combobox_definition(const config &cfg)
Definition combobox.cpp:409
virtual std::unique_ptr< widget > build() const override
Definition combobox.cpp:446
std::string definition
Parameters for the styled_widget.
std::vector< state_definition > state
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)