1 #include "ProductionWnd.h"
2 
3 #include "../util/Logger.h"
4 #include "../util/OptionsDB.h"
5 #include "BuildDesignatorWnd.h"
6 #include "ClientUI.h"
7 #include "CUIControls.h"
8 #include "QueueListBox.h"
9 #include "SidePanel.h"
10 #include "IconTextBrowseWnd.h"
11 #include "../Empire/Empire.h"
12 #include "../client/human/HumanClientApp.h"
13 #include "../util/i18n.h"
14 #include "../util/Order.h"
15 #include "../util/ScopedTimer.h"
16 #include "../universe/BuildingType.h"
17 #include "../universe/ShipDesign.h"
18 #include "../universe/Enums.h"
19 
20 #include <GG/Layout.h>
21 #include <GG/StaticGraphic.h>
22 
23 #include <boost/cast.hpp>
24 #include <boost/range/numeric.hpp>
25 #include <boost/range/adaptor/map.hpp>
26 
27 #include <cmath>
28 #include <iterator>
29 
30 
31 namespace {
32     const float OUTER_LINE_THICKNESS = 2.0f;
33 
AddOptions(OptionsDB & db)34     void AddOptions(OptionsDB& db) {
35         // queue width used also on research screen. prevent double-adding...
36         if (!db.OptionExists("ui.queue.width"))
37             db.Add("ui.queue.width", UserStringNop("OPTIONS_DB_UI_QUEUE_WIDTH"), 350, RangedValidator<int>(200, 500));
38         db.Add("ui.queue.production_location.shown", UserStringNop("OPTIONS_DB_UI_PROD_QUEUE_LOCATION"), true);
39     }
40     bool temp_bool = RegisterOptions(&AddOptions);
41 
42     ////////////////
43     // QuantLabel //
44     ////////////////
45     class QuantLabel : public GG::Control {
46     public:
QuantLabel(int quantity,int designID,GG::X nwidth,GG::Y h,bool inProgress,bool amBlockType)47         QuantLabel(int quantity, int designID, GG::X nwidth,
48                    GG::Y h, bool inProgress, bool amBlockType) :
49             Control(GG::X0, GG::Y0, nwidth, h, GG::NO_WND_FLAGS)
50         {
51             GG::Clr txtClr = inProgress ? GG::LightenClr(ClientUI::ResearchableTechTextAndBorderColor()) : ClientUI::ResearchableTechTextAndBorderColor();
52             std::string nameText;
53             if (amBlockType)
54                 nameText = boost::io::str(FlexibleFormat(UserString("PRODUCTION_QUEUE_MULTIPLES")) % quantity);
55             else
56                 nameText = boost::io::str(FlexibleFormat(UserString("PRODUCTION_QUEUE_REPETITIONS")) % quantity);
57             //nameText += GetShipDesign(designID)->Name();
58             m_text = GG::Wnd::Create<CUILabel>(nameText, GG::FORMAT_TOP | GG::FORMAT_LEFT | GG::FORMAT_NOWRAP);
59             m_text->SetTextColor(txtClr);
60             m_text->OffsetMove(GG::Pt(GG::X0, GG::Y(-3)));
61         }
62 
CompleteConstruction()63         void CompleteConstruction() override {
64             GG::Control::CompleteConstruction();
65             AttachChild(m_text);
66             Resize(GG::Pt(Width(), m_text->Height()));
67         }
68 
Render()69         void Render() override
70         {}
71 
72         std::shared_ptr<CUILabel> m_text;
73     };
74 
75     //////////////
76     // QuantRow //
77     //////////////
78     class QuantRow : public GG::ListBox::Row {
79     public:
QuantRow(int quantity,int designID,GG::X nwidth,GG::Y h,bool inProgress,bool amBlockType)80         QuantRow(int quantity, int designID, GG::X nwidth, GG::Y h,
81                  bool inProgress, bool amBlockType) :
82             GG::ListBox::Row(),
83             m_quant(quantity),
84             m_label(GG::Wnd::Create<QuantLabel>(m_quant, designID, nwidth, h, inProgress, amBlockType))
85         {}
86 
CompleteConstruction()87         void CompleteConstruction() override {
88             GG::ListBox::Row::CompleteConstruction();
89 
90             push_back(m_label);
91             Resize(GG::Pt(m_label->Width(), m_label->Height()-GG::Y0));//might subtract more; assessing aesthetics
92         }
93 
Quant() const94         int Quant() const { return m_quant; }
95 
96     private:
97         int m_quant;
98         std::shared_ptr<QuantLabel> m_label;
99     };
100 
101     //////////////////////
102     // QuantitySelector //
103     //////////////////////
104     class QuantitySelector : public CUIDropDownList {
105     public:
106         mutable boost::signals2::signal<void (int, int)> QuantChangedSignal;
107 
108         /** \name Structors */
QuantitySelector(const ProductionQueue::Element & build,GG::X xoffset,GG::Y yoffset,GG::Y h_,bool inProgress,GG::X nwidth,bool amBlockType_)109         QuantitySelector(const ProductionQueue::Element &build, GG::X xoffset, GG::Y yoffset,
110                          GG::Y h_, bool inProgress, GG::X nwidth, bool amBlockType_) :
111             CUIDropDownList(12),
112             quantity(build.remaining),
113             prevQuant(build.remaining),
114             blocksize(build.blocksize),
115             prevBlocksize(build.blocksize),
116             amBlockType(amBlockType_),
117             h(h_)
118         {
119             MoveTo(GG::Pt(xoffset, yoffset));
120             Resize(GG::Pt(nwidth, h - GG::Y(2)));
121 
122             DisableDropArrow();
123             SetStyle(GG::LIST_LEFT | GG::LIST_NOSORT);
124             SetColor(inProgress ? GG::LightenClr(ClientUI::ResearchableTechTextAndBorderColor())
125                                 : ClientUI::ResearchableTechTextAndBorderColor());
126             SetInteriorColor(inProgress ? GG::LightenClr(ClientUI::ResearchableTechFillColor())
127                                         : ClientUI::ResearchableTechFillColor());
128             SetNumCols(1);
129 
130             int quantInts[] = {1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 99};
131             std::set<int> myQuantSet(quantInts, quantInts + 11);
132 
133             if (amBlockType)
134                 myQuantSet.insert(blocksize); //as currently implemented this one not actually necessary since blocksize has no other way to change
135             else
136                 myQuantSet.insert(quantity);
137 
138             for (int droplist_quantity : myQuantSet) {
139                 auto row =  GG::Wnd::Create<QuantRow>(droplist_quantity, build.item.design_id, nwidth, h, inProgress, amBlockType);
140                 GG::DropDownList::iterator latest_it = Insert(row);
141 
142                 if (amBlockType) {
143                     if (build.blocksize == droplist_quantity)
144                         Select(latest_it);
145                 } else {
146                     if (build.remaining == droplist_quantity)
147                         Select(latest_it);
148                 }
149             }
150 
151 #if BOOST_VERSION >= 106000
152     using boost::placeholders::_1;
153 #endif
154 
155             this->SelChangedSignal.connect(
156                 boost::bind(&QuantitySelector::SelectionChanged, this, _1));
157         }
158 
SelectionChanged(GG::DropDownList::iterator it)159         void SelectionChanged(GG::DropDownList::iterator it) {
160             DebugLogger() << "QuantSelector:  selection made ";
161             if (it != end()) {
162                 int quant = boost::polymorphic_downcast<const QuantRow*>(it->get())->Quant();
163                 if (amBlockType) {
164                     DebugLogger() << "Blocksize Selector:  selection changed to " << quant;
165                     blocksize = quant;
166                 } else {
167                     DebugLogger() << "Quantity Selector:  selection changed to " << quant;
168                     quantity = quant;
169                 }
170             }
171         }
172 
173     private:
174         const ProductionQueue::Element elem;
175         int     quantity;
176         int     prevQuant;
177         int     blocksize;
178         int     prevBlocksize;
179         bool    amBlockType;
180         GG::Y   h;
181 
LButtonDown(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)182         void LButtonDown(const GG::Pt&  pt, GG::Flags<GG::ModKey> mod_keys) override {
183             if (this->Disabled())
184                 return;
185 
186             DropDownList::LButtonDown(pt, mod_keys);
187             if ( (quantity != prevQuant) || (blocksize != prevBlocksize) )
188                 QuantChangedSignal(quantity, blocksize);
189         }
190     };
191 
192     //////////////////////////////////////////////////
193     // QueueProductionItemPanel
194     //////////////////////////////////////////////////
195     class QueueProductionItemPanel : public GG::Control {
196     public:
197         QueueProductionItemPanel(GG::X x, GG::Y y, GG::X w,
198                                  const ProductionQueue::Element& build, double turn_cost, double total_cost,
199                                  int turns, int number, double completed_progress);
200 
201         void CompleteConstruction() override;
202         void PreRender() override;
203         void Render() override;
204         void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override;
205         void ItemQuantityChanged(int quant, int blocksize);
206         void ItemBlocksizeChanged(int quant, int blocksize);
207 
208         static GG::Y DefaultHeight();
209 
210         mutable boost::signals2::signal<void(int,int)> PanelUpdateQuantSignal;
211 
212     private:
213         void Draw(GG::Clr clr, bool fill);
214         void DoLayout();
215 
216         const ProductionQueue::Element          elem;
217         std::shared_ptr<GG::Label>              m_name_text;
218         std::shared_ptr<GG::Label>              m_location_text;
219         std::shared_ptr<GG::Label>              m_PPs_and_turns_text;
220         std::shared_ptr<GG::Label>              m_turns_remaining_until_next_complete_text;
221         std::shared_ptr<GG::StaticGraphic>      m_icon;
222         std::shared_ptr<MultiTurnProgressBar>   m_progress_bar;
223         std::shared_ptr<QuantitySelector>       m_quantity_selector;
224         std::shared_ptr<QuantitySelector>       m_block_size_selector;
225         bool                                    m_in_progress = false;
226         int                                     m_total_turns = 0;
227         double                                  m_turn_spending = 0.0;
228         double                                  m_total_cost = 0.0;
229         double                                  m_completed_progress = 0.0;
230         bool                                    m_order_issuing_enabled = true;
231     };
232 
233     /////////////////////////////
234     // ProductionItemBrowseWnd //
235     /////////////////////////////
ProductionItemBrowseWnd(const ProductionQueue::Element & elem)236     std::shared_ptr<GG::BrowseInfoWnd> ProductionItemBrowseWnd(const ProductionQueue::Element& elem) {
237         //const Empire* empire = GetEmpire(elem.empire_id);
238 
239         std::string main_text;
240         std::string item_name;
241 
242         //int min_turns = 1;
243         float total_cost = 0.0f;
244         float max_allocation = 0.0f;
245         std::shared_ptr<GG::Texture> icon;
246         //bool available = false;
247         bool location_ok = false;
248 
249         if (elem.item.build_type == BT_BUILDING) {
250             const BuildingType* building_type = GetBuildingType(elem.item.name);
251             if (!building_type)
252                 return nullptr;
253             main_text += UserString("OBJ_BUILDING") + "\n";
254 
255             item_name = UserString(elem.item.name);
256             //available = empire->BuildingTypeAvailable(elem.item.name);
257             location_ok = building_type->ProductionLocation(elem.empire_id, elem.location);
258             //min_turns = building_type->ProductionTime(elem.empire_id, elem.location);
259             total_cost = building_type->ProductionCost(elem.empire_id, elem.location);
260             max_allocation = building_type->PerTurnCost(elem.empire_id, elem.location);
261             icon = ClientUI::BuildingIcon(elem.item.name);
262 
263         } else if (elem.item.build_type == BT_SHIP) {
264             const ShipDesign* design = GetShipDesign(elem.item.design_id);
265             if (!design)
266                 return nullptr;
267             main_text += UserString("OBJ_SHIP") + "\n";
268 
269             item_name = design->Name(true);
270             //available = empire->ShipDesignAvailable(elem.item.design_id);
271             location_ok = design->ProductionLocation(elem.empire_id, elem.location);
272             //min_turns = design->ProductionTime(elem.empire_id, elem.location);
273             total_cost = design->ProductionCost(elem.empire_id, elem.location) * elem.blocksize;
274             max_allocation = design->PerTurnCost(elem.empire_id, elem.location) * elem.blocksize;
275             icon = ClientUI::ShipDesignIcon(elem.item.design_id);
276         } else if (elem.item.build_type == BT_STOCKPILE) {
277             main_text += UserString("BUILD_ITEM_TYPE_PROJECT") + "\n";
278 
279             item_name = UserString(elem.item.name);
280             auto loc = Objects().get(elem.location);
281             location_ok = loc && loc->OwnedBy(elem.empire_id) && (std::dynamic_pointer_cast<const ResourceCenter>(loc));
282 
283             total_cost = 1.0;
284             max_allocation = total_cost * elem.blocksize;
285             icon = ClientUI::MeterIcon(METER_STOCKPILE);
286         }
287 
288         if (auto rally_object = Objects().get(elem.rally_point_id)) {
289             main_text += boost::io::str(FlexibleFormat(UserString("PRODUCTION_QUEUE_RALLIED_TO"))
290                                         % rally_object->Name()) + "\n";
291         }
292 
293         if (auto location = Objects().get(elem.location))
294             main_text += boost::io::str(FlexibleFormat(UserString("PRODUCTION_QUEUE_ENQUEUED_ITEM_LOCATION"))
295                                         % location->Name()) + "\n";
296 
297         if (location_ok)
298             main_text += UserString("PRODUCTION_LOCATION_OK") + "\n";
299         else
300             main_text += UserString("PRODUCTION_LOCATION_INVALID") + "\n";
301 
302         float progress = elem.progress;
303         float allocation = elem.allocated_pp;
304 
305         // %1% / %2%  +  %3% / %4% PP/turn
306         main_text += boost::io::str(FlexibleFormat(UserString("PRODUCTION_WND_PROGRESS"))
307                         % DoubleToString(progress*100.0f, 3, false)
308                         % DoubleToString(total_cost, 3, false)
309                         % DoubleToString(allocation, 3, false)
310                         % DoubleToString(max_allocation, 3, false)) + "\n";
311 
312         if (elem.allowed_imperial_stockpile_use)
313             main_text += UserString("PRODUCTION_QUEUE_ITEM_STOCKPILE_ENABLED") + "\n";
314 
315         int ETA = elem.turns_left_to_completion;
316         if (ETA != -1)
317             main_text += boost::io::str(FlexibleFormat(UserString("TECH_WND_ETA"))
318                                         % ETA);
319 
320         std::string title_text;
321         if (elem.blocksize > 1)
322             title_text = std::to_string(elem.blocksize) + "x ";
323         title_text += item_name;
324 
325         return GG::Wnd::Create<IconTextBrowseWnd>(icon, title_text, main_text);
326     }
327 
328     //////////////////////////////////////////////////
329     // QueueRow
330     //////////////////////////////////////////////////
331     const int MARGIN = 2;
332     const GG::X X_MARGIN = GG::X(MARGIN);
333     const GG::Y Y_MARGIN = GG::Y(MARGIN);
334 
335     struct QueueRow : GG::ListBox::Row {
QueueRow__anonabd8f7920111::QueueRow336         QueueRow(GG::X w, const ProductionQueue::Element& elem_, int queue_index_) :
337             GG::ListBox::Row(w, QueueProductionItemPanel::DefaultHeight()),
338             queue_index(queue_index_),
339             elem(elem_)
340         {
341             SetDragDropDataType(BuildDesignatorWnd::PRODUCTION_ITEM_DROP_TYPE);
342             const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
343             float total_cost(1.0f);
344             int minimum_turns(1);
345             if (empire)
346                 std::tie(total_cost, minimum_turns) = empire->ProductionCostAndTime(elem);
347             total_cost *= elem.blocksize;
348 
349             float pp_accumulated = empire ? empire->ProductionStatus(queue_index) : 0.0f; // returns as PP
350             if (pp_accumulated == -1.0f)
351                 pp_accumulated = 0.0f;
352 
353             panel = GG::Wnd::Create<QueueProductionItemPanel>(
354                 GG::X0, GG::Y0, ClientWidth() - MARGIN - MARGIN,
355                 elem, elem.allocated_pp, total_cost, minimum_turns, elem.remaining,
356                 pp_accumulated);
357             SetDragDropDataType(BuildDesignatorWnd::PRODUCTION_ITEM_DROP_TYPE);
358 
359             SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
360             SetBrowseInfoWnd(ProductionItemBrowseWnd(elem));
361 
362 #if BOOST_VERSION >= 106000
363             using boost::placeholders::_1;
364             using boost::placeholders::_2;
365 #endif
366 
367             panel->PanelUpdateQuantSignal.connect(
368                 boost::bind(&QueueRow::RowQuantChanged, this, _1, _2));
369 
370             RequirePreRender();
371         }
372 
CompleteConstruction__anonabd8f7920111::QueueRow373         void CompleteConstruction() override {
374             GG::ListBox::Row::CompleteConstruction();
375 
376             push_back(panel);
377         }
378 
PreRender__anonabd8f7920111::QueueRow379         void PreRender() override {
380             GG::ListBox::Row::PreRender();
381 
382             GG::GUI::PreRenderWindow(panel);
383             GetLayout()->PreRender();
384         }
385 
Disable__anonabd8f7920111::QueueRow386         void Disable(bool b) override {
387             GG::ListBox::Row::Disable(b);
388 
389             for (auto& ctrl : m_cells) {
390                 if (ctrl)
391                     ctrl->Disable(this->Disabled());
392             }
393         }
394 
RowQuantChanged__anonabd8f7920111::QueueRow395         void RowQuantChanged(int quantity, int blocksize) {
396             if (this->Disabled())
397                 return;
398             RowQuantChangedSignal(queue_index, quantity, blocksize);
399         }
400 
401         std::shared_ptr<QueueProductionItemPanel>           panel;
402         const int                                           queue_index;
403         const ProductionQueue::Element                      elem;
404         mutable boost::signals2::signal<void (int,int,int)> RowQuantChangedSignal;
405     };
406 
407     //////////////////////////////////////////////////
408     // QueueProductionItemPanel implementation
409     //////////////////////////////////////////////////
QueueProductionItemPanel(GG::X x,GG::Y y,GG::X w,const ProductionQueue::Element & build,double turn_spending,double total_cost,int turns,int number,double completed_progress)410     QueueProductionItemPanel::QueueProductionItemPanel(
411         GG::X x, GG::Y y, GG::X w, const ProductionQueue::Element& build,
412         double turn_spending, double total_cost, int turns, int number, double completed_progress) :
413         GG::Control(x, y, w, DefaultHeight(), GG::NO_WND_FLAGS),
414         elem(build),
415         m_in_progress(build.allocated_pp || build.turns_left_to_next_item == 1),
416         m_total_turns(turns),
417         m_turn_spending(turn_spending),
418         m_total_cost(total_cost),
419         m_completed_progress(completed_progress)
420     {}
421 
CompleteConstruction()422     void QueueProductionItemPanel::CompleteConstruction() {
423     GG::Control::CompleteConstruction();
424         SetChildClippingMode(ClipToClient);
425 
426         GG::Clr clr = m_in_progress
427             ? GG::LightenClr(ClientUI::ResearchableTechTextAndBorderColor())
428             : ClientUI::ResearchableTechTextAndBorderColor();
429 
430         // get graphic and player-visible name text for item
431         std::shared_ptr<GG::Texture> graphic;
432         std::string name_text;
433         if (elem.item.build_type == BT_BUILDING) {
434             graphic = ClientUI::BuildingIcon(elem.item.name);
435             name_text = UserString(elem.item.name);
436         } else if (elem.item.build_type == BT_SHIP) {
437             graphic = ClientUI::ShipDesignIcon(elem.item.design_id);
438             const ShipDesign* design = GetShipDesign(elem.item.design_id);
439             if (design)
440                 name_text = design->Name();
441             else
442                 ErrorLogger() << "QueueProductionItemPanel unable to get design with id: " << elem.item.design_id;
443         } else if (elem.item.build_type == BT_STOCKPILE) {
444             graphic = ClientUI::MeterIcon(METER_STOCKPILE);
445             name_text = UserString(elem.item.name);
446         } else {
447             graphic = ClientUI::GetTexture(""); // get "missing texture" texture by supply intentionally bad path
448             name_text = UserString("FW_UNKNOWN_DESIGN_NAME");
449         }
450 
451         // get location indicator text
452         std::string location_text;
453         bool system_selected = false;
454         bool rally_dest_selected = (elem.rally_point_id != INVALID_OBJECT_ID && elem.rally_point_id == SidePanel::SystemID());
455         if (const auto location = Objects().get(elem.location)) {
456             system_selected = (location->SystemID() != INVALID_OBJECT_ID && location ->SystemID() == SidePanel::SystemID());
457             if (GetOptionsDB().Get<bool>("ui.queue.production_location.shown")) {
458                 if (rally_dest_selected && !system_selected) {
459                     location_text = boost::io::str(FlexibleFormat(UserString(
460                         "PRODUCTION_QUEUE_ITEM_RALLIED_FROM_LOCATION")) % location->Name()) + " ";
461                 } else {
462                     location_text = boost::io::str(FlexibleFormat(UserString(
463                         "PRODUCTION_QUEUE_ITEM_LOCATION")) % location->Name()) + " ";
464                 }
465             }
466         }
467 
468         const int FONT_PTS = ClientUI::Pts();
469 
470         m_icon = nullptr;
471         if (graphic)
472             m_icon = GG::Wnd::Create<GG::StaticGraphic>(graphic, GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
473 
474         if (elem.item.build_type == BT_SHIP || elem.item.build_type == BT_STOCKPILE) {
475             m_quantity_selector = GG::Wnd::Create<QuantitySelector>(
476                 elem, GG::X1, GG::Y(MARGIN), GG::Y(FONT_PTS-2*MARGIN), m_in_progress, GG::X(FONT_PTS*2.5), false);
477             m_block_size_selector = GG::Wnd::Create<QuantitySelector>(
478                 elem, GG::X1, GG::Y(MARGIN), GG::Y(FONT_PTS-2*MARGIN), m_in_progress, GG::X(FONT_PTS*2.5), true);
479             m_quantity_selector->SetOnlyMouseScrollWhenDropped(true);
480             m_block_size_selector->SetOnlyMouseScrollWhenDropped(true);
481         }
482 
483         m_name_text = GG::Wnd::Create<CUILabel>(name_text, GG::FORMAT_TOP | GG::FORMAT_LEFT);
484         m_name_text->SetTextColor(clr);
485         m_name_text->ClipText(true);
486 
487         GG::Clr location_clr = clr;
488         int client_empire_id = HumanClientApp::GetApp()->EmpireID();
489         const Empire* this_client_empire = GetEmpire(client_empire_id);
490         if (this_client_empire && (system_selected || rally_dest_selected)) {
491             auto empire_color = this_client_empire->Color();
492             auto rally_color = GG::DarkenClr(GG::InvertClr(empire_color));
493             auto location_color = system_selected ? empire_color : rally_color;
494             m_location_text = GG::Wnd::Create<GG::TextControl>(GG::X0, GG::Y0, GG::X1, GG::Y1, "<s>" + location_text + "</s>",
495                                                   ClientUI::GetBoldFont(), location_color, GG::FORMAT_TOP | GG::FORMAT_RIGHT);
496         } else {
497             m_location_text = GG::Wnd::Create<CUILabel>(location_text, GG::FORMAT_TOP | GG::FORMAT_RIGHT);
498             m_location_text->SetTextColor(location_clr);
499         }
500 
501         double perc_complete = 1.0;
502         double next_progress = 0.0;
503         if (m_total_cost > 0.0) {
504             perc_complete = m_completed_progress / m_total_cost;
505             next_progress = m_turn_spending / std::max(m_turn_spending, m_total_cost);
506         }
507 
508         GG::Clr outline_color = ClientUI::ResearchableTechFillColor();
509         if (m_in_progress)
510             outline_color = GG::LightenClr(outline_color);
511 
512         m_progress_bar = GG::Wnd::Create<MultiTurnProgressBar>(m_total_turns,
513                                                                perc_complete,
514                                                                next_progress,
515                                                                GG::LightenClr(ClientUI::TechWndProgressBarBackgroundColor()),
516                                                                ClientUI::TechWndProgressBarColor(),
517                                                                outline_color);
518 
519         double max_spending_per_turn = m_total_cost / m_total_turns;
520         std::string turn_spending_text = boost::io::str(FlexibleFormat(UserString("PRODUCTION_TURN_COST_STR"))
521             % DoubleToString(m_turn_spending, 3, false)
522             % DoubleToString(max_spending_per_turn, 3, false));
523         m_PPs_and_turns_text = GG::Wnd::Create<CUILabel>(turn_spending_text, GG::FORMAT_LEFT);
524         m_PPs_and_turns_text->SetTextColor(clr);
525 
526 
527         int turns_left = elem.turns_left_to_next_item;
528         std::string turns_left_text = turns_left < 0
529             ? UserString("PRODUCTION_TURNS_LEFT_NEVER")
530             : str(FlexibleFormat(UserString("PRODUCTION_TURNS_LEFT_STR")) % turns_left);
531         m_turns_remaining_until_next_complete_text = GG::Wnd::Create<CUILabel>(turns_left_text, GG::FORMAT_RIGHT);
532         m_turns_remaining_until_next_complete_text->SetTextColor(clr);
533         m_turns_remaining_until_next_complete_text->ClipText(true);
534 
535         if (m_icon)
536             AttachChild(m_icon);
537         if (m_quantity_selector)
538             AttachChild(m_quantity_selector);
539         if (m_block_size_selector)
540             AttachChild(m_block_size_selector);
541         AttachChild(m_name_text);
542         AttachChild(m_location_text);
543         AttachChild(m_PPs_and_turns_text);
544         AttachChild(m_turns_remaining_until_next_complete_text);
545         AttachChild(m_progress_bar);
546 
547 #if BOOST_VERSION >= 106000
548         using boost::placeholders::_1;
549         using boost::placeholders::_2;
550 #endif
551 
552         if (m_quantity_selector)
553             m_quantity_selector->QuantChangedSignal.connect(
554                 boost::bind(&QueueProductionItemPanel::ItemQuantityChanged, this, _1, _2));
555         if (m_block_size_selector)
556             m_block_size_selector->QuantChangedSignal.connect(
557                 boost::bind(&QueueProductionItemPanel::ItemBlocksizeChanged, this, _1, _2));
558 
559         RequirePreRender();
560     }
561 
DefaultHeight()562     GG::Y QueueProductionItemPanel::DefaultHeight() {
563         const int FONT_PTS = ClientUI::Pts();
564         const GG::Y HEIGHT = Y_MARGIN + FONT_PTS + Y_MARGIN + FONT_PTS + Y_MARGIN + FONT_PTS + Y_MARGIN + 6;
565         return HEIGHT;
566     }
567 
PreRender()568     void QueueProductionItemPanel::PreRender() {
569         GG::GUI::PreRenderWindow(m_quantity_selector.get());
570         GG::GUI::PreRenderWindow(m_block_size_selector.get());
571 
572         GG::Wnd::PreRender();
573         DoLayout();
574     }
575 
DoLayout()576     void QueueProductionItemPanel::DoLayout() {
577         const int FONT_PTS = ClientUI::Pts();
578         const GG::Y METER_HEIGHT(FONT_PTS);
579         const GG::Y HEIGHT = GG::Y(MARGIN) + FONT_PTS + MARGIN + METER_HEIGHT + MARGIN + FONT_PTS + MARGIN + 6;
580         const int GRAPHIC_SIZE = Value(HEIGHT - 9);    // 9 pixels accounts for border thickness so the sharp-cornered icon doesn't with the rounded panel corner
581         const GG::X METER_WIDTH = Width() - GRAPHIC_SIZE - 3*MARGIN - 3;
582 
583         // create and arrange widgets to display info
584         GG::Y top(MARGIN);
585         GG::X left(MARGIN);
586 
587         if (m_icon) {
588             m_icon->MoveTo(GG::Pt(left, top));
589             m_icon->Resize(GG::Pt(GG::X(GRAPHIC_SIZE), GG::Y(GRAPHIC_SIZE)));
590         }
591 
592         left += GRAPHIC_SIZE + MARGIN;
593         if (m_quantity_selector) {
594             m_quantity_selector->MoveTo(GG::Pt(left, GG::Y(MARGIN)));
595             left += m_quantity_selector->Width();
596         }
597         if (m_block_size_selector) {
598             m_block_size_selector->MoveTo(GG::Pt(left, GG::Y(MARGIN)));
599             left += m_block_size_selector->Width();
600         }
601 
602         const GG::X NAME_WIDTH = Width() - left - MARGIN - 3;
603         m_name_text->MoveTo(GG::Pt(left, top));
604         m_name_text->Resize(GG::Pt(NAME_WIDTH, GG::Y(FONT_PTS + 2*MARGIN)));
605         m_name_text->SetChildClippingMode(ClipToClient);
606 
607         m_location_text->MoveTo(GG::Pt(left, top));
608         m_location_text->Resize(GG::Pt(NAME_WIDTH, GG::Y(FONT_PTS + 2*MARGIN)));
609 
610         top += m_name_text->Height();
611         left = GG::X(GRAPHIC_SIZE + MARGIN*2);
612         m_progress_bar->MoveTo(GG::Pt(left, top));
613         m_progress_bar->Resize(GG::Pt(METER_WIDTH, METER_HEIGHT));
614 
615         top += m_progress_bar->Height() + MARGIN;
616 
617         GG::X TURNS_AND_COST_WIDTH = METER_WIDTH / 2;
618         m_PPs_and_turns_text->MoveTo(GG::Pt(left, top));
619         m_PPs_and_turns_text->Resize(GG::Pt(TURNS_AND_COST_WIDTH, GG::Y(FONT_PTS + MARGIN)));
620 
621         left += TURNS_AND_COST_WIDTH;
622 
623         m_turns_remaining_until_next_complete_text->MoveTo(GG::Pt(left, top));
624         m_turns_remaining_until_next_complete_text->Resize(GG::Pt(TURNS_AND_COST_WIDTH, GG::Y(FONT_PTS + MARGIN)));
625     }
626 
ItemQuantityChanged(int quant,int blocksize)627     void QueueProductionItemPanel::ItemQuantityChanged(int quant, int blocksize)
628     { if (m_order_issuing_enabled) PanelUpdateQuantSignal(quant, elem.blocksize); }
629 
ItemBlocksizeChanged(int quant,int blocksize)630     void QueueProductionItemPanel::ItemBlocksizeChanged(int quant, int blocksize)
631     { if (m_order_issuing_enabled) PanelUpdateQuantSignal(elem.remaining, blocksize); }
632 
Render()633     void QueueProductionItemPanel::Render() {
634         GG::Clr fill = m_in_progress
635             ? GG::LightenClr(ClientUI::ResearchableTechFillColor())
636             : ClientUI::ResearchableTechFillColor();
637         GG::Clr text_and_border = m_in_progress
638             ? GG::LightenClr(ClientUI::ResearchableTechTextAndBorderColor())
639             : ClientUI::ResearchableTechTextAndBorderColor();
640 
641         glDisable(GL_TEXTURE_2D);
642         Draw(fill, true);
643         glEnable(GL_LINE_SMOOTH);
644         glLineWidth(static_cast<GLfloat>(OUTER_LINE_THICKNESS));
645         Draw(GG::Clr(text_and_border.r, text_and_border.g, text_and_border.b, 127), false);
646         glLineWidth(1.0);
647         glDisable(GL_LINE_SMOOTH);
648         Draw(GG::Clr(text_and_border.r, text_and_border.g, text_and_border.b, 255), false);
649         glEnable(GL_TEXTURE_2D);
650     }
651 
Draw(GG::Clr clr,bool fill)652     void QueueProductionItemPanel::Draw(GG::Clr clr, bool fill) {
653         const int CORNER_RADIUS = 7;
654         glColor(clr);
655         GG::Pt LINE_WIDTH(GG::X(3), GG::Y0);
656         PartlyRoundedRect(UpperLeft(), LowerRight() - LINE_WIDTH, CORNER_RADIUS, true, false, true, false, fill);
657     }
658 
SizeMove(const GG::Pt & ul,const GG::Pt & lr)659     void QueueProductionItemPanel::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
660         const GG::Pt old_size = Size();
661         GG::Control::SizeMove(ul, lr);
662         if (Size() != old_size)
663             RequirePreRender();
664     }
665 
666     class ProdQueueListBox : public QueueListBox {
667     public:
ProdQueueListBox()668         ProdQueueListBox() :
669             QueueListBox(BuildDesignatorWnd::PRODUCTION_ITEM_DROP_TYPE, UserString("PRODUCTION_QUEUE_PROMPT"))
670         {}
671 
CompleteConstruction()672         void CompleteConstruction() override
673         { QueueListBox::CompleteConstruction(); }
674 
EnableOrderIssuing(bool enable=true)675         void EnableOrderIssuing(bool enable = true) override {
676             QueueListBox::EnableOrderIssuing(enable);
677 
678             for (auto it = this->begin(); it != this->end(); ++it) {
679                 if (auto queue_row = std::dynamic_pointer_cast<QueueRow>(*it))
680                     queue_row->Disable(!enable);
681             }
682         }
683 
684         boost::signals2::signal<void (GG::ListBox::iterator, int)>  QueueItemRalliedToSignal;
685         boost::signals2::signal<void ()>                            ShowPediaSignal;
686         boost::signals2::signal<void (GG::ListBox::iterator, bool)> QueueItemPausedSignal;
687         boost::signals2::signal<void (GG::ListBox::iterator)>       QueueItemDupedSignal;
688         boost::signals2::signal<void (GG::ListBox::iterator)>       QueueItemSplitSignal;
689         boost::signals2::signal<void (GG::ListBox::iterator, bool)> QueueItemUseImperialPPSignal;
690 
691     protected:
ItemRightClickedImpl(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)692         void ItemRightClickedImpl(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) override {
693             // mostly duplicated equivalent in QueueListBox, but with extra commands...
694             auto rally_to_action = [&it, this]() { this->QueueItemRalliedToSignal(it, SidePanel::SystemID()); };
695 
696             auto pedia_action = [&it, this, pt, modkeys]() {
697                 ShowPediaSignal();
698                 this->LeftClickedRowSignal(it, pt, modkeys);
699             };
700             auto resume_action = [&it, this]() { this->QueueItemPausedSignal(it, false); };
701             auto pause_action = [&it, this]() { this->QueueItemPausedSignal(it, true); };
702             auto disallow_stockpile_action = [&it, this]() { this->QueueItemUseImperialPPSignal(it, false); };
703             auto allow_stockpile_action = [&it, this]() { this->QueueItemUseImperialPPSignal(it, true); };
704 
705             auto dupe_action = [&it, this]() { this->QueueItemDupedSignal(it); };
706             auto split_action = [&it, this]() { this->QueueItemSplitSignal(it); };
707 
708             auto popup = GG::Wnd::Create<CUIPopupMenu>(pt.x, pt.y);
709 
710             bool disabled = !OrderIssuingEnabled();
711 
712             popup->AddMenuItem(GG::MenuItem(UserString("MOVE_UP_QUEUE_ITEM"),   disabled, false, MoveToTopAction(it)));
713             popup->AddMenuItem(GG::MenuItem(UserString("MOVE_DOWN_QUEUE_ITEM"), disabled, false, MoveToBottomAction(it)));
714             popup->AddMenuItem(GG::MenuItem(UserString("DELETE_QUEUE_ITEM"),    disabled, false, DeleteAction(it)));
715 
716             // inspect clicked item: was it a ship?
717             auto& row = *it;
718             QueueRow* queue_row = row ? dynamic_cast<QueueRow*>(row.get()) : nullptr;
719 
720             int remaining = 0;
721             bool location_passes = true;
722             if (queue_row) {
723                 ProductionQueue::Element elem = queue_row->elem;
724                 remaining = elem.remaining;
725                 location_passes = elem.item.EnqueueConditionPassedAt(elem.location);
726             }
727 
728             // Check if build type is ok. If not bail out. Note that DeleteAction does make sense in this case.
729             BuildType build_type = queue_row ? queue_row->elem.item.build_type : INVALID_BUILD_TYPE;
730             if (build_type != BT_SHIP && build_type != BT_BUILDING && build_type != BT_STOCKPILE) {
731                 ErrorLogger() << "Invalid build type (" << build_type << ") for row item";
732                 return;
733             }
734 
735             popup->AddMenuItem(GG::MenuItem(UserString("DUPLICATE"), disabled || !location_passes, false, dupe_action));
736             if (remaining > 1) {
737                 popup->AddMenuItem(GG::MenuItem(UserString("SPLIT_INCOMPLETE"), disabled, false, split_action));
738             }
739 
740             if (build_type == BT_SHIP) {
741                 // for ships, add a set rally point command
742                 if (auto system = Objects().get<System>(SidePanel::SystemID())) {
743                     std::string rally_prompt = boost::io::str(FlexibleFormat(UserString("RALLY_QUEUE_ITEM")) % system->PublicName(HumanClientApp::GetApp()->EmpireID()));
744                     popup->AddMenuItem(GG::MenuItem(rally_prompt, disabled, false, rally_to_action));
745                 }
746             }
747 
748             // pause / resume commands
749             if (queue_row && queue_row->elem.paused) {
750                 popup->AddMenuItem(GG::MenuItem(UserString("RESUME"), disabled, false, resume_action));
751             } else {
752                 popup->AddMenuItem(GG::MenuItem(UserString("PAUSE"), disabled, false, pause_action));
753             }
754 
755             // stockpile use allow/disallow commands
756             switch (build_type) {
757             case BT_BUILDING:
758             case BT_SHIP:
759                 if (queue_row && queue_row->elem.allowed_imperial_stockpile_use) {
760                     popup->AddMenuItem(GG::MenuItem(UserString("DISALLOW_IMPERIAL_PP_STOCKPILE_USE"), disabled, false, disallow_stockpile_action));
761                 } else {
762                     popup->AddMenuItem(GG::MenuItem(UserString("ALLOW_IMPERIAL_PP_STOCKPILE_USE"), disabled, false, allow_stockpile_action));
763                 }
764             default:
765                 break;
766             }
767 
768             // pedia lookup
769             std::string item_name;
770             switch (build_type) {
771             case BT_BUILDING:
772             case BT_STOCKPILE:
773                 item_name = queue_row->elem.item.name;
774                 break;
775             case BT_SHIP:
776                 item_name = GetShipDesign(queue_row->elem.item.design_id)->Name(false);
777                 break;
778             default:
779                 break;
780             }
781 
782             if (UserStringExists(item_name))
783                 item_name = UserString(item_name);
784             std::string popup_label = boost::io::str(FlexibleFormat(UserString("ENC_LOOKUP")) % item_name);
785             popup->AddMenuItem(GG::MenuItem(popup_label, false, false, pedia_action));
786 
787             popup->Run();
788         }
789     };
790 }
791 
792 //////////////////////////////////////////////////
793 // ProductionQueueWnd                           //
794 //////////////////////////////////////////////////
795 class ProductionQueueWnd : public CUIWnd {
796 public:
797     /** \name Structors */ //@{
ProductionQueueWnd(GG::X x,GG::Y y,GG::X w,GG::Y h)798     ProductionQueueWnd(GG::X x, GG::Y y, GG::X w, GG::Y h) :
799         CUIWnd("", x, y, w, h, GG::INTERACTIVE | GG::RESIZABLE | GG::DRAGABLE | GG::ONTOP | PINABLE,
800                "production.queue")
801     {}
802 
CompleteConstruction()803     void CompleteConstruction() override {
804         m_queue_lb = GG::Wnd::Create<ProdQueueListBox>();
805         m_queue_lb->SetStyle(GG::LIST_NOSORT | GG::LIST_NOSEL | GG::LIST_USERDELETE);
806         m_queue_lb->SetName("ProductionQueue ListBox");
807 
808         SetEmpire(HumanClientApp::GetApp()->EmpireID());
809 
810         AttachChild(m_queue_lb);
811 
812         CUIWnd::CompleteConstruction();
813 
814         DoLayout();
815         SaveDefaultedOptions();
816     }
817     //@}
818 
819     /** \name Mutators */ //@{
SizeMove(const GG::Pt & ul,const GG::Pt & lr)820     void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override {
821         GG::Pt sz = Size();
822         CUIWnd::SizeMove(ul, lr);
823         if (Size() != sz)
824             DoLayout();
825     }
826 
GetQueueListBox()827     ProdQueueListBox*   GetQueueListBox() { return m_queue_lb.get(); }
828 
SetEmpire(int id)829     void                SetEmpire(int id) {
830         if (const Empire* empire = GetEmpire(id))
831             SetName(boost::io::str(FlexibleFormat(UserString("PRODUCTION_QUEUE_EMPIRE")) % empire->Name()));
832         else
833             SetName("");
834     }
835     //@}
836 
837 private:
DoLayout()838     void DoLayout() {
839         m_queue_lb->SizeMove(GG::Pt(GG::X0, GG::Y0),
840                              GG::Pt(ClientWidth(), ClientHeight() - GG::Y(CUIWnd::INNER_BORDER_ANGLE_OFFSET)));
841     }
842 
843     std::shared_ptr<ProdQueueListBox> m_queue_lb;
844 };
845 
846 //////////////////////////////////////////////////
847 // ProductionWnd                                //
848 //////////////////////////////////////////////////
ProductionWnd(GG::X w,GG::Y h)849 ProductionWnd::ProductionWnd(GG::X w, GG::Y h) :
850     GG::Wnd(GG::X0, GG::Y0, w, h, GG::INTERACTIVE | GG::ONTOP),
851     m_order_issuing_enabled(false),
852     m_empire_shown_id(ALL_EMPIRES)
853 {}
854 
CompleteConstruction()855 void ProductionWnd::CompleteConstruction() {
856      GG::Wnd::CompleteConstruction();
857    //DebugLogger() << "ProductionWindow:  fullscreen width: "<< GetOptionsDB().Get<int>("video.fullscreen.width")
858    //              << " ; windowed width: " << GetOptionsDB().Get<int>("video.windowed.width");
859 
860     GG::X queue_width(GetOptionsDB().Get<int>("ui.queue.width"));
861     GG::Y info_height(ClientUI::Pts()*10);
862 
863     m_production_info_panel = GG::Wnd::Create<ResourceInfoPanel>(
864         UserString("PRODUCTION_WND_TITLE"), UserString("PRODUCTION_INFO_PP"),
865         GG::X0, GG::Y0, queue_width, info_height, "production.info");
866     m_queue_wnd = GG::Wnd::Create<ProductionQueueWnd>(GG::X0, info_height, queue_width,
867                                                       ClientSize().y - info_height);
868     m_build_designator_wnd = GG::Wnd::Create<BuildDesignatorWnd>(ClientSize().x, ClientSize().y);
869 
870     SetChildClippingMode(ClipToClient);
871 
872 #if BOOST_VERSION >= 106000
873     using boost::placeholders::_1;
874     using boost::placeholders::_2;
875     using boost::placeholders::_3;
876     using boost::placeholders::_4;
877 #endif
878 
879     m_build_designator_wnd->AddBuildToQueueSignal.connect(
880         boost::bind(&ProductionWnd::AddBuildToQueueSlot, this, _1, _2, _3, _4));
881     m_build_designator_wnd->BuildQuantityChangedSignal.connect(
882         boost::bind(&ProductionWnd::ChangeBuildQuantitySlot, this, _1, _2));
883     m_build_designator_wnd->SystemSelectedSignal.connect(
884         SystemSelectedSignal);
885     m_queue_wnd->GetQueueListBox()->MovedRowSignal.connect(
886         boost::bind(&ProductionWnd::QueueItemMoved, this, _1, _2));
887     m_queue_wnd->GetQueueListBox()->QueueItemDeletedSignal.connect(
888         boost::bind(&ProductionWnd::DeleteQueueItem, this, _1));
889     m_queue_wnd->GetQueueListBox()->LeftClickedRowSignal.connect(
890         boost::bind(&ProductionWnd::QueueItemClickedSlot, this, _1, _2, _3));
891     m_queue_wnd->GetQueueListBox()->DoubleClickedRowSignal.connect(
892         boost::bind(&ProductionWnd::QueueItemDoubleClickedSlot, this, _1, _2, _3));
893     m_queue_wnd->GetQueueListBox()->QueueItemRalliedToSignal.connect(
894         boost::bind(&ProductionWnd::QueueItemRallied, this, _1, _2));
895     m_queue_wnd->GetQueueListBox()->QueueItemDupedSignal.connect(
896         boost::bind(&ProductionWnd::QueueItemDuped, this, _1));
897     m_queue_wnd->GetQueueListBox()->QueueItemSplitSignal.connect(
898         boost::bind(&ProductionWnd::QueueItemSplit, this, _1));
899     m_queue_wnd->GetQueueListBox()->ShowPediaSignal.connect(
900         boost::bind(&ProductionWnd::ShowPedia, this));
901     m_queue_wnd->GetQueueListBox()->QueueItemPausedSignal.connect(
902         boost::bind(&ProductionWnd::QueueItemPaused, this, _1, _2));
903     m_queue_wnd->GetQueueListBox()->QueueItemUseImperialPPSignal.connect(
904         boost::bind(&ProductionWnd::QueueItemUseImperialPP, this, _1, _2));
905 
906     AttachChild(m_production_info_panel);
907     AttachChild(m_queue_wnd);
908     AttachChild(m_build_designator_wnd);
909 }
910 
~ProductionWnd()911 ProductionWnd::~ProductionWnd()
912 { m_empire_connection.disconnect(); }
913 
SelectedPlanetID() const914 int ProductionWnd::SelectedPlanetID() const
915 { return m_build_designator_wnd->SelectedPlanetID(); }
916 
ShownEmpireID() const917 int ProductionWnd::ShownEmpireID() const
918 { return m_empire_shown_id; }
919 
InWindow(const GG::Pt & pt) const920 bool ProductionWnd::InWindow(const GG::Pt& pt) const
921 { return m_production_info_panel->InWindow(pt) || m_queue_wnd->InWindow(pt) || m_build_designator_wnd->InWindow(pt); }
922 
InClient(const GG::Pt & pt) const923 bool ProductionWnd::InClient(const GG::Pt& pt) const
924 { return m_production_info_panel->InClient(pt) || m_queue_wnd->InClient(pt) || m_build_designator_wnd->InClient(pt); }
925 
SizeMove(const GG::Pt & ul,const GG::Pt & lr)926 void ProductionWnd::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
927     const GG::Pt old_size = Size();
928     GG::Wnd::SizeMove(ul, lr);
929     if (old_size != Size())
930         DoLayout();
931 }
932 
DoLayout()933 void ProductionWnd::DoLayout() {
934     GG::X queue_width(GetOptionsDB().Get<int>("ui.queue.width"));
935     GG::Y info_height(ClientUI::Pts()*8 + 34);
936 
937     m_production_info_panel->MoveTo(GG::Pt(GG::X0, GG::Y0));
938     m_production_info_panel->Resize(GG::Pt(queue_width, info_height));
939 
940     m_queue_wnd->MoveTo(GG::Pt(GG::X0, info_height));
941     m_queue_wnd->Resize(GG::Pt(queue_width, ClientSize().y - info_height));
942 
943     m_build_designator_wnd->Resize(ClientSize());
944 }
945 
Render()946 void ProductionWnd::Render()
947 {}
948 
SetEmpireShown(int empire_id)949 void ProductionWnd::SetEmpireShown(int empire_id) {
950     if (empire_id != m_empire_shown_id) {
951         m_empire_shown_id = empire_id;
952         Refresh();
953     }
954 }
955 
Refresh()956 void ProductionWnd::Refresh() {
957     // useful at start of turn or when loading empire from save, or when
958     // the selected empire shown has changed.
959     // because empire object is recreated based on turn update from server,
960     // connections of signals emitted from the empire must be remade after
961     // getting a turn update
962     m_empire_connection.disconnect();
963     if (Empire* empire = GetEmpire(m_empire_shown_id))
964         m_empire_connection = empire->GetProductionQueue().ProductionQueueChangedSignal.connect(
965             boost::bind(&ProductionWnd::ProductionQueueChangedSlot, this));
966 
967     UpdateInfoPanel();
968     UpdateQueue();
969 
970     m_build_designator_wnd->Refresh();
971 }
972 
Reset()973 void ProductionWnd::Reset() {
974     m_empire_shown_id = ALL_EMPIRES;
975     Refresh();
976     m_queue_wnd->GetQueueListBox()->BringRowIntoView(m_queue_wnd->GetQueueListBox()->begin());
977 }
978 
Update()979 void ProductionWnd::Update() {
980     // useful when empire hasn't changed, but production status of it might have
981     UpdateInfoPanel();
982     UpdateQueue();
983 
984     m_build_designator_wnd->Update();
985 }
986 
ShowBuildingTypeInEncyclopedia(const std::string & building_type)987 void ProductionWnd::ShowBuildingTypeInEncyclopedia(const std::string& building_type)
988 { m_build_designator_wnd->ShowBuildingTypeInEncyclopedia(building_type); }
989 
ShowShipDesignInEncyclopedia(int design_id)990 void ProductionWnd::ShowShipDesignInEncyclopedia(int design_id)
991 { m_build_designator_wnd->ShowShipDesignInEncyclopedia(design_id); }
992 
ShowPlanetInEncyclopedia(int planet_id)993 void ProductionWnd::ShowPlanetInEncyclopedia(int planet_id)
994 { m_build_designator_wnd->ShowPlanetInEncyclopedia(planet_id); }
995 
ShowTechInEncyclopedia(const std::string & tech_name)996 void ProductionWnd::ShowTechInEncyclopedia(const std::string& tech_name)
997 { m_build_designator_wnd->ShowTechInEncyclopedia(tech_name); }
998 
ShowShipPartInEncyclopedia(const std::string & part_name)999 void ProductionWnd::ShowShipPartInEncyclopedia(const std::string& part_name)
1000 { m_build_designator_wnd->ShowShipPartInEncyclopedia(part_name); }
1001 
ShowSpeciesInEncyclopedia(const std::string & species_name)1002 void ProductionWnd::ShowSpeciesInEncyclopedia(const std::string& species_name)
1003 { m_build_designator_wnd->ShowSpeciesInEncyclopedia(species_name); }
1004 
ShowEmpireInEncyclopedia(int empire_id)1005 void ProductionWnd::ShowEmpireInEncyclopedia(int empire_id)
1006 { m_build_designator_wnd->ShowEmpireInEncyclopedia(empire_id); }
1007 
ShowSpecialInEncyclopedia(const std::string & special_name)1008 void ProductionWnd::ShowSpecialInEncyclopedia(const std::string& special_name)
1009 { m_build_designator_wnd->ShowSpecialInEncyclopedia(special_name); }
1010 
ShowFieldTypeInEncyclopedia(const std::string & field_type_name)1011 void ProductionWnd::ShowFieldTypeInEncyclopedia(const std::string& field_type_name)
1012 { m_build_designator_wnd->ShowFieldTypeInEncyclopedia(field_type_name); }
1013 
ShowPedia()1014 void ProductionWnd::ShowPedia()
1015 { m_build_designator_wnd->ShowPedia(); }
1016 
HidePedia()1017 void ProductionWnd::HidePedia()
1018 { m_build_designator_wnd->HidePedia(); }
1019 
TogglePedia()1020 void ProductionWnd::TogglePedia()
1021 { m_build_designator_wnd->TogglePedia(); }
1022 
PediaVisible()1023 bool ProductionWnd::PediaVisible()
1024 { return m_build_designator_wnd->PediaVisible(); }
1025 
CenterOnBuild(int queue_idx,bool open)1026 void ProductionWnd::CenterOnBuild(int queue_idx, bool open)
1027 { m_build_designator_wnd->CenterOnBuild(queue_idx, open); }
1028 
SelectPlanet(int planet_id)1029 void ProductionWnd::SelectPlanet(int planet_id) {
1030     m_build_designator_wnd->SelectPlanet(planet_id);
1031     UpdateInfoPanel();
1032 }
1033 
SelectDefaultPlanet()1034 void ProductionWnd::SelectDefaultPlanet()
1035 { m_build_designator_wnd->SelectDefaultPlanet(); }
1036 
SelectSystem(int system_id)1037 void ProductionWnd::SelectSystem(int system_id) {
1038     if (system_id != SidePanel::SystemID()) {
1039         m_build_designator_wnd->SelectSystem(system_id);
1040         // refresh so as to correctly highlight builds for selected system
1041         Update();
1042     }
1043 }
1044 
QueueItemMoved(const GG::ListBox::iterator & row_it,const GG::ListBox::iterator & original_position_it)1045 void ProductionWnd::QueueItemMoved(const GG::ListBox::iterator& row_it,
1046                                    const GG::ListBox::iterator& original_position_it)
1047 {
1048     if (!m_order_issuing_enabled)
1049         return;
1050     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
1051     Empire* empire = GetEmpire(client_empire_id);
1052     if (!empire)
1053         return;
1054 
1055     // This precorrects the position for a factor in Empire::MoveProductionWithinQueue
1056     int new_position = m_queue_wnd->GetQueueListBox()->IteraterIndex(row_it);
1057     int original_position = m_queue_wnd->GetQueueListBox()->IteraterIndex(original_position_it);
1058     auto direction = original_position < new_position;
1059     int corrected_new_position = new_position + (direction ? 1 : 0);
1060 
1061     auto queue_it = empire->GetProductionQueue().find(original_position);
1062 
1063     if (queue_it != empire->GetProductionQueue().end())
1064         HumanClientApp::GetApp()->Orders().IssueOrder(
1065             std::make_shared<ProductionQueueOrder>(ProductionQueueOrder::MOVE_ITEM_TO_INDEX,
1066                                                    client_empire_id, queue_it->uuid,
1067                                                    corrected_new_position));
1068     empire->UpdateProductionQueue();
1069 }
1070 
Sanitize()1071 void ProductionWnd::Sanitize()
1072 { m_build_designator_wnd->Clear(); }
1073 
ProductionQueueChangedSlot()1074 void ProductionWnd::ProductionQueueChangedSlot() {
1075     UpdateInfoPanel();
1076     UpdateQueue();
1077     m_build_designator_wnd->Update();
1078 }
1079 
UpdateQueue()1080 void ProductionWnd::UpdateQueue() {
1081     DebugLogger() << "ProductionWnd::UpdateQueue()";
1082     ScopedTimer timer("ProductionWnd::UpdateQueue");
1083 
1084     m_queue_wnd->SetEmpire(m_empire_shown_id);
1085     ProdQueueListBox* queue_lb = m_queue_wnd->GetQueueListBox();
1086 
1087     // Capture the list scroll state
1088     // Try to preserve the same queue context with completely new queue items
1089     int initial_offset_from_begin = queue_lb->IteraterIndex(queue_lb->FirstRowShown());
1090     int initial_offset_to_end = 0;
1091     if (initial_offset_from_begin < 0)
1092         ErrorLogger() << "ProductionWnd::UpdateQueue initial offset from begin was invalid...";
1093     else
1094         initial_offset_to_end = std::distance(queue_lb->FirstRowShown(), queue_lb->end());
1095     bool initial_last_visible_row_is_end(queue_lb->LastVisibleRow() == queue_lb->end());
1096 
1097     queue_lb->Clear();
1098 
1099     const Empire* empire = GetEmpire(m_empire_shown_id);
1100     if (!empire)
1101         return;
1102 
1103 #if BOOST_VERSION >= 106000
1104     using boost::placeholders::_1;
1105     using boost::placeholders::_2;
1106     using boost::placeholders::_3;
1107 #endif
1108 
1109     int i = 0;
1110     for (const ProductionQueue::Element& elem : empire->GetProductionQueue()) {
1111         auto row = GG::Wnd::Create<QueueRow>(queue_lb->RowWidth(), elem, i);
1112         row->RowQuantChangedSignal.connect(
1113             boost::bind(&ProductionWnd::ChangeBuildQuantityBlockSlot, this, _1, _2, _3));
1114         queue_lb->Insert(row);
1115         ++i;
1116     }
1117 
1118     // Restore the list scroll state
1119     // If we were at the top stay at the top
1120     if (initial_offset_from_begin == 0)
1121         queue_lb->SetFirstRowShown(queue_lb->begin());
1122 
1123     // If we were not at the bottom then keep the same first row position
1124     else if (!initial_last_visible_row_is_end && initial_offset_from_begin < static_cast<int>(queue_lb->NumRows()))
1125         queue_lb->SetFirstRowShown(std::next(queue_lb->begin(), initial_offset_from_begin));
1126 
1127     // otherwise keep the same relative position from the bottom to
1128     // preserve the end of list dead space
1129     else if (initial_offset_to_end < static_cast<int>(queue_lb->NumRows()))
1130         queue_lb->SetFirstRowShown(std::next(queue_lb->begin(), queue_lb->NumRows() - initial_offset_to_end));
1131     else
1132         queue_lb->SetFirstRowShown(queue_lb->begin());
1133 }
1134 
UpdateInfoPanel()1135 void ProductionWnd::UpdateInfoPanel() {
1136     const Empire* empire = GetEmpire(m_empire_shown_id);
1137     if (!empire) {
1138         m_production_info_panel->SetName(UserString("PRODUCTION_WND_TITLE"));
1139         m_production_info_panel->ClearLocalInfo();
1140         return;
1141     } else {
1142         m_production_info_panel->SetEmpireID(m_empire_shown_id);
1143     }
1144 
1145     const ProductionQueue& queue = empire->GetProductionQueue();
1146     float PPs = empire->ResourceOutput(RE_INDUSTRY);
1147     float total_queue_cost = queue.TotalPPsSpent();
1148     float stockpile = empire->GetResourcePool(RE_INDUSTRY)->Stockpile();
1149     float stockpile_use = boost::accumulate(empire->GetProductionQueue().AllocatedStockpilePP() | boost::adaptors::map_values, 0.0f);
1150     float stockpile_use_max = queue.StockpileCapacity();
1151     m_production_info_panel->SetTotalPointsCost(PPs, total_queue_cost);
1152     m_production_info_panel->SetStockpileCost(stockpile, stockpile_use, stockpile_use_max);
1153 
1154     // find if there is a local location
1155     int prod_loc_id = this->SelectedPlanetID();
1156     auto loc_obj = Objects().get(prod_loc_id);
1157     if (!loc_obj) {
1158         // clear local info...
1159         m_production_info_panel->ClearLocalInfo();
1160         return;
1161     }
1162 
1163     // show location-specific information about supply group
1164     // resource availability
1165 
1166     // find available and allocated PP at selected production location
1167     auto available_pp = queue.AvailablePP(empire->GetResourcePool(RE_INDUSTRY));
1168     const auto& allocated_pp = queue.AllocatedPP();
1169 
1170     float available_pp_at_loc = 0.0f;
1171     float allocated_pp_at_loc = 0.0f;
1172     for (const auto& map : available_pp) {
1173         if (map.first.count(prod_loc_id)) {
1174             available_pp_at_loc = map.second;
1175             break;
1176         }
1177     }
1178 
1179     for (const auto& map : allocated_pp) {
1180         if (map.first.count(prod_loc_id)) {
1181             allocated_pp_at_loc = map.second;
1182             break;
1183         }
1184     }
1185 
1186 
1187     // find use of stockpile at selected production location
1188     float stockpile_local_use = 0.0f;
1189 
1190     for (const auto& map : empire->GetProductionQueue().AllocatedStockpilePP()) {
1191         if (map.first.count(prod_loc_id)) {
1192             stockpile_local_use = map.second;
1193             break;
1194         }
1195     }
1196 
1197     m_production_info_panel->SetLocalPointsCost(available_pp_at_loc, allocated_pp_at_loc,
1198                                                 stockpile_local_use, stockpile_use_max,
1199                                                 loc_obj->Name());
1200 }
1201 
AddBuildToQueueSlot(const ProductionQueue::ProductionItem & item,int number,int location,int pos)1202 void ProductionWnd::AddBuildToQueueSlot(const ProductionQueue::ProductionItem& item,
1203                                         int number, int location, int pos)
1204 {
1205     if (!m_order_issuing_enabled)
1206         return;
1207     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
1208     Empire* empire = GetEmpire(client_empire_id);
1209     if (!empire)
1210         return;
1211 
1212     HumanClientApp::GetApp()->Orders().IssueOrder(
1213         std::make_shared<ProductionQueueOrder>(ProductionQueueOrder::PLACE_IN_QUEUE,
1214                                                client_empire_id, item, number, location, pos));
1215 
1216     empire->UpdateProductionQueue();
1217     m_build_designator_wnd->CenterOnBuild(pos >= 0 ? pos : m_queue_wnd->GetQueueListBox()->NumRows() - 1);
1218 }
1219 
ChangeBuildQuantitySlot(int queue_idx,int quantity)1220 void ProductionWnd::ChangeBuildQuantitySlot(int queue_idx, int quantity) {
1221     if (!m_order_issuing_enabled)
1222         return;
1223     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
1224     Empire* empire = GetEmpire(client_empire_id);
1225     if (!empire)
1226         return;
1227 
1228     auto queue_it = empire->GetProductionQueue().find(queue_idx);
1229 
1230     if (queue_it != empire->GetProductionQueue().end())
1231         HumanClientApp::GetApp()->Orders().IssueOrder(
1232             std::make_shared<ProductionQueueOrder>(ProductionQueueOrder::SET_QUANTITY,
1233                                                    client_empire_id, queue_it->uuid,
1234                                                    quantity));
1235 
1236     empire->UpdateProductionQueue();
1237 }
1238 
ChangeBuildQuantityBlockSlot(int queue_idx,int quantity,int blocksize)1239 void ProductionWnd::ChangeBuildQuantityBlockSlot(int queue_idx, int quantity, int blocksize) {
1240     if (!m_order_issuing_enabled)
1241         return;
1242     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
1243     Empire* empire = GetEmpire(client_empire_id);
1244     if (!empire)
1245         return;
1246 
1247     auto queue_it = empire->GetProductionQueue().find(queue_idx);
1248 
1249     if (queue_it != empire->GetProductionQueue().end())
1250         HumanClientApp::GetApp()->Orders().IssueOrder(
1251             std::make_shared<ProductionQueueOrder>(ProductionQueueOrder::SET_QUANTITY_AND_BLOCK_SIZE,
1252                                                    client_empire_id, queue_it->uuid,
1253                                                    quantity, blocksize));
1254 
1255     empire->UpdateProductionQueue();
1256 }
1257 
DeleteQueueItem(GG::ListBox::iterator it)1258 void ProductionWnd::DeleteQueueItem(GG::ListBox::iterator it) {
1259     if (!m_order_issuing_enabled)
1260         return;
1261     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
1262     Empire* empire = GetEmpire(client_empire_id);
1263     if (!empire)
1264         return;
1265 
1266     auto idx = m_queue_wnd->GetQueueListBox()->IteraterIndex(it);
1267     auto queue_it = empire->GetProductionQueue().find(idx);
1268 
1269     if (queue_it != empire->GetProductionQueue().end()) {
1270         DebugLogger() << "DeleteQueueItem idx: " << idx << "  item: " << queue_it->Dump();
1271         HumanClientApp::GetApp()->Orders().IssueOrder(
1272             std::make_shared<ProductionQueueOrder>(ProductionQueueOrder::REMOVE_FROM_QUEUE,
1273                                                    client_empire_id, queue_it->uuid));
1274     }
1275 
1276     empire->UpdateProductionQueue();
1277 }
1278 
QueueItemClickedSlot(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)1279 void ProductionWnd::QueueItemClickedSlot(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) {
1280     if (m_queue_wnd->GetQueueListBox()->DisplayingValidQueueItems()) {
1281         if (modkeys & GG::MOD_KEY_CTRL)
1282             DeleteQueueItem(it);
1283         else
1284             m_build_designator_wnd->CenterOnBuild(m_queue_wnd->GetQueueListBox()->IteraterIndex(it));
1285     }
1286 }
1287 
QueueItemDoubleClickedSlot(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)1288 void ProductionWnd::QueueItemDoubleClickedSlot(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) {
1289     if (m_queue_wnd->GetQueueListBox()->DisplayingValidQueueItems())
1290         m_build_designator_wnd->CenterOnBuild(m_queue_wnd->GetQueueListBox()->IteraterIndex(it), true);
1291 }
1292 
QueueItemRallied(GG::ListBox::iterator it,int object_id)1293 void ProductionWnd::QueueItemRallied(GG::ListBox::iterator it, int object_id) {
1294     if (!m_order_issuing_enabled)
1295         return;
1296     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
1297     Empire* empire = GetEmpire(client_empire_id);
1298     if (!empire)
1299         return;
1300 
1301     int rally_point_id = object_id;
1302     if (rally_point_id == INVALID_OBJECT_ID) {
1303         // get rally point from selected system
1304         rally_point_id = SidePanel::SystemID();
1305     }
1306     if (rally_point_id == INVALID_OBJECT_ID)
1307         return;
1308 
1309     auto idx = m_queue_wnd->GetQueueListBox()->IteraterIndex(it);
1310     auto queue_it = empire->GetProductionQueue().find(idx);
1311 
1312     if (queue_it != empire->GetProductionQueue().end())
1313         HumanClientApp::GetApp()->Orders().IssueOrder(
1314             std::make_shared<ProductionQueueOrder>(ProductionQueueOrder::SET_RALLY_POINT,
1315                                                    client_empire_id, queue_it->uuid,
1316                                                    rally_point_id));
1317 
1318     empire->UpdateProductionQueue();
1319 }
1320 
QueueItemPaused(GG::ListBox::iterator it,bool pause)1321 void ProductionWnd::QueueItemPaused(GG::ListBox::iterator it, bool pause) {
1322     if (!m_order_issuing_enabled)
1323         return;
1324     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
1325     Empire* empire = GetEmpire(client_empire_id);
1326     if (!empire)
1327         return;
1328 
1329     auto idx = m_queue_wnd->GetQueueListBox()->IteraterIndex(it);
1330     auto queue_it = empire->GetProductionQueue().find(idx);
1331     auto action = pause ? ProductionQueueOrder::PAUSE_PRODUCTION : ProductionQueueOrder::RESUME_PRODUCTION;
1332 
1333     if (queue_it != empire->GetProductionQueue().end())
1334         HumanClientApp::GetApp()->Orders().IssueOrder(
1335             std::make_shared<ProductionQueueOrder>(action, client_empire_id, queue_it->uuid));
1336 
1337     empire->UpdateProductionQueue();
1338 }
1339 
QueueItemDuped(GG::ListBox::iterator it)1340 void ProductionWnd::QueueItemDuped(GG::ListBox::iterator it) {
1341     if (!m_order_issuing_enabled)
1342         return;
1343     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
1344     Empire* empire = GetEmpire(client_empire_id);
1345     if (!empire)
1346         return;
1347 
1348     auto idx = m_queue_wnd->GetQueueListBox()->IteraterIndex(it);
1349     auto queue_it = empire->GetProductionQueue().find(idx);
1350 
1351     if (queue_it != empire->GetProductionQueue().end())
1352         HumanClientApp::GetApp()->Orders().IssueOrder(
1353             std::make_shared<ProductionQueueOrder>(ProductionQueueOrder::DUPLICATE_ITEM,
1354                                                    client_empire_id, queue_it->uuid));
1355 
1356     empire->UpdateProductionQueue();
1357 }
1358 
QueueItemSplit(GG::ListBox::iterator it)1359 void ProductionWnd::QueueItemSplit(GG::ListBox::iterator it) {
1360     if (!m_order_issuing_enabled)
1361         return;
1362     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
1363     Empire* empire = GetEmpire(client_empire_id);
1364     if (!empire)
1365         return;
1366 
1367     auto idx = m_queue_wnd->GetQueueListBox()->IteraterIndex(it);
1368     auto queue_it = empire->GetProductionQueue().find(idx);
1369 
1370     if (queue_it != empire->GetProductionQueue().end())
1371         HumanClientApp::GetApp()->Orders().IssueOrder(
1372             std::make_shared<ProductionQueueOrder>(ProductionQueueOrder::SPLIT_INCOMPLETE,
1373                                                    client_empire_id, queue_it->uuid));
1374 
1375     empire->UpdateProductionQueue();
1376 }
1377 
QueueItemUseImperialPP(GG::ListBox::iterator it,bool allow)1378 void ProductionWnd::QueueItemUseImperialPP(GG::ListBox::iterator it, bool allow) {
1379     if (!m_order_issuing_enabled)
1380         return;
1381     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
1382     Empire* empire = GetEmpire(client_empire_id);
1383     if (!empire)
1384         return;
1385 
1386     auto idx = m_queue_wnd->GetQueueListBox()->IteraterIndex(it);
1387     auto queue_it = empire->GetProductionQueue().find(idx);
1388     auto action = allow ? ProductionQueueOrder::ALLOW_STOCKPILE_USE : ProductionQueueOrder::DISALLOW_STOCKPILE_USE;
1389 
1390     if (queue_it != empire->GetProductionQueue().end())
1391         HumanClientApp::GetApp()->Orders().IssueOrder(
1392             OrderPtr(new ProductionQueueOrder(action, client_empire_id, queue_it->uuid)));
1393 
1394     empire->UpdateProductionQueue();
1395 }
1396 
EnableOrderIssuing(bool enable)1397 void ProductionWnd::EnableOrderIssuing(bool enable/* = true*/) {
1398     m_order_issuing_enabled = enable;
1399     m_queue_wnd->GetQueueListBox()->EnableOrderIssuing(m_order_issuing_enabled);
1400 }
1401