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