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