1 #include "TechTreeWnd.h"
2 
3 #include "ClientUI.h"
4 #include "CUIControls.h"
5 #include "CUIDrawUtil.h"
6 #include "CUIWnd.h"
7 #include "Sound.h"
8 #include "IconTextBrowseWnd.h"
9 #include "EncyclopediaDetailPanel.h"
10 #include "../client/human/HumanClientApp.h"
11 #include "../util/i18n.h"
12 #include "../util/Logger.h"
13 #include "../util/OptionsDB.h"
14 #include "../util/Directories.h"
15 #include "../util/ScopedTimer.h"
16 #include "../universe/Effects.h"
17 #include "../universe/Enums.h"
18 #include "../universe/Tech.h"
19 #include "../universe/UnlockableItem.h"
20 #include "../universe/ValueRef.h"
21 #include "../Empire/Empire.h"
22 #include "TechTreeLayout.h"
23 #include "TechTreeArcs.h"
24 #include "Hotkeys.h"
25 
26 #include <GG/GLClientAndServerBuffer.h>
27 #include <GG/GUI.h>
28 #include <GG/Layout.h>
29 #include <GG/StaticGraphic.h>
30 
31 #include <algorithm>
32 
33 namespace {
34     const std::string RES_PEDIA_WND_NAME = "research.pedia";
35     const std::string RES_CONTROLS_WND_NAME = "research.control";
36 
37     // command-line options
AddOptions(OptionsDB & db)38     void AddOptions(OptionsDB& db) {
39         db.Add("ui.research.tree.spacing.horizontal",           UserStringNop("OPTIONS_DB_UI_TECH_LAYOUT_HORZ_SPACING"),
40                0.25,                    RangedStepValidator<double>(0.25, 0.25, 4.0));
41         db.Add("ui.research.tree.spacing.vertical",             UserStringNop("OPTIONS_DB_UI_TECH_LAYOUT_VERT_SPACING"),
42                0.75,                    RangedStepValidator<double>(0.25, 0.25, 4.0));
43         db.Add("ui.research.tree.zoom.scale",                   UserStringNop("OPTIONS_DB_UI_TECH_LAYOUT_ZOOM_SCALE"),
44                1.0,                     RangedStepValidator<double>(1.0, -25.0, 10.0));
45         db.Add("ui.research.control.graphic.size",              UserStringNop("OPTIONS_DB_UI_TECH_CTRL_ICON_SIZE"),
46                3.0,                     RangedStepValidator<double>(0.25, 0.5,  12.0));
47         db.Add("ui." + RES_PEDIA_WND_NAME + ".hidden.enabled",  UserStringNop("OPTIONS_DB_RESEARCH_PEDIA_HIDDEN"), false);
48 
49         // TechListBox::TechRow column widths
50         int default_pts = 16;
51         db.Add("ui.research.list.column.graphic.width",         UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_GRAPHIC"),
52                default_pts * 3,         StepValidator<int>(1));
53         db.Add("ui.research.list.column.name.width",            UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_NAME"),
54                default_pts * 18,        StepValidator<int>(1));
55         db.Add("ui.research.list.column.cost.width",            UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_COST"),
56                default_pts * 8,         StepValidator<int>(1));
57         db.Add("ui.research.list.column.time.width",            UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_TIME"),
58                default_pts * 6,         StepValidator<int>(1));
59         db.Add("ui.research.list.column.category.width",        UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_CATEGORY"),
60                default_pts * 12,        StepValidator<int>(1));
61         db.Add("ui.research.list.column.description.width",     UserStringNop("OPTIONS_DB_UI_TECH_LISTBOX_COL_WIDTH_DESCRIPTION"),
62                default_pts * 18,        StepValidator<int>(1));
63 
64         // Default status for TechTreeControl filter.
65         db.Add<bool>("ui.research.status.unresearchable.shown", UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_UNRESEARCHABLE"),         false);
66         db.Add<bool>("ui.research.status.partial.shown",        UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_HAS_RESEARCHED_PREREQ"),  true);
67         db.Add<bool>("ui.research.status.researchable.shown",   UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_RESEARCHABLE"),           true);
68         db.Add<bool>("ui.research.status.completed.shown",      UserStringNop("OPTIONS_DB_UI_TECH_TREE_STATUS_COMPLETED"),              true);
69     }
70     bool temp_bool = RegisterOptions(&AddOptions);
71 
TechPanelWidth()72     GG::X   TechPanelWidth()
73     { return GG::X(ClientUI::Pts()*38); }
TechPanelHeight()74     GG::Y   TechPanelHeight()
75     { return GG::Y(ClientUI::Pts()*6); }
76 
77     const double ZOOM_STEP_SIZE = 1.12;
78     const double MIN_SCALE = std::pow(ZOOM_STEP_SIZE, -25.0);
79     const double MAX_SCALE = std::pow(ZOOM_STEP_SIZE, 10.0);
80     const double INITIAL_SCALE = std::pow(ZOOM_STEP_SIZE, -5.0);
81 
82     const double PI = 3.1415926535897932384626433;
83 
TechVisible(const std::string & tech_name,const std::set<std::string> & categories_shown,const std::set<TechStatus> & statuses_shown)84     bool    TechVisible(const std::string& tech_name,
85                         const std::set<std::string>& categories_shown,
86                         const std::set<TechStatus>& statuses_shown)
87     {
88         const Tech* tech = GetTech(tech_name);
89         if (!tech)
90             return false;
91 
92         // Unresearchable techs are never to be shown on tree
93         if (!tech->Researchable())
94             return false;
95 
96         // check that category is visible
97         if (!categories_shown.count(tech->Category()))
98             return false;
99 
100         // check tech status
101         const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
102         if (!empire)
103             return true;    // if no empire, techs have no status, so just return true
104         if (!statuses_shown.count(empire->GetTechStatus(tech_name)))
105             return false;
106 
107         // all tests pass, so tech is visible
108         return true;
109     }
110 }
111 
112 ///////////////////////////
113 //   TechRowBrowseWnd    //
114 ///////////////////////////
TechRowBrowseWnd(const std::string & tech_name,int empire_id)115 std::shared_ptr<GG::BrowseInfoWnd> TechRowBrowseWnd(const std::string& tech_name, int empire_id) {
116     const Empire* empire = GetEmpire(empire_id);
117     const Tech* tech = GetTech(tech_name);
118     if (!tech)
119         return nullptr;
120 
121     std::string main_text;
122 
123     main_text += UserString(tech->Category()) + " - ";
124     main_text += UserString(tech->ShortDescription()) + "\n";
125 
126     if (empire) {
127         TechStatus tech_status = empire->GetTechStatus(tech_name);
128         if (!tech->Researchable()) {
129             main_text += UserString("TECH_WND_UNRESEARCHABLE") + "\n";
130 
131         } else if (tech_status == TS_UNRESEARCHABLE) {
132             main_text += UserString("TECH_WND_STATUS_LOCKED") + "\n";
133 
134             std::vector<std::string> unresearched_prereqs;
135             for (const std::string& prereq : tech->Prerequisites()) {
136                 TechStatus prereq_status = empire->GetTechStatus(prereq);
137                 if (prereq_status != TS_COMPLETE)
138                     unresearched_prereqs.push_back(prereq);
139             }
140             if (!unresearched_prereqs.empty()) {
141                 main_text += UserString("TECH_WND_UNRESEARCHED_PREREQUISITES");
142                 for (const std::string& prereq : unresearched_prereqs)
143                 { main_text += UserString(prereq) + "  "; }
144                 main_text += "\n";
145             }
146 
147         } else if (tech_status == TS_RESEARCHABLE) {
148             main_text += UserString("TECH_WND_STATUS_RESEARCHABLE") + "\n";
149 
150         } else if (tech_status == TS_COMPLETE) {
151             main_text += UserString("TECH_WND_STATUS_COMPLETED") + "\n";
152 
153         } else if (tech_status == TS_HAS_RESEARCHED_PREREQ) {
154             main_text += UserString("TECH_WND_STATUS_PARTIAL_UNLOCK") + "\n";
155         }
156 
157         const ResearchQueue& queue = empire->GetResearchQueue();
158         auto queue_it = queue.find(tech_name);
159         if (queue_it != queue.end()) {
160             main_text += UserString("TECH_WND_ENQUEUED") + "\n";
161 
162             float progress = empire->ResearchProgress(tech_name);
163             float total_cost = tech->ResearchCost(empire_id);
164             float allocation = queue_it->allocated_rp;
165             float max_allocation = tech->PerTurnCost(empire_id);
166 
167             // %1% / %2%  +  %3% / %4% RP/turn
168             main_text += boost::io::str(FlexibleFormat(UserString("TECH_WND_PROGRESS"))
169                     % DoubleToString(progress, 3, false)
170                     % DoubleToString(total_cost, 3, false)
171                     % DoubleToString(allocation, 3, false)
172                     % DoubleToString(max_allocation, 3, false)) + "\n";
173 
174             int ETA = queue_it->turns_left;
175             if (ETA != -1)
176                 main_text += boost::io::str(FlexibleFormat(UserString("TECH_WND_ETA"))
177                     % ETA);
178 
179         } else if (tech->Researchable()) {
180             int turns = tech->ResearchTime(empire_id);
181             float cost = tech->ResearchCost(empire_id);
182             const std::string& cost_units = UserString("ENC_RP");
183 
184             main_text += boost::io::str(FlexibleFormat(UserString("ENC_COST_AND_TURNS_STR"))
185                 % DoubleToString(cost, 3, false)
186                 % cost_units
187                 % turns);
188         }
189 
190     } else if (tech->Researchable()) {
191         int turns = tech->ResearchTime(empire_id);
192         float cost = tech->ResearchCost(empire_id);
193         const std::string& cost_units = UserString("ENC_RP");
194 
195         main_text += boost::io::str(FlexibleFormat(UserString("ENC_COST_AND_TURNS_STR"))
196             % DoubleToString(cost, 3, false)
197             % cost_units
198             % turns);
199     }
200 
201     return GG::Wnd::Create<IconTextBrowseWnd>(
202         ClientUI::TechIcon(tech_name), UserString(tech_name), main_text);
203 }
204 
205 
206 //////////////////////////////////////////////////
207 // TechTreeWnd::TechTreeControls                //
208 //////////////////////////////////////////////////
209 /** A panel of buttons that control how the tech tree is displayed: what
210   * categories, statuses and types of techs to show. */
211 class TechTreeWnd::TechTreeControls : public CUIWnd {
212 public:
213     //! \name Structors //@{
214     TechTreeControls(const std::string& config_name = "");
215     void CompleteConstruction() override;
216      //@}
217 
218     //! \name Mutators //@{
219     void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override;
220 
221     void Render() override;
222 
223     void LDrag(const GG::Pt& pt, const GG::Pt& move, GG::Flags<GG::ModKey> mod_keys) override;
224 
225     /** Set checked value of control for TechStatus @p status to @p state */
226     void SetTechStatus(TechStatus status, bool state);
227     //@}
228 
229 private:
230     void            DoButtonLayout();
231 
232     /** These values are determined when doing button layout, and stored.
233       * They are later used when rendering separator lines between the groups
234       * of buttons */
235     int m_buttons_per_row;              // number of buttons that can fit into available horizontal space
236     GG::X m_col_offset;                 // horizontal distance between each column of buttons
237     GG::Y m_row_offset;                 // vertical distance between each row of buttons
238 
239     /** These values are used for rendering separator lines between groups of buttons */
240     static const int BUTTON_SEPARATION; // vertical or horizontal sepration between adjacent buttons
241     static const int UPPER_LEFT_PAD;    // offset of buttons' position from top left of controls box
242 
243     // TODO: replace all the above stored information with a vector of pairs of GG::Pt (or perhaps GG::Rect)
244     // This will contain the start and end points of all separator lines that need to be drawn.  This will be
245     // calculated by SizeMove, and stored, so that start and end positions don't need to be recalculated each
246     // time Render is called.
247 
248     std::shared_ptr<GG::StateButton>                             m_view_type_button;
249     std::shared_ptr<GG::StateButton>                             m_all_cat_button;
250     std::map<std::string, std::shared_ptr<GG::StateButton>>      m_cat_buttons;
251     std::map<TechStatus, std::shared_ptr<GG::StateButton>>       m_status_buttons;
252 
253     friend class TechTreeWnd;               // so TechTreeWnd can access buttons
254 };
255 
256 const int TechTreeWnd::TechTreeControls::BUTTON_SEPARATION = 2;
257 const int TechTreeWnd::TechTreeControls::UPPER_LEFT_PAD = 2;
258 
TechTreeControls(const std::string & config_name)259 TechTreeWnd::TechTreeControls::TechTreeControls(const std::string& config_name) :
260     CUIWnd(UserString("TECH_DISPLAY"), GG::INTERACTIVE | GG::DRAGABLE | GG::RESIZABLE | GG::ONTOP, config_name)
261 {}
262 
CompleteConstruction()263 void TechTreeWnd::TechTreeControls::CompleteConstruction() {
264    const int tooltip_delay = GetOptionsDB().Get<int>("ui.tooltip.delay");
265     const boost::filesystem::path icon_dir = ClientUI::ArtDir() / "icons" / "tech" / "controls";
266 
267     // create a button for each tech category...
268     for (const std::string& category : GetTechManager().CategoryNames()) {
269         GG::Clr icon_clr = ClientUI::CategoryColor(category);
270         std::shared_ptr<GG::SubTexture> icon = std::make_shared<GG::SubTexture>(ClientUI::CategoryIcon(category));
271         m_cat_buttons[category] = GG::Wnd::Create<GG::StateButton>("", ClientUI::GetFont(), GG::FORMAT_NONE, GG::CLR_ZERO,
272                                                      std::make_shared<CUIIconButtonRepresenter>(icon, icon_clr));
273         m_cat_buttons[category]->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(UserString(category), ""));
274         m_cat_buttons[category]->SetBrowseModeTime(tooltip_delay);
275         AttachChild(m_cat_buttons[category]);
276     }
277 
278     GG::Clr icon_color = GG::Clr(113, 150, 182, 255);
279     // and one for "ALL"
280     m_all_cat_button = GG::Wnd::Create<GG::StateButton>("", ClientUI::GetFont(), GG::FORMAT_NONE, GG::CLR_ZERO,
281                                            std::make_shared<CUIIconButtonRepresenter>(std::make_shared<GG::SubTexture>(ClientUI::GetTexture(icon_dir / "00_all_cats.png", true)), icon_color));
282     m_all_cat_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(UserString("ALL"), ""));
283     m_all_cat_button->SetBrowseModeTime(tooltip_delay);
284     m_all_cat_button->SetCheck(true);
285     AttachChild(m_all_cat_button);
286 
287     // create a button for each tech status
288     m_status_buttons[TS_UNRESEARCHABLE] = GG::Wnd::Create<GG::StateButton>("", ClientUI::GetFont(), GG::FORMAT_NONE, GG::CLR_ZERO,
289                                                               std::make_shared<CUIIconButtonRepresenter>(std::make_shared<GG::SubTexture>(ClientUI::GetTexture(icon_dir / "01_locked.png", true)), icon_color));
290     m_status_buttons[TS_UNRESEARCHABLE]->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(UserString("TECH_WND_STATUS_LOCKED"), ""));
291     m_status_buttons[TS_UNRESEARCHABLE]->SetBrowseModeTime(tooltip_delay);
292     m_status_buttons[TS_UNRESEARCHABLE]->SetCheck(
293         GetOptionsDB().Get<bool>("ui.research.status.unresearchable.shown"));
294     AttachChild(m_status_buttons[TS_UNRESEARCHABLE]);
295 
296     m_status_buttons[TS_HAS_RESEARCHED_PREREQ] = GG::Wnd::Create<GG::StateButton>("", ClientUI::GetFont(), GG::FORMAT_NONE, GG::CLR_ZERO,
297                                                                      std::make_shared<CUIIconButtonRepresenter>(std::make_shared<GG::SubTexture>(ClientUI::GetTexture(icon_dir / "02_partial.png", true)), icon_color));
298     m_status_buttons[TS_HAS_RESEARCHED_PREREQ]->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(UserString("TECH_WND_STATUS_PARTIAL_UNLOCK"), ""));
299     m_status_buttons[TS_HAS_RESEARCHED_PREREQ]->SetBrowseModeTime(tooltip_delay);
300     m_status_buttons[TS_HAS_RESEARCHED_PREREQ]->SetCheck(
301         GetOptionsDB().Get<bool>("ui.research.status.partial.shown"));
302     AttachChild(m_status_buttons[TS_HAS_RESEARCHED_PREREQ]);
303 
304     m_status_buttons[TS_RESEARCHABLE] = GG::Wnd::Create<GG::StateButton>("", ClientUI::GetFont(), GG::FORMAT_NONE, GG::CLR_ZERO,
305                                                             std::make_shared<CUIIconButtonRepresenter>(std::make_shared<GG::SubTexture>(ClientUI::GetTexture(icon_dir / "03_unlocked.png", true)), icon_color));
306     m_status_buttons[TS_RESEARCHABLE]->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(UserString("TECH_WND_STATUS_RESEARCHABLE"), ""));
307     m_status_buttons[TS_RESEARCHABLE]->SetBrowseModeTime(tooltip_delay);
308     m_status_buttons[TS_RESEARCHABLE]->SetCheck(
309         GetOptionsDB().Get<bool>("ui.research.status.researchable.shown"));
310     AttachChild(m_status_buttons[TS_RESEARCHABLE]);
311 
312     m_status_buttons[TS_COMPLETE] = GG::Wnd::Create<GG::StateButton>("", ClientUI::GetFont(), GG::FORMAT_NONE, GG::CLR_ZERO,
313                                                         std::make_shared<CUIIconButtonRepresenter>(std::make_shared<GG::SubTexture>(ClientUI::GetTexture(icon_dir / "04_completed.png", true)), icon_color));
314     m_status_buttons[TS_COMPLETE]->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(UserString("TECH_WND_STATUS_COMPLETED"), ""));
315     m_status_buttons[TS_COMPLETE]->SetBrowseModeTime(tooltip_delay);
316     m_status_buttons[TS_COMPLETE]->SetCheck(
317         GetOptionsDB().Get<bool>("ui.research.status.completed.shown"));
318     AttachChild(m_status_buttons[TS_COMPLETE]);
319 
320     // create button to switch between tree and list views
321     m_view_type_button = GG::Wnd::Create<GG::StateButton>(
322         "", ClientUI::GetFont(), GG::FORMAT_NONE, GG::CLR_ZERO,
323         std::make_shared<CUIIconButtonRepresenter>(std::make_shared<GG::SubTexture>(ClientUI::GetTexture(icon_dir / "06_view_tree.png", true)), icon_color,
324                                                                                       std::make_shared<GG::SubTexture>(ClientUI::GetTexture(icon_dir / "05_view_list.png", true)), GG::Clr(110, 172, 150, 255)));
325     m_view_type_button->SetBrowseInfoWnd(GG::Wnd::Create<TextBrowseWnd>(UserString("TECH_WND_VIEW_TYPE"), ""));
326     m_view_type_button->SetBrowseModeTime(tooltip_delay);
327     m_view_type_button->SetCheck(false);
328     AttachChild(m_view_type_button);
329 
330     SetChildClippingMode(ClipToClient);
331 
332     CUIWnd::CompleteConstruction();
333 
334     DoButtonLayout();
335     SaveDefaultedOptions();
336     SaveOptions();
337 }
338 
DoButtonLayout()339 void TechTreeWnd::TechTreeControls::DoButtonLayout() {
340     const int PTS = ClientUI::Pts();
341     const GG::X RIGHT_EDGE_PAD(PTS / 3);
342     const GG::X USABLE_WIDTH = std::max(ClientWidth() - RIGHT_EDGE_PAD, GG::X1);   // space in which to do layout
343     const GG::X BUTTON_WIDTH = GG::X(PTS * std::max(GetOptionsDB().Get<double>("ui.research.control.graphic.size"), 0.5));
344     const GG::Y BUTTON_HEIGHT = GG::Y(Value(BUTTON_WIDTH));
345 
346     m_col_offset = BUTTON_WIDTH + BUTTON_SEPARATION;    // horizontal distance between each column of buttons
347     m_row_offset = BUTTON_HEIGHT + BUTTON_SEPARATION;   // vertical distance between each row of buttons
348     m_buttons_per_row = std::max(Value(USABLE_WIDTH / (m_col_offset)), 1);
349 
350     const int NUM_NON_CATEGORY_BUTTONS = 6;  //  ALL, Locked, Partial, Unlocked, Complete, ViewType
351 
352     // place category buttons: fill each row completely before starting next row
353     int row = 0, col = -1;
354     for (auto& cat_button : m_cat_buttons) {
355          ++col;
356         if (col >= m_buttons_per_row) {
357             ++row;
358             col = 0;
359         }
360         GG::Pt ul(UPPER_LEFT_PAD + col*m_col_offset, UPPER_LEFT_PAD + row*m_row_offset);
361         GG::Pt lr = ul + GG::Pt(BUTTON_WIDTH, BUTTON_HEIGHT);
362         cat_button.second->SizeMove(ul, lr);
363     }
364 
365     // add ALL button
366     ++col;
367     if (col >= m_buttons_per_row) {
368         ++row;
369         col =0;
370     }
371     GG::Pt all_cats_ul(UPPER_LEFT_PAD + col*m_col_offset, UPPER_LEFT_PAD + row*m_row_offset);
372     GG::Pt all_cats_lr = all_cats_ul + GG::Pt(BUTTON_WIDTH, BUTTON_HEIGHT);
373     m_all_cat_button->SizeMove(all_cats_ul, all_cats_lr);
374 
375     // rowbreak after category buttons, before type and status buttons, unless all buttons fit on one row
376     if (m_buttons_per_row < (static_cast<int>(m_cat_buttons.size()) + NUM_NON_CATEGORY_BUTTONS)) {
377         col = -1;
378         ++row;
379     }
380 
381     // place status buttons: fill each row completely before starting next row
382     for (auto& status_button : m_status_buttons) {
383         ++col;
384         if (col >= m_buttons_per_row) {
385             ++row;
386             col = 0;
387         }
388         GG::Pt ul(UPPER_LEFT_PAD + col*m_col_offset, UPPER_LEFT_PAD + row*m_row_offset);
389         GG::Pt lr = ul + GG::Pt(BUTTON_WIDTH, BUTTON_HEIGHT);
390         status_button.second->SizeMove(ul, lr);
391     }
392 
393     // place view type button
394     ++col;
395     if (col + 1 > m_buttons_per_row) {
396         col = 0;
397         ++row;
398     }
399     GG::Pt view_type_ul(UPPER_LEFT_PAD + col*m_col_offset, UPPER_LEFT_PAD + row*m_row_offset);
400     GG::Pt view_type_lr = view_type_ul + GG::Pt(BUTTON_WIDTH, BUTTON_HEIGHT);
401     m_view_type_button->SizeMove(view_type_ul, view_type_lr);
402 
403     // prevent window from being shrunk less than one button width, or current number of rows of height
404     SetMinSize(GG::Pt(UPPER_LEFT_PAD + BUTTON_WIDTH + 3*RIGHT_EDGE_PAD,
405                       TopBorder() + BottomBorder() + UPPER_LEFT_PAD + (++row)*m_row_offset));
406 }
407 
SizeMove(const GG::Pt & ul,const GG::Pt & lr)408 void TechTreeWnd::TechTreeControls::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
409     m_config_save = false;
410     // maybe later do something interesting with docking
411     CUIWnd::SizeMove(ul, lr);                               // set width and upper left as user-requested
412     DoButtonLayout();                                       // given set width, position buttons and set appropriate minimum height
413     m_config_save = true;
414     CUIWnd::SizeMove(ul, GG::Pt(lr.x, ul.y + MinSize().y)); // width and upper left unchanged.  set height to minimum height
415 }
416 
Render()417 void TechTreeWnd::TechTreeControls::Render() {
418     CUIWnd::Render();
419 
420     //GG::Pt cl_ul = ClientUpperLeft();
421     //GG::Pt cl_lr = ClientLowerRight();
422 
423     //glColor(ClientUI::WndOuterBorderColor());
424 
425     //GG::Y category_bottom = cl_ul.y + m_category_button_rows*m_row_offset - BUTTON_SEPARATION/2 + UPPER_LEFT_PAD;
426     //GG::Line(cl_ul.x, category_bottom, cl_lr.x - 1, category_bottom);
427 
428     //if (m_buttons_per_row >= 6) {
429     //    // all six status and type buttons are on one row, and need a vertical separator between them
430     //    GG::X middle = cl_ul.x + m_col_offset*3 - BUTTON_SEPARATION/2 + UPPER_LEFT_PAD;
431     //    GG::Line(middle, category_bottom, middle, cl_lr.y - 1);
432     //} else {
433     //    // the status and type buttons are split into separate vertical groups, and need a horiztonal separator between them
434     //    GG::Y status_bottom = category_bottom + m_status_button_rows*m_row_offset;
435     //    GG::Line(cl_ul.x, status_bottom, cl_lr.x - 1, status_bottom);
436     //}
437 }
438 
LDrag(const GG::Pt & pt,const GG::Pt & move,GG::Flags<GG::ModKey> mod_keys)439 void TechTreeWnd::TechTreeControls::LDrag(const GG::Pt& pt, const GG::Pt& move, GG::Flags<GG::ModKey> mod_keys) {
440     if (m_drag_offset != GG::Pt(-GG::X1, -GG::Y1)) {  // resize-dragging
441         GG::Pt new_lr = pt - m_drag_offset;
442 
443         new_lr.y = Bottom();    // ignore y-resizes
444 
445         // constrain to within parent
446         if (auto&& parent = Parent()) {
447             GG::Pt max_lr = parent->ClientLowerRight();
448             new_lr.x = std::min(new_lr.x, max_lr.x);
449         }
450 
451         Resize(new_lr - UpperLeft());
452     } else {    // normal-dragging
453         GG::Pt final_move = move;
454 
455         if (auto&& parent = Parent()) {
456             GG::Pt ul = UpperLeft();
457             GG::Pt new_ul = ul + move;
458             //GG::Pt new_lr = lr + move;
459 
460             GG::Pt min_ul = parent->ClientUpperLeft() + GG::Pt(GG::X1, GG::Y1);
461             GG::Pt max_lr = parent->ClientLowerRight();
462             GG::Pt max_ul = max_lr - Size();
463 
464             new_ul.x = std::max(min_ul.x, std::min(max_ul.x, new_ul.x));
465             new_ul.y = std::max(min_ul.y, std::min(max_ul.y, new_ul.y));
466 
467             final_move = new_ul - ul;
468         }
469 
470         GG::Wnd::LDrag(pt, final_move, mod_keys);
471     }
472 }
473 
SetTechStatus(TechStatus status,bool state)474 void TechTreeWnd::TechTreeControls::SetTechStatus(TechStatus status, bool state) {
475     auto control_it = m_status_buttons.find(status);
476     if (control_it == m_status_buttons.end()) {
477         WarnLogger() << "No control for status " << status << " found";
478         return;
479     }
480 
481     control_it->second->SetCheck(state);
482 }
483 
484 
485 //////////////////////////////////////////////////
486 // TechTreeWnd::LayoutPanel                     //
487 //////////////////////////////////////////////////
488 /** The window that contains the actual tech panels and dependency arcs. */
489 class TechTreeWnd::LayoutPanel : public GG::Wnd {
490 public:
491     /** \name Structors */ //@{
492     LayoutPanel(GG::X w, GG::Y h);
493     //@}
494 
495     void CompleteConstruction() override;
496 
497     /** \name Accessors */ //@{
498     GG::Pt ClientLowerRight() const override;
499 
500     double                  Scale() const;
501     std::set<std::string>   GetCategoriesShown() const;
502     std::set<TechStatus>    GetTechStatusesShown() const;
503 
504     mutable TechTreeWnd::TechClickSignalType    TechSelectedSignal;
505     mutable TechTreeWnd::TechClickSignalType    TechDoubleClickedSignal;
506     mutable TechTreeWnd::TechSignalType         TechPediaDisplaySignal;
507     //@}
508 
509     //! \name Mutators //@{
510     void Render() override;
511     void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override;
512 
513     void Update();  ///< update indicated \a tech panel or all panels if \a tech_name is an empty string, without redoing layout
514     void Clear();                               ///< remove all tech panels
515     void Reset();                               ///< redo layout, recentre on a tech
516     void SetScale(double scale);
517     void ShowCategory(const std::string& category);
518     void ShowAllCategories();
519     void HideCategory(const std::string& category);
520     void HideAllCategories();
521     void ShowStatus(TechStatus status);
522     void HideStatus(TechStatus status);
523     void SelectTech(const std::string& tech_name);
524     void CenterOnTech(const std::string& tech_name);
525     void DoZoom(const GG::Pt &pt) const;
526     void UndoZoom() const;
527 
528     // Converts between screen coordinates and virtual coordiantes
529     // doing the inverse or same transformation as DoZoom does with gl calls
530     GG::Pt ConvertPtScreenToZoomed(const GG::Pt& pt) const;
531     GG::Pt ConvertPtZoomedToScreen(const GG::Pt& pt) const;
532     //@}
533 
534 private:
535     class TechPanel;
536 
537     class LayoutSurface : public GG::Wnd {
538     public:
LayoutSurface()539         LayoutSurface() :
540             Wnd(GG::X0, GG::Y0, GG::X1, GG::Y1, GG::INTERACTIVE | GG::DRAGABLE)
541         {}
542 
LDrag(const GG::Pt & pt,const GG::Pt & move,GG::Flags<GG::ModKey> mod_keys)543         void LDrag(const GG::Pt& pt, const GG::Pt& move, GG::Flags<GG::ModKey> mod_keys) override
544         { DraggedSignal(move); }
545 
LButtonDown(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)546         void LButtonDown(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override
547         { ButtonDownSignal(pt); }
548 
LButtonUp(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)549         void LButtonUp(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override
550         { ButtonUpSignal(pt); }
551 
MouseWheel(const GG::Pt & pt,int move,GG::Flags<GG::ModKey> mod_keys)552         void MouseWheel(const GG::Pt& pt, int move, GG::Flags<GG::ModKey> mod_keys) override
553         { ZoomedSignal(move); }
554 
555         mutable boost::signals2::signal<void (int)>           ZoomedSignal;
556         mutable boost::signals2::signal<void (const GG::Pt&)> DraggedSignal;
557         mutable boost::signals2::signal<void (const GG::Pt&)> ButtonDownSignal;
558         mutable boost::signals2::signal<void (const GG::Pt&)> ButtonUpSignal;
559     };
560 
561     void Layout(bool keep_position);    // lays out tech panels
562 
563     void DoLayout();    // arranges child controls (scrolls, buttons) to account for window size
564 
565     void ScrolledSlot(int, int, int, int);
566 
567     void TreeDraggedSlot(const GG::Pt& move);
568     void TreeDragBegin(const GG::Pt& move);
569     void TreeDragEnd(const GG::Pt& move);
570     void TreeZoomedSlot(int move);
571     bool TreeZoomInKeyboard();
572     bool TreeZoomOutKeyboard();
573     void ConnectKeyboardAcceleratorSignals();
574 
575     double                  m_scale = INITIAL_SCALE;
576     std::set<std::string>   m_categories_shown;
577     std::set<TechStatus>    m_tech_statuses_shown;
578     std::string             m_selected_tech_name;
579     std::string             m_browsed_tech_name;
580     TechTreeLayout          m_graph;
581 
582     std::map<std::string, std::shared_ptr<TechPanel>> m_techs;
583     TechTreeArcs                    m_dependency_arcs;
584 
585     std::shared_ptr<LayoutSurface>  m_layout_surface;
586     std::shared_ptr<GG::Scroll>     m_vscroll;
587     std::shared_ptr<GG::Scroll>     m_hscroll;
588     double                          m_scroll_position_x = 0.0;      //actual scroll position
589     double                          m_scroll_position_y = 0.0;
590     double                          m_drag_scroll_position_x = 0.0; //position when drag started
591     double                          m_drag_scroll_position_y = 0.0;
592     std::shared_ptr<GG::Button>     m_zoom_in_button;
593     std::shared_ptr<GG::Button>     m_zoom_out_button;
594 };
595 
596 
597 //////////////////////////////////////////////////
598 // TechTreeWnd::LayoutPanel::TechPanel          //
599 //////////////////////////////////////////////////
600 /** Represents a single tech in the LayoutPanel. */
601 class TechTreeWnd::LayoutPanel::TechPanel : public GG::Wnd {
602 public:
603     TechPanel(const std::string& tech_name, const TechTreeWnd::LayoutPanel* panel);
604     virtual         ~TechPanel();
605     void CompleteConstruction() override;
606 
607     bool InWindow(const GG::Pt& pt) const override;
608 
609     /** Update layout and format only if required.*/
610     void PreRender() override;
611 
612     void Render() override;
613 
LDrag(const GG::Pt & pt,const GG::Pt & move,GG::Flags<GG::ModKey> mod_keys)614     void LDrag(const GG::Pt& pt, const GG::Pt& move, GG::Flags<GG::ModKey> mod_keys) override
615     { ForwardEventToParent(); }
616 
LButtonDown(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)617     void LButtonDown(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override
618     { ForwardEventToParent(); }
619 
LButtonUp(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)620     void LButtonUp(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override
621     { ForwardEventToParent(); }
622 
623     void LClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override;
624 
625     void RClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override;
626 
627     void LDoubleClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override;
628 
629     void MouseEnter(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) override;
630 
631     void MouseLeave() override;
632 
MouseWheel(const GG::Pt & pt,int move,GG::Flags<GG::ModKey> mod_keys)633     void MouseWheel(const GG::Pt& pt, int move, GG::Flags<GG::ModKey> mod_keys) override
634     { ForwardEventToParent(); }
635 
636     void            Update();
637     void            Select(bool select);
638     int             FontSize() const;
639 
640     mutable TechTreeWnd::TechClickSignalType    TechLeftClickedSignal;
641     mutable TechTreeWnd::TechClickSignalType    TechDoubleClickedSignal;
642     mutable TechTreeWnd::TechSignalType         TechPediaDisplaySignal;
643 
644 private:
645     void            InitBuffers();
646 
647     GG::GL2DVertexBuffer            m_border_buffer;
648     GG::GL2DVertexBuffer            m_eta_border_buffer;
649     GG::GL2DVertexBuffer            m_enqueued_indicator_buffer;
650 
651     const std::string&              m_tech_name;
652     std::string                     m_name_text;
653     std::string                     m_cost_and_duration_text;
654     std::string                     m_eta_text;
655     const TechTreeWnd::LayoutPanel* m_layout_panel;
656     std::shared_ptr<GG::StaticGraphic>              m_icon;
657     std::vector<std::shared_ptr<GG::StaticGraphic>> m_unlock_icons;
658     std::shared_ptr<GG::TextControl>                m_name_label;
659     std::shared_ptr<GG::TextControl>                m_cost_and_duration_label;
660     std::shared_ptr<GG::TextControl>                m_eta_label;
661     GG::Clr                         m_colour;
662     TechStatus                      m_status;
663     bool                            m_browse_highlight;
664     bool                            m_selected;
665     int                             m_eta;
666     bool                            m_enqueued;
667 };
668 
TechPanel(const std::string & tech_name,const LayoutPanel * panel)669 TechTreeWnd::LayoutPanel::TechPanel::TechPanel(const std::string& tech_name, const LayoutPanel* panel) :
670     GG::Wnd(GG::X0, GG::Y0, TechPanelWidth(), TechPanelHeight(), GG::INTERACTIVE),
671     m_tech_name(tech_name),
672     m_layout_panel(panel),
673     m_colour(GG::CLR_GRAY),
674     m_status(TS_RESEARCHABLE),
675     m_browse_highlight(false),
676     m_selected(false),
677     m_eta(-1),
678     m_enqueued(false)
679 {
680     const int GRAPHIC_SIZE = Value(TechPanelHeight());
681     m_icon = GG::Wnd::Create<GG::StaticGraphic>(ClientUI::TechIcon(m_tech_name), GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
682     m_icon->Resize(GG::Pt(GG::X(GRAPHIC_SIZE), GG::Y(GRAPHIC_SIZE)));
683 
684     m_name_label = GG::Wnd::Create<GG::TextControl>(GG::X0, GG::Y0, GG::X1, GG::Y1, "", ClientUI::GetFont(FontSize()), ClientUI::TextColor(), GG::FORMAT_WORDBREAK | GG::FORMAT_VCENTER | GG::FORMAT_LEFT);
685     m_cost_and_duration_label = GG::Wnd::Create<GG::TextControl>(GG::X0, GG::Y0, GG::X1, GG::Y1, "", ClientUI::GetFont(FontSize()), ClientUI::TextColor(), GG::FORMAT_VCENTER | GG::FORMAT_RIGHT);
686     m_eta_label = GG::Wnd::Create<GG::TextControl>(GG::X0, GG::Y0, GG::X1, GG::Y1, "", ClientUI::GetFont(FontSize()),ClientUI::TextColor());
687 
688     // intentionally not attaching as child; TechPanel::Render the child Render() function instead.
689 
690     SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
691 }
692 
CompleteConstruction()693 void TechTreeWnd::LayoutPanel::TechPanel::CompleteConstruction() {
694     GG::Wnd::CompleteConstruction();
695     SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
696     Update();
697 }
698 
~TechPanel()699 TechTreeWnd::LayoutPanel::TechPanel::~TechPanel()
700 {}
701 
FontSize() const702 int TechTreeWnd::LayoutPanel::TechPanel::FontSize() const
703 { return ClientUI::Pts() * 3 / 2; }
704 
InWindow(const GG::Pt & pt) const705 bool TechTreeWnd::LayoutPanel::TechPanel::InWindow(const GG::Pt& pt) const {
706     const GG::Pt p = m_layout_panel->ConvertPtScreenToZoomed(pt) - UpperLeft();
707     if (m_icon->InWindow(p))
708         return true;
709     return GG::Pt(GG::X0, GG::Y0) <= p && p < GG::Pt(TechPanelWidth(), TechPanelHeight());
710 }
711 
PreRender()712 void TechTreeWnd::LayoutPanel::TechPanel::PreRender() {
713     GG::Wnd::PreRender();
714 
715     const int PAD = 8;
716     GG::X text_left(GG::X(Value(TechPanelHeight())) + PAD);
717     GG::Y text_top(0);
718     GG::X text_width(TechPanelWidth() - text_left);
719     GG::Y text_height(TechPanelHeight()/2);
720 
721     GG::Pt ul = GG::Pt(text_left, text_top);
722     GG::Pt lr = ul + GG::Pt(text_width + PAD, TechPanelHeight());
723 
724     // size of tech name text
725     int font_pts = static_cast<int>(FontSize() * m_layout_panel->Scale() + 0.5);
726 
727     if (font_pts > 6) {
728         if (font_pts < 10) {
729             m_name_label->SetText(m_name_text);
730             m_cost_and_duration_label->SetText(m_cost_and_duration_text);
731         } else {
732             m_name_label->SetText("<s>" + m_name_text + "</s>");
733             m_cost_and_duration_label->SetText("<s>" + m_cost_and_duration_text + "</s>");
734         }
735 
736         GG::Pt text_ul(text_left + PAD/2, text_top);
737         GG::Pt text_size(text_width + PAD, m_unlock_icons.empty() ? text_height*2 : text_height - PAD/2);
738         m_name_label->SizeMove(text_ul, text_ul + text_size);
739 
740         // show cost and duration for unresearched techs
741         const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
742         if (empire) {
743             if (empire->TechResearched(m_tech_name))
744                 m_cost_and_duration_label->Hide();
745             else {
746                 GG::Pt text_ll(text_left + PAD / 2, TechPanelHeight() - font_pts - PAD);
747                 text_size = GG::Pt(text_width + PAD / 2, GG::Y(font_pts));
748                 m_cost_and_duration_label->SizeMove(text_ll, text_ll + text_size);
749                 m_cost_and_duration_label->Show();
750             }
751         }
752 
753         // ETA background and text
754         if (m_eta != -1 && font_pts > 10) {
755             GG::Pt panel_size = lr - ul;
756             GG::Pt eta_ul = ul + GG::Pt(panel_size.x * 3 / 4, panel_size.y * 3 / 4) - GG::Pt(GG::X(2), GG::Y(2));
757             GG::Pt eta_lr = eta_ul + GG::Pt(panel_size.x / 2, panel_size.y / 2) + GG::Pt(GG::X(2), GG::Y(2));
758 
759             m_eta_label->SizeMove(eta_ul, eta_lr);
760         }
761     }
762 }
763 
Render()764 void TechTreeWnd::LayoutPanel::TechPanel::Render() {
765     const int PAD = 8;
766     GG::X text_left(GG::X(Value(TechPanelHeight())) + PAD);
767     GG::Y text_top(0);
768     GG::X text_width(TechPanelWidth() - text_left);
769     GG::Y text_height(TechPanelHeight()/2);
770 
771     GG::Pt ul = GG::Pt(text_left, text_top);
772     GG::Pt lr = ul + GG::Pt(text_width + PAD, TechPanelHeight());
773     GG::Clr border_colour = GG::CLR_WHITE;
774 
775     m_layout_panel->DoZoom(UpperLeft());
776 
777     glDisable(GL_TEXTURE_2D);
778     glEnable(GL_LINE_SMOOTH);
779     glLineWidth(2.0);
780 
781     // size of tech name text
782     int font_pts = static_cast<int>(FontSize() * m_layout_panel->Scale() + 0.5);
783 
784     // cancel out dependency line under tech icon
785     glColor(ClientUI::CtrlColor());
786     PartlyRoundedRect(m_icon->UpperLeft(), m_icon->LowerRight(), PAD, true, true, true, true, true);
787 
788     // Render text part of tech panel, but only if zoomed in so the text is legible
789     if (font_pts > 6) {
790         // black out dependency lines under panel
791         glColor(GG::CLR_BLACK);
792         PartlyRoundedRect(ul, lr + GG::Pt(GG::X(4), GG::Y0), PAD, true, true, true, true, true);
793 
794         // background of panel
795         glColor(m_colour);
796         PartlyRoundedRect(ul, lr + GG::Pt(GG::X(4), GG::Y0), PAD, true, true, true, true, true);
797 
798         // panel border
799         if (m_browse_highlight) {
800             glColor(border_colour);
801             PartlyRoundedRect(ul, lr + GG::Pt(GG::X(4), GG::Y0), PAD, true, true, true, true, false);
802         }
803         else if (m_status == TS_COMPLETE || m_status == TS_RESEARCHABLE) {
804             border_colour = m_colour;
805             border_colour.a = 255;
806             glColor(border_colour);
807             PartlyRoundedRect(ul, lr + GG::Pt(GG::X(4), GG::Y0), PAD, true, true, true, true, false);
808         }
809         else {
810             border_colour = m_colour;
811             border_colour.a = 127;
812             // don't render border
813         }
814 
815         // render tech panel text; for small font sizes, remove shadow
816         glEnable(GL_TEXTURE_2D);
817 
818         GG::Pt text_ul(text_left + PAD/2, text_top);
819         GG::Pt text_size(text_width + PAD, m_unlock_icons.empty() ? text_height*2 : text_height - PAD/2);
820         m_name_label->SizeMove(text_ul, text_ul + text_size);
821 
822         /// Need to render children too
823         GG::GUI::GetGUI()->RenderWindow(m_name_label);
824         GG::GUI::GetGUI()->RenderWindow(m_cost_and_duration_label);
825 
826         // box around whole panel to indicate enqueue
827         if (m_enqueued) {
828             glColor(GG::CLR_WHITE);
829             GG::Pt gap = GG::Pt(GG::X(2 * PAD), GG::Y(2 * PAD));
830             GG::Pt enc_ul(-gap);
831             GG::Pt enc_lr(lr + gap);
832             PartlyRoundedRect(enc_ul, enc_lr, PAD + 6, true, true, true, true, false);
833         }
834 
835         // ETA background and text
836         if (m_eta != -1 && font_pts > 10) {
837             GG::Pt panel_size = lr - ul;
838             GG::Pt eta_ul = ul + GG::Pt(panel_size.x * 3 / 4, panel_size.y * 3 / 4) - GG::Pt(GG::X(2), GG::Y(2));
839             GG::Pt eta_lr = eta_ul + GG::Pt(panel_size.x / 2, panel_size.y / 2) + GG::Pt(GG::X(2), GG::Y(2));
840 
841             glColor(GG::CLR_BLACK);
842             CircleArc(eta_ul, eta_lr, 0, 2 * PI, true);
843             glColor(border_colour);
844             CircleArc(eta_ul, eta_lr, 0, 2 * PI, true);
845 
846             glEnable(GL_TEXTURE_2D);
847 
848             /// Need to render text too
849             GG::GUI::GetGUI()->RenderWindow(m_eta_label);
850             glDisable(GL_TEXTURE_2D);
851         }
852 
853     } else {
854         // box only around icon to indicate enqueue (when zoomed far out)
855         if (m_enqueued) {
856             glColor(GG::CLR_WHITE);
857             GG::Pt gap = GG::Pt(GG::X(2 * PAD), GG::Y(2 * PAD));
858             GG::Pt enc_ul(-gap);
859             GG::Pt enc_lr(m_icon->LowerRight() + gap);
860             PartlyRoundedRect(enc_ul, enc_lr, PAD + 6, true, true, true, true, false);
861         }
862     }
863 
864     // selection indicator
865     if (m_selected) {
866         // nothing!
867     }
868 
869     // render tech icon
870     glDisable(GL_LINE_SMOOTH);
871     glEnable(GL_TEXTURE_2D);
872     m_icon->Render();
873 
874     for (auto& icon : m_unlock_icons)
875     { icon->Render(); }
876 
877     m_layout_panel->UndoZoom();
878 }
879 
LClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)880 void TechTreeWnd::LayoutPanel::TechPanel::LClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys) {
881     if (m_layout_panel->m_selected_tech_name != m_tech_name)
882         TechLeftClickedSignal(m_tech_name, mod_keys);
883 }
884 
RClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)885 void TechTreeWnd::LayoutPanel::TechPanel::RClick(const GG::Pt& pt,
886                                                  GG::Flags<GG::ModKey> mod_keys)
887 {
888     auto dclick_action = [this, pt]() { LDoubleClick(pt, GG::Flags<GG::ModKey>()); };
889     auto ctrl_dclick_action = [this, pt]() { LDoubleClick(pt, GG::MOD_KEY_CTRL); };
890     auto pedia_display_action = [this]() { TechPediaDisplaySignal(m_tech_name); };
891 
892     auto popup = GG::Wnd::Create<CUIPopupMenu>(pt.x, pt.y);
893     if (!(m_enqueued) && !(m_status == TS_COMPLETE)) {
894         popup->AddMenuItem(GG::MenuItem(UserString("PRODUCTION_DETAIL_ADD_TO_QUEUE"),   false, false, dclick_action));
895         popup->AddMenuItem(GG::MenuItem(UserString("PRODUCTION_DETAIL_ADD_TO_TOP_OF_QUEUE"), false, false,
896                                         ctrl_dclick_action));
897     }
898 
899     std::string popup_label = boost::io::str(FlexibleFormat(UserString("ENC_LOOKUP")) % UserString(m_tech_name));
900     popup->AddMenuItem(GG::MenuItem(popup_label, false, false, pedia_display_action));
901     popup->Run();
902 }
903 
LDoubleClick(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)904 void TechTreeWnd::LayoutPanel::TechPanel::LDoubleClick(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys)
905 { TechDoubleClickedSignal(m_tech_name, mod_keys); }
906 
MouseEnter(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)907 void TechTreeWnd::LayoutPanel::TechPanel::MouseEnter(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys)
908 { m_browse_highlight = true; }
909 
MouseLeave()910 void TechTreeWnd::LayoutPanel::TechPanel::MouseLeave()
911 { m_browse_highlight = false; }
912 
Select(bool select)913 void TechTreeWnd::LayoutPanel::TechPanel::Select(bool select)
914 { m_selected = select; }
915 
Update()916 void TechTreeWnd::LayoutPanel::TechPanel::Update() {
917     Select(m_layout_panel->m_selected_tech_name == m_tech_name);
918 
919     int client_empire_id = HumanClientApp::GetApp()->EmpireID();
920 
921     if (const Empire* empire = GetEmpire(client_empire_id)) {
922         m_status = empire->GetTechStatus(m_tech_name);
923         m_enqueued = empire->GetResearchQueue().InQueue(m_tech_name);
924 
925         const ResearchQueue& queue = empire->GetResearchQueue();
926         auto queue_it = queue.find(m_tech_name);
927         if (queue_it != queue.end()) {
928             m_eta = queue_it->turns_left;
929             if (m_eta != -1)
930                 m_eta_text = std::to_string(m_eta);
931             else
932                 m_eta_text.clear();
933         }
934     }
935 
936     GG::Clr icon_colour = GG::CLR_WHITE;
937     if (const Tech* tech = GetTech(m_tech_name)) {
938         m_colour = ClientUI::CategoryColor(tech->Category());
939         icon_colour = m_colour;
940 
941         if (m_status == TS_UNRESEARCHABLE || m_status == TS_HAS_RESEARCHED_PREREQ) {
942             icon_colour = GG::CLR_GRAY;
943             m_colour.a = 64;
944         } else if (m_status == TS_RESEARCHABLE) {
945             icon_colour = GG::CLR_GRAY;
946             m_colour.a = 128;
947         } else {
948             m_colour.a = 255;
949         }
950 
951         if (m_unlock_icons.empty()) {
952             const int PAD = 8;
953             GG::X icon_left(GG::X(Value(TechPanelHeight())) + PAD*3/2);
954             GG::Y icon_height = TechPanelHeight()/2;
955             GG::X icon_width = GG::X(Value(icon_height));
956             GG::Y icon_bottom = TechPanelHeight() - PAD/2;
957             GG::Y icon_top = icon_bottom - icon_height;
958 
959             // add icons for unlocked items
960             for (const UnlockableItem& item : tech->UnlockedItems()) {
961                 std::shared_ptr<GG::Texture> texture;
962                 switch (item.type) {
963                 case UIT_BUILDING:  texture = ClientUI::BuildingIcon(item.name);    break;
964                 case UIT_SHIP_PART: texture = ClientUI::PartIcon(item.name);        break;
965                 case UIT_SHIP_HULL: texture = ClientUI::HullIcon(item.name);        break;
966                 default:    break;
967                 }
968 
969                 if (texture) {
970                     auto graphic = GG::Wnd::Create<GG::StaticGraphic>(texture, GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
971                     m_unlock_icons.push_back(graphic);
972                     graphic->SizeMove(GG::Pt(icon_left, icon_top), GG::Pt(icon_left + icon_width, icon_top + icon_height));
973                     icon_left += icon_width + PAD;
974                 }
975             }
976             // add icons for modified part meters / specials
977             std::set<MeterType> meters_affected;
978             std::set<std::string> specials_affected;
979             std::set<std::string> parts_whose_meters_are_affected;
980             for (auto& effects_group : tech->Effects()) {
981                 for (Effect::Effect* effect : effects_group->EffectsList()) {
982                     if (const Effect::SetMeter* set_meter_effect = dynamic_cast<const Effect::SetMeter*>(effect)) {
983                         meters_affected.insert(set_meter_effect->GetMeterType());
984 
985                     } else if (const Effect::SetShipPartMeter* set_ship_part_meter_effect = dynamic_cast<const Effect::SetShipPartMeter*>(effect)) {
986                         const ValueRef::ValueRef<std::string>* part_name = set_ship_part_meter_effect->GetPartName();
987                         if (part_name && part_name->ConstantExpr())
988                             parts_whose_meters_are_affected.insert(part_name->Eval());
989 
990                     } else if (const Effect::AddSpecial* add_special_effect = dynamic_cast<const Effect::AddSpecial*>(effect)) {
991                         const ValueRef::ValueRef<std::string>* special_name = add_special_effect->GetSpecialName();
992                         if (special_name && special_name->ConstantExpr())
993                             specials_affected.insert(special_name->Eval());
994                     }
995                 }
996             }
997 
998             for (const std::string& part_name : parts_whose_meters_are_affected) {
999                 std::shared_ptr<GG::Texture> texture = ClientUI::PartIcon(part_name);
1000                 if (texture) {
1001                     auto graphic = GG::Wnd::Create<GG::StaticGraphic>(texture, GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
1002                     m_unlock_icons.push_back(graphic);
1003                     graphic->SizeMove(GG::Pt(icon_left, icon_top), GG::Pt(icon_left + icon_width, icon_top + icon_height));
1004                     icon_left += icon_width + PAD;
1005                 }
1006             }
1007             for (MeterType meter_type : meters_affected) {
1008                 std::shared_ptr<GG::Texture> texture = ClientUI::MeterIcon(meter_type);
1009                 if (texture) {
1010                     auto graphic = GG::Wnd::Create<GG::StaticGraphic>(texture, GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
1011                     m_unlock_icons.push_back(graphic);
1012                     graphic->SizeMove(GG::Pt(icon_left, icon_top), GG::Pt(icon_left + icon_width, icon_top + icon_height));
1013                     icon_left += icon_width + PAD;
1014                 }
1015             }
1016 
1017             for (const std::string& special_name : specials_affected) {
1018                 std::shared_ptr<GG::Texture> texture = ClientUI::SpecialIcon(special_name);
1019                 if (texture) {
1020                     auto graphic = GG::Wnd::Create<GG::StaticGraphic>(texture, GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
1021                     m_unlock_icons.push_back(graphic);
1022                     graphic->SizeMove(GG::Pt(icon_left, icon_top), GG::Pt(icon_left + icon_width, icon_top + icon_height));
1023                     icon_left += icon_width + PAD;
1024                 }
1025             }
1026         }
1027     }
1028     m_icon->SetColor(icon_colour);
1029 
1030     m_name_text = UserString(m_tech_name);
1031     m_name_label->SetText("<s>" + m_name_text + "</s>");
1032 
1033     if (const Tech* tech = GetTech(m_tech_name))
1034         m_cost_and_duration_text = boost::io::str(FlexibleFormat(UserString("TECH_TOTAL_COST_ALT_STR"))
1035                                                   % DoubleToString(tech->ResearchCost(client_empire_id), 1, false)
1036                                                   % boost::lexical_cast<int>(tech->ResearchTime(client_empire_id)));
1037     m_cost_and_duration_label->SetText("<s>" + m_cost_and_duration_text + "<s>");
1038 
1039     m_eta_label->SetText("<s>" + m_eta_text + "</s>");
1040 
1041     ClearBrowseInfoWnd();
1042     SetBrowseInfoWnd(TechRowBrowseWnd(m_tech_name, client_empire_id));
1043 
1044     RequirePreRender();
1045 }
1046 
1047 
1048 //////////////////////////////////////////////////
1049 // TechTreeWnd::LayoutPanel                     //
1050 //////////////////////////////////////////////////
LayoutPanel(GG::X w,GG::Y h)1051 TechTreeWnd::LayoutPanel::LayoutPanel(GG::X w, GG::Y h) :
1052     GG::Wnd(GG::X0, GG::Y0, w, h, GG::INTERACTIVE)
1053 {}
1054 
CompleteConstruction()1055 void TechTreeWnd::LayoutPanel::CompleteConstruction() {
1056     GG::Wnd::CompleteConstruction();
1057 
1058     SetChildClippingMode(ClipToClient);
1059 
1060     m_scale = std::pow(ZOOM_STEP_SIZE, GetOptionsDB().Get<double>("ui.research.tree.zoom.scale")); // (LATHANDA) Initialise Fullzoom and do real zooming using GL. TODO: Check best size
1061 
1062     m_layout_surface = GG::Wnd::Create<LayoutSurface>();
1063 
1064     m_vscroll = GG::Wnd::Create<CUIScroll>(GG::VERTICAL);
1065     m_hscroll = GG::Wnd::Create<CUIScroll>(GG::HORIZONTAL);
1066 
1067     m_zoom_in_button = Wnd::Create<CUIButton>("+");
1068     m_zoom_in_button->SetColor(ClientUI::WndColor());
1069     m_zoom_out_button = Wnd::Create<CUIButton>("-");
1070     m_zoom_out_button->SetColor(ClientUI::WndColor());
1071 
1072     DoLayout();
1073 
1074     AttachChild(m_layout_surface);
1075     AttachChild(m_vscroll);
1076     AttachChild(m_hscroll);
1077     AttachChild(m_zoom_in_button);
1078     AttachChild(m_zoom_out_button);
1079 
1080 #if BOOST_VERSION >= 106000
1081     using boost::placeholders::_1;
1082     using boost::placeholders::_2;
1083     using boost::placeholders::_3;
1084     using boost::placeholders::_4;
1085 #endif
1086 
1087     m_layout_surface->DraggedSignal.connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeDraggedSlot, this, _1));
1088     m_layout_surface->ButtonUpSignal.connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeDragEnd, this, _1));
1089     m_layout_surface->ButtonDownSignal.connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeDragBegin, this, _1));
1090     m_layout_surface->ZoomedSignal.connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomedSlot, this, _1));
1091     m_vscroll->ScrolledSignal.connect(boost::bind(&TechTreeWnd::LayoutPanel::ScrolledSlot, this, _1, _2, _3, _4));
1092     m_hscroll->ScrolledSignal.connect(boost::bind(&TechTreeWnd::LayoutPanel::ScrolledSlot, this, _1, _2, _3, _4));
1093     m_zoom_in_button->LeftClickedSignal.connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomedSlot, this, 1));
1094     m_zoom_out_button->LeftClickedSignal.connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomedSlot, this, -1));
1095 
1096     ConnectKeyboardAcceleratorSignals();
1097 
1098     // show all categories...
1099     m_categories_shown.clear();
1100     for (const std::string& category_name : GetTechManager().CategoryNames())
1101         m_categories_shown.insert(category_name);
1102 
1103     // show statuses
1104     m_tech_statuses_shown.clear();
1105     //m_tech_statuses_shown.insert(TS_UNRESEARCHABLE);
1106     m_tech_statuses_shown.insert(TS_RESEARCHABLE);
1107     m_tech_statuses_shown.insert(TS_COMPLETE);
1108 }
1109 
ConnectKeyboardAcceleratorSignals()1110 void TechTreeWnd::LayoutPanel::ConnectKeyboardAcceleratorSignals() {
1111     HotkeyManager* hkm = HotkeyManager::GetManager();
1112 
1113     hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomInKeyboard, this), "ui.zoom.in",
1114                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
1115     hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomInKeyboard, this), "ui.zoom.in.alt",
1116                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
1117     hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomOutKeyboard, this), "ui.zoom.out",
1118                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
1119     hkm->Connect(boost::bind(&TechTreeWnd::LayoutPanel::TreeZoomOutKeyboard, this), "ui.zoom.out.alt",
1120                  AndCondition({VisibleWindowCondition(this), NoModalWndsOpenCondition}));
1121 
1122     hkm->RebuildShortcuts();
1123 }
1124 
ClientLowerRight() const1125 GG::Pt TechTreeWnd::LayoutPanel::ClientLowerRight() const
1126 { return LowerRight() - GG::Pt(GG::X(ClientUI::ScrollWidth()), GG::Y(ClientUI::ScrollWidth())); }
1127 
GetCategoriesShown() const1128 std::set<std::string> TechTreeWnd::LayoutPanel::GetCategoriesShown() const
1129 { return m_categories_shown; }
1130 
Scale() const1131 double TechTreeWnd::LayoutPanel::Scale() const
1132 { return m_scale; }
1133 
GetTechStatusesShown() const1134 std::set<TechStatus> TechTreeWnd::LayoutPanel::GetTechStatusesShown() const
1135 { return m_tech_statuses_shown; }
1136 
Render()1137 void TechTreeWnd::LayoutPanel::Render() {
1138     GG::FlatRectangle(UpperLeft(), LowerRight(), ClientUI::CtrlColor(), GG::CLR_ZERO);
1139 
1140     BeginClipping();
1141 
1142     // render dependency arcs
1143     DoZoom(ClientUpperLeft());
1144 
1145     m_dependency_arcs.Render(m_scale);
1146 
1147     EndClipping();
1148 
1149     UndoZoom();
1150     GG::GUI::RenderWindow(m_vscroll);
1151     GG::GUI::RenderWindow(m_hscroll);
1152 }
1153 
SizeMove(const GG::Pt & ul,const GG::Pt & lr)1154 void TechTreeWnd::LayoutPanel::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
1155     const GG::Pt old_size = Size();
1156     GG::Wnd::SizeMove(ul, lr);
1157     if (old_size != Size())
1158         DoLayout();
1159 }
1160 
DoLayout()1161 void TechTreeWnd::LayoutPanel::DoLayout() {
1162     const int SCRLWDTH = ClientUI::ScrollWidth();
1163 
1164     GG::Pt vscroll_ul = GG::Pt(Width() - SCRLWDTH, GG::Y0);
1165     GG::Pt vscroll_lr = GG::Pt(Width(), Height() - SCRLWDTH);
1166     m_vscroll->SizeMove(vscroll_ul, vscroll_lr);
1167 
1168     GG::Pt hscroll_ul = GG::Pt(GG::X0, Height() - SCRLWDTH);
1169     GG::Pt hscroll_lr = GG::Pt(Width() - SCRLWDTH, Height());
1170     m_hscroll->SizeMove(hscroll_ul, hscroll_lr);
1171 
1172     const GG::X ZBSIZE(ClientUI::ScrollWidth() * 2);
1173     const int ZBOFFSET = ClientUI::ScrollWidth() / 2;
1174 
1175     GG::Pt button_ul = GG::Pt(Width() - ZBSIZE - ZBOFFSET - SCRLWDTH, GG::Y(ZBOFFSET));
1176     m_zoom_in_button->MoveTo(button_ul);
1177     m_zoom_in_button->Resize(GG::Pt(ZBSIZE, m_zoom_in_button->MinUsableSize().y));
1178     button_ul += GG::Pt(GG::X0, m_zoom_in_button->Height() + ZBOFFSET);
1179     m_zoom_out_button->MoveTo(button_ul);
1180     m_zoom_out_button->Resize(GG::Pt(ZBSIZE, m_zoom_out_button->MinUsableSize().y));
1181 }
1182 
Update()1183 void TechTreeWnd::LayoutPanel::Update()
1184 { Layout(true); }
1185 
Clear()1186 void TechTreeWnd::LayoutPanel::Clear() {
1187     m_vscroll->ScrollTo(0);
1188     m_hscroll->ScrollTo(0);
1189     m_vscroll->SizeScroll(0, 1, 1, 1);
1190     m_hscroll->SizeScroll(0, 1, 1, 1);
1191     GG::SignalScroll(*m_vscroll, true);
1192     GG::SignalScroll(*m_hscroll, true);
1193 
1194     // delete all panels
1195     for (const auto& tech_panel: m_techs)
1196         m_layout_surface->DetachChild(tech_panel.second);
1197     m_techs.clear();
1198     m_graph.Clear();
1199 
1200     m_dependency_arcs.Reset();
1201 
1202     m_selected_tech_name.clear();
1203 }
1204 
Reset()1205 void TechTreeWnd::LayoutPanel::Reset() {
1206     // regenerate graph of panels and dependency lines
1207     Layout(false);
1208 }
1209 
SetScale(double scale)1210 void TechTreeWnd::LayoutPanel::SetScale(double scale) {
1211     if (scale < MIN_SCALE)
1212         scale = MIN_SCALE;
1213     if (MAX_SCALE < scale)
1214         scale = MAX_SCALE;
1215     m_scale = scale;
1216     GetOptionsDB().Set<double>("ui.research.tree.zoom.scale", std::floor(0.1 + (std::log(m_scale) / std::log(ZOOM_STEP_SIZE))));
1217 
1218     for (auto& entry : m_techs)
1219         entry.second->RequirePreRender();
1220 }
1221 
ShowCategory(const std::string & category)1222 void TechTreeWnd::LayoutPanel::ShowCategory(const std::string& category) {
1223     if (!m_categories_shown.count(category)) {
1224         m_categories_shown.insert(category);
1225         Layout(true);
1226     }
1227 }
1228 
ShowAllCategories()1229 void TechTreeWnd::LayoutPanel::ShowAllCategories() {
1230     const std::vector<std::string> all_cats = GetTechManager().CategoryNames();
1231     if (all_cats.size() == m_categories_shown.size())
1232         return;
1233     for (const std::string& category_name : all_cats)
1234         m_categories_shown.insert(category_name);
1235     Layout(true);
1236 }
1237 
HideCategory(const std::string & category)1238 void TechTreeWnd::LayoutPanel::HideCategory(const std::string& category) {
1239     std::set<std::string>::iterator it = m_categories_shown.find(category);
1240     if (it != m_categories_shown.end()) {
1241         m_categories_shown.erase(it);
1242         Layout(true);
1243     }
1244 }
1245 
HideAllCategories()1246 void TechTreeWnd::LayoutPanel::HideAllCategories() {
1247     if (m_categories_shown.empty())
1248         return;
1249     m_categories_shown.clear();
1250     Layout(true);
1251 }
1252 
ShowStatus(TechStatus status)1253 void TechTreeWnd::LayoutPanel::ShowStatus(TechStatus status) {
1254     if (!m_tech_statuses_shown.count(status)) {
1255         m_tech_statuses_shown.insert(status);
1256         Layout(true);
1257     }
1258 }
1259 
HideStatus(TechStatus status)1260 void TechTreeWnd::LayoutPanel::HideStatus(TechStatus status) {
1261     std::set<TechStatus>::iterator it = m_tech_statuses_shown.find(status);
1262     if (it != m_tech_statuses_shown.end()) {
1263         m_tech_statuses_shown.erase(it);
1264         Layout(true);
1265     }
1266 }
1267 
CenterOnTech(const std::string & tech_name)1268 void TechTreeWnd::LayoutPanel::CenterOnTech(const std::string& tech_name) {
1269     const auto& it = m_techs.find(tech_name);
1270     if (it == m_techs.end()) {
1271         DebugLogger() << "TechTreeWnd::LayoutPanel::CenterOnTech couldn't centre on " << tech_name
1272                                << " due to lack of such a tech panel";
1273         return;
1274     }
1275 
1276     auto& tech_panel = it->second;
1277     GG::Pt center_point = tech_panel->UpperLeft();
1278     m_hscroll->ScrollTo(Value(center_point.x));
1279     GG::SignalScroll(*m_hscroll, true);
1280     m_vscroll->ScrollTo(Value(center_point.y));
1281     GG::SignalScroll(*m_vscroll, true);
1282 }
1283 
DoZoom(const GG::Pt & pt) const1284 void TechTreeWnd::LayoutPanel::DoZoom(const GG::Pt& pt) const {
1285     glPushMatrix();
1286     //center to panel
1287     glTranslated(Value(Width()/2.0), Value(Height()/2.0), 0);
1288     //zoom
1289     glScaled(m_scale, m_scale, 1);
1290     //translate to actual scroll position
1291     glTranslated(-m_scroll_position_x, -m_scroll_position_y, 0);
1292     glTranslated(Value(pt.x), Value(pt.y), 0);
1293 }
1294 
UndoZoom() const1295 void TechTreeWnd::LayoutPanel::UndoZoom() const
1296 { glPopMatrix(); }
1297 
ConvertPtScreenToZoomed(const GG::Pt & pt) const1298 GG::Pt TechTreeWnd::LayoutPanel::ConvertPtScreenToZoomed(const GG::Pt& pt) const {
1299     double x = Value(pt.x);
1300     double y = Value(pt.y);
1301     x -= Value(Width()/2.0);
1302     y -= Value(Height()/2.0);
1303     x /= m_scale;
1304     y /= m_scale;
1305     x += m_scroll_position_x;
1306     y += m_scroll_position_y;
1307     return GG::Pt(GG::X(static_cast<int>(x)), GG::Y(static_cast<int>(y)));
1308 }
1309 
ConvertPtZoomedToScreen(const GG::Pt & pt) const1310 GG::Pt TechTreeWnd::LayoutPanel::ConvertPtZoomedToScreen(const GG::Pt& pt) const {
1311     double x = Value(pt.x);
1312     double y = Value(pt.y);
1313     x -= m_scroll_position_x;
1314     y -= m_scroll_position_y;
1315     x *= m_scale;
1316     y *= m_scale;
1317     x += Value(Width()/2.0);
1318     y += Value(Height()/2.0);
1319     return GG::Pt(GG::X(static_cast<int>(x)), GG::Y(static_cast<int>(y)));
1320 }
1321 
Layout(bool keep_position)1322 void TechTreeWnd::LayoutPanel::Layout(bool keep_position) {
1323     const GG::X TECH_PANEL_MARGIN_X(ClientUI::Pts()*16);
1324     const GG::Y TECH_PANEL_MARGIN_Y(ClientUI::Pts()*16 + 100);
1325     const double RANK_SEP = Value(TechPanelWidth()) * GetOptionsDB().Get<double>("ui.research.tree.spacing.horizontal");
1326     const double NODE_SEP = Value(TechPanelHeight()) * GetOptionsDB().Get<double>("ui.research.tree.spacing.vertical");
1327     const double WIDTH = Value(TechPanelWidth());
1328     const double HEIGHT = Value(TechPanelHeight());
1329     const double X_MARGIN(12);
1330 
1331     // view state initial data
1332     int initial_hscroll_pos = m_hscroll->PosnRange().first;
1333     int initial_vscroll_pos = m_vscroll->PosnRange().first;
1334     double initial_hscroll_page_size = m_hscroll->PageSize();
1335     double initial_vscroll_page_size = m_vscroll->PageSize();
1336     const std::string selected_tech = m_selected_tech_name;
1337 
1338     // cleanup old data for new layout
1339     Clear();
1340 
1341     DebugLogger() << "Tech Tree Layout Preparing Tech Data";
1342 
1343     // create a node for every tech
1344     TechManager& manager = GetTechManager();
1345     for (const auto& tech : manager) {
1346         if (!tech) continue;
1347         const std::string& tech_name = tech->Name();
1348         if (!TechVisible(tech_name, m_categories_shown, m_tech_statuses_shown)) continue;
1349         m_techs[tech_name] = GG::Wnd::Create<TechPanel>(tech_name, this);
1350         m_graph.AddNode(tech_name, m_techs[tech_name]->Width(), m_techs[tech_name]->Height());
1351     }
1352 
1353     // create an edge for every prerequisite
1354     for (const auto& tech : manager) {
1355         if (!tech) continue;
1356         const std::string& tech_name = tech->Name();
1357         if (!TechVisible(tech_name, m_categories_shown, m_tech_statuses_shown)) continue;
1358         for (const std::string& prereq : tech->Prerequisites()) {
1359             if (!TechVisible(prereq, m_categories_shown, m_tech_statuses_shown)) continue;
1360             m_graph.AddEdge(prereq, tech_name);
1361         }
1362     }
1363 
1364     DebugLogger() << "Tech Tree Layout Doing Graph Layout";
1365 
1366     //calculate layout
1367     m_graph.DoLayout(static_cast<int>(WIDTH + RANK_SEP),
1368                      static_cast<int>(HEIGHT + NODE_SEP),
1369                      static_cast<int>(X_MARGIN));
1370 
1371     DebugLogger() << "Tech Tree Layout Creating Panels";
1372 
1373     std::set<std::string> visible_techs;
1374 
1375     // create new tech panels and new dependency arcs
1376     for (const auto& tech : manager) {
1377         if (!tech) continue;
1378         const std::string& tech_name = tech->Name();
1379         if (!TechVisible(tech_name, m_categories_shown, m_tech_statuses_shown)) continue;
1380         //techpanel
1381         const TechTreeLayout::Node* node = m_graph.GetNode(tech_name);
1382         //move TechPanel
1383         auto& tech_panel = m_techs[tech_name];
1384         tech_panel->MoveTo(GG::Pt(node->GetX(), node->GetY()));
1385         m_layout_surface->AttachChild(tech_panel);
1386 
1387 #if BOOST_VERSION >= 106000
1388         using boost::placeholders::_1;
1389 #endif
1390 
1391         tech_panel->TechLeftClickedSignal.connect(
1392             boost::bind(&TechTreeWnd::LayoutPanel::SelectTech, this, _1));
1393         tech_panel->TechDoubleClickedSignal.connect(TechDoubleClickedSignal);
1394         tech_panel->TechPediaDisplaySignal.connect(TechPediaDisplaySignal);
1395 
1396         visible_techs.insert(tech_name);
1397     }
1398 
1399     m_dependency_arcs.Reset(m_graph, visible_techs);
1400 
1401     // format window
1402     GG::Pt client_sz = ClientSize();
1403     GG::Pt layout_size(client_sz.x + m_graph.GetWidth(), client_sz.y + m_graph.GetHeight());
1404     m_layout_surface->Resize(layout_size);
1405     // format scrollbar
1406     m_vscroll->SizeScroll(0, Value(layout_size.y - 1), std::max(50, Value(std::min(layout_size.y / 10, client_sz.y))), Value(client_sz.y));
1407     m_hscroll->SizeScroll(0, Value(layout_size.x - 1), std::max(50, Value(std::min(layout_size.x / 10, client_sz.x))), Value(client_sz.x));
1408 
1409     DebugLogger() << "Tech Tree Layout Done";
1410 
1411     // restore save data
1412     if (keep_position) {
1413         m_selected_tech_name = selected_tech;
1414         // select clicked on tech
1415         if (m_techs.count(m_selected_tech_name))
1416             m_techs[m_selected_tech_name]->Select(true);
1417         double hscroll_page_size_ratio = m_hscroll->PageSize() / initial_hscroll_page_size;
1418         double vscroll_page_size_ratio = m_vscroll->PageSize() / initial_vscroll_page_size;
1419         m_hscroll->ScrollTo(static_cast<int>(initial_hscroll_pos * hscroll_page_size_ratio));
1420         m_vscroll->ScrollTo(static_cast<int>(initial_vscroll_pos * vscroll_page_size_ratio));
1421         GG::SignalScroll(*m_hscroll, true);
1422         GG::SignalScroll(*m_vscroll, true);
1423     } else {
1424         m_selected_tech_name.clear();
1425         // find a tech to centre view on
1426         for (const auto& tech : manager) {
1427             const std::string& tech_name = tech->Name();
1428             if (TechVisible(tech_name, m_categories_shown, m_tech_statuses_shown)) {
1429                 CenterOnTech(tech_name);
1430                 break;
1431             }
1432         }
1433     }
1434 
1435     // ensure that the scrolls stay on top
1436     MoveChildUp(m_vscroll);
1437     MoveChildUp(m_hscroll);
1438 }
1439 
ScrolledSlot(int,int,int,int)1440 void TechTreeWnd::LayoutPanel::ScrolledSlot(int, int, int, int) {
1441     m_scroll_position_x = m_hscroll->PosnRange().first;
1442     m_scroll_position_y = m_vscroll->PosnRange().first;
1443 }
1444 
SelectTech(const std::string & tech_name)1445 void TechTreeWnd::LayoutPanel::SelectTech(const std::string& tech_name)
1446 {
1447     // deselect previously-selected tech panel
1448     if (m_techs.count(m_selected_tech_name))
1449         m_techs[m_selected_tech_name]->Select(false);
1450     // select clicked on tech
1451     if (m_techs.count(tech_name))
1452         m_techs[tech_name]->Select(true);
1453     m_selected_tech_name = tech_name;
1454     TechSelectedSignal(tech_name, GG::Flags<GG::ModKey>());
1455 }
1456 
TreeDraggedSlot(const GG::Pt & move)1457 void TechTreeWnd::LayoutPanel::TreeDraggedSlot(const GG::Pt& move) {
1458     m_hscroll->ScrollTo(m_drag_scroll_position_x - Value(move.x / m_scale));
1459     m_vscroll->ScrollTo(m_drag_scroll_position_y - Value(move.y / m_scale));
1460     m_scroll_position_x = m_hscroll->PosnRange().first;
1461     m_scroll_position_y = m_vscroll->PosnRange().first;
1462 }
1463 
TreeDragBegin(const GG::Pt & pt)1464 void TechTreeWnd::LayoutPanel::TreeDragBegin(const GG::Pt& pt) {
1465     m_drag_scroll_position_x = m_scroll_position_x;
1466     m_drag_scroll_position_y = m_scroll_position_y;
1467 }
1468 
TreeDragEnd(const GG::Pt & pt)1469 void TechTreeWnd::LayoutPanel::TreeDragEnd(const GG::Pt& pt) {
1470     m_drag_scroll_position_x = m_scroll_position_x;
1471     m_drag_scroll_position_y = m_scroll_position_y;
1472 }
1473 
TreeZoomedSlot(int move)1474 void TechTreeWnd::LayoutPanel::TreeZoomedSlot(int move) {
1475     if (0 < move)
1476         SetScale(m_scale * ZOOM_STEP_SIZE);
1477     else if (move < 0)
1478         SetScale(m_scale / ZOOM_STEP_SIZE);
1479     //std::cout << m_scale << std::endl;
1480 }
1481 
1482 // The bool return value is to re-use the MapWnd::KeyboardZoomIn hotkey
TreeZoomInKeyboard()1483 bool TechTreeWnd::LayoutPanel::TreeZoomInKeyboard() {
1484     TreeZoomedSlot(1);
1485     return true;
1486 }
1487 
TreeZoomOutKeyboard()1488 bool TechTreeWnd::LayoutPanel::TreeZoomOutKeyboard() {
1489     TreeZoomedSlot(-1);
1490     return true;
1491 }
1492 
1493 //////////////////////////////////////////////////
1494 // TechTreeWnd::TechListBox                     //
1495 //////////////////////////////////////////////////
1496 class TechTreeWnd::TechListBox : public CUIListBox {
1497 public:
1498     /** \name Structors */ //@{
1499     TechListBox(GG::X w, GG::Y h);
1500     virtual ~TechListBox();
1501     //@}
1502 
1503     void CompleteConstruction() override;
1504 
1505     /** \name Accessors */ //@{
1506     bool TechRowCmp(const GG::ListBox::Row& lhs, const GG::ListBox::Row& rhs, std::size_t column);
1507     //@}
1508 
1509     //! \name Mutators //@{
1510     void    Reset();
1511     void    Update(bool populate = true);
1512 
1513     void    ShowCategory(const std::string& category);
1514     void    ShowAllCategories();
1515     void    HideCategory(const std::string& category);
1516     void    HideAllCategories();
1517     void    ShowStatus(TechStatus status);
1518     void    HideStatus(TechStatus status);
1519     //@}
1520 
1521     mutable TechClickSignalType TechLeftClickedSignal;  ///< emitted when a technology is single-left-clicked
1522     mutable TechClickSignalType TechDoubleClickedSignal;///< emitted when a technology is double-clicked
1523     mutable TechSignalType      TechPediaDisplaySignal; ///< emitted when requesting a pedia lookup
1524 
1525 private:
1526     class TechRow : public CUIListBox::Row {
1527     public:
1528         TechRow(GG::X w, const std::string& tech_name);
1529 
1530         void CompleteConstruction() override;
1531 
1532         void Render() override;
1533 
GetTech()1534         const std::string&                  GetTech() { return m_tech; }
1535         static std::vector<GG::X>           ColWidths(GG::X total_width);
1536         static std::vector<GG::Alignment>   ColAlignments();
1537         void                                Update();
1538 
1539     private:
1540         std::string m_tech;
1541         GG::Clr m_background_color;
1542         bool m_enqueued;
1543     };
1544 
1545     void Populate(bool update = true);
1546     void TechDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys);
1547     void TechLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys);
1548     void TechRightClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys);
1549     void ToggleSortCol(unsigned int col);
1550 
1551     std::set<std::string>                                       m_categories_shown;
1552     std::set<TechStatus>                                        m_tech_statuses_shown;
1553     std::unordered_map<std::string, std::shared_ptr<TechRow>>   m_tech_row_cache;
1554     std::shared_ptr<GG::ListBox::Row>                           m_header_row;
1555     size_t                                                      m_previous_sort_col;
1556 };
1557 
Render()1558 void TechTreeWnd::TechListBox::TechRow::Render() {
1559     GG::Pt ul = UpperLeft();
1560     GG::Pt lr = LowerRight();
1561     GG::Pt offset{GG::X(3), GG::Y(3)};
1562     if (m_enqueued) {
1563         GG::FlatRectangle(ul, lr, ClientUI::WndColor(), GG::CLR_WHITE, 1);
1564         GG::FlatRoundedRectangle(ul + offset, lr - offset, m_background_color, GG::CLR_WHITE);
1565     } else {
1566         GG::FlatRectangle(ul, lr, m_background_color, GG::CLR_WHITE, 1);
1567     }
1568 }
1569 
ColWidths(GG::X total_width)1570 std::vector<GG::X> TechTreeWnd::TechListBox::TechRow::ColWidths(GG::X total_width) {
1571     GG::X graphic_width(GetOptionsDB().Get<int>("ui.research.list.column.graphic.width"));
1572     GG::X name_width(GetOptionsDB().Get<int>("ui.research.list.column.name.width"));
1573     GG::X cost_width(GetOptionsDB().Get<int>("ui.research.list.column.cost.width"));
1574     GG::X time_width(GetOptionsDB().Get<int>("ui.research.list.column.time.width"));
1575     GG::X category_width(GetOptionsDB().Get<int>("ui.research.list.column.category.width"));
1576 
1577     GG::X cols_width_sum = graphic_width + name_width + cost_width + time_width + category_width;
1578 
1579     GG::X desc_width(std::max(GetOptionsDB().Get<int>("ui.research.list.column.description.width"),
1580                               Value(total_width - cols_width_sum)));
1581 
1582     return {graphic_width, name_width, cost_width, time_width, category_width, desc_width};
1583 }
1584 
ColAlignments()1585 std::vector<GG::Alignment> TechTreeWnd::TechListBox::TechRow::ColAlignments() {
1586     //              graphic,         name,           cost,           time,         category,     description
1587     return {GG::ALIGN_CENTER, GG::ALIGN_LEFT, GG::ALIGN_RIGHT, GG::ALIGN_RIGHT, GG::ALIGN_LEFT, GG::ALIGN_LEFT};
1588 }
1589 
TechRowCmp(const GG::ListBox::Row & lhs,const GG::ListBox::Row & rhs,std::size_t column)1590 bool TechTreeWnd::TechListBox::TechRowCmp(const GG::ListBox::Row& lhs, const GG::ListBox::Row& rhs, std::size_t column) {
1591     bool retval = false;
1592     const std::string lhs_key = boost::trim_copy(lhs.SortKey(column));
1593     const std::string rhs_key = boost::trim_copy(rhs.SortKey(column));
1594 
1595     // When equal, sort by previous sorted column
1596     if ((lhs_key == rhs_key) && (m_previous_sort_col != column)) {
1597         retval = TechRowCmp(lhs, rhs, m_previous_sort_col);
1598     } else {
1599         try {  // attempt compare by int
1600             retval = boost::lexical_cast<int>(lhs_key) < boost::lexical_cast<int>(rhs_key);
1601         } catch (const boost::bad_lexical_cast& e) {
1602             retval = GetLocale("en_US.UTF-8").operator()(lhs_key, rhs_key);
1603         }
1604     }
1605 
1606     return retval;
1607 }
1608 
TechRow(GG::X w,const std::string & tech_name)1609 TechTreeWnd::TechListBox::TechRow::TechRow(GG::X w, const std::string& tech_name) :
1610     CUIListBox::Row(w, GG::Y(ClientUI::Pts() * 2 + 5)),
1611     m_tech(tech_name),
1612     m_background_color(ClientUI::WndColor()),
1613     m_enqueued(false)
1614 { SetDragDropDataType("TechListBox::TechRow"); }
1615 
CompleteConstruction()1616 void TechTreeWnd::TechListBox::TechRow::CompleteConstruction() {
1617 
1618     CUIListBox::Row::CompleteConstruction();
1619 
1620     const Tech* this_row_tech = ::GetTech(m_tech);
1621     if (!this_row_tech)
1622         return;
1623 
1624     std::vector<GG::X> col_widths = ColWidths(Width());
1625     const GG::X GRAPHIC_WIDTH =   col_widths[0];
1626     const GG::Y ICON_HEIGHT(std::min(Value(Height()) - 12, std::max(ClientUI::Pts(), Value(GRAPHIC_WIDTH) - 6)));
1627     // TODO replace string padding with new TextFormat flag
1628     std::string just_pad = "    ";
1629 
1630     auto graphic = GG::Wnd::Create<GG::StaticGraphic>(ClientUI::TechIcon(m_tech),
1631                                                       GG::GRAPHIC_VCENTER | GG::GRAPHIC_CENTER | GG::GRAPHIC_PROPSCALE | GG::GRAPHIC_FITGRAPHIC);
1632     graphic->Resize(GG::Pt(GRAPHIC_WIDTH, ICON_HEIGHT));
1633     graphic->SetColor(ClientUI::CategoryColor(this_row_tech->Category()));
1634     push_back(graphic);
1635 
1636     auto text = GG::Wnd::Create<CUILabel>(just_pad + UserString(m_tech), GG::FORMAT_LEFT);
1637     text->SetResetMinSize(false);
1638     text->ClipText(true);
1639     text->SetChildClippingMode(ClipToWindow);
1640     push_back(text);
1641 
1642     std::string cost_str = std::to_string(std::lround(this_row_tech->ResearchCost(HumanClientApp::GetApp()->EmpireID())));
1643     text = GG::Wnd::Create<CUILabel>(cost_str + just_pad + just_pad, GG::FORMAT_RIGHT);
1644     text->SetResetMinSize(false);
1645     text->ClipText(true);
1646     text->SetChildClippingMode(ClipToWindow);
1647     push_back(text);
1648 
1649     std::string time_str = std::to_string(this_row_tech->ResearchTime(HumanClientApp::GetApp()->EmpireID()));
1650     text = GG::Wnd::Create<CUILabel>(time_str + just_pad + just_pad, GG::FORMAT_RIGHT);
1651     text->SetResetMinSize(false);
1652     text->ClipText(true);
1653     text->SetChildClippingMode(ClipToWindow);
1654     push_back(text);
1655 
1656     text = GG::Wnd::Create<CUILabel>(just_pad + UserString(this_row_tech->Category()), GG::FORMAT_LEFT);
1657     text->SetResetMinSize(false);
1658     text->ClipText(true);
1659     text->SetChildClippingMode(ClipToWindow);
1660     push_back(text);
1661 
1662     text = GG::Wnd::Create<CUILabel>(just_pad + UserString(this_row_tech->ShortDescription()), GG::FORMAT_LEFT);
1663     text->ClipText(true);
1664     text->SetChildClippingMode(ClipToWindow);
1665     push_back(text);
1666 }
1667 
Update()1668 void TechTreeWnd::TechListBox::TechRow::Update() {
1669     const Tech* this_row_tech = ::GetTech(m_tech);
1670     if (!this_row_tech || this->size() < 4)
1671         return;
1672     // TODO replace string padding with new TextFormat flag
1673     std::string just_pad = "    ";
1674 
1675     auto client_empire_id = HumanClientApp::GetApp()->EmpireID();
1676     auto empire = GetEmpire(client_empire_id);
1677 
1678     std::string cost_str = std::to_string(std::lround(this_row_tech->ResearchCost(client_empire_id)));
1679     if (GG::Button* cost_btn = dynamic_cast<GG::Button*>((size() >= 3) ? at(2) : nullptr))
1680         cost_btn->SetText(cost_str + just_pad + just_pad);
1681 
1682     std::string time_str = std::to_string(this_row_tech->ResearchTime(client_empire_id));
1683     if (GG::Button* time_btn = dynamic_cast<GG::Button*>((size() >= 4) ? at(3) : nullptr))
1684         time_btn->SetText(time_str + just_pad + just_pad);
1685 
1686     // Adjust colors for tech status
1687     auto foreground_color = ClientUI::CategoryColor(this_row_tech->Category());
1688     auto this_row_status = empire ? empire->GetTechStatus(m_tech) : TS_RESEARCHABLE;
1689     if (this_row_status == TS_COMPLETE) {
1690         foreground_color.a = m_background_color.a;  // preserve users 'wnd-color' trasparency
1691         AdjustBrightness(foreground_color, 0.3);
1692         m_background_color = foreground_color;
1693         foreground_color = ClientUI::TextColor();
1694     } else if (this_row_status == TS_UNRESEARCHABLE || this_row_status == TS_HAS_RESEARCHED_PREREQ) {
1695         foreground_color.a = 96;
1696     }
1697 
1698     for (std::size_t i = 0; i < size(); ++i)
1699         at(i)->SetColor(foreground_color);
1700 
1701     if (empire) {
1702         const ResearchQueue& rq = empire->GetResearchQueue();
1703         m_enqueued = rq.InQueue(m_tech);
1704     } else {
1705         m_enqueued = false;
1706     }
1707 
1708     ClearBrowseInfoWnd();
1709     SetBrowseInfoWnd(TechRowBrowseWnd(m_tech, client_empire_id));
1710 }
1711 
ToggleSortCol(unsigned int col)1712 void TechTreeWnd::TechListBox::ToggleSortCol(unsigned int col) {
1713     if (SortCol() != col) {
1714         m_previous_sort_col = SortCol();
1715         SetSortCol(col);
1716     } else {  // toggle the sort direction
1717         SetStyle(Style() ^ GG::LIST_SORTDESCENDING);
1718     }
1719 }
1720 
TechListBox(GG::X w,GG::Y h)1721 TechTreeWnd::TechListBox::TechListBox(GG::X w, GG::Y h) :
1722     CUIListBox()
1723 {
1724     Resize(GG::Pt(w, h));
1725 }
1726 
CompleteConstruction()1727 void TechTreeWnd::TechListBox::CompleteConstruction() {
1728     CUIListBox::CompleteConstruction();
1729 
1730 #if BOOST_VERSION >= 106000
1731     using boost::placeholders::_1;
1732     using boost::placeholders::_2;
1733     using boost::placeholders::_3;
1734 #endif
1735 
1736     DoubleClickedRowSignal.connect(boost::bind(&TechListBox::TechDoubleClicked, this, _1, _2, _3));
1737     LeftClickedRowSignal.connect(boost::bind(&TechListBox::TechLeftClicked, this, _1, _2, _3));
1738     RightClickedRowSignal.connect(boost::bind(&TechListBox::TechRightClicked, this, _1, _2, _3));
1739 
1740     SetStyle(GG::LIST_NOSEL);
1741 
1742     // show all categories...
1743     m_categories_shown.clear();
1744     for (const std::string& category_name : GetTechManager().CategoryNames())
1745         m_categories_shown.insert(category_name);
1746 
1747     // show all statuses except unreasearchable
1748     m_tech_statuses_shown.clear();
1749     //m_tech_statuses_shown.insert(TS_UNRESEARCHABLE);
1750     m_tech_statuses_shown.insert(TS_RESEARCHABLE);
1751     m_tech_statuses_shown.insert(TS_COMPLETE);
1752 
1753     GG::X row_width = Width() - ClientUI::ScrollWidth() - ClientUI::Pts();
1754     std::vector<GG::X> col_widths = TechRow::ColWidths(row_width);
1755     const GG::Y HEIGHT(Value(col_widths[0]));
1756     m_header_row = GG::Wnd::Create<GG::ListBox::Row>(row_width, HEIGHT);
1757 
1758     auto graphic_col = GG::Wnd::Create<CUILabel>("");  // graphic
1759     graphic_col->Resize(GG::Pt(col_widths[0], HEIGHT));
1760     graphic_col->ClipText(true);
1761     graphic_col->SetChildClippingMode(ClipToWindow);
1762     m_header_row->push_back(graphic_col);
1763 
1764     auto name_col = Wnd::Create<CUIButton>(UserString("TECH_WND_LIST_COLUMN_NAME"));
1765     name_col->Resize(GG::Pt(col_widths[1], HEIGHT));
1766     name_col->SetChildClippingMode(ClipToWindow);
1767     name_col->LeftClickedSignal.connect([this]() { ToggleSortCol(1); });
1768     m_header_row->push_back(name_col);
1769 
1770     auto cost_col = Wnd::Create<CUIButton>(UserString("TECH_WND_LIST_COLUMN_COST"));
1771     cost_col->Resize(GG::Pt(col_widths[2], HEIGHT));
1772     cost_col->SetChildClippingMode(ClipToWindow);
1773     cost_col->LeftClickedSignal.connect([this]() { ToggleSortCol(2); });
1774     m_header_row->push_back(cost_col);
1775 
1776     auto time_col = Wnd::Create<CUIButton>(UserString("TECH_WND_LIST_COLUMN_TIME"));
1777     time_col->Resize(GG::Pt(col_widths[3], HEIGHT));
1778     time_col->SetChildClippingMode(ClipToWindow);
1779     time_col->LeftClickedSignal.connect([this]() { ToggleSortCol(3); });
1780     m_header_row->push_back(time_col);
1781 
1782     auto category_col = Wnd::Create<CUIButton>( UserString("TECH_WND_LIST_COLUMN_CATEGORY"));
1783     category_col->Resize(GG::Pt(col_widths[4], HEIGHT));
1784     category_col->SetChildClippingMode(ClipToWindow);
1785     category_col->LeftClickedSignal.connect([this]() { ToggleSortCol(4); });
1786     m_header_row->push_back(category_col);
1787 
1788     auto descr_col = Wnd::Create<CUIButton>(UserString("TECH_WND_LIST_COLUMN_DESCRIPTION"));
1789     descr_col->Resize(GG::Pt(col_widths[5], HEIGHT));
1790     descr_col->SetChildClippingMode(ClipToWindow);
1791     descr_col->LeftClickedSignal.connect([this]() { ToggleSortCol(5); });
1792     m_header_row->push_back(descr_col);
1793 
1794     m_header_row->Resize(GG::Pt(row_width, HEIGHT));
1795 
1796     // Initialize column widths before setting header
1797     int num_cols = m_header_row->size();
1798     std::vector<GG::Alignment> col_alignments = TechRow::ColAlignments();
1799     SetNumCols(num_cols);
1800     for (int i = 0; i < num_cols; ++i) {
1801         SetColWidth(i, m_header_row->at(i)->Width());
1802         SetColAlignment(i, col_alignments[i]);
1803     }
1804 
1805     SetColHeaders(m_header_row);
1806     LockColWidths();
1807 
1808     // Initialize sorting
1809     SetSortCol(2);
1810     m_previous_sort_col = 3;
1811     SetSortCmp([&](const GG::ListBox::Row& lhs, const GG::ListBox::Row& rhs, std::size_t col) { return TechRowCmp(lhs, rhs, col); });
1812 }
1813 
~TechListBox()1814 TechTreeWnd::TechListBox::~TechListBox()
1815 {}
1816 
Reset()1817 void TechTreeWnd::TechListBox::Reset()
1818 {
1819     m_tech_row_cache.clear();
1820     Populate();
1821 }
1822 
Update(bool populate)1823 void TechTreeWnd::TechListBox::Update(bool populate /* = true */) {
1824     if (populate)
1825         Populate(false);
1826 
1827     DebugLogger() << "Tech List Box Updating";
1828 
1829     double insertion_elapsed = 0.0;
1830     ScopedTimer insertion_timer;
1831     GG::X row_width = Width() - ClientUI::ScrollWidth() - ClientUI::Pts();
1832 
1833     // Try to preserve the first row, only works if a row for the tech is still visible
1834     std::string first_tech_shown;
1835     if (FirstRowShown() != end())
1836         if (auto first_row = FirstRowShown()->get())
1837             if (auto first_row_shown = dynamic_cast<TechRow*>(first_row))
1838                 first_tech_shown = first_row_shown->GetTech();
1839 
1840     // Skip setting first row during insertion
1841     bool first_tech_set = first_tech_shown.empty();
1842 
1843     // remove techs in listbox, then reset the rest of its state
1844     for (iterator it = begin(); it != end(); ) {
1845         iterator temp_it = it++;
1846         Erase(temp_it);
1847     }
1848 
1849     Clear();
1850 
1851     // Add rows from cache
1852     for (auto& row : m_tech_row_cache) {
1853         auto& tech_row = row.second;
1854         if (TechVisible(tech_row->GetTech(), m_categories_shown, m_tech_statuses_shown)) {
1855             tech_row->Update();
1856             insertion_timer.restart();
1857             auto listbox_row_it = Insert(tech_row);
1858             insertion_elapsed += insertion_timer.duration();
1859             if (!first_tech_set && row.first == first_tech_shown) {
1860                 first_tech_set = true;
1861                 SetFirstRowShown(listbox_row_it);
1862             }
1863         }
1864     }
1865 
1866     // set attributes after clear and insert, cached rows may have incorrect widths
1867     std::vector<GG::X> col_widths = TechRow::ColWidths(row_width);
1868     int num_cols = static_cast<int>(col_widths.size());
1869     for (int i = 0; i < num_cols; ++i) {
1870         SetColWidth(i, col_widths[i]);
1871         // only stretch the last column
1872         SetColStretch(i, (i < num_cols - 1) ? 0.0 : 1.0);
1873     }
1874     if (SortCol() < 1)
1875         SetSortCol(2);
1876 
1877     if (!first_tech_set || first_tech_shown.empty())
1878         BringRowIntoView(begin());
1879 
1880     DebugLogger() << "Tech List Box Updating Done, Insertion time = " << (insertion_elapsed * 1000) << " ms";
1881 }
1882 
Populate(bool update)1883 void TechTreeWnd::TechListBox::Populate(bool update /* = true*/) {
1884     DebugLogger() << "Tech List Box Populating";
1885 
1886     GG::X row_width = Width() - ClientUI::ScrollWidth() - ClientUI::Pts();
1887 
1888     ScopedTimer creation_timer;
1889 
1890     // Skip lookup check when starting with empty cache
1891     bool new_cache = m_tech_row_cache.empty();
1892     for (const auto& tech : GetTechManager()) {
1893         if (new_cache || !m_tech_row_cache.count(tech->Name()))
1894             m_tech_row_cache.emplace(tech->Name(), GG::Wnd::Create<TechRow>(row_width, tech->Name()));
1895     }
1896 
1897     DebugLogger() << "Tech List Box Populating Done,  Creation time = " << creation_timer.DurationString();
1898 
1899     if (update)
1900         Update(false);
1901 }
1902 
ShowCategory(const std::string & category)1903 void TechTreeWnd::TechListBox::ShowCategory(const std::string& category) {
1904     if (!m_categories_shown.count(category)) {
1905         m_categories_shown.insert(category);
1906         Populate();
1907     }
1908 }
1909 
ShowAllCategories()1910 void TechTreeWnd::TechListBox::ShowAllCategories() {
1911     const std::vector<std::string> all_cats = GetTechManager().CategoryNames();
1912     if (all_cats.size() == m_categories_shown.size())
1913         return;
1914     for (const std::string& category_name : all_cats)
1915         m_categories_shown.insert(category_name);
1916     Populate();
1917 }
1918 
HideCategory(const std::string & category)1919 void TechTreeWnd::TechListBox::HideCategory(const std::string& category) {
1920     std::set<std::string>::iterator it = m_categories_shown.find(category);
1921     if (it != m_categories_shown.end()) {
1922         m_categories_shown.erase(it);
1923         Populate();
1924     }
1925 }
1926 
HideAllCategories()1927 void TechTreeWnd::TechListBox::HideAllCategories() {
1928     if (m_categories_shown.empty())
1929         return;
1930     m_categories_shown.clear();
1931     Populate();
1932 }
1933 
ShowStatus(TechStatus status)1934 void TechTreeWnd::TechListBox::ShowStatus(TechStatus status) {
1935     if (!m_tech_statuses_shown.count(status)) {
1936         m_tech_statuses_shown.insert(status);
1937         Populate();
1938     }
1939 }
1940 
HideStatus(TechStatus status)1941 void TechTreeWnd::TechListBox::HideStatus(TechStatus status) {
1942     std::set<TechStatus>::iterator it = m_tech_statuses_shown.find(status);
1943     if (it != m_tech_statuses_shown.end()) {
1944         m_tech_statuses_shown.erase(it);
1945         Populate();
1946     }
1947 }
1948 
TechLeftClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)1949 void TechTreeWnd::TechListBox::TechLeftClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) {
1950     // determine type of row that was clicked, and emit appropriate signal
1951     if (TechRow* tech_row = dynamic_cast<TechRow*>(it->get()))
1952         TechLeftClickedSignal(tech_row->GetTech(), GG::Flags<GG::ModKey>());
1953 }
1954 
TechRightClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)1955 void TechTreeWnd::TechListBox::TechRightClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) {
1956     if ((*it)->Disabled())
1957         return;
1958     const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
1959     if (!empire)
1960         return;
1961 
1962     TechRow* tech_row = dynamic_cast<TechRow*>(it->get());
1963     if (!tech_row)
1964         return;
1965     const std::string& tech_name = tech_row->GetTech();
1966 
1967     auto popup = GG::Wnd::Create<CUIPopupMenu>(pt.x, pt.y);
1968     const ResearchQueue& rq = empire->GetResearchQueue();
1969     if (!rq.InQueue(tech_name)) {
1970         auto tech_dclick_action = [this, it, pt]() { TechDoubleClicked(it, pt, GG::Flags<GG::ModKey>()); };
1971         auto tech_ctrl_dclick_action = [this, it, pt]() { TechDoubleClicked(it, pt, GG::MOD_KEY_CTRL); };
1972 
1973         if (!empire->TechResearched(tech_name)) {
1974             popup->AddMenuItem(GG::MenuItem(UserString("PRODUCTION_DETAIL_ADD_TO_QUEUE"), false, false,
1975                                             tech_dclick_action));
1976             popup->AddMenuItem(GG::MenuItem(UserString("PRODUCTION_DETAIL_ADD_TO_TOP_OF_QUEUE"), false, false,
1977                                             tech_ctrl_dclick_action));
1978         }
1979     }
1980 
1981     auto pedia_display_action = [this, &tech_name]() { TechPediaDisplaySignal(tech_name); };
1982     std::string popup_label = boost::io::str(FlexibleFormat(UserString("ENC_LOOKUP")) % UserString(tech_name));
1983     popup->AddMenuItem(GG::MenuItem(popup_label, false, false, pedia_display_action));
1984 
1985     popup->Run();
1986 }
1987 
TechDoubleClicked(GG::ListBox::iterator it,const GG::Pt & pt,const GG::Flags<GG::ModKey> & modkeys)1988 void TechTreeWnd::TechListBox::TechDoubleClicked(GG::ListBox::iterator it, const GG::Pt& pt, const GG::Flags<GG::ModKey>& modkeys) {
1989     // determine type of row that was clicked, and emit appropriate signal
1990     TechRow* tech_row = dynamic_cast<TechRow*>(it->get());
1991     if (tech_row)
1992         TechDoubleClickedSignal(tech_row->GetTech(), modkeys);
1993 }
1994 
1995 
1996 //////////////////////////////////////////////////
1997 // TechTreeWnd                                  //
1998 //////////////////////////////////////////////////
TechTreeWnd(GG::X w,GG::Y h,bool initially_hidden)1999 TechTreeWnd::TechTreeWnd(GG::X w, GG::Y h, bool initially_hidden /*= true*/) :
2000     GG::Wnd(GG::X0, GG::Y0, w, h, GG::INTERACTIVE),
2001     m_init_flag(initially_hidden)
2002 {}
2003 
CompleteConstruction()2004 void TechTreeWnd::CompleteConstruction() {
2005     GG::Wnd::CompleteConstruction();
2006 
2007     Sound::TempUISoundDisabler sound_disabler;
2008 
2009 #if BOOST_VERSION >= 106000
2010     using boost::placeholders::_1;
2011     using boost::placeholders::_2;
2012 #endif
2013 
2014     m_layout_panel = GG::Wnd::Create<LayoutPanel>(Width(), Height());
2015     m_layout_panel->TechSelectedSignal.connect(boost::bind(&TechTreeWnd::TechLeftClickedSlot, this, _1, _2));
2016     m_layout_panel->TechDoubleClickedSignal.connect(
2017         [this](const std::string& tech_name, GG::Flags<GG::ModKey> modkeys)
2018     { this->AddTechToResearchQueue(tech_name, modkeys & GG::MOD_KEY_CTRL); });
2019     m_layout_panel->TechPediaDisplaySignal.connect(boost::bind(&TechTreeWnd::TechPediaDisplaySlot, this, _1));
2020     AttachChild(m_layout_panel);
2021 
2022     m_tech_list = GG::Wnd::Create<TechListBox>(Width(), Height());
2023     m_tech_list->TechLeftClickedSignal.connect(boost::bind(&TechTreeWnd::TechLeftClickedSlot, this, _1, _2));
2024     m_tech_list->TechDoubleClickedSignal.connect(
2025         [this](const std::string& tech_name, GG::Flags<GG::ModKey> modkeys)
2026     { this->AddTechToResearchQueue(tech_name, modkeys & GG::MOD_KEY_CTRL); });
2027     m_tech_list->TechPediaDisplaySignal.connect(
2028         boost::bind(&TechTreeWnd::TechPediaDisplaySlot, this, _1));
2029 
2030     m_enc_detail_panel = GG::Wnd::Create<EncyclopediaDetailPanel>(GG::ONTOP | GG::INTERACTIVE | GG::DRAGABLE | GG::RESIZABLE | CLOSABLE | PINABLE, RES_PEDIA_WND_NAME);
2031     m_tech_tree_controls =  GG::Wnd::Create<TechTreeControls>(RES_CONTROLS_WND_NAME);
2032 
2033     m_enc_detail_panel->ClosingSignal.connect(boost::bind(&TechTreeWnd::HidePedia, this));
2034 
2035     InitializeWindows();
2036     // Make sure the controls don't overlap the bottom scrollbar
2037     if (m_tech_tree_controls->Bottom() > m_layout_panel->Bottom() - ClientUI::ScrollWidth()) {
2038         m_tech_tree_controls->MoveTo(GG::Pt(m_tech_tree_controls->Left(),
2039                                             m_layout_panel->Bottom() - ClientUI::ScrollWidth() - m_tech_tree_controls->Height()));
2040         m_tech_tree_controls->SaveDefaultedOptions();
2041     }
2042 
2043     HumanClientApp::GetApp()->RepositionWindowsSignal.connect(
2044         boost::bind(&TechTreeWnd::InitializeWindows, this));
2045 
2046     AttachChild(m_enc_detail_panel);
2047     AttachChild(m_tech_tree_controls);
2048 
2049     // connect category button clicks to update display
2050     for (auto& cat_button : m_tech_tree_controls->m_cat_buttons) {
2051         const std::string& category_name = cat_button.first;
2052         cat_button.second->CheckedSignal.connect(
2053             [this, category_name](bool checked) {
2054                 if(checked)
2055                     this->ShowCategory(category_name);
2056                 else
2057                     this->HideCategory(category_name);
2058             }
2059         );
2060     }
2061 
2062     // connect button for all categories to update display
2063     m_tech_tree_controls->m_all_cat_button->CheckedSignal.connect(
2064         [this](bool checked) {
2065             if(checked)
2066                 this->ShowAllCategories();
2067             else
2068                 this->HideAllCategories();
2069         }
2070     );
2071 
2072     // connect status and type button clicks to update display
2073     for (auto& status_button : m_tech_tree_controls->m_status_buttons) {
2074         TechStatus tech_status = status_button.first;
2075         status_button.second->CheckedSignal.connect(
2076             [this, tech_status](bool checked){ this->SetTechStatus(tech_status, checked); });
2077     }
2078 
2079     // connect view type selector
2080     m_tech_tree_controls->m_view_type_button->CheckedSignal.connect(
2081         boost::bind(&TechTreeWnd::ToggleViewType, this, _1));
2082 
2083     //TechTreeWnd in typically constructed before the UI client has
2084     //accesss to the technologies so showing these categories takes a
2085     //long time and generates errors, but is never seen by the user.
2086     if (!m_init_flag) {
2087         ShowAllCategories();
2088         SetTechStatus(TS_COMPLETE, GetOptionsDB().Get<bool>("ui.research.status.completed.shown"));
2089         SetTechStatus(TS_UNRESEARCHABLE, GetOptionsDB().Get<bool>("ui.research.status.unresearchable.shown"));
2090         SetTechStatus(TS_HAS_RESEARCHED_PREREQ, GetOptionsDB().Get<bool>("ui.research.status.partial.shown"));
2091         SetTechStatus(TS_RESEARCHABLE, GetOptionsDB().Get<bool>("ui.research.status.researchable.shown"));
2092     }
2093 
2094     ShowTreeView();
2095 }
2096 
~TechTreeWnd()2097 TechTreeWnd::~TechTreeWnd()
2098 {}
2099 
SizeMove(const GG::Pt & ul,const GG::Pt & lr)2100 void TechTreeWnd::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
2101     const GG::Pt old_size = Size();
2102     GG::Wnd::SizeMove(ul, lr);
2103     if (old_size != Size()) {
2104         m_enc_detail_panel->ValidatePosition();
2105         m_tech_tree_controls->ValidatePosition();
2106         m_layout_panel->Resize(this->Size());
2107         m_tech_list->Resize(this->Size());
2108     }
2109 }
2110 
Scale() const2111 double TechTreeWnd::Scale() const
2112 { return m_layout_panel->Scale(); }
2113 
Update()2114 void TechTreeWnd::Update() {
2115     m_layout_panel->Update();
2116     m_tech_list->Update();
2117 }
2118 
Clear()2119 void TechTreeWnd::Clear() {
2120     m_enc_detail_panel->OnIndex();
2121     m_layout_panel->Clear();
2122 }
2123 
Reset()2124 void TechTreeWnd::Reset() {
2125     m_layout_panel->Reset();
2126     m_tech_list->Reset();
2127 }
2128 
InitializeWindows()2129 void TechTreeWnd::InitializeWindows() {
2130     const GG::Pt pedia_ul(GG::X0,  GG::Y0);
2131     const GG::Pt pedia_wh(GG::X(480), GG::Y(240));
2132 
2133     // Don't know this wnd's height in advance so place it off the bottom edge,
2134     // it subclasses CUIWnd so it will reposition itself to be visible.
2135     const GG::Pt controls_ul(GG::X1, m_layout_panel->Height());
2136     const GG::Pt controls_wh((m_layout_panel->Width() * 0.6) - ClientUI::ScrollWidth(), GG::Y0);
2137 
2138     m_enc_detail_panel->InitSizeMove(pedia_ul,  pedia_ul + pedia_wh);
2139     m_tech_tree_controls->InitSizeMove(controls_ul,  controls_ul + controls_wh);
2140 }
2141 
Show()2142 void TechTreeWnd::Show() {
2143     GG::Wnd::Show();
2144 
2145     // When Show() is called for TechTree the ClientUI should now have
2146     // access to the technologies so that parsing does not generate
2147     // errors.
2148     if (m_init_flag) {
2149         m_init_flag = false;
2150         ShowAllCategories();
2151         SetTechStatus(TS_COMPLETE, GetOptionsDB().Get<bool>("ui.research.status.completed.shown"));
2152         SetTechStatus(TS_UNRESEARCHABLE, GetOptionsDB().Get<bool>("ui.research.status.unresearchable.shown"));
2153         SetTechStatus(TS_HAS_RESEARCHED_PREREQ, GetOptionsDB().Get<bool>("ui.research.status.partial.shown"));
2154         SetTechStatus(TS_RESEARCHABLE, GetOptionsDB().Get<bool>("ui.research.status.researchable.shown"));
2155     }
2156 }
2157 
ShowCategory(const std::string & category)2158 void TechTreeWnd::ShowCategory(const std::string& category) {
2159     m_layout_panel->ShowCategory(category);
2160     m_tech_list->ShowCategory(category);
2161 
2162     const auto& maybe_button = m_tech_tree_controls->m_cat_buttons.find(category);
2163     if (maybe_button != m_tech_tree_controls->m_cat_buttons.end())
2164         maybe_button->second->SetCheck(true);
2165 }
2166 
ShowAllCategories()2167 void TechTreeWnd::ShowAllCategories() {
2168     m_layout_panel->ShowAllCategories();
2169     m_tech_list->ShowAllCategories();
2170 
2171     for (auto& cat_button : m_tech_tree_controls->m_cat_buttons)
2172     { cat_button.second->SetCheck(true); }
2173 }
2174 
HideCategory(const std::string & category)2175 void TechTreeWnd::HideCategory(const std::string& category) {
2176     m_layout_panel->HideCategory(category);
2177     m_tech_list->HideCategory(category);
2178 
2179     const auto& maybe_button = m_tech_tree_controls->m_cat_buttons.find(category);
2180     if (maybe_button != m_tech_tree_controls->m_cat_buttons.end())
2181         maybe_button->second->SetCheck(false);
2182 }
2183 
HideAllCategories()2184 void TechTreeWnd::HideAllCategories() {
2185     m_layout_panel->HideAllCategories();
2186     m_tech_list->HideAllCategories();
2187 
2188     for (auto& cat_button : m_tech_tree_controls->m_cat_buttons)
2189     { cat_button.second->SetCheck(false); }
2190 }
2191 
ToggleAllCategories()2192 void TechTreeWnd::ToggleAllCategories() {
2193     std::set<std::string> shown_cats = m_layout_panel->GetCategoriesShown();
2194     const std::vector<std::string> all_cats = GetTechManager().CategoryNames();
2195 
2196     if (shown_cats.size() == all_cats.size())
2197         HideAllCategories();
2198     else
2199         ShowAllCategories();
2200 }
2201 
SetTechStatus(const TechStatus status,const bool state)2202 void TechTreeWnd::SetTechStatus(const TechStatus status, const bool state) {
2203     switch (status) {
2204     case TS_UNRESEARCHABLE:
2205         GetOptionsDB().Set<bool>("ui.research.status.unresearchable.shown", state);
2206         break;
2207     case TS_HAS_RESEARCHED_PREREQ:
2208         GetOptionsDB().Set<bool>("ui.research.status.partial.shown", state);
2209         break;
2210     case TS_RESEARCHABLE:
2211         GetOptionsDB().Set<bool>("ui.research.status.researchable.shown", state);
2212         break;
2213     case TS_COMPLETE:
2214         GetOptionsDB().Set<bool>("ui.research.status.completed.shown", state);
2215         break;
2216     default:
2217         ; // do nothing
2218     }
2219     GetOptionsDB().Commit();
2220 
2221     if (state) {
2222         m_layout_panel->ShowStatus(status);
2223         m_tech_list->ShowStatus(status);
2224     } else {
2225         m_layout_panel->HideStatus(status);
2226         m_tech_list->HideStatus(status);
2227     }
2228 
2229     m_tech_tree_controls->SetTechStatus(status, state);
2230 }
2231 
ToggleViewType(bool show_list_view)2232 void TechTreeWnd::ToggleViewType(bool show_list_view)
2233 { show_list_view ? ShowListView() : ShowTreeView(); }
2234 
ShowTreeView()2235 void TechTreeWnd::ShowTreeView() {
2236     AttachChild(m_layout_panel);
2237     MoveChildDown(m_layout_panel);
2238     DetachChild(m_tech_list);
2239     MoveChildUp(m_tech_tree_controls);
2240 }
2241 
ShowListView()2242 void TechTreeWnd::ShowListView() {
2243     m_tech_list->Reset();
2244     AttachChild(m_tech_list);
2245     MoveChildDown(m_tech_list);
2246     DetachChild(m_layout_panel);
2247     MoveChildUp(m_tech_tree_controls);
2248 }
2249 
CenterOnTech(const std::string & tech_name)2250 void TechTreeWnd::CenterOnTech(const std::string& tech_name) {
2251     // ensure tech exists and is visible
2252     const Tech* tech = ::GetTech(tech_name);
2253     if (!tech) return;
2254     const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
2255     if (empire)
2256         SetTechStatus(empire->GetTechStatus(tech_name), true);
2257     ShowCategory(tech->Category());
2258 
2259     // centre on it
2260     m_layout_panel->CenterOnTech(tech_name);
2261 }
2262 
SetEncyclopediaTech(const std::string & tech_name)2263 void TechTreeWnd::SetEncyclopediaTech(const std::string& tech_name)
2264 { m_enc_detail_panel->SetTech(tech_name); }
2265 
SelectTech(const std::string & tech_name)2266 void TechTreeWnd::SelectTech(const std::string& tech_name)
2267 { m_layout_panel->SelectTech(tech_name); }
2268 
ShowPedia()2269 void TechTreeWnd::ShowPedia() {
2270     m_enc_detail_panel->Refresh();
2271     m_enc_detail_panel->Show();
2272 
2273     OptionsDB& db = GetOptionsDB();
2274     db.Set("ui." + RES_PEDIA_WND_NAME + ".hidden.enabled", false);
2275 }
2276 
HidePedia()2277 void TechTreeWnd::HidePedia() {
2278     m_enc_detail_panel->Hide();
2279 
2280     OptionsDB& db = GetOptionsDB();
2281     db.Set("ui." + RES_PEDIA_WND_NAME + ".hidden.enabled", true);
2282 }
2283 
TogglePedia()2284 void TechTreeWnd::TogglePedia() {
2285     if (!m_enc_detail_panel->Visible())
2286         ShowPedia();
2287     else
2288         HidePedia();
2289 }
2290 
PediaVisible()2291 bool TechTreeWnd::PediaVisible()
2292 { return m_enc_detail_panel->Visible(); }
2293 
TechIsVisible(const std::string & tech_name) const2294 bool TechTreeWnd::TechIsVisible(const std::string& tech_name) const
2295 { return TechVisible(tech_name, m_layout_panel->GetCategoriesShown(), m_layout_panel->GetTechStatusesShown()); }
2296 
TechLeftClickedSlot(const std::string & tech_name,const GG::Flags<GG::ModKey> & modkeys)2297 void TechTreeWnd::TechLeftClickedSlot(const std::string& tech_name,
2298                                   const GG::Flags<GG::ModKey>& modkeys)
2299 {
2300     if (modkeys & GG::MOD_KEY_SHIFT) {
2301         AddTechToResearchQueue(tech_name, modkeys & GG::MOD_KEY_CTRL);
2302     } else {
2303         SetEncyclopediaTech(tech_name);
2304         TechSelectedSignal(tech_name);
2305     }
2306 }
2307 
AddTechToResearchQueue(const std::string & tech_name,bool to_front)2308 void TechTreeWnd::AddTechToResearchQueue(const std::string& tech_name,
2309                                          bool to_front)
2310 {
2311     const Tech* tech = GetTech(tech_name);
2312     if (!tech) return;
2313     const Empire* empire = GetEmpire(HumanClientApp::GetApp()->EmpireID());
2314     TechStatus tech_status = TS_UNRESEARCHABLE;
2315     if (empire)
2316         tech_status = empire->GetTechStatus(tech_name);
2317 
2318     int queue_pos = -1;
2319     if (to_front)
2320         queue_pos = 0;
2321 
2322     // if tech can be researched already, just add it
2323     if (tech_status == TS_RESEARCHABLE) {
2324         std::vector<std::string> techs;
2325         techs.push_back(tech_name);
2326         AddTechsToQueueSignal(techs, queue_pos);
2327         return;
2328     }
2329 
2330     if (tech_status != TS_UNRESEARCHABLE && tech_status != TS_HAS_RESEARCHED_PREREQ)
2331         return;
2332 
2333     // if tech can't yet be researched, add any prerequisites it requires (recursively) and then add it
2334     TechManager& manager = GetTechManager();
2335     int empire_id = HumanClientApp::GetApp()->EmpireID();
2336     std::vector<std::string> tech_vec = manager.RecursivePrereqs(tech_name, empire_id);
2337     tech_vec.push_back(tech_name);
2338     AddTechsToQueueSignal(tech_vec, queue_pos);
2339 }
2340 
TechPediaDisplaySlot(const std::string & tech_name)2341 void TechTreeWnd::TechPediaDisplaySlot(const std::string& tech_name) {
2342     SetEncyclopediaTech(tech_name);
2343     if (!PediaVisible())
2344         ShowPedia();
2345 }
2346