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