1 #include "ClientUI.h"
2 
3 #include "CUIControls.h"
4 #include "FleetWnd.h"
5 #include "IntroScreen.h"
6 #include "MapWnd.h"
7 #include "DesignWnd.h"
8 #include "ChatWnd.h"
9 #include "PlayerListWnd.h"
10 #include "MultiplayerLobbyWnd.h"
11 #include "PasswordEnterWnd.h"
12 #include "Sound.h"
13 #include "Hotkeys.h"
14 
15 #undef int64_t
16 
17 #include "../util/Random.h"
18 #include "../util/Directories.h"
19 #include "../util/i18n.h"
20 #include "../util/OptionsDB.h"
21 #include "../universe/Building.h"
22 #include "../universe/BuildingType.h"
23 #include "../universe/Fleet.h"
24 #include "../universe/Planet.h"
25 #include "../universe/System.h"
26 #include "../universe/Ship.h"
27 #include "../universe/ShipDesign.h"
28 #include "../universe/ShipPart.h"
29 #include "../universe/ShipHull.h"
30 #include "../universe/Tech.h"
31 #include "../universe/Special.h"
32 #include "../universe/Species.h"
33 #include "../universe/FieldType.h"
34 #include "../universe/Enums.h"
35 #include "../combat/CombatLogManager.h"
36 #include "../client/human/HumanClientApp.h"
37 
38 #include <GG/Clr.h>
39 #include <GG/dialogs/ThreeButtonDlg.h>
40 #include <GG/GUI.h>
41 #include <GG/RichText/ImageBlock.h>
42 #include <GG/UnicodeCharsets.h>
43 
44 #include <boost/spirit/include/qi.hpp>
45 #include <boost/spirit/include/phoenix_operator.hpp>
46 
47 #include <boost/algorithm/string/predicate.hpp>
48 #include <boost/filesystem/fstream.hpp>
49 #include <boost/filesystem/operations.hpp>
50 #include <boost/system/system_error.hpp>
51 #include <boost/date_time/posix_time/posix_time_io.hpp>
52 #include <boost/date_time/c_local_time_adjustor.hpp>
53 
54 #include <string>
55 #include <algorithm>
56 #include <boost/locale/formatting.hpp>
57 #include <boost/locale/date_time.hpp>
58 
59 
TextureFileNameCompare(const std::shared_ptr<GG::Texture> t1,const std::shared_ptr<GG::Texture> t2)60 bool TextureFileNameCompare(const std::shared_ptr<GG::Texture> t1, const std::shared_ptr<GG::Texture> t2)
61 { return t1 && t2 && t1->Path() < t2->Path(); }
62 
63 namespace fs = boost::filesystem;
64 
65 // static members
ArtDir()66 fs::path    ClientUI::ArtDir()                  { return GetResourceDir() / "data" / "art"; }
SoundDir()67 fs::path    ClientUI::SoundDir()                { return GetResourceDir() / "data" / "sound"; }
68 
Pts()69 int         ClientUI::Pts()                     { return GetOptionsDB().Get<int>("ui.font.size"); }
TitlePts()70 int         ClientUI::TitlePts()                { return GetOptionsDB().Get<int>("ui.font.title.size"); }
71 
TextColor()72 GG::Clr     ClientUI::TextColor()               { return GetOptionsDB().Get<GG::Clr>("ui.font.color"); }
DefaultLinkColor()73 GG::Clr     ClientUI::DefaultLinkColor()        { return GetOptionsDB().Get<GG::Clr>("ui.font.link.color"); }
RolloverLinkColor()74 GG::Clr     ClientUI::RolloverLinkColor()       { return GetOptionsDB().Get<GG::Clr>("ui.font.link.rollover.color"); }
75 
76 // windows
WndColor()77 GG::Clr     ClientUI::WndColor()                { return GetOptionsDB().Get<GG::Clr>("ui.window.background.color"); }
WndOuterBorderColor()78 GG::Clr     ClientUI::WndOuterBorderColor()     { return GetOptionsDB().Get<GG::Clr>("ui.window.border.outer.color"); }
WndInnerBorderColor()79 GG::Clr     ClientUI::WndInnerBorderColor()     { return GetOptionsDB().Get<GG::Clr>("ui.window.border.inner.color"); }
80 
81 // controls
CtrlColor()82 GG::Clr     ClientUI::CtrlColor()               { return GetOptionsDB().Get<GG::Clr>("ui.control.background.color"); }
CtrlBorderColor()83 GG::Clr     ClientUI::CtrlBorderColor()         { return GetOptionsDB().Get<GG::Clr>("ui.control.border.color"); }
ButtonHiliteColor()84 GG::Clr     ClientUI::ButtonHiliteColor() {
85     GG::Clr colour = CtrlColor();
86     AdjustBrightness(colour, 50);
87     return colour;
88 }
89 
ButtonHiliteBorderColor()90 GG::Clr     ClientUI::ButtonHiliteBorderColor() {
91     GG::Clr colour = CtrlBorderColor();
92     AdjustBrightness(colour, 50);
93     return colour;
94 }
95 
ScrollWidth()96 int         ClientUI::ScrollWidth()             { return GetOptionsDB().Get<int>("ui.scroll.width"); }
97 
DropDownListArrowColor()98 GG::Clr     ClientUI::DropDownListArrowColor()  { return GetOptionsDB().Get<GG::Clr>("ui.dropdownlist.arrow.color"); }
99 
EditHiliteColor()100 GG::Clr     ClientUI::EditHiliteColor()         { return GetOptionsDB().Get<GG::Clr>("ui.control.edit.highlight.color"); }
101 
StatIncrColor()102 GG::Clr     ClientUI::StatIncrColor()           { return GetOptionsDB().Get<GG::Clr>("ui.font.stat.increase.color"); }
StatDecrColor()103 GG::Clr     ClientUI::StatDecrColor()           { return GetOptionsDB().Get<GG::Clr>("ui.font.stat.decrease.color"); }
104 
StateButtonColor()105 GG::Clr     ClientUI::StateButtonColor()        { return GetOptionsDB().Get<GG::Clr>("ui.button.state.color"); }
106 
SystemIconSize()107 int         ClientUI::SystemIconSize()                  { return GetOptionsDB().Get<int>("ui.map.system.icon.size"); }
SystemTinyIconSizeThreshold()108 int         ClientUI::SystemTinyIconSizeThreshold()     { return GetOptionsDB().Get<int>("ui.map.system.icon.tiny.threshold"); }
SystemCircleSize()109 int         ClientUI::SystemCircleSize()                { return static_cast<int>(SystemIconSize() * GetOptionsDB().Get<double>("ui.map.system.circle.size")); }
SystemSelectionIndicatorSize()110 int         ClientUI::SystemSelectionIndicatorSize()    { return static_cast<int>(SystemIconSize() * GetOptionsDB().Get<double>("ui.map.system.select.indicator.size")); }
SystemSelectionIndicatorRPM()111 int         ClientUI::SystemSelectionIndicatorRPM()     { return GetOptionsDB().Get<int>("ui.map.system.select.indicator.rpm"); }
112 
SystemNameTextColor()113 GG::Clr     ClientUI::SystemNameTextColor()             { return GetOptionsDB().Get<GG::Clr>("ui.map.system.unowned.name.color"); }
114 
TinyFleetButtonZoomThreshold()115 double      ClientUI::TinyFleetButtonZoomThreshold()    { return GetOptionsDB().Get<double>("ui.map.fleet.button.tiny.zoom.threshold"); }
SmallFleetButtonZoomThreshold()116 double      ClientUI::SmallFleetButtonZoomThreshold()   { return GetOptionsDB().Get<double>("ui.map.fleet.button.small.zoom.threshold"); }
MediumFleetButtonZoomThreshold()117 double      ClientUI::MediumFleetButtonZoomThreshold()  { return GetOptionsDB().Get<double>("ui.map.fleet.button.medium.zoom.threshold"); }
118 
DisplayTimestamp()119 bool        ClientUI::DisplayTimestamp()                { return GetOptionsDB().Get<bool>("ui.map.messages.timestamp.shown"); }
120 
121 // content texture getters
PlanetIcon(PlanetType planet_type)122 std::shared_ptr<GG::Texture> ClientUI::PlanetIcon(PlanetType planet_type) {
123     std::string icon_filename;
124     switch (planet_type) {
125     case PT_SWAMP:
126         icon_filename = "swamp.png";    break;
127     case PT_TOXIC:
128         icon_filename = "toxic.png";    break;
129     case PT_INFERNO:
130         icon_filename = "inferno.png";  break;
131     case PT_RADIATED:
132         icon_filename = "radiated.png"; break;
133     case PT_BARREN:
134         icon_filename = "barren.png";   break;
135     case PT_TUNDRA:
136         icon_filename = "tundra.png";   break;
137     case PT_DESERT:
138         icon_filename = "desert.png";   break;
139     case PT_TERRAN:
140         icon_filename = "terran.png";   break;
141     case PT_OCEAN:
142         icon_filename = "ocean.png";    break;
143     case PT_ASTEROIDS:
144         icon_filename = "asteroids.png";break;
145     case PT_GASGIANT:
146         icon_filename = "gasgiant.png"; break;
147     default:
148         break;
149     }
150     return ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "planet" / icon_filename, true);
151 }
152 
PlanetSizeIcon(PlanetSize planet_size)153 std::shared_ptr<GG::Texture> ClientUI::PlanetSizeIcon(PlanetSize planet_size) {
154     std::string icon_filename;
155     switch (planet_size) {
156     case SZ_TINY:
157         icon_filename = "tiny.png";    break;
158     case SZ_SMALL:
159         icon_filename = "small.png";    break;
160     case SZ_MEDIUM:
161         icon_filename = "medium.png";  break;
162     case SZ_LARGE:
163         icon_filename = "large.png"; break;
164     case SZ_HUGE:
165         icon_filename = "huge.png";   break;
166     case SZ_ASTEROIDS:
167         icon_filename = "asteroids.png";   break;
168     case SZ_GASGIANT:
169         icon_filename = "gasgiant.png";   break;
170     default:
171         break;
172     }
173     return ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "planet" / icon_filename, true);
174 }
175 
MeterIcon(MeterType meter_type)176 std::shared_ptr<GG::Texture> ClientUI::MeterIcon(MeterType meter_type) {
177     std::string icon_filename;
178     switch (meter_type) {
179     case METER_POPULATION:
180     case METER_TARGET_POPULATION:
181         icon_filename = "pop.png";          break;
182     case METER_INDUSTRY:
183     case METER_TARGET_INDUSTRY:
184         icon_filename = "industry.png";     break;
185     case METER_RESEARCH:
186     case METER_TARGET_RESEARCH:
187         icon_filename = "research.png";     break;
188     case METER_TRADE:
189     case METER_TARGET_TRADE:
190         icon_filename = "trade.png";        break;
191     case METER_CONSTRUCTION:
192     case METER_TARGET_CONSTRUCTION:
193         icon_filename = "construction.png"; break;
194     case METER_HAPPINESS:
195     case METER_TARGET_HAPPINESS:
196         icon_filename = "happiness.png";    break;
197     case METER_CAPACITY:
198     case METER_MAX_CAPACITY:
199         icon_filename = "capacity.png";   break;
200     case METER_SECONDARY_STAT:
201     case METER_MAX_SECONDARY_STAT:
202         icon_filename = "secondary.png";   break;
203     case METER_STRUCTURE:
204     case METER_MAX_STRUCTURE:
205         icon_filename = "structure.png";    break;
206     case METER_FUEL:
207     case METER_MAX_FUEL:
208         icon_filename = "fuel.png";         break;
209     case METER_SUPPLY:
210     case METER_MAX_SUPPLY:
211         icon_filename = "supply.png";       break;
212     case METER_STOCKPILE:
213     case METER_MAX_STOCKPILE:
214         icon_filename = "stockpile.png";    break;
215     case METER_STEALTH:
216         icon_filename = "stealth.png";      break;
217     case METER_DETECTION:
218         icon_filename = "detection.png";    break;
219     case METER_SHIELD:
220     case METER_MAX_SHIELD:
221         icon_filename = "shield.png";       break;
222     case METER_DEFENSE:
223     case METER_MAX_DEFENSE:
224         icon_filename = "defense.png";      break;
225     case METER_TROOPS:
226     case METER_MAX_TROOPS:
227         icon_filename = "troops.png";       break;
228     case METER_REBEL_TROOPS:
229         icon_filename = "rebels.png";       break;
230     case METER_SPEED:
231         icon_filename = "speed.png";        break;
232     default:
233         return ClientUI::GetTexture(ClientUI::ArtDir() / "misc" / "missing.png", true); break;
234     }
235     return ClientUI::GetTexture(ClientUI::ArtDir() / "icons" / "meter" / icon_filename, true);
236 }
237 
BuildingIcon(const std::string & building_type_name)238 std::shared_ptr<GG::Texture> ClientUI::BuildingIcon(const std::string& building_type_name) {
239     const BuildingType* building_type = GetBuildingType(building_type_name);
240     std::string graphic_name;
241     if (building_type)
242         graphic_name = building_type->Icon();
243     if (graphic_name.empty())
244         return ClientUI::GetTexture(ArtDir() / "icons" / "building" / "generic_building.png", true);
245     return ClientUI::GetTexture(ArtDir() / graphic_name, true);
246 }
247 
CategoryIcon(const std::string & category_name)248 std::shared_ptr<GG::Texture> ClientUI::CategoryIcon(const std::string& category_name) {
249     std::string icon_filename;
250     if (const TechCategory* category = GetTechCategory(category_name))
251         return ClientUI::GetTexture(ArtDir() / "icons" / "tech" / "categories" / category->graphic, true);
252     else
253         return ClientUI::GetTexture(ClientUI::ArtDir() / "", true);
254 }
255 
TechIcon(const std::string & tech_name)256 std::shared_ptr<GG::Texture> ClientUI::TechIcon(const std::string& tech_name) {
257     const Tech* tech = GetTechManager().GetTech(tech_name);
258     std::string texture_name;
259     if (tech) {
260         texture_name = tech->Graphic();
261         if (texture_name.empty())
262             return CategoryIcon(tech->Category());
263     }
264     return ClientUI::GetTexture(ArtDir() / texture_name, true);
265 }
266 
SpecialIcon(const std::string & special_name)267 std::shared_ptr<GG::Texture> ClientUI::SpecialIcon(const std::string& special_name) {
268     const Special* special = GetSpecial(special_name);
269     std::string texture_name;
270     if (special)
271         texture_name = special->Graphic();
272     if (texture_name.empty())
273         return ClientUI::GetTexture(ArtDir() / "icons" / "specials_huge" / "generic_special.png", true);
274     return ClientUI::GetTexture(ArtDir() / texture_name, true);
275 }
276 
SpeciesIcon(const std::string & species_name)277 std::shared_ptr<GG::Texture> ClientUI::SpeciesIcon(const std::string& species_name) {
278     const Species* species = GetSpecies(species_name);
279     std::string texture_name;
280     if (species)
281         texture_name = species->Graphic();
282     if (texture_name.empty())
283         return ClientUI::GetTexture(ArtDir() / "icons" / "meter" / "pop.png", true);
284     return ClientUI::GetTexture(ArtDir() / texture_name, true);
285 }
286 
FieldTexture(const std::string & field_type_name)287 std::shared_ptr<GG::Texture> ClientUI::FieldTexture(const std::string& field_type_name) {
288     const FieldType* type = GetFieldType(field_type_name);
289     std::string texture_name;
290     if (type)
291         texture_name = type->Graphic();
292     if (texture_name.empty())
293         return ClientUI::GetTexture(ArtDir() / "fields" / "rainbow_storm.png", true);
294     return ClientUI::GetTexture(ArtDir() / texture_name, true);
295 }
296 
PartIcon(const std::string & part_name)297 std::shared_ptr<GG::Texture> ClientUI::PartIcon(const std::string& part_name) {
298     const ShipPart* part = GetShipPart(part_name);
299     std::string texture_name;
300     if (part)
301         texture_name = part->Icon();
302     if (texture_name.empty())
303         return ClientUI::GetTexture(ArtDir() / "icons" / "ship_parts" / "generic_part.png", true);
304     return ClientUI::GetTexture(ArtDir() / texture_name, false);
305 }
306 
HullTexture(const std::string & hull_name)307 std::shared_ptr<GG::Texture> ClientUI::HullTexture(const std::string& hull_name) {
308     const ShipHull* hull = GetShipHull(hull_name);
309     std::string texture_name;
310     if (hull) {
311         texture_name = hull->Graphic();
312         if (texture_name.empty())
313             texture_name = hull->Icon();
314     }
315     if (texture_name.empty())
316         return ClientUI::GetTexture(ArtDir() / "hulls_design" / "generic_hull.png", true);
317     return ClientUI::GetTexture(ArtDir() / texture_name, true);
318 }
319 
HullIcon(const std::string & hull_name)320 std::shared_ptr<GG::Texture> ClientUI::HullIcon(const std::string& hull_name) {
321     const ShipHull* hull = GetShipHull(hull_name);
322     std::string texture_name;
323     if (hull) {
324         texture_name = hull->Icon();
325         if (texture_name.empty())
326             texture_name = hull->Graphic();
327     }
328     if (texture_name.empty())
329         return ClientUI::GetTexture(ArtDir() / "icons" / "ship_hulls"/ "generic_hull.png", true);
330     return ClientUI::GetTexture(ArtDir() / texture_name, true);
331 }
332 
ShipDesignIcon(int design_id)333 std::shared_ptr<GG::Texture> ClientUI::ShipDesignIcon(int design_id) {
334     if (const ShipDesign* design = GetShipDesign(design_id)) {
335         const std::string& icon_name = design->Icon();
336         if (icon_name.empty())
337             return ClientUI::HullIcon(design->Hull());
338         else
339             return ClientUI::GetTexture(ArtDir() / icon_name, true);
340     }
341     return ClientUI::HullTexture("");
342 }
343 
344 
345 // tech screen
KnownTechFillColor()346 GG::Clr     ClientUI::KnownTechFillColor()                   { return GetOptionsDB().Get<GG::Clr>("ui.research.status.completed.background.color"); }
KnownTechTextAndBorderColor()347 GG::Clr     ClientUI::KnownTechTextAndBorderColor()          { return GetOptionsDB().Get<GG::Clr>("ui.research.status.completed.border.color"); }
ResearchableTechFillColor()348 GG::Clr     ClientUI::ResearchableTechFillColor()            { return GetOptionsDB().Get<GG::Clr>("ui.research.status.researchable.background.color"); }
ResearchableTechTextAndBorderColor()349 GG::Clr     ClientUI::ResearchableTechTextAndBorderColor()   { return GetOptionsDB().Get<GG::Clr>("ui.research.status.researchable.border.color"); }
UnresearchableTechFillColor()350 GG::Clr     ClientUI::UnresearchableTechFillColor()          { return GetOptionsDB().Get<GG::Clr>("ui.research.status.unresearchable.background.color"); }
UnresearchableTechTextAndBorderColor()351 GG::Clr     ClientUI::UnresearchableTechTextAndBorderColor() { return GetOptionsDB().Get<GG::Clr>("ui.research.status.unresearchable.border.color"); }
TechWndProgressBarBackgroundColor()352 GG::Clr     ClientUI::TechWndProgressBarBackgroundColor()    { return GetOptionsDB().Get<GG::Clr>("ui.research.status.progress.background.color"); }
TechWndProgressBarColor()353 GG::Clr     ClientUI::TechWndProgressBarColor()              { return GetOptionsDB().Get<GG::Clr>("ui.research.status.progress.color"); }
354 
CategoryColor(const std::string & category_name)355 GG::Clr     ClientUI::CategoryColor(const std::string& category_name) {
356     const TechCategory* category = GetTechCategory(category_name);
357     if (category)
358         return category->colour;
359     return GG::Clr();
360 }
361 
PlanetTypeFilePrefixes()362 std::map<PlanetType, std::string>& ClientUI::PlanetTypeFilePrefixes() {
363     static std::map<PlanetType, std::string> prefixes;
364     if (prefixes.empty()) {
365         prefixes[PT_SWAMP] =    "Swamp";
366         prefixes[PT_TOXIC] =    "Toxic";
367         prefixes[PT_INFERNO] =  "Inferno";
368         prefixes[PT_RADIATED] = "Radiated";
369         prefixes[PT_BARREN] =   "Barren";
370         prefixes[PT_TUNDRA] =   "Tundra";
371         prefixes[PT_DESERT] =   "Desert";
372         prefixes[PT_TERRAN] =   "Terran";
373         prefixes[PT_OCEAN] =    "Ocean";
374         prefixes[PT_GASGIANT] = "GasGiant";
375     }
376     return prefixes;
377 }
378 
StarTypeFilePrefixes()379 std::map<StarType, std::string>& ClientUI::StarTypeFilePrefixes() {
380     static std::map<StarType, std::string> prefixes;
381     if (prefixes.empty()) {
382         prefixes[INVALID_STAR_TYPE] =   "unknown";
383         prefixes[STAR_BLUE] =           "blue";
384         prefixes[STAR_WHITE] =          "white";
385         prefixes[STAR_YELLOW] =         "yellow";
386         prefixes[STAR_ORANGE] =         "orange";
387         prefixes[STAR_RED] =            "red";
388         prefixes[STAR_NEUTRON] =        "neutron";
389         prefixes[STAR_BLACK] =          "blackhole";
390         prefixes[STAR_NONE] =           "nostar";
391     }
392     return prefixes;
393 }
394 
HaloStarTypeFilePrefixes()395 std::map<StarType, std::string>& ClientUI::HaloStarTypeFilePrefixes() {
396     static std::map<StarType, std::string> prefixes;
397     if (prefixes.empty()) {
398         prefixes[INVALID_STAR_TYPE] =   "halo_unknown";
399         prefixes[STAR_BLUE] =           "halo_blue";
400         prefixes[STAR_WHITE] =          "halo_white";
401         prefixes[STAR_YELLOW] =         "halo_yellow";
402         prefixes[STAR_ORANGE] =         "halo_orange";
403         prefixes[STAR_RED] =            "halo_red";
404         prefixes[STAR_NEUTRON] =        "halo_neutron";
405         prefixes[STAR_BLACK] =          "halo_blackhole";
406         prefixes[STAR_NONE] =           "halo_nostar";
407     }
408     return prefixes;
409 }
410 
411 // private static members
412 ClientUI* ClientUI::s_the_UI = nullptr;
413 
operator <<(std::ostream & os,const GG::UnicodeCharset & chset)414 std::ostream& operator<< (std::ostream& os, const GG::UnicodeCharset& chset) {
415     os << chset.m_script_name << " " << chset.m_first_char << " " << chset.m_last_char << "\n";
416     return os;
417 }
418 
419 namespace {
RequiredCharsets()420     const std::vector<GG::UnicodeCharset>& RequiredCharsets() {
421         static std::vector<GG::UnicodeCharset> retval;
422         if (retval.empty()) {
423             // Basic Latin, Latin-1 Supplement, and Latin Extended-A
424             // (character sets needed to display the credits page)
425             const std::string CREDITS_STR = "AöŁ";
426             std::set<GG::UnicodeCharset> credits_charsets = GG::UnicodeCharsetsToRender(CREDITS_STR);
427 
428             std::set<GG::UnicodeCharset> stringtable_charsets;
429             {
430                 std::string file_name = GetOptionsDB().Get<std::string>("resource.stringtable.path");
431                 std::string stringtable_str;
432                 boost::filesystem::ifstream ifs(file_name);
433                 while (ifs) {
434                     std::string line;
435                     std::getline(ifs, line);
436                     stringtable_str += line;
437                     stringtable_str += '\n';
438                 }
439                 stringtable_charsets = GG::UnicodeCharsetsToRender(stringtable_str);
440                 DebugLogger() << "loading " << stringtable_charsets.size() << " charsets for current stringtable characters";
441             }
442 
443             if (!GetOptionsDB().IsDefaultValue("resource.stringtable.path")) {
444                 DebugLogger() << "Non-default stringtable!";
445                 std::string file_name = GetOptionsDB().GetDefault<std::string>("resource.stringtable.path");
446                 std::string stringtable_str;
447                 boost::filesystem::ifstream ifs(file_name);
448                 while (ifs) {
449                     std::string line;
450                     std::getline(ifs, line);
451                     stringtable_str += line;
452                     stringtable_str += '\n';
453                 }
454                 std::set<GG::UnicodeCharset> default_stringtable_charsets = GG::UnicodeCharsetsToRender(stringtable_str);
455                 DebugLogger() << "loading " << default_stringtable_charsets.size() << " charsets for default stringtable characters";
456 
457                 stringtable_charsets.insert(default_stringtable_charsets.begin(), default_stringtable_charsets.end());
458                 DebugLogger() << "combined stringtable charsets have " << stringtable_charsets.size() << " charsets";
459             }
460 
461             std::set_union(credits_charsets.begin(), credits_charsets.end(),
462                            stringtable_charsets.begin(), stringtable_charsets.end(),
463                            std::back_inserter(retval));
464 
465             std::string message_text = "Loading " + std::to_string(retval.size()) + " Unicode charsets: ";
466             for (const GG::UnicodeCharset& cs : retval)
467             { message_text += cs.m_script_name + ", "; }
468 
469             DebugLogger() << message_text;
470         }
471         return retval;
472     }
473 
474     // command-line options
AddOptions(OptionsDB & db)475     void AddOptions(OptionsDB& db) {
476         db.Add("video.fps.shown",                                       UserStringNop("OPTIONS_DB_SHOW_FPS"),                       false);
477         db.Add("video.fps.max.enabled",                                 UserStringNop("OPTIONS_DB_LIMIT_FPS"),                      true);
478         db.Add("video.fps.max",                                         UserStringNop("OPTIONS_DB_MAX_FPS"),                        60.0,                           RangedStepValidator<double>(1.0, 0.0, 240.0));
479         db.Add("video.fps.unfocused.enabled",                           UserStringNop("OPTIONS_DB_LIMIT_FPS_NO_FOCUS"),             true);
480         db.Add("video.fps.unfocused",                                   UserStringNop("OPTIONS_DB_MAX_FPS_NO_FOCUS"),               15.0,                           RangedStepValidator<double>(0.125, 0.125, 30.0));
481 
482         // sound and music
483         db.Add<std::string>("audio.music.path",                         UserStringNop("OPTIONS_DB_BG_MUSIC"),                       (GetRootDataDir() / "default" / "data" / "sound" / "artificial_intelligence_v3.ogg").string());
484         db.Add("audio.music.volume",                                    UserStringNop("OPTIONS_DB_MUSIC_VOLUME"),                   127,                            RangedValidator<int>(1, 255));
485         db.Add("audio.effects.volume",                                  UserStringNop("OPTIONS_DB_UI_SOUND_VOLUME"),                255,                            RangedValidator<int>(0, 255));
486         db.Add<std::string>("ui.button.rollover.sound.path",            UserStringNop("OPTIONS_DB_UI_SOUND_BUTTON_ROLLOVER"),       (GetRootDataDir() / "default" / "data" / "sound" / "button_rollover.ogg").string());
487         db.Add<std::string>("ui.button.press.sound.path",               UserStringNop("OPTIONS_DB_UI_SOUND_BUTTON_CLICK"),          (GetRootDataDir() / "default" / "data" / "sound" / "button_click.ogg").string());
488         db.Add<std::string>("ui.button.turn.press.sound.path",          UserStringNop("OPTIONS_DB_UI_SOUND_TURN_BUTTON_CLICK"),     (GetRootDataDir() / "default" / "data" / "sound" / "turn_button_click.ogg").string());
489         db.Add<std::string>("ui.listbox.select.sound.path",             UserStringNop("OPTIONS_DB_UI_SOUND_LIST_SELECT"),           (GetRootDataDir() / "default" / "data" / "sound" / "list_select.ogg").string());
490         db.Add<std::string>("ui.listbox.drop.sound.path",               UserStringNop("OPTIONS_DB_UI_SOUND_ITEM_DROP"),             (GetRootDataDir() / "default" / "data" / "sound" / "list_select.ogg").string());//TODO: replace with dedicated 'item_drop' sound
491         db.Add<std::string>("ui.dropdownlist.select.sound.path",        UserStringNop("OPTIONS_DB_UI_SOUND_LIST_PULLDOWN"),         (GetRootDataDir() / "default" / "data" / "sound" / "list_pulldown.ogg").string());
492         db.Add<std::string>("ui.input.keyboard.sound.path",             UserStringNop("OPTIONS_DB_UI_SOUND_TEXT_TYPING"),           (GetRootDataDir() / "default" / "data" / "sound" / "text_typing.ogg").string());
493         db.Add<std::string>("ui.window.minimize.sound.path",            UserStringNop("OPTIONS_DB_UI_SOUND_WINDOW_MAXIMIZE"),       (GetRootDataDir() / "default" / "data" / "sound" / "window_maximize.ogg").string());
494         db.Add<std::string>("ui.window.maximize.sound.path",            UserStringNop("OPTIONS_DB_UI_SOUND_WINDOW_MINIMIZE"),       (GetRootDataDir() / "default" / "data" / "sound" / "window_minimize.ogg").string());
495         db.Add<std::string>("ui.window.close.sound.path",               UserStringNop("OPTIONS_DB_UI_SOUND_WINDOW_CLOSE"),          (GetRootDataDir() / "default" / "data" / "sound" / "window_close.ogg").string());
496         db.Add<std::string>("ui.alert.sound.path",                      UserStringNop("OPTIONS_DB_UI_SOUND_ALERT"),                 (GetRootDataDir() / "default" / "data" / "sound" / "alert.ogg").string());
497         db.Add<std::string>("ui.map.fleet.button.rollover.sound.path",  UserStringNop("OPTIONS_DB_UI_SOUND_FLEET_BUTTON_ROLLOVER"), (GetRootDataDir() / "default" / "data" / "sound" / "fleet_button_rollover.ogg").string());
498         db.Add<std::string>("ui.map.fleet.button.press.sound.path",     UserStringNop("OPTIONS_DB_UI_SOUND_FLEET_BUTTON_CLICK"),    (GetRootDataDir() / "default" / "data" / "sound" / "fleet_button_click.ogg").string());
499         db.Add<std::string>("ui.map.system.icon.rollover.sound.path",   UserStringNop("OPTIONS_DB_UI_SOUND_SYSTEM_ICON_ROLLOVER"),  (GetRootDataDir() / "default" / "data" / "sound" / "fleet_button_rollover.ogg").string());
500         db.Add<std::string>("ui.map.sidepanel.open.sound.path",         UserStringNop("OPTIONS_DB_UI_SOUND_SIDEPANEL_OPEN"),        (GetRootDataDir() / "default" / "data" / "sound" / "sidepanel_open.ogg").string());
501         db.Add("ui.turn.start.sound.enabled",                           UserStringNop("OPTIONS_DB_UI_SOUND_NEWTURN_TOGGLE"),        false);
502         db.Add<std::string>("ui.turn.start.sound.path",                 UserStringNop("OPTIONS_DB_UI_SOUND_NEWTURN_FILE"),          (GetRootDataDir() / "default" / "data" / "sound" / "newturn.ogg").string());
503 
504         // fonts
505         db.Add<std::string>("ui.font.path",                             UserStringNop("OPTIONS_DB_UI_FONT"),                        (GetRootDataDir() / "default/data/fonts/Roboto-Regular.ttf").string());
506         db.Add<std::string>("ui.font.bold.path",                        UserStringNop("OPTIONS_DB_UI_FONT_BOLD"),                   (GetRootDataDir() / "default" / "data" / "fonts" / "Roboto-Bold.ttf").string());
507 #ifdef FREEORION_MACOSX
508         db.Add("ui.font.size",                                          UserStringNop("OPTIONS_DB_UI_FONT_SIZE"),                   15,                             RangedValidator<int>(4, 40));
509 #else
510         db.Add("ui.font.size",                                          UserStringNop("OPTIONS_DB_UI_FONT_SIZE"),                   16,                             RangedValidator<int>(4, 40));
511 #endif
512         db.Add<std::string>("ui.font.title.path",                       UserStringNop("OPTIONS_DB_UI_TITLE_FONT"),                  (GetRootDataDir() / "default/data/fonts/Roboto-Regular.ttf").string());
513 #ifdef FREEORION_MACOSX
514         db.Add("ui.font.title.size",                                    UserStringNop("OPTIONS_DB_UI_TITLE_FONT_SIZE"),             16,                             RangedValidator<int>(4, 40));
515 #else
516         db.Add("ui.font.title.size",                                    UserStringNop("OPTIONS_DB_UI_TITLE_FONT_SIZE"),             17,                             RangedValidator<int>(4, 40));
517 #endif
518 
519         // colors
520         db.Add("ui.window.background.color",                            UserStringNop("OPTIONS_DB_UI_WND_COLOR"),                   GG::Clr(35, 35, 35, 240),       Validator<GG::Clr>());
521         db.Add("ui.window.border.outer.color",                          UserStringNop("OPTIONS_DB_UI_WND_OUTER_BORDER_COLOR"),      GG::Clr(64, 64, 64, 255),       Validator<GG::Clr>());
522         db.Add("ui.window.border.inner.color",                          UserStringNop("OPTIONS_DB_UI_WND_INNER_BORDER_COLOR"),      GG::Clr(192, 192, 192, 255),    Validator<GG::Clr>());
523 
524         db.Add("ui.control.background.color",                           UserStringNop("OPTIONS_DB_UI_CTRL_COLOR"),                  GG::Clr(15, 15, 15, 255),       Validator<GG::Clr>());
525         db.Add("ui.control.border.color",                               UserStringNop("OPTIONS_DB_UI_CTRL_BORDER_COLOR"),           GG::Clr(124, 124, 124, 255),    Validator<GG::Clr>());
526 
527         db.Add("ui.dropdownlist.arrow.color",                           UserStringNop("OPTIONS_DB_UI_DROPDOWNLIST_ARROW_COLOR"),    GG::Clr(130, 130, 0, 255),      Validator<GG::Clr>());
528 
529         db.Add("ui.control.edit.highlight.color",                       UserStringNop("OPTIONS_DB_UI_EDIT_HILITE"),                 GG::Clr(43, 81, 102, 255),      Validator<GG::Clr>());
530 
531         db.Add("ui.font.stat.increase.color",                           UserStringNop("OPTIONS_DB_UI_STAT_INCREASE_COLOR"),         GG::Clr(0, 255, 0, 255),        Validator<GG::Clr>());
532         db.Add("ui.font.stat.decrease.color",                           UserStringNop("OPTIONS_DB_UI_STAT_DECREASE_COLOR"),         GG::Clr(255, 0, 0, 255),        Validator<GG::Clr>());
533 
534         db.Add("ui.button.state.color",                                 UserStringNop("OPTIONS_DB_UI_STATE_BUTTON_COLOR"),          GG::Clr(0, 127, 0, 255),        Validator<GG::Clr>());
535 
536         db.Add("ui.font.color",                                         UserStringNop("OPTIONS_DB_UI_TEXT_COLOR"),                  GG::Clr(255, 255, 255, 255),    Validator<GG::Clr>());
537         db.Add("ui.font.link.color",                                    UserStringNop("OPTIONS_DB_UI_DEFAULT_LINK_COLOR"),          GG::Clr(80, 255, 128, 255),     Validator<GG::Clr>());
538         db.Add("ui.font.link.rollover.color",                           UserStringNop("OPTIONS_DB_UI_ROLLOVER_LINK_COLOR"),         GG::Clr(192, 80, 255, 255),     Validator<GG::Clr>());
539 
540         db.Add("ui.research.status.completed.background.color",         UserStringNop("OPTIONS_DB_UI_KNOWN_TECH"),                  GG::Clr(72, 72, 72, 255),       Validator<GG::Clr>());
541         db.Add("ui.research.status.completed.border.color",             UserStringNop("OPTIONS_DB_UI_KNOWN_TECH_BORDER"),           GG::Clr(164, 164, 164, 255),    Validator<GG::Clr>());
542         db.Add("ui.research.status.researchable.background.color",      UserStringNop("OPTIONS_DB_UI_RESEARCHABLE_TECH"),           GG::Clr(48, 48, 48, 255),       Validator<GG::Clr>());
543         db.Add("ui.research.status.researchable.border.color",          UserStringNop("OPTIONS_DB_UI_RESEARCHABLE_TECH_BORDER"),    GG::Clr(164, 164, 164, 255),    Validator<GG::Clr>());
544         db.Add("ui.research.status.unresearchable.background.color",    UserStringNop("OPTIONS_DB_UI_UNRESEARCHABLE_TECH"),         GG::Clr(30, 30, 30, 255),       Validator<GG::Clr>());
545         db.Add("ui.research.status.unresearchable.border.color",        UserStringNop("OPTIONS_DB_UI_UNRESEARCHABLE_TECH_BORDER"),  GG::Clr(86, 86, 86, 255),       Validator<GG::Clr>());
546         db.Add("ui.research.status.progress.background.color",          UserStringNop("OPTIONS_DB_UI_TECH_PROGRESS_BACKGROUND"),    GG::Clr(72, 72, 72, 255),       Validator<GG::Clr>());
547         db.Add("ui.research.status.progress.color",                     UserStringNop("OPTIONS_DB_UI_TECH_PROGRESS"),               GG::Clr(40, 40, 40, 255),       Validator<GG::Clr>());
548 
549         // misc
550         db.Add("ui.scroll.width",                                       UserStringNop("OPTIONS_DB_UI_SCROLL_WIDTH"),                14,                             RangedValidator<int>(8, 30));
551 
552         // UI behavior
553         db.Add("ui.tooltip.delay",                                      UserStringNop("OPTIONS_DB_UI_TOOLTIP_DELAY"),               500,                            RangedValidator<int>(0, 3000));
554         db.Add("ui.tooltip.extended.delay",                             UserStringNop("OPTIONS_DB_UI_TOOLTIP_LONG_DELAY"),          3500,                           RangedValidator<int>(0, 30000));
555         db.Add("ui.fleet.multiple.enabled",                             UserStringNop("OPTIONS_DB_UI_MULTIPLE_FLEET_WINDOWS"),      false);
556         db.Add("ui.quickclose.enabled",                                 UserStringNop("OPTIONS_DB_UI_WINDOW_QUICKCLOSE"),           true);
557         db.Add("ui.reposition.auto.enabled",                            UserStringNop("OPTIONS_DB_UI_AUTO_REPOSITION_WINDOWS"),     true);
558 
559         // UI behavior, hidden options
560         // currently lacking an options page widget, so can only be user-adjusted by manually editing config file or specifying on command line
561         db.Add("ui.design.pedia.title.dynamic.enabled",                 UserStringNop("OPTIONS_DB_DESIGN_PEDIA_DYNAMIC"),           false);
562         db.Add("ui.map.fleet.eta.shown",                                UserStringNop("OPTIONS_DB_SHOW_FLEET_ETA"),                 true);
563         db.Add("ui.name.id.shown",                                      UserStringNop("OPTIONS_DB_SHOW_IDS_AFTER_NAMES"),           false);
564 
565         // Other
566         db.Add("resource.shipdesign.saved.enabled",                     UserStringNop("OPTIONS_DB_AUTO_ADD_SAVED_DESIGNS"),         true);
567         db.Add("resource.shipdesign.default.enabled",                   UserStringNop("OPTIONS_DB_ADD_DEFAULT_DESIGNS"),            true);
568 
569     }
570     bool temp_bool = RegisterOptions(&AddOptions);
571 
572     const GG::Y PANEL_HEIGHT(160); // Also formerly "UI.chat-panel-height" default
573     const GG::X MESSAGE_PANEL_WIDTH(345); // Formerly "UI.chat-panel-width" default
574     const GG::X PLAYER_LIST_PANEL_WIDTH(445);
575 
576     const std::string MESSAGE_WND_NAME = "map.messages";
577     const std::string PLAYER_LIST_WND_NAME = "map.empires";
578 
579     template <typename OptionType, typename PredicateType>
ConditionalForward(const std::string & option_name,const OptionsDB::OptionChangedSignalType::slot_type & slot,OptionType ref_val,PredicateType pred)580     void ConditionalForward(const std::string& option_name,
581                             const OptionsDB::OptionChangedSignalType::slot_type& slot,
582                             OptionType ref_val,
583                             PredicateType pred)
584     {
585         if (pred(GetOptionsDB().Get<OptionType>(option_name), ref_val))
586             slot();
587     }
588 
589     template <typename OptionType, typename PredicateType>
ConditionalConnectOption(const std::string & option_name,const OptionsDB::OptionChangedSignalType::slot_type & slot,OptionType ref_val,PredicateType pred)590     void ConditionalConnectOption(const std::string& option_name,
591                                   const OptionsDB::OptionChangedSignalType::slot_type& slot,
592                                   OptionType ref_val,
593                                   PredicateType pred)
594     {
595         GetOptionsDB().OptionChangedSignal(option_name).connect(
596             boost::bind(&ConditionalForward<OptionType, PredicateType>,
597                         option_name, slot, ref_val, pred));
598     }
599 }
600 
601 
602 ////////////////////////////////////////////////
603 // ClientUI
604 ////////////////////////////////////////////////
ClientUI()605 ClientUI::ClientUI() :
606     m_ship_designs(new ShipDesignManager())
607 {
608     s_the_UI = this;
609     Hotkey::ReadFromOptions(GetOptionsDB());
610 
611     // Remove all window properties if asked to
612     if (GetOptionsDB().Get<bool>("window-reset"))
613         CUIWnd::InvalidateUnusedOptions();
614 
615     InitializeWindows();
616 
617     GetOptionsDB().OptionChangedSignal("video.fullscreen.width").connect(
618         boost::bind(&ClientUI::HandleSizeChange, this, true));
619     GetOptionsDB().OptionChangedSignal("video.fullscreen.height").connect(
620         boost::bind(&ClientUI::HandleSizeChange, this, true));
621     GetOptionsDB().OptionChangedSignal("video.windowed.width").connect(
622         boost::bind(&ClientUI::HandleSizeChange, this, false));
623     GetOptionsDB().OptionChangedSignal("video.windowed.height").connect(
624         boost::bind(&ClientUI::HandleSizeChange, this, false));
625     HumanClientApp::GetApp()->RepositionWindowsSignal.connect(
626         boost::bind(&ClientUI::InitializeWindows, this));
627     HumanClientApp::GetApp()->RepositionWindowsSignal.connect(
628         &CUIWnd::InvalidateUnusedOptions,
629         boost::signals2::at_front);
630 
631     // Connected at front to make sure CUIWnd::LoadOptions() doesn't overwrite
632     // the values we're checking here...
633     HumanClientApp::GetApp()->FullscreenSwitchSignal.connect(
634         boost::bind(&ClientUI::HandleFullscreenSwitch, this),
635         boost::signals2::at_front);
636 
637     ConditionalConnectOption("ui.reposition.auto.enabled",
638                              HumanClientApp::GetApp()->RepositionWindowsSignal,
639                              true, std::equal_to<bool>());
640 
641     // Set the root path for image tags in rich text.
642     GG::ImageBlock::SetDefaultImagePath(ArtDir().string());
643 }
644 
~ClientUI()645 ClientUI::~ClientUI()
646 { s_the_UI = nullptr; }
647 
GetMapWnd()648 std::shared_ptr<MapWnd> ClientUI::GetMapWnd() {
649     static bool initialized = m_map_wnd ? true : (m_map_wnd = GG::Wnd::Create<MapWnd>()) != nullptr;
650     (void)initialized; // Hide unused variable warning
651     return m_map_wnd;
652 }
653 
GetMapWndConst() const654 MapWnd const* ClientUI::GetMapWndConst() const {
655     static bool initialized = m_map_wnd ? true : (m_map_wnd = GG::Wnd::Create<MapWnd>()) != nullptr;
656     (void)initialized; // Hide unused variable warning
657     return m_map_wnd.get();
658 }
659 
GetMessageWnd()660 std::shared_ptr<MessageWnd> ClientUI::GetMessageWnd() {
661     if (!m_message_wnd)
662         m_message_wnd = GG::Wnd::Create<MessageWnd>(GG::INTERACTIVE | GG::DRAGABLE | GG::ONTOP | GG::RESIZABLE |
663                                                     CLOSABLE | PINABLE, MESSAGE_WND_NAME);
664     return m_message_wnd;
665 }
666 
GetPlayerListWnd()667 std::shared_ptr<PlayerListWnd> ClientUI::GetPlayerListWnd() {
668     if (!m_player_list_wnd)
669         m_player_list_wnd = GG::Wnd::Create<PlayerListWnd>(PLAYER_LIST_WND_NAME);
670     return m_player_list_wnd;
671 }
672 
GetIntroScreen()673 std::shared_ptr<IntroScreen> ClientUI::GetIntroScreen() {
674     if (!m_intro_screen)
675         m_intro_screen = GG::Wnd::Create<IntroScreen>();
676     return m_intro_screen;
677 }
678 
ShowIntroScreen()679 void ClientUI::ShowIntroScreen() {
680     if (m_map_wnd) {
681         HumanClientApp::GetApp()->Remove(m_map_wnd);
682         m_map_wnd->RemoveWindows();
683         m_map_wnd->Hide();
684     }
685 
686     // Update intro screen Load & Continue buttons if all savegames are deleted.
687     GetIntroScreen()->RequirePreRender();
688 
689     HumanClientApp::GetApp()->Register(GetIntroScreen());
690     HumanClientApp::GetApp()->Remove(m_message_wnd);
691     HumanClientApp::GetApp()->Remove(m_player_list_wnd);
692     HumanClientApp::GetApp()->Remove(m_multiplayer_lobby_wnd);
693 }
694 
ShowMultiPlayerLobbyWnd()695 void ClientUI::ShowMultiPlayerLobbyWnd() {
696     if (m_map_wnd) {
697         HumanClientApp::GetApp()->Remove(m_map_wnd);
698         m_map_wnd->RemoveWindows();
699         m_map_wnd->Hide();
700     }
701 
702     HumanClientApp::GetApp()->Register(GetMultiPlayerLobbyWnd());
703     HumanClientApp::GetApp()->Remove(m_message_wnd);
704     HumanClientApp::GetApp()->Remove(m_player_list_wnd);
705     HumanClientApp::GetApp()->Remove(m_intro_screen);
706 }
707 
GetMultiPlayerLobbyWnd()708 std::shared_ptr<MultiPlayerLobbyWnd> ClientUI::GetMultiPlayerLobbyWnd() {
709     if (!m_multiplayer_lobby_wnd)
710         m_multiplayer_lobby_wnd = GG::Wnd::Create<MultiPlayerLobbyWnd>();
711     return m_multiplayer_lobby_wnd;
712 }
713 
GetPasswordEnterWnd()714 std::shared_ptr<PasswordEnterWnd> ClientUI::GetPasswordEnterWnd() {
715     if (!m_password_enter_wnd)
716         m_password_enter_wnd = GG::Wnd::Create<PasswordEnterWnd>();
717     return m_password_enter_wnd;
718 }
719 
GetSaveFileDialog()720 std::shared_ptr<SaveFileDialog> ClientUI::GetSaveFileDialog()
721 { return m_savefile_dialog; }
722 
GetFilenameWithSaveFileDialog(const SaveFileDialog::Purpose purpose,const SaveFileDialog::SaveType type)723 std::string ClientUI::GetFilenameWithSaveFileDialog(
724     const SaveFileDialog::Purpose purpose, const SaveFileDialog::SaveType type)
725 {
726     // There can only be a single savefile_dialog at a time, becauase it is for
727     // a specific purpose.
728     if (m_savefile_dialog)
729         return "";
730 
731     m_savefile_dialog = GG::Wnd::Create<SaveFileDialog>(purpose, type);
732 
733     m_savefile_dialog->Run();
734     auto filename = m_savefile_dialog->Result();
735 
736     m_savefile_dialog = nullptr;
737     return filename;
738 }
739 
GetSaveGameUIData(SaveGameUIData & data) const740 void ClientUI::GetSaveGameUIData(SaveGameUIData& data) const {
741     GetMapWndConst()->GetSaveGameUIData(data);
742     m_ship_designs->Save(data);
743 }
744 
FormatTimestamp(boost::posix_time::ptime timestamp)745 std::string ClientUI::FormatTimestamp(boost::posix_time::ptime timestamp) {
746     TraceLogger() << "ClientUI::FormatTimestamp(" << timestamp << ")";
747     if (DisplayTimestamp()) {
748         std::stringstream date_format_sstream;
749         // Set facet to format timestamp in chat.
750         static auto facet = new boost::posix_time::time_facet("[%d %b %H:%M:%S] ");
751         static std::locale dt_locale(GetLocale(), facet);
752         TraceLogger() << "ClientUI::FormatTimestamp locale: " << dt_locale.name();
753         date_format_sstream.str("");
754         date_format_sstream.clear();
755         date_format_sstream.imbue(dt_locale);
756         // Determine local time from provided UTC timestamp
757         auto local_timestamp = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local(timestamp);
758         date_format_sstream << local_timestamp;
759         TraceLogger() << "ClientUI::FormatTimestamp date formatted: " << date_format_sstream.str()
760                       << " Valid utf8?: " << (IsValidUTF8(date_format_sstream.str()) ? "yes" : "no");
761 
762         return date_format_sstream.str();
763     }
764     return "";
765 }
766 
ZoomToObject(const std::string & name)767 bool ClientUI::ZoomToObject(const std::string& name) {
768     // try first by finding the object by name
769     for (auto& obj : GetUniverse().Objects().all<UniverseObject>())
770         if (boost::iequals(obj->Name(), name))
771             return ZoomToObject(obj->ID());
772 
773     // try again by converting string to an ID
774     try {
775         return ZoomToObject(std::stoi(name));
776     } catch (...) {
777     }
778 
779     return false;
780 }
781 
ZoomToObject(int id)782 bool ClientUI::ZoomToObject(int id) {
783     return ZoomToSystem(id)     || ZoomToPlanet(id) || ZoomToBuilding(id)   ||
784            ZoomToFleet(id)      || ZoomToShip(id)   || ZoomToField(id);
785 }
786 
ZoomToPlanet(int id)787 bool ClientUI::ZoomToPlanet(int id) {
788     if (auto planet = Objects().get<Planet>(id)) {
789         GetMapWnd()->CenterOnObject(planet->SystemID());
790         GetMapWnd()->SelectSystem(planet->SystemID());
791         GetMapWnd()->SelectPlanet(id);
792         return true;
793     }
794     return false;
795 }
796 
ZoomToPlanetPedia(int id)797 bool ClientUI::ZoomToPlanetPedia(int id) {
798     if (Objects().get<Planet>(id))
799         GetMapWnd()->ShowPlanet(id);
800     return false;
801 }
802 
ZoomToSystem(int id)803 bool ClientUI::ZoomToSystem(int id) {
804     if (auto system = Objects().get<System>(id)) {
805         ZoomToSystem(system);
806         return true;
807     }
808     return false;
809 }
810 
ZoomToFleet(int id)811 bool ClientUI::ZoomToFleet(int id) {
812     if (auto fleet = Objects().get<Fleet>(id)) {
813         ZoomToFleet(fleet);
814         return true;
815     }
816     return false;
817 }
818 
ZoomToShip(int id)819 bool ClientUI::ZoomToShip(int id) {
820     if (auto ship = Objects().get<Ship>(id))
821         return ZoomToFleet(ship->FleetID());
822     return false;
823 }
824 
ZoomToBuilding(int id)825 bool ClientUI::ZoomToBuilding(int id) {
826     if (auto building = Objects().get<Building>(id)) {
827         ZoomToBuildingType(building->BuildingTypeName());
828         return ZoomToPlanet(building->PlanetID());
829     }
830     return false;
831 }
832 
ZoomToField(int id)833 bool ClientUI::ZoomToField(int id) {
834     //if (auto field = Objects().get<Field>(id)) {
835     //  // TODO: implement this
836     //}
837     return false;
838 }
839 
ZoomToCombatLog(int id)840 bool ClientUI::ZoomToCombatLog(int id) {
841     if (GetCombatLogManager().GetLog(id)) {
842         GetMapWnd()->ShowCombatLog(id);
843         return true;
844     }
845     return false;
846 }
847 
ZoomToSystem(std::shared_ptr<const System> system)848 void ClientUI::ZoomToSystem(std::shared_ptr<const System> system) {
849     if (!system)
850         return;
851 
852     GetMapWnd()->CenterOnObject(system->ID());
853     GetMapWnd()->SelectSystem(system->ID());
854 }
855 
ZoomToFleet(std::shared_ptr<const Fleet> fleet)856 void ClientUI::ZoomToFleet(std::shared_ptr<const Fleet> fleet) {
857     if (!fleet)
858         return;
859 
860     GetMapWnd()->CenterOnObject(fleet->ID());
861     GetMapWnd()->SelectFleet(fleet->ID());
862     if (const auto& fleet_wnd = FleetUIManager::GetFleetUIManager().WndForFleetID(fleet->ID()))
863         fleet_wnd->SelectFleet(fleet->ID());
864 }
865 
ZoomToContent(const std::string & name,bool reverse_lookup)866 bool ClientUI::ZoomToContent(const std::string& name, bool reverse_lookup/* = false*/) {
867     if (reverse_lookup) {
868         for (const auto& tech : GetTechManager()) {
869             if (boost::iequals(name, UserString(tech->Name())))
870                 return ZoomToTech(tech->Name());
871         }
872 
873         for (const auto& entry : GetBuildingTypeManager())
874             if (boost::iequals(name, UserString(entry.first)))
875                 return ZoomToBuildingType(entry.first);
876 
877         for (const auto& special_name : SpecialNames())
878             if (boost::iequals(name, UserString(special_name)))
879                 return ZoomToSpecial(special_name);
880 
881         for (const auto& entry : GetShipHullManager())
882             if (boost::iequals(name, UserString(entry.first)))
883                 return ZoomToShipHull(entry.first);
884 
885         for (const auto& entry : GetShipPartManager())
886             if (boost::iequals(name, UserString(entry.first)))
887                 return ZoomToShipPart(entry.first);
888 
889         for (const auto& entry : GetSpeciesManager())
890             if (boost::iequals(name, UserString(entry.first)))
891                 return ZoomToSpecies(entry.first);
892 
893         return false;
894     } else {
895         // attempt to zoom to named content
896         bool success =  ZoomToTech(name)     || ZoomToBuildingType(name) || ZoomToSpecial(name) ||
897                         ZoomToShipHull(name) || ZoomToShipPart(name)     || ZoomToSpecies(name);
898         if (success)
899             return true;
900         // attempt to find a shipdesign with this name
901         // attempt to find empire with this name
902         return false;
903     }
904 }
905 
ZoomToTech(const std::string & tech_name)906 bool ClientUI::ZoomToTech(const std::string& tech_name) {
907     if (!GetTech(tech_name))
908         return false;
909     GetMapWnd()->ShowTech(tech_name);
910     return true;
911 }
912 
ZoomToBuildingType(const std::string & building_type_name)913 bool ClientUI::ZoomToBuildingType(const std::string& building_type_name) {
914     if (!GetBuildingType(building_type_name))
915         return false;
916     GetMapWnd()->ShowBuildingType(building_type_name);
917     return true;
918 }
919 
ZoomToSpecial(const std::string & special_name)920 bool ClientUI::ZoomToSpecial(const std::string& special_name) {
921     if (!GetSpecial(special_name))
922         return false;
923     GetMapWnd()->ShowSpecial(special_name);
924     return true;
925 }
926 
ZoomToShipHull(const std::string & hull_name)927 bool ClientUI::ZoomToShipHull(const std::string& hull_name) {
928     if (!GetShipHull(hull_name))
929         return false;
930     GetMapWnd()->ShowShipHull(hull_name);
931     return true;
932 }
933 
ZoomToShipPart(const std::string & part_name)934 bool ClientUI::ZoomToShipPart(const std::string& part_name) {
935     if (!GetShipPart(part_name))
936         return false;
937     GetMapWnd()->ShowShipPart(part_name);
938     return true;
939 }
940 
ZoomToSpecies(const std::string & species_name)941 bool ClientUI::ZoomToSpecies(const std::string& species_name) {
942     if (!GetSpecies(species_name))
943         return false;
944     GetMapWnd()->ShowSpecies(species_name);
945     return true;
946 }
947 
ZoomToFieldType(const std::string & field_type_name)948 bool ClientUI::ZoomToFieldType(const std::string& field_type_name) {
949     if (!GetFieldType(field_type_name))
950         return false;
951     GetMapWnd()->ShowFieldType(field_type_name);
952     return true;
953 }
954 
ZoomToShipDesign(int design_id)955 bool ClientUI::ZoomToShipDesign(int design_id) {
956     if (!GetShipDesign(design_id))
957         return false;
958     GetMapWnd()->ShowShipDesign(design_id);
959     return true;
960 }
961 
ZoomToEmpire(int empire_id)962 bool ClientUI::ZoomToEmpire(int empire_id) {
963     if (!GetEmpire(empire_id))
964         return false;
965     GetMapWnd()->ShowEmpire(empire_id);
966     return true;
967 }
968 
ZoomToMeterTypeArticle(const std::string & meter_string)969 bool ClientUI::ZoomToMeterTypeArticle(const std::string& meter_string) {
970     GetMapWnd()->ShowMeterTypeArticle(meter_string);
971     return true;
972 }
973 
ZoomToEncyclopediaEntry(const std::string & str)974 bool ClientUI::ZoomToEncyclopediaEntry(const std::string& str) {
975     GetMapWnd()->ShowEncyclopediaEntry(str);
976     return true;
977 }
978 
DumpObject(int object_id)979 void ClientUI::DumpObject(int object_id) {
980     auto obj = Objects().get(object_id);
981     if (!obj)
982         return;
983     m_message_wnd->HandleLogMessage(obj->Dump() + "\n");
984 }
985 
InitializeWindows()986 void ClientUI::InitializeWindows() {
987     const GG::Pt message_ul(GG::X0, GG::GUI::GetGUI()->AppHeight() - PANEL_HEIGHT);
988     const GG::Pt message_wh(MESSAGE_PANEL_WIDTH, PANEL_HEIGHT);
989 
990     const GG::Pt player_list_ul(MESSAGE_PANEL_WIDTH, GG::GUI::GetGUI()->AppHeight() - PANEL_HEIGHT);
991     const GG::Pt player_list_wh(PLAYER_LIST_PANEL_WIDTH, PANEL_HEIGHT);
992 
993     GetMessageWnd()->InitSizeMove(message_ul,     message_ul + message_wh);
994     GetPlayerListWnd()->InitSizeMove(player_list_ul, player_list_ul + player_list_wh);
995 }
996 
HandleSizeChange(bool fullscreen) const997 void ClientUI::HandleSizeChange(bool fullscreen) const {
998     OptionsDB& db = GetOptionsDB();
999 
1000     if (db.Get<bool>("ui.reposition.auto.enabled")) {
1001         std::string window_mode = fullscreen ? ".fullscreen" : ".windowed";
1002         std::string option_name = "ui." + MESSAGE_WND_NAME + window_mode + ".left";
1003 
1004         // Invalidate the message window position so that we know to
1005         // recalculate positions on the next resize or fullscreen switch...
1006         db.Set<int>(option_name, db.GetDefault<int>(option_name));
1007     }
1008 }
1009 
HandleFullscreenSwitch() const1010 void ClientUI::HandleFullscreenSwitch() const {
1011     OptionsDB& db = GetOptionsDB();
1012 
1013     std::string window_mode = db.Get<bool>("video.fullscreen.enabled") ? ".fullscreen" : ".windowed";
1014 
1015     // Check if the message window position has been invalidated as a stand-in
1016     // for actually checking if all windows have been given valid positions for
1017     // this video mode... (the default value is
1018     // std::numeric_limits<GG::X::value_type>::min(), defined in UI/CUIWnd.cpp).
1019     // This relies on the message window not supplying a default position to
1020     // the CUIWnd constructor...
1021     std::string option_name = "ui." + MESSAGE_WND_NAME + window_mode + ".left";
1022     if (db.Get<int>(option_name) == db.GetDefault<int>(option_name)) {
1023         HumanClientApp::GetApp()->RepositionWindowsSignal();
1024     }
1025 }
1026 
GetRandomTexture(const boost::filesystem::path & dir,const std::string & prefix,bool mipmap)1027 std::shared_ptr<GG::Texture> ClientUI::GetRandomTexture(const boost::filesystem::path& dir,
1028                                                         const std::string& prefix, bool mipmap/* = false*/)
1029 {
1030     TexturesAndDist prefixed_textures_and_dist = PrefixedTexturesAndDist(dir, prefix, mipmap);
1031     return prefixed_textures_and_dist.first[(*prefixed_textures_and_dist.second)()];
1032 }
1033 
GetModuloTexture(const boost::filesystem::path & dir,const std::string & prefix,int n,bool mipmap)1034 std::shared_ptr<GG::Texture> ClientUI::GetModuloTexture(const boost::filesystem::path& dir,
1035                                                         const std::string& prefix, int n, bool mipmap/* = false*/)
1036 {
1037     assert(0 <= n);
1038     TexturesAndDist prefixed_textures_and_dist = PrefixedTexturesAndDist(dir, prefix, mipmap);
1039     return prefixed_textures_and_dist.first.empty() ?
1040         nullptr :
1041         prefixed_textures_and_dist.first[n % prefixed_textures_and_dist.first.size()];
1042 }
1043 
GetPrefixedTextures(const boost::filesystem::path & dir,const std::string & prefix,bool mipmap)1044 std::vector<std::shared_ptr<GG::Texture>> ClientUI::GetPrefixedTextures(const boost::filesystem::path& dir,
1045                                                                         const std::string& prefix, bool mipmap/* = false*/)
1046 {
1047     TexturesAndDist prefixed_textures_and_dist = PrefixedTexturesAndDist(dir, prefix, mipmap);
1048     return prefixed_textures_and_dist.first;
1049 }
1050 
RestoreFromSaveData(const SaveGameUIData & ui_data)1051 void ClientUI::RestoreFromSaveData(const SaveGameUIData& ui_data) {
1052     GetMapWnd()->RestoreFromSaveData(ui_data);
1053     m_ship_designs->Load(ui_data);
1054 }
1055 
GetClientUI()1056 ClientUI* ClientUI::GetClientUI()
1057 { return s_the_UI; }
1058 
MessageBox(const std::string & message,bool play_alert_sound)1059 void ClientUI::MessageBox(const std::string& message, bool play_alert_sound/* = false*/) {
1060     auto dlg = GG::GUI::GetGUI()->GetStyleFactory()->NewThreeButtonDlg(
1061         GG::X(320), GG::Y(200), message, GetFont(Pts()+2),
1062         WndColor(), WndOuterBorderColor(), CtrlColor(), TextColor(),
1063         1, UserString("OK"));
1064     if (play_alert_sound)
1065         Sound::GetSound().PlaySound(GetOptionsDB().Get<std::string>("ui.alert.sound.path"), true);
1066     dlg->Run();
1067 }
1068 
GetTexture(const boost::filesystem::path & path,bool mipmap)1069 std::shared_ptr<GG::Texture> ClientUI::GetTexture(const boost::filesystem::path& path, bool mipmap/* = false*/) {
1070     std::shared_ptr<GG::Texture> retval;
1071     try {
1072         retval = HumanClientApp::GetApp()->GetTexture(path, mipmap);
1073     } catch (const std::exception& e) {
1074         ErrorLogger() << "Unable to load texture \"" + path.generic_string() + "\"\n"
1075             "reason: " << e.what();
1076         retval = HumanClientApp::GetApp()->GetTexture(ClientUI::ArtDir() / "misc" / "missing.png", mipmap);
1077     } catch (...) {
1078         ErrorLogger() << "Unable to load texture \"" + path.generic_string() + "\"\n"
1079             "reason unknown...?";
1080         retval = HumanClientApp::GetApp()->GetTexture(ClientUI::ArtDir() / "misc" / "missing.png", mipmap);
1081     }
1082 #ifdef FREEORION_MACOSX
1083     if (!mipmap)
1084         retval->SetFilters(GL_LINEAR, GL_LINEAR);
1085 #endif
1086     return retval;
1087 }
1088 
GetFont(int pts)1089 std::shared_ptr<GG::Font> ClientUI::GetFont(int pts/* = Pts()*/) {
1090      try {
1091         return GG::GUI::GetGUI()->GetFont(GetOptionsDB().Get<std::string>("ui.font.path"), pts, RequiredCharsets().begin(), RequiredCharsets().end());
1092      } catch (...) {
1093          try {
1094             return GG::GUI::GetGUI()->GetFont(GetOptionsDB().GetDefault<std::string>("ui.font.path"),
1095                                               pts, RequiredCharsets().begin(), RequiredCharsets().end());
1096         } catch (...) {
1097              return GG::GUI::GetGUI()->GetStyleFactory()->DefaultFont(pts);
1098         }
1099     }
1100 }
1101 
GetBoldFont(int pts)1102 std::shared_ptr<GG::Font> ClientUI::GetBoldFont(int pts/* = Pts()*/) {
1103     try {
1104         return GG::GUI::GetGUI()->GetFont(GetOptionsDB().Get<std::string>("ui.font.bold.path"), pts, RequiredCharsets().begin(), RequiredCharsets().end());
1105     } catch (...) {
1106         try {
1107              return GG::GUI::GetGUI()->GetFont(GetOptionsDB().GetDefault<std::string>("ui.font.bold.path"),
1108                                                pts, RequiredCharsets().begin(), RequiredCharsets().end());
1109         } catch (...) {
1110              return GG::GUI::GetGUI()->GetStyleFactory()->DefaultFont(pts);
1111         }
1112     }
1113 }
1114 
GetTitleFont(int pts)1115 std::shared_ptr<GG::Font> ClientUI::GetTitleFont(int pts/* = TitlePts()*/) {
1116     try {
1117         return GG::GUI::GetGUI()->GetFont(GetOptionsDB().Get<std::string>("ui.font.title.path"), pts, RequiredCharsets().begin(), RequiredCharsets().end());
1118     } catch (...) {
1119         try {
1120             return GG::GUI::GetGUI()->GetFont(GetOptionsDB().GetDefault<std::string>("ui.font.title.path"),
1121                                               pts, RequiredCharsets().begin(), RequiredCharsets().end());
1122         } catch (...) {
1123              return GG::GUI::GetGUI()->GetStyleFactory()->DefaultFont(pts);
1124         }
1125    }
1126 }
1127 
PrefixedTexturesAndDist(const boost::filesystem::path & dir,const std::string & prefix,bool mipmap)1128 ClientUI::TexturesAndDist ClientUI::PrefixedTexturesAndDist(const boost::filesystem::path& dir,
1129                                                             const std::string& prefix, bool mipmap)
1130 {
1131     namespace fs = boost::filesystem;
1132     assert(fs::is_directory(dir));
1133     const std::string KEY = dir.string() + "/" + prefix;
1134     auto prefixed_textures_it = m_prefixed_textures.find(KEY);
1135     if (prefixed_textures_it == m_prefixed_textures.end()) {
1136         prefixed_textures_it = m_prefixed_textures.insert({KEY, TexturesAndDist()}).first;
1137         auto& textures = prefixed_textures_it->second.first;
1138         auto& rand_int = prefixed_textures_it->second.second;
1139         fs::directory_iterator end_it;
1140         for (fs::directory_iterator it(dir); it != end_it; ++it) {
1141             try {
1142                 if (fs::exists(*it) && !fs::is_directory(*it) && boost::algorithm::starts_with(it->path().filename().string(), prefix))
1143                     textures.push_back(ClientUI::GetTexture(*it, mipmap));
1144             } catch (const fs::filesystem_error& e) {
1145                 // ignore files for which permission is denied, and rethrow other exceptions
1146                 if (e.code() != boost::system::errc::permission_denied)
1147                     throw;
1148             }
1149         }
1150         rand_int.reset(new SmallIntDistType(SmallIntDist(0, textures.size() - 1)));
1151         std::sort(textures.begin(), textures.end(), TextureFileNameCompare);
1152     }
1153     return prefixed_textures_it->second;
1154 }
1155 
FontBasedUpscale(int x)1156 int FontBasedUpscale(int x) {
1157     int retval(x);
1158     int font_pts = ClientUI::Pts();
1159     if (font_pts > 12) {
1160         retval *= static_cast<float>(font_pts) / 12.0f;
1161     }
1162     return retval;
1163 }
1164 
1165 namespace GG {
operator >>(std::istream & is,Clr & clr)1166     std::istream& operator>>(std::istream& is, Clr& clr) {
1167         namespace qi = boost::spirit::qi;
1168         namespace phx = boost::phoenix;
1169         std::string str;
1170         std::getline(is, str, ')');
1171         str.push_back(')');
1172         bool parsed = qi::phrase_parse(
1173             str.begin(), str.end(),
1174             (
1175                 qi::lit('(') >>
1176                 qi::int_[phx::ref(clr.r) = qi::_1] >> ',' >>
1177                 qi::int_[phx::ref(clr.g) = qi::_1] >> ',' >>
1178                 qi::int_[phx::ref(clr.b) = qi::_1] >> ',' >>
1179                 qi::int_[phx::ref(clr.a) = qi::_1] >> ')'),
1180             qi::blank
1181         );
1182         if (!parsed ||
1183             clr.r < 0 || 255 < clr.r ||
1184             clr.g < 0 || 255 < clr.g ||
1185             clr.b < 0 || 255 < clr.b ||
1186             clr.a < 0 || 255 < clr.a)
1187             is.setstate(std::ios_base::failbit);
1188         return is;
1189     }
1190 }
1191