1 #include "DesignWnd.h"
2
3 #include "ClientUI.h"
4 #include "CUIWnd.h"
5 #include "CUIControls.h"
6 #include "QueueListBox.h"
7 #include "EncyclopediaDetailPanel.h"
8 #include "IconTextBrowseWnd.h"
9 #include "Sound.h"
10 #include "TextBrowseWnd.h"
11 #include "../parse/Parse.h"
12 #include "../util/i18n.h"
13 #include "../util/Logger.h"
14 #include "../util/Order.h"
15 #include "../util/OptionsDB.h"
16 #include "../util/ScopedTimer.h"
17 #include "../util/Directories.h"
18 #include "../Empire/Empire.h"
19 #include "../client/human/HumanClientApp.h"
20 #include "../universe/ConditionAll.h"
21 #include "../universe/UniverseObject.h"
22 #include "../universe/ShipDesign.h"
23 #include "../universe/ShipPart.h"
24 #include "../universe/ShipHull.h"
25 #include "../universe/Enums.h"
26
27 #include <GG/StaticGraphic.h>
28 #include <GG/TabWnd.h>
29
30 #include <boost/cast.hpp>
31 #include <boost/algorithm/string/replace.hpp>
32 #include <boost/filesystem/operations.hpp>
33 #include <boost/filesystem/fstream.hpp>
34 #include <boost/uuid/random_generator.hpp>
35 #include <boost/uuid/uuid_io.hpp>
36 #include <boost/uuid/random_generator.hpp>
37
38 #include <algorithm>
39 #include <iterator>
40 #include <unordered_set>
41 #include <unordered_map>
42 #include <functional>
43
44 FO_COMMON_API extern const int INVALID_DESIGN_ID;
45
46 struct Availability {
47 // Declaring an enum inside a struct makes the syntax when using the enum
48 // with tuples simpler, without polluting the global namespace with 3
49 // generic names.
50 enum Enum {
51 Obsolete, // A design/part is researched/known by the player has marked it obsolete
52 Available, // A design/part is researched/known and currently available
53 Future // A design/part is unresearched and hence not available
54 };
55 };
56
57 namespace {
58 const std::string PART_CONTROL_DROP_TYPE_STRING = "Part Control";
59 const std::string HULL_PARTS_ROW_DROP_TYPE_STRING = "Hull and Parts Row";
60 const std::string COMPLETE_DESIGN_ROW_DROP_STRING = "Complete Design Row";
61 const std::string SAVED_DESIGN_ROW_DROP_STRING = "Saved Design Row";
62 const std::string EMPTY_STRING = "";
63 const std::string DES_PEDIA_WND_NAME = "design.pedia";
64 const std::string DES_MAIN_WND_NAME = "design.edit";
65 const std::string DES_BASE_SELECTOR_WND_NAME = "design.selector";
66 const std::string DES_PART_PALETTE_WND_NAME = "design.parts";
67 const GG::Y BASES_LIST_BOX_ROW_HEIGHT(100);
68 const GG::X PART_CONTROL_WIDTH(54);
69 const GG::Y PART_CONTROL_HEIGHT(54);
70 const GG::X SLOT_CONTROL_WIDTH(60);
71 const GG::Y SLOT_CONTROL_HEIGHT(60);
72 const GG::Pt PALETTE_MIN_SIZE{GG::X{450}, GG::Y{400}};
73 const GG::Pt MAIN_PANEL_MIN_SIZE{GG::X{400}, GG::Y{160}};
74 const GG::Pt BASES_MIN_SIZE{GG::X{160}, GG::Y{160}};
75 const int PAD(3);
76
77 /** Returns texture with which to render a SlotControl, depending on \a slot_type. */
SlotBackgroundTexture(ShipSlotType slot_type)78 std::shared_ptr<GG::Texture> SlotBackgroundTexture(ShipSlotType slot_type) {
79 if (slot_type == SL_EXTERNAL)
80 return ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "ship_parts" / "external_slot.png", true);
81 else if (slot_type == SL_INTERNAL)
82 return ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "ship_parts" / "internal_slot.png", true);
83 else if (slot_type == SL_CORE)
84 return ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "ship_parts" / "core_slot.png", true);
85 else
86 return ClientUI::GetTexture(ClientUI::ArtDir() / "misc" / "missing.png", true);
87 }
88
89 //! Returns background texture with which to render a PartControl,
90 //! depending on the types of slot that the indicated @a part can be put
91 //! into.
PartBackgroundTexture(const ShipPart * part)92 std::shared_ptr<GG::Texture> PartBackgroundTexture(const ShipPart* part) {
93 if (part) {
94 bool ex = part->CanMountInSlotType(SL_EXTERNAL);
95 bool in = part->CanMountInSlotType(SL_INTERNAL);
96 bool co = part->CanMountInSlotType(SL_CORE);
97
98 if (ex && in)
99 return ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "ship_parts" / "independent_part.png", true);
100 else if (ex)
101 return ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "ship_parts" / "external_part.png", true);
102 else if (in)
103 return ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "ship_parts" / "internal_part.png", true);
104 else if (co)
105 return ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "ship_parts" / "core_part.png", true);
106 }
107 return ClientUI::GetTexture(ClientUI::ArtDir() / "misc" / "missing.png", true);
108 }
109
GetMainStat(const ShipPart * part)110 float GetMainStat(const ShipPart* part) {
111 if (!part)
112 return 0.0f;
113 switch (part->Class()) {
114 case PC_DIRECT_WEAPON:
115 case PC_FIGHTER_BAY:
116 case PC_FIGHTER_HANGAR:
117 case PC_SHIELD:
118 case PC_DETECTION:
119 case PC_STEALTH:
120 case PC_FUEL:
121 case PC_COLONY:
122 case PC_ARMOUR:
123 case PC_SPEED:
124 case PC_TROOPS:
125 case PC_RESEARCH:
126 case PC_INDUSTRY:
127 case PC_TRADE:
128 return part->Capacity();
129 break;
130 case PC_GENERAL:
131 case PC_BOMBARD:
132 case PC_PRODUCTION_LOCATION:
133 default:
134 return 0.0f;
135 }
136 }
137
138 typedef std::map<std::pair<ShipPartClass, ShipSlotType>, std::vector<const ShipPart*>> PartGroupsType;
139
140 const std::string DESIGN_FILENAME_PREFIX = "ShipDesign-";
141 const std::string DESIGN_FILENAME_EXTENSION = ".focs.txt";
142 const std::string DESIGN_MANIFEST_PREFIX = "ShipDesignOrdering";
143 const std::string UNABLE_TO_OPEN_FILE = "Unable to open file";
SavedDesignsDir()144 boost::filesystem::path SavedDesignsDir() { return GetUserDataDir() / "shipdesigns/"; }
145
ReportFileError(const boost::filesystem::path & file)146 void ReportFileError(const boost::filesystem::path& file) {
147 std::string msg = boost::io::str(FlexibleFormat(UserString("ERROR_UNABLE_TO_WRITE_FILE")) % file);
148 ErrorLogger() << msg;
149 ClientUI::MessageBox(msg, true);
150 }
151
WriteToFile(const boost::filesystem::path & file,const std::string & ss)152 void WriteToFile(const boost::filesystem::path& file, const std::string& ss) {
153 try {
154 boost::filesystem::ofstream ofs(file);
155 if (!ofs)
156 return ReportFileError(file);
157
158 ofs << ss;
159 TraceLogger() << "Wrote to " << PathToString(file);
160
161 } catch (const boost::filesystem::filesystem_error& e) {
162 ErrorLogger() << "Error writing to file. Exception: " << ": " << e.what();
163 ReportFileError(file);
164 }
165 }
166
GetDesignsDir()167 boost::filesystem::path GetDesignsDir() {
168 // ensure directory present
169 boost::filesystem::path designs_dir_path(SavedDesignsDir());
170 if (!exists(designs_dir_path))
171 boost::filesystem::create_directories(designs_dir_path);
172 return designs_dir_path;
173 }
174
CreateSaveFileNameForDesign(const ShipDesign & design)175 boost::filesystem::path CreateSaveFileNameForDesign(const ShipDesign& design) {
176 boost::filesystem::path designs_dir_path = GetDesignsDir();
177
178 // Since there is no easy way to guarantee that an arbitrary design name with possibly
179 // embedded decorator code is a safe file name, use the UUID. The users will never interact
180 // with this filename.
181 std::string file_name =
182 DESIGN_FILENAME_PREFIX + boost::uuids::to_string(design.UUID()) + DESIGN_FILENAME_EXTENSION;
183
184 return boost::filesystem::absolute(PathToString(designs_dir_path / file_name));
185 }
186
187
188 /** DisplayedShipDesignManager allows for the storage and manipulation of an
189 * ordered list of design ids that are used to order the display of
190 * ShipDesigns in the DesignWnd and the ProductionWnd. */
191 class DisplayedShipDesignManager : public ShipDesignManager::Designs {
192 public:
DisplayedShipDesignManager()193 DisplayedShipDesignManager()
194 {}
195
196 /** Return non-obsolete available ordered ids. */
197 std::vector<int> OrderedIDs() const override;
198
199 /** Return all ids including obsoleted designs. */
200 std::vector<int> AllOrderedIDs() const;
201
202 /** Return non-obsolete available hulls. */
203 std::vector<std::string> OrderedHulls() const;
204
205 template <typename T>
206 void InsertOrderedIDs(const T& new_order);
207
208 void InsertBefore(const int id, const int next_id);
209 bool MoveBefore(const int moved_id, const int next_id);
210 void Remove(const int id);
211
212 void InsertHullBefore(const std::string& id, const std::string& next_id = "");
213
214 bool IsKnown(const int id) const;
215
216 /** Return true if design \p id is obsolete or boost::none if \p id is not in
217 the manager. */
218 boost::optional<bool> IsObsolete(const int id) const;
219 /** Return UI event number that obsoletes \p hull if it is obsolete. */
220 boost::optional<int> IsHullObsolete(const std::string& hull) const;
221 /** Return UI event number that obsoletes \p part if it is obsolete. */
222 boost::optional<int> IsPartObsolete(const std::string& part) const;
223
224 private:
225 /* Increment and return the obsolete ui event counter. */
226 int NextUIObsoleteEvent();
227
228 public:
229 /** If \p id is in manager, set \p id's obsolescence to \p obsolete. */
230 void SetObsolete(const int id, const bool obsolete);
231 /** Set \p hull's obsolescence to \p obsolete. */
232 void SetHullObsolete(const std::string& hull, const bool obsolete);
233 /** Set \p part's obsolescence to \p obsolete. */
234 void SetPartObsolete(const std::string& part, const bool obsolete);
235
236 void Load(const int obsolete_ui_event_count,
237 const std::vector<std::pair<int, boost::optional<std::pair<bool, int>>>>& design_ids_and_obsoletes,
238 const std::vector<std::pair<std::string, std::pair<bool, int>>>& hulls_and_obsoletes,
239 const std::unordered_map<std::string, int>& obsolete_parts);
240
241 /** Save modifies each of its parameters to store obsolete and
242 ordering data in the UI save data.*/
243 void Save(int& obsolete_ui_event_count,
244 std::vector<std::pair<int, boost::optional<std::pair<bool, int>>>>& design_ids_and_obsoletes,
245 std::vector<std::pair<std::string, std::pair<bool, int>>>& hulls_and_obsoletes,
246 std::unordered_map<std::string, int>& obsolete_parts);
247
248 private:
249 std::list<int> m_ordered_design_ids;
250 std::list<std::string> m_ordered_hulls;
251
252 // A counter to track the number of times a player has (un)obsoleted a part,
253 // hull or design. This allows the ui to determine if a design was obsoleted
254 // before/after its hull/parts.
255 int m_obsolete_ui_event_count = 1;
256
257 // An index from the id to the obsolescence state and the location in the
258 // m_ordered_design_ids list.
259 // For the state information ((false, ui_event), (true, ui_event), none)
260 // correspond to (not obsolete, obsolete and defer to parts and hull obsolescence)
261 std::unordered_map<int,
262 std::pair<boost::optional<std::pair<bool, int>>,
263 std::list<int>::const_iterator>> m_id_to_obsolete_and_loc;
264
265 // An index from the hull name to the obsolescence state and the location in the
266 // m_ordered_hull_ids list.
267 std::unordered_map<std::string,
268 std::pair<std::pair<bool, int>,
269 std::list<std::string>::const_iterator>> m_hull_to_obsolete_and_loc;
270
271 // A map from obsolete part name to the UI event count that changed it.
272 std::unordered_map<std::string, int> m_obsolete_parts;
273 };
274
275 class SavedDesignsManager : public ShipDesignManager::Designs {
276 public:
SavedDesignsManager()277 SavedDesignsManager()
278 {}
279
280 const std::list<boost::uuids::uuid>& OrderedDesignUUIDs() const;
281 std::vector<int> OrderedIDs() const override;
282
283 void StartParsingDesignsFromFileSystem(bool is_new_game);
284 void CheckPendingDesigns() const;
285
286 const ShipDesign* GetDesign(const boost::uuids::uuid& uuid) const;
287
288 void SaveManifest();
289
290 std::list<boost::uuids::uuid>::const_iterator
291 InsertBefore(const ShipDesign& design, std::list<boost::uuids::uuid>::const_iterator next);
292 bool MoveBefore(const boost::uuids::uuid& moved_uuid, const boost::uuids::uuid& next_uuid);
293 void Erase(const boost::uuids::uuid& erased_uuid);
294
295 private:
296 /** Save the design with the original filename or throw out_of_range. */
297 void SaveDesign(const boost::uuids::uuid &uuid);
298
299 /** SaveDesignConst allows CheckPendingDesigns to correct the designs
300 in the saved directory.*/
301 void SaveDesignConst(const boost::uuids::uuid &uuid) const;
302
303 /** A const version of SaveManifest to allow CheckPendingDesigns to
304 correct and save the loaded designs. */
305 void SaveManifestConst() const;
306
307 /** Future ship design type being parsed by parser. mutable so that it can
308 be assigned to m_saved_designs when completed.*/
309 mutable boost::optional<std::future<PredefinedShipDesignManager::ParsedShipDesignsType>>
310 m_pending_designs = boost::none;
311
312 mutable std::list<boost::uuids::uuid> m_ordered_uuids;
313 /// Saved designs with filename
314 mutable std::unordered_map<boost::uuids::uuid,
315 std::pair<std::unique_ptr<ShipDesign>,
316 boost::filesystem::path>,
317 boost::hash<boost::uuids::uuid>> m_saved_designs;
318
319 mutable bool m_is_new_game = false;
320 };
321
322
323 //////////////////////////////////////////////////
324 // Common Reused Actions //
325 //////////////////////////////////////////////////
GetDisplayedDesignsManager()326 DisplayedShipDesignManager& GetDisplayedDesignsManager() {
327 auto designs = dynamic_cast<DisplayedShipDesignManager*>(
328 ClientUI::GetClientUI()->GetShipDesignManager()->DisplayedDesigns());
329 return *designs;
330 }
331
GetSavedDesignsManager()332 SavedDesignsManager& GetSavedDesignsManager() {
333 auto designs = dynamic_cast<SavedDesignsManager*>(
334 ClientUI::GetClientUI()->GetShipDesignManager()->SavedDesigns());
335 return *designs;
336 }
337
CanAddDesignToDisplayedDesigns(const ShipDesign * design)338 bool CanAddDesignToDisplayedDesigns(const ShipDesign* design) {
339 if (!design)
340 return false;
341
342 const auto& current_ids = GetDisplayedDesignsManager().AllOrderedIDs();
343 const auto is_same_design = [&design](const int id) {
344 auto current_design = GetShipDesign(id);
345 return current_design && *current_design == *design;
346 };
347
348 return std::none_of(current_ids.begin(), current_ids.end(), is_same_design);
349 }
350
351 /** Add \p design to the \p is_front of \p empire_id's list of current designs. */
AddSavedDesignToDisplayedDesigns(const boost::uuids::uuid & uuid,int empire_id,bool is_front=true)352 void AddSavedDesignToDisplayedDesigns(const boost::uuids::uuid& uuid, int empire_id,
353 bool is_front = true)
354 {
355 const auto empire = GetEmpire(empire_id);
356 if (!empire) {
357 ErrorLogger() << "AddSavedDesignsToDisplayedDesigns HumanClient Does Not Control an Empire";
358 return;
359 }
360
361 const auto design = GetSavedDesignsManager().GetDesign(uuid);
362 if (!design) {
363 ErrorLogger() << "AddSavedDesignsToDisplayedDesigns missing expected uuid " << uuid;
364 return;
365 }
366
367 // Is it the same as an existing design
368 if (!CanAddDesignToDisplayedDesigns(design)) {
369 DebugLogger() << "AddSavedDesignsToDisplayedDesigns saved design already present: "
370 << design->Name();
371 return;
372 }
373
374 TraceLogger() << "Add saved design " << design->Name() << " to current designs.";
375
376 // Give it a new UUID so that the empire design is distinct.
377 auto new_current_design = *design;
378 new_current_design.SetUUID(boost::uuids::random_generator()());
379
380 auto order = std::make_shared<ShipDesignOrder>(empire_id, new_current_design);
381 HumanClientApp::GetApp()->Orders().IssueOrder(order);
382
383 auto& current_manager = GetDisplayedDesignsManager();
384 const auto& all_ids = current_manager.AllOrderedIDs();
385 const int before_id = (all_ids.empty() || !is_front) ? INVALID_OBJECT_ID : *all_ids.begin() ;
386 current_manager.InsertBefore(order->DesignID(), before_id);
387 }
388
389 /** Set whether a currently known design is obsolete or not. Not obsolete
390 * means that it is known to the empire (on the server) and will appear in
391 * the production list. Obsolete means that it will only appear in the
392 * list of obsolete designs. */
SetObsoleteInDisplayedDesigns(const int design_id,bool obsolete)393 void SetObsoleteInDisplayedDesigns(const int design_id, bool obsolete) {
394 auto& manager = GetDisplayedDesignsManager();
395
396 if (!manager.IsKnown(design_id)) {
397 WarnLogger() << "Attempted to toggle obsolete state of design id "
398 << design_id << " which is unknown to the empire";
399 return;
400 }
401
402 const auto empire_id = HumanClientApp::GetApp()->EmpireID();
403
404 manager.SetObsolete(design_id, obsolete);
405
406 if (obsolete) {
407 // make empire forget on the server
408 HumanClientApp::GetApp()->Orders().IssueOrder(
409 std::make_shared<ShipDesignOrder>(empire_id, design_id, true));
410 } else {
411 const auto design = GetShipDesign(design_id);
412 if (!design) {
413 ErrorLogger() << "Attempted to toggle obsolete state of design id "
414 << design_id << " which is unknown to the server";
415 return;
416 }
417
418 //make known to empire on server
419 HumanClientApp::GetApp()->Orders().IssueOrder(
420 std::make_shared<ShipDesignOrder>(empire_id, design_id));
421 }
422 }
423
424 /** Remove design from DisplayedDesigns. */
DeleteFromDisplayedDesigns(const int design_id)425 void DeleteFromDisplayedDesigns(const int design_id) {
426 auto& manager = GetDisplayedDesignsManager();
427
428 const auto empire_id = HumanClientApp::GetApp()->EmpireID();
429 const auto maybe_obsolete = manager.IsObsolete(design_id); // purpose of this obsolescence check is unclear... author didn't comment
430 if (maybe_obsolete && !*maybe_obsolete)
431 HumanClientApp::GetApp()->Orders().IssueOrder( // erase design id order : empire should forget this design
432 std::make_shared<ShipDesignOrder>(empire_id, design_id, true));
433 manager.Remove(design_id);
434 }
435
436
437 //////////////////////////////////////////////////
438 // SavedDesignsManager implementations
439 //////////////////////////////////////////////////
OrderedDesignUUIDs() const440 const std::list<boost::uuids::uuid>& SavedDesignsManager::OrderedDesignUUIDs() const {
441 CheckPendingDesigns();
442 return m_ordered_uuids;
443 }
444
OrderedIDs() const445 std::vector<int> SavedDesignsManager::OrderedIDs() const {
446 CheckPendingDesigns();
447 std::vector<int> retval;
448 for (const auto uuid: m_ordered_uuids) {
449 const auto& it = m_saved_designs.find(uuid);
450 if (it == m_saved_designs.end())
451 continue;
452 retval.push_back(it->second.first->ID());
453 }
454 return retval;
455 }
456
StartParsingDesignsFromFileSystem(bool is_new_game)457 void SavedDesignsManager::StartParsingDesignsFromFileSystem(bool is_new_game) {
458 auto saved_designs_dir = SavedDesignsDir();
459 if (!exists(saved_designs_dir))
460 return;
461
462 m_is_new_game = is_new_game;
463
464 TraceLogger() << "Start parsing saved designs from directory";
465 m_pending_designs = std::async(std::launch::async, parse::ship_designs, saved_designs_dir);
466 }
467
CheckPendingDesigns() const468 void SavedDesignsManager::CheckPendingDesigns() const {
469 if (!m_pending_designs)
470 return;
471
472 TraceLogger() << "Waiting for pending saved designs";
473
474 // Only print waiting message if not immediately ready
475 while (m_pending_designs->wait_for(std::chrono::seconds(1)) == std::future_status::timeout) {
476 DebugLogger() << "Waiting for saved ShipDesigns to parse.";
477 }
478
479 bool inconsistent;
480 std::vector<boost::uuids::uuid> ordering;
481
482 try {
483 TraceLogger() << "Receive parsed saved designs.";
484
485 auto parsed_designs = m_pending_designs->get();
486
487 std::tie(inconsistent, m_saved_designs, ordering) =
488 LoadShipDesignsAndManifestOrderFromParseResults(parsed_designs);
489
490 } catch (const std::exception& e) {
491 ErrorLogger() << "Failed parsing designs: error: " << e.what();
492 throw;
493 }
494
495 m_pending_designs = boost::none;
496
497 m_ordered_uuids = std::list<boost::uuids::uuid>(ordering.begin(), ordering.end());
498
499 // Write any corrected ordering back to disk.
500 if (inconsistent) {
501 WarnLogger() << "Writing corrected ship designs back to saved designs.";
502 SaveManifestConst();
503
504 // Correct file name extension
505 // Nothing fancy, just convert '.txt' to '.txt.focs.txt' which the scripting system is
506 // happy with and there is minimal risk of botching the file name;
507 for (auto& uuid_and_design_and_path: m_saved_designs) {
508 auto& path = uuid_and_design_and_path.second.second;
509 auto extension = path.extension();
510 bool extension_wrong = extension.empty() || extension != ".txt";
511
512 auto stem_extension = path.stem().extension();
513 bool stem_wrong = extension_wrong || stem_extension.empty() || stem_extension != ".focs";
514
515 if (extension_wrong || stem_wrong)
516 path += DESIGN_FILENAME_EXTENSION;
517
518 }
519
520
521 for (auto& uuid: m_ordered_uuids)
522 SaveDesignConst(uuid);
523 }
524
525 if (!m_is_new_game)
526 return;
527
528 m_is_new_game = false;
529
530 // If requested on the first turn copy all of the saved designs to the client empire.
531 if (GetOptionsDB().Get<bool>("resource.shipdesign.saved.enabled")) {
532 const auto empire_id = HumanClientApp::GetApp()->EmpireID();
533 TraceLogger() << "Adding saved designs to empire.";
534 // assume the saved designs are preferred by the user: add them to the front.
535 // note that this also ensures correct ordering.
536 for (const auto& uuid : m_ordered_uuids)
537 AddSavedDesignToDisplayedDesigns(uuid, empire_id, true);
538 }
539 }
540
GetDesign(const boost::uuids::uuid & uuid) const541 const ShipDesign* SavedDesignsManager::GetDesign(const boost::uuids::uuid& uuid) const {
542 CheckPendingDesigns();
543 const auto& it = m_saved_designs.find(uuid);
544 if (it == m_saved_designs.end())
545 return nullptr;
546 return it->second.first.get();
547 }
548
SaveManifest()549 void SavedDesignsManager::SaveManifest()
550 { SaveManifestConst(); }
551
SaveManifestConst() const552 void SavedDesignsManager::SaveManifestConst() const {
553 CheckPendingDesigns();
554 boost::filesystem::path designs_dir_path = GetDesignsDir();
555
556 std::string file_name = DESIGN_MANIFEST_PREFIX + DESIGN_FILENAME_EXTENSION;
557
558 boost::filesystem::path file =
559 boost::filesystem::absolute(PathToString(designs_dir_path / file_name));
560
561 std::stringstream ss;
562 ss << DESIGN_MANIFEST_PREFIX << "\n";
563 for (const auto uuid: m_ordered_uuids)
564 ss << " uuid = \"" << uuid << "\"\n";
565 WriteToFile(file, ss.str());
566 }
567
InsertBefore(const ShipDesign & design,std::list<boost::uuids::uuid>::const_iterator next)568 std::list<boost::uuids::uuid>::const_iterator SavedDesignsManager::InsertBefore(
569 const ShipDesign& design,
570 std::list<boost::uuids::uuid>::const_iterator next)
571 {
572 if (design.UUID() == boost::uuids::uuid{{0}}) {
573 ErrorLogger() << "Ship design has a nil UUID for " << design.Name() << ". Not saving.";
574 return next;
575 }
576
577 CheckPendingDesigns();
578 if (m_saved_designs.count(design.UUID())) {
579 // UUID already exists so this is a move. Remove the old UUID location
580 const auto existing_it = std::find(m_ordered_uuids.begin(), m_ordered_uuids.end(), design.UUID());
581 if (existing_it != m_ordered_uuids.end())
582 m_ordered_uuids.erase(existing_it);
583
584 } else {
585 // Add the new saved design.
586 std::unique_ptr<ShipDesign> design_copy{std::make_unique<ShipDesign>(design)};
587
588 const auto save_path = CreateSaveFileNameForDesign(design);
589
590 m_saved_designs.insert(std::make_pair(design.UUID(), std::make_pair(std::move(design_copy), save_path)));
591 SaveDesign(design.UUID());
592 }
593
594 // Insert in the list.
595 const auto retval = m_ordered_uuids.insert(next, design.UUID());
596 SaveManifest();
597 return retval;
598 }
599
MoveBefore(const boost::uuids::uuid & moved_uuid,const boost::uuids::uuid & next_uuid)600 bool SavedDesignsManager::MoveBefore(const boost::uuids::uuid& moved_uuid, const boost::uuids::uuid& next_uuid) {
601 if (moved_uuid == next_uuid)
602 return false;
603
604 CheckPendingDesigns();
605 if (!m_saved_designs.count(moved_uuid)) {
606 ErrorLogger() << "Unable to move saved design because moved design is missing.";
607 return false;
608 }
609
610 if (next_uuid != boost::uuids::uuid{{0}} && !m_saved_designs.count(next_uuid)) {
611 ErrorLogger() << "Unable to move saved design because target design is missing.";
612 return false;
613 }
614
615 const auto moved_it = std::find(m_ordered_uuids.begin(), m_ordered_uuids.end(), moved_uuid);
616 if (moved_it == m_ordered_uuids.end()) {
617 ErrorLogger() << "Unable to move saved design because moved design is missing.";
618 return false;
619 }
620
621 m_ordered_uuids.erase(moved_it);
622
623 const auto next_it = std::find(m_ordered_uuids.begin(), m_ordered_uuids.end(), next_uuid);
624
625 // Insert in the list.
626 m_ordered_uuids.insert(next_it, moved_uuid);
627 SaveManifest();
628 return true;
629 }
630
Erase(const boost::uuids::uuid & erased_uuid)631 void SavedDesignsManager::Erase(const boost::uuids::uuid& erased_uuid) {
632 CheckPendingDesigns();
633 const auto& saved_design_it = m_saved_designs.find(erased_uuid);
634 if (saved_design_it != m_saved_designs.end()) {
635 const auto& file = saved_design_it->second.second;
636 boost::filesystem::remove(file);
637 m_saved_designs.erase(erased_uuid);
638 }
639
640 const auto& uuid_it = std::find(m_ordered_uuids.begin(), m_ordered_uuids.end(), erased_uuid);
641 m_ordered_uuids.erase(uuid_it);
642 }
643
SaveDesign(const boost::uuids::uuid & uuid)644 void SavedDesignsManager::SaveDesign(const boost::uuids::uuid &uuid)
645 { SaveDesignConst(uuid); }
646
647 /** Save the design with the original filename or throw out_of_range..*/
SaveDesignConst(const boost::uuids::uuid & uuid) const648 void SavedDesignsManager::SaveDesignConst(const boost::uuids::uuid &uuid) const {
649 CheckPendingDesigns();
650 const auto& design_and_filename = m_saved_designs.at(uuid);
651
652 WriteToFile(design_and_filename.second, design_and_filename.first->Dump());
653 }
654
655
656 //////////////////////////////////////////////////
657 // CurrentShipDesignsManager implementations
658 //////////////////////////////////////////////////
OrderedIDs() const659 std::vector<int> DisplayedShipDesignManager::OrderedIDs() const {
660 // Make sure that saved designs are included.
661 // Only OrderedIDs is part of the Designs base class and
662 // accessible outside this file.
663 GetSavedDesignsManager().CheckPendingDesigns();
664
665 // Remove all obsolete ids from the list
666 std::vector<int> retval;
667 std::copy_if(m_ordered_design_ids.begin(), m_ordered_design_ids.end(), std::back_inserter(retval),
668 [this](const int id) {
669 const auto maybe_obsolete = IsObsolete(id);
670 const auto known_and_not_obsolete = maybe_obsolete ? !*maybe_obsolete : false;
671 return known_and_not_obsolete;
672 });
673 return retval;
674 }
675
AllOrderedIDs() const676 std::vector<int> DisplayedShipDesignManager::AllOrderedIDs() const
677 { return std::vector<int>(m_ordered_design_ids.begin(), m_ordered_design_ids.end()); }
678
OrderedHulls() const679 std::vector<std::string> DisplayedShipDesignManager::OrderedHulls() const
680 { return std::vector<std::string>(m_ordered_hulls.begin(), m_ordered_hulls.end()); }
681
682 template <typename T>
InsertOrderedIDs(const T & new_order)683 void DisplayedShipDesignManager::InsertOrderedIDs(const T& new_order) {
684 for (const auto id : new_order)
685 InsertBefore(id, INVALID_DESIGN_ID);
686 }
687
InsertBefore(const int id,const int next_id)688 void DisplayedShipDesignManager::InsertBefore(const int id, const int next_id) {
689 if (id == INVALID_DESIGN_ID) {
690 ErrorLogger() << "Ship design is invalid";
691 return;
692 }
693
694 if (id == next_id) {
695 ErrorLogger() << "Ship design " << id << " is the same as next_id";
696 return;
697 }
698
699 // if id already exists so this is a move. Remove the old location
700 if (IsKnown(id)) {
701 WarnLogger() << "DisplayedShipDesignManager::InsertBefore id = " << id
702 << " already inserted. Removing and reinserting at new location";
703 Remove(id);
704 }
705
706 // Insert in the list, either before next_id or at the end of the list.
707 const auto next_it = m_id_to_obsolete_and_loc.find(next_id);
708 bool is_valid_next_id = (next_id != INVALID_DESIGN_ID
709 && next_it != m_id_to_obsolete_and_loc.end());
710 const auto insert_before_it = (is_valid_next_id ? next_it->second.second :m_ordered_design_ids.end());
711 const auto inserted_it = m_ordered_design_ids.insert(insert_before_it, id);
712
713 m_id_to_obsolete_and_loc[id] = std::make_pair(boost::none, inserted_it);
714 }
715
MoveBefore(const int moved_id,const int next_id)716 bool DisplayedShipDesignManager::MoveBefore(const int moved_id, const int next_id) {
717 if (moved_id == next_id)
718 return false;
719
720 auto existing_it = m_id_to_obsolete_and_loc.find(moved_id);
721 if (existing_it == m_id_to_obsolete_and_loc.end()) {
722 ErrorLogger() << "Unable to move design because moved design is missing.";
723 return false;
724 }
725
726 m_ordered_design_ids.erase(existing_it->second.second);
727
728 const auto next_it = m_id_to_obsolete_and_loc.find(next_id);
729 bool is_valid_next_id = (next_id != INVALID_DESIGN_ID
730 && next_it != m_id_to_obsolete_and_loc.end());
731 const auto insert_before_it = (is_valid_next_id ? next_it->second.second :m_ordered_design_ids.end());
732 const auto inserted_it = m_ordered_design_ids.insert(insert_before_it, moved_id);
733
734 existing_it->second.second = inserted_it;
735 return true;
736 }
737
Remove(const int id)738 void DisplayedShipDesignManager::Remove(const int id) {
739 auto it = m_id_to_obsolete_and_loc.find(id);
740 if (it == m_id_to_obsolete_and_loc.end())
741 return;
742
743 m_ordered_design_ids.erase(it->second.second);
744 m_id_to_obsolete_and_loc.erase(it);
745 }
746
InsertHullBefore(const std::string & hull,const std::string & next_hull)747 void DisplayedShipDesignManager::InsertHullBefore(const std::string& hull, const std::string& next_hull) {
748 if (hull.empty()) {
749 ErrorLogger() << "Hull name is empty()";
750 return;
751 }
752
753 if (hull == next_hull)
754 return;
755
756 // if hull already exists, this is a move. Remove the old location
757 auto existing_it = m_hull_to_obsolete_and_loc.find(hull);
758 if (existing_it != m_hull_to_obsolete_and_loc.end()) {
759 m_ordered_hulls.erase(existing_it->second.second);
760 m_hull_to_obsolete_and_loc.erase(existing_it);
761 }
762
763 // Insert in the list, either before next_id or at the end of the list.
764 const auto next_it = (!next_hull.empty()
765 ? m_hull_to_obsolete_and_loc.find(next_hull)
766 : m_hull_to_obsolete_and_loc.end());
767 bool is_valid_next_hull = (!next_hull.empty()
768 && next_it != m_hull_to_obsolete_and_loc.end());
769 const auto insert_before_it = (is_valid_next_hull
770 ? next_it->second.second
771 : m_ordered_hulls.end());
772 const auto inserted_it = m_ordered_hulls.insert(insert_before_it, hull);
773
774 m_hull_to_obsolete_and_loc[hull] =
775 std::make_pair(std::make_pair(false, NextUIObsoleteEvent()), inserted_it);
776 }
777
IsKnown(const int id) const778 bool DisplayedShipDesignManager::IsKnown(const int id) const
779 { return m_id_to_obsolete_and_loc.count(id); }
780
781
IsObsolete(const int id) const782 boost::optional<bool> DisplayedShipDesignManager::IsObsolete(const int id) const {
783 // A non boost::none value for a specific design overrides the hull and part values
784 auto it_id = m_id_to_obsolete_and_loc.find(id);
785
786 // Unknown design
787 if (it_id == m_id_to_obsolete_and_loc.end())
788 return boost::none;
789
790 const auto design = GetShipDesign(id);
791 if (!design) {
792 ErrorLogger() << "DisplayedShipDesignManager::IsObsolete design id "
793 << id << " is unknown to the server";
794 return boost::none;
795 }
796
797 // Check the UI (un)obsoleting events for design, hull and parts. Events
798 // with a later/higher/more recent stamp supercede older instructions.
799 // If there are no instructions from the user the design is not obsolete.
800 int latest_obsolete_event = -1;
801 int latest_unobsolete_event = 0;
802
803 if (const auto maybe_obsolete_design = it_id->second.first) {
804 if (maybe_obsolete_design->first)
805 latest_obsolete_event = maybe_obsolete_design->second;
806 else
807 latest_unobsolete_event = maybe_obsolete_design->second;
808 }
809
810 if (const auto maybe_hull_obsolete = IsHullObsolete(design->Hull()))
811 latest_obsolete_event = std::max(latest_obsolete_event, *maybe_hull_obsolete);
812
813 for (const auto& part: design->Parts()) {
814 if (const auto maybe_part_obsolete = IsPartObsolete(part))
815 latest_obsolete_event = std::max(latest_obsolete_event, *maybe_part_obsolete);
816 }
817
818 // Default to false if the player has not obsoleted the design, its hull or its parts
819 return latest_obsolete_event > latest_unobsolete_event;
820 }
821
IsHullObsolete(const std::string & hull) const822 boost::optional<int> DisplayedShipDesignManager::IsHullObsolete(const std::string& hull) const {
823 auto it_hull = m_hull_to_obsolete_and_loc.find(hull);
824 if (it_hull == m_hull_to_obsolete_and_loc.end())
825 return boost::none;
826
827 return (it_hull->second.first.first ? boost::optional<int>(it_hull->second.first.second) : boost::none);
828 }
829
IsPartObsolete(const std::string & part) const830 boost::optional<int> DisplayedShipDesignManager::IsPartObsolete(const std::string& part) const {
831 auto it_part = m_obsolete_parts.find(part);
832 return (it_part != m_obsolete_parts.end()) ? boost::optional<int>(it_part->second) : boost::none ;
833 }
834
NextUIObsoleteEvent()835 int DisplayedShipDesignManager::NextUIObsoleteEvent() {
836 ++m_obsolete_ui_event_count;
837 if (m_obsolete_ui_event_count < 0)
838 // Report but don't fix so the error appears more than once in the log
839 ErrorLogger() << "The counter of UI obsoletion events has wrapped to negative values.";
840 return m_obsolete_ui_event_count > 0 ? m_obsolete_ui_event_count : 0;
841 }
842
SetObsolete(const int id,const bool obsolete)843 void DisplayedShipDesignManager::SetObsolete(const int id, const bool obsolete) {
844 auto it = m_id_to_obsolete_and_loc.find(id);
845 if (it == m_id_to_obsolete_and_loc.end())
846 return;
847
848 it->second.first = std::make_pair(obsolete, NextUIObsoleteEvent());
849 }
850
SetHullObsolete(const std::string & name,const bool obsolete)851 void DisplayedShipDesignManager::SetHullObsolete(const std::string& name, const bool obsolete) {
852 auto it = m_hull_to_obsolete_and_loc.find(name);
853 if (it == m_hull_to_obsolete_and_loc.end()) {
854 // All hulls should be known so tack it on the end and try again.
855 ErrorLogger() << "Hull " << name << " is missing in DisplayedShipDesignManager. Adding...";
856 InsertHullBefore(name);
857 return SetHullObsolete(name, obsolete);
858 }
859 it->second.first = {obsolete, NextUIObsoleteEvent()};
860 }
861
SetPartObsolete(const std::string & name,const bool obsolete)862 void DisplayedShipDesignManager::SetPartObsolete(const std::string& name, const bool obsolete) {
863 if (obsolete)
864 m_obsolete_parts[name] = NextUIObsoleteEvent();
865 else
866 m_obsolete_parts.erase(name);
867 }
868
Load(const int obsolete_ui_event_count,const std::vector<std::pair<int,boost::optional<std::pair<bool,int>>>> & design_ids_and_obsoletes,const std::vector<std::pair<std::string,std::pair<bool,int>>> & hulls_and_obsoletes,const std::unordered_map<std::string,int> & obsolete_parts)869 void DisplayedShipDesignManager::Load(
870 const int obsolete_ui_event_count,
871 const std::vector<std::pair<int, boost::optional<std::pair<bool, int>>>>& design_ids_and_obsoletes,
872 const std::vector<std::pair<std::string, std::pair<bool, int>>>& hulls_and_obsoletes,
873 const std::unordered_map<std::string, int>& obsolete_parts)
874 {
875 m_obsolete_ui_event_count = obsolete_ui_event_count;
876 if (m_obsolete_ui_event_count < 0)
877 ErrorLogger() << "DisplayedShipDesignManager::Load obsolete_ui_event_count = "
878 << obsolete_ui_event_count << " < 0 ";
879
880 // Clear and load the ship design ids
881 m_id_to_obsolete_and_loc.clear();
882 m_ordered_design_ids.clear();
883 for (const auto& id_and_obsolete : design_ids_and_obsoletes) {
884 const auto id = id_and_obsolete.first;
885 const auto& obsolete = id_and_obsolete.second;
886 if (m_id_to_obsolete_and_loc.count(id)) {
887 ErrorLogger() << "DisplayedShipDesignManager::Load duplicate design id = " << id;
888 continue;
889 }
890 if (obsolete && obsolete->first
891 && (obsolete->second < 0 || obsolete->second >= m_obsolete_ui_event_count))
892 {
893 ErrorLogger() << "DisplayedShipDesignManager::Load design with id = " << id
894 << " has an obsolete_ui_event_count = " << obsolete->second
895 << " which does not satisfy 0 < obsolete_ui_event_count < m_obsolete_ui_event_count = "
896 << m_obsolete_ui_event_count;
897 }
898 m_ordered_design_ids.push_back(id);
899 m_id_to_obsolete_and_loc[id] = std::make_pair(obsolete, --m_ordered_design_ids.end());
900 }
901
902 // Clear and load the ship hulls
903 m_hull_to_obsolete_and_loc.clear();
904 m_ordered_hulls.clear();
905 for (const auto& name_and_obsolete : hulls_and_obsoletes) {
906 const auto& name = name_and_obsolete.first;
907 const auto& obsolete = name_and_obsolete.second;
908 if (m_hull_to_obsolete_and_loc.count(name)) {
909 ErrorLogger() << "DisplayedShipDesignManager::Load duplicate hull name = " << name;
910 continue;
911 }
912 if (obsolete.first && (obsolete.second < 0 || obsolete.second >= m_obsolete_ui_event_count))
913 ErrorLogger() << "DisplayedShipDesignManager::Load hull \"" << name
914 << "\" has an obsolete_ui_event_count = " << obsolete.second
915 << " which does not satisfy 0 < obsolete_ui_event_count < m_obsolete_ui_event_count = "
916 << m_obsolete_ui_event_count;
917 m_ordered_hulls.push_back(name);
918 m_hull_to_obsolete_and_loc[name] = std::make_pair(obsolete, --m_ordered_hulls.end());
919 }
920
921 // Clear and load the ship parts
922 m_obsolete_parts = obsolete_parts;
923 for (const auto& part_and_event_count : m_obsolete_parts) {
924 const auto& name = part_and_event_count.first;
925 const auto& count = part_and_event_count.second;
926 if (count < 0 || count >= m_obsolete_ui_event_count)
927 ErrorLogger() << "DisplayedShipDesignManager::Load part \"" << name
928 << "\" has an obsolete_ui_event_count = " << count
929 << " which does not satisfy 0 < obsolete_ui_event_count < m_obsolete_ui_event_count = "
930 << m_obsolete_ui_event_count;
931
932 }
933 }
934
Save(int & obsolete_ui_event_count,std::vector<std::pair<int,boost::optional<std::pair<bool,int>>>> & design_ids_and_obsoletes,std::vector<std::pair<std::string,std::pair<bool,int>>> & hulls_and_obsoletes,std::unordered_map<std::string,int> & obsolete_parts)935 void DisplayedShipDesignManager::Save(
936 int& obsolete_ui_event_count,
937 std::vector<std::pair<int, boost::optional<std::pair<bool, int>>>>& design_ids_and_obsoletes,
938 std::vector<std::pair<std::string, std::pair<bool, int>>>& hulls_and_obsoletes,
939 std::unordered_map<std::string, int>& obsolete_parts)
940 {
941 obsolete_ui_event_count = m_obsolete_ui_event_count;
942
943 design_ids_and_obsoletes.clear();
944 for (const auto id : m_ordered_design_ids) {
945 try {
946 design_ids_and_obsoletes.push_back({id, m_id_to_obsolete_and_loc.at(id).first});
947 } catch (const std::out_of_range&) {
948 ErrorLogger() << "DisplayedShipDesignManager::Save missing id = " << id;
949 continue;
950 }
951 }
952
953 hulls_and_obsoletes.clear();
954 for (const auto name : m_ordered_hulls) {
955 try {
956 hulls_and_obsoletes.push_back({name, m_hull_to_obsolete_and_loc.at(name).first});
957 } catch (const std::out_of_range&) {
958 ErrorLogger() << "DisplayedShipDesignManager::Save missing hull = " << name;
959 continue;
960 }
961 }
962
963 obsolete_parts = m_obsolete_parts;
964 }
965
966 //////////////////////////////////////////////////
967 // AvailabilityManager //
968 //////////////////////////////////////////////////
969 /** A class to allow the storage of the state of a GUI availabilty filter
970 and the querying of that state WRT a ship design. */
971 class AvailabilityManager {
972 public:
973 // DisplayedAvailabilies is indexed by Availability::Enum
974 using DisplayedAvailabilies = std::tuple<bool, bool, bool>;
975
976 AvailabilityManager(bool obsolete, bool available, bool unavailable);
977
GetAvailabilities() const978 const DisplayedAvailabilies& GetAvailabilities() const { return m_availabilities; };
979 bool GetAvailability(const Availability::Enum type) const;
980 void SetAvailability(const Availability::Enum type, const bool state);
981 void ToggleAvailability(const Availability::Enum type);
982
983 /** Given the GUI's displayed availabilities as stored in this
984 AvailabilityManager, return the displayed state of the \p design.
985 Return none if the \p design should not be displayed. */
986 boost::optional<DisplayedAvailabilies> DisplayedDesignAvailability(const ShipDesign& design) const;
987 /** Given the GUI's displayed availabilities as stored in this
988 AvailabilityManager, return the displayed state of the hull \p
989 name. Return none if the hull should not be displayed. */
990 boost::optional<DisplayedAvailabilies> DisplayedHullAvailability(const std::string& name) const;
991 /** Given the GUI's displayed availabilities as stored in this
992 AvailabilityManager, return the displayed state of the part \p
993 name. Return none if the part should not be displayed. */
994 boost::optional<DisplayedAvailabilies> DisplayedPartAvailability(const std::string& name) const;
995
996 private:
997 /** Given the GUI's displayed availabilities as stored in this
998 AvailabilityManager and that the X is \p available and \p obsolete,
999 return the displayed state of the X. Return none if the X should
1000 not be displayed. */
1001 boost::optional<DisplayedAvailabilies> DisplayedXAvailability(bool available, bool obsolete) const;
1002
1003 // A tuple of the toogle state of the 3-tuple of coupled
1004 // availability filters in the GUI:
1005 // Obsolete, Available and Unavailable
1006 DisplayedAvailabilies m_availabilities;
1007 };
1008
AvailabilityManager(bool obsolete,bool available,bool unavailable)1009 AvailabilityManager::AvailabilityManager(bool obsolete, bool available, bool unavailable) :
1010 m_availabilities{obsolete, available, unavailable}
1011 {}
1012
GetAvailability(const Availability::Enum type) const1013 bool AvailabilityManager::GetAvailability(const Availability::Enum type) const {
1014 switch (type) {
1015 case Availability::Obsolete:
1016 return std::get<Availability::Obsolete>(m_availabilities);
1017 case Availability::Available:
1018 return std::get<Availability::Available>(m_availabilities);
1019 case Availability::Future:
1020 return std::get<Availability::Future>(m_availabilities);
1021 }
1022 return std::get<Availability::Future>(m_availabilities);
1023 }
1024
SetAvailability(const Availability::Enum type,const bool state)1025 void AvailabilityManager::SetAvailability(const Availability::Enum type, const bool state) {
1026 switch (type) {
1027 case Availability::Obsolete:
1028 std::get<Availability::Obsolete>(m_availabilities) = state;
1029 break;
1030 case Availability::Available:
1031 std::get<Availability::Available>(m_availabilities) = state;
1032 break;
1033 case Availability::Future:
1034 std::get<Availability::Future>(m_availabilities) = state;
1035 break;
1036 }
1037 }
1038
ToggleAvailability(const Availability::Enum type)1039 void AvailabilityManager::ToggleAvailability(const Availability::Enum type)
1040 { SetAvailability(type, !GetAvailability(type)); }
1041
1042 boost::optional<AvailabilityManager::DisplayedAvailabilies>
DisplayedDesignAvailability(const ShipDesign & design) const1043 AvailabilityManager::DisplayedDesignAvailability(const ShipDesign& design) const {
1044 int empire_id = HumanClientApp::GetApp()->EmpireID();
1045 const Empire* empire = GetEmpire(empire_id); // may be nullptr
1046 bool available = empire ? empire->ShipDesignAvailable(design) : true;
1047
1048 const auto& manager = GetDisplayedDesignsManager();
1049 const auto maybe_obsolete = manager.IsObsolete(design.ID());
1050 bool is_obsolete = maybe_obsolete && *maybe_obsolete;
1051
1052 return DisplayedXAvailability(available, is_obsolete);
1053 }
1054
1055 boost::optional<AvailabilityManager::DisplayedAvailabilies>
DisplayedHullAvailability(const std::string & id) const1056 AvailabilityManager::DisplayedHullAvailability(const std::string& id) const {
1057 int empire_id = HumanClientApp::GetApp()->EmpireID();
1058 const Empire* empire = GetEmpire(empire_id); // may be nullptr
1059 bool available = empire ? empire->ShipHullAvailable(id) : true;
1060
1061 const auto& manager = GetDisplayedDesignsManager();
1062 const auto obsolete = bool(manager.IsHullObsolete(id));
1063
1064 return DisplayedXAvailability(available, obsolete);
1065 }
1066
1067 boost::optional<AvailabilityManager::DisplayedAvailabilies>
DisplayedPartAvailability(const std::string & id) const1068 AvailabilityManager::DisplayedPartAvailability(const std::string& id) const {
1069 int empire_id = HumanClientApp::GetApp()->EmpireID();
1070 const Empire* empire = GetEmpire(empire_id); // may be nullptr
1071 bool available = empire ? empire->ShipPartAvailable(id) : true;
1072
1073 const auto& manager = GetDisplayedDesignsManager();
1074 const auto obsolete = bool(manager.IsPartObsolete(id));
1075
1076 return DisplayedXAvailability(available, obsolete);
1077 }
1078
1079 boost::optional<AvailabilityManager::DisplayedAvailabilies>
DisplayedXAvailability(bool available,bool obsolete) const1080 AvailabilityManager::DisplayedXAvailability(bool available, bool obsolete) const {
1081 // TODO: C++17, Replace with structured binding auto [a, b, c] = m_availabilities;
1082 const bool showing_obsolete = std::get<Availability::Obsolete>(m_availabilities);
1083 const bool showing_available = std::get<Availability::Available>(m_availabilities);
1084 const bool showing_future = std::get<Availability::Future>(m_availabilities);
1085
1086 auto show = (
1087 (showing_obsolete && obsolete && showing_available && available)
1088 || (showing_obsolete && obsolete && showing_future && !available)
1089 || (showing_obsolete && obsolete && !showing_available && !showing_future)
1090 || (showing_available && available && !obsolete)
1091 || (showing_future && !available && !obsolete));
1092
1093 if (!show)
1094 return boost::none;
1095
1096 return std::make_tuple(showing_obsolete && obsolete,
1097 showing_available && available,
1098 showing_future && !available);
1099 }
1100 }
1101
1102 //////////////////////////////////////////////////
1103 // ShipDesignManager //
1104 //////////////////////////////////////////////////
1105
ShipDesignManager()1106 ShipDesignManager::ShipDesignManager() :
1107 m_displayed_designs(std::make_unique<DisplayedShipDesignManager>()),
1108 m_saved_designs(std::make_unique<SavedDesignsManager>())
1109 {}
1110
~ShipDesignManager()1111 ShipDesignManager::~ShipDesignManager()
1112 {}
1113
StartGame(int empire_id,bool is_new_game)1114 void ShipDesignManager::StartGame(int empire_id, bool is_new_game) {
1115 auto empire = GetEmpire(empire_id);
1116 if (!empire) {
1117 ErrorLogger() << "Unable to initialize ShipDesignManager because empire id, " << empire_id << ", is invalid";
1118 return;
1119 }
1120
1121 DebugLogger() << "ShipDesignManager initializing. New game " << is_new_game;
1122
1123 m_displayed_designs = std::make_unique<DisplayedShipDesignManager>();
1124 auto displayed_designs = dynamic_cast<DisplayedShipDesignManager*>(m_displayed_designs.get());
1125
1126 m_saved_designs = std::make_unique<SavedDesignsManager>();
1127 auto saved_designs = dynamic_cast<SavedDesignsManager*>(m_saved_designs.get());
1128 saved_designs->StartParsingDesignsFromFileSystem(is_new_game);
1129
1130 // Only setup saved and current designs for new games
1131 if (!is_new_game)
1132 return;
1133
1134 // Initialize the hull ordering from the ShipHullManager
1135 for (const auto& name_and_type : GetShipHullManager()) {
1136 const auto& hull_name = name_and_type.first;
1137 const auto& ship_hull = name_and_type.second;
1138
1139 if (!ship_hull || !ship_hull->Producible())
1140 continue;
1141 displayed_designs->InsertHullBefore(hull_name);
1142 }
1143
1144 // If requested, initialize the current designs to all designs known by the empire
1145 if (GetOptionsDB().Get<bool>("resource.shipdesign.default.enabled")) {
1146 // While initializing a new game, before sending info to players, the
1147 // server should have added the default design ids to an empire's known
1148 // designs. Loop over these, and add them to "current" designs.
1149 DebugLogger() << "Add default designs to empire's current designs";
1150 const auto& ids = empire->ShipDesigns();
1151 std::set<int> ordered_ids(ids.begin(), ids.end());
1152
1153 displayed_designs->InsertOrderedIDs(ordered_ids);
1154
1155 } else {
1156 // Remove the default designs from the empire's current designs.
1157 // Purpose and logic of this is unclear... author didn't comment upon inquiry, but having this here reportedly fixes some issues...
1158 DebugLogger() << "Remove default designs from empire";
1159 const auto ids = empire->ShipDesigns();
1160 for (const auto design_id : ids) {
1161 HumanClientApp::GetApp()->Orders().IssueOrder(
1162 std::make_shared<ShipDesignOrder>(empire_id, design_id, true));
1163 }
1164 }
1165
1166 TraceLogger() << "ShipDesignManager initialized";
1167 }
1168
Save(SaveGameUIData & data) const1169 void ShipDesignManager::Save(SaveGameUIData& data) const {
1170 GetDisplayedDesignsManager().Save(data.obsolete_ui_event_count,
1171 data.ordered_ship_design_ids_and_obsolete,
1172 data.ordered_ship_hull_and_obsolete,
1173 data.obsolete_ship_parts);
1174 }
1175
Load(const SaveGameUIData & data)1176 void ShipDesignManager::Load(const SaveGameUIData& data) {
1177 GetDisplayedDesignsManager().Load(data.obsolete_ui_event_count,
1178 data.ordered_ship_design_ids_and_obsolete,
1179 data.ordered_ship_hull_and_obsolete,
1180 data.obsolete_ship_parts);
1181 }
1182
DisplayedDesigns()1183 ShipDesignManager::Designs* ShipDesignManager::DisplayedDesigns() {
1184 auto retval = m_displayed_designs.get();
1185 if (retval == nullptr) {
1186 ErrorLogger() << "ShipDesignManager m_displayed_designs was not correctly initialized "
1187 << "with ShipDesignManager::GameStart().";
1188 m_displayed_designs = std::make_unique<DisplayedShipDesignManager>();
1189 return m_displayed_designs.get();
1190 }
1191 return retval;
1192 }
1193
SavedDesigns()1194 ShipDesignManager::Designs* ShipDesignManager::SavedDesigns() {
1195 auto retval = m_saved_designs.get();
1196 if (retval == nullptr) {
1197 ErrorLogger() << "ShipDesignManager m_saved_designs was not correctly initialized "
1198 << "with ShipDesignManager::GameStart().";
1199 m_saved_designs = std::make_unique<SavedDesignsManager>();
1200 return m_saved_designs.get();
1201 }
1202 return retval;
1203 }
1204
1205
1206 //////////////////////////////////////////////////
1207 // PartControl //
1208 //////////////////////////////////////////////////
1209 /** UI representation of a ship part. Displayed in the PartPalette, and can be
1210 * dragged onto SlotControls to add parts to the design. */
1211 class PartControl : public GG::Control {
1212 public:
1213 /** \name Structors */ //@{
1214 PartControl(const ShipPart* part);
1215 //@}
1216 void CompleteConstruction() override;
1217
1218 /** \name Accessors */ //@{
Part() const1219 const ShipPart* Part() const { return m_part; }
PartName() const1220 const std::string& PartName() const { return m_part ? m_part->Name() : EMPTY_STRING; }
1221 //@}
1222
1223 /** \name Mutators */ //@{
1224 void Render() override;
1225 void LClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override;
1226 void LDoubleClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override;
1227 void RClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override;
1228 void SetAvailability(const AvailabilityManager::DisplayedAvailabilies& type);
1229 //@}
1230
1231 mutable boost::signals2::signal<void (const ShipPart*, GG::Flags<GG::ModKey>)> ClickedSignal;
1232 mutable boost::signals2::signal<void (const ShipPart*, const GG::Pt& pt)> RightClickedSignal;
1233 mutable boost::signals2::signal<void (const ShipPart*)> DoubleClickedSignal;
1234
1235 private:
1236 std::shared_ptr<GG::StaticGraphic> m_icon = nullptr;
1237 std::shared_ptr<GG::StaticGraphic> m_background = nullptr;
1238 const ShipPart* m_part = nullptr;
1239 };
1240
PartControl(const ShipPart * part)1241 PartControl::PartControl(const ShipPart* part) :
1242 GG::Control(GG::X0, GG::Y0, SLOT_CONTROL_WIDTH, SLOT_CONTROL_HEIGHT, GG::INTERACTIVE),
1243 m_part(part)
1244 {}
1245
CompleteConstruction()1246 void PartControl::CompleteConstruction() {
1247 GG::Control::CompleteConstruction();
1248 if (!m_part)
1249 return;
1250
1251 m_background = GG::Wnd::Create<GG::StaticGraphic>(PartBackgroundTexture(m_part), GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
1252 m_background->Resize(GG::Pt(SLOT_CONTROL_WIDTH, SLOT_CONTROL_HEIGHT));
1253 m_background->Show();
1254 AttachChild(m_background);
1255
1256
1257 // position of part image centred within part control. control is size of a slot, but the
1258 // part image is smaller
1259 GG::X part_left = (Width() - PART_CONTROL_WIDTH) / 2;
1260 GG::Y part_top = (Height() - PART_CONTROL_HEIGHT) / 2;
1261
1262 //DebugLogger() << "PartControl::PartControl this: " << this << " part: " << part << " named: " << (part ? part->Name() : "no part");
1263 m_icon = GG::Wnd::Create<GG::StaticGraphic>(ClientUI::PartIcon(m_part->Name()), GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
1264 m_icon->MoveTo(GG::Pt(part_left, part_top));
1265 m_icon->Resize(GG::Pt(PART_CONTROL_WIDTH, PART_CONTROL_HEIGHT));
1266 m_icon->Show();
1267 AttachChild(m_icon);
1268
1269 SetDragDropDataType(PART_CONTROL_DROP_TYPE_STRING);
1270
1271 //DebugLogger() << "PartControl::PartControl part name: " << m_part->Name();
1272 SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1273 SetBrowseInfoWnd(GG::Wnd::Create<IconTextBrowseWnd>(
1274 ClientUI::PartIcon(m_part->Name()),
1275 UserString(m_part->Name()),
1276 UserString(m_part->Description()) + "\n" + m_part->CapacityDescription()
1277 ));
1278 }
1279
Render()1280 void PartControl::Render() {}
1281
LClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)1282 void PartControl::LClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys)
1283 { ClickedSignal(m_part, mod_keys); }
1284
LDoubleClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)1285 void PartControl::LDoubleClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys)
1286 { DoubleClickedSignal(m_part); }
1287
RClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)1288 void PartControl::RClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys)
1289 { RightClickedSignal(m_part, pt); }
1290
SetAvailability(const AvailabilityManager::DisplayedAvailabilies & type)1291 void PartControl::SetAvailability(const AvailabilityManager::DisplayedAvailabilies& type) {
1292 auto disabled = std::get<Availability::Obsolete>(type);
1293 m_icon->Disable(disabled);
1294 m_background->Disable(disabled);
1295 }
1296
1297 //////////////////////////////////////////////////
1298 // PartsListBox //
1299 //////////////////////////////////////////////////
1300 /** Arrangement of PartControls that can be dragged onto SlotControls */
1301 class PartsListBox : public CUIListBox {
1302 public:
1303 class PartsListBoxRow : public CUIListBox::Row {
1304 public:
1305 PartsListBoxRow(GG::X w, GG::Y h, const AvailabilityManager& availabilities_state);
1306 void ChildrenDraggedAway(const std::vector<GG::Wnd*>& wnds,
1307 const GG::Wnd* destination) override;
1308 private:
1309 const AvailabilityManager& m_availabilities_state;
1310 };
1311
1312 /** \name Structors */ //@{
1313 explicit PartsListBox(const AvailabilityManager& availabilities_state);
1314 //@}
1315
1316 /** \name Accessors */ //@{
1317 const std::set<ShipPartClass>& GetClassesShown() const;
AvailabilityState() const1318 const AvailabilityManager& AvailabilityState() const { return m_availabilities_state; }
GetShowingSuperfluous() const1319 bool GetShowingSuperfluous() const { return m_show_superfluous_parts; }
1320 //@}
1321
1322 /** \name Mutators */ //@{
1323 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override;
1324 void AcceptDrops(const GG::Pt& pt, std::vector<std::shared_ptr<GG::Wnd>> wnds,
1325 GG::Flags<GG::ModKey> mod_keys) override;
1326 void Populate();
1327
1328 void ShowClass(ShipPartClass part_class, bool refresh_list = true);
1329 void ShowAllClasses(bool refresh_list = true);
1330 void HideClass(ShipPartClass part_class, bool refresh_list = true);
1331 void HideAllClasses(bool refresh_list = true);
1332 void ShowSuperfluousParts(bool refresh_list = true);
1333 void HideSuperfluousParts(bool refresh_list = true);
1334 //@}
1335
1336 mutable boost::signals2::signal<void (const ShipPart*, GG::Flags<GG::ModKey>)> ShipPartClickedSignal;
1337 mutable boost::signals2::signal<void (const ShipPart*)> ShipPartDoubleClickedSignal;
1338 mutable boost::signals2::signal<void (const ShipPart*, const GG::Pt& pt)> ShipPartRightClickedSignal;
1339 mutable boost::signals2::signal<void (const std::string&)> ClearPartSignal;
1340
1341 protected:
1342 void DropsAcceptable(DropsAcceptableIter first, DropsAcceptableIter last,
1343 const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) const override;
1344
1345 private:
1346 PartGroupsType GroupAvailableDisplayableParts(const Empire* empire) const;
1347 void CullSuperfluousParts(std::vector<const ShipPart*>& this_group,
1348 ShipPartClass part_class, int empire_id, int loc_id) const;
1349
1350 std::set<ShipPartClass> m_part_classes_shown; // which part classes should be shown
1351 bool m_show_superfluous_parts = true;
1352 int m_previous_num_columns = -1;
1353 const AvailabilityManager& m_availabilities_state;
1354 };
1355
PartsListBoxRow(GG::X w,GG::Y h,const AvailabilityManager & availabilities_state)1356 PartsListBox::PartsListBoxRow::PartsListBoxRow(GG::X w, GG::Y h, const AvailabilityManager& availabilities_state) :
1357 CUIListBox::Row(w, h),
1358 m_availabilities_state(availabilities_state)
1359 {}
1360
ChildrenDraggedAway(const std::vector<GG::Wnd * > & wnds,const GG::Wnd * destination)1361 void PartsListBox::PartsListBoxRow::ChildrenDraggedAway(const std::vector<GG::Wnd*>& wnds, const GG::Wnd* destination) {
1362 if (wnds.empty())
1363 return;
1364 // should only be one wnd in list because PartControls doesn't allow selection, so dragging is
1365 // only one-at-a-time
1366 auto control = dynamic_cast<GG::Control*>(wnds.front());
1367 if (!control || empty())
1368 return;
1369
1370 // find control in row
1371 unsigned int ii = 0;
1372 for (; ii < size(); ++ii) {
1373 if (at(ii) != control)
1374 continue;
1375 }
1376
1377 if (ii == size())
1378 return;
1379
1380 RemoveCell(ii); // Wnd that accepts drop takes ownership of dragged-away control
1381
1382 auto part_control = dynamic_cast<PartControl*>(control);
1383 if (!part_control)
1384 return;
1385
1386 const auto part = part_control->Part();
1387 if (!part)
1388 return;
1389
1390 auto new_part_control = GG::Wnd::Create<PartControl>(part);
1391 const auto parent = dynamic_cast<const PartsListBox*>(Parent().get());
1392 if (parent) {
1393 new_part_control->ClickedSignal.connect(
1394 parent->ShipPartClickedSignal);
1395 new_part_control->DoubleClickedSignal.connect(
1396 parent->ShipPartDoubleClickedSignal);
1397 new_part_control->RightClickedSignal.connect(
1398 parent->ShipPartRightClickedSignal);
1399 }
1400
1401 // set availability shown
1402 auto shown = m_availabilities_state.DisplayedPartAvailability(part->Name());
1403 if (shown)
1404 new_part_control->SetAvailability(*shown);
1405
1406 SetCell(ii, new_part_control);
1407 }
1408
PartsListBox(const AvailabilityManager & availabilities_state)1409 PartsListBox::PartsListBox(const AvailabilityManager& availabilities_state) :
1410 CUIListBox(),
1411 m_availabilities_state(availabilities_state)
1412 {
1413 ManuallyManageColProps();
1414 NormalizeRowsOnInsert(false);
1415 SetStyle(GG::LIST_NOSEL);
1416 }
1417
GetClassesShown() const1418 const std::set<ShipPartClass>& PartsListBox::GetClassesShown() const
1419 { return m_part_classes_shown; }
1420
SizeMove(const GG::Pt & ul,const GG::Pt & lr)1421 void PartsListBox::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
1422 GG::Pt old_size = GG::Wnd::Size();
1423
1424 // maybe later do something interesting with docking
1425 CUIListBox::SizeMove(ul, lr);
1426
1427 if (old_size != GG::Wnd::Size()) {
1428 // determine how many columns can fit in the box now...
1429 const GG::X TOTAL_WIDTH = Size().x - ClientUI::ScrollWidth();
1430 const int NUM_COLUMNS = std::max(1,
1431 Value(TOTAL_WIDTH / (SLOT_CONTROL_WIDTH + GG::X(PAD))));
1432
1433 if (NUM_COLUMNS != m_previous_num_columns)
1434 Populate();
1435 }
1436 }
1437
AcceptDrops(const GG::Pt & pt,std::vector<std::shared_ptr<GG::Wnd>> wnds,GG::Flags<GG::ModKey> mod_keys)1438 void PartsListBox::AcceptDrops(const GG::Pt& pt,
1439 std::vector<std::shared_ptr<GG::Wnd>> wnds,
1440 GG::Flags<GG::ModKey> mod_keys)
1441 {
1442 // Accept parts being discarded from the ship under design
1443
1444 // If ctrl is pressed then signal all parts of the same type to be cleared.
1445 if (!(GG::GUI::GetGUI()->ModKeys() & GG::MOD_KEY_CTRL))
1446 return;
1447
1448 if (wnds.empty())
1449 return;
1450
1451 auto* control = boost::polymorphic_downcast<const PartControl*>(wnds.begin()->get());
1452 auto* part = control ? control->Part() : nullptr;
1453 if (!part)
1454 return;
1455
1456 ClearPartSignal(part->Name());
1457 }
1458
GroupAvailableDisplayableParts(const Empire * empire) const1459 PartGroupsType PartsListBox::GroupAvailableDisplayableParts(const Empire* empire) const {
1460 PartGroupsType part_groups;
1461
1462 // loop through all possible parts
1463 for (const auto& entry : GetShipPartManager()) {
1464 const auto& part = entry.second;
1465 if (!part->Producible())
1466 continue;
1467
1468 // check whether this part should be shown in list
1469 ShipPartClass part_class = part->Class();
1470 if (!m_part_classes_shown.count(part_class))
1471 continue; // part of this class is not requested to be shown
1472
1473 // Check if part satisfies availability and obsolecense
1474 auto shown = m_availabilities_state.DisplayedPartAvailability(part->Name());
1475 if (!shown)
1476 continue;
1477
1478 for (ShipSlotType slot_type : part->MountableSlotTypes())
1479 part_groups[{part_class, slot_type}].push_back(part.get());
1480 }
1481 return part_groups;
1482 }
1483
1484 namespace {
1485 // Checks if the Location condition of the check_part totally contains the Location condition of ref_part
1486 // i,e,, the ref_part condition is met anywhere the check_part condition is
LocationASubsumesLocationB(const Condition::Condition * check_part_loc,const Condition::Condition * ref_part_loc)1487 bool LocationASubsumesLocationB(const Condition::Condition* check_part_loc,
1488 const Condition::Condition* ref_part_loc)
1489 {
1490 //const Condition::ConditionBase* check_part_loc = check_part->Location();
1491 //const Condition::ConditionBase* ref_part_loc = ref_part->Location();
1492 if (dynamic_cast<const Condition::All*>(ref_part_loc))
1493 return true;
1494 if (!check_part_loc || !ref_part_loc)
1495 return false;
1496 if (*check_part_loc == *ref_part_loc)
1497 return true;
1498 // could do more involved checking for And conditions & Or, etc,
1499 // for now, will simply be conservative
1500 return false;
1501 }
1502
PartALocationSubsumesPartB(const ShipPart * check_part,const ShipPart * ref_part)1503 bool PartALocationSubsumesPartB(const ShipPart* check_part, const ShipPart* ref_part) {
1504 static std::map<std::pair<std::string, std::string>, bool> part_loc_comparison_map;
1505
1506 auto part_pair = std::make_pair(check_part->Name(), ref_part->Name());
1507 auto map_it = part_loc_comparison_map.find(part_pair);
1508 if (map_it != part_loc_comparison_map.end())
1509 return map_it->second;
1510
1511 bool result = true;
1512 if (check_part->Name() == "SH_MULTISPEC" || ref_part->Name() == "SH_MULTISPEC")
1513 result = false;
1514
1515 auto check_part_loc = check_part->Location();
1516 auto ref_part_loc = ref_part->Location();
1517 result = result && LocationASubsumesLocationB(check_part_loc, ref_part_loc);
1518 part_loc_comparison_map[part_pair] = result;
1519 //if (result && check_part_loc && ref_part_loc) {
1520 // DebugLogger() << "Location for partA, " << check_part->Name() << ", subsumes that for partB, " << ref_part->Name();
1521 // DebugLogger() << " ...PartA Location is " << check_part_loc->Description();
1522 // DebugLogger() << " ...PartB Location is " << ref_part_loc->Description();
1523 //}
1524 return result;
1525 }
1526 }
1527
CullSuperfluousParts(std::vector<const ShipPart * > & this_group,ShipPartClass part_class,int empire_id,int loc_id) const1528 void PartsListBox::CullSuperfluousParts(std::vector<const ShipPart*>& this_group,
1529 ShipPartClass part_class, int empire_id,
1530 int loc_id) const
1531 {
1532 // This is not merely a check for obsolescence; see PartsListBox::Populate
1533 // for more info
1534 static float min_bargain_ratio = -1.0;
1535 static float max_cost_ratio = -1.0;
1536 static float max_time_ratio = -1.0;
1537
1538 if (min_bargain_ratio == -1.0) {
1539 min_bargain_ratio = 1.0;
1540 try {
1541 if (UserStringExists("FUNCTIONAL_MIN_BARGAIN_RATIO")) {
1542 float new_bargain_ratio = std::atof(UserString("FUNCTIONAL_MIN_BARGAIN_RATIO").c_str());
1543 if (new_bargain_ratio > 1.0f)
1544 min_bargain_ratio = new_bargain_ratio;
1545 }
1546 } catch (...) {}
1547 }
1548
1549 if (max_cost_ratio == -1.0) {
1550 max_cost_ratio = 1.0;
1551 try {
1552 if (UserStringExists("FUNCTIONAL_MAX_COST_RATIO")) {
1553 float new_cost_ratio = std::atof(UserString("FUNCTIONAL_MAX_COST_RATIO").c_str());
1554 if (new_cost_ratio > 1.0f)
1555 max_cost_ratio = new_cost_ratio;
1556 }
1557 } catch (...) {}
1558 }
1559
1560 if (max_time_ratio == -1.0) {
1561 max_time_ratio = 1.0;
1562 try {
1563 if (UserStringExists("FUNCTIONAL_MAX_TIME_RATIO")) {
1564 float new_time_ratio = std::atof(UserString("FUNCTIONAL_MAX_TIME_RATIO").c_str());
1565 if (new_time_ratio > 1.0f)
1566 max_time_ratio = new_time_ratio;
1567 }
1568 } catch (...) {}
1569 }
1570
1571 for (auto part_it = this_group.begin();
1572 part_it != this_group.end(); ++part_it)
1573 {
1574 const ShipPart* checkPart = *part_it;
1575 for (const ShipPart* ref_part : this_group) {
1576 float cap_check = GetMainStat(checkPart);
1577 float cap_ref = GetMainStat(ref_part);
1578 if ((cap_check < 0.0f) || (cap_ref < 0.0f))
1579 continue; // not intended to handle such cases
1580 float cap_ratio = cap_ref / std::max(cap_check, 1e-4f) ; // some part types currently have zero capacity, but need to reject if both are zero
1581 float cost_check = checkPart->ProductionCost(empire_id, loc_id);
1582 float cost_ref = ref_part->ProductionCost(empire_id, loc_id);
1583 if ((cost_check < 0.0f) || (cost_ref < 0.0f))
1584 continue; // not intended to handle such cases
1585 float cost_ratio = (cost_ref + 1e-4) / (cost_check + 1e-4); // can accept if somehow they both have cost zero
1586 float bargain_ratio = cap_ratio / std::max(cost_ratio, 1e-4f);
1587 float time_ratio = float(std::max(1, ref_part->ProductionTime(empire_id, loc_id))) / std::max(1, checkPart->ProductionTime(empire_id, loc_id));
1588 // adjusting the max cost ratio to 1.4 or higher, will allow, for example, for
1589 // Zortium armor to make Standard armor redundant. Setting a min_bargain_ratio higher than one can keep
1590 // trivial bargains from blocking lower valued parts.
1591 // TODO: move these values into default/customizations/common_user_customizations.txt once that is supported
1592
1593 if ((cap_ratio > 1.0) && ((cost_ratio <= 1.0) || ((bargain_ratio >= min_bargain_ratio) && (cost_ratio <= max_cost_ratio))) &&
1594 (time_ratio <= max_time_ratio) && PartALocationSubsumesPartB(checkPart, ref_part))
1595 {
1596 //DebugLogger() << "Filtering " << checkPart->Name() << " because of " << ref_part->Name();
1597 this_group.erase(part_it--);
1598 break;
1599 }
1600 }
1601
1602 }
1603 }
1604
Populate()1605 void PartsListBox::Populate() {
1606 ScopedTimer scoped_timer("PartsListBox::Populate");
1607
1608 const GG::X TOTAL_WIDTH = ClientWidth() - ClientUI::ScrollWidth();
1609 const int NUM_COLUMNS = std::max(1, Value(TOTAL_WIDTH / (SLOT_CONTROL_WIDTH + GG::X(PAD))));
1610
1611 int empire_id = HumanClientApp::GetApp()->EmpireID();
1612 const Empire* empire = GetEmpire(empire_id); // may be nullptr
1613
1614 int cur_col = NUM_COLUMNS;
1615 std::shared_ptr<PartsListBoxRow> cur_row;
1616 int num_parts = 0;
1617
1618 // remove parts currently in rows of listbox
1619 Clear();
1620
1621 /**
1622 * The Parts are first filtered for availability to this empire and according to the current
1623 * selections of which part classes are to be displayed. Then, in order to eliminate presentation
1624 * of clearly suboptimal parts, such as Mass Driver I when Mass Driver II is available at the same
1625 * cost & build time, some orgnization, paring and sorting of parts is done. The previously
1626 * filtered parts are grouped according to (class, slot). Within each group, parts are compared
1627 * and pared for display; only parts within the same group may suppress display of each other.
1628 * The paring is (currently) done on the basis of main stat, construction cost, and construction
1629 * time. If two parts have the same class and slot, and one has a lower main stat but also a lower
1630 * cost, they will both be presented; if one has a higher main stat and is at least as good on cost
1631 * and time, it will suppress the other.
1632 *
1633 * An example of one of the more subtle possible results is that if a part class had multiple parts
1634 * with different but overlapping MountableSlotType patterns, then a part with two possible slot
1635 * types might be rendered superfluous for the first slot type by a first other part, be rendered
1636 * superfluous for the second slot type by a second other part, even if neither of the latter two
1637 * parts would be considered to individually render the former part obsolete.
1638 */
1639
1640 /// filter parts by availability and current designation of classes for display; group according to (class, slot)
1641 PartGroupsType part_groups = GroupAvailableDisplayableParts(empire);
1642
1643 // get empire id and location to use for cost and time comparisons
1644 int loc_id = INVALID_OBJECT_ID;
1645 if (empire) {
1646 auto location = Objects().get(empire->CapitalID());
1647 loc_id = location ? location->ID() : INVALID_OBJECT_ID;
1648 }
1649
1650 // if showing parts for a particular empire, cull redundant parts (if enabled)
1651 if (empire) {
1652 for (auto& part_group : part_groups) {
1653 ShipPartClass part_class = part_group.first.first;
1654 if (!m_show_superfluous_parts)
1655 CullSuperfluousParts(part_group.second, part_class, empire_id, loc_id);
1656 }
1657 }
1658
1659 // now sort the parts within each group according to main stat, via weak
1660 // sorting in a multimap also, if a part was in multiple groups due to being
1661 // compatible with multiple slot types, ensure it is only displayed once
1662 std::set<const ShipPart*> already_added;
1663 for (auto& part_group : part_groups) {
1664 std::multimap<double, const ShipPart*> sorted_group;
1665 for (const ShipPart* part : part_group.second) {
1666 if (already_added.count(part))
1667 continue;
1668 already_added.insert(part);
1669 sorted_group.insert({GetMainStat(part), part});
1670 }
1671
1672 // take the sorted parts and make UI elements (technically rows) for the PartsListBox
1673 for (auto& group : sorted_group) {
1674 const ShipPart* part = group.second;
1675 // check if current row is full, and make a new row if necessary
1676 if (cur_col >= NUM_COLUMNS) {
1677 if (cur_row)
1678 Insert(cur_row);
1679 cur_col = 0;
1680 cur_row = GG::Wnd::Create<PartsListBoxRow>(
1681 TOTAL_WIDTH, SLOT_CONTROL_HEIGHT + GG::Y(PAD), m_availabilities_state);
1682 }
1683 ++cur_col;
1684 ++num_parts;
1685
1686 // make new part control and add to row
1687 auto control = GG::Wnd::Create<PartControl>(part);
1688 control->ClickedSignal.connect(
1689 PartsListBox::ShipPartClickedSignal);
1690 control->DoubleClickedSignal.connect(
1691 PartsListBox::ShipPartDoubleClickedSignal);
1692 control->RightClickedSignal.connect(
1693 PartsListBox::ShipPartRightClickedSignal);
1694
1695 auto shown = m_availabilities_state.DisplayedPartAvailability(part->Name());
1696 if (shown)
1697 control->SetAvailability(*shown);
1698
1699 cur_row->push_back(control);
1700 }
1701 }
1702 // add any incomplete rows
1703 if (cur_row)
1704 Insert(cur_row);
1705
1706 // keep track of how many columns are present now
1707 m_previous_num_columns = NUM_COLUMNS;
1708
1709 // If there are no parts add a prompt to suggest a solution.
1710 if (num_parts == 0)
1711 Insert(GG::Wnd::Create<PromptRow>(TOTAL_WIDTH,
1712 UserString("ALL_AVAILABILITY_FILTERS_BLOCKING_PROMPT")),
1713 begin(), false);
1714
1715 }
1716
ShowClass(ShipPartClass part_class,bool refresh_list)1717 void PartsListBox::ShowClass(ShipPartClass part_class, bool refresh_list) {
1718 if (!m_part_classes_shown.count(part_class)) {
1719 m_part_classes_shown.insert(part_class);
1720 if (refresh_list)
1721 Populate();
1722 }
1723 }
1724
ShowAllClasses(bool refresh_list)1725 void PartsListBox::ShowAllClasses(bool refresh_list) {
1726 for (ShipPartClass part_class = ShipPartClass(0); part_class != NUM_SHIP_PART_CLASSES; part_class = ShipPartClass(part_class + 1))
1727 m_part_classes_shown.insert(part_class);
1728 if (refresh_list)
1729 Populate();
1730 }
1731
HideClass(ShipPartClass part_class,bool refresh_list)1732 void PartsListBox::HideClass(ShipPartClass part_class, bool refresh_list) {
1733 auto it = m_part_classes_shown.find(part_class);
1734 if (it != m_part_classes_shown.end()) {
1735 m_part_classes_shown.erase(it);
1736 if (refresh_list)
1737 Populate();
1738 }
1739 }
1740
HideAllClasses(bool refresh_list)1741 void PartsListBox::HideAllClasses(bool refresh_list) {
1742 m_part_classes_shown.clear();
1743 if (refresh_list)
1744 Populate();
1745 }
1746
ShowSuperfluousParts(bool refresh_list)1747 void PartsListBox::ShowSuperfluousParts(bool refresh_list) {
1748 if (m_show_superfluous_parts)
1749 return;
1750 m_show_superfluous_parts = true;
1751 if (refresh_list)
1752 Populate();
1753 }
1754
HideSuperfluousParts(bool refresh_list)1755 void PartsListBox::HideSuperfluousParts(bool refresh_list) {
1756 if (!m_show_superfluous_parts)
1757 return;
1758 m_show_superfluous_parts = false;
1759 if (refresh_list)
1760 Populate();
1761 }
1762
1763
1764 //////////////////////////////////////////////////
1765 // DesignWnd::PartPalette //
1766 //////////////////////////////////////////////////
1767 /** Contains graphical list of PartControl which can be dragged and dropped
1768 * onto slots to assign parts to those slots */
1769 class DesignWnd::PartPalette : public CUIWnd {
1770 public:
1771 /** \name Structors */ //@{
1772 PartPalette(const std::string& config_name);
1773 void CompleteConstruction() override;
1774 //@}
1775
1776 /** \name Mutators */ //@{
1777 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override;
1778
1779 void ShowClass(ShipPartClass part_class, bool refresh_list = true);
1780 void ShowAllClasses(bool refresh_list = true);
1781 void HideClass(ShipPartClass part_class, bool refresh_list = true);
1782 void HideAllClasses(bool refresh_list = true);
1783 void ToggleClass(ShipPartClass part_class, bool refresh_list = true);
1784 void ToggleAllClasses(bool refresh_list = true);
1785
1786 void ToggleAvailability(const Availability::Enum type);
1787
1788 void ShowSuperfluous(bool refresh_list = true);
1789 void HideSuperfluous(bool refresh_list = true);
1790 void ToggleSuperfluous(bool refresh_list = true);
1791
1792 void Populate();
1793 //@}
1794
1795 mutable boost::signals2::signal<void (const ShipPart*, GG::Flags<GG::ModKey>)> ShipPartClickedSignal;
1796 mutable boost::signals2::signal<void (const ShipPart*)> ShipPartDoubleClickedSignal;
1797 mutable boost::signals2::signal<void (const ShipPart*, const GG::Pt& pt)> ShipPartRightClickedSignal;
1798 mutable boost::signals2::signal<void ()> PartObsolescenceChangedSignal;
1799 mutable boost::signals2::signal<void (const std::string&)> ClearPartSignal;
1800
1801 private:
1802 void DoLayout();
1803
1804 /** A part type click with ctrl obsoletes part. */
1805 void HandleShipPartClicked(const ShipPart*, GG::Flags<GG::ModKey>);
1806 void HandleShipPartRightClicked(const ShipPart*, const GG::Pt& pt);
1807
1808 std::shared_ptr<PartsListBox> m_parts_list = nullptr;
1809 std::map<ShipPartClass, std::shared_ptr<CUIStateButton>> m_class_buttons;
1810 std::shared_ptr<CUIStateButton> m_superfluous_parts_button = nullptr;
1811
1812 // Holds the state of the availabilities filter.
1813 AvailabilityManager m_availabilities_state;
1814 std::tuple<std::shared_ptr<CUIStateButton>,
1815 std::shared_ptr<CUIStateButton>,
1816 std::shared_ptr<CUIStateButton>> m_availabilities_buttons;
1817
1818 };
1819
PartPalette(const std::string & config_name)1820 DesignWnd::PartPalette::PartPalette(const std::string& config_name) :
1821 CUIWnd(UserString("DESIGN_WND_PART_PALETTE_TITLE"),
1822 GG::ONTOP | GG::INTERACTIVE | GG::DRAGABLE | GG::RESIZABLE,
1823 config_name),
1824 m_availabilities_state(false, true, false)
1825 {}
1826
CompleteConstruction()1827 void DesignWnd::PartPalette::CompleteConstruction() {
1828 //TempUISoundDisabler sound_disabler; // should be redundant with disabler in DesignWnd::DesignWnd. uncomment if this is not the case
1829 SetChildClippingMode(ClipToClient);
1830
1831 #if BOOST_VERSION >= 106000
1832 using boost::placeholders::_1;
1833 using boost::placeholders::_2;
1834 #endif
1835
1836 m_parts_list = GG::Wnd::Create<PartsListBox>(m_availabilities_state);
1837 AttachChild(m_parts_list);
1838 m_parts_list->ShipPartClickedSignal.connect(
1839 boost::bind(&DesignWnd::PartPalette::HandleShipPartClicked, this, _1, _2));
1840 m_parts_list->ShipPartDoubleClickedSignal.connect(
1841 ShipPartDoubleClickedSignal);
1842 m_parts_list->ShipPartRightClickedSignal.connect(
1843 boost::bind(&DesignWnd::PartPalette::HandleShipPartRightClicked, this, _1, _2));
1844 m_parts_list->ClearPartSignal.connect(ClearPartSignal);
1845
1846 const ShipPartManager& part_manager = GetShipPartManager();
1847
1848 // class buttons
1849 for (ShipPartClass part_class = ShipPartClass(0); part_class != NUM_SHIP_PART_CLASSES; part_class = ShipPartClass(part_class + 1)) {
1850 // are there any parts of this class?
1851 bool part_of_this_class_exists = false;
1852 for (const auto& entry : part_manager) {
1853 if (const auto& part = entry.second) {
1854 if (part->Class() == part_class) {
1855 part_of_this_class_exists = true;
1856 break;
1857 }
1858 }
1859 }
1860 if (!part_of_this_class_exists)
1861 continue;
1862
1863 m_class_buttons[part_class] = GG::Wnd::Create<CUIStateButton>(UserString(boost::lexical_cast<std::string>(part_class)), GG::FORMAT_CENTER, std::make_shared<CUILabelButtonRepresenter>());
1864 AttachChild(m_class_buttons[part_class]);
1865 m_class_buttons[part_class]->CheckedSignal.connect(
1866 boost::bind(&DesignWnd::PartPalette::ToggleClass, this, part_class, true));
1867 }
1868
1869 // availability buttons
1870 // TODO: C++17, Collect and replace with structured binding auto [a, b, c] = m_availabilities;
1871 auto& m_obsolete_button = std::get<Availability::Obsolete>(m_availabilities_buttons);
1872 m_obsolete_button = GG::Wnd::Create<CUIStateButton>(UserString("PRODUCTION_WND_AVAILABILITY_OBSOLETE"), GG::FORMAT_CENTER, std::make_shared<CUILabelButtonRepresenter>());
1873 AttachChild(m_obsolete_button);
1874 m_obsolete_button->CheckedSignal.connect(
1875 boost::bind(&DesignWnd::PartPalette::ToggleAvailability, this, Availability::Obsolete));
1876 m_obsolete_button->SetCheck(m_availabilities_state.GetAvailability(Availability::Obsolete));
1877
1878 auto& m_available_button = std::get<Availability::Available>(m_availabilities_buttons);
1879 m_available_button = GG::Wnd::Create<CUIStateButton>(UserString("PRODUCTION_WND_AVAILABILITY_AVAILABLE"), GG::FORMAT_CENTER, std::make_shared<CUILabelButtonRepresenter>());
1880 AttachChild(m_available_button);
1881 m_available_button->CheckedSignal.connect(
1882 boost::bind(&DesignWnd::PartPalette::ToggleAvailability, this, Availability::Available));
1883 m_available_button->SetCheck(m_availabilities_state.GetAvailability(Availability::Available));
1884
1885 auto& m_unavailable_button = std::get<Availability::Future>(m_availabilities_buttons);
1886 m_unavailable_button = GG::Wnd::Create<CUIStateButton>(UserString("PRODUCTION_WND_AVAILABILITY_UNAVAILABLE"), GG::FORMAT_CENTER, std::make_shared<CUILabelButtonRepresenter>());
1887 AttachChild(m_unavailable_button);
1888 m_unavailable_button->CheckedSignal.connect(
1889 boost::bind(&DesignWnd::PartPalette::ToggleAvailability, this, Availability::Future));
1890 m_unavailable_button->SetCheck(m_availabilities_state.GetAvailability(Availability::Future));
1891
1892 // superfluous parts button
1893 m_superfluous_parts_button = GG::Wnd::Create<CUIStateButton>(UserString("PRODUCTION_WND_REDUNDANT"), GG::FORMAT_CENTER, std::make_shared<CUILabelButtonRepresenter>());
1894 AttachChild(m_superfluous_parts_button);
1895 m_superfluous_parts_button->CheckedSignal.connect(
1896 boost::bind(&DesignWnd::PartPalette::ToggleSuperfluous, this, true));
1897
1898 // default to showing nothing
1899 ShowAllClasses(false);
1900 ShowSuperfluous(false);
1901 Populate();
1902
1903 CUIWnd::CompleteConstruction();
1904
1905 SetMinSize(PALETTE_MIN_SIZE);
1906
1907 DoLayout();
1908 SaveDefaultedOptions();
1909 }
1910
SizeMove(const GG::Pt & ul,const GG::Pt & lr)1911 void DesignWnd::PartPalette::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
1912 CUIWnd::SizeMove(ul, lr);
1913 DoLayout();
1914 }
1915
DoLayout()1916 void DesignWnd::PartPalette::DoLayout() {
1917 const int PTS = ClientUI::Pts();
1918 const GG::X PTS_WIDE(PTS/2); // guess at how wide per character the font needs
1919 const GG::Y BUTTON_HEIGHT(PTS*3/2);
1920 const int BUTTON_SEPARATION = 3; // vertical or horizontal sepration between adjacent buttons
1921 const int BUTTON_EDGE_PAD = 2; // distance from edges of control to buttons
1922 const GG::X RIGHT_EDGE_PAD(8); // to account for border of CUIWnd
1923
1924 const GG::X USABLE_WIDTH = std::max(ClientWidth() - RIGHT_EDGE_PAD, GG::X1); // space in which to fit buttons
1925 const int GUESSTIMATE_NUM_CHARS_IN_BUTTON_LABEL = 14; // rough guesstimate... avoid overly long part class names
1926 const GG::X MIN_BUTTON_WIDTH = PTS_WIDE*GUESSTIMATE_NUM_CHARS_IN_BUTTON_LABEL;
1927 const int MAX_BUTTONS_PER_ROW = std::max(Value(USABLE_WIDTH / (MIN_BUTTON_WIDTH + BUTTON_SEPARATION)), 1);
1928
1929 const int NUM_CLASS_BUTTONS = std::max(1, static_cast<int>(m_class_buttons.size()));
1930 const int NUM_SUPERFLUOUS_CULL_BUTTONS = 1;
1931 const int NUM_AVAILABILITY_BUTTONS = 3;
1932 const int NUM_NON_CLASS_BUTTONS = NUM_SUPERFLUOUS_CULL_BUTTONS + NUM_AVAILABILITY_BUTTONS;
1933
1934 // determine whether to put non-class buttons (availability and redundancy)
1935 // in one column or two.
1936 // -> if class buttons fill up fewer rows than (the non-class buttons in one
1937 // column), split the non-class buttons into two columns
1938 int num_non_class_buttons_per_row = 1;
1939 if (NUM_CLASS_BUTTONS < NUM_NON_CLASS_BUTTONS*(MAX_BUTTONS_PER_ROW - num_non_class_buttons_per_row))
1940 num_non_class_buttons_per_row = 2;
1941
1942 const int MAX_CLASS_BUTTONS_PER_ROW = std::max(1, MAX_BUTTONS_PER_ROW - num_non_class_buttons_per_row);
1943
1944 const int NUM_CLASS_BUTTON_ROWS = static_cast<int>(std::ceil(static_cast<float>(NUM_CLASS_BUTTONS) / MAX_CLASS_BUTTONS_PER_ROW));
1945 const int NUM_CLASS_BUTTONS_PER_ROW = static_cast<int>(std::ceil(static_cast<float>(NUM_CLASS_BUTTONS) / NUM_CLASS_BUTTON_ROWS));
1946
1947 const int TOTAL_BUTTONS_PER_ROW = NUM_CLASS_BUTTONS_PER_ROW + num_non_class_buttons_per_row;
1948
1949 const GG::X BUTTON_WIDTH = (USABLE_WIDTH - (TOTAL_BUTTONS_PER_ROW - 1)*BUTTON_SEPARATION) / TOTAL_BUTTONS_PER_ROW;
1950
1951 const GG::X COL_OFFSET = BUTTON_WIDTH + BUTTON_SEPARATION; // horizontal distance between each column of buttons
1952 const GG::Y ROW_OFFSET = BUTTON_HEIGHT + BUTTON_SEPARATION; // vertical distance between each row of buttons
1953
1954 // place class buttons
1955 int col = NUM_CLASS_BUTTONS_PER_ROW;
1956 int row = -1;
1957 for (auto& entry : m_class_buttons) {
1958 if (col >= NUM_CLASS_BUTTONS_PER_ROW) {
1959 col = 0;
1960 ++row;
1961 }
1962 GG::Pt ul(BUTTON_EDGE_PAD + col*COL_OFFSET, BUTTON_EDGE_PAD + row*ROW_OFFSET);
1963 GG::Pt lr = ul + GG::Pt(BUTTON_WIDTH, BUTTON_HEIGHT);
1964 entry.second->SizeMove(ul, lr);
1965 ++col;
1966 }
1967
1968 // place parts list. note: assuming at least as many rows of class buttons as availability buttons, as should
1969 // be the case given how num_non_class_buttons_per_row is determined
1970 m_parts_list->SizeMove(GG::Pt(GG::X0, BUTTON_EDGE_PAD + ROW_OFFSET*(row + 1)),
1971 ClientSize() - GG::Pt(GG::X(BUTTON_SEPARATION), GG::Y(BUTTON_SEPARATION)));
1972
1973 // place slot type buttons
1974 col = NUM_CLASS_BUTTONS_PER_ROW;
1975 row = 0;
1976 auto ul = GG::Pt(BUTTON_EDGE_PAD + col*COL_OFFSET, BUTTON_EDGE_PAD + row*ROW_OFFSET);
1977 auto lr = ul + GG::Pt(BUTTON_WIDTH, BUTTON_HEIGHT);
1978 m_superfluous_parts_button->SizeMove(ul, lr);
1979
1980 // a function to place availability buttons either in a single column below the
1981 // superfluous button or to complete a 2X2 grid left of the class buttons.
1982 auto place_avail_button_adjacent =
1983 [&col, &row, &num_non_class_buttons_per_row, NUM_CLASS_BUTTONS_PER_ROW,
1984 BUTTON_EDGE_PAD, COL_OFFSET, ROW_OFFSET, BUTTON_WIDTH, BUTTON_HEIGHT]
1985 (GG::Wnd* avail_btn)
1986 {
1987 if (num_non_class_buttons_per_row == 1) {
1988 ++row;
1989 } else {
1990 if (col >= NUM_CLASS_BUTTONS_PER_ROW + num_non_class_buttons_per_row - 1) {
1991 col = NUM_CLASS_BUTTONS_PER_ROW - 1;
1992 ++row;
1993 }
1994 ++col;
1995 }
1996
1997 auto ul = GG::Pt(BUTTON_EDGE_PAD + col*COL_OFFSET, BUTTON_EDGE_PAD + row*ROW_OFFSET);
1998 auto lr = ul + GG::Pt(BUTTON_WIDTH, BUTTON_HEIGHT);
1999 avail_btn->SizeMove(ul, lr);
2000 };
2001
2002 //place availability buttons
2003 // TODO: C++17, Replace with structured binding auto [a, b, c] = m_availabilities;
2004 auto& m_obsolete_button = std::get<Availability::Obsolete>(m_availabilities_buttons);
2005 auto& m_available_button = std::get<Availability::Available>(m_availabilities_buttons);
2006 auto& m_unavailable_button = std::get<Availability::Future>(m_availabilities_buttons);
2007
2008 place_avail_button_adjacent(m_obsolete_button.get());
2009 place_avail_button_adjacent(m_available_button.get());
2010 place_avail_button_adjacent(m_unavailable_button.get());
2011 }
2012
HandleShipPartClicked(const ShipPart * part,GG::Flags<GG::ModKey> modkeys)2013 void DesignWnd::PartPalette::HandleShipPartClicked(const ShipPart* part, GG::Flags<GG::ModKey> modkeys) {
2014 // Toggle obsolete for a control click.
2015 if (modkeys & GG::MOD_KEY_CTRL) {
2016 auto& manager = GetDisplayedDesignsManager();
2017 const auto obsolete = manager.IsPartObsolete(part->Name());
2018 manager.SetPartObsolete(part->Name(), !obsolete);
2019
2020 PartObsolescenceChangedSignal();
2021 Populate();
2022 }
2023 else
2024 ShipPartClickedSignal(part, modkeys);
2025 }
2026
HandleShipPartRightClicked(const ShipPart * part,const GG::Pt & pt)2027 void DesignWnd::PartPalette::HandleShipPartRightClicked(const ShipPart* part, const GG::Pt& pt) {
2028 // Context menu actions
2029 auto& manager = GetDisplayedDesignsManager();
2030 const auto& part_name = part->Name();
2031 auto is_obsolete = manager.IsPartObsolete(part_name);
2032 auto toggle_obsolete_design_action = [&manager, &part_name, is_obsolete, this]() {
2033 manager.SetPartObsolete(part_name, !is_obsolete);
2034 PartObsolescenceChangedSignal();
2035 Populate();
2036 };
2037
2038 // create popup menu with a commands in it
2039 auto popup = GG::Wnd::Create<CUIPopupMenu>(pt.x, pt.y);
2040
2041 const auto empire_id = HumanClientApp::GetApp()->EmpireID();
2042 if (empire_id != ALL_EMPIRES)
2043 popup->AddMenuItem(GG::MenuItem(
2044 (is_obsolete
2045 ? UserString("DESIGN_WND_UNOBSOLETE_PART")
2046 : UserString("DESIGN_WND_OBSOLETE_PART")),
2047 false, false, toggle_obsolete_design_action));
2048
2049 popup->Run();
2050
2051 ShipPartRightClickedSignal(part, pt);
2052 }
2053
ShowClass(ShipPartClass part_class,bool refresh_list)2054 void DesignWnd::PartPalette::ShowClass(ShipPartClass part_class, bool refresh_list) {
2055 if (part_class >= ShipPartClass(0) && part_class < NUM_SHIP_PART_CLASSES) {
2056 m_parts_list->ShowClass(part_class, refresh_list);
2057 m_class_buttons[part_class]->SetCheck();
2058 } else {
2059 throw std::invalid_argument("PartPalette::ShowClass was passed an invalid ShipPartClass");
2060 }
2061 }
2062
ShowAllClasses(bool refresh_list)2063 void DesignWnd::PartPalette::ShowAllClasses(bool refresh_list) {
2064 m_parts_list->ShowAllClasses(refresh_list);
2065 for (auto& entry : m_class_buttons)
2066 entry.second->SetCheck();
2067 }
2068
HideClass(ShipPartClass part_class,bool refresh_list)2069 void DesignWnd::PartPalette::HideClass(ShipPartClass part_class, bool refresh_list) {
2070 if (part_class >= ShipPartClass(0) && part_class < NUM_SHIP_PART_CLASSES) {
2071 m_parts_list->HideClass(part_class, refresh_list);
2072 m_class_buttons[part_class]->SetCheck(false);
2073 } else {
2074 throw std::invalid_argument("PartPalette::HideClass was passed an invalid ShipPartClass");
2075 }
2076 }
2077
HideAllClasses(bool refresh_list)2078 void DesignWnd::PartPalette::HideAllClasses(bool refresh_list) {
2079 m_parts_list->HideAllClasses(refresh_list);
2080 for (auto& entry : m_class_buttons)
2081 entry.second->SetCheck(false);
2082 }
2083
ToggleClass(ShipPartClass part_class,bool refresh_list)2084 void DesignWnd::PartPalette::ToggleClass(ShipPartClass part_class, bool refresh_list) {
2085 if (part_class >= ShipPartClass(0) && part_class < NUM_SHIP_PART_CLASSES) {
2086 const auto& classes_shown = m_parts_list->GetClassesShown();
2087 if (!classes_shown.count(part_class))
2088 ShowClass(part_class, refresh_list);
2089 else
2090 HideClass(part_class, refresh_list);
2091 } else {
2092 throw std::invalid_argument("PartPalette::ToggleClass was passed an invalid ShipPartClass");
2093 }
2094 }
2095
ToggleAllClasses(bool refresh_list)2096 void DesignWnd::PartPalette::ToggleAllClasses(bool refresh_list)
2097 {
2098 const auto& classes_shown = m_parts_list->GetClassesShown();
2099 if (classes_shown.size() == NUM_SHIP_PART_CLASSES)
2100 HideAllClasses(refresh_list);
2101 else
2102 ShowAllClasses(refresh_list);
2103 }
2104
ToggleAvailability(Availability::Enum type)2105 void DesignWnd::PartPalette::ToggleAvailability(Availability::Enum type) {
2106 std::shared_ptr<CUIStateButton> button;
2107 bool state = false;
2108 switch (type) {
2109 case Availability::Obsolete:
2110 m_availabilities_state.ToggleAvailability(Availability::Obsolete);
2111 state = m_availabilities_state.GetAvailability(Availability::Obsolete);
2112 button = std::get<Availability::Obsolete>(m_availabilities_buttons);
2113 break;
2114 case Availability::Available:
2115 m_availabilities_state.ToggleAvailability(Availability::Available);
2116 state = m_availabilities_state.GetAvailability(Availability::Available);
2117 button = std::get<Availability::Available>(m_availabilities_buttons);
2118 break;
2119 case Availability::Future:
2120 m_availabilities_state.ToggleAvailability(Availability::Future);
2121 state = m_availabilities_state.GetAvailability(Availability::Future);
2122 button = std::get<Availability::Future>(m_availabilities_buttons);
2123 break;
2124 }
2125
2126 button->SetCheck(state);
2127
2128 Populate();
2129 }
2130
ShowSuperfluous(bool refresh_list)2131 void DesignWnd::PartPalette::ShowSuperfluous(bool refresh_list) {
2132 m_parts_list->ShowSuperfluousParts(refresh_list);
2133 m_superfluous_parts_button->SetCheck();
2134 }
2135
HideSuperfluous(bool refresh_list)2136 void DesignWnd::PartPalette::HideSuperfluous(bool refresh_list) {
2137 m_parts_list->HideSuperfluousParts(refresh_list);
2138 m_superfluous_parts_button->SetCheck(false);
2139 }
2140
ToggleSuperfluous(bool refresh_list)2141 void DesignWnd::PartPalette::ToggleSuperfluous(bool refresh_list) {
2142 bool showing_superfluous = m_parts_list->GetShowingSuperfluous();
2143 if (showing_superfluous)
2144 HideSuperfluous(refresh_list);
2145 else
2146 ShowSuperfluous(refresh_list);
2147 }
2148
Populate()2149 void DesignWnd::PartPalette::Populate()
2150 { m_parts_list->Populate(); }
2151
2152
2153 //////////////////////////////////////////////////
2154 // BasesListBox //
2155 //////////////////////////////////////////////////
2156 /** List of starting points for designs, such as empty hulls, existing designs
2157 * kept by this empire or seen elsewhere in the universe, design template
2158 * scripts or saved (on disk) designs from previous games. */
2159 class BasesListBox : public QueueListBox {
2160 public:
2161 static const std::string BASES_LIST_BOX_DROP_TYPE;
2162
2163 /** \name Structors */ //@{
2164 BasesListBox(const AvailabilityManager& availabilities_state,
2165 const boost::optional<std::string>& drop_type = boost::none,
2166 const boost::optional<std::string>& empty_prompt = boost::none);
2167 //@}
2168 void CompleteConstruction() override;
2169
2170 /** \name Accessors */ //@{
2171 //@}
2172
2173 /** \name Mutators */ //@{
2174 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override;
2175 void ChildrenDraggedAway(const std::vector<GG::Wnd*>& wnds, const GG::Wnd* destination) override;
QueueItemMoved(const GG::ListBox::iterator & row_it,const GG::ListBox::iterator & original_position_it)2176 virtual void QueueItemMoved(const GG::ListBox::iterator& row_it, const GG::ListBox::iterator& original_position_it)
2177 {}
2178 void SetEmpireShown(int empire_id, bool refresh_list = true);
2179 virtual void Populate();
2180 //@}
2181
2182 mutable boost::signals2::signal<void (int)> DesignSelectedSignal;
2183 mutable boost::signals2::signal<void (int)> DesignUpdatedSignal;
2184 mutable boost::signals2::signal<void (const std::string&, const std::vector<std::string>&)>
2185 DesignComponentsSelectedSignal;
2186 mutable boost::signals2::signal<void (const boost::uuids::uuid&)> SavedDesignSelectedSignal;
2187
2188 mutable boost::signals2::signal<void (const ShipDesign*)> DesignClickedSignal;
2189 mutable boost::signals2::signal<void (const ShipHull*)> HullClickedSignal;
2190 mutable boost::signals2::signal<void (const ShipDesign*)> DesignRightClickedSignal;
2191
2192 class HullAndNamePanel : public GG::Control {
2193 public:
2194 HullAndNamePanel(GG::X w, GG::Y h, const std::string& hull, const std::string& name);
2195
2196 void CompleteConstruction() override;
2197 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override;
2198
Render()2199 void Render() override
2200 {}
2201
2202 void SetAvailability(const AvailabilityManager::DisplayedAvailabilies& type);
2203 void SetDisplayName(const std::string& name);
2204
2205 private:
2206 std::shared_ptr<GG::StaticGraphic> m_graphic = nullptr;
2207 std::shared_ptr<GG::Label> m_name = nullptr;
2208 };
2209
2210 class BasesListBoxRow : public CUIListBox::Row {
2211 public:
2212 BasesListBoxRow(GG::X w, GG::Y h, const std::string& hull, const std::string& name);
2213
2214 void CompleteConstruction() override;
2215 void Render() override;
2216
2217 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override;
2218
2219 virtual void SetAvailability(const AvailabilityManager::DisplayedAvailabilies& type);
2220 virtual void SetDisplayName(const std::string& name);
2221
2222 private:
2223 std::shared_ptr<HullAndNamePanel> m_hull_panel = nullptr;
2224 };
2225
2226 class HullAndPartsListBoxRow : public BasesListBoxRow {
2227 public:
2228 HullAndPartsListBoxRow(GG::X w, GG::Y h, const std::string& hull,
2229 const std::vector<std::string>& parts);
2230 void CompleteConstruction() override;
Hull() const2231 const std::string& Hull() const { return m_hull_name; }
Parts() const2232 const std::vector<std::string>& Parts() const { return m_parts; }
2233
2234 protected:
2235 std::string m_hull_name;
2236 std::vector<std::string> m_parts;
2237 };
2238
2239 class CompletedDesignListBoxRow : public BasesListBoxRow {
2240 public:
2241 CompletedDesignListBoxRow(GG::X w, GG::Y h, const ShipDesign& design);
2242 void CompleteConstruction() override;
DesignID() const2243 int DesignID() const { return m_design_id; }
2244 private:
2245 int m_design_id = INVALID_DESIGN_ID;
2246 };
2247
2248 protected:
2249 void ItemRightClickedImpl(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2250
2251 /** An implementation of BasesListBox provides a PopulateCore to fill itself.*/
2252 virtual void PopulateCore() = 0;
2253
2254 /** Reset the empty list prompt. */
2255 virtual void ResetEmptyListPrompt();
2256
2257 /** If \p wnd is a valid dragged child return a replacement row. Otherwise return nullptr. */
2258 virtual std::shared_ptr<Row> ChildrenDraggedAwayCore(const GG::Wnd* const wnd) = 0;
2259
2260 /** \name Accessors for derived classes. */ //@{
EmpireID() const2261 int EmpireID() const { return m_empire_id_shown; }
2262
AvailabilityState() const2263 const AvailabilityManager& AvailabilityState() const
2264 { return m_availabilities_state; }
2265
2266 GG::Pt ListRowSize();
2267 //@}
2268
BaseDoubleClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2269 virtual void BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys)
2270 {}
BaseLeftClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2271 virtual void BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys)
2272 {}
BaseRightClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2273 virtual void BaseRightClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys)
2274 {}
2275
2276 private:
2277 void InitRowSizes();
2278
2279 int m_empire_id_shown = ALL_EMPIRES;
2280 const AvailabilityManager& m_availabilities_state;
2281 boost::signals2::connection m_empire_designs_changed_signal;
2282 };
2283
HullAndNamePanel(GG::X w,GG::Y h,const std::string & hull,const std::string & name)2284 BasesListBox::HullAndNamePanel::HullAndNamePanel(GG::X w, GG::Y h, const std::string& hull, const std::string& name) :
2285 GG::Control(GG::X0, GG::Y0, w, h, GG::NO_WND_FLAGS)
2286 {
2287 SetChildClippingMode(ClipToClient);
2288
2289 m_graphic = GG::Wnd::Create<GG::StaticGraphic>(ClientUI::HullIcon(hull),
2290 GG::GRAPHIC_PROPSCALE | GG::GRAPHIC_FITGRAPHIC);
2291 m_graphic->Resize(GG::Pt(w, h));
2292 m_name = GG::Wnd::Create<CUILabel>(name, GG::FORMAT_WORDBREAK | GG::FORMAT_CENTER | GG::FORMAT_TOP);
2293 }
2294
CompleteConstruction()2295 void BasesListBox::HullAndNamePanel::CompleteConstruction() {
2296 GG::Control::CompleteConstruction();
2297 AttachChild(m_graphic);
2298 AttachChild(m_name);
2299 }
2300
SizeMove(const GG::Pt & ul,const GG::Pt & lr)2301 void BasesListBox::HullAndNamePanel::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
2302 GG::Control::SizeMove(ul, lr);
2303 m_graphic->Resize(Size());
2304 m_name->Resize(Size());
2305 }
2306
SetAvailability(const AvailabilityManager::DisplayedAvailabilies & type)2307 void BasesListBox::HullAndNamePanel::SetAvailability(
2308 const AvailabilityManager::DisplayedAvailabilies& type)
2309 {
2310 auto disabled = std::get<Availability::Obsolete>(type);
2311 m_graphic->Disable(disabled);
2312 m_name->Disable(disabled);
2313 }
2314
SetDisplayName(const std::string & name)2315 void BasesListBox::HullAndNamePanel::SetDisplayName(const std::string& name) {
2316 m_name->SetText(name);
2317 m_name->Resize(GG::Pt(Width(), m_name->Height()));
2318 }
2319
BasesListBoxRow(GG::X w,GG::Y h,const std::string & hull,const std::string & name)2320 BasesListBox::BasesListBoxRow::BasesListBoxRow(GG::X w, GG::Y h, const std::string& hull, const std::string& name) :
2321 CUIListBox::Row(w, h)
2322 {
2323 SetDragDropDataType(BASES_LIST_BOX_DROP_TYPE);
2324 if (hull.empty()) {
2325 ErrorLogger() << "No hull name provided for ship row display.";
2326 return;
2327 }
2328
2329 m_hull_panel = GG::Wnd::Create<HullAndNamePanel>(w, h, hull, name);
2330
2331 SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
2332 }
2333
CompleteConstruction()2334 void BasesListBox::BasesListBoxRow::CompleteConstruction() {
2335 CUIListBox::Row::CompleteConstruction();
2336 push_back(m_hull_panel);
2337 }
2338
Render()2339 void BasesListBox::BasesListBoxRow::Render() {
2340 GG::Pt ul = UpperLeft();
2341 GG::Pt lr = LowerRight();
2342 GG::Pt ul_adjusted_for_drop_indicator = GG::Pt(ul.x, ul.y + GG::Y(1));
2343 GG::Pt lr_adjusted_for_drop_indicator = GG::Pt(lr.x, lr.y - GG::Y(2));
2344 GG::FlatRectangle(ul_adjusted_for_drop_indicator, lr_adjusted_for_drop_indicator,
2345 ClientUI::WndColor(),
2346 (Disabled() ? DisabledColor(GG::CLR_WHITE) : GG::CLR_WHITE), 1);
2347 }
2348
SizeMove(const GG::Pt & ul,const GG::Pt & lr)2349 void BasesListBox::BasesListBoxRow::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
2350 const GG::Pt old_size = Size();
2351 CUIListBox::Row::SizeMove(ul, lr);
2352 if (!empty() && old_size != Size())
2353 at(0)->Resize(Size());
2354 }
2355
SetAvailability(const AvailabilityManager::DisplayedAvailabilies & type)2356 void BasesListBox::BasesListBoxRow::SetAvailability(const AvailabilityManager::DisplayedAvailabilies& type) {
2357 if (std::get<Availability::Obsolete>(type) && std::get<Availability::Future>(type))
2358 SetBrowseText(UserString("PRODUCTION_WND_AVAILABILITY_OBSOLETE_AND_UNAVAILABLE"));
2359 else if (std::get<Availability::Obsolete>(type))
2360 SetBrowseText(UserString("PRODUCTION_WND_AVAILABILITY_OBSOLETE"));
2361 else if (std::get<Availability::Future>(type))
2362 SetBrowseText(UserString("PRODUCTION_WND_AVAILABILITY_UNAVAILABLE"));
2363 else
2364 ClearBrowseInfoWnd();
2365
2366 auto disabled = std::get<Availability::Obsolete>(type);
2367 Disable(disabled);
2368 if (m_hull_panel)
2369 m_hull_panel->SetAvailability(type);
2370 }
2371
SetDisplayName(const std::string & name)2372 void BasesListBox::BasesListBoxRow::SetDisplayName(const std::string& name) {
2373 if (m_hull_panel)
2374 m_hull_panel->SetDisplayName(name);
2375 }
2376
HullAndPartsListBoxRow(GG::X w,GG::Y h,const std::string & hull,const std::vector<std::string> & parts)2377 BasesListBox::HullAndPartsListBoxRow::HullAndPartsListBoxRow(GG::X w, GG::Y h, const std::string& hull,
2378 const std::vector<std::string>& parts) :
2379 BasesListBoxRow(w, h, hull, UserString(hull)),
2380 m_hull_name(hull),
2381 m_parts(parts)
2382 {}
2383
CompleteConstruction()2384 void BasesListBox::HullAndPartsListBoxRow::CompleteConstruction() {
2385 BasesListBoxRow::CompleteConstruction();
2386 SetDragDropDataType(HULL_PARTS_ROW_DROP_TYPE_STRING);
2387 }
2388
CompletedDesignListBoxRow(GG::X w,GG::Y h,const ShipDesign & design)2389 BasesListBox::CompletedDesignListBoxRow::CompletedDesignListBoxRow(
2390 GG::X w, GG::Y h, const ShipDesign &design) :
2391 BasesListBoxRow(w, h, design.Hull(), design.Name()),
2392 m_design_id(design.ID())
2393 {}
2394
CompleteConstruction()2395 void BasesListBox::CompletedDesignListBoxRow::CompleteConstruction() {
2396 BasesListBoxRow::CompleteConstruction();
2397 SetDragDropDataType(COMPLETE_DESIGN_ROW_DROP_STRING);
2398 }
2399
2400 const std::string BasesListBox::BASES_LIST_BOX_DROP_TYPE = "BasesListBoxRow";
2401
BasesListBox(const AvailabilityManager & availabilities_state,const boost::optional<std::string> & drop_type,const boost::optional<std::string> & empty_prompt)2402 BasesListBox::BasesListBox(const AvailabilityManager& availabilities_state,
2403 const boost::optional<std::string>& drop_type,
2404 const boost::optional<std::string>& empty_prompt /*= boost::none*/) :
2405 QueueListBox(drop_type,
2406 empty_prompt ? *empty_prompt : UserString("ADD_FIRST_DESIGN_DESIGN_QUEUE_PROMPT")),
2407 m_empire_id_shown(ALL_EMPIRES),
2408 m_availabilities_state(availabilities_state)
2409 {}
2410
CompleteConstruction()2411 void BasesListBox::CompleteConstruction() {
2412 QueueListBox::CompleteConstruction();
2413
2414 InitRowSizes();
2415 SetStyle(GG::LIST_NOSEL | GG::LIST_NOSORT);
2416
2417 #if BOOST_VERSION >= 106000
2418 using boost::placeholders::_1;
2419 using boost::placeholders::_2;
2420 using boost::placeholders::_3;
2421 #endif
2422
2423 DoubleClickedRowSignal.connect(boost::bind(&BasesListBox::BaseDoubleClicked, this, _1, _2, _3));
2424 LeftClickedRowSignal.connect(boost::bind(&BasesListBox::BaseLeftClicked, this, _1, _2, _3));
2425 MovedRowSignal.connect(boost::bind(&BasesListBox::QueueItemMoved, this, _1, _2));
2426
2427 EnableOrderIssuing(false);
2428 }
2429
SizeMove(const GG::Pt & ul,const GG::Pt & lr)2430 void BasesListBox::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
2431 const GG::Pt old_size = Size();
2432 CUIListBox::SizeMove(ul, lr);
2433 if (old_size != Size()) {
2434 const GG::Pt row_size = ListRowSize();
2435 for (auto& row : *this)
2436 row->Resize(row_size);
2437 }
2438 }
2439
ChildrenDraggedAway(const std::vector<GG::Wnd * > & wnds,const GG::Wnd * destination)2440 void BasesListBox::ChildrenDraggedAway(const std::vector<GG::Wnd*>& wnds, const GG::Wnd* destination) {
2441 if (MatchesOrContains(this, destination))
2442 return;
2443 if (wnds.empty())
2444 return;
2445 if (wnds.size() != 1)
2446 ErrorLogger() << "BasesListBox::ChildrenDraggedAway unexpected informed that multiple Wnds were dragged away...";
2447 const GG::Wnd* wnd = wnds.front(); // should only be one wnd in list as BasesListBost doesn't allow selection, so dragging is only one-at-a-time
2448 const auto control = dynamic_cast<const GG::Control*>(wnd);
2449 if (!control)
2450 return;
2451
2452 Row* original_row = boost::polymorphic_downcast<Row*>(*wnds.begin());
2453 iterator insertion_point = std::find_if(
2454 begin(), end(), [&original_row](const std::shared_ptr<Row>& xx){return xx.get() == original_row;});
2455 if (insertion_point != end())
2456 ++insertion_point;
2457
2458 // replace dragged-away control with new copy
2459 auto row = ChildrenDraggedAwayCore(wnd);
2460 if (row) {
2461 Insert(row, insertion_point);
2462 row->Resize(ListRowSize());
2463 }
2464
2465 // remove dragged-away row from this ListBox
2466 CUIListBox::ChildrenDraggedAway(wnds, destination);
2467 DetachChild(wnds.front());
2468 }
2469
SetEmpireShown(int empire_id,bool refresh_list)2470 void BasesListBox::SetEmpireShown(int empire_id, bool refresh_list) {
2471 m_empire_id_shown = empire_id;
2472
2473 // disconnect old signal
2474 m_empire_designs_changed_signal.disconnect();
2475
2476 // connect signal to update this list if the empire's designs change
2477 if (const Empire* empire = GetEmpire(m_empire_id_shown))
2478 m_empire_designs_changed_signal = empire->ShipDesignsChangedSignal.connect(
2479 boost::bind(&BasesListBox::Populate, this));
2480
2481 if (refresh_list)
2482 Populate();
2483 }
2484
Populate()2485 void BasesListBox::Populate() {
2486 DebugLogger() << "BasesListBox::Populate";
2487
2488 // Provide conditional reminder text when the list is empty
2489 if (AvailabilityState().GetAvailabilities() == AvailabilityManager::DisplayedAvailabilies(false, false, false))
2490 SetEmptyPromptText(UserString("ALL_AVAILABILITY_FILTERS_BLOCKING_PROMPT"));
2491 else
2492 this->ResetEmptyListPrompt();
2493
2494 // make note of first visible row to preserve state
2495 auto init_first_row_shown = FirstRowShown();
2496 std::size_t init_first_row_offset = std::distance(begin(), init_first_row_shown);
2497
2498 Clear();
2499
2500 this->PopulateCore();
2501
2502 if (!Empty())
2503 BringRowIntoView(--end());
2504 if (init_first_row_offset < NumRows())
2505 BringRowIntoView(std::next(begin(), init_first_row_offset));
2506
2507 }
2508
ListRowSize()2509 GG::Pt BasesListBox::ListRowSize()
2510 { return GG::Pt(Width() - ClientUI::ScrollWidth() - 5, BASES_LIST_BOX_ROW_HEIGHT); }
2511
InitRowSizes()2512 void BasesListBox::InitRowSizes() {
2513 // preinitialize listbox/row column widths, because what
2514 // ListBox::Insert does on default is not suitable for this case
2515 ManuallyManageColProps();
2516 NormalizeRowsOnInsert(false);
2517 }
2518
ItemRightClickedImpl(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2519 void BasesListBox::ItemRightClickedImpl(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys)
2520 { this->BaseRightClicked(it, pt, modkeys); }
2521
2522
2523 //////////////////////////////////////////////////
2524 // BasesListBox derived classes //
2525 //////////////////////////////////////////////////
2526 class EmptyHullsListBox : public BasesListBox {
2527 public:
EmptyHullsListBox(const AvailabilityManager & availabilities_state,const boost::optional<std::string> & drop_type=boost::none)2528 EmptyHullsListBox(const AvailabilityManager& availabilities_state,
2529 const boost::optional<std::string>& drop_type = boost::none) :
2530 BasesListBox::BasesListBox(availabilities_state, drop_type, UserString("ALL_AVAILABILITY_FILTERS_BLOCKING_PROMPT"))
2531 {}
2532
2533 void EnableOrderIssuing(bool enable = true) override;
2534
2535 protected:
2536 void PopulateCore() override;
2537 std::shared_ptr<Row> ChildrenDraggedAwayCore(const GG::Wnd* const wnd) override;
2538 void QueueItemMoved(const GG::ListBox::iterator& row_it, const GG::ListBox::iterator& original_position_it) override;
2539
2540 void BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2541 void BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2542 void BaseRightClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2543 };
2544
2545 class CompletedDesignsListBox : public BasesListBox {
2546 public:
CompletedDesignsListBox(const AvailabilityManager & availabilities_state,const boost::optional<std::string> & drop_type=boost::none)2547 CompletedDesignsListBox(const AvailabilityManager& availabilities_state,
2548 const boost::optional<std::string>& drop_type = boost::none) :
2549 BasesListBox::BasesListBox(availabilities_state, drop_type)
2550 {};
2551
2552 protected:
2553 void PopulateCore() override;
2554 void ResetEmptyListPrompt() override;
2555 std::shared_ptr<Row> ChildrenDraggedAwayCore(const GG::Wnd* const wnd) override;
2556 void QueueItemMoved(const GG::ListBox::iterator& row_it, const GG::ListBox::iterator& original_position_it) override;
2557 void BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2558 void BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2559 void BaseRightClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2560 };
2561
2562 class SavedDesignsListBox : public BasesListBox {
2563 public:
SavedDesignsListBox(const AvailabilityManager & availabilities_state,const boost::optional<std::string> & drop_type=boost::none)2564 SavedDesignsListBox(const AvailabilityManager& availabilities_state,
2565 const boost::optional<std::string>& drop_type = boost::none) :
2566 BasesListBox::BasesListBox(availabilities_state, drop_type, UserString("ADD_FIRST_SAVED_DESIGN_QUEUE_PROMPT"))
2567 {};
2568
2569 class SavedDesignListBoxRow : public BasesListBoxRow {
2570 public:
2571 SavedDesignListBoxRow(GG::X w, GG::Y h, const ShipDesign& design);
2572 void CompleteConstruction() override;
2573 const boost::uuids::uuid DesignUUID() const;
2574 const std::string& DesignName() const;
2575 const std::string& Description() const;
2576 bool LookupInStringtable() const;
2577
2578 private:
2579 boost::uuids::uuid m_design_uuid;
2580 };
2581
2582 protected:
2583 void PopulateCore() override;
2584 void ResetEmptyListPrompt() override;
2585 std::shared_ptr<Row> ChildrenDraggedAwayCore(const GG::Wnd* const wnd) override;
2586 void QueueItemMoved(const GG::ListBox::iterator& row_it, const GG::ListBox::iterator& original_position_it) override;
2587
2588 void BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2589 void BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2590 void BaseRightClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2591 };
2592
2593 class MonstersListBox : public BasesListBox {
2594 public:
MonstersListBox(const AvailabilityManager & availabilities_state,const boost::optional<std::string> & drop_type=boost::none)2595 MonstersListBox(const AvailabilityManager& availabilities_state,
2596 const boost::optional<std::string>& drop_type = boost::none) :
2597 BasesListBox::BasesListBox(availabilities_state, drop_type)
2598 {}
2599
2600 void EnableOrderIssuing(bool enable = true) override;
2601
2602 protected:
2603 void PopulateCore() override;
2604 std::shared_ptr<Row> ChildrenDraggedAwayCore(const GG::Wnd* const wnd) override;
2605
2606 void BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2607 void BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2608 };
2609
2610 class AllDesignsListBox : public BasesListBox {
2611 public:
AllDesignsListBox(const AvailabilityManager & availabilities_state,const boost::optional<std::string> & drop_type=boost::none)2612 AllDesignsListBox(const AvailabilityManager& availabilities_state,
2613 const boost::optional<std::string>& drop_type = boost::none) :
2614 BasesListBox::BasesListBox(availabilities_state, drop_type)
2615 {}
2616
2617 protected:
2618 void PopulateCore() override;
2619 std::shared_ptr<Row> ChildrenDraggedAwayCore(const GG::Wnd* const wnd) override;
2620
2621 void BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2622 void BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override;
2623 };
2624
PopulateCore()2625 void EmptyHullsListBox::PopulateCore() {
2626 ScopedTimer scoped_timer("EmptyHulls::PopulateCore");
2627 DebugLogger() << "EmptyHulls::PopulateCore EmpireID(): " << EmpireID();
2628
2629 const GG::Pt row_size = ListRowSize();
2630
2631 const auto& manager = GetDisplayedDesignsManager();
2632
2633 auto hulls = manager.OrderedHulls();
2634 if (hulls.size() < GetShipHullManager().size()) {
2635 ErrorLogger() << "EmptyHulls::PopulateCoreordered has fewer than expected entries...";
2636 for (auto& hull : GetShipHullManager()) {
2637 auto it = std::find(hulls.begin(), hulls.end(), hull.first);
2638 if (it == hulls.end())
2639 hulls.push_back(hull.first); // O(N^2) in loop, but I don't care...
2640 }
2641 }
2642
2643 for (const auto& hull_name : hulls) {
2644 const auto& ship_hull = GetShipHullManager().GetShipHull(hull_name);
2645
2646 if (!ship_hull || !ship_hull->Producible())
2647 continue;
2648
2649 auto shown = AvailabilityState().DisplayedHullAvailability(hull_name);
2650 if (!shown)
2651 continue;
2652 const std::vector<std::string> empty_parts_vec;
2653 auto row = GG::Wnd::Create<HullAndPartsListBoxRow>(row_size.x, row_size.y, hull_name, empty_parts_vec);
2654 row->SetAvailability(*shown);
2655 Insert(row);
2656 row->Resize(row_size);
2657 }
2658 }
2659
PopulateCore()2660 void CompletedDesignsListBox::PopulateCore() {
2661 ScopedTimer scoped_timer("CompletedDesignsListBox::PopulateCore");
2662 DebugLogger() << "CompletedDesignsListBox::PopulateCore for empire " << EmpireID();
2663
2664 const bool showing_available = AvailabilityState().GetAvailability(Availability::Available);
2665 const Universe& universe = GetUniverse();
2666 const GG::Pt row_size = ListRowSize();
2667
2668 if (const auto empire = GetEmpire(EmpireID())) {
2669 // add rows for designs this empire is keeping
2670 const auto& manager = GetDisplayedDesignsManager();
2671 for (int design_id : manager.AllOrderedIDs()) {
2672 try {
2673 const ShipDesign* design = GetShipDesign(design_id);
2674 if (!design)
2675 continue;
2676
2677 auto shown = AvailabilityState().DisplayedDesignAvailability(*design);
2678 if (shown) {
2679 auto row = GG::Wnd::Create<CompletedDesignListBoxRow>(row_size.x, row_size.y, *design);
2680 row->SetAvailability(*shown);
2681 Insert(row);
2682 row->Resize(row_size);
2683 }
2684 } catch (const std::out_of_range&) {
2685 ErrorLogger() << "ship design with id " << design_id << " incorrectly stored in manager.";
2686 }
2687 }
2688
2689 } else if (showing_available) {
2690 // add all known / existing designs
2691 for (auto it = universe.beginShipDesigns();
2692 it != universe.endShipDesigns(); ++it)
2693 {
2694 const ShipDesign* design = it->second;
2695 if (!design->Producible())
2696 continue;
2697 auto row = GG::Wnd::Create<CompletedDesignListBoxRow>(row_size.x, row_size.y, *design);
2698 Insert(row);
2699 row->Resize(row_size);
2700 }
2701 }
2702 }
2703
PopulateCore()2704 void SavedDesignsListBox::PopulateCore() {
2705 ScopedTimer scoped_timer("CompletedDesigns::PopulateCore");
2706 DebugLogger() << "CompletedDesigns::PopulateCore";
2707
2708 const GG::Pt row_size = ListRowSize();
2709
2710 for (const auto& uuid : GetSavedDesignsManager().OrderedDesignUUIDs()) {
2711 const auto design = GetSavedDesignsManager().GetDesign(uuid);
2712 auto shown = AvailabilityState().DisplayedDesignAvailability(*design);
2713 if (!shown)
2714 continue;
2715
2716 auto row = GG::Wnd::Create<SavedDesignListBoxRow>(row_size.x, row_size.y, *design);
2717 Insert(row);
2718 row->Resize(row_size);
2719 row->SetAvailability(*shown);
2720 }
2721 }
2722
PopulateCore()2723 void MonstersListBox::PopulateCore() {
2724 ScopedTimer scoped_timer("Monsters::PopulateCore");
2725
2726 const Universe& universe = GetUniverse();
2727
2728 const GG::Pt row_size = ListRowSize();
2729
2730 for (auto it = universe.beginShipDesigns();
2731 it != universe.endShipDesigns(); ++it)
2732 {
2733 const ShipDesign* design = it->second;
2734 if (!design->IsMonster())
2735 continue;
2736 auto row = GG::Wnd::Create<CompletedDesignListBoxRow>(row_size.x, row_size.y, *design);
2737 Insert(row);
2738 row->Resize(row_size);
2739 }
2740 }
2741
PopulateCore()2742 void AllDesignsListBox::PopulateCore() {
2743 ScopedTimer scoped_timer("All::PopulateCore");
2744
2745 const Universe& universe = GetUniverse();
2746
2747 const GG::Pt row_size = ListRowSize();
2748
2749 for (auto it = universe.beginShipDesigns();
2750 it != universe.endShipDesigns(); ++it)
2751 {
2752 const ShipDesign* design = it->second;
2753 auto row = GG::Wnd::Create<CompletedDesignListBoxRow>(row_size.x, row_size.y, *design);
2754 Insert(row);
2755 row->Resize(row_size);
2756 }
2757 }
2758
2759
ResetEmptyListPrompt()2760 void BasesListBox::ResetEmptyListPrompt()
2761 { SetEmptyPromptText(UserString("ALL_AVAILABILITY_FILTERS_BLOCKING_PROMPT")); }
2762
ResetEmptyListPrompt()2763 void CompletedDesignsListBox::ResetEmptyListPrompt() {
2764 if (!GetOptionsDB().Get<bool>("resource.shipdesign.saved.enabled")
2765 && !GetOptionsDB().Get<bool>("resource.shipdesign.default.enabled"))
2766 {
2767 SetEmptyPromptText(UserString("NO_SAVED_OR_DEFAULT_DESIGNS_ADDED_PROMPT"));
2768 } else {
2769 SetEmptyPromptText(UserString("ADD_FIRST_DESIGN_DESIGN_QUEUE_PROMPT"));
2770 }
2771 }
2772
ResetEmptyListPrompt()2773 void SavedDesignsListBox::ResetEmptyListPrompt()
2774 { SetEmptyPromptText(UserString("ADD_FIRST_SAVED_DESIGN_QUEUE_PROMPT")); }
2775
2776
ChildrenDraggedAwayCore(const GG::Wnd * const wnd)2777 std::shared_ptr<BasesListBox::Row> EmptyHullsListBox::ChildrenDraggedAwayCore(const GG::Wnd* const wnd) {
2778 // find type of hull that was dragged away, and replace
2779 const auto design_row = dynamic_cast<const BasesListBox::HullAndPartsListBoxRow*>(wnd);
2780 if (!design_row)
2781 return nullptr;
2782
2783 const std::string& hull_name = design_row->Hull();
2784 const auto row_size = ListRowSize();
2785 std::vector<std::string> empty_parts_vec;
2786 auto row = GG::Wnd::Create<HullAndPartsListBoxRow>(row_size.x, row_size.y, hull_name, empty_parts_vec);
2787
2788 if (auto shown = AvailabilityState().DisplayedHullAvailability(hull_name))
2789 row->SetAvailability(*shown);
2790
2791 return row;
2792 }
2793
ChildrenDraggedAwayCore(const GG::Wnd * const wnd)2794 std::shared_ptr<BasesListBox::Row> CompletedDesignsListBox::ChildrenDraggedAwayCore(const GG::Wnd* const wnd) {
2795 // find design that was dragged away, and replace
2796
2797 const auto design_row = dynamic_cast<const BasesListBox::CompletedDesignListBoxRow*>(wnd);
2798 if (!design_row)
2799 return nullptr;
2800
2801 int design_id = design_row->DesignID();
2802 const ShipDesign* design = GetShipDesign(design_id);
2803 if (!design) {
2804 ErrorLogger() << "Missing design with id " << design_id;
2805 return nullptr;
2806 }
2807
2808 const auto row_size = ListRowSize();
2809 auto row = GG::Wnd::Create<CompletedDesignListBoxRow>(row_size.x, row_size.y, *design);
2810 if (auto shown = AvailabilityState().DisplayedDesignAvailability(*design))
2811 row->SetAvailability(*shown);
2812 return row;
2813 }
2814
ChildrenDraggedAwayCore(const GG::Wnd * const wnd)2815 std::shared_ptr<BasesListBox::Row> SavedDesignsListBox::ChildrenDraggedAwayCore(const GG::Wnd* const wnd) {
2816 // find name of design that was dragged away, and replace
2817 const auto design_row = dynamic_cast<const SavedDesignsListBox::SavedDesignListBoxRow*>(wnd);
2818 if (!design_row)
2819 return nullptr;
2820
2821 SavedDesignsManager& manager = GetSavedDesignsManager();
2822 const auto design = manager.GetDesign(design_row->DesignUUID());
2823 if (!design) {
2824 ErrorLogger() << "Saved design missing with uuid " << design_row->DesignUUID();
2825 return nullptr;
2826 }
2827
2828 const auto row_size = ListRowSize();
2829 auto row = GG::Wnd::Create<SavedDesignListBoxRow>(row_size.x, row_size.y, *design);
2830
2831 if (auto shown = AvailabilityState().DisplayedDesignAvailability(*design))
2832 row->SetAvailability(*shown);
2833
2834 return row;
2835 }
2836
ChildrenDraggedAwayCore(const GG::Wnd * const wnd)2837 std::shared_ptr<BasesListBox::Row> MonstersListBox::ChildrenDraggedAwayCore(const GG::Wnd* const wnd) {
2838 // Replace the design that was dragged away
2839 const auto design_row = dynamic_cast<const BasesListBox::CompletedDesignListBoxRow*>(wnd);
2840 if (!design_row)
2841 return nullptr;
2842
2843 int design_id = design_row->DesignID();
2844 const ShipDesign* design = GetShipDesign(design_id);
2845 if (!design) {
2846 ErrorLogger() << "Missing design with id " << design_id;
2847 return nullptr;
2848 }
2849
2850 const auto row_size = ListRowSize();
2851 auto row = GG::Wnd::Create<CompletedDesignListBoxRow>(row_size.x, row_size.y, *design);
2852 return row;
2853 }
2854
ChildrenDraggedAwayCore(const GG::Wnd * const wnd)2855 std::shared_ptr<BasesListBox::Row> AllDesignsListBox::ChildrenDraggedAwayCore(const GG::Wnd* const wnd) {
2856 // Replace the design that was dragged away
2857 const auto design_row = dynamic_cast<const BasesListBox::CompletedDesignListBoxRow*>(wnd);
2858 if (!design_row)
2859 return nullptr;
2860
2861 int design_id = design_row->DesignID();
2862 const ShipDesign* design = GetShipDesign(design_id);
2863 if (!design) {
2864 ErrorLogger() << "Missing design with id " << design_id;
2865 return nullptr;
2866 }
2867
2868 const auto row_size = ListRowSize();
2869 auto row = GG::Wnd::Create<CompletedDesignListBoxRow>(row_size.x, row_size.y, *design);
2870 return row;
2871 }
2872
2873
EnableOrderIssuing(bool enable)2874 void EmptyHullsListBox::EnableOrderIssuing(bool enable/* = true*/)
2875 { QueueListBox::EnableOrderIssuing(enable); }
2876
EnableOrderIssuing(bool)2877 void MonstersListBox::EnableOrderIssuing(bool)
2878 { QueueListBox::EnableOrderIssuing(false); }
2879
2880
BaseDoubleClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2881 void EmptyHullsListBox::BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt,
2882 const GG::Flags<GG::ModKey>& modkeys)
2883 {
2884 const auto hp_row = dynamic_cast<HullAndPartsListBoxRow*>(it->get());
2885 if (!hp_row)
2886 return;
2887
2888 if (!hp_row->Hull().empty() || !hp_row->Parts().empty())
2889 DesignComponentsSelectedSignal(hp_row->Hull(), hp_row->Parts());
2890 }
2891
BaseDoubleClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2892 void CompletedDesignsListBox::BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt,
2893 const GG::Flags<GG::ModKey>& modkeys)
2894 {
2895 const auto cd_row = dynamic_cast<CompletedDesignListBoxRow*>(it->get());
2896 if (!cd_row || cd_row->DesignID() == INVALID_DESIGN_ID)
2897 return;
2898
2899 DesignSelectedSignal(cd_row->DesignID());
2900 }
2901
BaseDoubleClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2902 void SavedDesignsListBox::BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt,
2903 const GG::Flags<GG::ModKey>& modkeys)
2904 {
2905 const auto sd_row = dynamic_cast<SavedDesignListBoxRow*>(it->get());
2906
2907 if (!sd_row)
2908 return;
2909 SavedDesignSelectedSignal(sd_row->DesignUUID());
2910 }
2911
BaseDoubleClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2912 void MonstersListBox::BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt,
2913 const GG::Flags<GG::ModKey>& modkeys)
2914 {
2915 const auto cd_row = dynamic_cast<CompletedDesignListBoxRow*>(it->get());
2916 if (!cd_row || cd_row->DesignID() == INVALID_DESIGN_ID)
2917 return;
2918
2919 DesignSelectedSignal(cd_row->DesignID());
2920 }
2921
BaseDoubleClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2922 void AllDesignsListBox::BaseDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt,
2923 const GG::Flags<GG::ModKey>& modkeys)
2924 {
2925 const auto cd_row = dynamic_cast<CompletedDesignListBoxRow*>(it->get());
2926 if (!cd_row || cd_row->DesignID() == INVALID_DESIGN_ID)
2927 return;
2928
2929 DesignSelectedSignal(cd_row->DesignID());
2930 }
2931
2932
BaseLeftClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2933 void EmptyHullsListBox::BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt,
2934 const GG::Flags<GG::ModKey>& modkeys)
2935 {
2936 const auto hull_parts_row = dynamic_cast<HullAndPartsListBoxRow*>(it->get());
2937 if (!hull_parts_row)
2938 return;
2939 const std::string& hull_name = hull_parts_row->Hull();
2940 const ShipHull* ship_hull = GetShipHull(hull_name);
2941 const std::vector<std::string>& parts = hull_parts_row->Parts();
2942
2943 if (modkeys & GG::MOD_KEY_CTRL) {
2944 // Toggle hull obsolete
2945 auto& manager = GetDisplayedDesignsManager();
2946 const auto is_obsolete = manager.IsHullObsolete(hull_name);
2947 manager.SetHullObsolete(hull_name, !is_obsolete);
2948 Populate();
2949 }
2950 else if (ship_hull && parts.empty())
2951 HullClickedSignal(ship_hull);
2952 }
2953
BaseLeftClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2954 void CompletedDesignsListBox::BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt,
2955 const GG::Flags<GG::ModKey>& modkeys)
2956 {
2957 const auto design_row = dynamic_cast<CompletedDesignListBoxRow*>(it->get());
2958 if (!design_row)
2959 return;
2960 int id = design_row->DesignID();
2961 const ShipDesign* design = GetShipDesign(id);
2962 if (!design)
2963 return;
2964
2965 const auto& manager = GetDisplayedDesignsManager();
2966
2967 if (modkeys & GG::MOD_KEY_CTRL && manager.IsKnown(id)) {
2968 const auto maybe_obsolete = manager.IsObsolete(id);
2969 bool is_obsolete = maybe_obsolete && *maybe_obsolete;
2970 SetObsoleteInDisplayedDesigns(id, !is_obsolete);
2971 Populate();
2972
2973 } else {
2974 DesignClickedSignal(design);
2975 }
2976 }
2977
BaseLeftClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2978 void SavedDesignsListBox::BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt,
2979 const GG::Flags<GG::ModKey>& modkeys)
2980 {
2981 const auto saved_design_row = dynamic_cast<SavedDesignListBoxRow*>(it->get());
2982 if (!saved_design_row)
2983 return;
2984 const auto design_uuid = saved_design_row->DesignUUID();
2985 auto& manager = GetSavedDesignsManager();
2986 const auto design = manager.GetDesign(design_uuid);
2987 if (!design)
2988 return;
2989 if (modkeys & GG::MOD_KEY_CTRL)
2990 AddSavedDesignToDisplayedDesigns(design->UUID(), EmpireID());
2991 else
2992 DesignClickedSignal(design);
2993 }
2994
BaseLeftClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)2995 void MonstersListBox::BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt,
2996 const GG::Flags<GG::ModKey>& modkeys)
2997 {
2998 const auto design_row = dynamic_cast<CompletedDesignListBoxRow*>(it->get());
2999 if (!design_row)
3000 return;
3001 int id = design_row->DesignID();
3002 const ShipDesign* design = GetShipDesign(id);
3003 if (!design)
3004 return;
3005
3006 DesignClickedSignal(design);
3007 }
3008
BaseLeftClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)3009 void AllDesignsListBox::BaseLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt,
3010 const GG::Flags<GG::ModKey>& modkeys)
3011 {
3012 const auto design_row = dynamic_cast<CompletedDesignListBoxRow*>(it->get());
3013 if (!design_row)
3014 return;
3015 int id = design_row->DesignID();
3016 const ShipDesign* design = GetShipDesign(id);
3017 if (!design)
3018 return;
3019
3020 DesignClickedSignal(design);
3021 }
3022
3023
BaseRightClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)3024 void EmptyHullsListBox::BaseRightClicked(GG::ListBox::iterator it, const GG::Pt& pt,
3025 const GG::Flags<GG::ModKey>& modkeys)
3026 {
3027 const auto hull_parts_row = dynamic_cast<HullAndPartsListBoxRow*>(it->get());
3028 if (!hull_parts_row)
3029 return;
3030 const std::string& hull_name = hull_parts_row->Hull();
3031
3032 // Context menu actions
3033 auto& manager = GetDisplayedDesignsManager();
3034 auto is_obsolete = manager.IsHullObsolete(hull_name);
3035 auto toggle_obsolete_design_action = [&manager, &hull_name, is_obsolete, this]() {
3036 manager.SetHullObsolete(hull_name, !is_obsolete);
3037 Populate();
3038 };
3039
3040 // create popup menu with a commands in it
3041 auto popup = GG::Wnd::Create<CUIPopupMenu>(pt.x, pt.y);
3042
3043 const auto empire_id = EmpireID();
3044 if (empire_id != ALL_EMPIRES)
3045 popup->AddMenuItem(GG::MenuItem(
3046 (is_obsolete
3047 ? UserString("DESIGN_WND_UNOBSOLETE_HULL")
3048 : UserString("DESIGN_WND_OBSOLETE_HULL")),
3049 false, false, toggle_obsolete_design_action));
3050
3051 popup->Run();
3052 }
3053
BaseRightClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)3054 void CompletedDesignsListBox::BaseRightClicked(GG::ListBox::iterator it, const GG::Pt& pt,
3055 const GG::Flags<GG::ModKey>& modkeys)
3056 {
3057 const auto design_row = dynamic_cast<CompletedDesignListBoxRow*>(it->get());
3058 if (!design_row)
3059 return;
3060
3061 const auto design_id = design_row->DesignID();
3062 const auto design = GetShipDesign(design_id);
3063 if (!design)
3064 return;
3065
3066 DesignRightClickedSignal(design);
3067
3068 const auto empire_id = EmpireID();
3069
3070 DebugLogger() << "BasesListBox::BaseRightClicked on design id : " << design_id;
3071
3072 if (design->UUID() == boost::uuids::uuid{{0}})
3073 ErrorLogger() << "BasesListBox::BaseRightClicked Design UUID is null";
3074
3075 // Context menu actions
3076 const auto& manager = GetDisplayedDesignsManager();
3077 const auto maybe_obsolete = manager.IsObsolete(design_id);
3078 bool is_obsolete = maybe_obsolete && *maybe_obsolete;
3079 auto toggle_obsolete_design_action = [&design_id, is_obsolete, this]() {
3080 SetObsoleteInDisplayedDesigns(design_id, !is_obsolete);
3081 Populate();
3082 };
3083
3084 auto delete_design_action = [&design_id, this]() {
3085 DeleteFromDisplayedDesigns(design_id);
3086 Populate();
3087 DesignUpdatedSignal(design_id);
3088 };
3089
3090 auto rename_design_action = [&empire_id, &design_id, design, &design_row]() {
3091 auto edit_wnd = GG::Wnd::Create<CUIEditWnd>(GG::X(350), UserString("DESIGN_ENTER_NEW_DESIGN_NAME"), design->Name());
3092 edit_wnd->Run();
3093 const std::string& result = edit_wnd->Result();
3094 if (!result.empty() && result != design->Name()) {
3095 HumanClientApp::GetApp()->Orders().IssueOrder(
3096 std::make_shared<ShipDesignOrder>(empire_id, design_id, result));
3097 design_row->SetDisplayName(design->Name());
3098 }
3099 };
3100
3101 auto movetotop_design_action = [&design, this]() {
3102 GetDisplayedDesignsManager().MoveBefore(design->ID(), *GetDisplayedDesignsManager().OrderedIDs().begin());
3103 Populate();
3104 };
3105
3106 auto movetobottom_design_action = [&design, this]() {
3107 GetDisplayedDesignsManager().MoveBefore(design->ID(), INVALID_DESIGN_ID);
3108 Populate();
3109 };
3110
3111 auto save_design_action = [&design]() {
3112 auto saved_design = *design;
3113 saved_design.SetUUID(boost::uuids::random_generator()());
3114 GetSavedDesignsManager().InsertBefore(saved_design, GetSavedDesignsManager().OrderedDesignUUIDs().begin());
3115 };
3116
3117 // toggle the option to add all saved designs at game start.
3118 const auto add_defaults = GetOptionsDB().Get<bool>("resource.shipdesign.default.enabled");
3119 auto toggle_add_default_designs_at_game_start_action = [add_defaults]() {
3120 GetOptionsDB().Set<bool>("resource.shipdesign.default.enabled", !add_defaults);
3121 };
3122
3123 // create popup menu with a commands in it
3124 auto popup = GG::Wnd::Create<CUIPopupMenu>(pt.x, pt.y);
3125
3126 // obsolete design
3127 if (empire_id != ALL_EMPIRES)
3128 popup->AddMenuItem(GG::MenuItem(
3129 (is_obsolete
3130 ? UserString("DESIGN_WND_UNOBSOLETE_DESIGN")
3131 : UserString("DESIGN_WND_OBSOLETE_DESIGN")),
3132 false, false, toggle_obsolete_design_action));
3133
3134 // delete design
3135 if (empire_id != ALL_EMPIRES)
3136 popup->AddMenuItem(GG::MenuItem(UserString("DESIGN_WND_DELETE_DESIGN"), false, false, delete_design_action));
3137
3138 // rename design
3139 if (design->DesignedByEmpire() == empire_id && empire_id != ALL_EMPIRES)
3140 popup->AddMenuItem(GG::MenuItem(UserString("DESIGN_RENAME"), false, false, rename_design_action));
3141
3142 // save design
3143 popup->AddMenuItem(GG::MenuItem(UserString("DESIGN_SAVE"), false, false, save_design_action));
3144
3145 // move design
3146 popup->AddMenuItem(GG::MenuItem(true)); // separator
3147 popup->AddMenuItem(GG::MenuItem(UserString("MOVE_UP_LIST_ITEM"), false, false, movetotop_design_action));
3148 popup->AddMenuItem(GG::MenuItem(UserString("MOVE_DOWN_LIST_ITEM"), false, false, movetobottom_design_action));
3149
3150 popup->AddMenuItem(GG::MenuItem(true)); // separator
3151 popup->AddMenuItem(GG::MenuItem(UserString("DESIGN_WND_ADD_ALL_DEFAULT_START"), false, add_defaults,
3152 toggle_add_default_designs_at_game_start_action));
3153
3154 popup->Run();
3155 }
3156
BaseRightClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)3157 void SavedDesignsListBox::BaseRightClicked(GG::ListBox::iterator it, const GG::Pt& pt,
3158 const GG::Flags<GG::ModKey>& modkeys)
3159 {
3160 const auto design_row = dynamic_cast<SavedDesignListBoxRow*>(it->get());
3161 if (!design_row)
3162 return;
3163 const auto design_uuid = design_row->DesignUUID();
3164 auto& manager = GetSavedDesignsManager();
3165 const auto design = manager.GetDesign(design_uuid);
3166 if (!design)
3167 return;
3168 const auto empire_id = EmpireID();
3169
3170 DesignRightClickedSignal(design);
3171
3172 DebugLogger() << "BasesListBox::BaseRightClicked on design name : " << design->Name();
3173
3174 // Context menu actions
3175 // add design to empire
3176 auto add_design_action = [&design, &empire_id]() {
3177 AddSavedDesignToDisplayedDesigns(design->UUID(), empire_id);
3178 };
3179
3180 // delete design from saved designs
3181 auto delete_saved_design_action = [&manager, &design, this]() {
3182 DebugLogger() << "BasesListBox::BaseRightClicked Delete Saved Design" << design->Name();
3183 manager.Erase(design->UUID());
3184 Populate();
3185 };
3186
3187 auto movetotop_design_action = [&design, this]() {
3188 GetSavedDesignsManager().MoveBefore(design->UUID(), *GetSavedDesignsManager().OrderedDesignUUIDs().begin());
3189 Populate();
3190 };
3191
3192 auto movetobottom_design_action = [&design, this]() {
3193 GetSavedDesignsManager().MoveBefore(design->UUID(), boost::uuids::uuid{{0}});
3194 Populate();
3195 };
3196
3197 // add all saved designs
3198 auto add_all_saved_designs_action = [&manager, &empire_id]() {
3199 DebugLogger() << "BasesListBox::BaseRightClicked AddAllSavedDesignsToDisplayedDesigns";
3200 // add the items to the end of the existing list, in correct order
3201 // TODO: think about adding them at the front.
3202 auto design_uuids = manager.OrderedDesignUUIDs();
3203 for (auto it = design_uuids.rbegin(); it != design_uuids.rend(); ++it)
3204 AddSavedDesignToDisplayedDesigns(*it, empire_id);
3205 };
3206
3207 // toggle the option to add all saved designs at game start.
3208 const auto add_all = GetOptionsDB().Get<bool>("resource.shipdesign.saved.enabled");
3209 auto toggle_add_all_saved_game_start_action = [add_all]() {
3210 GetOptionsDB().Set<bool>("resource.shipdesign.saved.enabled", !add_all);
3211 };
3212
3213
3214 // create popup menu with a commands in it
3215 auto popup = GG::Wnd::Create<CUIPopupMenu>(pt.x, pt.y);
3216 if (design->Producible() && CanAddDesignToDisplayedDesigns(design))
3217 popup->AddMenuItem(GG::MenuItem(UserString("DESIGN_ADD"), false, false, add_design_action));
3218 popup->AddMenuItem(GG::MenuItem(UserString("DESIGN_WND_ADD_ALL_SAVED_NOW"), false, false, add_all_saved_designs_action));
3219 popup->AddMenuItem(GG::MenuItem(true)); // separator
3220 popup->AddMenuItem(GG::MenuItem(UserString("DESIGN_WND_DELETE_SAVED"), false, false, delete_saved_design_action));
3221 popup->AddMenuItem(GG::MenuItem(true)); // separator
3222 popup->AddMenuItem(GG::MenuItem(UserString("MOVE_UP_LIST_ITEM"), false, false, movetotop_design_action));
3223 popup->AddMenuItem(GG::MenuItem(UserString("MOVE_DOWN_LIST_ITEM"), false, false, movetobottom_design_action));
3224 popup->AddMenuItem(GG::MenuItem(true)); // separator
3225 popup->AddMenuItem(GG::MenuItem(UserString("DESIGN_WND_ADD_ALL_SAVED_START"), false, add_all,
3226 toggle_add_all_saved_game_start_action));
3227
3228 popup->Run();
3229
3230 }
3231
3232
QueueItemMoved(const GG::ListBox::iterator & row_it,const GG::ListBox::iterator & original_position_it)3233 void EmptyHullsListBox::QueueItemMoved(const GG::ListBox::iterator& row_it,
3234 const GG::ListBox::iterator& original_position_it)
3235 {
3236 const auto control = dynamic_cast<HullAndPartsListBoxRow*>(row_it->get());
3237 if (!control || !GetEmpire(EmpireID()))
3238 return;
3239
3240 const std::string& hull_name = control->Hull();
3241
3242 iterator insert_before_row = std::next(row_it);
3243
3244 const auto insert_before_control = (insert_before_row == end()) ? nullptr :
3245 boost::polymorphic_downcast<const HullAndPartsListBoxRow*>(insert_before_row->get());
3246 std::string insert_before_hull = insert_before_control
3247 ? insert_before_control->Hull() : "";
3248
3249 control->Resize(ListRowSize());
3250
3251 GetDisplayedDesignsManager().InsertHullBefore(hull_name, insert_before_hull);
3252 }
3253
QueueItemMoved(const GG::ListBox::iterator & row_it,const GG::ListBox::iterator & original_position_it)3254 void CompletedDesignsListBox::QueueItemMoved(const GG::ListBox::iterator& row_it,
3255 const GG::ListBox::iterator& original_position_it)
3256 {
3257 const auto control = dynamic_cast<BasesListBox::CompletedDesignListBoxRow*>(row_it->get());
3258 if (!control || !GetEmpire(EmpireID()))
3259 return;
3260
3261 int design_id = control->DesignID();
3262
3263 iterator insert_before_row = std::next(row_it);
3264
3265 const auto insert_before_control = (insert_before_row == end()) ? nullptr :
3266 boost::polymorphic_downcast<const BasesListBox::CompletedDesignListBoxRow*>(insert_before_row->get());
3267 int insert_before_id = insert_before_control
3268 ? insert_before_control->DesignID() : INVALID_DESIGN_ID;
3269
3270 control->Resize(ListRowSize());
3271
3272 GetDisplayedDesignsManager().MoveBefore(design_id, insert_before_id);
3273 }
3274
QueueItemMoved(const GG::ListBox::iterator & row_it,const GG::ListBox::iterator & original_position_it)3275 void SavedDesignsListBox::QueueItemMoved(const GG::ListBox::iterator& row_it,
3276 const GG::ListBox::iterator& original_position_it)
3277 {
3278 const auto control = dynamic_cast<SavedDesignsListBox::SavedDesignListBoxRow*>(row_it->get());
3279 if (!control)
3280 return;
3281
3282 const auto& uuid = control->DesignUUID();
3283
3284 iterator insert_before_row = std::next(row_it);
3285
3286 const auto insert_before_control = (insert_before_row == end()) ? nullptr :
3287 boost::polymorphic_downcast<const SavedDesignsListBox::SavedDesignListBoxRow*>(insert_before_row->get());
3288 const auto& next_uuid = insert_before_control
3289 ? insert_before_control->DesignUUID() : boost::uuids::uuid{{0}};
3290
3291 if (GetSavedDesignsManager().MoveBefore(uuid, next_uuid))
3292 control->Resize(ListRowSize());
3293 }
3294
3295
3296 //////////////////////////////////////////////////
3297 // BasesListBox derived class rows //
3298 //////////////////////////////////////////////////
SavedDesignListBoxRow(GG::X w,GG::Y h,const ShipDesign & design)3299 SavedDesignsListBox::SavedDesignListBoxRow::SavedDesignListBoxRow(
3300 GG::X w, GG::Y h, const ShipDesign& design) :
3301 BasesListBoxRow(w, h, design.Hull(), design.Name()),
3302 m_design_uuid(design.UUID())
3303 {}
3304
CompleteConstruction()3305 void SavedDesignsListBox::SavedDesignListBoxRow::CompleteConstruction() {
3306 BasesListBoxRow::CompleteConstruction();
3307 SetDragDropDataType(SAVED_DESIGN_ROW_DROP_STRING);
3308 }
3309
DesignUUID() const3310 const boost::uuids::uuid SavedDesignsListBox::SavedDesignListBoxRow::DesignUUID() const {
3311 SavedDesignsManager& manager = GetSavedDesignsManager();
3312 const ShipDesign* design = manager.GetDesign(m_design_uuid);
3313 if (!design) {
3314 ErrorLogger() << "Saved ship design missing with uuid " << m_design_uuid;
3315 return boost::uuids::uuid{};
3316 }
3317 return design->UUID();
3318 }
3319
DesignName() const3320 const std::string& SavedDesignsListBox::SavedDesignListBoxRow::DesignName() const {
3321 SavedDesignsManager& manager = GetSavedDesignsManager();
3322 const ShipDesign* design = manager.GetDesign(m_design_uuid);
3323 if (!design)
3324 return EMPTY_STRING;
3325 return design->Name();
3326 }
3327
Description() const3328 const std::string& SavedDesignsListBox::SavedDesignListBoxRow::Description() const {
3329 SavedDesignsManager& manager = GetSavedDesignsManager();
3330 const ShipDesign* design = manager.GetDesign(m_design_uuid);
3331 if (!design)
3332 return EMPTY_STRING;
3333 return design->Description();
3334 }
3335
LookupInStringtable() const3336 bool SavedDesignsListBox::SavedDesignListBoxRow::LookupInStringtable() const {
3337 SavedDesignsManager& manager = GetSavedDesignsManager();
3338 const ShipDesign* design = manager.GetDesign(m_design_uuid);
3339 if (!design)
3340 return false;
3341 return design->LookupInStringtable();
3342 }
3343
3344
3345 //////////////////////////////////////////////////
3346 // DesignWnd::BaseSelector //
3347 //////////////////////////////////////////////////
3348 class DesignWnd::BaseSelector : public CUIWnd {
3349 public:
3350 /** \name Structors */ //@{
3351 BaseSelector(const std::string& config_name);
3352 void CompleteConstruction() override;
3353 //@}
3354
3355 /** \name Mutators */ //@{
3356 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override;
3357
3358 void Reset();
3359 void ToggleAvailability(const Availability::Enum type);
3360 void SetEmpireShown(int empire_id, bool refresh_list);
3361 void EnableOrderIssuing(bool enable/* = true*/);
3362 //@}
3363
3364 mutable boost::signals2::signal<void (int)> DesignSelectedSignal;
3365 mutable boost::signals2::signal<void (int)> DesignUpdatedSignal;
3366 mutable boost::signals2::signal<void (const std::string&, const std::vector<std::string>&)>
3367 DesignComponentsSelectedSignal;
3368 mutable boost::signals2::signal<void (const boost::uuids::uuid&)> SavedDesignSelectedSignal;
3369 mutable boost::signals2::signal<void (const ShipDesign*)> DesignClickedSignal;
3370 mutable boost::signals2::signal<void (const ShipHull*)> HullClickedSignal;
3371
3372 enum class BaseSelectorTab : std::size_t {Hull, Current, Saved, Monster, All};
3373 mutable boost::signals2::signal<void (const BaseSelectorTab)> TabChangedSignal;
3374
3375 private:
3376 void DoLayout();
3377
3378 std::shared_ptr<GG::TabWnd> m_tabs;
3379 std::shared_ptr<EmptyHullsListBox> m_hulls_list; // empty hulls on which a new design can be based
3380 std::shared_ptr<CompletedDesignsListBox> m_designs_list; // designs this empire has created or learned how to make
3381 std::shared_ptr<SavedDesignsListBox> m_saved_designs_list; // designs saved to files
3382 std::shared_ptr<MonstersListBox> m_monsters_list; // monster designs
3383 std::shared_ptr<AllDesignsListBox> m_all_list; // all designs known to empire
3384
3385 // Holds the state of the availabilities filter.
3386 AvailabilityManager m_availabilities_state{false, true, false};
3387
3388 std::tuple<std::shared_ptr<CUIStateButton>, std::shared_ptr<CUIStateButton>,
3389 std::shared_ptr<CUIStateButton>> m_availabilities_buttons;
3390 };
3391
BaseSelector(const std::string & config_name)3392 DesignWnd::BaseSelector::BaseSelector(const std::string& config_name) :
3393 CUIWnd(UserString("DESIGN_WND_STARTS"),
3394 GG::INTERACTIVE | GG::RESIZABLE | GG::ONTOP | GG::DRAGABLE | PINABLE,
3395 config_name)
3396 {}
3397
CompleteConstruction()3398 void DesignWnd::BaseSelector::CompleteConstruction() {
3399 // TODO: C++17, Collect and replace with structured binding auto [a, b, c] = m_availabilities;
3400 auto& m_obsolete_button = std::get<Availability::Obsolete>(m_availabilities_buttons);
3401 m_obsolete_button = GG::Wnd::Create<CUIStateButton>(UserString("PRODUCTION_WND_AVAILABILITY_OBSOLETE"),
3402 GG::FORMAT_CENTER, std::make_shared<CUILabelButtonRepresenter>());
3403 AttachChild(m_obsolete_button);
3404 m_obsolete_button->CheckedSignal.connect(
3405 boost::bind(&DesignWnd::BaseSelector::ToggleAvailability, this, Availability::Obsolete));
3406 m_obsolete_button->SetCheck(m_availabilities_state.GetAvailability(Availability::Obsolete));
3407
3408 auto& m_available_button = std::get<Availability::Available>(m_availabilities_buttons);
3409 m_available_button = GG::Wnd::Create<CUIStateButton>(UserString("PRODUCTION_WND_AVAILABILITY_AVAILABLE"),
3410 GG::FORMAT_CENTER, std::make_shared<CUILabelButtonRepresenter>());
3411 AttachChild(m_available_button);
3412 m_available_button->CheckedSignal.connect(
3413 boost::bind(&DesignWnd::BaseSelector::ToggleAvailability, this, Availability::Available));
3414 m_available_button->SetCheck(m_availabilities_state.GetAvailability(Availability::Available));
3415
3416 auto& m_unavailable_button = std::get<Availability::Future>(m_availabilities_buttons);
3417 m_unavailable_button = GG::Wnd::Create<CUIStateButton>(UserString("PRODUCTION_WND_AVAILABILITY_UNAVAILABLE"),
3418 GG::FORMAT_CENTER, std::make_shared<CUILabelButtonRepresenter>());
3419 AttachChild(m_unavailable_button);
3420 m_unavailable_button->CheckedSignal.connect(
3421 boost::bind(&DesignWnd::BaseSelector::ToggleAvailability, this, Availability::Future));
3422 m_unavailable_button->SetCheck(m_availabilities_state.GetAvailability(Availability::Future));
3423
3424 m_tabs = GG::Wnd::Create<GG::TabWnd>(GG::X(5), GG::Y(2), GG::X(10), GG::Y(10), ClientUI::GetFont(),
3425 ClientUI::WndColor(), ClientUI::TextColor());
3426 m_tabs->TabChangedSignal.connect(boost::bind(&DesignWnd::BaseSelector::Reset, this));
3427 AttachChild(m_tabs);
3428
3429 m_hulls_list = GG::Wnd::Create<EmptyHullsListBox>(m_availabilities_state, HULL_PARTS_ROW_DROP_TYPE_STRING);
3430 m_hulls_list->Resize(GG::Pt(GG::X(10), GG::Y(10)));
3431 m_tabs->AddWnd(m_hulls_list, UserString("DESIGN_WND_HULLS"));
3432 m_hulls_list->DesignComponentsSelectedSignal.connect(DesignWnd::BaseSelector::DesignComponentsSelectedSignal);
3433 m_hulls_list->HullClickedSignal.connect(DesignWnd::BaseSelector::HullClickedSignal);
3434
3435 m_designs_list = GG::Wnd::Create<CompletedDesignsListBox>(m_availabilities_state, COMPLETE_DESIGN_ROW_DROP_STRING);
3436 m_designs_list->Resize(GG::Pt(GG::X(10), GG::Y(10)));
3437 m_tabs->AddWnd(m_designs_list, UserString("DESIGN_WND_FINISHED_DESIGNS"));
3438 m_designs_list->DesignSelectedSignal.connect(DesignWnd::BaseSelector::DesignSelectedSignal);
3439 m_designs_list->DesignUpdatedSignal.connect(DesignWnd::BaseSelector::DesignUpdatedSignal);
3440 m_designs_list->DesignClickedSignal.connect(DesignWnd::BaseSelector::DesignClickedSignal);
3441
3442 m_saved_designs_list = GG::Wnd::Create<SavedDesignsListBox>(m_availabilities_state, SAVED_DESIGN_ROW_DROP_STRING);
3443 m_saved_designs_list->Resize(GG::Pt(GG::X(10), GG::Y(10)));
3444 m_tabs->AddWnd(m_saved_designs_list, UserString("DESIGN_WND_SAVED_DESIGNS"));
3445 m_saved_designs_list->SavedDesignSelectedSignal.connect(DesignWnd::BaseSelector::SavedDesignSelectedSignal);
3446 m_saved_designs_list->DesignClickedSignal.connect(DesignWnd::BaseSelector::DesignClickedSignal);
3447
3448 m_monsters_list = GG::Wnd::Create<MonstersListBox>(m_availabilities_state);
3449 m_monsters_list->Resize(GG::Pt(GG::X(10), GG::Y(10)));
3450 m_tabs->AddWnd(m_monsters_list, UserString("DESIGN_WND_MONSTERS"));
3451 m_monsters_list->DesignSelectedSignal.connect(DesignWnd::BaseSelector::DesignSelectedSignal);
3452 m_monsters_list->DesignClickedSignal.connect(DesignWnd::BaseSelector::DesignClickedSignal);
3453
3454 m_all_list = GG::Wnd::Create<AllDesignsListBox>(m_availabilities_state);
3455 m_all_list->Resize(GG::Pt(GG::X(10), GG::Y(10)));
3456 m_tabs->AddWnd(m_all_list, UserString("DESIGN_WND_ALL"));
3457 m_all_list->DesignSelectedSignal.connect(DesignWnd::BaseSelector::DesignSelectedSignal);
3458 m_all_list->DesignClickedSignal.connect(DesignWnd::BaseSelector::DesignClickedSignal);
3459
3460
3461 CUIWnd::CompleteConstruction();
3462
3463 SetMinSize(BASES_MIN_SIZE);
3464
3465 DoLayout();
3466 SaveDefaultedOptions();
3467 }
3468
SizeMove(const GG::Pt & ul,const GG::Pt & lr)3469 void DesignWnd::BaseSelector::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
3470 const GG::Pt old_size = Size();
3471 CUIWnd::SizeMove(ul, lr);
3472 if (old_size != Size())
3473 DoLayout();
3474 }
3475
Reset()3476 void DesignWnd::BaseSelector::Reset() {
3477 ScopedTimer scoped_timer("BaseSelector::Reset");
3478
3479 const int empire_id = HumanClientApp::GetApp()->EmpireID();
3480 SetEmpireShown(empire_id, false);
3481
3482 if (auto base_box = dynamic_cast<BasesListBox*>(m_tabs->CurrentWnd()))
3483 base_box->Populate();
3484
3485 // Signal the type of tab selected
3486 auto tab_type = BaseSelectorTab(m_tabs->CurrentWndIndex());
3487 switch (tab_type) {
3488 case BaseSelectorTab::Hull:
3489 case BaseSelectorTab::Current:
3490 case BaseSelectorTab::Saved:
3491 case BaseSelectorTab::Monster:
3492 case BaseSelectorTab::All:
3493 TabChangedSignal(tab_type);
3494 break;
3495 default:
3496 break;
3497 }
3498 }
3499
SetEmpireShown(int empire_id,bool refresh_list)3500 void DesignWnd::BaseSelector::SetEmpireShown(int empire_id, bool refresh_list) {
3501 m_hulls_list->SetEmpireShown(empire_id, refresh_list);
3502 m_designs_list->SetEmpireShown(empire_id, refresh_list);
3503 m_saved_designs_list->SetEmpireShown(empire_id, refresh_list);
3504 }
3505
ToggleAvailability(Availability::Enum type)3506 void DesignWnd::BaseSelector::ToggleAvailability(Availability::Enum type) {
3507 std::shared_ptr<CUIStateButton> button;
3508 bool state = false;
3509 switch (type) {
3510 case Availability::Obsolete:
3511 m_availabilities_state.ToggleAvailability(Availability::Obsolete);
3512 state = m_availabilities_state.GetAvailability(Availability::Obsolete);
3513 button = std::get<Availability::Obsolete>(m_availabilities_buttons);
3514 break;
3515 case Availability::Available:
3516 m_availabilities_state.ToggleAvailability(Availability::Available);
3517 state = m_availabilities_state.GetAvailability(Availability::Available);
3518 button = std::get<Availability::Available>(m_availabilities_buttons);
3519 break;
3520 case Availability::Future:
3521 m_availabilities_state.ToggleAvailability(Availability::Future);
3522 state = m_availabilities_state.GetAvailability(Availability::Future);
3523 button = std::get<Availability::Future>(m_availabilities_buttons);
3524 break;
3525 }
3526
3527 button->SetCheck(state);
3528
3529 m_hulls_list->Populate();
3530 m_designs_list->Populate();
3531 m_saved_designs_list->Populate();
3532 }
3533
EnableOrderIssuing(bool enable)3534 void DesignWnd::BaseSelector::EnableOrderIssuing(bool enable/* = true*/) {
3535 m_hulls_list->EnableOrderIssuing(enable);
3536 m_designs_list->EnableOrderIssuing(enable);
3537 m_saved_designs_list->EnableOrderIssuing(enable);
3538 m_monsters_list->EnableOrderIssuing(enable);
3539 }
3540
DoLayout()3541 void DesignWnd::BaseSelector::DoLayout() {
3542 const GG::X LEFT_PAD(5);
3543 const GG::Y TOP_PAD(2);
3544 const GG::X AVAILABLE_WIDTH = ClientWidth() - 2*LEFT_PAD;
3545 const int BUTTON_SEPARATION = 3;
3546 const GG::X BUTTON_WIDTH = (AVAILABLE_WIDTH - 2*BUTTON_SEPARATION) / 3;
3547 const int PTS = ClientUI::Pts();
3548 const GG::Y BUTTON_HEIGHT(PTS * 2);
3549
3550 GG::Y top(TOP_PAD);
3551 GG::X left(LEFT_PAD);
3552
3553 // TODO: C++17, Replace with structured binding auto [a, b, c] = m_availabilities;
3554 auto& m_obsolete_button = std::get<Availability::Obsolete>(m_availabilities_buttons);
3555 auto& m_available_button = std::get<Availability::Available>(m_availabilities_buttons);
3556 auto& m_unavailable_button = std::get<Availability::Future>(m_availabilities_buttons);
3557
3558 m_obsolete_button->SizeMove(GG::Pt(left, top), GG::Pt(left + BUTTON_WIDTH, top + BUTTON_HEIGHT));
3559 left = left + BUTTON_WIDTH + BUTTON_SEPARATION;
3560 m_available_button->SizeMove(GG::Pt(left, top), GG::Pt(left + BUTTON_WIDTH, top + BUTTON_HEIGHT));
3561 left = left + BUTTON_WIDTH + BUTTON_SEPARATION;
3562 m_unavailable_button->SizeMove(GG::Pt(left, top), GG::Pt(left + BUTTON_WIDTH, top + BUTTON_HEIGHT));
3563 left = LEFT_PAD;
3564 top = top + BUTTON_HEIGHT + BUTTON_SEPARATION;
3565
3566 m_tabs->SizeMove(GG::Pt(left, top), ClientSize() - GG::Pt(LEFT_PAD, TOP_PAD));
3567 }
3568
3569
3570 //////////////////////////////////////////////////
3571 // SlotControl //
3572 //////////////////////////////////////////////////
3573 /** UI representation and drop-target for slots of a design. PartControl may
3574 * be dropped into slots to add the corresponding parts to the ShipDesign, or
3575 * the part may be set programmatically with SetPart(). */
3576 class SlotControl : public GG::Control {
3577 public:
3578 /** \name Structors */ //@{
3579 SlotControl();
3580 SlotControl(double x, double y, ShipSlotType slot_type);
3581 //@}
3582 void CompleteConstruction() override;
3583
3584 /** \name Accessors */ //@{
3585 ShipSlotType SlotType() const;
3586 double XPositionFraction() const;
3587 double YPositionFraction() const;
3588 const ShipPart* GetPart() const;
3589 //@}
3590
3591 /** \name Mutators */ //@{
3592 void StartingChildDragDrop(const GG::Wnd* wnd, const GG::Pt& offset) override;
3593 void CancellingChildDragDrop(const std::vector<const GG::Wnd*>& wnds) override;
3594 void AcceptDrops(const GG::Pt& pt, std::vector<std::shared_ptr<GG::Wnd>> wnds,
3595 GG::Flags<GG::ModKey> mod_keys) override;
3596 void ChildrenDraggedAway(const std::vector<GG::Wnd*>& wnds, const GG::Wnd* destination) override;
3597 void DragDropEnter(const GG::Pt& pt, std::map<const Wnd*, bool>& drop_wnds_acceptable,
3598 GG::Flags<GG::ModKey> mod_keys) override;
3599 void DragDropLeave() override;
3600
3601 void Render() override;
3602 void Highlight(bool actually = true);
3603
3604 //! Used to programmatically set the ShipPart in this slot.
3605 //! Does not emit signal.
3606 void SetPart(const std::string& part_name);
3607 //! Used to programmatically set the ShipPart in this slot.
3608 //! Does not emit signal.
3609 void SetPart(const ShipPart* part = nullptr);
3610 //@}
3611
3612 /** emitted when the contents of a slot are altered by the dragging
3613 * a PartControl in or out of the slot. signal should be caught and the
3614 * slot contents set using SetPart accordingly */
3615 mutable boost::signals2::signal<void (const ShipPart*, bool)> SlotContentsAlteredSignal;
3616
3617 mutable boost::signals2::signal<void (const ShipPart*, GG::Flags<GG::ModKey>)> ShipPartClickedSignal;
3618
3619 protected:
3620 bool EventFilter(GG::Wnd* w, const GG::WndEvent& event) override;
3621 void DropsAcceptable(DropsAcceptableIter first, DropsAcceptableIter last,
3622 const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) const override;
3623
3624 private:
3625 bool m_highlighted = false;
3626 ShipSlotType m_slot_type = INVALID_SHIP_SLOT_TYPE;
3627 double m_x_position_fraction = 0.4; //!< position on hull image where slot should be shown, as a fraction of that image's size
3628 double m_y_position_fraction = 0.4;
3629 std::shared_ptr<PartControl> m_part_control;
3630 std::shared_ptr<GG::StaticGraphic> m_background;
3631 };
3632
SlotControl()3633 SlotControl::SlotControl() :
3634 GG::Control(GG::X0, GG::Y0, SLOT_CONTROL_WIDTH, SLOT_CONTROL_HEIGHT, GG::INTERACTIVE)
3635 {}
3636
SlotControl(double x,double y,ShipSlotType slot_type)3637 SlotControl::SlotControl(double x, double y, ShipSlotType slot_type) :
3638 GG::Control(GG::X0, GG::Y0, SLOT_CONTROL_WIDTH, SLOT_CONTROL_HEIGHT, GG::INTERACTIVE),
3639 m_slot_type(slot_type),
3640 m_x_position_fraction(x),
3641 m_y_position_fraction(y)
3642 {}
3643
CompleteConstruction()3644 void SlotControl::CompleteConstruction() {
3645 GG::Control::CompleteConstruction();
3646
3647 m_background = GG::Wnd::Create<GG::StaticGraphic>(SlotBackgroundTexture(m_slot_type), GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
3648 m_background->Resize(GG::Pt(SLOT_CONTROL_WIDTH, SLOT_CONTROL_HEIGHT));
3649 m_background->Show();
3650 AttachChild(m_background);
3651
3652 SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
3653
3654 // set up empty slot tool tip
3655 std::string title_text;
3656 if (m_slot_type == SL_EXTERNAL)
3657 title_text = UserString("SL_EXTERNAL");
3658 else if (m_slot_type == SL_INTERNAL)
3659 title_text = UserString("SL_INTERNAL");
3660 else if (m_slot_type == SL_CORE)
3661 title_text = UserString("SL_CORE");
3662
3663 SetBrowseInfoWnd(GG::Wnd::Create<IconTextBrowseWnd>(
3664 SlotBackgroundTexture(m_slot_type),
3665 title_text,
3666 UserString("SL_TOOLTIP_DESC")
3667 ));
3668 }
3669
EventFilter(GG::Wnd * w,const GG::WndEvent & event)3670 bool SlotControl::EventFilter(GG::Wnd* w, const GG::WndEvent& event) {
3671 if (w == this)
3672 return false;
3673
3674 switch (event.Type()) {
3675 case GG::WndEvent::DragDropEnter:
3676 case GG::WndEvent::DragDropHere:
3677 case GG::WndEvent::CheckDrops:
3678 case GG::WndEvent::DragDropLeave:
3679 case GG::WndEvent::DragDroppedOn:
3680 HandleEvent(event);
3681 return true;
3682 break;
3683 default:
3684 return false;
3685 }
3686 }
3687
DropsAcceptable(DropsAcceptableIter first,DropsAcceptableIter last,const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys) const3688 void SlotControl::DropsAcceptable(DropsAcceptableIter first, DropsAcceptableIter last,
3689 const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) const
3690 {
3691 for (DropsAcceptableIter it = first; it != last; ++it)
3692 it->second = false;
3693
3694 // if more than one control dropped somehow, reject all
3695 if (std::distance(first, last) != 1)
3696 return;
3697
3698 for (DropsAcceptableIter it = first; it != last; ++it) {
3699 if (it->first->DragDropDataType() != PART_CONTROL_DROP_TYPE_STRING)
3700 continue;
3701 const auto part_control = boost::polymorphic_downcast<const PartControl* const>(it->first);
3702 const ShipPart* part = part_control->Part();
3703 if (part &&
3704 part->CanMountInSlotType(m_slot_type) &&
3705 part_control != m_part_control.get())
3706 {
3707 it->second = true;
3708 return;
3709 }
3710 }
3711 }
3712
SlotType() const3713 ShipSlotType SlotControl::SlotType() const
3714 { return m_slot_type; }
3715
XPositionFraction() const3716 double SlotControl::XPositionFraction() const
3717 { return m_x_position_fraction; }
3718
YPositionFraction() const3719 double SlotControl::YPositionFraction() const
3720 { return m_y_position_fraction; }
3721
GetPart() const3722 const ShipPart* SlotControl::GetPart() const {
3723 if (m_part_control)
3724 return m_part_control->Part();
3725 else
3726 return nullptr;
3727 }
3728
StartingChildDragDrop(const GG::Wnd * wnd,const GG::Pt & offset)3729 void SlotControl::StartingChildDragDrop(const GG::Wnd* wnd, const GG::Pt& offset) {
3730 if (!m_part_control)
3731 return;
3732
3733 const auto control = dynamic_cast<const PartControl*>(wnd);
3734 if (!control)
3735 return;
3736
3737 if (control == m_part_control.get())
3738 m_part_control->Hide();
3739 }
3740
CancellingChildDragDrop(const std::vector<const GG::Wnd * > & wnds)3741 void SlotControl::CancellingChildDragDrop(const std::vector<const GG::Wnd*>& wnds) {
3742 if (!m_part_control)
3743 return;
3744
3745 for (const auto& wnd : wnds) {
3746 const auto control = dynamic_cast<const PartControl*>(wnd);
3747 if (!control)
3748 continue;
3749
3750 if (control == m_part_control.get())
3751 m_part_control->Show();
3752 }
3753 }
3754
AcceptDrops(const GG::Pt & pt,std::vector<std::shared_ptr<GG::Wnd>> wnds,GG::Flags<GG::ModKey> mod_keys)3755 void SlotControl::AcceptDrops(const GG::Pt& pt, std::vector<std::shared_ptr<GG::Wnd>> wnds,
3756 GG::Flags<GG::ModKey> mod_keys)
3757 {
3758 if (wnds.size() != 1)
3759 ErrorLogger() << "SlotControl::AcceptDrops given multiple wnds unexpectedly...";
3760
3761 const auto wnd = *(wnds.begin());
3762 const PartControl* control = boost::polymorphic_downcast<const PartControl*>(wnd.get());
3763 const ShipPart* part = control ? control->Part() : nullptr;
3764
3765 if (part)
3766 SlotContentsAlteredSignal(part, (mod_keys & GG::MOD_KEY_CTRL));
3767 }
3768
ChildrenDraggedAway(const std::vector<GG::Wnd * > & wnds,const GG::Wnd * destination)3769 void SlotControl::ChildrenDraggedAway(const std::vector<GG::Wnd*>& wnds,
3770 const GG::Wnd* destination)
3771 {
3772 if (wnds.empty())
3773 return;
3774 const GG::Wnd* wnd = wnds.front();
3775 const auto part_control = dynamic_cast<const PartControl*>(wnd);
3776 if (part_control != m_part_control.get())
3777 return;
3778 DetachChildAndReset(m_part_control);
3779 SlotContentsAlteredSignal(nullptr, false);
3780 }
3781
DragDropEnter(const GG::Pt & pt,std::map<const Wnd *,bool> & drop_wnds_acceptable,GG::Flags<GG::ModKey> mod_keys)3782 void SlotControl::DragDropEnter(const GG::Pt& pt, std::map<const Wnd*, bool>& drop_wnds_acceptable,
3783 GG::Flags<GG::ModKey> mod_keys) {
3784
3785 if (drop_wnds_acceptable.empty())
3786 return;
3787
3788 DropsAcceptable(drop_wnds_acceptable.begin(), drop_wnds_acceptable.end(), pt, mod_keys);
3789
3790 // Note: If this SlotControl is being dragged over this indicates the dragged part would
3791 // replace this part.
3792 if (drop_wnds_acceptable.begin()->second && m_part_control)
3793 m_part_control->Hide();
3794 }
3795
DragDropLeave()3796 void SlotControl::DragDropLeave() {
3797 // Note: If m_part_control is being dragged, this does nothing, because it is detached.
3798 // If this SlotControl is being dragged over this indicates the dragged part would
3799 // replace this part.
3800 if (m_part_control && !GG::GUI::GetGUI()->DragDropWnd(m_part_control.get()))
3801 m_part_control->Show();
3802 }
3803
Render()3804 void SlotControl::Render()
3805 {}
3806
Highlight(bool actually)3807 void SlotControl::Highlight(bool actually)
3808 { m_highlighted = actually; }
3809
SetPart(const std::string & part_name)3810 void SlotControl::SetPart(const std::string& part_name)
3811 { SetPart(GetShipPart(part_name)); }
3812
SetPart(const ShipPart * part)3813 void SlotControl::SetPart(const ShipPart* part) {
3814 // remove existing part control, if any
3815 DetachChildAndReset(m_part_control);
3816
3817 if (!part)
3818 return;
3819
3820 // create new part control for passed in part
3821 m_part_control = GG::Wnd::Create<PartControl>(part);
3822 AttachChild(m_part_control);
3823 m_part_control->InstallEventFilter(shared_from_this());
3824
3825 // single click shows encyclopedia data
3826 m_part_control->ClickedSignal.connect(ShipPartClickedSignal);
3827
3828 // double click clears slot
3829 m_part_control->DoubleClickedSignal.connect(
3830 [this](const ShipPart*){ this->SlotContentsAlteredSignal(nullptr, false); });
3831 SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
3832
3833 // set part occupying slot's tool tip to say slot type
3834 std::string title_text;
3835 if (m_slot_type == SL_EXTERNAL)
3836 title_text = UserString("SL_EXTERNAL");
3837 else if (m_slot_type == SL_INTERNAL)
3838 title_text = UserString("SL_INTERNAL");
3839 else if (m_slot_type == SL_CORE)
3840 title_text = UserString("SL_CORE");
3841
3842 m_part_control->SetBrowseInfoWnd(GG::Wnd::Create<IconTextBrowseWnd>(
3843 ClientUI::PartIcon(part->Name()),
3844 UserString(part->Name()) + " (" + title_text + ")",
3845 UserString(part->Description())
3846 ));
3847 }
3848
DropsAcceptable(DropsAcceptableIter first,DropsAcceptableIter last,const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys) const3849 void PartsListBox::DropsAcceptable(DropsAcceptableIter first, DropsAcceptableIter last,
3850 const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) const
3851 {
3852 // PartsListBox accepts parts that are being removed from a SlotControl
3853
3854 for (DropsAcceptableIter it = first; it != last; ++it)
3855 it->second = false;
3856
3857 // if more than one control dropped somehow, reject all
3858 if (std::distance(first, last) != 1)
3859 return;
3860
3861 const auto&& parent = first->first->Parent();
3862 if (first->first->DragDropDataType() == PART_CONTROL_DROP_TYPE_STRING
3863 && parent
3864 && dynamic_cast<const SlotControl*>(parent.get()))
3865 {
3866 first->second = true;
3867 }
3868 }
3869
3870
3871 //////////////////////////////////////////////////
3872 // DesignWnd::MainPanel //
3873 //////////////////////////////////////////////////
3874 class DesignWnd::MainPanel : public CUIWnd {
3875 public:
3876
3877 /** I18nString stores a string that might be in the stringtable. */
3878 class I18nString {
3879 public:
I18nString(bool is_in_stringtable,const std::string & text)3880 I18nString (bool is_in_stringtable, const std::string& text) :
3881 m_is_in_stringtable(is_in_stringtable), m_text(text)
3882 {}
I18nString(bool is_in_stringtable,std::string && text)3883 I18nString (bool is_in_stringtable, std::string&& text) :
3884 m_is_in_stringtable(is_in_stringtable), m_text(std::move(text))
3885 {}
3886
3887 /** Return the text a displayed. */
DisplayText() const3888 std::string DisplayText() const
3889 { return m_is_in_stringtable ? UserString(m_text) : m_text; }
3890
3891 /** Return the text as stored. */
StoredString() const3892 std::string StoredString() const
3893 { return m_text; }
3894
IsInStringtable() const3895 bool IsInStringtable() const
3896 { return m_is_in_stringtable; }
3897
3898 private:
3899 const bool m_is_in_stringtable;
3900 const std::string m_text;
3901 };
3902
3903 /** \name Structors */ //@{
3904 MainPanel(const std::string& config_name);
3905 void CompleteConstruction() override;
3906 //@}
3907
3908 /** \name Accessors */ //@{
3909 /** If editing a current design return a ShipDesign* otherwise boost::none. */
3910 boost::optional<const ShipDesign*> EditingCurrentDesign() const;
3911 /** If editing a saved design return a ShipDesign* otherwise boost::none. */
3912 boost::optional<const ShipDesign*> EditingSavedDesign() const;
3913
3914 const std::vector<std::string> Parts() const; //!< returns vector of names of parts in slots of current shown design. empty slots are represented with empty stri
3915 const std::string& Hull() const; //!< returns name of hull of current shown design
3916 bool IsDesignNameValid() const; //!< checks design name validity
3917 /** Return a validated name and description. If the design is a saved design then either both
3918 or neither will be stringtable values.*/
3919 std::pair<I18nString, I18nString> ValidatedNameAndDescription() const;
3920 const I18nString ValidatedDesignName() const;//!< returns name currently entered for design or valid default
3921 const I18nString DesignDescription() const; //!< returns description currently entered for design
3922
3923 /** Returns a pointer to the design currently being modified (if any). May
3924 return an empty pointer if not currently modifying a design. */
3925 std::shared_ptr<const ShipDesign> GetIncompleteDesign() const;
3926 boost::optional<int> GetReplacedDesignID() const;//!< returns ID of completed design selected to be replaced.
3927
3928 /** If a design with the same hull and parts is registered with the empire then return the
3929 design, otherwise return boost::none. */
3930 boost::optional<const ShipDesign*> CurrentDesignIsRegistered();
3931 //@}
3932
3933 /** \name Mutators */ //@{
3934 void LClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override;
3935
3936 void AcceptDrops(const GG::Pt& pt, std::vector<std::shared_ptr<GG::Wnd>> wnds, GG::Flags<GG::ModKey> mod_keys) override;
3937
3938 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override;
3939
3940 void Sanitize();
3941
3942 //! Puts specified part in specified slot. Does nothing if slot is out of
3943 //! range of available slots for current hull
3944 void SetPart(const std::string& part_name, unsigned int slot);
3945
3946 //! Sets the part in @p slot to @p part and emits and signal if requested.
3947 //! Changes all similar parts if @p change_all_similar_parts.
3948 void SetPart(const ShipPart* part, unsigned int slot, bool emit_signal = false, bool change_all_similar_parts = false);
3949
3950 //! Puts specified parts in slots. Attempts to put each part into the slot
3951 //! corresponding to its place in the passed vector. If a part cannot be
3952 //! placed, it is ignored. More parts than there are slots available are
3953 //! ignored, and slots for which there are insufficient parts in the passed
3954 //! vector are unmodified.
3955 void SetParts(const std::vector<std::string>& parts);
3956
3957 //! Attempts to add the specified part to the design, if possible. Will
3958 //! first attempt to add part to an empty slot of the appropriate type, and
3959 //! if no appropriate slots are available, may or may not move other parts
3960 //! around within the design to open up a compatible slot in which to add
3961 //! this part (and then add it). May also do nothing.
3962 void AddPart(const ShipPart* part);
3963 bool CanPartBeAdded(const ShipPart* part);
3964
3965 void ClearParts(); //!< removes all parts from design. hull is not altered
3966 /** Remove parts called \p part_name*/
3967 void ClearPart(const std::string& part_name);
3968
3969 /** Set the design hull \p hull_name, displaying appropriate background image and creating
3970 appropriate SlotControls. If \p signal is false do not emit the the
3971 DesignChangedSignal(). */
3972 void SetHull(const std::string& hull_name, bool signal = true);
3973 void SetHull(const ShipHull* hull, bool signal = true);
3974 void SetDesign(const ShipDesign* ship_design); //!< sets the displayed design by setting the appropriate hull and parts
3975 void SetDesign(int design_id); //!< sets the displayed design by setting the appropriate hull and parts
3976 /** SetDesign to the design with \p uuid from the SavedDesignManager. */
3977 void SetDesign(const boost::uuids::uuid& uuid);
3978
3979 /** sets design hull and parts to those specified */
3980 void SetDesignComponents(const std::string& hull,
3981 const std::vector<std::string>& parts);
3982 void SetDesignComponents(const std::string& hull,
3983 const std::vector<std::string>& parts,
3984 const std::string& name,
3985 const std::string& desc);
3986
3987 /** Responds to the design being changed **/
3988 void DesignChanged();
3989
3990 /** Add a design. */
3991 std::pair<int, boost::uuids::uuid> AddDesign();
3992
3993 /** Replace an existing design.*/
3994 void ReplaceDesign();
3995
3996 void ToggleDescriptionEditor();
3997
3998 void HighlightSlotType(std::vector<ShipSlotType>& slot_types); //!< renders slots of the indicated types differently, perhaps to indicate that that those slots can be drop targets for a particular part?
3999
4000 /** Track changes in base type. */
4001 void HandleBaseTypeChange(const DesignWnd::BaseSelector::BaseSelectorTab base_type);
4002 //@}
4003
4004 /** emitted when the design is changed (by adding or removing parts, not
4005 * name or description changes) */
4006 mutable boost::signals2::signal<void ()> DesignChangedSignal;
4007
4008 /** emitted when the design name is changed */
4009 mutable boost::signals2::signal<void ()> DesignNameChangedSignal;
4010
4011 //! Propagates signals from contained SlotControls that signal that a part
4012 //! has been clicked.
4013 mutable boost::signals2::signal<void (const ShipPart*, GG::Flags<GG::ModKey>)> ShipPartClickedSignal;
4014
4015 mutable boost::signals2::signal<void (const ShipHull*)> ShipHullClickedSignal;
4016
4017 /** emitted when the user clicks the m_replace_button to replace the currently selected
4018 * design with the new design in the player's empire */
4019 mutable boost::signals2::signal<void ()> DesignReplacedSignal;
4020
4021 /** emitted when the user clicks the m_confirm_button to add the new
4022 * design to the player's empire */
4023 mutable boost::signals2::signal<void ()> DesignConfirmedSignal;
4024
4025 /** emitted when the user clicks on the background of this main panel and
4026 * a completed design is showing */
4027 mutable boost::signals2::signal<void (int)> CompleteDesignClickedSignal;
4028
4029 protected:
4030 void DropsAcceptable(DropsAcceptableIter first, DropsAcceptableIter last,
4031 const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) const override;
4032
4033 private:
4034 void Populate(); //!< creates and places SlotControls for current hull
4035 void DoLayout(); //!< positions buttons, text entry boxes and SlotControls
4036 void DesignNameChanged(); //!< responds to the design name being changed
4037 void RefreshIncompleteDesign() const;
4038 std::string GetCleanDesignDump(const ShipDesign* ship_design); //!< similar to ship design dump but without 'lookup_strings', icon and model entries
4039
4040 //! Adds part to slot number
4041 bool AddPartEmptySlot(const ShipPart* part, int slot_number);
4042
4043 //! Swaps part in slot # pair.first to slot # pair.second, adds given part
4044 //! to slot # pair.first
4045 bool AddPartWithSwapping(const ShipPart* part, std::pair<int, int> swap_and_empty_slot);
4046
4047 //! Determines if a part can be added to any empty slot, returns the slot
4048 //! index if possible, otherwise -1
4049 int FindEmptySlotForPart(const ShipPart* part);
4050
4051 void DesignNameEditedSlot(const std::string& new_name); //!< triggered when m_design_name's AfterTextChangedSignal fires. Used for basic name validation.
4052
4053 //! Determines if a part can be added to a slot with swapping, returns
4054 //! a pair containing the slot to swap and an empty slot, otherwise a pair
4055 //! with -1.
4056 //! This function only tries to find a way to add the given part by
4057 //! swapping a part already in a slot to an empty slot.
4058 //! If theres an open slot that the given part could go into but all of the
4059 //! occupied slots contain parts that can't swap into the open slot
4060 //! This function will indicate that it could not add the part, even though
4061 //! adding the part is possible
4062 std::pair<int, int> FindSlotForPartWithSwapping(const ShipPart* part);
4063
4064 const ShipHull* m_hull = nullptr;
4065 std::vector<std::shared_ptr<SlotControl>> m_slots;
4066 boost::optional<int> m_replaced_design_id = boost::none; // The design id if this design is replacable
4067 boost::optional<boost::uuids::uuid> m_replaced_design_uuid = boost::none; // The design uuid if this design is replacable
4068
4069 /// Whether to add new designs to current or saved designs
4070 /// This tracks the last relevant selected tab in the base selector
4071 DesignWnd::BaseSelector::BaseSelectorTab m_type_to_create = DesignWnd::BaseSelector::BaseSelectorTab::Current;
4072
4073 mutable std::shared_ptr<ShipDesign> m_incomplete_design;
4074
4075 std::shared_ptr<GG::StaticGraphic> m_background_image;
4076 std::shared_ptr<GG::Label> m_design_name_label;
4077 std::shared_ptr<GG::Edit> m_design_name;
4078 std::shared_ptr<GG::StateButton> m_design_description_toggle;
4079 std::shared_ptr<GG::MultiEdit> m_design_description_edit;
4080 std::shared_ptr<GG::Button> m_replace_button;
4081 std::shared_ptr<GG::Button> m_confirm_button;
4082 std::shared_ptr<GG::Button> m_clear_button;
4083 bool m_disabled_by_name = false; // if the design confirm button is currently disabled due to empty name
4084 bool m_disabled_by_part_conflict = false;
4085
4086 boost::signals2::connection m_empire_designs_changed_signal;
4087 };
4088
MainPanel(const std::string & config_name)4089 DesignWnd::MainPanel::MainPanel(const std::string& config_name) :
4090 CUIWnd(UserString("DESIGN_WND_MAIN_PANEL_TITLE"),
4091 GG::INTERACTIVE | GG::DRAGABLE | GG::RESIZABLE,
4092 config_name)
4093 {}
4094
CompleteConstruction()4095 void DesignWnd::MainPanel::CompleteConstruction() {
4096 SetChildClippingMode(ClipToClient);
4097
4098 m_design_name_label = GG::Wnd::Create<CUILabel>(UserString("DESIGN_WND_DESIGN_NAME"), GG::FORMAT_RIGHT, GG::INTERACTIVE);
4099 m_design_name = GG::Wnd::Create<CUIEdit>(UserString("DESIGN_NAME_DEFAULT"));
4100 m_design_description_toggle = GG::Wnd::Create<CUIStateButton>(UserString("DESIGN_WND_DESIGN_DESCRIPTION"),GG::FORMAT_CENTER, std::make_shared<CUILabelButtonRepresenter>());
4101 m_design_description_edit = GG::Wnd::Create<CUIMultiEdit>(UserString("DESIGN_DESCRIPTION_DEFAULT"));
4102 m_design_description_edit->SetTextFormat(m_design_description_edit->GetTextFormat() | GG::FORMAT_IGNORETAGS);
4103 m_replace_button = Wnd::Create<CUIButton>(UserString("DESIGN_WND_UPDATE"));
4104 m_confirm_button = Wnd::Create<CUIButton>(UserString("DESIGN_WND_ADD_FINISHED"));
4105 m_clear_button = Wnd::Create<CUIButton>(UserString("DESIGN_WND_CLEAR"));
4106
4107 m_replace_button->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
4108 m_confirm_button->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
4109
4110 AttachChild(m_design_name_label);
4111 AttachChild(m_design_name);
4112 AttachChild(m_design_description_toggle);
4113 AttachChild(m_design_description_edit);
4114 AttachChild(m_replace_button);
4115 AttachChild(m_confirm_button);
4116 AttachChild(m_clear_button);
4117
4118 #if BOOST_VERSION >= 106000
4119 using boost::placeholders::_1;
4120 #endif
4121
4122 m_clear_button->LeftClickedSignal.connect(boost::bind(&DesignWnd::MainPanel::ClearParts, this));
4123 m_design_name->EditedSignal.connect(
4124 boost::bind(&DesignWnd::MainPanel::DesignNameEditedSlot, this, _1));
4125 m_replace_button->LeftClickedSignal.connect(DesignReplacedSignal);
4126 m_confirm_button->LeftClickedSignal.connect(DesignConfirmedSignal);
4127 m_design_description_toggle->CheckedSignal.connect(
4128 boost::bind(&DesignWnd::MainPanel::ToggleDescriptionEditor, this));
4129 DesignChangedSignal.connect(boost::bind(&DesignWnd::MainPanel::DesignChanged, this));
4130 DesignReplacedSignal.connect(boost::bind(&DesignWnd::MainPanel::ReplaceDesign, this));
4131 DesignConfirmedSignal.connect(boost::bind(&DesignWnd::MainPanel::AddDesign, this));
4132
4133 DesignChanged(); // Initialize components that rely on the current state of the design.
4134
4135 CUIWnd::CompleteConstruction();
4136 SetMinSize(MAIN_PANEL_MIN_SIZE);
4137
4138 DoLayout();
4139 SaveDefaultedOptions();
4140 }
4141
EditingSavedDesign() const4142 boost::optional<const ShipDesign*> DesignWnd::MainPanel::EditingSavedDesign() const {
4143 // Is there a valid replaced_uuid that indexes a saved design?
4144 if (!m_replaced_design_uuid)
4145 return boost::none;
4146
4147 const auto maybe_design = GetSavedDesignsManager().GetDesign(*m_replaced_design_uuid);
4148 if (!maybe_design)
4149 return boost::none;
4150 return maybe_design;
4151 }
4152
EditingCurrentDesign() const4153 boost::optional<const ShipDesign*> DesignWnd::MainPanel::EditingCurrentDesign() const {
4154 // Is there a valid replaced_uuid that indexes a saved design?
4155 if (!m_replaced_design_id || !GetDisplayedDesignsManager().IsKnown(*m_replaced_design_id))
4156 return boost::none;
4157
4158 const auto maybe_design = GetShipDesign(*m_replaced_design_id);
4159 if (!maybe_design)
4160 return boost::none;
4161 return maybe_design;
4162 }
4163
Parts() const4164 const std::vector<std::string> DesignWnd::MainPanel::Parts() const {
4165 std::vector<std::string> retval;
4166 for (const auto& slot : m_slots) {
4167 const ShipPart* part = slot->GetPart();
4168 if (part)
4169 retval.push_back(part->Name());
4170 else
4171 retval.push_back("");
4172 }
4173 return retval;
4174 }
4175
Hull() const4176 const std::string& DesignWnd::MainPanel::Hull() const {
4177 if (m_hull)
4178 return m_hull->Name();
4179 else
4180 return EMPTY_STRING;
4181 }
4182
IsDesignNameValid() const4183 bool DesignWnd::MainPanel::IsDesignNameValid() const {
4184 // All whitespace probably shouldn't be OK either.
4185 return !m_design_name->Text().empty();
4186 }
4187
4188 std::pair<DesignWnd::MainPanel::I18nString, DesignWnd::MainPanel::I18nString>
ValidatedNameAndDescription() const4189 DesignWnd::MainPanel::ValidatedNameAndDescription() const
4190 {
4191 const auto maybe_saved = EditingSavedDesign();
4192
4193 // Determine if the title and descrition could both be string table values.
4194
4195 // Is the title a stringtable index or the same as the saved designs value
4196 const std::string name_index =
4197 (UserStringExists(m_design_name->Text()) ? m_design_name->Text() :
4198 ((maybe_saved && (*maybe_saved)->LookupInStringtable()
4199 && (m_design_name->Text() == (*maybe_saved)->Name())) ? (*maybe_saved)->Name(false) : ""));
4200
4201 // Is the descrition a stringtable index or the same as the saved designs value
4202 const std::string desc_index =
4203 (UserStringExists(m_design_description_edit->Text()) ? m_design_description_edit->Text() :
4204 ((maybe_saved && (*maybe_saved)->LookupInStringtable()
4205 && (m_design_description_edit->Text() == (*maybe_saved)->Description())) ? (*maybe_saved)->Description(false) : ""));
4206
4207 // Are both the title and the description string table lookup values
4208 if (!name_index.empty() && !desc_index.empty())
4209 return std::make_pair(
4210 I18nString(true, name_index),
4211 I18nString(true, desc_index));
4212
4213 return std::make_pair(
4214 I18nString(false, (IsDesignNameValid()) ? m_design_name->Text() : UserString("DESIGN_NAME_DEFAULT")),
4215 I18nString(false, m_design_description_edit->Text()));
4216 }
4217
ValidatedDesignName() const4218 const DesignWnd::MainPanel::I18nString DesignWnd::MainPanel::ValidatedDesignName() const
4219 { return ValidatedNameAndDescription().first; }
4220
DesignDescription() const4221 const DesignWnd::MainPanel::I18nString DesignWnd::MainPanel::DesignDescription() const
4222 { return ValidatedNameAndDescription().second; }
4223
GetIncompleteDesign() const4224 std::shared_ptr<const ShipDesign> DesignWnd::MainPanel::GetIncompleteDesign() const {
4225 RefreshIncompleteDesign();
4226 return m_incomplete_design;
4227 }
4228
GetReplacedDesignID() const4229 boost::optional<int> DesignWnd::MainPanel::GetReplacedDesignID() const
4230 { return m_replaced_design_id; }
4231
CurrentDesignIsRegistered()4232 boost::optional<const ShipDesign*> DesignWnd::MainPanel::CurrentDesignIsRegistered() {
4233 int empire_id = HumanClientApp::GetApp()->EmpireID();
4234 const auto empire = GetEmpire(empire_id);
4235 if (!empire) {
4236 ErrorLogger() << "DesignWnd::MainPanel::CurrentDesignIsRegistered couldn't get the current empire.";
4237 return boost::none;
4238 }
4239
4240 if (const auto& cur_design = GetIncompleteDesign()) {
4241 for (const auto design_id : empire->ShipDesigns()) {
4242 const auto ship_design = GetShipDesign(design_id);
4243 if (*ship_design == *cur_design.get())
4244 return ship_design;
4245 }
4246 }
4247 return boost::none;
4248 }
4249
LClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)4250 void DesignWnd::MainPanel::LClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) {
4251 if (m_hull)
4252 ShipHullClickedSignal(m_hull);
4253 CUIWnd::LClick(pt, mod_keys);
4254 }
4255
SizeMove(const GG::Pt & ul,const GG::Pt & lr)4256 void DesignWnd::MainPanel::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
4257 CUIWnd::SizeMove(ul, lr);
4258 DoLayout();
4259 }
4260
Sanitize()4261 void DesignWnd::MainPanel::Sanitize() {
4262 SetHull(nullptr, false);
4263 m_design_name->SetText(UserString("DESIGN_NAME_DEFAULT"));
4264 m_design_description_edit->SetText(UserString("DESIGN_DESCRIPTION_DEFAULT"));
4265 // disconnect old empire design signal
4266 m_empire_designs_changed_signal.disconnect();
4267 }
4268
SetPart(const std::string & part_name,unsigned int slot)4269 void DesignWnd::MainPanel::SetPart(const std::string& part_name, unsigned int slot)
4270 { SetPart(GetShipPart(part_name), slot); }
4271
SetPart(const ShipPart * part,unsigned int slot,bool emit_signal,bool change_all_similar_parts)4272 void DesignWnd::MainPanel::SetPart(const ShipPart* part, unsigned int slot,
4273 bool emit_signal /* = false */,
4274 bool change_all_similar_parts /*= false*/)
4275 {
4276 //DebugLogger() << "DesignWnd::MainPanel::SetPart(" << (part ? part->Name() : "no part") << ", slot " << slot << ")";
4277 if (slot > m_slots.size()) {
4278 ErrorLogger() << "DesignWnd::MainPanel::SetPart specified nonexistant slot";
4279 return;
4280 }
4281
4282 if (!change_all_similar_parts) {
4283 m_slots[slot]->SetPart(part);
4284
4285 } else {
4286 const auto original_part = m_slots[slot]->GetPart();
4287 std::string original_part_name = original_part ? original_part->Name() : "";
4288
4289 if (change_all_similar_parts) {
4290 for (auto& slot : m_slots) {
4291 // skip incompatible slots
4292 if (!part->CanMountInSlotType(slot->SlotType()))
4293 continue;
4294
4295 // skip different type parts
4296 const auto replaced_part = slot->GetPart();
4297 if (replaced_part && (replaced_part->Name() != original_part_name))
4298 continue;
4299
4300 slot->SetPart(part);
4301 }
4302 }
4303 }
4304
4305 if (emit_signal) // to avoid unnecessary signal repetition.
4306 DesignChangedSignal();
4307 }
4308
SetParts(const std::vector<std::string> & parts)4309 void DesignWnd::MainPanel::SetParts(const std::vector<std::string>& parts) {
4310 unsigned int num_parts = std::min(parts.size(), m_slots.size());
4311 for (unsigned int i = 0; i < num_parts; ++i)
4312 m_slots[i]->SetPart(parts[i]);
4313
4314 DesignChangedSignal();
4315 }
4316
AddPart(const ShipPart * part)4317 void DesignWnd::MainPanel::AddPart(const ShipPart* part) {
4318 if (AddPartEmptySlot(part, FindEmptySlotForPart(part)))
4319 return;
4320
4321 if (!AddPartWithSwapping(part, FindSlotForPartWithSwapping(part)))
4322 DebugLogger() << "DesignWnd::MainPanel::AddPart(" << (part ? part->Name() : "no part")
4323 << ") couldn't find a slot for the part";
4324 }
4325
CanPartBeAdded(const ShipPart * part)4326 bool DesignWnd::MainPanel::CanPartBeAdded(const ShipPart* part) {
4327 std::pair<int, int> swap_result = FindSlotForPartWithSwapping(part);
4328 return (FindEmptySlotForPart(part) >= 0 || (swap_result.first >= 0 && swap_result.second >= 0));
4329 }
4330
AddPartEmptySlot(const ShipPart * part,int slot_number)4331 bool DesignWnd::MainPanel::AddPartEmptySlot(const ShipPart* part, int slot_number) {
4332 if (!part || slot_number < 0)
4333 return false;
4334 SetPart(part, slot_number);
4335 DesignChangedSignal();
4336 return true;
4337 }
4338
AddPartWithSwapping(const ShipPart * part,std::pair<int,int> swap_and_empty_slot)4339 bool DesignWnd::MainPanel::AddPartWithSwapping(const ShipPart* part,
4340 std::pair<int, int> swap_and_empty_slot)
4341 {
4342 if (!part || swap_and_empty_slot.first < 0 || swap_and_empty_slot.second < 0)
4343 return false;
4344 // Move the flexible part to the first open spot
4345 SetPart(m_slots[swap_and_empty_slot.first]->GetPart(), swap_and_empty_slot.second);
4346 // Move replacement part into the newly opened slot
4347 SetPart(part, swap_and_empty_slot.first);
4348 DesignChangedSignal();
4349 return true;
4350 }
4351
FindEmptySlotForPart(const ShipPart * part)4352 int DesignWnd::MainPanel::FindEmptySlotForPart(const ShipPart* part) {
4353 int result = -1;
4354 if (!part)
4355 return result;
4356
4357 if (part->Class() == PC_FIGHTER_HANGAR) {
4358 // give up if part is a hangar and there is already a hangar of another type
4359 std::string already_seen_hangar_name;
4360 for (const auto& slot : m_slots) {
4361 const ShipPart* part = slot->GetPart();
4362 if (!part || part->Class() != PC_FIGHTER_HANGAR)
4363 continue;
4364 if (part->Name() != part->Name())
4365 return result;
4366 }
4367 }
4368
4369 for (unsigned int i = 0; i < m_slots.size(); ++i) { // scan through slots to find one that can mount part
4370 const ShipSlotType slot_type = m_slots[i]->SlotType();
4371 const ShipPart* slotted_part = m_slots[i]->GetPart();
4372
4373 if (!slotted_part && part->CanMountInSlotType(slot_type)) {
4374 result = i;
4375 return result;
4376 }
4377 }
4378 return result;
4379 }
4380
DesignNameEditedSlot(const std::string & new_name)4381 void DesignWnd::MainPanel::DesignNameEditedSlot(const std::string& new_name) {
4382 DesignNameChanged(); // Check whether the confirmation button should be enabled or disabled each time the name changes.
4383 }
4384
FindSlotForPartWithSwapping(const ShipPart * part)4385 std::pair<int, int> DesignWnd::MainPanel::FindSlotForPartWithSwapping(const ShipPart* part) {
4386 // result.first = swap_slot, result.second = empty_slot
4387 // if any of the pair == -1, no swap!
4388
4389 if (!part)
4390 return {-1, -1};
4391
4392 // check if adding the part would cause the design to have multiple different types of hangar (which is not allowed)
4393 if (part->Class() == PC_FIGHTER_HANGAR) {
4394 for (const auto& slot : m_slots) {
4395 const ShipPart* existing_part = slot->GetPart();
4396 if (!existing_part || existing_part->Class() != PC_FIGHTER_HANGAR)
4397 continue;
4398 if (existing_part->Name() != part->Name())
4399 return {-1, -1}; // conflict; new part can't be added
4400 }
4401 }
4402
4403 // first search for an empty compatible slot for the new part
4404 for (const auto& slot : m_slots) {
4405 if (!part->CanMountInSlotType(slot->SlotType()))
4406 continue; // skip incompatible slots
4407
4408 if (!slot->GetPart())
4409 return {-1, -1}; // empty slot that can hold part. no swapping needed.
4410 }
4411
4412
4413 // second, scan for a slot containing a part that can be moved to another
4414 // slot to make room for the new part
4415 for (unsigned int i = 0; i < m_slots.size(); ++i) {
4416 if (!part->CanMountInSlotType(m_slots[i]->SlotType()))
4417 continue; // skip incompatible slots
4418
4419 // can now assume m_slots[i] has a part, as if it didn't, it would have
4420 // been found in the first loop
4421
4422 // see if we can move the part in the candidate slot to an empty slot elsewhere
4423 for (unsigned int j = 0; j < m_slots.size(); ++j) {
4424 if (m_slots[j]->GetPart())
4425 continue; // only consider moving into empty slots
4426
4427 if (m_slots[i]->GetPart()->CanMountInSlotType(m_slots[j]->SlotType()))
4428 return {i, j}; // other slot can hold current part to make room for new part
4429 }
4430 }
4431
4432 return {-1, -1};
4433 }
4434
ClearParts()4435 void DesignWnd::MainPanel::ClearParts() {
4436 for (auto& slot : m_slots)
4437 slot->SetPart(nullptr);
4438 DesignChangedSignal();
4439 }
4440
ClearPart(const std::string & part_name)4441 void DesignWnd::MainPanel::ClearPart(const std::string& part_name) {
4442 bool changed = false;
4443 for (const auto& slot : m_slots) {
4444 const ShipPart* existing_part = slot->GetPart();
4445 if (!existing_part)
4446 continue;
4447 if (existing_part->Name() != part_name)
4448 continue;
4449 slot->SetPart(nullptr);
4450 changed = true;
4451 }
4452
4453 if (changed)
4454 DesignChangedSignal();
4455 }
4456
SetHull(const std::string & hull_name,bool signal)4457 void DesignWnd::MainPanel::SetHull(const std::string& hull_name, bool signal)
4458 { SetHull(GetShipHull(hull_name), signal); }
4459
SetHull(const ShipHull * hull,bool signal)4460 void DesignWnd::MainPanel::SetHull(const ShipHull* hull, bool signal) {
4461 m_hull = hull;
4462 DetachChild(m_background_image);
4463 m_background_image = nullptr;
4464 if (m_hull) {
4465 std::shared_ptr<GG::Texture> texture = ClientUI::HullTexture(hull->Name());
4466 m_background_image = GG::Wnd::Create<GG::StaticGraphic>(texture, GG::GRAPHIC_PROPSCALE | GG::GRAPHIC_FITGRAPHIC);
4467 AttachChild(m_background_image);
4468 MoveChildDown(m_background_image);
4469 }
4470 Populate();
4471 DoLayout();
4472 if (signal)
4473 DesignChangedSignal();
4474 }
4475
SetDesign(const ShipDesign * ship_design)4476 void DesignWnd::MainPanel::SetDesign(const ShipDesign* ship_design) {
4477 m_incomplete_design.reset();
4478
4479 if (!ship_design) {
4480 SetHull(nullptr);
4481 return;
4482 }
4483
4484 if (!ship_design->IsMonster()) {
4485 m_replaced_design_id = ship_design->ID();
4486 m_replaced_design_uuid = ship_design->UUID();
4487 } else {
4488 // Allow editing of monsters if the design is a saved design
4489 const auto is_saved_monster = GetSavedDesignsManager().GetDesign(ship_design->UUID());
4490 m_replaced_design_id = is_saved_monster ? ship_design->ID() : boost::optional<int>();
4491 m_replaced_design_uuid = is_saved_monster ? ship_design->UUID() : boost::optional<boost::uuids::uuid>();
4492 }
4493
4494 m_design_name->SetText(ship_design->Name());
4495 m_design_description_edit->SetText(ship_design->Description());
4496
4497 bool suppress_design_changed_signal = true;
4498 SetHull(ship_design->Hull(), !suppress_design_changed_signal);
4499
4500 SetParts(ship_design->Parts());
4501 DesignChangedSignal();
4502 }
4503
SetDesign(int design_id)4504 void DesignWnd::MainPanel::SetDesign(int design_id)
4505 { SetDesign(GetShipDesign(design_id)); }
4506
SetDesign(const boost::uuids::uuid & uuid)4507 void DesignWnd::MainPanel::SetDesign(const boost::uuids::uuid& uuid)
4508 { SetDesign(GetSavedDesignsManager().GetDesign(uuid)); }
4509
SetDesignComponents(const std::string & hull,const std::vector<std::string> & parts)4510 void DesignWnd::MainPanel::SetDesignComponents(const std::string& hull,
4511 const std::vector<std::string>& parts)
4512 {
4513 m_replaced_design_id = boost::none;
4514 m_replaced_design_uuid = boost::none;
4515 SetHull(hull, false);
4516 SetParts(parts);
4517 }
4518
SetDesignComponents(const std::string & hull,const std::vector<std::string> & parts,const std::string & name,const std::string & desc)4519 void DesignWnd::MainPanel::SetDesignComponents(const std::string& hull,
4520 const std::vector<std::string>& parts,
4521 const std::string& name,
4522 const std::string& desc)
4523 {
4524 SetDesignComponents(hull, parts);
4525 m_design_name->SetText(name);
4526 m_design_description_edit->SetText(desc);
4527 }
4528
HighlightSlotType(std::vector<ShipSlotType> & slot_types)4529 void DesignWnd::MainPanel::HighlightSlotType(std::vector<ShipSlotType>& slot_types) {
4530 for (auto& control : m_slots) {
4531 if (std::count(slot_types.begin(), slot_types.end(), control->SlotType()))
4532 control->Highlight(true);
4533 else
4534 control->Highlight(false);
4535 }
4536 }
4537
HandleBaseTypeChange(DesignWnd::BaseSelector::BaseSelectorTab base_type)4538 void DesignWnd::MainPanel::HandleBaseTypeChange(DesignWnd::BaseSelector::BaseSelectorTab base_type) {
4539 if (m_type_to_create == base_type)
4540 return;
4541 switch (base_type) {
4542 case DesignWnd::BaseSelector::BaseSelectorTab::Current:
4543 case DesignWnd::BaseSelector::BaseSelectorTab::Saved:
4544 m_type_to_create = base_type;
4545 break;
4546 default:
4547 break;
4548 }
4549 DesignChanged();
4550 }
4551
Populate()4552 void DesignWnd::MainPanel::Populate() {
4553 for (const auto& slot: m_slots)
4554 DetachChild(slot);
4555 m_slots.clear();
4556
4557 if (!m_hull)
4558 return;
4559
4560 const std::vector<ShipHull::Slot>& hull_slots = m_hull->Slots();
4561
4562 for (size_t i = 0; i != hull_slots.size(); ++i) {
4563 const ShipHull::Slot& slot = hull_slots[i];
4564 auto slot_control = GG::Wnd::Create<SlotControl>(slot.x, slot.y, slot.type);
4565 m_slots.push_back(slot_control);
4566 AttachChild(slot_control);
4567
4568 #if BOOST_VERSION >= 106000
4569 using boost::placeholders::_1;
4570 using boost::placeholders::_2;
4571 #endif
4572
4573 slot_control->SlotContentsAlteredSignal.connect(
4574 boost::bind(static_cast<void (DesignWnd::MainPanel::*)(
4575 const ShipPart*, unsigned int, bool, bool)>(&DesignWnd::MainPanel::SetPart),
4576 this, _1, i, true, _2));
4577 slot_control->ShipPartClickedSignal.connect(
4578 ShipPartClickedSignal);
4579 }
4580 }
4581
DoLayout()4582 void DesignWnd::MainPanel::DoLayout() {
4583 // position labels and text edit boxes for name and description and buttons to clear and confirm design
4584
4585 const int PTS = ClientUI::Pts();
4586 const GG::X PTS_WIDE(PTS / 2); // guess at how wide per character the font needs
4587 const int PAD = 6;
4588
4589 GG::Pt ul,lr,ll,ur,mus;
4590 lr = ClientSize() - GG::Pt(GG::X(PAD), GG::Y(PAD));
4591 m_confirm_button->SizeMove(lr - m_confirm_button->MinUsableSize(), lr);
4592
4593 mus=m_replace_button->MinUsableSize();
4594 ul = m_confirm_button->RelativeUpperLeft() - GG::Pt(mus.x+PAD, GG::Y(0));
4595 m_replace_button->SizeMove(ul, ul+mus);
4596
4597 ll= GG::Pt(GG::X(PAD), ClientHeight() - PAD);
4598 mus=m_clear_button->MinUsableSize();
4599 ul = ll-GG::Pt(GG::X0, mus.y);
4600 m_clear_button->SizeMove(ul, ul+mus);
4601
4602 ul = GG::Pt(GG::X(PAD), GG::Y(PAD));
4603 // adjust based on the (bigger) height of the edit bar
4604 lr= ul+GG::Pt(m_design_name_label->MinUsableSize().x, m_design_name->MinUsableSize().y);
4605 m_design_name_label->SizeMove(ul, lr);
4606
4607 ul= GG::Pt(m_design_name_label->RelativeLowerRight().x+PAD, GG::Y(PAD));
4608 m_design_name->SizeMove(ul, GG::Pt(GG::X(ClientWidth()-PAD), ul.y+m_design_name->MinUsableSize().y));
4609
4610 ul=GG::Pt(GG::X(PAD), GG::Y(m_design_name->RelativeLowerRight().y+PAD));
4611 // Apparently calling minuseablesize on the button itself doesn't work
4612 lr= ul+GG::Pt(m_design_description_toggle->GetLabel()->MinUsableSize().x+10, m_design_name->MinUsableSize().y);
4613 m_design_description_toggle->SizeMove(ul, lr);
4614
4615 ul.x = m_design_description_toggle->RelativeLowerRight().x + PAD;
4616 m_design_description_edit->SizeMove(ul, GG::Pt(GG::X(ClientWidth()-PAD),ul.y+PTS*4+8));
4617 if (m_design_description_toggle->Checked()) { m_design_description_edit->Show() ; }
4618 else { m_design_description_edit->Hide(); }
4619
4620 // place background image of hull
4621 ul.x = GG::X0;
4622 ul.y += m_design_name->Height();
4623 GG::Rect background_rect = GG::Rect(ul, ClientLowerRight());
4624
4625 if (m_background_image) {
4626 GG::Pt bg_ul = background_rect.UpperLeft();
4627 GG::Pt bg_lr = ClientSize();
4628 m_background_image->SizeMove(bg_ul, bg_lr);
4629 background_rect = m_background_image->RenderedArea();
4630 }
4631
4632 // place slot controls over image of hull
4633 for (auto& slot : m_slots) {
4634 GG::X x(background_rect.Left() - slot->Width()/2 - ClientUpperLeft().x + slot->XPositionFraction() * background_rect.Width());
4635 GG::Y y(background_rect.Top() - slot->Height()/2 - ClientUpperLeft().y + slot->YPositionFraction() * background_rect.Height());
4636 slot->MoveTo(GG::Pt(x, y));
4637 }
4638 }
4639
DesignChanged()4640 void DesignWnd::MainPanel::DesignChanged() {
4641 m_replace_button->ClearBrowseInfoWnd();
4642 m_confirm_button->ClearBrowseInfoWnd();
4643
4644 int client_empire_id = HumanClientApp::GetApp()->EmpireID();
4645 m_disabled_by_name = false;
4646 m_disabled_by_part_conflict = false;
4647
4648 m_replace_button->Disable(true);
4649 m_confirm_button->Disable(true);
4650
4651 m_replace_button->SetText(UserString("DESIGN_WND_UPDATE_FINISHED"));
4652 m_confirm_button->SetText(UserString("DESIGN_WND_ADD_FINISHED"));
4653
4654 if (!m_hull) {
4655 m_replace_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4656 UserString("DESIGN_INVALID"), UserString("DESIGN_UPDATE_INVALID_NO_CANDIDATE")));
4657 m_confirm_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4658 UserString("DESIGN_INVALID"), UserString("DESIGN_INV_NO_HULL")));
4659 return;
4660 }
4661
4662 if (client_empire_id == ALL_EMPIRES) {
4663 m_replace_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4664 UserString("DESIGN_INVALID"), UserString("DESIGN_INV_MODERATOR")));
4665 m_confirm_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4666 UserString("DESIGN_INVALID"), UserString("DESIGN_INV_MODERATOR")));
4667 return;
4668 }
4669
4670 if (!IsDesignNameValid()) {
4671 m_disabled_by_name = true;
4672
4673 m_replace_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4674 UserString("DESIGN_INVALID"), UserString("DESIGN_INV_NO_NAME")));
4675 m_confirm_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4676 UserString("DESIGN_INVALID"), UserString("DESIGN_INV_NO_NAME")));
4677 return;
4678 }
4679
4680 if (!ShipDesign::ValidDesign(m_hull->Name(), Parts())) {
4681 // if a design has exclusion violations between parts and hull, highlight these and indicate it on the button
4682
4683 std::pair<std::string, std::string> problematic_components;
4684
4685 // check hull exclusions against all parts...
4686 const std::set<std::string>& hull_exclusions = m_hull->Exclusions();
4687 for (const std::string& part_name : Parts()) {
4688 if (part_name.empty())
4689 continue;
4690 if (hull_exclusions.count(part_name)) {
4691 m_disabled_by_part_conflict = true;
4692 problematic_components.first = m_hull->Name();
4693 problematic_components.second = part_name;
4694 }
4695 }
4696
4697 // check part exclusions against other parts and hull
4698 std::set<std::string> already_seen_component_names;
4699 already_seen_component_names.insert(m_hull->Name());
4700 for (const std::string& part_name : Parts()) {
4701 if (m_disabled_by_part_conflict)
4702 break;
4703 const ShipPart* part = GetShipPart(part_name);
4704 if (!part)
4705 continue;
4706 for (const std::string& excluded_part : part->Exclusions()) {
4707 if (already_seen_component_names.count(excluded_part)) {
4708 m_disabled_by_part_conflict = true;
4709 problematic_components.first = part_name;
4710 problematic_components.second = excluded_part;
4711 break;
4712 }
4713 }
4714 already_seen_component_names.insert(part_name);
4715 }
4716
4717
4718 if (m_disabled_by_part_conflict) {
4719 m_replace_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4720 UserString("DESIGN_WND_COMPONENT_CONFLICT"),
4721 boost::io::str(FlexibleFormat(UserString("DESIGN_WND_COMPONENT_CONFLICT_DETAIL"))
4722 % UserString(problematic_components.first)
4723 % UserString(problematic_components.second))));
4724 m_confirm_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4725 UserString("DESIGN_WND_COMPONENT_CONFLICT"),
4726 boost::io::str(FlexibleFormat(UserString("DESIGN_WND_COMPONENT_CONFLICT_DETAIL"))
4727 % UserString(problematic_components.first)
4728 % UserString(problematic_components.second))));
4729
4730 // todo: mark conflicting parts somehow
4731 }
4732 return;
4733 }
4734
4735 const auto& cur_design = GetIncompleteDesign();
4736
4737 if (!cur_design)
4738 return;
4739
4740 const auto new_design_name = ValidatedDesignName().DisplayText();
4741
4742 // producible only matters for empire designs.
4743 // Monster designs can be edited as saved designs.
4744 bool producible = cur_design->Producible();
4745
4746 // Current designs can not duplicate other designs, be already registered.
4747 const auto existing_design = CurrentDesignIsRegistered();
4748
4749 const auto& replaced_saved_design = EditingSavedDesign();
4750
4751 const auto& replaced_current_design = EditingCurrentDesign();
4752
4753 // Choose text for the replace button: replace saved design, replace current design or already known.
4754
4755 // A changed saved design can be replaced with an updated design
4756 if (replaced_saved_design) {
4757 if (cur_design && !(*cur_design == **replaced_saved_design)) {
4758 m_replace_button->SetText(UserString("DESIGN_WND_UPDATE_SAVED"));
4759 m_replace_button->SetBrowseInfoWnd(
4760 GG::Wnd::Create<TextBrowseWnd>(
4761 UserString("DESIGN_WND_UPDATE_SAVED"),
4762 boost::io::str(FlexibleFormat(UserString("DESIGN_WND_UPDATE_SAVED_DETAIL"))
4763 % (*replaced_saved_design)->Name()
4764 % new_design_name)));
4765 m_replace_button->Disable(false);
4766 }
4767 }
4768
4769 if (producible) {
4770 if (existing_design
4771 && m_type_to_create == DesignWnd::BaseSelector::BaseSelectorTab::Current)
4772 {
4773 // Rename duplicate finished designs
4774 if ((*existing_design)->Name() != new_design_name) {
4775 m_replace_button->SetText(UserString("DESIGN_WND_RENAME_FINISHED"));
4776 m_replace_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4777 UserString("DESIGN_WND_RENAME_FINISHED"),
4778 boost::io::str(FlexibleFormat(UserString("DESIGN_WND_RENAME_FINISHED_DETAIL"))
4779 % ((*existing_design)->Name())
4780 % new_design_name)));
4781 m_replace_button->Disable(false);
4782
4783 // Otherwise mark it as known.
4784 } else {
4785 m_disabled_by_name = true;
4786 m_replace_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4787 UserString("DESIGN_WND_KNOWN"),
4788 boost::io::str(FlexibleFormat(UserString("DESIGN_WND_KNOWN_DETAIL"))
4789 % (*existing_design)->Name())));
4790 }
4791
4792
4793 } else if (replaced_current_design) {
4794 // A current design can be replaced if it doesn't duplicate an existing design
4795 m_replace_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4796 UserString("DESIGN_WND_UPDATE_FINISHED"),
4797 boost::io::str(FlexibleFormat(UserString("DESIGN_WND_UPDATE_FINISHED_DETAIL"))
4798 % (*replaced_current_design)->Name()
4799 % new_design_name)));
4800 m_replace_button->Disable(false);
4801 }
4802 }
4803
4804 // Choose text for the add new design button: add saved design, add current design or already known.
4805
4806 // Add a saved design if the saved base selector was visited more recently than the current tab.
4807 if (m_type_to_create == DesignWnd::BaseSelector::BaseSelectorTab::Saved) {
4808 // A new saved design can always be created
4809 m_confirm_button->SetText(UserString("DESIGN_WND_ADD_SAVED"));
4810 m_confirm_button->SetBrowseInfoWnd(
4811 GG::Wnd::Create<TextBrowseWnd>(
4812 UserString("DESIGN_WND_ADD_SAVED"),
4813 boost::io::str(FlexibleFormat(UserString("DESIGN_WND_ADD_SAVED_DETAIL"))
4814 % new_design_name)));
4815 m_confirm_button->Disable(false);
4816 } else if (producible) {
4817 if (!existing_design) {
4818 // A new current can be added if it does not duplicate an existing design.
4819 m_confirm_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4820 UserString("DESIGN_WND_ADD_FINISHED"),
4821 boost::io::str(FlexibleFormat(UserString("DESIGN_WND_ADD_FINISHED_DETAIL"))
4822 % new_design_name)));
4823 m_confirm_button->Disable(false);
4824
4825 } else {
4826 // Otherwise the design is already known.
4827 m_confirm_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(
4828 UserString("DESIGN_WND_KNOWN"),
4829 boost::io::str(FlexibleFormat(UserString("DESIGN_WND_KNOWN_DETAIL"))
4830 % (*existing_design)->Name())));
4831 }
4832 }
4833 }
4834
DesignNameChanged()4835 void DesignWnd::MainPanel::DesignNameChanged() {
4836 if (m_disabled_by_name || (!IsDesignNameValid() && !m_confirm_button->Disabled()))
4837 DesignChangedSignal();
4838 else if (GetOptionsDB().Get<bool>("ui.design.pedia.title.dynamic.enabled"))
4839 DesignNameChangedSignal();
4840 else
4841 RefreshIncompleteDesign();
4842 }
4843
GetCleanDesignDump(const ShipDesign * ship_design)4844 std::string DesignWnd::MainPanel::GetCleanDesignDump(const ShipDesign* ship_design) {
4845 std::string retval = "ShipDesign\n";
4846 retval += ship_design->Name() + "\"\n";
4847 retval += ship_design->Hull() + "\"\n";
4848 for (const std::string& part_name : ship_design->Parts()) {
4849 retval += "\"" + part_name + "\"\n";
4850 }
4851 return retval;
4852 }
4853
RefreshIncompleteDesign() const4854 void DesignWnd::MainPanel::RefreshIncompleteDesign() const {
4855 const auto name_and_description = ValidatedNameAndDescription();
4856 const auto& name = name_and_description.first;
4857 const auto& description = name_and_description.second;
4858
4859 if (ShipDesign* design = m_incomplete_design.get()) {
4860 if (design->Hull() == Hull() &&
4861 design->Name(false) == name.StoredString() &&
4862 design->Description(false) == description.StoredString() &&
4863 design->Parts() == Parts())
4864 {
4865 // nothing has changed, so don't need to update
4866 return;
4867 }
4868 }
4869
4870 // assemble and check info for new design
4871 const std::string& hull = Hull();
4872 std::vector<std::string> parts = Parts();
4873
4874 const std::string& icon = m_hull ? m_hull->Icon() : EMPTY_STRING;
4875
4876 const auto uuid = boost::uuids::random_generator()();
4877
4878 // update stored design
4879 m_incomplete_design.reset();
4880 if (hull.empty())
4881 return;
4882 try {
4883 m_incomplete_design = std::make_shared<ShipDesign>(
4884 std::invalid_argument(""),
4885 name.StoredString(), description.StoredString(),
4886 CurrentTurn(), ClientApp::GetApp()->EmpireID(),
4887 hull, parts, icon, "", name.IsInStringtable(),
4888 false, uuid);
4889 } catch (const std::invalid_argument& e) {
4890 ErrorLogger() << "DesignWnd::MainPanel::RefreshIncompleteDesign " << e.what();
4891 }
4892 }
4893
DropsAcceptable(DropsAcceptableIter first,DropsAcceptableIter last,const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys) const4894 void DesignWnd::MainPanel::DropsAcceptable(DropsAcceptableIter first, DropsAcceptableIter last,
4895 const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) const
4896 {
4897 for (DropsAcceptableIter it = first; it != last; ++it)
4898 it->second = false;
4899
4900 // if multiple things dropped simultaneously somehow, reject all
4901 if (std::distance(first, last) != 1)
4902 return;
4903
4904 if (dynamic_cast<const BasesListBox::BasesListBoxRow*>(first->first))
4905 first->second = true;
4906 }
4907
AcceptDrops(const GG::Pt & pt,std::vector<std::shared_ptr<GG::Wnd>> wnds,GG::Flags<GG::ModKey> mod_keys)4908 void DesignWnd::MainPanel::AcceptDrops(const GG::Pt& pt, std::vector<std::shared_ptr<GG::Wnd>> wnds, GG::Flags<GG::ModKey> mod_keys) {
4909 if (wnds.size() != 1)
4910 ErrorLogger() << "DesignWnd::MainPanel::AcceptDrops given multiple wnds unexpectedly...";
4911
4912 const auto& wnd = *(wnds.begin());
4913 if (!wnd)
4914 return;
4915
4916 if (const auto completed_design_row = dynamic_cast<const BasesListBox::CompletedDesignListBoxRow*>(wnd.get())) {
4917 SetDesign(GetShipDesign(completed_design_row->DesignID()));
4918 }
4919 else if (const auto hullandparts_row = dynamic_cast<const BasesListBox::HullAndPartsListBoxRow*>(wnd.get())) {
4920 const std::string& hull = hullandparts_row->Hull();
4921 const std::vector<std::string>& parts = hullandparts_row->Parts();
4922
4923 SetDesignComponents(hull, parts);
4924 }
4925 else if (const auto saved_design_row = dynamic_cast<const SavedDesignsListBox::SavedDesignListBoxRow*>(wnd.get())) {
4926 const auto& uuid = saved_design_row->DesignUUID();
4927 SetDesign(GetSavedDesignsManager().GetDesign(uuid));
4928 }
4929 }
4930
AddDesign()4931 std::pair<int, boost::uuids::uuid> DesignWnd::MainPanel::AddDesign() {
4932 try {
4933 std::vector<std::string> parts = Parts();
4934 const std::string& hull_name = Hull();
4935
4936 const auto name = ValidatedDesignName();
4937
4938 const auto description = DesignDescription();
4939
4940 std::string icon = "ship_hulls/generic_hull.png";
4941 if (const ShipHull* hull = GetShipHull(hull_name))
4942 icon = hull->Icon();
4943
4944 auto new_uuid = boost::uuids::random_generator()();
4945 auto new_design_id = INVALID_DESIGN_ID;
4946
4947 // create design from stuff chosen in UI
4948 ShipDesign design(std::invalid_argument(""),
4949 name.StoredString(), description.StoredString(),
4950 CurrentTurn(), ClientApp::GetApp()->EmpireID(),
4951 hull_name, parts, icon, "some model", name.IsInStringtable(),
4952 false, new_uuid);
4953
4954 // If editing a saved design insert into saved designs
4955 if (m_type_to_create == DesignWnd::BaseSelector::BaseSelectorTab::Saved) {
4956 auto& manager = GetSavedDesignsManager();
4957 manager.InsertBefore(design, manager.OrderedDesignUUIDs().begin());
4958 new_uuid = *manager.OrderedDesignUUIDs().begin();
4959
4960 // Otherwise insert into current empire designs
4961 } else {
4962 int empire_id = HumanClientApp::GetApp()->EmpireID();
4963 const Empire* empire = GetEmpire(empire_id);
4964 if (!empire) return {INVALID_DESIGN_ID, boost::uuids::uuid{{0}}};
4965
4966 auto order = std::make_shared<ShipDesignOrder>(empire_id, design);
4967 HumanClientApp::GetApp()->Orders().IssueOrder(order);
4968 new_design_id = order->DesignID();
4969
4970 auto& manager = GetDisplayedDesignsManager();
4971 const auto& all_ids = manager.AllOrderedIDs();
4972 manager.InsertBefore(new_design_id, all_ids.empty() ? INVALID_DESIGN_ID : *all_ids.begin());
4973 }
4974
4975 DesignChangedSignal();
4976
4977 DebugLogger() << "Added new design: " << design.Name();
4978
4979 return std::make_pair(new_design_id, new_uuid);
4980
4981 } catch (std::invalid_argument&) {
4982 ErrorLogger() << "DesignWnd::AddDesign tried to add an invalid ShipDesign";
4983 return {INVALID_DESIGN_ID, boost::uuids::uuid{{0}}};
4984 }
4985 }
4986
ReplaceDesign()4987 void DesignWnd::MainPanel::ReplaceDesign() {
4988 auto old_m_type_to_create = m_type_to_create;
4989 m_type_to_create = EditingSavedDesign()
4990 ? DesignWnd::BaseSelector::BaseSelectorTab::Saved
4991 : DesignWnd::BaseSelector::BaseSelectorTab::Current;
4992
4993 const auto new_id_and_uuid = AddDesign();
4994 const auto& new_uuid = new_id_and_uuid.second;
4995 const auto new_design_id = new_id_and_uuid.first;
4996
4997 m_type_to_create = old_m_type_to_create;
4998
4999 // If replacing a saved design
5000 if (const auto replaced_design = EditingSavedDesign()) {
5001 auto& manager = GetSavedDesignsManager();
5002
5003 manager.MoveBefore(new_uuid, (*replaced_design)->UUID());
5004 manager.Erase((*replaced_design)->UUID());
5005
5006 // Update the replaced design on the bench
5007 SetDesign(manager.GetDesign(new_uuid));
5008
5009 } else {
5010 // If replacing or renaming a currect design
5011 const auto current_maybe_design = EditingCurrentDesign();
5012 const auto existing_design = CurrentDesignIsRegistered();
5013 if (current_maybe_design || existing_design) {
5014 auto& manager = GetDisplayedDesignsManager();
5015 int empire_id = HumanClientApp::GetApp()->EmpireID();
5016 int replaced_id = (*(current_maybe_design ? current_maybe_design : existing_design))->ID();
5017
5018 if (new_design_id == INVALID_DESIGN_ID) return;
5019
5020 // Remove the old id from the Empire.
5021 const auto maybe_obsolete = manager.IsObsolete(replaced_id);
5022 bool is_obsolete = maybe_obsolete && *maybe_obsolete;
5023 if (!is_obsolete)
5024 HumanClientApp::GetApp()->Orders().IssueOrder(
5025 std::make_shared<ShipDesignOrder>(empire_id, replaced_id, true));
5026
5027 // Replace the old id in the manager.
5028 manager.MoveBefore(new_design_id, replaced_id);
5029 manager.Remove(replaced_id);
5030
5031 // Update the replaced design on the bench
5032 SetDesign(new_design_id);
5033
5034 DebugLogger() << "Replaced design #" << replaced_id << " with #" << new_design_id ;
5035 }
5036 }
5037
5038 DesignChangedSignal();
5039 }
5040
ToggleDescriptionEditor()5041 void DesignWnd::MainPanel::ToggleDescriptionEditor() {
5042 if (m_design_description_toggle->Checked()) { m_design_description_edit->Show() ; }
5043 else { m_design_description_edit->Hide(); }
5044 }
5045
5046 //////////////////////////////////////////////////
5047 // DesignWnd //
5048 //////////////////////////////////////////////////
DesignWnd(GG::X w,GG::Y h)5049 DesignWnd::DesignWnd(GG::X w, GG::Y h) :
5050 GG::Wnd(GG::X0, GG::Y0, w, h, GG::ONTOP | GG::INTERACTIVE)
5051 {}
5052
CompleteConstruction()5053 void DesignWnd::CompleteConstruction() {
5054 GG::Wnd::CompleteConstruction();
5055
5056 Sound::TempUISoundDisabler sound_disabler;
5057 SetChildClippingMode(ClipToClient);
5058
5059 m_detail_panel = GG::Wnd::Create<EncyclopediaDetailPanel>(GG::ONTOP | GG::INTERACTIVE | GG::DRAGABLE | GG::RESIZABLE | PINABLE, DES_PEDIA_WND_NAME);
5060 m_main_panel = GG::Wnd::Create<MainPanel>(DES_MAIN_WND_NAME);
5061 m_part_palette = GG::Wnd::Create<PartPalette>(DES_PART_PALETTE_WND_NAME);
5062 m_base_selector = GG::Wnd::Create<BaseSelector>(DES_BASE_SELECTOR_WND_NAME);
5063 InitializeWindows();
5064 HumanClientApp::GetApp()->RepositionWindowsSignal.connect(
5065 boost::bind(&DesignWnd::InitializeWindows, this));
5066
5067 AttachChild(m_detail_panel);
5068
5069 #if BOOST_VERSION >= 106000
5070 using boost::placeholders::_1;
5071 using boost::placeholders::_2;
5072 #endif
5073
5074 AttachChild(m_main_panel);
5075 m_main_panel->ShipPartClickedSignal.connect(
5076 boost::bind(static_cast<void (EncyclopediaDetailPanel::*)(const ShipPart*)>(
5077 &EncyclopediaDetailPanel::SetItem), m_detail_panel, _1));
5078 m_main_panel->ShipHullClickedSignal.connect(
5079 boost::bind(static_cast<void (EncyclopediaDetailPanel::*)(const ShipHull*)>(
5080 &EncyclopediaDetailPanel::SetItem), m_detail_panel, _1));
5081 m_main_panel->DesignChangedSignal.connect(boost::bind(&DesignWnd::DesignChanged, this));
5082 m_main_panel->DesignNameChangedSignal.connect(boost::bind(&DesignWnd::DesignNameChanged, this));
5083 m_main_panel->CompleteDesignClickedSignal.connect(
5084 boost::bind(static_cast<void (EncyclopediaDetailPanel::*)(int)>(
5085 &EncyclopediaDetailPanel::SetDesign), m_detail_panel, _1));
5086 //m_main_panel->Sanitize();
5087
5088 AttachChild(m_part_palette);
5089 m_part_palette->ShipPartClickedSignal.connect(
5090 boost::bind(static_cast<void (EncyclopediaDetailPanel::*)(const ShipPart*)>(
5091 &EncyclopediaDetailPanel::SetItem), m_detail_panel, _1));
5092 m_part_palette->ShipPartDoubleClickedSignal.connect(
5093 boost::bind(&DesignWnd::MainPanel::AddPart, m_main_panel, _1));
5094 m_part_palette->ClearPartSignal.connect(
5095 boost::bind(&DesignWnd::MainPanel::ClearPart, m_main_panel, _1));
5096
5097 AttachChild(m_base_selector);
5098
5099 m_base_selector->DesignSelectedSignal.connect(
5100 boost::bind(static_cast<void (MainPanel::*)(int)>(&MainPanel::SetDesign), m_main_panel, _1));
5101 m_base_selector->DesignUpdatedSignal.connect(
5102 boost::bind(static_cast<void (MainPanel::*)()>(&MainPanel::DesignChanged), m_main_panel));
5103 m_base_selector->DesignComponentsSelectedSignal.connect(
5104 boost::bind(static_cast<void (MainPanel::*)(const std::string& hull, const std::vector<std::string>& parts)>(
5105 &MainPanel::SetDesignComponents), m_main_panel, _1, _2));
5106 m_base_selector->SavedDesignSelectedSignal.connect(
5107 boost::bind(static_cast<void (MainPanel::*)(const boost::uuids::uuid&)>(
5108 &MainPanel::SetDesign), m_main_panel, _1));
5109
5110 m_base_selector->DesignClickedSignal.connect(
5111 boost::bind(static_cast<void (EncyclopediaDetailPanel::*)(const ShipDesign*)>(
5112 &EncyclopediaDetailPanel::SetItem), m_detail_panel, _1));
5113 m_base_selector->HullClickedSignal.connect(
5114 boost::bind(static_cast<void (EncyclopediaDetailPanel::*)(const ShipHull*)>(
5115 &EncyclopediaDetailPanel::SetItem), m_detail_panel, _1));
5116 m_base_selector->TabChangedSignal.connect(boost::bind(
5117 &MainPanel::HandleBaseTypeChange, m_main_panel, _1));
5118
5119 // Connect signals to re-populate when part obsolescence changes
5120 m_part_palette->PartObsolescenceChangedSignal.connect(
5121 boost::bind(&BaseSelector::Reset, m_base_selector));
5122 }
5123
SizeMove(const GG::Pt & ul,const GG::Pt & lr)5124 void DesignWnd::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
5125 const GG::Pt old_size = Size();
5126 GG::Wnd::SizeMove(ul, lr);
5127 if (old_size != Size()) {
5128 m_detail_panel->ValidatePosition();
5129 m_base_selector->ValidatePosition();
5130 m_part_palette->ValidatePosition();
5131 m_main_panel->ValidatePosition();
5132 }
5133 }
5134
Reset()5135 void DesignWnd::Reset() {
5136 m_part_palette->Populate();
5137 m_base_selector->Reset();
5138 m_detail_panel->Refresh();
5139 m_main_panel->Sanitize();
5140 }
5141
Sanitize()5142 void DesignWnd::Sanitize()
5143 { m_main_panel->Sanitize(); }
5144
Render()5145 void DesignWnd::Render()
5146 { GG::FlatRectangle(UpperLeft(), LowerRight(), ClientUI::WndColor(), GG::CLR_ZERO, 0); }
5147
InitializeWindows()5148 void DesignWnd::InitializeWindows() {
5149 const GG::X selector_width = GG::X(300);
5150 const GG::X main_width = ClientWidth() - selector_width;
5151
5152 const GG::Pt pedia_ul(selector_width, GG::Y0);
5153 const GG::Pt pedia_wh(5*main_width/11, 2*ClientHeight()/5);
5154
5155 const GG::Pt main_ul(selector_width, pedia_ul.y + pedia_wh.y);
5156 const GG::Pt main_wh(main_width, ClientHeight() - main_ul.y);
5157
5158 const GG::Pt palette_ul(selector_width + pedia_wh.x, pedia_ul.y);
5159 const GG::Pt palette_wh(main_width - pedia_wh.x, pedia_wh.y);
5160
5161 const GG::Pt selector_ul(GG::X0, GG::Y0);
5162 const GG::Pt selector_wh(selector_width, ClientHeight());
5163
5164 m_detail_panel-> InitSizeMove(pedia_ul, pedia_ul + pedia_wh);
5165 m_main_panel-> InitSizeMove(main_ul, main_ul + main_wh);
5166 m_part_palette-> InitSizeMove(palette_ul, palette_ul + palette_wh);
5167 m_base_selector->InitSizeMove(selector_ul, selector_ul + selector_wh);
5168 }
5169
ShowShipPartInEncyclopedia(const std::string & part)5170 void DesignWnd::ShowShipPartInEncyclopedia(const std::string& part)
5171 { m_detail_panel->SetShipPart(part); }
5172
ShowShipHullInEncyclopedia(const std::string & ship_hull)5173 void DesignWnd::ShowShipHullInEncyclopedia(const std::string& ship_hull)
5174 { m_detail_panel->SetShipHull(ship_hull); }
5175
ShowShipDesignInEncyclopedia(int design_id)5176 void DesignWnd::ShowShipDesignInEncyclopedia(int design_id)
5177 { m_detail_panel->SetDesign(design_id); }
5178
DesignChanged()5179 void DesignWnd::DesignChanged() {
5180 m_detail_panel->SetIncompleteDesign(m_main_panel->GetIncompleteDesign());
5181 m_base_selector->Reset();
5182 }
5183
DesignNameChanged()5184 void DesignWnd::DesignNameChanged() {
5185 m_detail_panel->SetIncompleteDesign(m_main_panel->GetIncompleteDesign());
5186 m_base_selector->Reset();
5187 }
5188
EnableOrderIssuing(bool enable)5189 void DesignWnd::EnableOrderIssuing(bool enable/* = true*/)
5190 { m_base_selector->EnableOrderIssuing(enable); }
5191