The Battle for Wesnoth  1.17.10+dev
location_palette.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
3  by David White <dave@whitevine.net>
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-editor"
17 
19 
20 #include "draw.hpp"
21 #include "editor/editor_common.hpp"
23 #include "floating_label.hpp"
24 #include "font/sdl_ttf_compat.hpp"
25 #include "font/standard_colors.hpp"
26 #include "formula/string_utils.hpp"
27 #include "gettext.hpp"
30 #include "tooltips.hpp"
31 
32 #include <boost/regex.hpp>
33 
34 static bool is_positive_integer(const std::string& str) {
35  return str != "0" && std::find_if(str.begin(), str.end(), [](char c) { return !std::isdigit(c); }) == str.end();
36 }
37 
39 {
40 public:
41  struct state_t {
43  : selected(false)
44  , mouseover(false)
45  {}
46  bool selected;
47  bool mouseover;
48  friend bool operator==(state_t r, state_t l)
49  {
50  return r.selected == l.selected && r.mouseover == l.mouseover;
51  }
52 
53  };
55  : gui::widget(true)
56  , parent_(parent)
57  {
58  }
59 
60  void draw_contents() override
61  {
62  if (state_.mouseover) {
63  draw::fill(location(), 200, 200, 200, 26);
64  }
65  if (state_.selected) {
66  draw::rect(location(), 255, 255, 255, 255);
67  }
68  font::pango_draw_text(true, location(), 16, font::NORMAL_COLOR, desc_.empty() ? id_ : desc_, location().x + 2, location().y, 0);
69  }
70 
71  //TODO move to widget
72  bool hit(int x, int y) const
73  {
74  return location().contains(x, y);
75  }
76 
77  void mouse_up(const SDL_MouseButtonEvent& e)
78  {
79  if (!(hit(e.x, e.y)))
80  return;
81  if (e.button == SDL_BUTTON_LEFT) {
83  }
84  if (e.button == SDL_BUTTON_RIGHT) {
85  //TODO: add a context menu with the following options:
86  // 1) 'copy it to clipboard'
87  // 2) 'jump to item'
88  // 3) 'delete item'.
89  }
90  }
91 
92  void handle_event(const SDL_Event& e) override
93  {
95 
96  if (hidden() || !enabled() || mouse_locked())
97  return;
98 
99  state_t start_state = state_;
100 
101  switch (e.type) {
102  case SDL_MOUSEBUTTONUP:
103  mouse_up(e.button);
104  break;
105  case SDL_MOUSEMOTION:
106  state_.mouseover = hit(e.motion.x, e.motion.y);
107  break;
108  default:
109  return;
110  }
111 
112  if (!(start_state == state_))
113  set_dirty(true);
114  }
115 
116  void set_item_id(const std::string& id)
117  {
118  id_ = id;
119  if (is_positive_integer(id)) {
120  desc_ = VGETTEXT("Player $side_num", utils::string_map{ {"side_num", id} });
121  }
122  else {
123  desc_ = "";
124  }
125  }
127  {
129  }
130 
131 private:
132  std::string id_;
133  std::string desc_;
136 };
137 
139 {
140 public:
141  location_palette_button(const SDL_Rect& location, const std::string& text, const std::function<void (void)>& callback)
142  : gui::button(text)
143  , callback_(callback)
144  {
145  this->set_location(location.x, location.y);
146  this->hide(false);
147  }
148 protected:
149  virtual void mouse_up(const SDL_MouseButtonEvent& e) override
150  {
152  if (callback_) {
153  if (this->pressed()) {
154  callback_();
155  }
156  }
157  }
158  std::function<void (void)> callback_;
159 
160 };
161 namespace editor {
162 location_palette::location_palette(editor_display &gui, const game_config_view& /*cfg*/,
163  editor_toolkit &toolkit)
164  : common_palette()
165  , item_size_(20)
166  //TODO avoid magic number
167  , item_space_(20 + 3)
168  , items_start_(0)
169  , selected_item_()
170  , items_()
171  , toolkit_(toolkit)
172  , buttons_()
173  , button_add_()
174  , button_delete_()
175  , button_goto_()
176  , disp_(gui)
177  {
178  for (int i = 1; i < 10; ++i) {
179  items_.push_back(std::to_string(i));
180  }
181  selected_item_ = items_[0];
182  }
183 
185 {
187  for (gui::widget& b : buttons_) {
188  h.push_back(&b);
189  }
190  if (button_add_) { h.push_back(button_add_.get()); }
191  if (button_delete_) { h.push_back(button_delete_.get()); }
192  if (button_goto_) { h.push_back(button_goto_.get()); }
193  return h;
194 }
195 
197 {
198  widget::hide(hidden);
199 
201 
202  std::shared_ptr<gui::button> palette_menu_button = disp_.find_menu_button("menu-editor-terrain");
203  palette_menu_button->set_overlay("");
204  palette_menu_button->enable(false);
205 
206  for(auto& w : handler_members()) {
207  static_cast<gui::widget&>(*w).hide(hidden);
208  }
209 }
210 
212 {
213  bool scrolled = false;
214  if(can_scroll_up()) {
215  --items_start_;
216  scrolled = true;
217  set_dirty(true);
218  }
219 
220  return scrolled;
221 }
223 {
224  return (items_start_ != 0);
225 }
226 
228 {
229  return (items_start_ + num_visible_items() + 1 <= num_items());
230 }
231 
233 {
234  bool scrolled = false;
235  if(can_scroll_down()) {
236  ++items_start_;
237  scrolled = true;
238  set_dirty(true);
239  }
240 
241  return scrolled;
242 }
243 
244 void location_palette::adjust_size(const SDL_Rect& target)
245 {
246  const int button_height = 22;
247  const int button_y = 30;
248  int bottom = target.y + target.h;
249  if (!button_goto_) {
250  button_goto_.reset(new location_palette_button(SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height }, _("Go To"), [this]() {
251  //static_cast<mouse_action_starting_position&>(toolkit_.get_mouse_action()). ??
253  if (pos.valid()) {
255  }
256  }));
257  button_add_.reset(new location_palette_button(SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height }, _("Add"), [this]() {
258  std::string newid;
259  if (gui2::dialogs::edit_text::execute(_("New Location Identifier"), "", newid)) {
260  static const boost::regex valid_id("[a-zA-Z0-9_]+");
261  if(boost::regex_match(newid, valid_id)) {
262  add_item(newid);
263  }
264  else {
266  _("Error"),
267  _("Invalid location id")
268  );
269  //TODO: a user visible messae would be nice.
270  ERR_ED << "entered invalid location id";
271  }
272  }
273  }));
274  button_delete_.reset(new location_palette_button(SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height }, _("Delete"), nullptr));
275  }
276  else {
277  button_goto_->set_location(SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height });
278  button_add_->set_location(SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height });
279  button_delete_->set_location(SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height });
280  }
281 
282  const int space_for_items = bottom - target.y;
283  const int items_fitting = space_for_items / item_space_;
284  // This might be called while the palette is not visible onscreen.
285  // If that happens, no items will fit and we'll have a negative number here.
286  // Just skip it in that case.
287  if(items_fitting > 0) {
288  // Items may be added dynamically via add_item(), so this creates all the buttons that
289  // fit in the space, even if some of them will be hidden until more items are added.
290  // This simplifies the scrolling code in add_item.
291  const std::size_t buttons_needed = items_fitting;
292  if(buttons_.size() != buttons_needed) {
293  location_palette_item lpi(this);
294  buttons_.resize(buttons_needed, lpi);
295  }
296  }
297 
298  // Update button locations and sizes. Needs to be done even if the number of buttons hasn't changed,
299  // because adjust_size() also handles moving left and right when the window's width is changed.
300  SDL_Rect dstrect;
301  dstrect.w = target.w - 10;
302  dstrect.h = item_size_ + 2;
303  for(std::size_t i = 0; i < buttons_.size(); ++i) {
304  dstrect.x = target.x;
305  dstrect.y = target.y + i * item_space_;
306  buttons_[i].set_location(dstrect);
307  }
308 
309  set_location(target);
310  set_dirty(true);
313 }
314 
315 void location_palette::select_item(const std::string& item_id)
316 {
317  if (selected_item_ != item_id) {
318  selected_item_ = item_id;
319  set_dirty();
320  }
323 }
324 
326 {
327  return items_.size();
328 }
330 {
331  return buttons_.size();
332 }
333 
334 bool location_palette::is_selected_item(const std::string& id)
335 {
336  return selected_item_ == id;
337 }
338 
340 {
341  if (!dirty()) {
342  return;
343  }
344 
346 
347  // The hotkey system will automatically enable and disable the buttons when it runs, but it doesn't
348  // get triggered when handling mouse-wheel scrolling. Therefore duplicate that functionality here.
349  std::shared_ptr<gui::button> upscroll_button = disp_.find_action_button("upscroll-button-editor");
350  if(upscroll_button)
351  upscroll_button->enable(can_scroll_up());
352  std::shared_ptr<gui::button> downscroll_button = disp_.find_action_button("downscroll-button-editor");
353  if(downscroll_button)
354  downscroll_button->enable(can_scroll_down());
355 
356  if(button_goto_) {
357  button_goto_->set_dirty(true);
358  }
359  if(button_add_) {
360  button_add_->set_dirty(true);
361  }
362  if(button_delete_) {
363  button_delete_->set_dirty(true);
364  }
365  for(std::size_t i = 0; i < num_visible_items(); ++i) {
366  const auto item_index = items_start_ + i;
368 
369  tile.hide(true);
370 
371  // If we've scrolled to the end of the list, or if there aren't many items, leave the button hidden
372  if(item_index >= num_items()) {
373  // We want to hide all following buttons so we cannot use break here.
374  continue;
375  }
376 
377  const std::string item_id = items_[item_index];
378 
379  // These could have tooltips, but currently don't. Adding their hex co-ordinates would be an option,
380  // and for player starts adding the raw ID next might be good.
381  std::stringstream tooltip_text;
382 
383  tile.set_tooltip_string(tooltip_text.str());
384  tile.set_item_id(item_id);
385  tile.set_selected(is_selected_item(item_id));
386  tile.set_dirty(true);
387  tile.hide(false);
388  }
389 
390  set_dirty(false);
391 }
392 
394 {
395  // This is unnecessary as every GUI1 widget is a TLD.
396  //for(std::size_t i = 0; i < num_visible_items(); ++i) {
397  // location_palette_item& tile = buttons_[i];
398  // tile.draw();
399  //}
400 }
401 
402 std::vector<std::string> location_palette::action_pressed() const
403 {
404  std::vector<std::string> res;
405  if (button_delete_ && button_delete_->pressed()) {
406  res.push_back("editor-remove-location");
407  }
408  return res;
409 }
410 
412 {
413 }
414 
415 // Sort numbers before all other strings.
416 static bool loc_id_comp(const std::string& lhs, const std::string& rhs) {
417  if(is_positive_integer(lhs)) {
418  if(is_positive_integer(rhs)) {
419  return std::stoi(lhs) < std::stoi(rhs);
420  } else {
421  return true;
422  }
423  }
424  if(is_positive_integer(rhs)) {
425  return false;
426  }
427  return lhs < rhs;
428 }
429 
430 void location_palette::add_item(const std::string& id)
431 {
432  decltype(items_)::difference_type pos;
433 
434  // Insert the new ID at the sorted location, unless it's already in the list
435  const auto itor = std::upper_bound(items_.begin(), items_.end(), id, loc_id_comp);
436  if(itor == items_.begin() || *(itor - 1) != id) {
437  pos = std::distance(items_.begin(), items_.insert(itor, id));
438  } else {
439  pos = std::distance(items_.begin(), itor);
440  }
441  selected_item_ = id;
442 
443  // pos will always be positive because begin() was used as the first arg of std::distance(),
444  // but we need this (or casts) to prevent warnings about signed/unsigned comparisons.
445  const std::size_t unsigned_pos = pos;
446 
447  // Scroll if necessary so that the new item is visible
448  if(unsigned_pos < items_start_ || unsigned_pos >= items_start_ + num_visible_items()) {
449  if(unsigned_pos < num_visible_items()) {
450  items_start_ = 0;
451  } else if(unsigned_pos + num_visible_items() > num_items()) {
452  // This can't underflow, because unsigned_pos < num_items() and the
453  // previous conditional block would have been entered instead.
455  } else {
456  items_start_ = unsigned_pos - num_visible_items() / 2;
457  }
458  }
459 
460  // No need to call adjust_size(), because initialisation creates all possible buttons even when num_visible_items() > num_items().
461 }
462 
463 } // end namespace editor
Drawing functions, for drawing things on the screen.
std::shared_ptr< gui::button > find_action_button(const std::string &id)
Retrieves a pointer to a theme UI button.
Definition: display.cpp:825
virtual bool scroll_down() override
Scroll the editor-palette down one step if possible.
bool enabled() const
Definition: widget.cpp:176
std::vector< location_palette_item > buttons_
virtual void mouse_up(const SDL_MouseButtonEvent &event)
Definition: button.cpp:486
virtual bool can_scroll_up() override
std::map< std::string, t_string > string_map
std::vector< events::sdl_handler * > sdl_handler_vector
Definition: events.hpp:187
virtual bool can_scroll_down() override
bool hidden() const
Definition: widget.cpp:162
bool hit(int x, int y) const
General purpose widgets.
virtual bool is_selected_item(const std::string &id)
void set_mouseover_overlay(editor_display &gui)
#define h
List of starting locations and location ids.
virtual std::vector< std::string > action_pressed() const override
bool dirty() const
Definition: widget.cpp:194
static std::string _(const char *str)
Definition: gettext.hpp:93
widget(const bool auto_join=true)
Definition: widget.cpp:34
virtual void mouse_up(const SDL_MouseButtonEvent &e) override
editor::location_palette * parent_
void set_help_string(const std::string &str)
Displays a help string with the given text.
bool contains(int x, int y) const
Whether the given point lies within the rectangle.
Definition: rect.cpp:54
map_location special_location(const std::string &id) const
Definition: map.cpp:312
void rect(const SDL_Rect &rect)
Draw a rectangle.
Definition: draw.cpp:141
std::unique_ptr< location_palette_button > button_goto_
#define b
void set_dirty(bool dirty=true)
Definition: widget.cpp:181
void clear_help_string()
Removes the help string.
location_palette_item(editor::location_palette *parent)
static bool is_positive_integer(const std::string &str)
virtual void hide(bool value=true)
Definition: widget.cpp:142
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
bool valid() const
Definition: location.hpp:89
std::size_t num_items() override
Return the number of items in the palette.
const rect & location() const
Definition: widget.cpp:124
virtual std::string get_help_string()
Manage the empty-palette in the editor.
Definition: action.cpp:30
void set_item_id(const std::string &id)
virtual void set_location(const SDL_Rect &rect)
Definition: widget.cpp:70
const color_t NORMAL_COLOR
void set_selected(bool selected)
Encapsulates the map of the game.
Definition: location.hpp:38
virtual bool scroll_up() override
Scroll the editor-palette up one step if possible.
A button is a control that can be pushed to start an action or close a dialog.
Definition: button.hpp:51
void mouse_up(const SDL_MouseButtonEvent &e)
std::size_t i
Definition: function.cpp:967
void add_item(const std::string &id)
void scroll_to_tile(const map_location &loc, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, bool force=true)
Scroll such that location loc is on-screen.
Definition: display.cpp:2056
const std::string & id() const
Definition: widget.cpp:199
Main (common) editor header.
virtual void handle_event(const SDL_Event &) override
Definition: widget.hpp:93
std::size_t num_visible_items()
Return the number of GUI elements that can show items.
virtual void draw_contents() override
Called by widget::draw()
rect pango_draw_text(bool actually_draw, const rect &area, int size, const color_t &color, const std::string &text, int x, int y, bool use_tooltips, pango_text::FONT_STYLE style)
Draws text on the screen.
int w
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
bool mouse_locked() const
Definition: widget.cpp:65
std::unique_ptr< location_palette_button > button_add_
void set_tooltip_string(const std::string &str)
Definition: widget.cpp:252
void adjust_size(const SDL_Rect &target) override
Update the size of this widget.
virtual void layout() override
Called by draw_manager to validate layout before drawing.
location_palette_button(const SDL_Rect &location, const std::string &text, const std::function< void(void)> &callback)
const gamemap & get_map() const
Definition: display.hpp:102
std::unique_ptr< location_palette_button > button_delete_
#define ERR_ED
std::function< void(void)> callback_
void draw_contents() override
#define e
virtual void select_item(const std::string &item_id)
mock_char c
Transitional API for porting SDL_ttf-based code to Pango.
void fill(const SDL_Rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:41
static bool loc_id_comp(const std::string &lhs, const std::string &rhs)
void hide(bool hidden) override
void handle_event(const SDL_Event &e) override
std::shared_ptr< gui::button > find_menu_button(const std::string &id)
Definition: display.cpp:835
friend bool operator==(state_t r, state_t l)
std::vector< std::string > items_
virtual sdl_handler_vector handler_members() override