Merge lp:~widelands-dev/widelands/fix-dropdowns into lp:widelands

Proposed by GunChleoc
Status: Merged
Merged at revision: 9160
Proposed branch: lp:~widelands-dev/widelands/fix-dropdowns
Merge into: lp:widelands
Diff against target: 2577 lines (+782/-513)
44 files modified
data/templates/default/init.lua (+20/-0)
src/editor/CMakeLists.txt (+2/-0)
src/editor/ui_menus/main_menu_new_map.cc (+5/-34)
src/editor/ui_menus/main_menu_new_map.h (+3/-4)
src/editor/ui_menus/main_menu_random_map.cc (+15/-57)
src/editor/ui_menus/main_menu_random_map.h (+2/-2)
src/editor/ui_menus/map_size_box.cc (+76/-0)
src/editor/ui_menus/map_size_box.h (+57/-0)
src/editor/ui_menus/player_menu.cc (+10/-10)
src/editor/ui_menus/tool_resize_options_menu.cc (+21/-58)
src/editor/ui_menus/tool_resize_options_menu.h (+3/-4)
src/graphic/style_manager.cc (+2/-1)
src/graphic/styles/font_style.h (+1/-0)
src/graphic/styles/table_style.h (+6/-2)
src/graphic/text/rt_parse.cc (+4/-2)
src/graphic/text_layout.cc (+7/-0)
src/graphic/text_layout.h (+2/-0)
src/scripting/lua_ui.cc (+135/-32)
src/scripting/lua_ui.h (+38/-1)
src/ui_basic/button.cc (+1/-3)
src/ui_basic/button.h (+0/-3)
src/ui_basic/checkbox.cc (+2/-2)
src/ui_basic/checkbox.h (+2/-2)
src/ui_basic/dropdown.cc (+93/-59)
src/ui_basic/dropdown.h (+42/-22)
src/ui_basic/icongrid.cc (+1/-1)
src/ui_basic/icongrid.h (+1/-1)
src/ui_basic/listselect.cc (+98/-74)
src/ui_basic/listselect.h (+30/-40)
src/ui_basic/panel.cc (+10/-3)
src/ui_basic/panel.h (+3/-2)
src/ui_basic/radiobutton.cc (+1/-1)
src/ui_basic/radiobutton.h (+1/-1)
src/ui_basic/slider.h (+0/-1)
src/ui_basic/unique_window.cc (+7/-1)
src/ui_fsmenu/launch_game.cc (+3/-3)
src/ui_fsmenu/launch_spg.cc (+2/-0)
src/ui_fsmenu/options.cc (+12/-10)
src/wui/economy_options_window.cc (+1/-1)
src/wui/fieldaction.cc (+1/-1)
src/wui/game_client_disconnected.cc (+3/-2)
src/wui/game_message_menu.cc (+17/-25)
src/wui/multiplayersetupgroup.cc (+12/-8)
src/wui/seafaring_statistics_menu.cc (+30/-40)
To merge this branch: bzr merge lp:~widelands-dev/widelands/fix-dropdowns
Reviewer Review Type Date Requested Status
Klaus Halfmann testplay Needs Fixing
Review via email: mp+368223@code.launchpad.net

Commit message

Dropdown fixes and improvements
- Fix positioning of dropdown lists on fullscreen switches
- Define dropdown list height by number of items
- Add dropdowns to Lua interface
- Panel::free_children() now frees all its children
- Create styles for hotkeys and reduce number of render calls in listselect
- Get rid of map width/height code duplication in the editor
- Add support for using dropdowns as toolbar menus with hotkeys
- Fix toggling of minimized UniqueWindows

Description of the change

I pulled out some code from

https://code.launchpad.net/~widelands-dev/widelands/toolbar-dropdown-menus

to make it more reviewable, because that branch has become too big.

The new dropdown functionality in the Lua interface and for toolbar menus is not being used in this branch, but it can be tested with the other branch.

To post a comment you must log in.
9140. By GunChleoc

Get rid of some unneeded variables.

9141. By GunChleoc

Merged trunk.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 5113. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/540070598.
Appveyor build 4895. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_fix_dropdowns-4895.

9142. By GunChleoc

Fix typo.

9143. By GunChleoc

Fix clang compiler warnings.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 5121. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/540143701.
Appveyor build 4903. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_fix_dropdowns-4903.

9144. By GunChleoc

Add richtext_escape back in.

9145. By GunChleoc

Merged trunk.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 5163. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/542120713.
Appveyor build 4945. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_fix_dropdowns-4945.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Most travis builds green, two fail as of test_inputqueues.lua

Compiled. To test:
* in MapEditor
** Widht/Height dropdowns (new Map)
** Widht/Height dropdowns (resize Map)
** Number of Players dropdown
* Filter in news overview
* Which examples of minimzed UniqueWindows can you give me?

* How can I find the hotkeys and test them?
* Improved SyntaxErrorImpl - no idea how to test this :-)
  Can I break som RichText in an easy way?

* Sure we want to log every (Lua Button) keypress?

Nice refactoring of boring code :-)

Will no test the Lua part -> next Branch.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Found a unrealted, minor Bug: when leaving the "Set Player Positions" Window,
the Map still shows only Big buidling Plots. Not sure if we _must_ fix this.
Will go away with the next map commands.

When trying to open an Online Game I get:
InternetGaming: Connecting to the metaserver.
[NetClient]: Trying to connect to 2a03:4000:32:524:48cb:feff:feae:b0c6:7395 ... failed.
Could not resolve network name: resolve: Host not found (authoritative)

> dig widelands.org AAAA
widelands.org. 2431 IN AAAA 2a03:4000:32:524:48cb:feff:feae:b0c6

> dig widelands.org A
widelands.org. 3312 IN A 85.235.66.69

./widelands --metaserver=2a03:4000:32:524:48cb:feff:feae:b0c6
> OK

./widelands --metaserver=85.235.66.69
> OK

How did this branch break the Internet (IPv6) connectivitiy?
Can anybody confirm.

I will continue testing with the hardcoded IPv6 Adress

Revision history for this message
GunChleoc (gunchleoc) wrote :

> Found a unrealted, minor Bug: when leaving the "Set Player Positions" Window,
> the Map still shows only Big buidling Plots. Not sure if we _must_ fix this.
> Will go away with the next map commands.

That's independent from this branch, so I have created a new bug report: https://bugs.launchpad.net/widelands/+bug/1834001

> When trying to open an Online Game I get:

This is probably related to the server move this weekend. I don't think that this branch broke it, but I'll merge trunk to make sure that the networking code is up to date.

Revision history for this message
GunChleoc (gunchleoc) wrote :

> * How can I find the hotkeys and test them?

Best use https://code.launchpad.net/~widelands-dev/widelands/toolbar-dropdown-menus/+merge/368230 for that where you can see them in action.

> * Improved SyntaxErrorImpl - no idea how to test this :-)
> Can I break som RichText in an easy way?

You could edit one of the basic layouting functions in data/scripting/richtext.lua (for example, add an additional "<p>" to the p() function without closing the tag) and then access the encyclopedia.

> * Sure we want to log every (Lua Button) keypress?

It makes debugging scenarios easier.

> Nice refactoring of boring code :-)

And that bug in UI::Panel is really nasty, it cost me many hours of pulling my hair when I originally implemented the dropdowns.

9146. By GunChleoc

Merged trunk.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Played a bit and tested the MapEditor. So far all fine.

The new WorkAreaIndicator (when selecting a building) is a pain, at least on my OSX-Computer.
It is so slow, that selecting the correct building becomes quit difficult.
Do we have an optin to disable this? Do we have a bug for this?

This branch per se is fine, except for the Networking Issue.

Will wait for other opinions for now.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Can you test the networking issue with trunk? I don't think that it's related to this branch.

We also just merged an improvement for the work areas, so they will hopefully be a bit faster now.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 5233. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/549672031.
Appveyor build 5012. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_fix_dropdowns-5012.

9147. By GunChleoc

Merged trunk.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 5237. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/549847068.
Appveyor build 5016. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_fix_dropdowns-5016.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Compiling again, got:

src/wui/constructionsitewindow.cc:33:19: warning: unused variable 'pic_max_fill_indicator'
static const char pic_max_fill_indicator[] = "images/wui/buildings/max_fill_indicator.png";
                  ^
src/wui/constructionsitewindow.cc:34:19: warning: unused variable 'pic_priority_low'
static const char pic_priority_low[] = "images/wui/buildings/low_priority_button.png";
                  ^
src/wui/constructionsitewindow.cc:35:19: warning: unused variable 'pic_priority_normal'
static const char pic_priority_normal[] = "images/wui/buildings/normal_priority_button.png";
                  ^
src/wui/constructionsitewindow.cc:36:19: warning: unused variable 'pic_priority_high'
static const char pic_priority_high[] = "images/wui/buildings/high_priority_button.png";

Not sure where got that from?

After merging with trunk I dont see the network Issue any longer, well.

Did we just break savegame ccompatibility (yes with the elk_moose)
OK, will start a new game for testing

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

No I got a crash :( kaputtnik is right, we must play some network games on trunk, RSN.

FATAL ERROR - game crashed. Attempting emergency save.
...
InternetGaming: logout(SERVER_CRASHED)
[NetClient] Closing network socket connected to 2a03:4000:32:524:48cb:feff:feae:b0c6:7395.
Warning: Economy still has requests left on destruction
Warning: Economy still has flags left on destruction
Warning: Economy still has warehouses left on destruction
WareList: 125 items of 30 left.
WareList: 2 items of 33 left.
...
WareList: 1 items of 102 left.
Warning: Economy still has requests left on destruction
Warning: Economy still has flags left on destruction
Warning: Economy still has warehouses left on destruction
WareList: 127 items of 0 left.
...
WareList: 12 items of 102 left.
ObjectManager: ouch! remaining objects
lastserial: 2499
[NetRelayConnection] Closing network socket connected to 2a03:4000:32:524:48cb:feff:feae:b0c6:7397.
InternetGaming: logout(SERVER_CRASHED)
libc++abi.dylib: terminating with uncaught exception of type WException: [../src/logic/playercommand.cc:1070] Unreachable code was reached.
Abort trap: 6
ObjectManager: ouch! remaining objects
lastserial: 2499
[NetRelayConnection] Closing network socket connected to 2a03:4000:32:524:48cb:feff:feae:b0c6:7397.
InternetGaming: logout(SERVER_CRASHED)

I dont think this is related to tis particular branch, though.

Will try this on trunk again, later

review: Needs Fixing (testplay)
Revision history for this message
GunChleoc (gunchleoc) wrote :

Can you please create a bug report when finding errors caused by trunk, rather than putting them in a code review? You now have marked this branch as "needs fixing"; but it's trunk that needs fixing ;)

I have created a new bug report for this:

https://bugs.launchpad.net/widelands/+bug/1834151

Since you have found no further error in this branch, I'll go ahead and merge.

@bunnybot merge

> Compiling again, got:
>
> src/wui/constructionsitewindow.cc:33:19: warning: unused variable
> 'pic_max_fill_indicator'
> static const char pic_max_fill_indicator[] =
> "images/wui/buildings/max_fill_indicator.png";
> ^

These are already in trunk. It's time for another "fix compiler warnings" branch.
> After merging with trunk I dont see the network Issue any longer, well.

Excellent

> Did we just break savegame ccompatibility (yes with the elk_moose)

No, that branch has compatibility code. You won't be able to load new maps on older WIdelands versions though.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Tracked down the line for the crash, it is caused by the new WIP constructionsite options. So, definitely not related to this branch.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/templates/default/init.lua'
2--- data/templates/default/init.lua 2019-05-25 09:40:09 +0000
3+++ data/templates/default/init.lua 2019-06-24 16:19:42 +0000
4@@ -422,6 +422,13 @@
5 size = fs_font_size,
6 bold = true,
7 shadow = true
8+ },
9+ hotkey = {
10+ color = {180, 180, 180},
11+ face = fs_font_face,
12+ size = fs_font_size,
13+ bold = true,
14+ shadow = true
15 }
16 },
17 wui = {
18@@ -438,6 +445,13 @@
19 size = fs_font_size,
20 bold = true,
21 shadow = true
22+ },
23+ hotkey = {
24+ color = {180, 180, 180},
25+ face = fs_font_face,
26+ size = fs_font_size,
27+ bold = true,
28+ shadow = true
29 }
30 },
31 },
32@@ -615,6 +629,12 @@
33 size = 14,
34 bold = false,
35 },
36+ tooltip_hotkey = {
37+ color = {180, 180, 180},
38+ face = fs_font_face,
39+ size = 14,
40+ bold = false,
41+ },
42 tooltip_header = {
43 color = fs_font_color,
44 face = fs_font_face,
45
46=== modified file 'src/editor/CMakeLists.txt'
47--- src/editor/CMakeLists.txt 2019-05-12 07:45:59 +0000
48+++ src/editor/CMakeLists.txt 2019-06-24 16:19:42 +0000
49@@ -65,6 +65,8 @@
50 ui_menus/main_menu_save_map.h
51 ui_menus/main_menu_save_map_make_directory.cc
52 ui_menus/main_menu_save_map_make_directory.h
53+ ui_menus/map_size_box.cc
54+ ui_menus/map_size_box.h
55 ui_menus/player_menu.cc
56 ui_menus/player_menu.h
57 ui_menus/tool_change_height_options_menu.cc
58
59=== modified file 'src/editor/ui_menus/main_menu_new_map.cc'
60--- src/editor/ui_menus/main_menu_new_map.cc 2019-04-26 05:52:49 +0000
61+++ src/editor/ui_menus/main_menu_new_map.cc 2019-06-24 16:19:42 +0000
62@@ -46,24 +46,7 @@
63 margin_(4),
64 box_width_(get_inner_w() - 2 * margin_),
65 box_(this, margin_, margin_, UI::Box::Vertical, 0, 0, margin_),
66- width_(&box_,
67- 0,
68- 0,
69- box_width_,
70- box_width_ / 3,
71- 24,
72- _("Width"),
73- UI::DropdownType::kTextual,
74- UI::PanelStyle::kWui),
75- height_(&box_,
76- 0,
77- 0,
78- box_width_,
79- box_width_ / 3,
80- 24,
81- _("Height"),
82- UI::DropdownType::kTextual,
83- UI::PanelStyle::kWui),
84+ map_size_box_(box_, "new_map_menu", 4, parent.egbase().map().get_width(), parent.egbase().map().get_height()),
85 list_(&box_, 0, 0, box_width_, 330, UI::PanelStyle::kWui),
86 // Buttons
87 button_box_(&box_, 0, 0, UI::Box::Horizontal, 0, 0, margin_),
88@@ -84,18 +67,8 @@
89 UI::ButtonStyle::kWuiSecondary,
90 _("Cancel")) {
91
92- for (const int32_t& i : Widelands::kMapDimensions) {
93- width_.add(std::to_string(i), i);
94- height_.add(std::to_string(i), i);
95- }
96- width_.select(parent.egbase().map().get_width());
97- height_.select(parent.egbase().map().get_height());
98- width_.set_max_items(12);
99- height_.set_max_items(12);
100-
101 box_.set_size(100, 20); // Prevent assert failures
102- box_.add(&width_);
103- box_.add(&height_);
104+ box_.add(&map_size_box_, UI::Box::Resizing::kExpandBoth);
105 box_.add_space(margin_);
106 UI::Textarea* terrain_label = new UI::Textarea(&box_, _("Terrain:"));
107 box_.add(terrain_label);
108@@ -113,9 +86,7 @@
109 }
110 box_.add(&button_box_);
111
112- box_.set_size(box_width_, width_.get_h() + height_.get_h() + terrain_label->get_h() +
113- list_.get_h() + button_box_.get_h() + 9 * margin_);
114- set_size(get_w(), box_.get_h() + 2 * margin_ + get_h() - get_inner_h());
115+ set_center_panel(&box_);
116 fill_list();
117 center_to_parent();
118 }
119@@ -132,8 +103,8 @@
120
121 map->create_empty_map(
122 egbase.world(),
123- width_.get_selected() > 0 ? width_.get_selected() : Widelands::kMapDimensions[0],
124- height_.get_selected() > 0 ? height_.get_selected() : Widelands::kMapDimensions[0],
125+ map_size_box_.selected_width(),
126+ map_size_box_.selected_height(),
127 list_.get_selected(), _("No Name"),
128 g_options.pull_section("global").get_string("realname", pgettext("author_name", "Unknown")));
129
130
131=== modified file 'src/editor/ui_menus/main_menu_new_map.h'
132--- src/editor/ui_menus/main_menu_new_map.h 2019-04-26 05:52:49 +0000
133+++ src/editor/ui_menus/main_menu_new_map.h 2019-06-24 16:19:42 +0000
134@@ -20,10 +20,10 @@
135 #ifndef WL_EDITOR_UI_MENUS_MAIN_MENU_NEW_MAP_H
136 #define WL_EDITOR_UI_MENUS_MAIN_MENU_NEW_MAP_H
137
138-#include "logic/widelands.h"
139+#include "editor/ui_menus/map_size_box.h"
140+#include "logic/map_objects/description_maintainer.h"
141 #include "ui_basic/box.h"
142 #include "ui_basic/button.h"
143-#include "ui_basic/dropdown.h"
144 #include "ui_basic/listselect.h"
145 #include "ui_basic/window.h"
146
147@@ -46,8 +46,7 @@
148 int32_t margin_;
149 int32_t box_width_;
150 UI::Box box_;
151- UI::Dropdown<int32_t> width_;
152- UI::Dropdown<int32_t> height_;
153+ MapSizeBox map_size_box_;
154
155 // Terrains list
156 UI::Listselect<Widelands::DescriptionIndex> list_;
157
158=== modified file 'src/editor/ui_menus/main_menu_random_map.cc'
159--- src/editor/ui_menus/main_menu_random_map.cc 2019-05-26 17:21:15 +0000
160+++ src/editor/ui_menus/main_menu_random_map.cc 2019-06-24 16:19:42 +0000
161@@ -55,24 +55,7 @@
162 label_height_(text_height(UI::FontStyle::kLabel) + 2),
163 box_(this, margin_, margin_, UI::Box::Vertical, 0, 0, margin_),
164 // Size
165- width_(&box_,
166- 0,
167- 0,
168- box_width_,
169- box_width_ / 3,
170- 24,
171- _("Width"),
172- UI::DropdownType::kTextual,
173- UI::PanelStyle::kWui),
174- height_(&box_,
175- 0,
176- 0,
177- box_width_,
178- box_width_ / 3,
179- 24,
180- _("Height"),
181- UI::DropdownType::kTextual,
182- UI::PanelStyle::kWui),
183+ map_size_box_(box_, "random_map_menu", 4, parent.egbase().map().get_width(), parent.egbase().map().get_height()),
184 max_players_(2),
185 players_(&box_,
186 0,
187@@ -212,28 +195,13 @@
188 UI::ButtonStyle::kWuiSecondary,
189 _("Cancel")) {
190 int32_t box_height = 0;
191+ box_.set_size(100, 20); // Prevent assert failures
192
193 // ---------- Width + Height ----------
194
195- for (const int32_t& i : Widelands::kMapDimensions) {
196- width_.add(std::to_string(i), i);
197- height_.add(std::to_string(i), i);
198- }
199- width_.select(parent.egbase().map().get_width());
200- height_.select(parent.egbase().map().get_height());
201- width_.set_max_items(12);
202- height_.set_max_items(12);
203-
204- width_.selected.connect(
205- boost::bind(&MainMenuNewRandomMap::button_clicked, this, ButtonId::kMapSize));
206- height_.selected.connect(
207- boost::bind(&MainMenuNewRandomMap::button_clicked, this, ButtonId::kMapSize));
208-
209- box_.set_size(100, 20); // Prevent assert failures
210- box_.add(&width_);
211- box_.add(&height_);
212- box_height += margin_ + width_.get_h();
213- box_height += margin_ + height_.get_h();
214+ map_size_box_.set_selection_function([this] { button_clicked(ButtonId::kMapSize); });
215+ box_.add(&map_size_box_, UI::Box::Resizing::kExpandBoth);
216+ box_height += margin_ + map_size_box_.get_h();
217
218 // ---------- Players -----------
219
220@@ -396,19 +364,6 @@
221 */
222 void MainMenuNewRandomMap::button_clicked(MainMenuNewRandomMap::ButtonId n) {
223 switch (n) {
224- case ButtonId::kPlayers: // intended fall-through
225- case ButtonId::kMapSize:
226- // Restrict maximum players according to map size, but allow at least 2 players.
227- max_players_ = std::min(
228- static_cast<size_t>(kMaxMapgenPlayers), (find_dimension_index(width_.get_selected()) +
229- find_dimension_index(height_.get_selected())) /
230- 2 +
231- 2);
232- players_.set_interval(1, max_players_);
233- if (players_.get_value() > max_players_) {
234- players_.set_value(max_players_);
235- }
236- break;
237 case ButtonId::kWater:
238 waterval_ = water_.get_value();
239 normalize_landmass(n);
240@@ -433,17 +388,20 @@
241 break;
242 case ButtonId::kIslandMode:
243 break;
244+ case ButtonId::kPlayers: // intended fall-through
245+ case ButtonId::kMapSize:
246 case ButtonId::kNone:
247- // Make sure that all conditions are met
248+ // Restrict maximum players according to map size, but allow at least 2 players.
249 max_players_ = std::min(
250- static_cast<size_t>(kMaxMapgenPlayers), (find_dimension_index(width_.get_selected()) +
251- find_dimension_index(height_.get_selected())) /
252+ static_cast<size_t>(kMaxMapgenPlayers), (find_dimension_index(map_size_box_.selected_width()) +
253+ find_dimension_index(map_size_box_.selected_height())) /
254 2 +
255 2);
256 players_.set_interval(1, max_players_);
257 if (players_.get_value() > max_players_) {
258 players_.set_value(max_players_);
259 }
260+ // Make sure that landmass is consistent
261 normalize_landmass(n);
262 }
263 nr_edit_box_changed(); // Update ID String
264@@ -598,8 +556,8 @@
265 sstrm << map_info.mapNumber;
266 map_number_edit_.set_text(sstrm.str());
267
268- width_.select(map_info.w);
269- height_.select(map_info.h);
270+ map_size_box_.select_width(map_info.w);
271+ map_size_box_.select_height(map_info.h);
272
273 players_.set_interval(1, map_info.numPlayers); // hack to make sure we can set the value
274 players_.set_value(map_info.numPlayers);
275@@ -652,8 +610,8 @@
276 }
277
278 void MainMenuNewRandomMap::set_map_info(Widelands::UniqueRandomMapInfo& map_info) const {
279- map_info.w = width_.get_selected() > 0 ? width_.get_selected() : Widelands::kMapDimensions[0];
280- map_info.h = height_.get_selected() > 0 ? height_.get_selected() : Widelands::kMapDimensions[0];
281+ map_info.w = map_size_box_.selected_width();
282+ map_info.h = map_size_box_.selected_height();
283 map_info.waterRatio = static_cast<double>(waterval_) / 100.0;
284 map_info.landRatio = static_cast<double>(landval_) / 100.0;
285 map_info.wastelandRatio = static_cast<double>(wastelandval_) / 100.0;
286
287=== modified file 'src/editor/ui_menus/main_menu_random_map.h'
288--- src/editor/ui_menus/main_menu_random_map.h 2019-04-10 10:42:22 +0000
289+++ src/editor/ui_menus/main_menu_random_map.h 2019-06-24 16:19:42 +0000
290@@ -23,6 +23,7 @@
291 #include <vector>
292
293 #include "base/macros.h"
294+#include "editor/ui_menus/map_size_box.h"
295 #include "ui_basic/box.h"
296 #include "ui_basic/checkbox.h"
297 #include "ui_basic/dropdown.h"
298@@ -89,8 +90,7 @@
299 UI::Box box_;
300
301 // Size
302- UI::Dropdown<int32_t> width_;
303- UI::Dropdown<int32_t> height_;
304+ MapSizeBox map_size_box_;
305
306 uint8_t max_players_;
307 UI::SpinBox players_;
308
309=== added file 'src/editor/ui_menus/map_size_box.cc'
310--- src/editor/ui_menus/map_size_box.cc 1970-01-01 00:00:00 +0000
311+++ src/editor/ui_menus/map_size_box.cc 2019-06-24 16:19:42 +0000
312@@ -0,0 +1,76 @@
313+/*
314+ * Copyright (C) 2019 by the Widelands Development Team
315+ *
316+ * This program is free software; you can redistribute it and/or
317+ * modify it under the terms of the GNU General Public License
318+ * as published by the Free Software Foundation; either version 2
319+ * of the License, or (at your option) any later version.
320+ *
321+ * This program is distributed in the hope that it will be useful,
322+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
323+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
324+ * GNU General Public License for more details.
325+ *
326+ * You should have received a copy of the GNU General Public License
327+ * along with this program; if not, write to the Free Software
328+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
329+ *
330+ */
331+
332+#include "editor/ui_menus/map_size_box.h"
333+
334+#include "base/i18n.h"
335+#include "logic/map.h"
336+
337+MapSizeBox::MapSizeBox(UI::Box& parent, const std::string& name, int spacing, int map_width, int map_height)
338+ : UI::Box(&parent, 0, 0, UI::Box::Horizontal, 0, 0, spacing),
339+ width_(this,
340+ name + "_map_width",
341+ 0,
342+ 0,
343+ 160,
344+ 12,
345+ 24,
346+ _("Width"),
347+ UI::DropdownType::kTextual,
348+ UI::PanelStyle::kWui,
349+ UI::ButtonStyle::kWuiSecondary),
350+ height_(this,
351+ name + "_map_height",
352+ 0,
353+ 0,
354+ 160,
355+ 12,
356+ 24,
357+ _("Height"),
358+ UI::DropdownType::kTextual,
359+ UI::PanelStyle::kWui,
360+ UI::ButtonStyle::kWuiSecondary) {
361+ for (const int32_t& i : Widelands::kMapDimensions) {
362+ width_.add(std::to_string(i), i);
363+ height_.add(std::to_string(i), i);
364+ }
365+ width_.select(map_width);
366+ height_.select(map_height);
367+ add(&width_, UI::Box::Resizing::kFillSpace);
368+ add(&height_, UI::Box::Resizing::kFillSpace);
369+}
370+
371+void MapSizeBox::set_selection_function(const std::function<void()> func) {
372+ width_.selected.connect(func);
373+ height_.selected.connect(func);
374+}
375+
376+uint32_t MapSizeBox::selected_width() const {
377+ return width_.get_selected();
378+}
379+uint32_t MapSizeBox::selected_height() const {
380+ return height_.get_selected();
381+}
382+
383+void MapSizeBox::select_width(int new_width) {
384+ width_.select(new_width);
385+}
386+void MapSizeBox::select_height(int new_height) {
387+ height_.select(new_height);
388+}
389
390=== added file 'src/editor/ui_menus/map_size_box.h'
391--- src/editor/ui_menus/map_size_box.h 1970-01-01 00:00:00 +0000
392+++ src/editor/ui_menus/map_size_box.h 2019-06-24 16:19:42 +0000
393@@ -0,0 +1,57 @@
394+/*
395+ * Copyright (C) 2019 by the Widelands Development Team
396+ *
397+ * This program is free software; you can redistribute it and/or
398+ * modify it under the terms of the GNU General Public License
399+ * as published by the Free Software Foundation; either version 2
400+ * of the License, or (at your option) any later version.
401+ *
402+ * This program is distributed in the hope that it will be useful,
403+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
404+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
405+ * GNU General Public License for more details.
406+ *
407+ * You should have received a copy of the GNU General Public License
408+ * along with this program; if not, write to the Free Software
409+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
410+ *
411+ */
412+
413+#ifndef WL_EDITOR_UI_MENUS_MAP_SIZE_BOX_H
414+#define WL_EDITOR_UI_MENUS_MAP_SIZE_BOX_H
415+
416+#include "ui_basic/box.h"
417+#include "ui_basic/dropdown.h"
418+
419+/**
420+ * A box containing 2 dropdowns to select map width and height with horizontal layout.
421+ * Selections are taken from Widelands::kMapDimensions.
422+ */
423+struct MapSizeBox : public UI::Box {
424+
425+ /**
426+ * @param parent The parent panel
427+ * @param name A string to prefix for the dropdown names, so that they can be idenfitied uniquely
428+ * @param spacing The horizontal space between the 2 dropdowns
429+ * @param map_width Width to preselect
430+ * @param map_height Height to preselect
431+ */
432+ MapSizeBox(UI::Box& parent, const std::string& name, int spacing, int map_width, int map_height);
433+
434+ /// This function will be triggered when a new width or height is selected from the dropdowns
435+ void set_selection_function(const std::function<void()> func);
436+ /// The currently selected width
437+ uint32_t selected_width() const;
438+ /// The currently selected height
439+ uint32_t selected_height() const;
440+ /// Set the selected width
441+ void select_width(int new_width);
442+ /// Set the selected height
443+ void select_height(int new_height);
444+
445+private:
446+ UI::Dropdown<uint32_t> width_;
447+ UI::Dropdown<uint32_t> height_;
448+};
449+
450+#endif // end of include guard: WL_EDITOR_UI_MENUS_MAP_SIZE_BOX_H
451
452=== modified file 'src/editor/ui_menus/player_menu.cc'
453--- src/editor/ui_menus/player_menu.cc 2019-05-12 07:45:59 +0000
454+++ src/editor/ui_menus/player_menu.cc 2019-06-24 16:19:42 +0000
455@@ -38,8 +38,9 @@
456
457 namespace {
458 constexpr int kMargin = 4;
459+// Make room for 8 players
460 // If this ever gets changed, don't forget to change the strings in the warning box as well.
461-constexpr Widelands::PlayerNumber max_recommended_players = 8;
462+constexpr Widelands::PlayerNumber kMaxRecommendedPlayers = 8;
463 } // namespace
464
465 class EditorPlayerMenuWarningBox : public UI::Window {
466@@ -122,14 +123,15 @@
467 : UI::UniqueWindow(&parent, "players_menu", &registry, 100, 100, _("Player Options")),
468 box_(this, kMargin, kMargin, UI::Box::Vertical),
469 no_of_players_(&box_,
470+ "dropdown_map_players",
471 0,
472 0,
473 50,
474- 100,
475+ kMaxRecommendedPlayers,
476 24,
477 _("Number of players"),
478 UI::DropdownType::kTextual,
479- UI::PanelStyle::kWui) {
480+ UI::PanelStyle::kWui, UI::ButtonStyle::kWuiSecondary) {
481 box_.set_size(100, 100); // Prevent assert failures
482 box_.add(&no_of_players_, UI::Box::Resizing::kFullSize);
483 box_.add_space(2 * kMargin);
484@@ -153,7 +155,7 @@
485 iterate_player_numbers(p, kMaxPlayers) {
486 const bool map_has_player = p <= nr_players;
487
488- no_of_players_.add(boost::lexical_cast<std::string>(static_cast<unsigned int>(p)), p);
489+ no_of_players_.add(boost::lexical_cast<std::string>(static_cast<unsigned int>(p)), p, nullptr, p == nr_players);
490 no_of_players_.selected.connect(
491 boost::bind(&EditorPlayerMenu::no_of_players_clicked, boost::ref(*this)));
492
493@@ -168,8 +170,8 @@
494
495 // Tribe
496 UI::Dropdown<std::string>* plr_tribe =
497- new UI::Dropdown<std::string>(row, 0, 0, 50, 400, plr_name->get_h(), _("Tribe"),
498- UI::DropdownType::kPictorial, UI::PanelStyle::kWui);
499+ new UI::Dropdown<std::string>(row, (boost::format("dropdown_tribe%d") % static_cast<unsigned int>(p)).str(), 0, 0, 50, 16, plr_name->get_h(), _("Tribe"),
500+ UI::DropdownType::kPictorial, UI::PanelStyle::kWui, UI::ButtonStyle::kWuiSecondary);
501 {
502 i18n::Textdomain td("tribes");
503 for (const Widelands::TribeBasicInfo& tribeinfo : Widelands::get_all_tribeinfos()) {
504@@ -219,8 +221,6 @@
505 std::unique_ptr<PlayerEditRow>(new PlayerEditRow(row, plr_name, plr_position, plr_tribe)));
506 }
507
508- // Make room for 8 players
509- no_of_players_.set_max_items(max_recommended_players);
510 no_of_players_.select(nr_players);
511
512 // Init button states
513@@ -249,13 +249,13 @@
514 }
515
516 // Display a warning if there are too many players
517- if (nr_players > max_recommended_players) {
518+ if (nr_players > kMaxRecommendedPlayers) {
519 if (g_options.pull_section("global").get_bool(
520 "editor_player_menu_warn_too_many_players", true)) {
521 EditorPlayerMenuWarningBox warning(get_parent());
522 if (warning.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kBack) {
523 // Abort setting of players
524- no_of_players_.select(std::min(old_nr_players, max_recommended_players));
525+ no_of_players_.select(std::min(old_nr_players, kMaxRecommendedPlayers));
526 }
527 }
528 }
529
530=== modified file 'src/editor/ui_menus/tool_resize_options_menu.cc'
531--- src/editor/ui_menus/tool_resize_options_menu.cc 2019-04-24 07:09:29 +0000
532+++ src/editor/ui_menus/tool_resize_options_menu.cc 2019-06-24 16:19:42 +0000
533@@ -39,69 +39,32 @@
534 : EditorToolOptionsMenu(parent, registry, 260, 200, _("Resize")),
535 resize_tool_(resize_tool),
536 box_(this, hmargin(), vmargin(), UI::Box::Vertical, 0, 0, vspacing()),
537- new_width_(&box_,
538- 0,
539- 0,
540- get_inner_w() - 2 * hmargin(),
541- 200,
542- 24,
543- _("New width"),
544- UI::DropdownType::kTextual,
545- UI::PanelStyle::kWui),
546- new_height_(&box_,
547- 0,
548- 0,
549- get_inner_w() - 2 * hmargin(),
550- 200,
551- 24,
552- _("New height"),
553- UI::DropdownType::kTextual,
554- UI::PanelStyle::kWui),
555+ map_size_box_(box_, "tool_resize_map", 4, parent.egbase().map().get_width(), parent.egbase().map().get_height()),
556 text_area_(
557- &box_,
558- 0,
559- 0,
560- get_inner_w() - 2 * hmargin(),
561- 48,
562- UI::PanelStyle::kWui,
563- _("Select the new map size, then click the map to split it at the desired location."),
564- UI::Align::kCenter,
565- UI::MultilineTextarea::ScrollMode::kNoScrolling) {
566-
567- for (const int32_t& i : Widelands::kMapDimensions) {
568- new_width_.add(std::to_string(i), i);
569- new_height_.add(std::to_string(i), i);
570- }
571- new_width_.select(parent.egbase().map().get_width());
572- new_height_.select(parent.egbase().map().get_height());
573- new_width_.set_max_items(8);
574- new_height_.set_max_items(8);
575-
576- new_width_.selected.connect(
577- boost::bind(&EditorToolResizeOptionsMenu::update_width, boost::ref(*this)));
578- new_height_.selected.connect(
579- boost::bind(&EditorToolResizeOptionsMenu::update_height, boost::ref(*this)));
580-
581- box_.add(&text_area_);
582- box_.set_size(100, 20); // Prevent assert failures
583- box_.add(&new_width_, UI::Box::Resizing::kFullSize);
584- box_.add(&new_height_, UI::Box::Resizing::kFullSize);
585-
586- box_.set_size(get_inner_w() - 2 * hmargin(),
587- new_width_.get_h() + new_height_.get_h() + text_area_.get_h() + 2 * vspacing());
588- set_inner_size(get_inner_w(), box_.get_h() + 1 * vmargin());
589+ &box_,
590+ 0,
591+ 0,
592+ get_inner_w() - 2 * hmargin(),
593+ 48,
594+ UI::PanelStyle::kWui,
595+ _("Select the new map size, then click the map to split it at the desired location."),
596+ UI::Align::kCenter,
597+ UI::MultilineTextarea::ScrollMode::kNoScrolling) {
598+
599+ map_size_box_.set_selection_function([this] { update_dimensions(); });
600+
601+ box_.add(&map_size_box_, UI::Box::Resizing::kExpandBoth);
602+ box_.add(&text_area_, UI::Box::Resizing::kFullSize);
603+
604+ set_center_panel(&box_);
605 }
606
607-void EditorToolResizeOptionsMenu::update_width() {
608- int32_t w = new_width_.get_selected();
609+void EditorToolResizeOptionsMenu::update_dimensions() {
610+ const int32_t w = map_size_box_.selected_width();
611+ const int32_t h = map_size_box_.selected_height();
612 assert(w > 0);
613+ assert(h > 0);
614 resize_tool_.set_width(w);
615- select_correct_tool();
616-}
617-
618-void EditorToolResizeOptionsMenu::update_height() {
619- int32_t h = new_height_.get_selected();
620- assert(h > 0);
621 resize_tool_.set_height(h);
622 select_correct_tool();
623 }
624
625=== modified file 'src/editor/ui_menus/tool_resize_options_menu.h'
626--- src/editor/ui_menus/tool_resize_options_menu.h 2019-04-24 07:09:29 +0000
627+++ src/editor/ui_menus/tool_resize_options_menu.h 2019-06-24 16:19:42 +0000
628@@ -20,6 +20,7 @@
629 #ifndef WL_EDITOR_UI_MENUS_TOOL_RESIZE_OPTIONS_MENU_H
630 #define WL_EDITOR_UI_MENUS_TOOL_RESIZE_OPTIONS_MENU_H
631
632+#include "editor/ui_menus/map_size_box.h"
633 #include "editor/ui_menus/tool_options_menu.h"
634 #include "ui_basic/box.h"
635 #include "ui_basic/dropdown.h"
636@@ -33,13 +34,11 @@
637
638 private:
639 EditorInteractive& eia();
640- void update_width();
641- void update_height();
642+ void update_dimensions();
643
644 EditorResizeTool& resize_tool_;
645 UI::Box box_;
646- UI::Dropdown<int32_t> new_width_;
647- UI::Dropdown<int32_t> new_height_;
648+ MapSizeBox map_size_box_;
649 UI::MultilineTextarea text_area_;
650 };
651
652
653=== modified file 'src/graphic/style_manager.cc'
654--- src/graphic/style_manager.cc 2019-05-26 17:21:15 +0000
655+++ src/graphic/style_manager.cc 2019-06-24 16:19:42 +0000
656@@ -214,6 +214,7 @@
657 add_font_style(UI::FontStyle::kDisabled, *element_table, "disabled");
658 add_font_style(UI::FontStyle::kLabel, *element_table, "label");
659 add_font_style(UI::FontStyle::kTooltipHeader, *element_table, "tooltip_header");
660+ add_font_style(UI::FontStyle::kTooltipHotkey, *element_table, "tooltip_hotkey");
661 add_font_style(UI::FontStyle::kTooltip, *element_table, "tooltip");
662 add_font_style(UI::FontStyle::kWarning, *element_table, "warning");
663 add_font_style(
664@@ -339,7 +340,7 @@
665 void StyleManager::add_table_style(UI::PanelStyle style, const LuaTable& table) {
666 table_styles_.insert(std::make_pair(
667 style, std::unique_ptr<const UI::TableStyleInfo>(new UI::TableStyleInfo(
668- read_font_style(table, "enabled"), read_font_style(table, "disabled")))));
669+ read_font_style(table, "enabled"), read_font_style(table, "disabled"), read_font_style(table, "hotkey")))));
670 }
671
672 void StyleManager::set_statistics_plot_style(const LuaTable& table) {
673
674=== modified file 'src/graphic/styles/font_style.h'
675--- src/graphic/styles/font_style.h 2019-05-26 17:21:15 +0000
676+++ src/graphic/styles/font_style.h 2019-06-24 16:19:42 +0000
677@@ -44,6 +44,7 @@
678 kDisabled,
679 kLabel,
680 kTooltipHeader,
681+ kTooltipHotkey,
682 kTooltip,
683 kWarning,
684 kWuiAttackBoxSliderLabel,
685
686=== modified file 'src/graphic/styles/table_style.h'
687--- src/graphic/styles/table_style.h 2019-05-26 17:21:15 +0000
688+++ src/graphic/styles/table_style.h 2019-06-24 16:19:42 +0000
689@@ -27,8 +27,8 @@
690 namespace UI {
691
692 struct TableStyleInfo {
693- explicit TableStyleInfo(UI::FontStyleInfo* init_enabled, UI::FontStyleInfo* init_disabled)
694- : enabled_(init_enabled), disabled_(init_disabled) {
695+ explicit TableStyleInfo(UI::FontStyleInfo* init_enabled, UI::FontStyleInfo* init_disabled, UI::FontStyleInfo* init_hotkey)
696+ : enabled_(init_enabled), disabled_(init_disabled), hotkey_(init_hotkey) {
697 }
698
699 const UI::FontStyleInfo& enabled() const {
700@@ -37,10 +37,14 @@
701 const UI::FontStyleInfo& disabled() const {
702 return *disabled_.get();
703 }
704+ const UI::FontStyleInfo& hotkey() const {
705+ return *hotkey_.get();
706+ }
707
708 private:
709 std::unique_ptr<const UI::FontStyleInfo> enabled_;
710 std::unique_ptr<const UI::FontStyleInfo> disabled_;
711+ std::unique_ptr<const UI::FontStyleInfo> hotkey_;
712 };
713
714 } // namespace UI
715
716=== modified file 'src/graphic/text/rt_parse.cc'
717--- src/graphic/text/rt_parse.cc 2019-02-28 11:46:22 +0000
718+++ src/graphic/text/rt_parse.cc 2019-06-24 16:19:42 +0000
719@@ -119,8 +119,10 @@
720
721 void Tag::parse_attribute(TextStream& ts, std::unordered_set<std::string>& allowed_attrs) {
722 std::string aname = ts.till_any("=");
723- if (!allowed_attrs.count(aname))
724- throw SyntaxErrorImpl(ts.line(), ts.col(), "an allowed attribute", aname, ts.peek(100));
725+ if (!allowed_attrs.count(aname)) {
726+ const std::string error_info = (boost::format("an allowed attribute for '%s' tag") % name_).str();
727+ throw SyntaxErrorImpl(ts.line(), ts.col(), error_info, aname, ts.peek(100));
728+ }
729
730 ts.skip(1);
731
732
733=== modified file 'src/graphic/text_layout.cc'
734--- src/graphic/text_layout.cc 2019-05-26 17:21:15 +0000
735+++ src/graphic/text_layout.cc 2019-06-24 16:19:42 +0000
736@@ -243,3 +243,10 @@
737 }
738 NEVER_HERE();
739 }
740+
741+std::string as_tooltip_text_with_hotkey(const std::string& text, const std::string& hotkey) {
742+ static boost::format f("<rt><p>%s %s</p></rt>");
743+ f % g_gr->styles().font_style(UI::FontStyle::kTooltip).as_font_tag(text);
744+ f % g_gr->styles().font_style(UI::FontStyle::kTooltipHotkey).as_font_tag("(" + hotkey + ")");
745+ return f.str();
746+}
747
748=== modified file 'src/graphic/text_layout.h'
749--- src/graphic/text_layout.h 2019-05-26 17:21:15 +0000
750+++ src/graphic/text_layout.h 2019-06-24 16:19:42 +0000
751@@ -107,4 +107,6 @@
752 /// Paragraph in menu info texts
753 std::string as_content(const std::string& txt, UI::PanelStyle style);
754
755+std::string as_tooltip_text_with_hotkey(const std::string& text, const std::string& hotkey);
756+
757 #endif // end of include guard: WL_GRAPHIC_TEXT_LAYOUT_H
758
759=== modified file 'src/scripting/lua_ui.cc'
760--- src/scripting/lua_ui.cc 2019-02-23 11:00:49 +0000
761+++ src/scripting/lua_ui.cc 2019-06-24 16:19:42 +0000
762@@ -73,7 +73,7 @@
763 */
764 const char LuaPanel::className[] = "Panel";
765 const PropertyType<LuaPanel> LuaPanel::Properties[] = {
766- PROP_RO(LuaPanel, buttons), PROP_RO(LuaPanel, tabs), PROP_RO(LuaPanel, windows),
767+ PROP_RO(LuaPanel, buttons), PROP_RO(LuaPanel, dropdowns), PROP_RO(LuaPanel, tabs), PROP_RO(LuaPanel, windows),
768 PROP_RW(LuaPanel, position_x), PROP_RW(LuaPanel, position_y), PROP_RW(LuaPanel, width),
769 PROP_RW(LuaPanel, height), {nullptr, nullptr, nullptr},
770 };
771@@ -82,6 +82,26 @@
772 {nullptr, nullptr},
773 };
774
775+// Look for all descendant panels of class P and add the corresponding Lua version to the currently active Lua table. Class P needs to be a NamedPanel.
776+template <class P, class LuaP>
777+static void put_all_visible_panels_into_table(lua_State* L, UI::Panel* g) {
778+ if (g == nullptr) {
779+ return;
780+ }
781+
782+ for (UI::Panel* child = g->get_first_child(); child; child = child->get_next_sibling()) {
783+ put_all_visible_panels_into_table<P, LuaP>(L, child);
784+
785+ if (upcast(P, specific_panel, child)) {
786+ if (specific_panel->is_visible()) {
787+ lua_pushstring(L, specific_panel->get_name());
788+ to_lua<LuaP>(L, new LuaP(specific_panel));
789+ lua_rawset(L, -3);
790+ }
791+ }
792+ }
793+}
794+
795 /*
796 * Properties
797 */
798@@ -97,26 +117,26 @@
799
800 (RO) An :class:`array` of all visible buttons inside this Panel.
801 */
802-static void put_all_visible_buttons_into_table(lua_State* L, UI::Panel* g) {
803- if (!g)
804- return;
805-
806- for (UI::Panel* f = g->get_first_child(); f; f = f->get_next_sibling()) {
807- put_all_visible_buttons_into_table(L, f);
808-
809- if (upcast(UI::Button, b, f))
810- if (b->is_visible()) {
811- lua_pushstring(L, b->get_name());
812- to_lua<LuaButton>(L, new LuaButton(b));
813- lua_rawset(L, -3);
814- }
815- }
816-}
817 int LuaPanel::get_buttons(lua_State* L) {
818 assert(panel_);
819
820 lua_newtable(L);
821- put_all_visible_buttons_into_table(L, panel_);
822+ put_all_visible_panels_into_table<UI::Button, LuaButton>(L, panel_);
823+
824+ return 1;
825+}
826+
827+
828+/* RST
829+ .. attribute:: dropdowns
830+
831+ (RO) An :class:`array` of all visible dropdowns inside this Panel.
832+*/
833+int LuaPanel::get_dropdowns(lua_State* L) {
834+ assert(panel_);
835+
836+ lua_newtable(L);
837+ put_all_visible_panels_into_table<UI::BaseDropdown, LuaDropdown>(L, panel_);
838
839 return 1;
840 }
841@@ -156,25 +176,11 @@
842 (RO) A :class:`array` of all currently open windows that are
843 children of this Panel.
844 */
845-static void put_all_visible_windows_into_table(lua_State* L, UI::Panel* g) {
846- if (!g)
847- return;
848-
849- for (UI::Panel* f = g->get_first_child(); f; f = f->get_next_sibling()) {
850- put_all_visible_windows_into_table(L, f);
851-
852- if (upcast(UI::Window, win, f)) {
853- lua_pushstring(L, win->get_name());
854- to_lua<LuaWindow>(L, new LuaWindow(win));
855- lua_rawset(L, -3);
856- }
857- }
858-}
859 int LuaPanel::get_windows(lua_State* L) {
860 assert(panel_);
861
862 lua_newtable(L);
863- put_all_visible_windows_into_table(L, panel_);
864+ put_all_visible_panels_into_table<UI::Window, LuaWindow>(L, panel_);
865
866 return 1;
867 }
868@@ -317,6 +323,7 @@
869 event in tutorials
870 */
871 int LuaButton::press(lua_State* /* L */) {
872+ log("Pressing button '%s'\n", get()->get_name().c_str());
873 get()->handle_mousein(true);
874 get()->handle_mousepress(SDL_BUTTON_LEFT, 1, 1);
875 return 0;
876@@ -328,12 +335,102 @@
877 it.
878 */
879 int LuaButton::click(lua_State* /* L */) {
880+ log("Clicking button '%s'\n", get()->get_name().c_str());
881 get()->handle_mousein(true);
882 get()->handle_mousepress(SDL_BUTTON_LEFT, 1, 1);
883 get()->handle_mouserelease(SDL_BUTTON_LEFT, 1, 1);
884 return 0;
885 }
886
887+
888+/*
889+ * C Functions
890+ */
891+
892+/* RST
893+Dropdown
894+--------
895+
896+.. class:: Dropdown
897+
898+ Child of: :class:`Panel`
899+
900+ This represents a dropdown menu.
901+*/
902+const char LuaDropdown::className[] = "Dropdown";
903+const MethodType<LuaDropdown> LuaDropdown::Methods[] = {
904+ METHOD(LuaDropdown, open),
905+ METHOD(LuaDropdown, highlight_item),
906+ METHOD(LuaDropdown, select),
907+ {nullptr, nullptr},
908+};
909+const PropertyType<LuaDropdown> LuaDropdown::Properties[] = {
910+ PROP_RO(LuaDropdown, name),
911+ {nullptr, nullptr, nullptr},
912+};
913+
914+/*
915+ * Properties
916+ */
917+
918+// Documented in parent Class
919+int LuaDropdown::get_name(lua_State* L) {
920+ lua_pushstring(L, get()->get_name());
921+ return 1;
922+}
923+
924+/*
925+ * Lua Functions
926+ */
927+/* RST
928+ .. method:: open
929+
930+ Open this dropdown menu.
931+*/
932+int LuaDropdown::open(lua_State* /* L */) {
933+ log("Opening dropdown '%s'\n", get()->get_name().c_str());
934+ get()->set_list_visibility(true);
935+ return 0;
936+}
937+
938+/* RST
939+ .. method:: highlight_item(index)
940+
941+ :arg index: the index of the item to highlight, starting from ``1``
942+ :type index: :class:`integer`
943+
944+ Highlights an item in this dropdown without triggering a selection.
945+*/
946+int LuaDropdown::highlight_item(lua_State* L) {
947+ unsigned int desired_item = luaL_checkuint32(L, -1);
948+ if (desired_item < 1 || desired_item > get()->size()) {
949+ report_error(L, "Attempted to highlight item %d on dropdown '%s'. Avaliable range for this dropdown is 1-%d.", desired_item, get()->get_name().c_str(), get()->size());
950+ }
951+ log("Highlighting item %d in dropdown '%s'\n", desired_item, get()->get_name().c_str());
952+ // Open the dropdown
953+ get()->set_list_visibility(true);
954+ // Press arrow down until the desired item is highlighted
955+ SDL_Keysym code;
956+ code.sym = SDLK_DOWN;
957+ for (size_t i = 1; i < desired_item; ++i) {
958+ get()->handle_key(true, code);
959+ }
960+ return 0;
961+}
962+
963+/* RST
964+ .. method:: select()
965+
966+ Selects the currently highlighted item in this dropdown.
967+*/
968+int LuaDropdown::select(lua_State* /* L */) {
969+ log("Selecting current item in dropdown '%s'\n", get()->get_name().c_str());
970+ SDL_Keysym code;
971+ code.sym = SDLK_RETURN;
972+ get()->handle_key(true, code);
973+ return 0;
974+}
975+
976 /*
977 * C Functions
978 */
979@@ -388,6 +485,7 @@
980 Click this tab making it the active one.
981 */
982 int LuaTab::click(lua_State* /* L */) {
983+ log("Clicking tab '%s'\n", get()->get_name().c_str());
984 get()->activate();
985 return 0;
986 }
987@@ -437,6 +535,7 @@
988 not use it any longer.
989 */
990 int LuaWindow::close(lua_State* /* L */) {
991+ log("Closing window '%s'\n", get()->get_name().c_str());
992 delete panel_;
993 panel_ = nullptr;
994 return 0;
995@@ -789,6 +888,10 @@
996 add_parent<LuaButton, LuaPanel>(L);
997 lua_pop(L, 1); // Pop the meta table
998
999+ register_class<LuaDropdown>(L, "ui", true);
1000+ add_parent<LuaDropdown, LuaPanel>(L);
1001+ lua_pop(L, 1); // Pop the meta table
1002+
1003 register_class<LuaTab>(L, "ui", true);
1004 add_parent<LuaTab, LuaPanel>(L);
1005 lua_pop(L, 1); // Pop the meta table
1006
1007=== modified file 'src/scripting/lua_ui.h'
1008--- src/scripting/lua_ui.h 2019-02-23 11:00:49 +0000
1009+++ src/scripting/lua_ui.h 2019-06-24 16:19:42 +0000
1010@@ -23,6 +23,7 @@
1011 #include "scripting/lua.h"
1012 #include "scripting/luna.h"
1013 #include "ui_basic/button.h"
1014+#include "ui_basic/dropdown.h"
1015 #include "ui_basic/tabpanel.h"
1016 #include "ui_basic/window.h"
1017 #include "wui/interactive_base.h"
1018@@ -57,7 +58,7 @@
1019 }
1020
1021 void __persist(lua_State* L) override {
1022- report_error(L, "Trying to persist a User Interface Panel which is no supported!");
1023+ report_error(L, "Trying to persist a User Interface Panel which is not supported!");
1024 }
1025 void __unpersist(lua_State* L) override {
1026 report_error(L, "Trying to unpersist a User Interface Panel which is "
1027@@ -68,6 +69,7 @@
1028 * Properties
1029 */
1030 int get_buttons(lua_State* L);
1031+ int get_dropdowns(lua_State* L);
1032 int get_tabs(lua_State* L);
1033 int get_windows(lua_State* L);
1034 int get_width(lua_State* L);
1035@@ -121,6 +123,41 @@
1036 }
1037 };
1038
1039+
1040+class LuaDropdown : public LuaPanel {
1041+public:
1042+ LUNA_CLASS_HEAD(LuaDropdown);
1043+
1044+ LuaDropdown() : LuaPanel() {
1045+ }
1046+ explicit LuaDropdown(UI::Panel* p) : LuaPanel(p) {
1047+ }
1048+ explicit LuaDropdown(lua_State* L) : LuaPanel(L) {
1049+ }
1050+ ~LuaDropdown() override {
1051+ }
1052+
1053+ /*
1054+ * Properties
1055+ */
1056+ int get_name(lua_State* L);
1057+
1058+ /*
1059+ * Lua Methods
1060+ */
1061+ int open(lua_State* L);
1062+ int highlight_item(lua_State* L);
1063+ int select(lua_State* L);
1064+
1065+ /*
1066+ * C Methods
1067+ */
1068+ UI::BaseDropdown* get() {
1069+ return static_cast<UI::BaseDropdown*>(panel_);
1070+ }
1071+};
1072+
1073+
1074 class LuaTab : public LuaPanel {
1075 public:
1076 LUNA_CLASS_HEAD(LuaTab);
1077
1078=== modified file 'src/ui_basic/button.cc'
1079--- src/ui_basic/button.cc 2019-05-26 17:21:15 +0000
1080+++ src/ui_basic/button.cc 2019-06-24 16:19:42 +0000
1081@@ -216,7 +216,7 @@
1082 }
1083 }
1084
1085- } else if (title_.length()) {
1086+ } else if (!title_.empty()) {
1087 // Otherwise draw title string centered
1088 std::shared_ptr<const UI::RenderedText> rendered_text = autofit_text(
1089 richtext_escape(title_), style_to_use.font(), get_inner_w() - 2 * kButtonImageMargin);
1090@@ -285,7 +285,6 @@
1091 time_nextact_ = time;
1092 play_click();
1093 sigclicked();
1094- clicked();
1095 // The button may not exist at this point (for example if the button
1096 // closed the dialog that it is part of). So member variables may no
1097 // longer be accessed.
1098@@ -339,7 +338,6 @@
1099 if (highlighted_ && enabled_) {
1100 play_click();
1101 sigclicked();
1102- clicked();
1103 // The button may not exist at this point (for example if the button
1104 // closed the dialog that it is part of). So member variables may no
1105 // longer be accessed.
1106
1107=== modified file 'src/ui_basic/button.h'
1108--- src/ui_basic/button.h 2019-04-17 16:52:55 +0000
1109+++ src/ui_basic/button.h 2019-06-24 16:19:42 +0000
1110@@ -156,9 +156,6 @@
1111 boost::signals2::signal<void()> sigmouseout;
1112
1113 protected:
1114- virtual void clicked() {
1115- } /// Override this to react on the click.
1116-
1117 bool highlighted_; // mouse is over the button
1118 bool pressed_; // mouse is clicked over the button
1119 bool enabled_;
1120
1121=== modified file 'src/ui_basic/checkbox.cc'
1122--- src/ui_basic/checkbox.cc 2019-05-26 17:21:15 +0000
1123+++ src/ui_basic/checkbox.cc 2019-06-24 16:19:42 +0000
1124@@ -177,7 +177,7 @@
1125 */
1126 bool Statebox::handle_mousepress(const uint8_t btn, int32_t, int32_t) {
1127 if (btn == SDL_BUTTON_LEFT && (flags_ & Is_Enabled)) {
1128- clicked();
1129+ button_clicked();
1130 return true;
1131 }
1132 return false;
1133@@ -190,7 +190,7 @@
1134 /**
1135 * Toggle the checkbox state
1136 */
1137-void Checkbox::clicked() {
1138+void Checkbox::button_clicked() {
1139 clickedto(!get_state());
1140 set_state(!get_state());
1141 play_click();
1142
1143=== modified file 'src/ui_basic/checkbox.h'
1144--- src/ui_basic/checkbox.h 2019-02-23 11:00:49 +0000
1145+++ src/ui_basic/checkbox.h 2019-06-24 16:19:42 +0000
1146@@ -74,7 +74,7 @@
1147
1148 private:
1149 void layout() override;
1150- virtual void clicked() = 0;
1151+ virtual void button_clicked() = 0;
1152
1153 enum Flags {
1154 Is_Highlighted = 0x01,
1155@@ -131,7 +131,7 @@
1156 }
1157
1158 private:
1159- void clicked() override;
1160+ void button_clicked() override;
1161 };
1162 } // namespace UI
1163
1164
1165=== modified file 'src/ui_basic/dropdown.cc'
1166--- src/ui_basic/dropdown.cc 2019-05-26 17:21:15 +0000
1167+++ src/ui_basic/dropdown.cc 2019-06-24 16:19:42 +0000
1168@@ -27,7 +27,6 @@
1169 #include "base/macros.h"
1170 #include "graphic/align.h"
1171 #include "graphic/font_handler.h"
1172-#include "graphic/graphic.h"
1173 #include "graphic/rendertarget.h"
1174 #include "graphic/text_layout.h"
1175 #include "ui_basic/mouse_constants.h"
1176@@ -53,30 +52,30 @@
1177
1178 int BaseDropdown::next_id_ = 0;
1179
1180-BaseDropdown::BaseDropdown(UI::Panel* parent,
1181+BaseDropdown::BaseDropdown(UI::Panel* parent, const std::string& name,
1182 int32_t x,
1183 int32_t y,
1184 uint32_t w,
1185- uint32_t h,
1186+ uint32_t max_list_items,
1187 int button_dimension,
1188 const std::string& label,
1189 const DropdownType type,
1190- UI::PanelStyle style)
1191- : UI::Panel(parent,
1192+ UI::PanelStyle style, ButtonStyle button_style)
1193+ : UI::NamedPanel(parent,
1194+ name,
1195 x,
1196 y,
1197- type == DropdownType::kPictorial ? button_dimension : w,
1198+ (type == DropdownType::kPictorial || type == DropdownType::kPictorialMenu) ? button_dimension : w,
1199 // Height only to fit the button, so we can use this in Box layout.
1200 base_height(button_dimension, style)),
1201 id_(next_id_++),
1202- max_list_height_(h - 2 * get_h()),
1203- list_width_(w),
1204+ max_list_items_(max_list_items),
1205+ max_list_height_(std::numeric_limits<uint32_t>::max()),
1206 list_offset_x_(0),
1207 list_offset_y_(0),
1208- button_dimension_(button_dimension),
1209 base_height_(base_height(button_dimension, style)),
1210 mouse_tolerance_(50),
1211- button_box_(this, 0, 0, UI::Box::Horizontal, w, h),
1212+ button_box_(this, 0, 0, UI::Box::Horizontal, w, get_h()),
1213 push_button_(type == DropdownType::kTextual ?
1214 new UI::Button(&button_box_,
1215 "dropdown_select",
1216@@ -84,9 +83,7 @@
1217 0,
1218 button_dimension,
1219 get_h(),
1220- style == UI::PanelStyle::kFsMenu ?
1221- UI::ButtonStyle::kFsMenuMenu :
1222- UI::ButtonStyle::kWuiSecondary,
1223+ button_style,
1224 g_gr->images().get("images/ui_basic/scrollbar_down.png")) :
1225 nullptr),
1226 display_button_(&button_box_,
1227@@ -97,8 +94,10 @@
1228 w - button_dimension :
1229 type == DropdownType::kTextualNarrow ? w : button_dimension,
1230 get_h(),
1231- style == UI::PanelStyle::kFsMenu ? UI::ButtonStyle::kFsMenuSecondary :
1232- UI::ButtonStyle::kWuiSecondary,
1233+ type == DropdownType::kTextual ?
1234+ (style == UI::PanelStyle::kFsMenu ? UI::ButtonStyle::kFsMenuSecondary :
1235+ UI::ButtonStyle::kWuiSecondary) :
1236+ button_style,
1237 label),
1238 label_(label),
1239 type_(type),
1240@@ -110,26 +109,30 @@
1241 }
1242
1243 // Close whenever another dropdown is opened
1244- subscriber_ = Notifications::subscribe<NoteDropdown>([this](const NoteDropdown& note) {
1245+ dropdown_subscriber_ = Notifications::subscribe<NoteDropdown>([this](const NoteDropdown& note) {
1246 if (id_ != note.id) {
1247 close();
1248 }
1249 });
1250+ graphic_resolution_changed_subscriber_ = Notifications::subscribe<GraphicResolutionChanged>(
1251+ [this](const GraphicResolutionChanged&) {
1252+ layout();
1253+ });
1254
1255- assert(max_list_height_ > 0);
1256+ assert(max_list_items_ > 0);
1257 // Hook into highest parent that we can get so that we can drop down outside the panel.
1258- // Positioning breaks down with TabPanels, so we exclude them.
1259- while (parent->get_parent() && !is_a(UI::TabPanel, parent->get_parent())) {
1260- parent = parent->get_parent();
1261+ UI::Panel* list_parent = &display_button_;
1262+ while (list_parent->get_parent()) {
1263+ list_parent = list_parent->get_parent();
1264 }
1265- list_ = new UI::Listselect<uintptr_t>(parent, 0, 0, w, 0, style, ListselectLayout::kDropdown);
1266+ list_ = new UI::Listselect<uintptr_t>(list_parent, 0, 0, w, 0, style, ListselectLayout::kDropdown);
1267
1268 list_->set_visible(false);
1269- button_box_.add(&display_button_);
1270+ button_box_.add(&display_button_, UI::Box::Resizing::kExpandBoth);
1271 display_button_.sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
1272 if (push_button_ != nullptr) {
1273 display_button_.set_perm_pressed(true);
1274- button_box_.add(push_button_);
1275+ button_box_.add(push_button_, UI::Box::Resizing::kFullSize);
1276 push_button_->sigclicked.connect(boost::bind(&BaseDropdown::toggle_list, this));
1277 }
1278 button_box_.set_size(w, get_h());
1279@@ -138,26 +141,19 @@
1280 set_can_focus(true);
1281 set_value();
1282
1283- // Find parent windows so that we can move the list along with them
1284- UI::Panel* parent_window_candidate = get_parent();
1285- while (parent_window_candidate) {
1286- if (upcast(UI::Window, window, parent_window_candidate)) {
1287- window->position_changed.connect(boost::bind(&BaseDropdown::layout, this));
1288- }
1289- parent_window_candidate = parent_window_candidate->get_parent();
1290+ // Find parent windows, boxes etc. so that we can move the list along with them
1291+ UI::Panel* ancestor = this;
1292+ while ((ancestor = ancestor->get_parent()) != nullptr) {
1293+ ancestor->position_changed.connect([this] {layout(); });
1294 }
1295-
1296 layout();
1297 }
1298
1299 BaseDropdown::~BaseDropdown() {
1300 // The list needs to be able to drop outside of windows, so it won't close with the window.
1301- // Deleting here leads to conflict with who gets to delete it, so we hide it instead.
1302+ // Deleting here leads to a conflict as to who gets to delete it, so we just leave it.
1303+ // It will be hidden as soon as the mouse moves away anyway.
1304 // TODO(GunChleoc): Investigate whether we can find a better solution for this
1305- if (list_) {
1306- list_->clear();
1307- list_->set_visible(false);
1308- }
1309 }
1310
1311 void BaseDropdown::set_height(int height) {
1312@@ -165,27 +161,19 @@
1313 layout();
1314 }
1315
1316-void BaseDropdown::set_max_items(int items) {
1317- set_height(list_->get_lineheight() * items + base_height_);
1318-}
1319-
1320 void BaseDropdown::layout() {
1321- const int base_h = base_height_;
1322- const int w = type_ == DropdownType::kPictorial ? button_dimension_ : get_w();
1323- button_box_.set_size(w, base_h);
1324- display_button_.set_desired_size(
1325- type_ == DropdownType::kTextual ? w - button_dimension_ : w, base_h);
1326- int new_list_height =
1327- std::min(static_cast<int>(list_->size()) * list_->get_lineheight(), max_list_height_);
1328- list_->set_size(type_ != DropdownType::kPictorial ? w : list_width_, new_list_height);
1329- set_desired_size(w, base_h);
1330+ int list_width = list_->calculate_desired_width();
1331+
1332+ const int new_list_height =
1333+ std::min(max_list_height_ / list_->get_lineheight(), std::min(list_->size(), max_list_items_)) * list_->get_lineheight();
1334+ list_->set_size(std::max(list_width, button_box_.get_w()), new_list_height);
1335
1336 // Update list position. The list is hooked into the highest parent that we can get so that we
1337- // can drop down outside the panel. Positioning breaks down with TabPanels, so we exclude them.
1338- UI::Panel* parent = get_parent();
1339- int new_list_x = get_x() + parent->get_x() + parent->get_lborder();
1340- int new_list_y = get_y() + parent->get_y() + parent->get_tborder();
1341- while (parent->get_parent() && !is_a(UI::TabPanel, parent->get_parent())) {
1342+ // can drop down outside the panel.
1343+ UI::Panel* parent = &display_button_;
1344+ int new_list_x = display_button_.get_x();
1345+ int new_list_y = display_button_.get_y();
1346+ while (parent->get_parent()) {
1347 parent = parent->get_parent();
1348 new_list_x += parent->get_x() + parent->get_lborder();
1349 new_list_y += parent->get_y() + parent->get_tborder();
1350@@ -217,13 +205,24 @@
1351 }
1352 }
1353
1354+void BaseDropdown::set_size(int nw, int nh) {
1355+ button_box_.set_size(nw, nh);
1356+ Panel::set_size(nw, nh);
1357+ layout();
1358+}
1359+void BaseDropdown::set_desired_size(int nw, int nh) {
1360+ button_box_.set_desired_size(nw, nh);
1361+ Panel::set_desired_size(nw, nh);
1362+ layout();
1363+}
1364+
1365 void BaseDropdown::add(const std::string& name,
1366 const uint32_t value,
1367 const Image* pic,
1368 const bool select_this,
1369- const std::string& tooltip_text) {
1370+ const std::string& tooltip_text, const std::string& hotkey = std::string()) {
1371 assert(pic != nullptr || type_ != DropdownType::kPictorial);
1372- list_->add(name, value, pic, select_this, tooltip_text);
1373+ list_->add(name, value, pic, select_this, tooltip_text, hotkey);
1374 if (select_this) {
1375 set_value();
1376 }
1377@@ -248,7 +247,7 @@
1378
1379 void BaseDropdown::set_label(const std::string& text) {
1380 label_ = text;
1381- if (type_ != DropdownType::kPictorial) {
1382+ if (type_ != DropdownType::kPictorial && type_ != DropdownType::kPictorialMenu) {
1383 display_button_.set_title(label_);
1384 }
1385 }
1386@@ -267,7 +266,7 @@
1387
1388 void BaseDropdown::set_errored(const std::string& error_message) {
1389 set_tooltip((boost::format(_("%1%: %2%")) % _("Error") % error_message).str());
1390- if (type_ != DropdownType::kPictorial) {
1391+ if (type_ != DropdownType::kPictorial && type_ != DropdownType::kPictorialMenu) {
1392 set_label(_("Error"));
1393 } else {
1394 set_image(g_gr->images().get("images/ui_basic/different.png"));
1395@@ -295,7 +294,7 @@
1396
1397 void BaseDropdown::set_pos(Vector2i point) {
1398 UI::Panel::set_pos(point);
1399- list_->set_pos(Vector2i(point.x, point.y + get_h()));
1400+ layout();
1401 }
1402
1403 void BaseDropdown::clear() {
1404@@ -321,6 +320,11 @@
1405 }
1406
1407 void BaseDropdown::update() {
1408+ if (type_ == DropdownType::kPictorialMenu) {
1409+ // Menus never change their main image and text
1410+ return;
1411+ }
1412+
1413 const std::string name = list_->has_selection() ?
1414 list_->get_selected_name() :
1415 /** TRANSLATORS: Selection in Dropdown menus. */
1416@@ -349,6 +353,34 @@
1417 current_selection_ = list_->selection_index();
1418 }
1419
1420+void BaseDropdown::toggle() {
1421+ set_list_visibility(!list_->is_visible());
1422+}
1423+
1424+void BaseDropdown::set_list_visibility(bool open) {
1425+ if (!is_enabled_) {
1426+ list_->set_visible(false);
1427+ return;
1428+ }
1429+ list_->set_visible(open);
1430+ if (list_->is_visible()) {
1431+ list_->move_to_top();
1432+ focus();
1433+ set_mouse_pos(
1434+ Vector2i(
1435+ display_button_.get_x() + (display_button_.get_w() * 3 / 5),
1436+ display_button_.get_y() + (display_button_.get_h() * 2 / 5)));
1437+ if (type_ == DropdownType::kPictorialMenu && !has_selection() && !list_->empty()) {
1438+ select(0);
1439+ }
1440+ }
1441+ if (type_ != DropdownType::kTextual) {
1442+ display_button_.set_perm_pressed(list_->is_visible());
1443+ }
1444+ // Make sure that the list covers and deactivates the elements below it
1445+ set_layout_toplevel(list_->is_visible());
1446+}
1447+
1448 void BaseDropdown::toggle_list() {
1449 if (!is_enabled_) {
1450 list_->set_visible(false);
1451@@ -387,6 +419,7 @@
1452 case SDLK_RETURN:
1453 if (list_->is_visible()) {
1454 set_value();
1455+ return true;
1456 }
1457 break;
1458 case SDLK_ESCAPE:
1459@@ -397,6 +430,7 @@
1460 }
1461 break;
1462 case SDLK_DOWN:
1463+ case SDLK_UP:
1464 if (!list_->is_visible() && !is_mouse_away()) {
1465 toggle_list();
1466 return true;
1467
1468=== modified file 'src/ui_basic/dropdown.h'
1469--- src/ui_basic/dropdown.h 2019-04-17 16:52:55 +0000
1470+++ src/ui_basic/dropdown.h 2019-06-24 16:19:42 +0000
1471@@ -25,6 +25,7 @@
1472
1473 #include <boost/signals2.hpp>
1474
1475+#include "graphic/graphic.h"
1476 #include "graphic/image.h"
1477 #include "notifications/note_ids.h"
1478 #include "notifications/notifications.h"
1479@@ -44,31 +45,35 @@
1480 }
1481 };
1482
1483-/// The narrow textual dropdown omits the extra push button
1484-enum class DropdownType { kTextual, kTextualNarrow, kPictorial };
1485+/// The narrow textual dropdown omits the extra push button.
1486+/// Use kPictorialMenu if you want to trigger an action without changing the menu button.
1487+enum class DropdownType { kTextual, kTextualNarrow, kPictorial, kPictorialMenu };
1488
1489 /// Implementation for a dropdown menu that lets the user select a value.
1490-class BaseDropdown : public Panel {
1491+class BaseDropdown : public NamedPanel {
1492 protected:
1493 /// \param parent the parent panel
1494+ /// \param name a name so that we can reference the dropdown via Lua
1495 /// \param x the x-position within 'parent'
1496 /// \param y the y-position within 'parent'
1497 /// \param list_w the dropdown's width
1498- /// \param list_h the maximum height for the dropdown list
1499+ /// \param max_list_items the maximum number of items shown in the list before it starts using a scrollbar
1500 /// \param button_dimension the width of the push button in textual dropdowns. For pictorial
1501 /// dropdowns, this is both the width and the height of the button.
1502 /// \param label a label to prefix to the selected entry on the display button.
1503 /// \param type whether this is a textual or pictorial dropdown
1504 /// \param style the style used for buttons and background
1505 BaseDropdown(Panel* parent,
1506+ const std::string& name,
1507 int32_t x,
1508 int32_t y,
1509 uint32_t list_w,
1510- uint32_t list_h,
1511+ uint32_t max_list_items,
1512 int button_dimension,
1513 const std::string& label,
1514 const DropdownType type,
1515- PanelStyle style);
1516+ PanelStyle style,
1517+ ButtonStyle button_style);
1518 ~BaseDropdown() override;
1519
1520 public:
1521@@ -120,10 +125,20 @@
1522 /// Handle keypresses
1523 bool handle_key(bool down, SDL_Keysym code) override;
1524
1525+ /// Set maximum available height in the UI
1526 void set_height(int height);
1527
1528- /// Set the number of items to fit in the list
1529- void set_max_items(int items);
1530+ /// Toggle the list on and off and position the mouse on the button so that the dropdown won't close on us.
1531+ /// If this is a menu and nothing was selected yet, select the first item for easier keyboard navigation.
1532+ void toggle();
1533+
1534+ /// If 'open', show the list and position the mouse on the button so that the dropdown won't close on us.
1535+ /// If this is a menu and nothing was selected yet, select the first item for easier keyboard navigation.
1536+ /// If not 'open', close the list.
1537+ void set_list_visibility(bool open);
1538+
1539+ void set_size(int nw, int nh) override;
1540+ void set_desired_size(int w, int h) override;
1541
1542 protected:
1543 /// Add an element to the list
1544@@ -132,13 +147,14 @@
1545 /// \param pic an image to illustrate the entry. Can be nullptr for textual dropdowns.
1546 /// \param select_this whether this element should be selected
1547 /// \param tooltip_text a tooltip for this entry
1548+ /// \param hotkey a hotkey tip if any
1549 ///
1550 /// Text conventions: Title Case for the 'name', Sentence case for the 'tooltip_text'
1551 void add(const std::string& name,
1552 uint32_t value,
1553- const Image* pic = nullptr,
1554- const bool select_this = false,
1555- const std::string& tooltip_text = std::string());
1556+ const Image* pic,
1557+ const bool select_this,
1558+ const std::string& tooltip_text, const std::string& hotkey);
1559
1560 /// \return the index of the selected element
1561 uint32_t get_selected() const;
1562@@ -161,7 +177,7 @@
1563
1564 /// Updates the title and tooltip of the display button and triggers a 'selected' signal.
1565 void set_value();
1566- /// Toggles the dropdown list on and off.
1567+ /// Toggles the dropdown list on and off and sends a notification if the list is visible afterwards.
1568 void toggle_list();
1569 /// Toggle the list closed if the dropdown is currently expanded.
1570 void close();
1571@@ -172,14 +188,14 @@
1572 /// Give each dropdown a unique ID
1573 static int next_id_;
1574 const int id_;
1575- std::unique_ptr<Notifications::Subscriber<NoteDropdown>> subscriber_;
1576+ std::unique_ptr<Notifications::Subscriber<NoteDropdown>> dropdown_subscriber_;
1577+ std::unique_ptr<Notifications::Subscriber<GraphicResolutionChanged>> graphic_resolution_changed_subscriber_;
1578
1579 // Dimensions
1580- int max_list_height_;
1581- int list_width_;
1582+ unsigned int max_list_items_;
1583+ unsigned int max_list_height_;
1584 int list_offset_x_;
1585 int list_offset_y_;
1586- const int button_dimension_;
1587 const int base_height_;
1588 const int mouse_tolerance_; // Allow mouse outside the panel a bit before autocollapse
1589 UI::Box button_box_;
1590@@ -199,10 +215,11 @@
1591 template <typename Entry> class Dropdown : public BaseDropdown {
1592 public:
1593 /// \param parent the parent panel
1594+ /// \param name a name so that we can reference the dropdown via Lua
1595 /// \param x the x-position within 'parent'
1596 /// \param y the y-position within 'parent'
1597 /// \param list_w the dropdown's width
1598- /// \param list_h the maximum height for the dropdown list
1599+ /// \param max_list_items the maximum number of items shown in the list before it starts using a scrollbar
1600 /// \param button_dimension the width of the push button in textual dropdowns. For pictorial
1601 /// dropdowns, this is both the width and the height of the button.
1602 /// \param label a label to prefix to the selected entry on the display button.
1603@@ -210,15 +227,17 @@
1604 /// \param style the style used for buttons and background
1605 /// Text conventions: Title Case for all elements
1606 Dropdown(Panel* parent,
1607+ const std::string& name,
1608 int32_t x,
1609 int32_t y,
1610 uint32_t list_w,
1611- uint32_t list_h,
1612+ uint32_t max_list_items,
1613 int button_dimension,
1614 const std::string& label,
1615 const DropdownType type,
1616- PanelStyle style)
1617- : BaseDropdown(parent, x, y, list_w, list_h, button_dimension, label, type, style) {
1618+ PanelStyle style,
1619+ ButtonStyle button_style)
1620+ : BaseDropdown(parent, name, x, y, list_w, max_list_items, button_dimension, label, type, style, button_style) {
1621 }
1622 ~Dropdown() {
1623 entry_cache_.clear();
1624@@ -231,13 +250,14 @@
1625 /// only.
1626 /// \param select_this whether this element should be selected
1627 /// \param tooltip_text a tooltip for this entry
1628+ /// \param hotkey a hotkey tip if any
1629 void add(const std::string& name,
1630 Entry value,
1631 const Image* pic = nullptr,
1632 const bool select_this = false,
1633- const std::string& tooltip_text = std::string()) {
1634+ const std::string& tooltip_text = std::string(), const std::string& hotkey = std::string()) {
1635 entry_cache_.push_back(std::unique_ptr<Entry>(new Entry(value)));
1636- BaseDropdown::add(name, size(), pic, select_this, tooltip_text);
1637+ BaseDropdown::add(name, size(), pic, select_this, tooltip_text, hotkey);
1638 }
1639
1640 /// \return the selected element
1641
1642=== modified file 'src/ui_basic/icongrid.cc'
1643--- src/ui_basic/icongrid.cc 2019-02-23 11:00:49 +0000
1644+++ src/ui_basic/icongrid.cc 2019-06-24 16:19:42 +0000
1645@@ -104,7 +104,7 @@
1646 }
1647
1648 void IconGrid::clicked_button(uint32_t idx) {
1649- clicked(idx);
1650+ icon_clicked(idx);
1651 play_click();
1652 }
1653
1654
1655=== modified file 'src/ui_basic/icongrid.h'
1656--- src/ui_basic/icongrid.h 2019-02-23 11:00:49 +0000
1657+++ src/ui_basic/icongrid.h 2019-06-24 16:19:42 +0000
1658@@ -38,7 +38,7 @@
1659 struct IconGrid : public Panel {
1660 IconGrid(Panel* parent, int32_t x, int32_t y, int32_t cellw, int32_t cellh, int32_t cols);
1661
1662- boost::signals2::signal<void(int32_t)> clicked;
1663+ boost::signals2::signal<void(int32_t)> icon_clicked;
1664 boost::signals2::signal<void(int32_t)> mouseout;
1665 boost::signals2::signal<void(int32_t)> mousein;
1666
1667
1668=== modified file 'src/ui_basic/listselect.cc'
1669--- src/ui_basic/listselect.cc 2019-05-26 17:21:15 +0000
1670+++ src/ui_basic/listselect.cc 2019-06-24 16:19:42 +0000
1671@@ -34,8 +34,25 @@
1672 #include "ui_basic/mouse_constants.h"
1673
1674 constexpr int kMargin = 2;
1675+constexpr int kHotkeyGap = 16;
1676
1677 namespace UI {
1678+
1679+
1680+BaseListselect::EntryRecord::EntryRecord(const std::string& init_name,
1681+ uint32_t init_entry,
1682+ const Image* init_pic,
1683+ const std::string& tooltip_text, const std::string& hotkey_text, const TableStyleInfo& style) :
1684+ name(init_name),
1685+ entry_(init_entry),
1686+ pic(init_pic),
1687+ tooltip(tooltip_text),
1688+ name_alignment(i18n::has_rtl_character(init_name.c_str(), 20) ? Align::kRight : Align::kLeft),
1689+ hotkey_alignment(i18n::has_rtl_character(hotkey_text.c_str(), 20) ? Align::kRight : Align::kLeft) {
1690+ rendered_name = UI::g_fh->render(as_richtext_paragraph(richtext_escape(name), style.enabled()));
1691+ rendered_hotkey = UI::g_fh->render(as_richtext_paragraph(richtext_escape(hotkey_text), style.hotkey()));
1692+}
1693+
1694 /**
1695 * Initialize a list select panel
1696 *
1697@@ -53,17 +70,19 @@
1698 UI::PanelStyle style,
1699 const ListselectLayout selection_mode)
1700 : Panel(parent, x, y, w, h),
1701+ widest_text_(0),
1702+ widest_hotkey_(0),
1703 scrollbar_(this, get_w() - Scrollbar::kSize, 0, Scrollbar::kSize, h, style),
1704 scrollpos_(0),
1705 selection_(no_selection_index()),
1706 last_click_time_(-10000),
1707 last_selection_(no_selection_index()),
1708 selection_mode_(selection_mode),
1709- font_style_(&g_gr->styles().table_style(style).enabled()),
1710+ table_style_(g_gr->styles().table_style(style)),
1711 background_style_(selection_mode == ListselectLayout::kDropdown ?
1712 g_gr->styles().dropdown_style(style) :
1713 nullptr),
1714- lineheight_(text_height(*font_style_) + kMargin) {
1715+ lineheight_(text_height(table_style_.enabled()) + kMargin) {
1716 set_thinks(false);
1717
1718 scrollbar_.moved.connect(boost::bind(&BaseListselect::set_scrollpos, this, _1));
1719@@ -115,13 +134,12 @@
1720 uint32_t entry,
1721 const Image* pic,
1722 bool const sel,
1723- const std::string& tooltip_text) {
1724- EntryRecord* er = new EntryRecord();
1725+ const std::string& tooltip_text, const std::string& hotkey) {
1726+ EntryRecord* er = new EntryRecord(
1727+ name,
1728+ entry, pic, tooltip_text,
1729+ hotkey, table_style_);
1730
1731- er->entry_ = entry;
1732- er->pic = pic;
1733- er->name = name;
1734- er->tooltip = tooltip_text;
1735 int entry_height = lineheight_;
1736 if (pic) {
1737 int w = pic->width();
1738@@ -142,41 +160,6 @@
1739 select(entry_records_.size() - 1);
1740 }
1741
1742-void BaseListselect::add_front(const std::string& name,
1743- const Image* pic,
1744- bool const sel,
1745- const std::string& tooltip_text) {
1746- EntryRecord* er = new EntryRecord();
1747-
1748- er->entry_ = 0;
1749- for (EntryRecord* temp_entry : entry_records_) {
1750- ++(temp_entry)->entry_;
1751- }
1752-
1753- er->pic = pic;
1754- er->name = name;
1755- er->tooltip = tooltip_text;
1756-
1757- int entry_height = lineheight_;
1758- if (pic) {
1759- int w = pic->width();
1760- int h = pic->height();
1761- entry_height = (h >= entry_height) ? h : entry_height;
1762- if (max_pic_width_ < w)
1763- max_pic_width_ = w;
1764- }
1765-
1766- if (entry_height > lineheight_)
1767- lineheight_ = entry_height;
1768-
1769- entry_records_.push_front(er);
1770-
1771- layout();
1772-
1773- if (sel)
1774- select(0);
1775-}
1776-
1777 /**
1778 * Sort the listbox alphabetically. make sure that the current selection stays
1779 * valid (though it might scroll out of visibility).
1780@@ -275,13 +258,45 @@
1781 }
1782
1783 int BaseListselect::get_lineheight() const {
1784- return lineheight_ + kMargin;
1785+ return lineheight_ + (selection_mode_ == ListselectLayout::kDropdown ? 2 * kMargin : kMargin);
1786 }
1787
1788 uint32_t BaseListselect::get_eff_w() const {
1789 return scrollbar_.is_enabled() ? get_w() - scrollbar_.get_w() : get_w();
1790 }
1791
1792+// Make enough room for all texts + hotkeys in tabular format
1793+int BaseListselect::calculate_desired_width() {
1794+ if (entry_records_.empty()) {
1795+ return 0;
1796+ }
1797+
1798+ // Find the widest entries
1799+ widest_text_ = 0;
1800+ widest_hotkey_ = 0;
1801+ for (const EntryRecord* er : entry_records_) {
1802+ const int current_text_width = er->rendered_name->width();
1803+ if (current_text_width > widest_text_) {
1804+ widest_text_ = current_text_width;
1805+ }
1806+ const int current_hotkey_width = er->rendered_hotkey->width();
1807+ if (current_hotkey_width > widest_hotkey_) {
1808+ widest_hotkey_ = current_hotkey_width;
1809+ }
1810+ }
1811+
1812+ // Add up the width
1813+ int text_width = widest_text_;
1814+ if (widest_hotkey_ > 0) {
1815+ text_width += kHotkeyGap;
1816+ text_width += widest_hotkey_;
1817+ }
1818+
1819+ const int picw = max_pic_width_ ? max_pic_width_ + 10 : 0;
1820+ const int old_width = get_w();
1821+ return text_width + picw + 8 + old_width - get_eff_w();
1822+}
1823+
1824 void BaseListselect::layout() {
1825 scrollbar_.set_size(scrollbar_.get_w(), get_h());
1826 scrollbar_.set_pos(Vector2i(get_w() - Scrollbar::kSize, 0));
1827@@ -294,15 +309,9 @@
1828 }
1829 // For dropdowns, autoincrease width
1830 if (selection_mode_ == ListselectLayout::kDropdown) {
1831- for (size_t i = 0; i < entry_records_.size(); ++i) {
1832- const EntryRecord& er = *entry_records_[i];
1833- std::shared_ptr<const UI::RenderedText> rendered_text =
1834- UI::g_fh->render(as_richtext_paragraph(richtext_escape(er.name), *font_style_));
1835- int picw = max_pic_width_ ? max_pic_width_ + 10 : 0;
1836- int difference = rendered_text->width() + picw + 8 - get_eff_w();
1837- if (difference > 0) {
1838- set_size(get_w() + difference, get_h());
1839- }
1840+ const int new_width = calculate_desired_width();
1841+ if (new_width > get_w()) {
1842+ set_size(new_width, get_h());
1843 }
1844 }
1845 }
1846@@ -341,10 +350,9 @@
1847 assert(eff_h < std::numeric_limits<int32_t>::max());
1848
1849 const EntryRecord& er = *entry_records_[idx];
1850- std::shared_ptr<const UI::RenderedText> rendered_text =
1851- UI::g_fh->render(as_richtext_paragraph(richtext_escape(er.name), *font_style_));
1852+ const int text_height = std::max(er.rendered_name->height(), er.rendered_hotkey->height());
1853
1854- int lineheight = std::max(get_lineheight(), rendered_text->height());
1855+ int lineheight = std::max(get_lineheight(), text_height);
1856
1857 // Don't draw over the bottom edge
1858 lineheight = std::min(eff_h - y, lineheight);
1859@@ -380,26 +388,15 @@
1860 // Now draw pictures
1861 if (er.pic) {
1862 dst.blit(Vector2i(UI::g_fh->fontset()->is_rtl() ? get_eff_w() - er.pic->width() - 1 : 1,
1863- y + (get_lineheight() - er.pic->height()) / 2),
1864+ y + (lineheight_ - er.pic->height()) / 2),
1865 er.pic);
1866 }
1867
1868- // Position the text according to alignment
1869- Align alignment = i18n::has_rtl_character(er.name.c_str(), 20) ? Align::kRight : Align::kLeft;
1870- if (alignment == UI::Align::kRight) {
1871- point.x += maxw - picw;
1872- }
1873-
1874- // Shift for image width
1875- if (!UI::g_fh->fontset()->is_rtl()) {
1876- point.x += picw;
1877- }
1878-
1879 // Fix vertical position for mixed font heights
1880- if (get_lineheight() > rendered_text->height()) {
1881- point.y += (lineheight_ - rendered_text->height()) / 2;
1882+ if (get_lineheight() > text_height) {
1883+ point.y += (lineheight_ - text_height) / 2;
1884 } else {
1885- point.y -= (rendered_text->height() - lineheight_) / 2;
1886+ point.y -= (text_height - lineheight_) / 2;
1887 }
1888
1889 // Don't draw over the bottom edge
1890@@ -407,8 +404,35 @@
1891 if (lineheight < 0) {
1892 break;
1893 }
1894- rendered_text->draw(
1895- dst, point, Recti(0, 0, maxw, lineheight), alignment, RenderedText::CropMode::kSelf);
1896+
1897+ // Tabular layout for hotkeys + shift for image width
1898+ Vector2i text_point(point);
1899+ Vector2i hotkey_point(point);
1900+ if (UI::g_fh->fontset()->is_rtl()) {
1901+ if (er.name_alignment == UI::Align::kRight) {
1902+ text_point.x = maxw - widest_text_ - picw;
1903+ } else if (widest_hotkey_ > 0) {
1904+ text_point.x += widest_hotkey_ + kHotkeyGap;
1905+ }
1906+ } else {
1907+ hotkey_point.x = maxw - widest_hotkey_;
1908+ text_point.x += picw;
1909+ }
1910+
1911+ // Position the text and hotkey according to their alignment
1912+ if (er.name_alignment == UI::Align::kRight) {
1913+ text_point.x += widest_text_ - er.rendered_name->width();
1914+ }
1915+ if (er.hotkey_alignment == UI::Align::kRight) {
1916+ hotkey_point.x += widest_hotkey_ - er.rendered_hotkey->width();
1917+ }
1918+
1919+ er.rendered_name->draw(
1920+ dst, text_point, Recti(0, 0, maxw - widest_hotkey_, lineheight), UI::Align::kLeft, RenderedText::CropMode::kSelf);
1921+ if (er.rendered_hotkey->width() > 0) {
1922+ er.rendered_hotkey->draw(
1923+ dst, hotkey_point, Recti(0, 0, maxw - widest_text_, lineheight), UI::Align::kLeft, RenderedText::CropMode::kSelf);
1924+ }
1925 y += get_lineheight();
1926 ++idx;
1927 }
1928@@ -442,7 +466,7 @@
1929 return false;
1930 play_click();
1931 select(y);
1932- clicked(selection_);
1933+ clicked();
1934
1935 if // check if doubleclicked
1936 (time - real_last_click_time < DOUBLE_CLICK_INTERVAL && last_selection_ == selection_ &&
1937
1938=== modified file 'src/ui_basic/listselect.h'
1939--- src/ui_basic/listselect.h 2019-04-19 05:59:14 +0000
1940+++ src/ui_basic/listselect.h 2019-06-24 16:19:42 +0000
1941@@ -26,7 +26,7 @@
1942 #include <boost/signals2.hpp>
1943
1944 #include "graphic/color.h"
1945-#include "graphic/styles/font_style.h"
1946+#include "graphic/styles/table_style.h"
1947 #include "ui_basic/panel.h"
1948 #include "ui_basic/scrollbar.h"
1949
1950@@ -56,7 +56,6 @@
1951 ~BaseListselect() override;
1952
1953 boost::signals2::signal<void(uint32_t)> selected;
1954- boost::signals2::signal<void(uint32_t)> clicked;
1955 boost::signals2::signal<void(uint32_t)> double_clicked;
1956
1957 void clear();
1958@@ -66,16 +65,10 @@
1959 */
1960 void add(const std::string& name,
1961 uint32_t value,
1962- const Image* pic = nullptr,
1963- const bool select_this = false,
1964- const std::string& tooltip_text = std::string());
1965- /**
1966- * Text conventions: Title Case for the 'name', Sentence case for the 'tooltip_text'
1967- */
1968- void add_front(const std::string& name,
1969- const Image* pic = nullptr,
1970- const bool select_this = false,
1971- const std::string& tooltip_text = std::string());
1972+ const Image* pic,
1973+ const bool select_this,
1974+ const std::string& tooltip_text, const std::string& hotkey);
1975+
1976 void remove(uint32_t);
1977 void remove(const char* name);
1978
1979@@ -113,6 +106,8 @@
1980
1981 uint32_t get_eff_w() const;
1982
1983+ int calculate_desired_width();
1984+
1985 void layout() override;
1986
1987 // Drawing and event handling
1988@@ -125,22 +120,32 @@
1989
1990 private:
1991 static const int32_t DOUBLE_CLICK_INTERVAL = 500; // half a second
1992+ static const int32_t ms_darken_value = -20;
1993
1994 void set_scrollpos(int32_t);
1995
1996-private:
1997- static const int32_t ms_darken_value = -20;
1998-
1999 struct EntryRecord {
2000- uint32_t entry_;
2001+ explicit EntryRecord(const std::string& init_name,
2002+ uint32_t init_entry,
2003+ const Image* init_pic,
2004+ const std::string& tooltip_text, const std::string& hotkey_text,
2005+ const UI::TableStyleInfo& style);
2006+
2007+ const std::string name;
2008+ const uint32_t entry_;
2009 const Image* pic;
2010- std::string name;
2011- std::string tooltip;
2012+ const std::string tooltip;
2013+ const Align name_alignment;
2014+ const Align hotkey_alignment;
2015+ std::shared_ptr<const UI::RenderedText> rendered_name;
2016+ std::shared_ptr<const UI::RenderedText> rendered_hotkey;
2017 };
2018- using EntryRecordDeque = std::deque<EntryRecord*>;
2019
2020 int max_pic_width_;
2021- EntryRecordDeque entry_records_;
2022+ int widest_text_;
2023+ int widest_hotkey_;
2024+
2025+ std::deque<EntryRecord*> entry_records_;
2026 Scrollbar scrollbar_;
2027 uint32_t scrollpos_; // in pixels
2028 uint32_t selection_;
2029@@ -148,7 +153,7 @@
2030 uint32_t last_selection_; // for double clicks
2031 ListselectLayout selection_mode_;
2032 const Image* check_pic_;
2033- const FontStyleInfo* font_style_;
2034+ const UI::TableStyleInfo& table_style_;
2035 const UI::PanelStyleInfo* background_style_; // Background color and texture. Not owned.
2036 int lineheight_;
2037 std::string current_tooltip_;
2038@@ -169,17 +174,9 @@
2039 Entry value,
2040 const Image* pic = nullptr,
2041 const bool select_this = false,
2042- const std::string& tooltip_text = std::string()) {
2043+ const std::string& tooltip_text = std::string(), const std::string& hotkey = std::string()) {
2044 entry_cache_.push_back(value);
2045- BaseListselect::add(name, entry_cache_.size() - 1, pic, select_this, tooltip_text);
2046- }
2047- void add_front(const std::string& name,
2048- Entry value,
2049- const Image* pic = nullptr,
2050- const bool select_this = false,
2051- const std::string& tooltip_text = std::string()) {
2052- entry_cache_.push_front(value);
2053- BaseListselect::add_front(name, pic, select_this, tooltip_text);
2054+ BaseListselect::add(name, entry_cache_.size() - 1, pic, select_this, tooltip_text, hotkey);
2055 }
2056
2057 const Entry& operator[](uint32_t const i) const {
2058@@ -218,15 +215,8 @@
2059 Entry& value,
2060 const Image* pic = nullptr,
2061 const bool select_this = false,
2062- const std::string& tooltip_text = std::string()) {
2063- Base::add(name, &value, pic, select_this, tooltip_text);
2064- }
2065- void add_front(const std::string& name,
2066- Entry& value,
2067- const Image* pic = nullptr,
2068- const bool select_this = false,
2069- const std::string& tooltip_text = std::string()) {
2070- Base::add_front(name, &value, pic, select_this, tooltip_text);
2071+ const std::string& tooltip_text = std::string(), const std::string& hotkey = std::string()) {
2072+ Base::add(name, &value, pic, select_this, tooltip_text, hotkey);
2073 }
2074
2075 Entry& operator[](uint32_t const i) const {
2076
2077=== modified file 'src/ui_basic/panel.cc'
2078--- src/ui_basic/panel.cc 2019-05-03 19:24:19 +0000
2079+++ src/ui_basic/panel.cc 2019-06-24 16:19:42 +0000
2080@@ -123,8 +123,12 @@
2081 // Scan-build claims this results in double free.
2082 // This is a false positive.
2083 // See https://bugs.launchpad.net/widelands/+bug/1198928
2084- while (first_child_)
2085+ while (first_child_) {
2086+ Panel* next_child = first_child_->next_;
2087 delete first_child_;
2088+ first_child_ = next_child;
2089+ }
2090+ first_child_ = nullptr;
2091 }
2092
2093 /**
2094@@ -530,6 +534,7 @@
2095 bool Panel::handle_mousepress(const uint8_t btn, int32_t, int32_t) {
2096 if (btn == SDL_BUTTON_LEFT && get_can_focus()) {
2097 focus();
2098+ clicked();
2099 }
2100 return false;
2101 }
2102@@ -735,10 +740,12 @@
2103 Panel* p = next;
2104 next = p->next_;
2105
2106- if (p->flags_ & pf_die)
2107+ if (p->flags_ & pf_die) {
2108 delete p;
2109- else if (p->flags_ & pf_child_die)
2110+ p = nullptr;
2111+ } else if (p->flags_ & pf_child_die) {
2112 p->check_child_death();
2113+ }
2114 }
2115
2116 flags_ &= ~pf_child_die;
2117
2118=== modified file 'src/ui_basic/panel.h'
2119--- src/ui_basic/panel.h 2019-05-12 07:45:59 +0000
2120+++ src/ui_basic/panel.h 2019-06-24 16:19:42 +0000
2121@@ -89,6 +89,7 @@
2122 const std::string& tooltip_text = std::string());
2123 virtual ~Panel();
2124
2125+ boost::signals2::signal<void()> clicked;
2126 boost::signals2::signal<void()> position_changed;
2127
2128 Panel* get_parent() const {
2129@@ -119,8 +120,8 @@
2130 virtual void end();
2131
2132 // Geometry
2133- void set_size(int nw, int nh);
2134- void set_desired_size(int w, int h);
2135+ virtual void set_size(int nw, int nh);
2136+ virtual void set_desired_size(int w, int h);
2137 virtual void set_pos(Vector2i);
2138 virtual void move_inside_parent();
2139 virtual void layout();
2140
2141=== modified file 'src/ui_basic/radiobutton.cc'
2142--- src/ui_basic/radiobutton.cc 2019-02-23 11:00:49 +0000
2143+++ src/ui_basic/radiobutton.cc 2019-06-24 16:19:42 +0000
2144@@ -48,7 +48,7 @@
2145 * Inform the radiogroup about the click; the group is responsible of setting
2146 * button states.
2147 */
2148-void Radiobutton::clicked() {
2149+void Radiobutton::button_clicked() {
2150 group_.set_state(id_);
2151 play_click();
2152 }
2153
2154=== modified file 'src/ui_basic/radiobutton.h'
2155--- src/ui_basic/radiobutton.h 2019-02-23 11:00:49 +0000
2156+++ src/ui_basic/radiobutton.h 2019-06-24 16:19:42 +0000
2157@@ -42,7 +42,7 @@
2158 }
2159
2160 private:
2161- void clicked() override;
2162+ void button_clicked() override;
2163
2164 Radiobutton* nextbtn_;
2165 Radiogroup& group_;
2166
2167=== modified file 'src/ui_basic/slider.h'
2168--- src/ui_basic/slider.h 2019-05-03 19:24:19 +0000
2169+++ src/ui_basic/slider.h 2019-06-24 16:19:42 +0000
2170@@ -106,7 +106,6 @@
2171 void set_highlighted(bool highlighted);
2172
2173 public:
2174- boost::signals2::signal<void()> clicked;
2175 boost::signals2::signal<void()> changed;
2176 boost::signals2::signal<void(int32_t)> changedto;
2177
2178
2179=== modified file 'src/ui_basic/unique_window.cc'
2180--- src/ui_basic/unique_window.cc 2019-02-23 11:00:49 +0000
2181+++ src/ui_basic/unique_window.cc 2019-06-24 16:19:42 +0000
2182@@ -58,7 +58,13 @@
2183 */
2184 void UniqueWindow::Registry::toggle() {
2185 if (window) {
2186- window->die();
2187+ // There is already a window. If it is minimal, restore it.
2188+ if (window->is_minimal()) {
2189+ window->restore();
2190+ opened();
2191+ } else {
2192+ window->die();
2193+ }
2194 } else {
2195 open_window();
2196 }
2197
2198=== modified file 'src/ui_fsmenu/launch_game.cc'
2199--- src/ui_fsmenu/launch_game.cc 2019-05-26 17:21:15 +0000
2200+++ src/ui_fsmenu/launch_game.cc 2019-06-24 16:19:42 +0000
2201@@ -46,15 +46,15 @@
2202 buth_(get_h() * 9 / 200),
2203
2204 win_condition_dropdown_(this,
2205+ "dropdown_wincondition",
2206 get_w() * 7 / 10,
2207 get_h() * 4 / 10 + buth_,
2208 butw_,
2209- get_h() - get_h() * 4 / 10 - buth_,
2210+ 10, // max number of items
2211 buth_,
2212 "",
2213 UI::DropdownType::kTextual,
2214- UI::PanelStyle::kFsMenu),
2215-
2216+ UI::PanelStyle::kFsMenu, UI::ButtonStyle::kFsMenuMenu),
2217 peaceful_(this, Vector2i(get_w() * 7 / 10, get_h() * 19 / 40 + buth_), _("Peaceful mode")),
2218 ok_(this, "ok", 0, 0, butw_, buth_, UI::ButtonStyle::kFsMenuPrimary, _("Start game")),
2219 back_(this, "back", 0, 0, butw_, buth_, UI::ButtonStyle::kFsMenuSecondary, _("Back")),
2220
2221=== modified file 'src/ui_fsmenu/launch_spg.cc'
2222--- src/ui_fsmenu/launch_spg.cc 2019-05-26 17:21:15 +0000
2223+++ src/ui_fsmenu/launch_spg.cc 2019-06-24 16:19:42 +0000
2224@@ -114,6 +114,8 @@
2225 ok_.set_pos(Vector2i(get_w() * 7 / 10, get_h() * 9 / 10));
2226 back_.set_pos(Vector2i(get_w() * 7 / 10, get_h() * 17 / 20));
2227 win_condition_dropdown_.set_pos(Vector2i(get_w() * 7 / 10, get_h() * 4 / 10 + buth_));
2228+ win_condition_dropdown_.set_size(select_map_.get_w(), win_condition_dropdown_.get_h());
2229+
2230 title_.set_text(_("Launch Game"));
2231 select_map_.sigclicked.connect(
2232 boost::bind(&FullscreenMenuLaunchSPG::select_map, boost::ref(*this)));
2233
2234=== modified file 'src/ui_fsmenu/options.cc'
2235--- src/ui_fsmenu/options.cc 2019-05-26 17:21:15 +0000
2236+++ src/ui_fsmenu/options.cc 2019-06-24 16:19:42 +0000
2237@@ -105,23 +105,25 @@
2238
2239 // Interface options
2240 language_dropdown_(&box_interface_left_,
2241- 0,
2242- 0,
2243- 100, // 100 is arbitrary, will be resized in layout().
2244- 100, // 100 is arbitrary, will be resized in layout().
2245+ "dropdown_language",
2246+ 0,
2247+ 0,
2248+ 100, // 100 is arbitrary, will be resized in layout().
2249+ 50,
2250 24,
2251 _("Language"),
2252 UI::DropdownType::kTextual,
2253- UI::PanelStyle::kFsMenu),
2254+ UI::PanelStyle::kFsMenu, UI::ButtonStyle::kFsMenuMenu),
2255 resolution_dropdown_(&box_interface_left_,
2256- 0,
2257- 0,
2258- 100, // 100 is arbitrary, will be resized in layout().
2259- 100, // 100 is arbitrary, will be resized in layout().
2260+ "dropdown_resolution",
2261+ 0,
2262+ 0,
2263+ 100, // 100 is arbitrary, will be resized in layout().
2264+ 50,
2265 24,
2266 _("Window Size"),
2267 UI::DropdownType::kTextual,
2268- UI::PanelStyle::kFsMenu),
2269+ UI::PanelStyle::kFsMenu, UI::ButtonStyle::kFsMenuMenu),
2270
2271 fullscreen_(&box_interface_left_, Vector2i::zero(), _("Fullscreen"), "", 0),
2272 inputgrab_(&box_interface_left_, Vector2i::zero(), _("Grab Input"), "", 0),
2273
2274=== modified file 'src/wui/economy_options_window.cc'
2275--- src/wui/economy_options_window.cc 2019-06-21 15:31:15 +0000
2276+++ src/wui/economy_options_window.cc 2019-06-24 16:19:42 +0000
2277@@ -52,7 +52,7 @@
2278 &tabpanel_, this, serial_, player_, can_act, Widelands::wwWORKER, kDesiredWidth)),
2279 dropdown_box_(this, 0, 0, UI::Box::Horizontal),
2280 dropdown_(
2281- &dropdown_box_, 0, 0, 174, 200, 34, "", UI::DropdownType::kTextual, UI::PanelStyle::kWui),
2282+ &dropdown_box_, "economy_profiles", 0, 0, 174, 10, 34, "", UI::DropdownType::kTextual, UI::PanelStyle::kWui, UI::ButtonStyle::kWuiSecondary), // NOCOM test if this is the correct button style. heap-use-after-free somewhere too.
2283 time_last_thought_(0),
2284 save_profile_dialog_(nullptr) {
2285 set_center_panel(&main_box_);
2286
2287=== modified file 'src/wui/fieldaction.cc'
2288--- src/wui/fieldaction.cc 2019-06-23 12:45:29 +0000
2289+++ src/wui/fieldaction.cc 2019-06-24 16:19:42 +0000
2290@@ -76,7 +76,7 @@
2291
2292 BuildGrid::BuildGrid(UI::Panel* parent, Widelands::Player* plr, int32_t x, int32_t y, int32_t cols)
2293 : UI::IconGrid(parent, x, y, kBuildGridCellSize, kBuildGridCellSize, cols), plr_(plr) {
2294- clicked.connect(boost::bind(&BuildGrid::click_slot, this, _1));
2295+ icon_clicked.connect(boost::bind(&BuildGrid::click_slot, this, _1));
2296 mouseout.connect(boost::bind(&BuildGrid::mouseout_slot, this, _1));
2297 mousein.connect(boost::bind(&BuildGrid::mousein_slot, this, _1));
2298 }
2299
2300=== modified file 'src/wui/game_client_disconnected.cc'
2301--- src/wui/game_client_disconnected.cc 2019-02-23 11:00:49 +0000
2302+++ src/wui/game_client_disconnected.cc 2019-06-24 16:19:42 +0000
2303@@ -74,16 +74,17 @@
2304 /** TRANSLATORS: Button tooltip */
2305 _("Replace the disconnected player with the selected AI and continue playing")),
2306 type_dropdown_(&box_h_,
2307+ "dropdown_ai",
2308 width - 50, // x
2309 0, // y
2310 60, // width of selection box
2311- 800, // height of selection box, shrinks automatically
2312+ 16, // maximum number of items in the selection box, shrinks automatically
2313 35, // width/height of button
2314 /** TRANSLATORS: Dropdown tooltip to select the AI difficulty when a player has
2315 disconnected from a game */
2316 _("AI for the disconnected player"),
2317 UI::DropdownType::kPictorial,
2318- UI::PanelStyle::kWui),
2319+ UI::PanelStyle::kWui, UI::ButtonStyle::kWuiMenu),
2320 exit_game_(&box_,
2321 "exit_game",
2322 0,
2323
2324=== modified file 'src/wui/game_message_menu.cc'
2325--- src/wui/game_message_menu.cc 2019-05-26 17:21:15 +0000
2326+++ src/wui/game_message_menu.cc 2019-06-24 16:19:42 +0000
2327@@ -130,11 +130,9 @@
2328 new UI::Button(this, "center_main_mapview_on_location", kWindowWidth - kPadding - kButtonSize,
2329 archivebtn_->get_y(), kButtonSize, kButtonSize, UI::ButtonStyle::kWuiPrimary,
2330 g_gr->images().get("images/wui/menus/menu_goto.png"),
2331- /** TRANSLATORS: %s is a tooltip, G is the corresponding hotkey */
2332- (boost::format(_("G: %s"))
2333+ as_tooltip_text_with_hotkey(
2334 /** TRANSLATORS: Tooltip in the messages window */
2335- % _("Center main mapview on location"))
2336- .str());
2337+ _("Center main mapview on location"), "g"));
2338 centerviewbtn_->sigclicked.connect(boost::bind(&GameMessageMenu::center_view, this));
2339 centerviewbtn_->set_enabled(false);
2340
2341@@ -524,10 +522,9 @@
2342 message_filter_ = msgtype;
2343
2344 /** TRANSLATORS: %1% is a tooltip, %2% is the corresponding hotkey */
2345- button.set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2346+ button.set_tooltip(as_tooltip_text_with_hotkey(
2347 /** TRANSLATORS: Tooltip in the messages window */
2348- % _("Show all messages") % pgettext("hotkey", "Alt + 0"))
2349- .str());
2350+ _("Show all messages"), pgettext("hotkey", "Alt+0")));
2351 }
2352 }
2353
2354@@ -535,27 +532,22 @@
2355 * Helper for filter_messages
2356 */
2357 void GameMessageMenu::set_filter_messages_tooltips() {
2358- geologistsbtn_->set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2359+ geologistsbtn_->set_tooltip(as_tooltip_text_with_hotkey(
2360 /** TRANSLATORS: Tooltip in the messages window */
2361- % _("Show geologists' messages only") %
2362- pgettext("hotkey", "Alt + 1"))
2363- .str());
2364- economybtn_->set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2365+ _("Show geologists' messages only"),
2366+ pgettext("hotkey", "Alt+1")));
2367+ economybtn_->set_tooltip(as_tooltip_text_with_hotkey(
2368 /** TRANSLATORS: Tooltip in the messages window */
2369- % _("Show economy messages only") % pgettext("hotkey", "Alt + 2"))
2370- .str());
2371- seafaringbtn_->set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2372+ _("Show economy messages only"), pgettext("hotkey", "Alt+2")));
2373+ seafaringbtn_->set_tooltip(as_tooltip_text_with_hotkey(
2374 /** TRANSLATORS: Tooltip in the messages window */
2375- % _("Show seafaring messages only") % pgettext("hotkey", "Alt + 3"))
2376- .str());
2377- warfarebtn_->set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2378+ _("Show seafaring messages only"), pgettext("hotkey", "Alt+3")));
2379+ warfarebtn_->set_tooltip(as_tooltip_text_with_hotkey(
2380 /** TRANSLATORS: Tooltip in the messages window */
2381- % _("Show warfare messages only") % pgettext("hotkey", "Alt + 4"))
2382- .str());
2383- scenariobtn_->set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2384+ _("Show warfare messages only"), pgettext("hotkey", "Alt+4")));
2385+ scenariobtn_->set_tooltip(as_tooltip_text_with_hotkey(
2386 /** TRANSLATORS: Tooltip in the messages window */
2387- % _("Show scenario messages only") % pgettext("hotkey", "Alt + 5"))
2388- .str());
2389+ _("Show scenario messages only"), pgettext("hotkey", "Alt+5")));
2390 }
2391
2392 /**
2393@@ -651,6 +643,6 @@
2394 }
2395 break;
2396 }
2397- /** TRANSLATORS: %s is a tooltip, Del is the corresponding hotkey */
2398- archivebtn_->set_tooltip((boost::format(_("Del: %s")) % button_tooltip).str());
2399+ /** TRANSLATORS: Del is the "Delete" key on the keyboard */
2400+ archivebtn_->set_tooltip(as_tooltip_text_with_hotkey(button_tooltip, pgettext("hotkey", "Del")));
2401 }
2402
2403=== modified file 'src/wui/multiplayersetupgroup.cc'
2404--- src/wui/multiplayersetupgroup.cc 2019-05-12 07:45:59 +0000
2405+++ src/wui/multiplayersetupgroup.cc 2019-06-24 16:19:42 +0000
2406@@ -55,7 +55,8 @@
2407 GameSettingsProvider* const settings)
2408 : UI::Box(parent, 0, 0, UI::Box::Horizontal, w, h, kPadding),
2409 slot_dropdown_(
2410- this, 0, 0, h, 200, h, _("Role"), UI::DropdownType::kPictorial, UI::PanelStyle::kFsMenu),
2411+ this, (boost::format("dropdown_slot%d") % static_cast<unsigned int>(id)).str(),
2412+ 0, 0, h, 16, h, _("Role"), UI::DropdownType::kPictorial, UI::PanelStyle::kFsMenu, UI::ButtonStyle::kFsMenuSecondary),
2413 // Name needs to be initialized after the dropdown, otherwise the layout function will
2414 // crash.
2415 name(this, 0, 0, w - h - UI::Scrollbar::kSize * 11 / 5, h),
2416@@ -187,34 +188,37 @@
2417 (boost::format(_("Player %u")) % static_cast<unsigned int>(id_ + 1)).str(),
2418 UI::Button::VisualState::kFlat),
2419 type_dropdown_(this,
2420+ (boost::format("dropdown_type%d") % static_cast<unsigned int>(id)).str(),
2421 0,
2422 0,
2423 50,
2424- 200,
2425+ 16,
2426 h,
2427 _("Type"),
2428 UI::DropdownType::kPictorial,
2429- UI::PanelStyle::kFsMenu),
2430+ UI::PanelStyle::kFsMenu, UI::ButtonStyle::kFsMenuSecondary),
2431 tribes_dropdown_(this,
2432+ (boost::format("dropdown_tribes%d") % static_cast<unsigned int>(id)).str(),
2433 0,
2434 0,
2435 50,
2436- 200,
2437+ 16,
2438 h,
2439 _("Tribe"),
2440 UI::DropdownType::kPictorial,
2441- UI::PanelStyle::kFsMenu),
2442+ UI::PanelStyle::kFsMenu, UI::ButtonStyle::kFsMenuSecondary),
2443 init_dropdown_(this,
2444+ (boost::format("dropdown_init%d") % static_cast<unsigned int>(id)).str(),
2445 0,
2446 0,
2447 w - 4 * h - 3 * kPadding,
2448- 200,
2449+ 16,
2450 h,
2451 "",
2452 UI::DropdownType::kTextualNarrow,
2453- UI::PanelStyle::kFsMenu),
2454+ UI::PanelStyle::kFsMenu, UI::ButtonStyle::kFsMenuSecondary),
2455 team_dropdown_(
2456- this, 0, 0, h, 200, h, _("Team"), UI::DropdownType::kPictorial, UI::PanelStyle::kFsMenu),
2457+ this, (boost::format("dropdown_team%d") % static_cast<unsigned int>(id)).str(), 0, 0, h, 16, h, _("Team"), UI::DropdownType::kPictorial, UI::PanelStyle::kFsMenu, UI::ButtonStyle::kFsMenuSecondary),
2458 last_state_(PlayerSettings::State::kClosed),
2459 type_selection_locked_(false),
2460 tribe_selection_locked_(false),
2461
2462=== modified file 'src/wui/seafaring_statistics_menu.cc'
2463--- src/wui/seafaring_statistics_menu.cc 2019-02-23 11:00:49 +0000
2464+++ src/wui/seafaring_statistics_menu.cc 2019-06-24 16:19:42 +0000
2465@@ -26,6 +26,7 @@
2466
2467 #include "economy/fleet.h"
2468 #include "graphic/graphic.h"
2469+#include "graphic/text_layout.h"
2470 #include "logic/game.h"
2471 #include "logic/player.h"
2472 #include "logic/playercommand.h"
2473@@ -98,10 +99,8 @@
2474 kButtonSize,
2475 UI::ButtonStyle::kWuiPrimary,
2476 g_gr->images().get("images/wui/menus/menu_watch_follow.png"),
2477- (boost::format(_("%1% (Hotkey: %2%)")) %
2478 /** TRANSLATORS: Tooltip in the seafaring statistics window */
2479- _("Watch the selected ship") % pgettext("hotkey", "W"))
2480- .str()),
2481+ as_tooltip_text_with_hotkey(_("Watch the selected ship"), "w")),
2482 openwindowbtn_(
2483 &navigation_box_,
2484 "seafaring_stats_watch_button",
2485@@ -112,13 +111,12 @@
2486 UI::ButtonStyle::kWuiPrimary,
2487 g_gr->images().get("images/ui_basic/fsel.png"),
2488 (boost::format("%s<br>%s") %
2489- (boost::format(pgettext("hotkey_description", "%1%: %2%")) % pgettext("hotkey", "O") %
2490- /** TRANSLATORS: Tooltip in the seafaring statistics window */
2491- _("Open the selected ship’s window")) %
2492- (boost::format(pgettext("hotkey_description", "%1%: %2%")) %
2493- pgettext("hotkey", "CTRL + O") %
2494- /** TRANSLATORS: Tooltip in the seafaring statistics window */
2495- _("Go to the selected ship and open its window")))
2496+ as_tooltip_text_with_hotkey(
2497+ /** TRANSLATORS: Tooltip in the seafaring statistics window */
2498+ _("Open the selected ship’s window"), "o") %
2499+ as_tooltip_text_with_hotkey(
2500+ /** TRANSLATORS: Tooltip in the seafaring statistics window */
2501+ _("Go to the selected ship and open its window"), pgettext("hotkey", "CTRL+o")))
2502 .str()),
2503 centerviewbtn_(&navigation_box_,
2504 "seafaring_stats_center_main_mapview_button",
2505@@ -128,10 +126,9 @@
2506 kButtonSize,
2507 UI::ButtonStyle::kWuiPrimary,
2508 g_gr->images().get("images/wui/ship/menu_ship_goto.png"),
2509- (boost::format(_("%1% (Hotkey: %2%)")) %
2510+ as_tooltip_text_with_hotkey(
2511 /** TRANSLATORS: Tooltip in the seafaring statistics window */
2512- _("Center the map on the selected ship") % pgettext("hotkey", "G"))
2513- .str()),
2514+ _("Center the map on the selected ship"), "g")),
2515 table_(&main_box_, 0, 0, get_inner_w() - 2 * kPadding, 100, UI::PanelStyle::kWui) {
2516
2517 const Widelands::TribeDescr& tribe = iplayer().player().tribe();
2518@@ -495,39 +492,32 @@
2519 button.set_perm_pressed(true);
2520 ship_filter_ = status;
2521
2522- /** TRANSLATORS: %1% is a tooltip, %2% is the corresponding hotkey */
2523- button.set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2524- /** TRANSLATORS: Tooltip in the messages window */
2525- % _("Show all ships") % pgettext("hotkey", "Alt + 0"))
2526- .str());
2527+ button.set_tooltip(as_tooltip_text_with_hotkey(
2528+ /** TRANSLATORS: Tooltip in the ship statistics window */
2529+ _("Show all ships"), pgettext("hotkey", "Alt+0")));
2530 }
2531 }
2532
2533 void SeafaringStatisticsMenu::set_filter_ships_tooltips() {
2534
2535- idle_btn_.set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2536- /** TRANSLATORS: Tooltip in the messages window */
2537- % _("Show idle ships") % pgettext("hotkey", "Alt + 1"))
2538- .str());
2539- shipping_btn_.set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2540- /** TRANSLATORS: Tooltip in the messages window */
2541- % _("Show ships shipping wares and workers") %
2542- pgettext("hotkey", "Alt + 2"))
2543- .str());
2544- waiting_btn_.set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2545- /** TRANSLATORS: Tooltip in the messages window */
2546- % _("Show waiting expeditions") % pgettext("hotkey", "Alt + 3"))
2547- .str());
2548- scouting_btn_.set_tooltip((boost::format(_("%1% (Hotkey: %2%)"))
2549- /** TRANSLATORS: Tooltip in the messages window */
2550- % _("Show scouting expeditions") % pgettext("hotkey", "Alt + 4"))
2551- .str());
2552+ idle_btn_.set_tooltip(as_tooltip_text_with_hotkey(
2553+ /** TRANSLATORS: Tooltip in the ship statistics window */
2554+ _("Show idle ships"), pgettext("hotkey", "Alt+1")));
2555+ shipping_btn_.set_tooltip(as_tooltip_text_with_hotkey(
2556+ /** TRANSLATORS: Tooltip in the ship statistics window */
2557+ _("Show ships shipping wares and workers"),
2558+ pgettext("hotkey", "Alt+2")));
2559+ waiting_btn_.set_tooltip(as_tooltip_text_with_hotkey(
2560+ /** TRANSLATORS: Tooltip in the ship statistics window */
2561+ _("Show waiting expeditions"), pgettext("hotkey", "Alt+3")));
2562+ scouting_btn_.set_tooltip(as_tooltip_text_with_hotkey(
2563+ /** TRANSLATORS: Tooltip in the ship statistics window */
2564+ _("Show scouting expeditions"), pgettext("hotkey", "Alt+4")));
2565 portspace_btn_.set_tooltip(
2566- (boost::format(_("%1% (Hotkey: %2%)"))
2567- /** TRANSLATORS: Tooltip in the messages window */
2568- % _("Show expeditions that have found a port space or are founding a colony") %
2569- pgettext("hotkey", "Alt + 5"))
2570- .str());
2571+ as_tooltip_text_with_hotkey(
2572+ /** TRANSLATORS: Tooltip in the ship statistics window */
2573+ _("Show expeditions that have found a port space or are founding a colony"),
2574+ pgettext("hotkey", "Alt+5")));
2575 }
2576
2577 bool SeafaringStatisticsMenu::satisfies_filter(const ShipInfo& info, ShipFilterStatus filter) {

Subscribers

People subscribed via source and target branches

to status/vote changes: