Merge lp:~widelands-dev/widelands/bug-1358880-ship-statistics into lp:widelands
- bug-1358880-ship-statistics
- Merge into trunk
Status: | Merged | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 8669 | ||||||||||||
Proposed branch: | lp:~widelands-dev/widelands/bug-1358880-ship-statistics | ||||||||||||
Merge into: | lp:widelands | ||||||||||||
Prerequisite: | lp:~widelands-dev/widelands/ships_optr | ||||||||||||
Diff against target: |
2040 lines (+1087/-215) 23 files modified
data/tribes/scripting/help/controls.lua (+39/-12) src/ai/defaultai.cc (+10/-6) src/ai/defaultai.h (+1/-1) src/game_io/game_player_info_packet.cc (+2/-2) src/logic/map_objects/tribes/ship.cc (+39/-32) src/logic/map_objects/tribes/ship.h (+9/-20) src/logic/player.cc (+27/-3) src/logic/player.h (+8/-1) src/notifications/note_ids.h (+1/-2) src/scripting/lua_game.cc (+7/-21) src/ui_basic/table.cc (+12/-0) src/ui_basic/table.h (+25/-3) src/wui/CMakeLists.txt (+2/-0) src/wui/game_statistics_menu.cc (+12/-1) src/wui/interactive_gamebase.cc (+21/-18) src/wui/interactive_gamebase.h (+2/-0) src/wui/interactive_player.cc (+11/-0) src/wui/seafaring_statistics_menu.cc (+580/-0) src/wui/seafaring_statistics_menu.h (+159/-0) src/wui/shipwindow.cc (+42/-33) src/wui/shipwindow.h (+2/-1) src/wui/watchwindow.cc (+20/-57) src/wui/watchwindow.h (+56/-2) |
||||||||||||
To merge this branch: | bzr merge lp:~widelands-dev/widelands/bug-1358880-ship-statistics | ||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Notabilis | Approve | ||
GunChleoc | Needs Resubmitting | ||
Review via email: mp+343293@code.launchpad.net |
Commit message
Adds a new Ship Statistics window.
Description of the change
Finally, ths ship list.
lp:~widelands-dev/widelands/ships_optr needs to go in first.
bunnybot (widelandsofficial) wrote : | # |
Notabilis (notabilis27) wrote : | # |
Great work, I really like the new list!
Most of the code looks good, three small bugs and some other comments, though.
The bugs:
- Hotkey 'e' even works on non-seafaring maps
- Missing "ship name" column in the list when there currently are no ships
- Memory leak with create_shipinfo
Two non code related comments (feel free to ignore) :
- I would like a button for opening the ship window without actually moving there. This could, e.g., be useful to start constructing a port while busy elsewhere. Currently both buttons move the view to the ship, maybe have one button for the window and one button to move to the ship?
- I am not really happy with calling the window "ship statistics" (in code and UI). For me, that sounds like a graph showing the number of ships (or so), and not like a list of all ships.
For the other comments, see the diff.
GunChleoc (gunchleoc) wrote : | # |
Thanks for the review! Everything that I haven't added a comment to, I will implement exactly as you suggested.
As to the name - I guess that's for consistency, just like we have the "Building Statistics", which is also a mix of info and unit access. I am open to suggestions though :)
Notabilis (notabilis27) wrote : | # |
I guess I would have called it "Ship List" or something like that. But consistency is a good argument, lets leave it as it is.
GunChleoc (gunchleoc) wrote : | # |
I have now fixed everything except maybe for this one:
- Memory leak with create_shipinfo
I thought I had caught that one already, but I have worked on the function just in case - it now returns a unique_ptr. That function had also produced a compiler warning which I fixed (yay for upgrading my Ubuntu). Do you have steps to reproduce the memory leak?
Notabilis (notabilis27) wrote : | # |
Thanks for the fixes/changes! Testing and reviewing the commits went fine.
One really minor nit: Some indentations are now not correctly aligned (some assert() somewhere). ;)
Unfortunately I don't know any steps to reproduce the memory leak. It didn't seem to turn up every time when I tested it. But the changes look good and I played around with the window and wasn't able to trigger any further ASAN complains, so I guess its fine now.
GunChleoc (gunchleoc) wrote : | # |
Thanks for the review and testing again!
I am still tweaking my IDE's layout function as best as I can. bunnybot will take care of the misaligned assert :)
@bunnybot merge
bunnybot (widelandsofficial) wrote : | # |
Refusing to merge, since Travis is not green. Use @bunnybot merge force for merging anyways.
Travis build 3386. State: failed. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
Transient failure on Travis
@bunnybot merge force
kaputtnik (franku) wrote : | # |
Finally we got this!!!
Thanks GunChleoc :-)
Preview Diff
1 | === added file 'data/images/wui/editor/fsel_editor_set_port_space.png' |
2 | Binary files data/images/wui/editor/fsel_editor_set_port_space.png 1970-01-01 00:00:00 +0000 and data/images/wui/editor/fsel_editor_set_port_space.png 2018-04-28 09:26:35 +0000 differ |
3 | === removed file 'data/images/wui/editor/fsel_editor_set_port_space.png' |
4 | Binary files data/images/wui/editor/fsel_editor_set_port_space.png 2018-01-05 12:07:27 +0000 and data/images/wui/editor/fsel_editor_set_port_space.png 1970-01-01 00:00:00 +0000 differ |
5 | === added file 'data/images/wui/editor/fsel_editor_unset_port_space.png' |
6 | Binary files data/images/wui/editor/fsel_editor_unset_port_space.png 1970-01-01 00:00:00 +0000 and data/images/wui/editor/fsel_editor_unset_port_space.png 2018-04-28 09:26:35 +0000 differ |
7 | === removed file 'data/images/wui/editor/fsel_editor_unset_port_space.png' |
8 | Binary files data/images/wui/editor/fsel_editor_unset_port_space.png 2018-01-05 12:07:27 +0000 and data/images/wui/editor/fsel_editor_unset_port_space.png 1970-01-01 00:00:00 +0000 differ |
9 | === added file 'data/images/wui/ship/ship_construct_port_space.png' |
10 | Binary files data/images/wui/ship/ship_construct_port_space.png 1970-01-01 00:00:00 +0000 and data/images/wui/ship/ship_construct_port_space.png 2018-04-28 09:26:35 +0000 differ |
11 | === added file 'data/images/wui/stats/ship_stats_idle.png' |
12 | Binary files data/images/wui/stats/ship_stats_idle.png 1970-01-01 00:00:00 +0000 and data/images/wui/stats/ship_stats_idle.png 2018-04-28 09:26:35 +0000 differ |
13 | === added file 'data/images/wui/stats/ship_stats_shipping.png' |
14 | Binary files data/images/wui/stats/ship_stats_shipping.png 1970-01-01 00:00:00 +0000 and data/images/wui/stats/ship_stats_shipping.png 2018-04-28 09:26:35 +0000 differ |
15 | === modified file 'data/tribes/scripting/help/controls.lua' |
16 | --- data/tribes/scripting/help/controls.lua 2017-12-06 08:16:46 +0000 |
17 | +++ data/tribes/scripting/help/controls.lua 2018-04-28 09:26:35 +0000 |
18 | @@ -21,6 +21,16 @@ |
19 | -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
20 | dl(help_format_hotkey(pgettext("hotkey", "Ctrl + Left-click on Button")), _"Skip confirmation dialog")) .. |
21 | |
22 | + h2(_"Table Control") .. |
23 | + h3(_"In tables that allow the selection of multiple entries, the following key combinations are available:") .. |
24 | + p( |
25 | + -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
26 | + dl(help_format_hotkey(pgettext("hotkey", "Ctrl + Click")), pgettext("table_control", "Select multiple entries")) .. |
27 | + -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
28 | + dl(help_format_hotkey(pgettext("hotkey", "Shift + Click")), pgettext("table_control", "Select a range of entries")) .. |
29 | + -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
30 | + dl(help_format_hotkey(pgettext("hotkey", "Ctrl + A")), pgettext("table_control", "Select all entries"))) .. |
31 | + |
32 | h2(_"Road Control") .. |
33 | p( |
34 | -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
35 | @@ -52,6 +62,8 @@ |
36 | dl(help_format_hotkey("I"), _"Toggle stock inventory") .. |
37 | -- TRANSLATORS: This is an access key combination. The hotkey is 'b' |
38 | dl(help_format_hotkey("B"), _"Toggle building statistics") .. |
39 | + -- TRANSLATORS: This is an access key combination. The hotkey is 'e' |
40 | + dl(help_format_hotkey("E"), _"Toggle seafaring statistics") .. |
41 | -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
42 | dl(help_format_hotkey(pgettext("hotkey", "Home")), _"Center main mapview on starting location") .. |
43 | -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
44 | @@ -72,18 +84,8 @@ |
45 | dl(help_format_hotkey(pgettext("hotkey", "F6")), _"Show the debug console (only in debug-builds)") |
46 | ) .. |
47 | |
48 | - h2(_"Table Control") .. |
49 | - h3(_"In tables that allow the selection of multiple entries, the following key combinations are available:") .. |
50 | - p( |
51 | - -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
52 | - dl(help_format_hotkey(pgettext("hotkey", "Ctrl + Click")), pgettext("table_control", "Select multiple entries")) .. |
53 | - -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
54 | - dl(help_format_hotkey(pgettext("hotkey", "Shift + Click")), pgettext("table_control", "Select a range of entries")) .. |
55 | - -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
56 | - dl(help_format_hotkey(pgettext("hotkey", "Ctrl + A")), pgettext("table_control", "Select all entries"))) .. |
57 | - |
58 | + -- TRANSLATORS: Heading in "Controls" help |
59 | h2(_"Message Window") .. |
60 | - h3(_"In the message window, the following additional shortcuts are available:") .. |
61 | p( |
62 | -- TRANSLATORS: This is the helptext for an access key combination. |
63 | dl(help_format_hotkey(pgettext("hotkey", "Alt + 0")), _"Show all messages") .. |
64 | @@ -101,5 +103,30 @@ |
65 | dl(help_format_hotkey("G"), _"Jump to the location corresponding to the current message") .. |
66 | -- TRANSLATORS: This is an access key combination. Localize, but do not change the key. |
67 | dl(help_format_hotkey(pgettext("hotkey", "Delete")), _"Archive/Restore the current message") |
68 | - ) |
69 | + ) .. |
70 | + |
71 | + -- TRANSLATORS: Heading in "Controls" help |
72 | + h2(_"Ship Statistics") .. |
73 | + p( |
74 | + -- TRANSLATORS: This is the helptext for an access key combination. |
75 | + dl(help_format_hotkey(pgettext("hotkey", "Alt + 0")), _"Show all ships") .. |
76 | + -- TRANSLATORS: This is the helptext for an access key combination. |
77 | + dl(help_format_hotkey(pgettext("hotkey", "Alt + 1")), _"Show idle ships") .. |
78 | + -- TRANSLATORS: This is the helptext for an access key combination. |
79 | + dl(help_format_hotkey(pgettext("hotkey", "Alt + 2")), _"Show ships shipping wares and workers") .. |
80 | + -- TRANSLATORS: This is the helptext for an access key combination. |
81 | + dl(help_format_hotkey(pgettext("hotkey", "Alt + 3")), _"Show waiting expeditions") .. |
82 | + -- TRANSLATORS: This is the helptext for an access key combination. |
83 | + dl(help_format_hotkey(pgettext("hotkey", "Alt + 4")), _"Show scouting expeditions") .. |
84 | + -- TRANSLATORS: This is the helptext for an access key combination. |
85 | + dl(help_format_hotkey(pgettext("hotkey", "Alt + 5")), _"Show expeditions that have found a port space or are founding a colony") .. |
86 | + -- TRANSLATORS: This is the helptext for an access key combination. |
87 | + dl(help_format_hotkey("G"), _"Center the map on the selected ship") .. |
88 | + -- TRANSLATORS: This is the helptext for an access key combination. |
89 | + dl(help_format_hotkey("O"), _"Open the selected ship’s window") .. |
90 | + -- TRANSLATORS: This is the helptext for an access key combination. |
91 | + dl(help_format_hotkey("CTRL + O"), _"Go to the selected ship and open its window") .. |
92 | + -- TRANSLATORS: This is the helptext for an access key combination. |
93 | + dl(help_format_hotkey("W"), _"Watch the selected ship") |
94 | + ) |
95 | } |
96 | |
97 | === modified file 'src/ai/defaultai.cc' |
98 | --- src/ai/defaultai.cc 2018-04-08 22:33:43 +0000 |
99 | +++ src/ai/defaultai.cc 2018-04-28 09:26:35 +0000 |
100 | @@ -167,8 +167,8 @@ |
101 | }); |
102 | |
103 | // Subscribe to ShipNotes. |
104 | - shipnotes_subscriber_ = Notifications::subscribe<NoteShipMessage>([this]( |
105 | - const NoteShipMessage& note) { |
106 | + shipnotes_subscriber_ = Notifications::subscribe<NoteShip>([this]( |
107 | + const NoteShip& note) { |
108 | |
109 | // in a short time between start and late_initialization the player |
110 | // can get notes that can not be processed. |
111 | @@ -180,13 +180,13 @@ |
112 | return; |
113 | } |
114 | |
115 | - switch (note.message) { |
116 | + switch (note.action) { |
117 | |
118 | - case NoteShipMessage::Message::kGained: |
119 | + case NoteShip::Action::kGained: |
120 | gain_ship(*note.ship, NewShip::kBuilt); |
121 | break; |
122 | |
123 | - case NoteShipMessage::Message::kLost: |
124 | + case NoteShip::Action::kLost: |
125 | for (std::deque<ShipObserver>::iterator i = allships.begin(); i != allships.end(); ++i) { |
126 | if (i->ship == note.ship) { |
127 | allships.erase(i); |
128 | @@ -195,15 +195,19 @@ |
129 | } |
130 | break; |
131 | |
132 | - case NoteShipMessage::Message::kWaitingForCommand: |
133 | + case NoteShip::Action::kWaitingForCommand: |
134 | for (std::deque<ShipObserver>::iterator i = allships.begin(); i != allships.end(); ++i) { |
135 | if (i->ship == note.ship) { |
136 | i->waiting_for_command_ = true; |
137 | break; |
138 | } |
139 | } |
140 | + default: |
141 | + // Do nothing |
142 | + break; |
143 | } |
144 | }); |
145 | + |
146 | } |
147 | |
148 | DefaultAI::~DefaultAI() { |
149 | |
150 | === modified file 'src/ai/defaultai.h' |
151 | --- src/ai/defaultai.h 2018-04-08 22:33:43 +0000 |
152 | +++ src/ai/defaultai.h 2018-04-28 09:26:35 +0000 |
153 | @@ -414,7 +414,7 @@ |
154 | outofresource_subscriber_; |
155 | std::unique_ptr<Notifications::Subscriber<Widelands::NoteTrainingSiteSoldierTrained>> |
156 | soldiertrained_subscriber_; |
157 | - std::unique_ptr<Notifications::Subscriber<Widelands::NoteShipMessage>> shipnotes_subscriber_; |
158 | + std::unique_ptr<Notifications::Subscriber<Widelands::NoteShip>> shipnotes_subscriber_; |
159 | }; |
160 | |
161 | #endif // end of include guard: WL_AI_DEFAULTAI_H |
162 | |
163 | === modified file 'src/game_io/game_player_info_packet.cc' |
164 | --- src/game_io/game_player_info_packet.cc 2018-04-07 16:59:00 +0000 |
165 | +++ src/game_io/game_player_info_packet.cc 2018-04-28 09:26:35 +0000 |
166 | @@ -30,7 +30,7 @@ |
167 | |
168 | namespace Widelands { |
169 | |
170 | -constexpr uint16_t kCurrentPacketVersion = 20; |
171 | +constexpr uint16_t kCurrentPacketVersion = 21; |
172 | |
173 | void GamePlayerInfoPacket::read(FileSystem& fs, Game& game, MapObjectLoader*) { |
174 | try { |
175 | @@ -60,7 +60,7 @@ |
176 | |
177 | player->set_ai(fr.c_string()); |
178 | player->read_statistics(fr); |
179 | - player->read_remaining_shipnames(fr); |
180 | + player->read_remaining_shipnames(fr, packet_version); |
181 | |
182 | player->casualties_ = fr.unsigned_32(); |
183 | player->kills_ = fr.unsigned_32(); |
184 | |
185 | === modified file 'src/logic/map_objects/tribes/ship.cc' |
186 | --- src/logic/map_objects/tribes/ship.cc 2018-04-07 16:59:00 +0000 |
187 | +++ src/logic/map_objects/tribes/ship.cc 2018-04-28 09:26:35 +0000 |
188 | @@ -133,7 +133,6 @@ |
189 | } |
190 | |
191 | Ship::~Ship() { |
192 | - Notifications::publish(NoteShipWindow(serial(), NoteShipWindow::Action::kClose)); |
193 | } |
194 | |
195 | PortDock* Ship::get_destination(EditorGameBase& egbase) const { |
196 | @@ -155,12 +154,13 @@ |
197 | bool Ship::init(EditorGameBase& egbase) { |
198 | Bob::init(egbase); |
199 | init_fleet(egbase); |
200 | - Notifications::publish(NoteShipMessage(this, NoteShipMessage::Message::kGained)); |
201 | assert(get_owner()); |
202 | + get_owner()->add_ship(serial()); |
203 | |
204 | // Assigning a ship name |
205 | shipname_ = get_owner()->pick_shipname(); |
206 | molog("New ship: %s\n", shipname_.c_str()); |
207 | + Notifications::publish(NoteShip(this, NoteShip::Action::kGained)); |
208 | return true; |
209 | } |
210 | |
211 | @@ -182,12 +182,17 @@ |
212 | fleet_->remove_ship(egbase, this); |
213 | } |
214 | |
215 | + Player* o = get_owner(); |
216 | + if (o != nullptr) { |
217 | + o->remove_ship(serial()); |
218 | + } |
219 | + |
220 | while (!items_.empty()) { |
221 | items_.back().remove(egbase); |
222 | items_.pop_back(); |
223 | } |
224 | |
225 | - Notifications::publish(NoteShipMessage(this, NoteShipMessage::Message::kLost)); |
226 | + Notifications::publish(NoteShip(this, NoteShip::Action::kLost)); |
227 | |
228 | Bob::cleanup(egbase); |
229 | } |
230 | @@ -279,7 +284,7 @@ |
231 | case ShipStates::kSinkAnimation: |
232 | // The sink animation has been played, so finally remove the ship from the map |
233 | pop_task(game); |
234 | - remove(game); |
235 | + schedule_destroy(game); |
236 | return; |
237 | } |
238 | // if the real update function failed (e.g. nothing to transport), the ship goes idle |
239 | @@ -304,6 +309,7 @@ |
240 | destination_ = nullptr; |
241 | dst->ship_arrived(game, *this); |
242 | start_task_idle(game, descr().main_animation(), 250); |
243 | + Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged)); |
244 | return true; |
245 | } |
246 | |
247 | @@ -411,17 +417,13 @@ |
248 | } |
249 | } while (mr.advance(*map)); |
250 | |
251 | + expedition_->seen_port_buildspaces = temp_port_buildspaces; |
252 | if (new_port_space) { |
253 | - ship_state_ = ShipStates::kExpeditionPortspaceFound; |
254 | + set_ship_state_and_notify(ShipStates::kExpeditionPortspaceFound, NoteShip::Action::kWaitingForCommand); |
255 | send_message(game, _("Port Space"), _("Port Space Found"), |
256 | _("An expedition ship found a new port build space."), |
257 | "images/wui/editor/fsel_editor_set_port_space.png"); |
258 | } |
259 | - expedition_->seen_port_buildspaces = temp_port_buildspaces; |
260 | - if (new_port_space) { |
261 | - Notifications::publish( |
262 | - NoteShipMessage(this, NoteShipMessage::Message::kWaitingForCommand)); |
263 | - } |
264 | } |
265 | } |
266 | |
267 | @@ -532,17 +534,13 @@ |
268 | } else { |
269 | // Check whether the island was completely surrounded |
270 | if (get_position() == expedition_->exploration_start) { |
271 | + set_ship_state_and_notify(ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand); |
272 | send_message(game, |
273 | /** TRANSLATORS: A ship has circumnavigated an island and is waiting |
274 | for orders */ |
275 | pgettext("ship", "Waiting"), _("Island Circumnavigated"), |
276 | _("An expedition ship sailed around its island without any events."), |
277 | "images/wui/ship/ship_explore_island_cw.png"); |
278 | - ship_state_ = ShipStates::kExpeditionWaiting; |
279 | - |
280 | - Notifications::publish( |
281 | - NoteShipMessage(this, NoteShipMessage::Message::kWaitingForCommand)); |
282 | - |
283 | return start_task_idle(game, descr().main_animation(), 1500); |
284 | } |
285 | } |
286 | @@ -582,10 +580,10 @@ |
287 | } |
288 | } |
289 | // if we are here, it seems something really strange happend. |
290 | - log("WARNING: ship %s was not able to start exploration. Entering WAIT mode.\n", |
291 | - shipname_.c_str()); |
292 | - ship_state_ = ShipStates::kExpeditionWaiting; |
293 | - return start_task_idle(game, descr().main_animation(), 1500); |
294 | + log("WARNING: ship %s was not able to start exploration. Entering WAIT mode.", shipname_.c_str()); |
295 | + set_ship_state_and_notify(ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand); |
296 | + start_task_idle(game, descr().main_animation(), 1500); |
297 | + return; |
298 | } |
299 | } else { // scouting towards a specific direction |
300 | if (exp_dir_swimmable(expedition_->scouting_direction)) { |
301 | @@ -595,7 +593,7 @@ |
302 | return; |
303 | } |
304 | // coast reached |
305 | - ship_state_ = ShipStates::kExpeditionWaiting; |
306 | + set_ship_state_and_notify(ShipStates::kExpeditionWaiting, NoteShip::Action::kWaitingForCommand); |
307 | start_task_idle(game, descr().main_animation(), 1500); |
308 | // Send a message to the player, that a new coast was reached |
309 | send_message(game, |
310 | @@ -603,10 +601,6 @@ |
311 | _("Land Ahoy!"), _("Coast Reached"), |
312 | _("An expedition ship reached a coast and is waiting for further commands."), |
313 | "images/wui/ship/ship_scout_ne.png"); |
314 | - |
315 | - Notifications::publish( |
316 | - NoteShipMessage(this, NoteShipMessage::Message::kWaitingForCommand)); |
317 | - |
318 | return; |
319 | } |
320 | } |
321 | @@ -710,6 +704,13 @@ |
322 | NEVER_HERE(); |
323 | } |
324 | |
325 | +void Ship::set_ship_state_and_notify(ShipStates state, NoteShip::Action action) { |
326 | + if (ship_state_ != state) { |
327 | + ship_state_ = state; |
328 | + Notifications::publish(NoteShip(this, action)); |
329 | + } |
330 | +} |
331 | + |
332 | void Ship::set_economy(Game& game, Economy* e) { |
333 | // Do not check here that the economy actually changed, because on loading |
334 | // we rely that wares really get reassigned our economy. |
335 | @@ -730,6 +731,7 @@ |
336 | items_.size()); |
337 | destination_ = &pd; |
338 | send_signal(game, "wakeup"); |
339 | + Notifications::publish(NoteShip(this, NoteShip::Action::kDestinationChanged)); |
340 | } |
341 | |
342 | void Ship::add_item(Game& game, const ShippingItem& item) { |
343 | @@ -847,14 +849,14 @@ |
344 | pgettext("ship", "Expedition"), _("Expedition Ready"), |
345 | _("An expedition ship is waiting for your commands."), |
346 | "images/wui/buildings/start_expedition.png"); |
347 | - Notifications::publish(NoteShipMessage(this, NoteShipMessage::Message::kWaitingForCommand)); |
348 | + Notifications::publish(NoteShip(this, NoteShip::Action::kWaitingForCommand)); |
349 | } |
350 | |
351 | /// Initializes / changes the direction of scouting to @arg direction |
352 | /// @note only called via player command |
353 | void Ship::exp_scouting_direction(Game&, WalkingDir scouting_direction) { |
354 | assert(expedition_); |
355 | - ship_state_ = ShipStates::kExpeditionScouting; |
356 | + set_ship_state_and_notify(ShipStates::kExpeditionScouting, NoteShip::Action::kDestinationChanged); |
357 | expedition_->scouting_direction = scouting_direction; |
358 | expedition_->island_exploration = false; |
359 | } |
360 | @@ -880,7 +882,7 @@ |
361 | for (auto& tree : trees) { |
362 | tree.object->remove(game); |
363 | } |
364 | - ship_state_ = ShipStates::kExpeditionColonizing; |
365 | + set_ship_state_and_notify(ShipStates::kExpeditionColonizing, NoteShip::Action::kDestinationChanged); |
366 | } |
367 | |
368 | /// Initializes / changes the direction the island exploration in @arg island_explore_direction |
369 | @@ -888,7 +890,7 @@ |
370 | /// @note only called via player command |
371 | void Ship::exp_explore_island(Game&, IslandExploreDirection island_explore_direction) { |
372 | assert(expedition_); |
373 | - ship_state_ = ShipStates::kExpeditionScouting; |
374 | + set_ship_state_and_notify(ShipStates::kExpeditionScouting, NoteShip::Action::kDestinationChanged); |
375 | expedition_->island_explore_direction = island_explore_direction; |
376 | expedition_->scouting_direction = WalkingDir::IDLE; |
377 | expedition_->island_exploration = true; |
378 | @@ -945,7 +947,7 @@ |
379 | } |
380 | } |
381 | |
382 | - Notifications::publish(NoteShipWindow(serial(), NoteShipWindow::Action::kNoPortLeft)); |
383 | + Notifications::publish(NoteShip(this, NoteShip::Action::kNoPortLeft)); |
384 | return; |
385 | } |
386 | assert(get_economy() && get_economy() != expedition_->economy.get()); |
387 | @@ -962,7 +964,6 @@ |
388 | // Running colonization has the highest priority + a sink request is only valid once |
389 | if (!state_is_sinkable()) |
390 | return; |
391 | - Notifications::publish(NoteShipWindow(serial(), NoteShipWindow::Action::kClose)); |
392 | ship_state_ = ShipStates::kSinkRequest; |
393 | // Make sure the ship is active and close possible open windows |
394 | ship_wakeup(game); |
395 | @@ -980,8 +981,13 @@ |
396 | if (draw_text & TextToDraw::kStatistics) { |
397 | switch (ship_state_) { |
398 | case (ShipStates::kTransport): |
399 | - /** TRANSLATORS: This is a ship state */ |
400 | - statistics_string = pgettext("ship_state", "Shipping"); |
401 | + if (destination_.is_set()) { |
402 | + /** TRANSLATORS: This is a ship state. The ship is currently transporting wares. */ |
403 | + statistics_string = pgettext("ship_state", "Shipping"); |
404 | + } else { |
405 | + /** TRANSLATORS: This is a ship state. The ship is ready to transport wares, but has nothing to do. */ |
406 | + statistics_string = pgettext("ship_state", "Idle"); |
407 | + } |
408 | break; |
409 | case (ShipStates::kExpeditionWaiting): |
410 | /** TRANSLATORS: This is a ship state. An expedition is waiting for your commands. */ |
411 | @@ -1182,6 +1188,7 @@ |
412 | // economy. Also, we might are on an expedition which means that we just now |
413 | // created the economy of this ship and must inform all wares. |
414 | ship.set_economy(dynamic_cast<Game&>(egbase()), ship.economy_); |
415 | + ship.get_owner()->add_ship(ship.serial()); |
416 | } |
417 | |
418 | MapObject::Loader* Ship::load(EditorGameBase& egbase, MapObjectLoader& mol, FileRead& fr) { |
419 | |
420 | === modified file 'src/logic/map_objects/tribes/ship.h' |
421 | --- src/logic/map_objects/tribes/ship.h 2018-04-07 16:59:00 +0000 |
422 | +++ src/logic/map_objects/tribes/ship.h 2018-04-28 09:26:35 +0000 |
423 | @@ -41,29 +41,16 @@ |
424 | kNotSet |
425 | }; |
426 | |
427 | -struct NoteShipMessage { |
428 | - CAN_BE_SENT_AS_NOTE(NoteId::ShipMessage) |
429 | +struct NoteShip { |
430 | + CAN_BE_SENT_AS_NOTE(NoteId::Ship) |
431 | |
432 | Ship* ship; |
433 | |
434 | - enum class Message { kLost, kGained, kWaitingForCommand }; |
435 | - Message message; |
436 | - |
437 | - NoteShipMessage(Ship* const init_ship, const Message& init_message) |
438 | - : ship(init_ship), message(init_message) { |
439 | - } |
440 | -}; |
441 | - |
442 | -struct NoteShipWindow { |
443 | - CAN_BE_SENT_AS_NOTE(NoteId::ShipWindow) |
444 | - |
445 | - Serial serial; |
446 | - |
447 | - enum class Action { kClose, kNoPortLeft }; |
448 | - const Action action; |
449 | - |
450 | - NoteShipWindow(Serial init_serial, const Action& init_action) |
451 | - : serial(init_serial), action(init_action) { |
452 | + enum class Action { kDestinationChanged, kWaitingForCommand, kNoPortLeft, kLost, kGained }; |
453 | + Action action; |
454 | + |
455 | + NoteShip(Ship* const init_ship, const Action& init_action) |
456 | + : ship(init_ship), action(init_action) { |
457 | } |
458 | }; |
459 | |
460 | @@ -263,6 +250,8 @@ |
461 | bool ship_update_transport(Game&, State&); |
462 | void ship_update_expedition(Game&, State&); |
463 | void ship_update_idle(Game&, State&); |
464 | + /// Set the ship's state to 'state' and if the ship state has changed, publish a notification. |
465 | + void set_ship_state_and_notify(ShipStates state, NoteShip::Action action); |
466 | |
467 | bool init_fleet(EditorGameBase&); |
468 | void set_fleet(Fleet* fleet); |
469 | |
470 | === modified file 'src/logic/player.cc' |
471 | --- src/logic/player.cc 2018-04-07 16:59:00 +0000 |
472 | +++ src/logic/player.cc 2018-04-28 09:26:35 +0000 |
473 | @@ -23,6 +23,7 @@ |
474 | #include <memory> |
475 | |
476 | #include <boost/bind.hpp> |
477 | +#include <boost/format.hpp> |
478 | #include <boost/signals2.hpp> |
479 | |
480 | #include "base/i18n.h" |
481 | @@ -133,6 +134,7 @@ |
482 | msites_defeated_(0), |
483 | civil_blds_lost_(0), |
484 | civil_blds_defeated_(0), |
485 | + ship_name_counter_(0), |
486 | fields_(nullptr), |
487 | allowed_worker_types_(the_egbase.tribes().nrworkers(), true), |
488 | allowed_building_types_(the_egbase.tribes().nrbuildings(), true), |
489 | @@ -372,6 +374,19 @@ |
490 | game->cmdqueue().enqueue(new CmdDeleteMessage(game->get_gametime(), player_number_, message_id)); |
491 | } |
492 | |
493 | +const std::set<Serial>& Player::ships() const { |
494 | + return ships_; |
495 | +} |
496 | +void Player::add_ship(Serial ship) { |
497 | + ships_.insert(ship); |
498 | +} |
499 | +void Player::remove_ship(Serial ship) { |
500 | + auto it = ships_.find(ship); |
501 | + if (it != ships_.end()) { |
502 | + ships_.erase(it); |
503 | + } |
504 | +} |
505 | + |
506 | /* |
507 | =============== |
508 | Return filtered buildcaps that take the player's territory into account. |
509 | @@ -1293,6 +1308,8 @@ |
510 | * Pick random name from remaining names (if any) |
511 | */ |
512 | const std::string Player::pick_shipname() { |
513 | + ++ship_name_counter_; |
514 | + |
515 | if (!remaining_shipnames_.empty()) { |
516 | Game& game = dynamic_cast<Game&>(egbase()); |
517 | assert(is_a(Game, &egbase())); |
518 | @@ -1303,7 +1320,7 @@ |
519 | remaining_shipnames_.erase(it); |
520 | return new_name; |
521 | } |
522 | - return "Ship"; |
523 | + return (boost::format(pgettext("shipname", "Ship %d")) % ship_name_counter_).str(); |
524 | } |
525 | |
526 | /** |
527 | @@ -1311,13 +1328,19 @@ |
528 | * |
529 | * \param fr source stream |
530 | */ |
531 | -void Player::read_remaining_shipnames(FileRead& fr) { |
532 | +void Player::read_remaining_shipnames(FileRead& fr, uint16_t packet_version) { |
533 | // First get rid of default shipnames |
534 | remaining_shipnames_.clear(); |
535 | const uint16_t count = fr.unsigned_16(); |
536 | for (uint16_t i = 0; i < count; ++i) { |
537 | remaining_shipnames_.insert(fr.string()); |
538 | } |
539 | + // TODO(GunChleoc): Savegame compatibility. Remove after Build 20. |
540 | + if (packet_version >= 21) { |
541 | + ship_name_counter_ = fr.unsigned_32(); |
542 | + } else { |
543 | + ship_name_counter_ = ships_.size(); |
544 | + } |
545 | } |
546 | |
547 | /** |
548 | @@ -1396,13 +1419,14 @@ |
549 | } |
550 | |
551 | /** |
552 | - * Write remaining ship indexes to the give file |
553 | + * Write remaining ship indexes to the given file |
554 | */ |
555 | void Player::write_remaining_shipnames(FileWrite& fw) const { |
556 | fw.unsigned_16(remaining_shipnames_.size()); |
557 | for (const auto& shipname : remaining_shipnames_) { |
558 | fw.string(shipname); |
559 | } |
560 | + fw.unsigned_32(ship_name_counter_); |
561 | } |
562 | |
563 | /** |
564 | |
565 | === modified file 'src/logic/player.h' |
566 | --- src/logic/player.h 2018-04-07 16:59:00 +0000 |
567 | +++ src/logic/player.h 2018-04-28 09:26:35 +0000 |
568 | @@ -111,6 +111,10 @@ |
569 | get_messages()->set_message_status(id, status); |
570 | } |
571 | |
572 | + const std::set<Serial>& ships() const; |
573 | + void add_ship(Serial ship); |
574 | + void remove_ship(Serial ship); |
575 | + |
576 | const EditorGameBase& egbase() const { |
577 | return egbase_; |
578 | } |
579 | @@ -583,7 +587,7 @@ |
580 | |
581 | void read_statistics(FileRead&); |
582 | void write_statistics(FileWrite&) const; |
583 | - void read_remaining_shipnames(FileRead&); |
584 | + void read_remaining_shipnames(FileRead&, uint16_t packet_version); |
585 | void write_remaining_shipnames(FileWrite&) const; |
586 | void sample_statistics(); |
587 | void ware_produced(DescriptionIndex); |
588 | @@ -634,11 +638,14 @@ |
589 | uint32_t msites_lost_, msites_defeated_; |
590 | uint32_t civil_blds_lost_, civil_blds_defeated_; |
591 | std::unordered_set<std::string> remaining_shipnames_; |
592 | + // If we run out of ship names, we'll want to continue with unique numbers |
593 | + uint32_t ship_name_counter_; |
594 | |
595 | Field* fields_; |
596 | std::vector<bool> allowed_worker_types_; |
597 | std::vector<bool> allowed_building_types_; |
598 | Economies economies_; |
599 | + std::set<Serial> ships_; |
600 | std::string name_; // Player name |
601 | std::string ai_; /**< Name of preferred AI implementation */ |
602 | |
603 | |
604 | === modified file 'src/notifications/note_ids.h' |
605 | --- src/notifications/note_ids.h 2018-04-07 16:59:00 +0000 |
606 | +++ src/notifications/note_ids.h 2018-04-28 09:26:35 +0000 |
607 | @@ -33,8 +33,7 @@ |
608 | FieldTerrainChanged, |
609 | ProductionSiteOutOfResources, |
610 | TrainingSiteSoldierTrained, |
611 | - ShipMessage, |
612 | - ShipWindow, |
613 | + Ship, |
614 | Building, |
615 | Economy, |
616 | GraphicResolutionChanged, |
617 | |
618 | === modified file 'src/scripting/lua_game.cc' |
619 | --- src/scripting/lua_game.cc 2018-04-07 16:59:00 +0000 |
620 | +++ src/scripting/lua_game.cc 2018-04-28 09:26:35 +0000 |
621 | @@ -672,30 +672,16 @@ |
622 | */ |
623 | int LuaPlayer::get_ships(lua_State* L) { |
624 | EditorGameBase& egbase = get_egbase(L); |
625 | - const Map& map = egbase.map(); |
626 | PlayerNumber p = (get(L, egbase)).player_number(); |
627 | lua_newtable(L); |
628 | uint32_t cidx = 1; |
629 | - |
630 | - std::set<OPtr<Ship>> found_ships; |
631 | - for (int16_t y = 0; y < map.get_height(); ++y) { |
632 | - for (int16_t x = 0; x < map.get_width(); ++x) { |
633 | - FCoords f = map.get_fcoords(Coords(x, y)); |
634 | - // there are too many bobs on the map so we investigate |
635 | - // only bobs on water |
636 | - if (f.field->nodecaps() & MOVECAPS_SWIM) { |
637 | - for (Bob* bob = f.field->get_first_bob(); bob; bob = bob->get_next_on_field()) { |
638 | - if (upcast(Ship, ship, bob)) { |
639 | - if (ship->get_owner()->player_number() == p && !found_ships.count(ship)) { |
640 | - found_ships.insert(ship); |
641 | - lua_pushuint32(L, cidx++); |
642 | - LuaMaps::upcasted_map_object_to_lua(L, ship); |
643 | - lua_rawset(L, -3); |
644 | - } |
645 | - } |
646 | - } |
647 | - } |
648 | - } |
649 | + for (const auto& serial : egbase.player(p).ships()) { |
650 | + Widelands::MapObject* obj = egbase.objects().get_object(serial); |
651 | + assert(obj->descr().type() == Widelands::MapObjectType::SHIP); |
652 | + upcast(Widelands::Ship, ship, obj); |
653 | + lua_pushuint32(L, cidx++); |
654 | + LuaMaps::upcasted_map_object_to_lua(L, ship); |
655 | + lua_rawset(L, -3); |
656 | } |
657 | return 1; |
658 | } |
659 | |
660 | === modified file 'src/ui_basic/table.cc' |
661 | --- src/ui_basic/table.cc 2018-04-07 16:59:00 +0000 |
662 | +++ src/ui_basic/table.cc 2018-04-28 09:26:35 +0000 |
663 | @@ -551,6 +551,18 @@ |
664 | layout(); |
665 | } |
666 | |
667 | +/** |
668 | + * Remove the given table entry if it exists. |
669 | + */ |
670 | +void Table<void*>::remove_entry(const void* const entry) { |
671 | + for (uint32_t i = 0; i < entry_records_.size(); ++i) { |
672 | + if (entry_records_[i]->entry() == entry) { |
673 | + remove(i); |
674 | + return; |
675 | + } |
676 | + } |
677 | +} |
678 | + |
679 | bool Table<void*>::sort_helper(uint32_t a, uint32_t b) { |
680 | if (sort_descending_) { |
681 | return columns_[sort_column_].compare(b, a); |
682 | |
683 | === modified file 'src/ui_basic/table.h' |
684 | --- src/ui_basic/table.h 2018-04-07 16:59:00 +0000 |
685 | +++ src/ui_basic/table.h 2018-04-28 09:26:35 +0000 |
686 | @@ -84,6 +84,7 @@ |
687 | |
688 | void sort(uint32_t lower_bound = 0, uint32_t upper_bound = std::numeric_limits<uint32_t>::max()); |
689 | void remove(uint32_t); |
690 | + void remove_entry(Entry); |
691 | |
692 | EntryRecord& add(void* const entry, const bool select_this = false); |
693 | |
694 | @@ -150,7 +151,7 @@ |
695 | return clr; |
696 | } |
697 | |
698 | - private: |
699 | + private: |
700 | friend class Table<void*>; |
701 | void* entry_; |
702 | bool use_clr; |
703 | @@ -190,8 +191,6 @@ |
704 | void set_column_title(uint8_t col, const std::string& title); |
705 | void set_column_compare(uint8_t col, const CompareFn& fn); |
706 | |
707 | - void layout() override; |
708 | - |
709 | void clear(); |
710 | void set_sort_column(uint8_t const col) { |
711 | assert(col < columns_.size()); |
712 | @@ -209,6 +208,7 @@ |
713 | |
714 | void sort(uint32_t lower_bound = 0, uint32_t upper_bound = std::numeric_limits<uint32_t>::max()); |
715 | void remove(uint32_t); |
716 | + void remove_entry(const void* const entry); |
717 | |
718 | EntryRecord& add(void* entry = nullptr, bool select = false); |
719 | |
720 | @@ -275,6 +275,8 @@ |
721 | /// If entries == 0, the current entries are used. |
722 | void fit_height(uint32_t entries = 0); |
723 | |
724 | + void layout() override; |
725 | + |
726 | // Drawing and event handling |
727 | void draw(RenderTarget&) override; |
728 | bool handle_mousepress(uint8_t btn, int32_t x, int32_t y) override; |
729 | @@ -335,6 +337,10 @@ |
730 | : Base(parent, x, y, w, h, button_background, rowtype) { |
731 | } |
732 | |
733 | + void remove_entry(Entry const* const entry) { |
734 | + Base::remove_entry(const_cast<Entry*>(entry)); |
735 | + } |
736 | + |
737 | EntryRecord& add(Entry const* const entry = 0, bool const select_this = false) { |
738 | return Base::add(const_cast<Entry*>(entry), select_this); |
739 | } |
740 | @@ -365,6 +371,10 @@ |
741 | : Base(parent, x, y, w, h, button_background, rowtype) { |
742 | } |
743 | |
744 | + void remove_entry(Entry const* entry) { |
745 | + Base::remove_entry(entry); |
746 | + } |
747 | + |
748 | EntryRecord& add(Entry* const entry = 0, bool const select_this = false) { |
749 | return Base::add(entry, select_this); |
750 | } |
751 | @@ -395,6 +405,10 @@ |
752 | : Base(parent, x, y, w, h, button_background, rowtype) { |
753 | } |
754 | |
755 | + void remove_entry(const Entry& entry) { |
756 | + Base::remove_entry(&const_cast<Entry&>(entry)); |
757 | + } |
758 | + |
759 | EntryRecord& add(const Entry& entry, bool const select_this = false) { |
760 | return Base::add(&const_cast<Entry&>(entry), select_this); |
761 | } |
762 | @@ -429,6 +443,10 @@ |
763 | : Base(parent, x, y, w, h, button_background, rowtype) { |
764 | } |
765 | |
766 | + void remove_entry(Entry& entry) { |
767 | + Base::remove_entry(&entry); |
768 | + } |
769 | + |
770 | EntryRecord& add(Entry& entry, bool const select_this = false) { |
771 | return Base::add(&entry, select_this); |
772 | } |
773 | @@ -465,6 +483,10 @@ |
774 | : Base(parent, x, y, w, h, button_background, rowtype) { |
775 | } |
776 | |
777 | + void remove_entry(uintptr_t const entry) { |
778 | + Base::remove_entry(reinterpret_cast<void*>(entry)); |
779 | + } |
780 | + |
781 | EntryRecord& add(uintptr_t const entry, bool const select_this = false) { |
782 | return Base::add(reinterpret_cast<void*>(entry), select_this); |
783 | } |
784 | |
785 | === modified file 'src/wui/CMakeLists.txt' |
786 | --- src/wui/CMakeLists.txt 2017-11-20 13:50:51 +0000 |
787 | +++ src/wui/CMakeLists.txt 2018-04-28 09:26:35 +0000 |
788 | @@ -227,6 +227,8 @@ |
789 | portdockwaresdisplay.h |
790 | productionsitewindow.cc |
791 | productionsitewindow.h |
792 | + seafaring_statistics_menu.cc |
793 | + seafaring_statistics_menu.h |
794 | shipwindow.cc |
795 | shipwindow.h |
796 | soldiercapacitycontrol.cc |
797 | |
798 | === modified file 'src/wui/game_statistics_menu.cc' |
799 | --- src/wui/game_statistics_menu.cc 2018-04-07 16:59:00 +0000 |
800 | +++ src/wui/game_statistics_menu.cc 2018-04-28 09:26:35 +0000 |
801 | @@ -27,6 +27,7 @@ |
802 | #include "wui/building_statistics_menu.h" |
803 | #include "wui/general_statistics_menu.h" |
804 | #include "wui/interactive_player.h" |
805 | +#include "wui/seafaring_statistics_menu.h" |
806 | #include "wui/stock_menu.h" |
807 | #include "wui/ware_statistics_menu.h" |
808 | |
809 | @@ -37,6 +38,7 @@ |
810 | player_(plr), |
811 | windows_(windows), |
812 | box_(this, 0, 0, UI::Box::Horizontal, 0, 0, 5) { |
813 | + const bool is_seafaring = plr.egbase().mutable_map()->allows_seafaring(); |
814 | add_button("wui/menus/menu_general_stats", "general_stats", _("General statistics"), |
815 | &windows_.general_stats); |
816 | add_button( |
817 | @@ -44,8 +46,12 @@ |
818 | add_button("wui/menus/menu_building_stats", "building_stats", _("Building statistics"), |
819 | &windows_.building_stats); |
820 | add_button("wui/menus/menu_stock", "stock", _("Stock"), &windows_.stock); |
821 | + if (is_seafaring) { |
822 | + add_button("wui/buildings/start_expedition", "seafaring_stats", _("Seafaring Statistics"), |
823 | + &windows_.seafaring_stats); |
824 | + } |
825 | box_.set_pos(Vector2i(10, 10)); |
826 | - box_.set_size((34 + 5) * 4, 34); |
827 | + box_.set_size((34 + 5) * (is_seafaring ? 5 : 4), 34); |
828 | set_inner_size(box_.get_w() + 20, box_.get_h() + 20); |
829 | |
830 | windows_.general_stats.open_window = [this] { |
831 | @@ -58,6 +64,11 @@ |
832 | new BuildingStatisticsMenu(player_, windows_.building_stats); |
833 | }; |
834 | // The stock window is defined in InteractivePlayer because of the keyboard shortcut. |
835 | + if (is_seafaring) { |
836 | + windows_.seafaring_stats.open_window = [this] { |
837 | + new SeafaringStatisticsMenu(player_, windows_.seafaring_stats); |
838 | + }; |
839 | + } |
840 | |
841 | if (get_usedefaultpos()) |
842 | center_to_parent(); |
843 | |
844 | === modified file 'src/wui/interactive_gamebase.cc' |
845 | --- src/wui/interactive_gamebase.cc 2018-04-27 20:12:08 +0000 |
846 | +++ src/wui/interactive_gamebase.cc 2018-04-28 09:26:35 +0000 |
847 | @@ -243,30 +243,33 @@ |
848 | const Widelands::Map& map = game().map(); |
849 | Widelands::Area<Widelands::FCoords> area(map.get_fcoords(get_sel_pos().node), 1); |
850 | |
851 | - if (!(area.field->nodecaps() & Widelands::MOVECAPS_SWIM)) |
852 | + if (!(area.field->nodecaps() & Widelands::MOVECAPS_SWIM)) { |
853 | return false; |
854 | + } |
855 | |
856 | std::vector<Widelands::Bob*> ships; |
857 | - if (!map.find_bobs(area, &ships, Widelands::FindBobShip())) |
858 | - return false; |
859 | - |
860 | - for (Widelands::Bob* temp_ship : ships) { |
861 | - if (upcast(Widelands::Ship, ship, temp_ship)) { |
862 | - if (can_see(ship->get_owner()->player_number())) { |
863 | - UI::UniqueWindow::Registry& registry = |
864 | - unique_windows().get_registry((boost::format("ship_%d") % ship->serial()).str()); |
865 | - registry.open_window = [this, ®istry, ship] { |
866 | - new ShipWindow(*this, registry, ship); |
867 | - }; |
868 | - registry.create(); |
869 | - return true; |
870 | - } |
871 | - } |
872 | - } |
873 | - |
874 | + if (map.find_bobs(area, &ships, Widelands::FindBobShip())) { |
875 | + for (Widelands::Bob* ship : ships) { |
876 | + if (can_see(ship->owner().player_number())) { |
877 | + // FindBobShip should have returned only ships |
878 | + assert(ship->descr().type() == Widelands::MapObjectType::SHIP); |
879 | + show_ship_window(dynamic_cast<Widelands::Ship*>(ship)); |
880 | + return true; |
881 | + } |
882 | + } |
883 | + } |
884 | return false; |
885 | } |
886 | |
887 | +void InteractiveGameBase::show_ship_window(Widelands::Ship* ship) { |
888 | + UI::UniqueWindow::Registry& registry = |
889 | + unique_windows().get_registry((boost::format("ship_%d") % ship->serial()).str()); |
890 | + registry.open_window = [this, ®istry, ship] { |
891 | + new ShipWindow(*this, registry, ship); |
892 | + }; |
893 | + registry.create(); |
894 | +} |
895 | + |
896 | void InteractiveGameBase::show_game_summary() { |
897 | if (game_summary_.window) { |
898 | game_summary_.window->set_visible(true); |
899 | |
900 | === modified file 'src/wui/interactive_gamebase.h' |
901 | --- src/wui/interactive_gamebase.h 2018-04-16 07:16:00 +0000 |
902 | +++ src/wui/interactive_gamebase.h 2018-04-28 09:26:35 +0000 |
903 | @@ -48,6 +48,7 @@ |
904 | GeneralStatisticsMenu::Registry general_stats; |
905 | UI::UniqueWindow::Registry ware_stats; |
906 | UI::UniqueWindow::Registry stock; |
907 | + UI::UniqueWindow::Registry seafaring_stats; |
908 | }; |
909 | |
910 | InteractiveGameBase(Widelands::Game&, |
911 | @@ -86,6 +87,7 @@ |
912 | bool was_minimal); |
913 | UI::UniqueWindow* show_building_window(const Widelands::Coords& coords, bool avoid_fastclick); |
914 | bool try_show_ship_window(); |
915 | + void show_ship_window(Widelands::Ship* ship); |
916 | bool is_multiplayer() { |
917 | return multiplayer_; |
918 | } |
919 | |
920 | === modified file 'src/wui/interactive_player.cc' |
921 | --- src/wui/interactive_player.cc 2018-04-07 16:59:00 +0000 |
922 | +++ src/wui/interactive_player.cc 2018-04-28 09:26:35 +0000 |
923 | @@ -51,6 +51,7 @@ |
924 | #include "wui/game_options_menu.h" |
925 | #include "wui/game_statistics_menu.h" |
926 | #include "wui/general_statistics_menu.h" |
927 | +#include "wui/seafaring_statistics_menu.h" |
928 | #include "wui/stock_menu.h" |
929 | #include "wui/tribal_encyclopedia.h" |
930 | #include "wui/ware_statistics_menu.h" |
931 | @@ -459,6 +460,16 @@ |
932 | } |
933 | return true; |
934 | |
935 | + case SDLK_e: |
936 | + if (game().map().allows_seafaring()) { |
937 | + if (main_windows_.seafaring_stats.window == nullptr) { |
938 | + new SeafaringStatisticsMenu(*this, main_windows_.seafaring_stats); |
939 | + } else { |
940 | + main_windows_.seafaring_stats.toggle(); |
941 | + } |
942 | + } |
943 | + return true; |
944 | + |
945 | case SDLK_s: |
946 | if (code.mod & (KMOD_LCTRL | KMOD_RCTRL)) |
947 | new GameMainMenuSaveGame(*this, main_windows_.savegame); |
948 | |
949 | === added file 'src/wui/seafaring_statistics_menu.cc' |
950 | --- src/wui/seafaring_statistics_menu.cc 1970-01-01 00:00:00 +0000 |
951 | +++ src/wui/seafaring_statistics_menu.cc 2018-04-28 09:26:35 +0000 |
952 | @@ -0,0 +1,580 @@ |
953 | +/* |
954 | + * Copyright (C) 2017-2018 by the Widelands Development Team |
955 | + * |
956 | + * This program is free software; you can redistribute it and/or |
957 | + * modify it under the terms of the GNU General Public License |
958 | + * as published by the Free Software Foundation; either version 2 |
959 | + * of the License, or (at your option) any later version. |
960 | + * |
961 | + * This program is distributed in the hope that it will be useful, |
962 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
963 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
964 | + * GNU General Public License for more details. |
965 | + * |
966 | + * You should have received a copy of the GNU General Public License |
967 | + * along with this program; if not, write to the Free Software |
968 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
969 | + * |
970 | + */ |
971 | + |
972 | +#include "wui/seafaring_statistics_menu.h" |
973 | + |
974 | +#include <memory> |
975 | + |
976 | +#include <boost/bind.hpp> |
977 | +#include <boost/format.hpp> |
978 | + |
979 | +#include "economy/fleet.h" |
980 | +#include "graphic/graphic.h" |
981 | +#include "logic/game.h" |
982 | +#include "logic/player.h" |
983 | +#include "logic/playercommand.h" |
984 | +#include "ui_basic/box.h" |
985 | +#include "wui/interactive_player.h" |
986 | +#include "wui/shipwindow.h" |
987 | +#include "wui/watchwindow.h" |
988 | + |
989 | +inline InteractivePlayer& SeafaringStatisticsMenu::iplayer() const { |
990 | + return dynamic_cast<InteractivePlayer&>(*get_parent()); |
991 | +} |
992 | + |
993 | +constexpr int kPadding = 5; |
994 | +constexpr int kButtonSize = 34; |
995 | + |
996 | +SeafaringStatisticsMenu::SeafaringStatisticsMenu(InteractivePlayer& plr, |
997 | + UI::UniqueWindow::Registry& registry) |
998 | + : UI::UniqueWindow(&plr, "seafaring_statistics", ®istry, 355, 375, _("Seafaring Statistics")), |
999 | + main_box_(this, kPadding, kPadding, UI::Box::Vertical, get_inner_w(), get_inner_h(), kPadding), |
1000 | + filter_box_( |
1001 | + &main_box_, 0, 0, UI::Box::Horizontal, get_inner_w() - 2 * kPadding, kButtonSize, kPadding), |
1002 | + idle_btn_(&filter_box_, |
1003 | + "filter_ship_idle", |
1004 | + 0, |
1005 | + 0, |
1006 | + kButtonSize, |
1007 | + kButtonSize, |
1008 | + g_gr->images().get("images/ui_basic/but0.png"), |
1009 | + status_to_image(ShipFilterStatus::kIdle)), |
1010 | + waiting_btn_(&filter_box_, |
1011 | + "filter_ship_waiting", |
1012 | + 0, |
1013 | + 0, |
1014 | + kButtonSize, |
1015 | + kButtonSize, |
1016 | + g_gr->images().get("images/ui_basic/but0.png"), |
1017 | + status_to_image(ShipFilterStatus::kExpeditionWaiting)), |
1018 | + scouting_btn_(&filter_box_, |
1019 | + "filter_ship_scouting", |
1020 | + 0, |
1021 | + 0, |
1022 | + kButtonSize, |
1023 | + kButtonSize, |
1024 | + g_gr->images().get("images/ui_basic/but0.png"), |
1025 | + status_to_image(ShipFilterStatus::kExpeditionScouting)), |
1026 | + portspace_btn_(&filter_box_, |
1027 | + "filter_ship_portspace", |
1028 | + 0, |
1029 | + 0, |
1030 | + kButtonSize, |
1031 | + kButtonSize, |
1032 | + g_gr->images().get("images/ui_basic/but0.png"), |
1033 | + status_to_image(ShipFilterStatus::kExpeditionPortspaceFound)), |
1034 | + shipping_btn_(&filter_box_, |
1035 | + "filter_ship_transporting", |
1036 | + 0, |
1037 | + 0, |
1038 | + kButtonSize, |
1039 | + kButtonSize, |
1040 | + g_gr->images().get("images/ui_basic/but0.png"), |
1041 | + status_to_image(ShipFilterStatus::kShipping)), |
1042 | + ship_filter_(ShipFilterStatus::kAll), |
1043 | + navigation_box_( |
1044 | + &main_box_, 0, 0, UI::Box::Horizontal, get_inner_w() - 2 * kPadding, kButtonSize, kPadding), |
1045 | + watchbtn_(&navigation_box_, |
1046 | + "seafaring_stats_watch_button", |
1047 | + 0, |
1048 | + 0, |
1049 | + kButtonSize, |
1050 | + kButtonSize, |
1051 | + g_gr->images().get("images/ui_basic/but2.png"), |
1052 | + g_gr->images().get("images/wui/menus/menu_watch_follow.png"), |
1053 | + (boost::format(_("%1% (Hotkey: %2%)")) % |
1054 | + /** TRANSLATORS: Tooltip in the seafaring statistics window */ |
1055 | + _("Watch the selected ship") % |
1056 | + pgettext("hotkey", "W")) |
1057 | + .str()), |
1058 | + openwindowbtn_(&navigation_box_, |
1059 | + "seafaring_stats_watch_button", |
1060 | + 0, |
1061 | + 0, |
1062 | + kButtonSize, |
1063 | + kButtonSize, |
1064 | + g_gr->images().get("images/ui_basic/but2.png"), |
1065 | + g_gr->images().get("images/ui_basic/fsel.png"), |
1066 | + (boost::format("%s<br>%s") % |
1067 | + (boost::format(pgettext("hotkey_description", "%1%: %2%")) % |
1068 | + pgettext("hotkey", "O") % |
1069 | + /** TRANSLATORS: Tooltip in the seafaring statistics window */ |
1070 | + _("Open the selected ship’s window")) % |
1071 | + (boost::format(pgettext("hotkey_description", "%1%: %2%")) % |
1072 | + pgettext("hotkey", "CTRL + O") % |
1073 | + /** TRANSLATORS: Tooltip in the seafaring statistics window */ |
1074 | + _("Go to the selected ship and open its window"))) |
1075 | + .str()), |
1076 | + centerviewbtn_(&navigation_box_, |
1077 | + "seafaring_stats_center_main_mapview_button", |
1078 | + 0, |
1079 | + 0, |
1080 | + kButtonSize, |
1081 | + kButtonSize, |
1082 | + g_gr->images().get("images/ui_basic/but2.png"), |
1083 | + g_gr->images().get("images/wui/ship/menu_ship_goto.png"), |
1084 | + (boost::format(_("%1% (Hotkey: %2%)")) % |
1085 | + /** TRANSLATORS: Tooltip in the seafaring statistics window */ |
1086 | + _("Center the map on the selected ship") % |
1087 | + pgettext("hotkey", "G")) |
1088 | + .str()), |
1089 | + table_(&main_box_, |
1090 | + 0, |
1091 | + 0, |
1092 | + get_inner_w() - 2 * kPadding, |
1093 | + 100, |
1094 | + g_gr->images().get("images/ui_basic/but1.png")) { |
1095 | + |
1096 | + const Widelands::TribeDescr& tribe = iplayer().player().tribe(); |
1097 | + colony_icon_ = tribe.get_worker_descr(tribe.builder())->icon(); |
1098 | + |
1099 | + // Buttons for ship states |
1100 | + main_box_.add(&filter_box_, UI::Box::Resizing::kFullSize); |
1101 | + filter_box_.add(&idle_btn_); |
1102 | + filter_box_.add(&shipping_btn_); |
1103 | + filter_box_.add(&waiting_btn_); |
1104 | + filter_box_.add(&scouting_btn_); |
1105 | + filter_box_.add(&portspace_btn_); |
1106 | + |
1107 | + main_box_.add(&table_, UI::Box::Resizing::kExpandBoth); |
1108 | + |
1109 | + // Navigation buttons |
1110 | + main_box_.add(&navigation_box_, UI::Box::Resizing::kFullSize); |
1111 | + navigation_box_.add(&watchbtn_); |
1112 | + navigation_box_.add_inf_space(); |
1113 | + navigation_box_.add(&openwindowbtn_); |
1114 | + navigation_box_.add(¢erviewbtn_); |
1115 | + main_box_.set_size(get_inner_w() - 2 * kPadding, get_inner_h() - 2 * kPadding); |
1116 | + |
1117 | + // Configure actions |
1118 | + idle_btn_.sigclicked.connect( |
1119 | + boost::bind(&SeafaringStatisticsMenu::filter_ships, this, ShipFilterStatus::kIdle)); |
1120 | + shipping_btn_.sigclicked.connect( |
1121 | + boost::bind(&SeafaringStatisticsMenu::filter_ships, this, ShipFilterStatus::kShipping)); |
1122 | + waiting_btn_.sigclicked.connect(boost::bind( |
1123 | + &SeafaringStatisticsMenu::filter_ships, this, ShipFilterStatus::kExpeditionWaiting)); |
1124 | + scouting_btn_.sigclicked.connect(boost::bind( |
1125 | + &SeafaringStatisticsMenu::filter_ships, this, ShipFilterStatus::kExpeditionScouting)); |
1126 | + portspace_btn_.sigclicked.connect(boost::bind( |
1127 | + &SeafaringStatisticsMenu::filter_ships, this, ShipFilterStatus::kExpeditionPortspaceFound)); |
1128 | + ship_filter_ = ShipFilterStatus::kAll; |
1129 | + set_filter_ships_tooltips(); |
1130 | + |
1131 | + watchbtn_.sigclicked.connect(boost::bind(&SeafaringStatisticsMenu::watch_ship, this)); |
1132 | + openwindowbtn_.sigclicked.connect(boost::bind(&SeafaringStatisticsMenu::open_ship_window, this)); |
1133 | + centerviewbtn_.sigclicked.connect(boost::bind(&SeafaringStatisticsMenu::center_view, this)); |
1134 | + |
1135 | + // Configure table |
1136 | + table_.selected.connect(boost::bind(&SeafaringStatisticsMenu::selected, this)); |
1137 | + table_.double_clicked.connect(boost::bind(&SeafaringStatisticsMenu::double_clicked, this)); |
1138 | + table_.add_column( |
1139 | + 0, pgettext("ship", "Name"), "", UI::Align::kLeft, UI::TableColumnType::kFlexible); |
1140 | + table_.add_column(200, pgettext("ship", "Status")); |
1141 | + table_.set_sort_column(ColName); |
1142 | + fill_table(); |
1143 | + |
1144 | + set_can_focus(true); |
1145 | + set_thinks(false); |
1146 | + table_.focus(); |
1147 | + |
1148 | + shipnotes_subscriber_ = |
1149 | + Notifications::subscribe<Widelands::NoteShip>([this](const Widelands::NoteShip& note) { |
1150 | + if (iplayer().get_player() == note.ship->get_owner()) { |
1151 | + switch (note.action) { |
1152 | + case Widelands::NoteShip::Action::kDestinationChanged: |
1153 | + case Widelands::NoteShip::Action::kWaitingForCommand: |
1154 | + case Widelands::NoteShip::Action::kGained: |
1155 | + assert(note.ship != nullptr); |
1156 | + update_ship(*note.ship); |
1157 | + break; |
1158 | + case Widelands::NoteShip::Action::kLost: |
1159 | + remove_ship(note.ship->serial()); |
1160 | + break; |
1161 | + default: |
1162 | + NEVER_HERE(); |
1163 | + } |
1164 | + } |
1165 | + }); |
1166 | +} |
1167 | + |
1168 | +const std::string |
1169 | +SeafaringStatisticsMenu::status_to_string(SeafaringStatisticsMenu::ShipFilterStatus status) const { |
1170 | + switch (status) { |
1171 | + case SeafaringStatisticsMenu::ShipFilterStatus::kIdle: |
1172 | + return pgettext("ship_state", "Idle"); |
1173 | + case SeafaringStatisticsMenu::ShipFilterStatus::kShipping: |
1174 | + return pgettext("ship_state", "Shipping"); |
1175 | + case SeafaringStatisticsMenu::ShipFilterStatus::kExpeditionWaiting: |
1176 | + return pgettext("ship_state", "Waiting"); |
1177 | + case SeafaringStatisticsMenu::ShipFilterStatus::kExpeditionScouting: |
1178 | + return pgettext("ship_state", "Scouting"); |
1179 | + case SeafaringStatisticsMenu::ShipFilterStatus::kExpeditionPortspaceFound: |
1180 | + return pgettext("ship_state", "Port Space Found"); |
1181 | + case SeafaringStatisticsMenu::ShipFilterStatus::kExpeditionColonizing: |
1182 | + return pgettext("ship_state", "Founding a Colony"); |
1183 | + case SeafaringStatisticsMenu::ShipFilterStatus::kAll: |
1184 | + return "All"; // The user shouldn't see this, so we don't localize |
1185 | + default: |
1186 | + NEVER_HERE(); |
1187 | + } |
1188 | +} |
1189 | + |
1190 | +const Image* |
1191 | +SeafaringStatisticsMenu::status_to_image(SeafaringStatisticsMenu::ShipFilterStatus status) const { |
1192 | + std::string filename = ""; |
1193 | + switch (status) { |
1194 | + case SeafaringStatisticsMenu::ShipFilterStatus::kIdle: |
1195 | + filename = "images/wui/stats/ship_stats_idle.png"; |
1196 | + break; |
1197 | + case SeafaringStatisticsMenu::ShipFilterStatus::kShipping: |
1198 | + filename = "images/wui/stats/ship_stats_shipping.png"; |
1199 | + break; |
1200 | + case SeafaringStatisticsMenu::ShipFilterStatus::kExpeditionWaiting: |
1201 | + filename = "images/wui/buildings/start_expedition.png"; |
1202 | + break; |
1203 | + case SeafaringStatisticsMenu::ShipFilterStatus::kExpeditionScouting: |
1204 | + filename = "images/wui/ship/ship_explore_island_cw.png"; |
1205 | + break; |
1206 | + case SeafaringStatisticsMenu::ShipFilterStatus::kExpeditionPortspaceFound: |
1207 | + filename = "images/wui/ship/ship_construct_port_space.png"; |
1208 | + break; |
1209 | + case SeafaringStatisticsMenu::ShipFilterStatus::kExpeditionColonizing: |
1210 | + return colony_icon_; |
1211 | + case SeafaringStatisticsMenu::ShipFilterStatus::kAll: |
1212 | + filename = "images/wui/ship/ship_scout_ne.png"; |
1213 | + break; |
1214 | + default: |
1215 | + NEVER_HERE(); |
1216 | + } |
1217 | + return g_gr->images().get(filename); |
1218 | +} |
1219 | + |
1220 | +std::unique_ptr<const SeafaringStatisticsMenu::ShipInfo> |
1221 | +SeafaringStatisticsMenu::create_shipinfo(const Widelands::Ship& ship) const { |
1222 | + const Widelands::Ship::ShipStates state = ship.get_ship_state(); |
1223 | + ShipFilterStatus status = ShipFilterStatus::kAll; |
1224 | + switch (state) { |
1225 | + case Widelands::Ship::ShipStates::kTransport: |
1226 | + if (ship.get_destination(iplayer().game()) != nullptr) { |
1227 | + status = ShipFilterStatus::kShipping; |
1228 | + } else { |
1229 | + status = ShipFilterStatus::kIdle; |
1230 | + } |
1231 | + break; |
1232 | + case Widelands::Ship::ShipStates::kExpeditionWaiting: |
1233 | + status = ShipFilterStatus::kExpeditionWaiting; |
1234 | + break; |
1235 | + case Widelands::Ship::ShipStates::kExpeditionScouting: |
1236 | + status = ShipFilterStatus::kExpeditionScouting; |
1237 | + break; |
1238 | + case Widelands::Ship::ShipStates::kExpeditionPortspaceFound: |
1239 | + status = ShipFilterStatus::kExpeditionPortspaceFound; |
1240 | + break; |
1241 | + case Widelands::Ship::ShipStates::kExpeditionColonizing: |
1242 | + status = ShipFilterStatus::kExpeditionColonizing; |
1243 | + break; |
1244 | + case Widelands::Ship::ShipStates::kSinkRequest: |
1245 | + case Widelands::Ship::ShipStates::kSinkAnimation: |
1246 | + status = ShipFilterStatus::kAll; |
1247 | + } |
1248 | + return std::unique_ptr<const ShipInfo>(new ShipInfo(ship.get_shipname(), status, ship.serial())); |
1249 | +} |
1250 | + |
1251 | +void SeafaringStatisticsMenu::set_entry_record(UI::Table<uintptr_t>::EntryRecord* er, |
1252 | + const ShipInfo& info) { |
1253 | + if (info.status != ShipFilterStatus::kAll) { |
1254 | + er->set_string(ColName, info.name); |
1255 | + er->set_picture(ColStatus, status_to_image(info.status), status_to_string(info.status)); |
1256 | + } |
1257 | +} |
1258 | + |
1259 | +Widelands::Ship* SeafaringStatisticsMenu::serial_to_ship(Widelands::Serial serial) const { |
1260 | + Widelands::MapObject* obj = iplayer().game().objects().get_object(serial); |
1261 | + assert(obj->descr().type() == Widelands::MapObjectType::SHIP); |
1262 | + upcast(Widelands::Ship, ship, obj); |
1263 | + return ship; |
1264 | +} |
1265 | + |
1266 | +void SeafaringStatisticsMenu::update_ship(const Widelands::Ship& ship) { |
1267 | + assert(iplayer().get_player() == ship.get_owner()); |
1268 | + std::unique_ptr<const ShipInfo> info = create_shipinfo(ship); |
1269 | + // Remove ships that don't satisfy the filter |
1270 | + if (ship_filter_ != ShipFilterStatus::kAll && !satisfies_filter(*info, ship_filter_)) { |
1271 | + remove_ship(info->serial); |
1272 | + return; |
1273 | + } |
1274 | + // Try to find the ship in the table |
1275 | + if (data_.count(info->serial) == 1) { |
1276 | + const ShipInfo* old_info = data_[info->serial].get(); |
1277 | + if (info->status != old_info->status) { |
1278 | + // The status has changed - we need an update |
1279 | + UI::Table<uintptr_t>::EntryRecord* er = table_.find(info->serial); |
1280 | + set_entry_record(er, *info); |
1281 | + data_[info->serial] = std::move(info); |
1282 | + } |
1283 | + } else { |
1284 | + // This is a new ship or it was filtered away before |
1285 | + UI::Table<uintptr_t>::EntryRecord& er = table_.add(info->serial); |
1286 | + set_entry_record(&er, *info); |
1287 | + data_.insert(std::make_pair(info->serial, std::move(info))); |
1288 | + } |
1289 | + table_.sort(); |
1290 | + update_button_states(); |
1291 | +} |
1292 | + |
1293 | +void SeafaringStatisticsMenu::remove_ship(Widelands::Serial serial) { |
1294 | + if (data_.count(serial) == 1) { |
1295 | + table_.remove_entry(serial); |
1296 | + data_.erase(data_.find(serial)); |
1297 | + if (!table_.empty() && !table_.has_selection()) { |
1298 | + table_.select(0); |
1299 | + } |
1300 | + update_button_states(); |
1301 | + } |
1302 | +} |
1303 | + |
1304 | +void SeafaringStatisticsMenu::update_entry_record(UI::Table<uintptr_t>::EntryRecord& er, |
1305 | + const ShipInfo& info) { |
1306 | + er.set_picture(ColStatus, status_to_image(info.status), status_to_string(info.status)); |
1307 | +} |
1308 | + |
1309 | +void SeafaringStatisticsMenu::selected() { |
1310 | + update_button_states(); |
1311 | +} |
1312 | + |
1313 | +void SeafaringStatisticsMenu::double_clicked() { |
1314 | + if (table_.has_selection()) { |
1315 | + center_view(); |
1316 | + } |
1317 | +} |
1318 | + |
1319 | +void SeafaringStatisticsMenu::update_button_states() { |
1320 | + centerviewbtn_.set_enabled(table_.has_selection()); |
1321 | + openwindowbtn_.set_enabled(table_.has_selection()); |
1322 | + watchbtn_.set_enabled(table_.has_selection()); |
1323 | +} |
1324 | + |
1325 | +bool SeafaringStatisticsMenu::handle_key(bool down, SDL_Keysym code) { |
1326 | + if (down) { |
1327 | + switch (code.sym) { |
1328 | + // Don't forget to change the tooltips if any of these get reassigned |
1329 | + case SDLK_g: |
1330 | + center_view(); |
1331 | + return true; |
1332 | + case SDLK_o: |
1333 | + open_ship_window(); |
1334 | + return true; |
1335 | + case SDLK_w: |
1336 | + watch_ship(); |
1337 | + return true; |
1338 | + case SDLK_0: |
1339 | + if (code.mod & KMOD_ALT) { |
1340 | + filter_ships(ShipFilterStatus::kAll); |
1341 | + return true; |
1342 | + } |
1343 | + return false; |
1344 | + case SDLK_1: |
1345 | + if (code.mod & KMOD_ALT) { |
1346 | + filter_ships(ShipFilterStatus::kIdle); |
1347 | + return true; |
1348 | + } |
1349 | + return false; |
1350 | + case SDLK_2: |
1351 | + if (code.mod & KMOD_ALT) { |
1352 | + filter_ships(ShipFilterStatus::kShipping); |
1353 | + return true; |
1354 | + } |
1355 | + return false; |
1356 | + case SDLK_3: |
1357 | + if (code.mod & KMOD_ALT) { |
1358 | + filter_ships(ShipFilterStatus::kExpeditionWaiting); |
1359 | + return true; |
1360 | + } |
1361 | + return false; |
1362 | + case SDLK_4: |
1363 | + if (code.mod & KMOD_ALT) { |
1364 | + filter_ships(ShipFilterStatus::kExpeditionScouting); |
1365 | + return true; |
1366 | + } |
1367 | + return false; |
1368 | + case SDLK_5: |
1369 | + if (code.mod & KMOD_ALT) { |
1370 | + filter_ships(ShipFilterStatus::kExpeditionPortspaceFound); |
1371 | + return true; |
1372 | + } |
1373 | + return false; |
1374 | + case SDL_SCANCODE_KP_PERIOD: |
1375 | + case SDLK_KP_PERIOD: |
1376 | + if (code.mod & KMOD_NUM) |
1377 | + break; |
1378 | + /* no break */ |
1379 | + default: |
1380 | + break; // not handled |
1381 | + } |
1382 | + } |
1383 | + |
1384 | + return table_.handle_key(down, code); |
1385 | +} |
1386 | + |
1387 | +void SeafaringStatisticsMenu::center_view() { |
1388 | + if (table_.has_selection()) { |
1389 | + Widelands::Ship* ship = serial_to_ship(table_.get_selected()); |
1390 | + iplayer().map_view()->scroll_to_field(ship->get_position(), MapView::Transition::Smooth); |
1391 | + } |
1392 | +} |
1393 | + |
1394 | +void SeafaringStatisticsMenu::watch_ship() { |
1395 | + if (table_.has_selection()) { |
1396 | + Widelands::Ship* ship = serial_to_ship(table_.get_selected()); |
1397 | + WatchWindow* window = show_watch_window(iplayer(), ship->get_position()); |
1398 | + window->follow(ship); |
1399 | + } |
1400 | +} |
1401 | + |
1402 | +void SeafaringStatisticsMenu::open_ship_window() { |
1403 | + if (table_.has_selection()) { |
1404 | + // Move to ship if CTRL is prssed |
1405 | + if (SDL_GetModState() & KMOD_CTRL) { |
1406 | + center_view(); |
1407 | + } |
1408 | + Widelands::Ship* ship = serial_to_ship(table_.get_selected()); |
1409 | + iplayer().show_ship_window(ship); |
1410 | + } |
1411 | +} |
1412 | + |
1413 | +void SeafaringStatisticsMenu::filter_ships(ShipFilterStatus status) { |
1414 | + switch (status) { |
1415 | + case ShipFilterStatus::kExpeditionWaiting: |
1416 | + toggle_filter_ships_button(waiting_btn_, status); |
1417 | + break; |
1418 | + case ShipFilterStatus::kExpeditionScouting: |
1419 | + toggle_filter_ships_button(scouting_btn_, status); |
1420 | + break; |
1421 | + // We're grouping the "colonizing" status with the port space. |
1422 | + case ShipFilterStatus::kExpeditionColonizing: |
1423 | + case ShipFilterStatus::kExpeditionPortspaceFound: |
1424 | + toggle_filter_ships_button(portspace_btn_, status); |
1425 | + break; |
1426 | + case ShipFilterStatus::kShipping: |
1427 | + toggle_filter_ships_button(shipping_btn_, status); |
1428 | + break; |
1429 | + case ShipFilterStatus::kIdle: |
1430 | + toggle_filter_ships_button(idle_btn_, status); |
1431 | + break; |
1432 | + case ShipFilterStatus::kAll: |
1433 | + set_filter_ships_tooltips(); |
1434 | + ship_filter_ = ShipFilterStatus::kAll; |
1435 | + waiting_btn_.set_perm_pressed(false); |
1436 | + scouting_btn_.set_perm_pressed(false); |
1437 | + portspace_btn_.set_perm_pressed(false); |
1438 | + shipping_btn_.set_perm_pressed(false); |
1439 | + idle_btn_.set_perm_pressed(false); |
1440 | + break; |
1441 | + } |
1442 | + fill_table(); |
1443 | +} |
1444 | + |
1445 | +void SeafaringStatisticsMenu::toggle_filter_ships_button(UI::Button& button, |
1446 | + ShipFilterStatus status) { |
1447 | + set_filter_ships_tooltips(); |
1448 | + if (button.style() == UI::Button::Style::kPermpressed) { |
1449 | + button.set_perm_pressed(false); |
1450 | + ship_filter_ = ShipFilterStatus::kAll; |
1451 | + } else { |
1452 | + waiting_btn_.set_perm_pressed(false); |
1453 | + scouting_btn_.set_perm_pressed(false); |
1454 | + portspace_btn_.set_perm_pressed(false); |
1455 | + shipping_btn_.set_perm_pressed(false); |
1456 | + idle_btn_.set_perm_pressed(false); |
1457 | + button.set_perm_pressed(true); |
1458 | + ship_filter_ = status; |
1459 | + |
1460 | + /** TRANSLATORS: %1% is a tooltip, %2% is the corresponding hotkey */ |
1461 | + button.set_tooltip((boost::format(_("%1% (Hotkey: %2%)")) |
1462 | + /** TRANSLATORS: Tooltip in the messages window */ |
1463 | + % _("Show all ships") % pgettext("hotkey", "Alt + 0")) |
1464 | + .str()); |
1465 | + } |
1466 | +} |
1467 | + |
1468 | +void SeafaringStatisticsMenu::set_filter_ships_tooltips() { |
1469 | + |
1470 | + idle_btn_.set_tooltip((boost::format(_("%1% (Hotkey: %2%)")) |
1471 | + /** TRANSLATORS: Tooltip in the messages window */ |
1472 | + % _("Show idle ships") % pgettext("hotkey", "Alt + 1")) |
1473 | + .str()); |
1474 | + shipping_btn_.set_tooltip((boost::format(_("%1% (Hotkey: %2%)")) |
1475 | + /** TRANSLATORS: Tooltip in the messages window */ |
1476 | + % _("Show ships shipping wares and workers") % |
1477 | + pgettext("hotkey", "Alt + 2")) |
1478 | + .str()); |
1479 | + waiting_btn_.set_tooltip((boost::format(_("%1% (Hotkey: %2%)")) |
1480 | + /** TRANSLATORS: Tooltip in the messages window */ |
1481 | + % _("Show waiting expeditions") % pgettext("hotkey", "Alt + 3")) |
1482 | + .str()); |
1483 | + scouting_btn_.set_tooltip((boost::format(_("%1% (Hotkey: %2%)")) |
1484 | + /** TRANSLATORS: Tooltip in the messages window */ |
1485 | + % _("Show scouting expeditions") % pgettext("hotkey", "Alt + 4")) |
1486 | + .str()); |
1487 | + portspace_btn_.set_tooltip( |
1488 | + (boost::format(_("%1% (Hotkey: %2%)")) |
1489 | + /** TRANSLATORS: Tooltip in the messages window */ |
1490 | + % _("Show expeditions that have found a port space or are founding a colony") % |
1491 | + pgettext("hotkey", "Alt + 5")) |
1492 | + .str()); |
1493 | +} |
1494 | + |
1495 | +bool SeafaringStatisticsMenu::satisfies_filter(const ShipInfo& info, ShipFilterStatus filter) { |
1496 | + return filter == info.status || (filter == ShipFilterStatus::kExpeditionPortspaceFound && |
1497 | + info.status == ShipFilterStatus::kExpeditionColonizing); |
1498 | +} |
1499 | + |
1500 | +void SeafaringStatisticsMenu::fill_table() { |
1501 | + const Widelands::Serial last_selection = |
1502 | + table_.has_selection() ? table_.get_selected() : Widelands::INVALID_INDEX; |
1503 | + table_.clear(); |
1504 | + data_.clear(); |
1505 | + // Disable buttons while we update the data |
1506 | + update_button_states(); |
1507 | + for (const auto& serial : iplayer().player().ships()) { |
1508 | + Widelands::Ship* ship = serial_to_ship(serial); |
1509 | + assert(ship != nullptr); |
1510 | + assert(iplayer().get_player() == ship->get_owner()); |
1511 | + std::unique_ptr<const ShipInfo> info = create_shipinfo(*ship); |
1512 | + if (info->status != ShipFilterStatus::kAll) { |
1513 | + if (ship_filter_ == ShipFilterStatus::kAll || satisfies_filter(*info, ship_filter_)) { |
1514 | + UI::Table<uintptr_t const>::EntryRecord& er = |
1515 | + table_.add(serial, serial == last_selection); |
1516 | + set_entry_record(&er, *info); |
1517 | + data_.insert(std::make_pair(serial, std::move(info))); |
1518 | + } |
1519 | + } |
1520 | + } |
1521 | + |
1522 | + if (!table_.empty()) { |
1523 | + table_.sort(); |
1524 | + if (!table_.has_selection()) { |
1525 | + // This will take care of the button state too |
1526 | + table_.select(0); |
1527 | + } |
1528 | + } else { |
1529 | + // Fix column width. Because no entries were added, the table didn't layout itself |
1530 | + table_.layout(); |
1531 | + } |
1532 | +} |
1533 | |
1534 | === added file 'src/wui/seafaring_statistics_menu.h' |
1535 | --- src/wui/seafaring_statistics_menu.h 1970-01-01 00:00:00 +0000 |
1536 | +++ src/wui/seafaring_statistics_menu.h 2018-04-28 09:26:35 +0000 |
1537 | @@ -0,0 +1,159 @@ |
1538 | +/* |
1539 | + * Copyright (C) 2017-2018 by the Widelands Development Team |
1540 | + * |
1541 | + * This program is free software; you can redistribute it and/or |
1542 | + * modify it under the terms of the GNU General Public License |
1543 | + * as published by the Free Software Foundation; either version 2 |
1544 | + * of the License, or (at your option) any later version. |
1545 | + * |
1546 | + * This program is distributed in the hope that it will be useful, |
1547 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1548 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1549 | + * GNU General Public License for more details. |
1550 | + * |
1551 | + * You should have received a copy of the GNU General Public License |
1552 | + * along with this program; if not, write to the Free Software |
1553 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
1554 | + * |
1555 | + */ |
1556 | + |
1557 | +#ifndef WL_WUI_SEAFARING_STATISTICS_MENU_H |
1558 | +#define WL_WUI_SEAFARING_STATISTICS_MENU_H |
1559 | + |
1560 | +#include <memory> |
1561 | +#include <unordered_map> |
1562 | + |
1563 | +#include "base/i18n.h" |
1564 | +#include "logic/map_objects/tribes/ship.h" |
1565 | +#include "notifications/notifications.h" |
1566 | +#include "ui_basic/box.h" |
1567 | +#include "ui_basic/button.h" |
1568 | +#include "ui_basic/table.h" |
1569 | +#include "ui_basic/unique_window.h" |
1570 | + |
1571 | +class InteractivePlayer; |
1572 | + |
1573 | +/// Shows a list of the ships owned by the interactive player with filtering and navigation options. |
1574 | +struct SeafaringStatisticsMenu : public UI::UniqueWindow { |
1575 | + SeafaringStatisticsMenu(InteractivePlayer&, UI::UniqueWindow::Registry&); |
1576 | + |
1577 | +private: |
1578 | + /// For identifying the columns in the table. |
1579 | + enum Cols { ColName, ColStatus }; |
1580 | + /** |
1581 | + * A list of ship status that we can filter for. This differs a bit from that the Widelands::Ship |
1582 | + * class has, so we define our own. |
1583 | + * */ |
1584 | + enum class ShipFilterStatus { |
1585 | + kIdle, |
1586 | + kShipping, |
1587 | + kExpeditionWaiting, |
1588 | + kExpeditionScouting, |
1589 | + kExpeditionPortspaceFound, |
1590 | + kExpeditionColonizing, |
1591 | + kAll |
1592 | + }; |
1593 | + |
1594 | + /// Returns the localized strings that we use to display the 'status' in the table. |
1595 | + const std::string status_to_string(ShipFilterStatus status) const; |
1596 | + /// Returns the icon that we use to represent the 'status' in the table and on the filter |
1597 | + /// buttons. |
1598 | + const Image* status_to_image(ShipFilterStatus status) const; |
1599 | + |
1600 | + /// The dataset that we need to display ships in the table. |
1601 | + struct ShipInfo { |
1602 | + ShipInfo(const std::string& init_name, |
1603 | + const ShipFilterStatus init_status, |
1604 | + const Widelands::Serial init_serial) |
1605 | + : name(init_name), status(init_status), serial(init_serial) { |
1606 | + } |
1607 | + ShipInfo() : ShipInfo("", ShipFilterStatus::kAll, 0) { |
1608 | + } |
1609 | + ShipInfo(const ShipInfo& other) : ShipInfo(other.name, other.status, other.serial) { |
1610 | + } |
1611 | + bool operator==(const ShipInfo& other) const { |
1612 | + return serial == other.serial; |
1613 | + } |
1614 | + bool operator<(const ShipInfo& other) const { |
1615 | + return serial < other.serial; |
1616 | + } |
1617 | + |
1618 | + const std::string name; |
1619 | + ShipFilterStatus status; |
1620 | + const Widelands::Serial serial; |
1621 | + }; |
1622 | + |
1623 | + /// Creates our dataset from a 'ship'. |
1624 | + std::unique_ptr<const ShipInfo> create_shipinfo(const Widelands::Ship& ship) const; |
1625 | + /// Uses the 'serial' to identify and get a ship from the game. |
1626 | + Widelands::Ship* serial_to_ship(Widelands::Serial serial) const; |
1627 | + |
1628 | + /// Convenience function to upcast the panel's parent. |
1629 | + InteractivePlayer& iplayer() const; |
1630 | + |
1631 | + /// A ship was selected, so enable the navigation buttons. |
1632 | + void selected(); |
1633 | + /// A ship was double clicked. Centers main view on ship. |
1634 | + void double_clicked(); |
1635 | + /// Handle filter and navigation hotkeys |
1636 | + bool handle_key(bool down, SDL_Keysym code) override; |
1637 | + |
1638 | + /// Enables the navigation buttons if a ship is selected, disables them otherwise. |
1639 | + void update_button_states(); |
1640 | + /// Center the mapview on the currently selected ship. |
1641 | + void center_view(); |
1642 | + /// Follow the selected ship in a watch window. |
1643 | + void watch_ship(); |
1644 | + /// Open the currently selected ship's window. If CTRL is pressed, center the mapview on it. |
1645 | + void open_ship_window(); |
1646 | + |
1647 | + /** |
1648 | + * Updates the status for the ship. If the ship is new and satisfies the 'ship_filter_', |
1649 | + * adds it to the table and data. If it doesn't satisfy the 'ship_filter_', removes the ship |
1650 | + * instead. |
1651 | + * */ |
1652 | + void update_ship(const Widelands::Ship&); |
1653 | + /// If we listed the ship, remove it from table and data. |
1654 | + void remove_ship(Widelands::Serial serial); |
1655 | + /// Sets the contents for the entry record in the table. |
1656 | + void set_entry_record(UI::Table<uintptr_t>::EntryRecord*, const ShipInfo& info); |
1657 | + /// Updates the ship status display in the table. |
1658 | + void update_entry_record(UI::Table<uintptr_t>::EntryRecord& er, const ShipInfo&); |
1659 | + /// Rebuilds data and table with all ships that satisfy the current 'ship_filter_'. |
1660 | + void fill_table(); |
1661 | + |
1662 | + /// Show only the ships that have the given status. Toggle the appropriate buttons. |
1663 | + void filter_ships(ShipFilterStatus status); |
1664 | + /// Helper for filter_ships |
1665 | + void toggle_filter_ships_button(UI::Button&, ShipFilterStatus); |
1666 | + /// Helper for filter_ships |
1667 | + void set_filter_ships_tooltips(); |
1668 | + |
1669 | + /// We group colonizing status with port space found. Anything else needs to have an identical |
1670 | + /// status. |
1671 | + bool satisfies_filter(const ShipInfo& info, ShipFilterStatus filter); |
1672 | + |
1673 | + const Image* colony_icon_; |
1674 | + UI::Box main_box_; |
1675 | + // Buttons for ship states |
1676 | + UI::Box filter_box_; |
1677 | + UI::Button idle_btn_; |
1678 | + UI::Button waiting_btn_; |
1679 | + UI::Button scouting_btn_; |
1680 | + UI::Button portspace_btn_; |
1681 | + UI::Button shipping_btn_; |
1682 | + ShipFilterStatus ship_filter_; |
1683 | + // Navigation buttons |
1684 | + UI::Box navigation_box_; |
1685 | + UI::Button watchbtn_; |
1686 | + UI::Button openwindowbtn_; |
1687 | + UI::Button centerviewbtn_; |
1688 | + |
1689 | + // Data |
1690 | + UI::Table<uintptr_t> table_; |
1691 | + std::unordered_map<Widelands::Serial, std::unique_ptr<const ShipInfo>> data_; |
1692 | + |
1693 | + std::unique_ptr<Notifications::Subscriber<Widelands::NoteShip>> shipnotes_subscriber_; |
1694 | +}; |
1695 | + |
1696 | +#endif // end of include guard: WL_WUI_SEAFARING_STATISTICS_MENU_H |
1697 | |
1698 | === modified file 'src/wui/shipwindow.cc' |
1699 | --- src/wui/shipwindow.cc 2018-04-27 16:46:34 +0000 |
1700 | +++ src/wui/shipwindow.cc 2018-04-28 09:26:35 +0000 |
1701 | @@ -46,7 +46,7 @@ |
1702 | static const char pic_scout_e[] = "images/wui/ship/ship_scout_e.png"; |
1703 | static const char pic_scout_sw[] = "images/wui/ship/ship_scout_sw.png"; |
1704 | static const char pic_scout_se[] = "images/wui/ship/ship_scout_se.png"; |
1705 | -static const char pic_construct_port[] = "images/wui/editor/fsel_editor_set_port_space.png"; |
1706 | +static const char pic_construct_port[] = "images/wui/ship/ship_construct_port_space.png"; |
1707 | |
1708 | constexpr int kPadding = 5; |
1709 | } // namespace |
1710 | @@ -158,38 +158,29 @@ |
1711 | move_out_of_the_way(); |
1712 | warp_mouse_to_fastclick_panel(); |
1713 | |
1714 | - shipnotes_subscriber_ = Notifications::subscribe<Widelands::NoteShipWindow>( |
1715 | - [this](const Widelands::NoteShipWindow& note) { |
1716 | - if (note.serial == ship_.serial()) { |
1717 | - switch (note.action) { |
1718 | - // Unable to cancel the expedition |
1719 | - case Widelands::NoteShipWindow::Action::kNoPortLeft: { |
1720 | - Widelands::Ship* note_ship = ship_.get(igbase_.egbase()); |
1721 | - if (note_ship == nullptr) { |
1722 | - return; |
1723 | - } |
1724 | - if (upcast(InteractiveGameBase, igamebase, |
1725 | - note_ship->get_owner()->egbase().get_ibase())) { |
1726 | - if (igamebase->can_act(note_ship->get_owner()->player_number())) { |
1727 | - UI::WLMessageBox messagebox( |
1728 | - get_parent(), |
1729 | - /** TRANSLATORS: Window label when an expedition can't be canceled */ |
1730 | - _("Cancel Expedition"), _("This expedition can’t be canceled, because the " |
1731 | - "ship has no port to return to."), |
1732 | - UI::WLMessageBox::MBoxType::kOk); |
1733 | - messagebox.run<UI::Panel::Returncodes>(); |
1734 | - } |
1735 | - } |
1736 | - } break; |
1737 | - // The ship is no more |
1738 | - case Widelands::NoteShipWindow::Action::kClose: |
1739 | - // Stop this from thinking to avoid segfaults |
1740 | - set_thinks(false); |
1741 | - die(); |
1742 | - break; |
1743 | - } |
1744 | - } |
1745 | - }); |
1746 | + shipnotes_subscriber_ = Notifications::subscribe<Widelands::NoteShip>([this]( |
1747 | + const Widelands::NoteShip& note) { |
1748 | + if (note.ship->serial() == ship_.serial()) { |
1749 | + switch (note.action) { |
1750 | + // Unable to cancel the expedition |
1751 | + case Widelands::NoteShip::Action::kNoPortLeft: |
1752 | + no_port_error_message(); |
1753 | + break; |
1754 | + // The ship is no more |
1755 | + case Widelands::NoteShip::Action::kLost: |
1756 | + // Stop this from thinking to avoid segfaults |
1757 | + set_thinks(false); |
1758 | + die(); |
1759 | + break; |
1760 | + // If the ship state has changed, e.g. expedition started or scouting direction changed, |
1761 | + // think() will take care of it. |
1762 | + case Widelands::NoteShip::Action::kDestinationChanged: |
1763 | + case Widelands::NoteShip::Action::kWaitingForCommand: |
1764 | + case Widelands::NoteShip::Action::kGained: |
1765 | + break; |
1766 | + } |
1767 | + } |
1768 | + }); |
1769 | |
1770 | // Init button visibility |
1771 | navigation_box_height_ = navigation_box_.get_h(); |
1772 | @@ -215,6 +206,24 @@ |
1773 | } |
1774 | } |
1775 | |
1776 | +void ShipWindow::no_port_error_message() { |
1777 | + Widelands::Ship* ship = ship_.get(igbase_.egbase()); |
1778 | + if (ship == nullptr) { |
1779 | + return; |
1780 | + } |
1781 | + if (upcast(InteractiveGameBase, igamebase, ship->get_owner()->egbase().get_ibase())) { |
1782 | + if (igamebase->can_act(ship->owner().player_number())) { |
1783 | + UI::WLMessageBox messagebox( |
1784 | + get_parent(), |
1785 | + /** TRANSLATORS: Window label when an expedition can't be canceled */ |
1786 | + _("Cancel Expedition"), _("This expedition can’t be canceled, because the " |
1787 | + "ship has no port to return to."), |
1788 | + UI::WLMessageBox::MBoxType::kOk); |
1789 | + messagebox.run<UI::Panel::Returncodes>(); |
1790 | + } |
1791 | + } |
1792 | +} |
1793 | + |
1794 | void ShipWindow::think() { |
1795 | UI::Window::think(); |
1796 | Widelands::Ship* ship = ship_.get(igbase_.egbase()); |
1797 | |
1798 | === modified file 'src/wui/shipwindow.h' |
1799 | --- src/wui/shipwindow.h 2018-04-17 03:09:29 +0000 |
1800 | +++ src/wui/shipwindow.h 2018-04-28 09:26:35 +0000 |
1801 | @@ -47,6 +47,7 @@ |
1802 | const std::string& picname, |
1803 | boost::function<void()> callback); |
1804 | void set_button_visibility(); |
1805 | + void no_port_error_message(); |
1806 | |
1807 | void act_goto(); |
1808 | void act_destination(); |
1809 | @@ -74,7 +75,7 @@ |
1810 | UI::Button* btn_construct_port_; |
1811 | ItemWaresDisplay* display_; |
1812 | int navigation_box_height_; |
1813 | - std::unique_ptr<Notifications::Subscriber<Widelands::NoteShipWindow>> shipnotes_subscriber_; |
1814 | + std::unique_ptr<Notifications::Subscriber<Widelands::NoteShip>> shipnotes_subscriber_; |
1815 | DISALLOW_COPY_AND_ASSIGN(ShipWindow); |
1816 | }; |
1817 | |
1818 | |
1819 | === modified file 'src/wui/watchwindow.cc' |
1820 | --- src/wui/watchwindow.cc 2018-04-07 16:59:00 +0000 |
1821 | +++ src/wui/watchwindow.cc 2018-04-28 09:26:35 +0000 |
1822 | @@ -31,66 +31,20 @@ |
1823 | #include "logic/map_objects/bob.h" |
1824 | #include "logic/player.h" |
1825 | #include "profile/profile.h" |
1826 | -#include "ui_basic/button.h" |
1827 | -#include "ui_basic/window.h" |
1828 | #include "wui/interactive_gamebase.h" |
1829 | #include "wui/interactive_player.h" |
1830 | -#include "wui/mapview.h" |
1831 | #include "wui/mapviewpixelconstants.h" |
1832 | #include "wui/mapviewpixelfunctions.h" |
1833 | |
1834 | -#define NUM_VIEWS 5 |
1835 | #define REFRESH_TIME 5000 |
1836 | |
1837 | // Holds information for a view |
1838 | -struct WatchWindowView { |
1839 | - MapView::View view; |
1840 | - Widelands::ObjectPointer tracking; // if non-null, we're tracking a Bob |
1841 | -}; |
1842 | - |
1843 | -struct WatchWindow : public UI::Window { |
1844 | - WatchWindow(InteractiveGameBase& parent, |
1845 | - int32_t x, |
1846 | - int32_t y, |
1847 | - uint32_t w, |
1848 | - uint32_t h, |
1849 | - bool single_window_ = false); |
1850 | - ~WatchWindow() override; |
1851 | - |
1852 | - Widelands::Game& game() const { |
1853 | - return parent_.game(); |
1854 | - } |
1855 | - |
1856 | - boost::signals2::signal<void(Vector2f)> warp_mainview; |
1857 | - |
1858 | - void add_view(Widelands::Coords); |
1859 | - void next_view(); |
1860 | - void save_coords(); |
1861 | - void close_cur_view(); |
1862 | - void toggle_buttons(); |
1863 | - |
1864 | -protected: |
1865 | - void think() override; |
1866 | - void stop_tracking_by_drag(); |
1867 | - void draw(RenderTarget&) override; |
1868 | - |
1869 | -private: |
1870 | - void do_follow(); |
1871 | - void do_goto(); |
1872 | - void view_button_clicked(uint8_t index); |
1873 | - void set_current_view(uint8_t idx, bool save_previous = true); |
1874 | - |
1875 | - InteractiveGameBase& parent_; |
1876 | - MapView map_view_; |
1877 | - uint32_t last_visit_; |
1878 | - bool single_window_; |
1879 | - uint8_t cur_index_; |
1880 | - UI::Button* view_btns_[NUM_VIEWS]; |
1881 | - std::vector<WatchWindowView> views_; |
1882 | -}; |
1883 | - |
1884 | static WatchWindow* g_watch_window = nullptr; |
1885 | |
1886 | +Widelands::Game& WatchWindow::game() const { |
1887 | + return parent_.game(); |
1888 | +} |
1889 | + |
1890 | WatchWindow::WatchWindow(InteractiveGameBase& parent, |
1891 | int32_t const x, |
1892 | int32_t const y, |
1893 | @@ -115,7 +69,7 @@ |
1894 | gotobtn->sigclicked.connect(boost::bind(&WatchWindow::do_goto, this)); |
1895 | |
1896 | if (init_single_window) { |
1897 | - for (uint8_t i = 0; i < NUM_VIEWS; ++i) { |
1898 | + for (uint8_t i = 0; i < kViews; ++i) { |
1899 | view_btns_[i] = new UI::Button(this, "view", 74 + (17 * i), 200 - 34, 17, 34, |
1900 | g_gr->images().get("images/ui_basic/but0.png"), "-"); |
1901 | view_btns_[i]->sigclicked.connect(boost::bind(&WatchWindow::view_button_clicked, this, i)); |
1902 | @@ -154,9 +108,9 @@ |
1903 | * This also resets the view cycling timer. |
1904 | */ |
1905 | void WatchWindow::add_view(Widelands::Coords const coords) { |
1906 | - if (views_.size() >= NUM_VIEWS) |
1907 | + if (views_.size() >= kViews) |
1908 | return; |
1909 | - WatchWindowView view; |
1910 | + WatchWindow::View view; |
1911 | |
1912 | map_view_.scroll_to_field(coords, MapView::Transition::Jump); |
1913 | |
1914 | @@ -183,7 +137,7 @@ |
1915 | |
1916 | // Enables/Disables buttons for views_ |
1917 | void WatchWindow::toggle_buttons() { |
1918 | - for (uint32_t i = 0; i < NUM_VIEWS; ++i) { |
1919 | + for (uint32_t i = 0; i < kViews; ++i) { |
1920 | if (i < views_.size()) { |
1921 | view_btns_[i]->set_title(std::to_string(i + 1)); |
1922 | view_btns_[i]->set_enabled(true); |
1923 | @@ -256,6 +210,13 @@ |
1924 | } |
1925 | |
1926 | /** |
1927 | + * Track the specified bob. |
1928 | + */ |
1929 | +void WatchWindow::follow(Widelands::Bob* bob) { |
1930 | + views_[cur_index_].tracking = bob; |
1931 | +} |
1932 | + |
1933 | +/** |
1934 | * Called when the user clicks the "follow" button. |
1935 | * |
1936 | * If we are currently tracking a bob, stop tracking. |
1937 | @@ -299,14 +260,14 @@ |
1938 | closest_dist = dist; |
1939 | } |
1940 | } |
1941 | - views_[cur_index_].tracking = closest; |
1942 | + follow(closest); |
1943 | } |
1944 | } |
1945 | |
1946 | /** |
1947 | * Called when the "go to" button is clicked. |
1948 | * |
1949 | - * Cause the main map_view_ to jump to our current position. |
1950 | + * Cause the main mapview_ to jump to our current position. |
1951 | */ |
1952 | void WatchWindow::do_goto() { |
1953 | warp_mainview(map_view_.view_area().rect().center()); |
1954 | @@ -344,14 +305,16 @@ |
1955 | Open a watch window. |
1956 | =============== |
1957 | */ |
1958 | -void show_watch_window(InteractiveGameBase& parent, const Widelands::Coords& coords) { |
1959 | +WatchWindow* show_watch_window(InteractiveGameBase& parent, const Widelands::Coords& coords) { |
1960 | if (g_options.pull_section("global").get_bool("single_watchwin", false)) { |
1961 | if (!g_watch_window) { |
1962 | g_watch_window = new WatchWindow(parent, 250, 150, 200, 200, true); |
1963 | } |
1964 | g_watch_window->add_view(coords); |
1965 | + return g_watch_window; |
1966 | } else { |
1967 | auto* window = new WatchWindow(parent, 250, 150, 200, 200, false); |
1968 | window->add_view(coords); |
1969 | + return window; |
1970 | } |
1971 | } |
1972 | |
1973 | === modified file 'src/wui/watchwindow.h' |
1974 | --- src/wui/watchwindow.h 2016-08-04 15:49:05 +0000 |
1975 | +++ src/wui/watchwindow.h 2018-04-28 09:26:35 +0000 |
1976 | @@ -22,8 +22,62 @@ |
1977 | |
1978 | #include "logic/widelands_geometry.h" |
1979 | |
1980 | +#include "ui_basic/button.h" |
1981 | +#include "ui_basic/window.h" |
1982 | +#include "wui/mapview.h" |
1983 | + |
1984 | class InteractiveGameBase; |
1985 | - |
1986 | -void show_watch_window(InteractiveGameBase&, const Widelands::Coords&); |
1987 | +namespace Widelands { |
1988 | +class Game; |
1989 | +} |
1990 | + |
1991 | +struct WatchWindow : public UI::Window { |
1992 | + WatchWindow(InteractiveGameBase& parent, |
1993 | + int32_t x, |
1994 | + int32_t y, |
1995 | + uint32_t w, |
1996 | + uint32_t h, |
1997 | + bool single_window_ = false); |
1998 | + ~WatchWindow(); |
1999 | + |
2000 | + boost::signals2::signal<void(Vector2f)> warp_mainview; |
2001 | + |
2002 | + void add_view(Widelands::Coords); |
2003 | + void follow(Widelands::Bob* bob); |
2004 | + |
2005 | +private: |
2006 | + static constexpr size_t kViews = 5; |
2007 | + |
2008 | + // Holds information for a view |
2009 | + struct View { |
2010 | + MapView::View view; |
2011 | + Widelands::ObjectPointer tracking; // if non-null, we're tracking a Bob |
2012 | + }; |
2013 | + |
2014 | + Widelands::Game& game() const; |
2015 | + |
2016 | + void think() override; |
2017 | + void stop_tracking_by_drag(); |
2018 | + void draw(RenderTarget&) override; |
2019 | + void save_coords(); |
2020 | + void next_view(); |
2021 | + void close_cur_view(); |
2022 | + void toggle_buttons(); |
2023 | + |
2024 | + void do_follow(); |
2025 | + void do_goto(); |
2026 | + void view_button_clicked(uint8_t index); |
2027 | + void set_current_view(uint8_t idx, bool save_previous = true); |
2028 | + |
2029 | + InteractiveGameBase& parent_; |
2030 | + MapView map_view_; |
2031 | + uint32_t last_visit_; |
2032 | + bool single_window_; |
2033 | + uint8_t cur_index_; |
2034 | + UI::Button* view_btns_[kViews]; |
2035 | + std::vector<WatchWindow::View> views_; |
2036 | +}; |
2037 | + |
2038 | +WatchWindow* show_watch_window(InteractiveGameBase&, const Widelands::Coords&); |
2039 | |
2040 | #endif // end of include guard: WL_WUI_WATCHWINDOW_H |
Continuous integration builds have changed state:
Travis build 3386. State: failed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 367231513. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ bug_1358880_ ship_statistics -3192.
Appveyor build 3192. State: success. Details: https:/