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