The Battle for Wesnoth  1.17.0-dev
location_palette.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
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 "gettext.hpp"
21 #include "font/sdl_ttf_compat.hpp"
22 #include "font/standard_colors.hpp"
23 #include "tooltips.hpp"
24 
25 #include "editor/editor_common.hpp"
29 
30 #include "formula/string_utils.hpp"
31 
32 #include <regex>
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(video, true)
56  , parent_(parent)
57  {
58  }
59 
60  void draw_contents() override
61  {
62  if (state_.mouseover) {
63  sdl::fill_rectangle(location(), {200, 200, 200, 26});
64  }
65  if (state_.selected) {
66  sdl::draw_rectangle(location(), {255, 255, 255, 255});
67  }
68  font::pango_draw_text(&video(), 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 sdl::point_in_rect(x, y, location());
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  void draw() override { gui::widget::draw(); }
131 private:
132  std::string id_;
133  std::string desc_;
136 };
137 
139 {
140 public:
141  location_palette_button(CVideo& video, const SDL_Rect& location, const std::string& text, const std::function<void (void)>& callback)
142  : gui::button(video, 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(gui.video())
165  , item_size_(20)
166  //TODO avoid magic number
167  , item_space_(20 + 3)
168  , palette_y_(0)
169  , palette_x_(0)
170  , items_start_(0)
171  , selected_item_()
172  , items_()
173  , toolkit_(toolkit)
174  , buttons_()
175  , button_add_()
176  , button_delete_()
177  , button_goto_()
178  , help_handle_(-1)
179  , disp_(gui)
180  {
181  for (int i = 1; i < 10; ++i) {
182  items_.push_back(std::to_string(i));
183  }
184  selected_item_ = items_[0];
185  }
186 
188 {
190  for (gui::widget& b : buttons_) {
191  h.push_back(&b);
192  }
193  if (button_add_) { h.push_back(button_add_.get()); }
194  if (button_delete_) { h.push_back(button_delete_.get()); }
195  if (button_goto_) { h.push_back(button_goto_.get()); }
196  return h;
197 }
198 
200 {
201  widget::hide(hidden);
202 
204 
205  std::shared_ptr<gui::button> palette_menu_button = disp_.find_menu_button("menu-editor-terrain");
206  palette_menu_button->set_overlay("");
207  palette_menu_button->enable(false);
208 
209  for(auto& w : handler_members()) {
210  static_cast<gui::widget&>(*w).hide(hidden);
211  }
212 }
213 
215 {
216  int decrement = 1;
217  if(items_start_ >= decrement) {
218  items_start_ -= decrement;
219  draw();
220  return true;
221  }
222  return false;
223 }
225 {
226  return (items_start_ != 0);
227 }
228 
230 {
231  return (items_start_ + num_visible_items() + 1 <= num_items());
232 }
233 
235 {
236  bool end_reached = (!(items_start_ + num_visible_items() + 1 <= num_items()));
237  bool scrolled = false;
238 
239  // move downwards
240  if(!end_reached) {
241  items_start_ += 1;
242  scrolled = true;
243  set_dirty(true);
244  }
245  draw();
246  return scrolled;
247 }
248 
249 void location_palette::adjust_size(const SDL_Rect& target)
250 {
251  palette_x_ = target.x;
252  palette_y_ = target.y;
253  const int button_height = 22;
254  const int button_y = 30;
255  int bottom = target.y + target.h;
256  if (!button_goto_) {
257  button_goto_.reset(new location_palette_button(video(), SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height }, _("Go To"), [this]() {
258  //static_cast<mouse_action_starting_position&>(toolkit_.get_mouse_action()). ??
260  if (pos.valid()) {
262  }
263  }));
264  button_add_.reset(new location_palette_button(video(), SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height }, _("Add"), [this]() {
265  std::string newid;
266  if (gui2::dialogs::edit_text::execute(_("New Location Identifier"), "", newid)) {
267  static const std::regex valid_id("[a-zA-Z0-9_]+");
268  if(std::regex_match(newid, valid_id)) {
269  add_item(newid);
270  }
271  else {
273  _("Error"),
274  _("Invalid location id")
275  );
276  //TODO: a user visible messae would be nice.
277  ERR_ED << "entered invalid location id\n";
278  }
279  }
280  }));
281  button_delete_.reset(new location_palette_button(video(), SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height }, _("Delete"), nullptr));
282  }
283  else {
284  button_goto_->set_location(SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height });
285  button_add_->set_location(SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height });
286  button_delete_->set_location(SDL_Rect{ target.x , bottom -= button_y, target.w - 10, button_height });
287  }
288 
289  const int space_for_items = bottom - target.y;
290  const int items_fitting = space_for_items / item_space_;
291  // This might be called while the palette is not visible onscreen.
292  // If that happens, no items will fit and we'll have a negative number here.
293  // Just skip it in that case.
294  if(items_fitting > 0 && num_visible_items() != items_fitting) {
295  location_palette_item lpi(disp_.video(), this);
296  buttons_.resize(items_fitting, lpi);
297  }
298 
299  set_location(target);
300  set_dirty(true);
303 }
304 
305 void location_palette::select_item(const std::string& item_id)
306 {
307  if (selected_item_ != item_id) {
308  selected_item_ = item_id;
309  set_dirty();
310  }
313 }
314 
316 {
317  return items_.size();
318 }
320 {
321  return buttons_.size();
322 }
323 
324 bool location_palette::is_selected_item(const std::string& id)
325 {
326  return selected_item_ == id;
327 }
328 
330 {
332  int y = palette_y_;
333  const int x = palette_x_;
334  const int starting = items_start_;
335  int ending = std::min<int>(starting + num_visible_items(), num_items());
336  std::shared_ptr<gui::button> upscroll_button = disp_.find_action_button("upscroll-button-editor");
337  if (upscroll_button)
338  upscroll_button->enable(starting != 0);
339  std::shared_ptr<gui::button> downscroll_button = disp_.find_action_button("downscroll-button-editor");
340  if (downscroll_button)
341  downscroll_button->enable(ending != num_items());
342 
343  if (button_goto_) {
344  button_goto_->set_dirty(true);
345  }
346  if (button_add_) {
347  button_add_->set_dirty(true);
348  }
349  if (button_delete_) {
350  button_delete_->set_dirty(true);
351  }
352  for (int i = 0, size = num_visible_items(); i < size; i++) {
353 
354  location_palette_item & tile = buttons_[i];
355 
356  tile.hide(true);
357 
358  if (i >= ending) {
359  //We want to hide all following buttons so we cannot use break here.
360  continue;
361  }
362 
363  const std::string item_id = items_[starting + i];
364 
365  std::stringstream tooltip_text;
366 
367  SDL_Rect dstrect;
368  dstrect.x = x;
369  dstrect.y = y;
370  dstrect.w = location().w - 10;
371  dstrect.h = item_size_ + 2;
372 
373  tile.set_location(dstrect);
374  tile.set_tooltip_string(tooltip_text.str());
375  tile.set_item_id(item_id);
376  tile.set_selected(is_selected_item(item_id));
377  tile.set_dirty(true);
378  tile.hide(false);
379  tile.draw();
380 
381  // Adjust location
382  y += item_space_;
383  }
384 }
385 
386 std::vector<std::string> location_palette::action_pressed() const
387 {
388  std::vector<std::string> res;
389  if (button_delete_ && button_delete_->pressed()) {
390  res.push_back("editor-remove-location");
391  }
392  return res;
393 }
394 
396 {
397 }
398 
399 // Sort numbers before all other strings.
400 static bool loc_id_comp(const std::string& lhs, const std::string& rhs) {
401  if(is_positive_integer(lhs)) {
402  if(is_positive_integer(rhs)) {
403  return std::stoi(lhs) < std::stoi(rhs);
404  } else {
405  return true;
406  }
407  }
408  if(is_positive_integer(rhs)) {
409  return false;
410  }
411  return lhs < rhs;
412 }
413 
414 void location_palette::add_item(const std::string& id)
415 {
416  int pos;
417  // Insert the new ID at the sorted location, unless it's already in the list
418  const auto itor = std::upper_bound(items_.begin(), items_.end(), id, loc_id_comp);
419  if(itor == items_.begin() || *(itor - 1) != id) {
420  pos = std::distance(items_.begin(), items_.insert(itor, id));
421  } else {
422  pos = std::distance(items_.begin(), itor);
423  }
424  selected_item_ = id;
425  if(num_visible_items() == 0) {
426  items_start_ = 0;
427  } else {
428  items_start_ = std::max(pos - num_visible_items() + 1, items_start_);
429  items_start_ = std::min(pos, items_start_);
430  }
432 }
433 
434 } // end namespace editor
std::shared_ptr< gui::button > find_action_button(const std::string &id)
Retrieves a pointer to a theme UI button.
Definition: display.cpp:832
virtual bool scroll_down() override
Scroll the editor-palette down one step if possible.
bool enabled() const
Definition: widget.cpp:202
std::vector< location_palette_item > buttons_
virtual void mouse_up(const SDL_MouseButtonEvent &event)
Definition: button.cpp:481
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:190
virtual bool can_scroll_down() override
bool hidden() const
Definition: widget.cpp:188
bool hit(int x, int y) const
Definition: video.hpp:32
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, const bool restore_background)
Shows a transient message to the user.
General purpose widgets.
virtual bool is_selected_item(const std::string &id)
void set_mouseover_overlay(editor_display &gui)
#define h
virtual std::vector< std::string > action_pressed() const override
static std::string _(const char *str)
Definition: gettext.hpp:93
virtual void mouse_up(const SDL_MouseButtonEvent &e) override
editor::location_palette * parent_
map_location special_location(const std::string &id) const
Definition: map.cpp:312
std::unique_ptr< location_palette_button > button_goto_
#define b
virtual void draw() override
widget(CVideo &video, const bool auto_join=true)
Definition: widget.cpp:33
void set_dirty(bool dirty=true)
Definition: widget.cpp:207
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
virtual void handle_event(const SDL_Event &)
Definition: widget.cpp:330
static bool is_positive_integer(const std::string &str)
virtual void hide(bool value=true)
Definition: widget.cpp:152
bool valid() const
Definition: location.hpp:89
const SDL_Rect & location() const
Definition: widget.cpp:134
void draw_rectangle(const SDL_Rect &rect, const color_t &color)
Draw a rectangle outline.
Definition: rect.cpp:58
SDL_Rect pango_draw_text(CVideo *gui, const SDL_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.
location_palette_button(CVideo &video, const SDL_Rect &location, const std::string &text, const std::function< void(void)> &callback)
virtual std::string get_help_string()
Manage the empty-palette in the editor.
Definition: action.cpp:30
int num_visible_items()
Return the maximum number of items shown at the same time.
void set_item_id(const std::string &id)
virtual void set_location(const SDL_Rect &rect)
Definition: widget.cpp:75
const color_t NORMAL_COLOR
bool point_in_rect(int x, int y, const SDL_Rect &rect)
Tests whether a point is inside a rectangle.
Definition: rect.cpp:23
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.
location_palette_item(CVideo &video, editor::location_palette *parent)
A button is a control that can be pushed to start an action or close a dialog.
Definition: button.hpp:50
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:2203
const std::string & id() const
Definition: widget.cpp:222
Main (common) editor header.
virtual void draw_contents() override
virtual void draw()
Definition: widget.cpp:269
int w
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
bool mouse_locked() const
Definition: widget.cpp:62
std::unique_ptr< location_palette_button > button_add_
void set_tooltip_string(const std::string &str)
Definition: widget.cpp:304
CVideo & video() const
Definition: widget.hpp:84
int set_help_string(const std::string &str)
Displays a help string with the given text.
Definition: video.cpp:498
void adjust_size(const SDL_Rect &target) override
Update the size of this widget.
const gamemap & get_map() const
Definition: display.hpp:96
std::unique_ptr< location_palette_button > button_delete_
#define ERR_ED
void fill_rectangle(const SDL_Rect &rect, const color_t &color)
Draws a filled rectangle.
Definition: rect.cpp:66
std::function< void(void)> callback_
CVideo & video()
Gets the underlying screen object.
Definition: display.hpp:201
void draw_contents() override
#define e
virtual void select_item(const std::string &item_id)
mock_char c
int num_items() override
Return the number of items in the palette.
Transitional API for porting SDL_ttf-based code to Pango.
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:842
friend bool operator==(state_t r, state_t l)
std::vector< std::string > items_
void clear_help_string(int handle)
Removes the help string with the given handle.
Definition: video.cpp:530
virtual sdl_handler_vector handler_members() override