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