1 #include "OptionsWnd.h"
2
3 #include "../client/human/HumanClientApp.h"
4 #include "../util/Directories.h"
5 #include "../util/i18n.h"
6 #include "../util/Logger.h"
7 #include "../util/LoggerWithOptionsDB.h"
8
9 #include "ClientUI.h"
10 #include "CUISpin.h"
11 #include "CUISlider.h"
12 #include "Sound.h"
13 #include "Hotkeys.h"
14
15 #include <GG/GUI.h>
16 #include <GG/Layout.h>
17 #include <GG/TabWnd.h>
18
19 #include <boost/spirit/include/qi.hpp>
20 #include <boost/spirit/include/phoenix_operator.hpp>
21
22 #include <boost/format.hpp>
23 #include <boost/algorithm/string/predicate.hpp>
24 #include <boost/filesystem/operations.hpp>
25 #include <regex>
26
27
28 namespace fs = boost::filesystem;
29
30 namespace {
31 const GG::X PAGE_WIDTH(400);
32 const GG::Y PAGE_HEIGHT(520);
33 const GG::X INDENTATION(20);
34 const GG::X ROW_WIDTH(PAGE_WIDTH - 4 - 14 - 5);
35 const GG::X COLOR_SELECTOR_WIDTH(75);
36 const GG::X SPIN_WIDTH(92);
37 const int LAYOUT_MARGIN = 5;
38
39 const std::string OPTIONS_WND_NAME = "options";
40
41 const std::string STRINGTABLE_FILE_SUFFIX = ".txt";
42 const std::string MUSIC_FILE_SUFFIX = ".ogg";
43 const std::string SOUND_FILE_SUFFIX = ".ogg";
44 const std::string FONT_FILE_SUFFIX = ".ttf";
45
46 class RowContentsWnd : public GG::Control {
47 public:
RowContentsWnd(GG::X w,GG::Y h,std::shared_ptr<Wnd> contents,int indentation_level)48 RowContentsWnd(GG::X w, GG::Y h, std::shared_ptr<Wnd> contents, int indentation_level) :
49 Control(GG::X0, GG::Y0, w, h, GG::INTERACTIVE),
50 m_contents(std::forward<std::shared_ptr<Wnd>>(contents)),
51 m_indentation_level(indentation_level)
52 {}
53
CompleteConstruction()54 void CompleteConstruction() override {
55 GG::Control::CompleteConstruction();
56
57 if (!m_contents)
58 return;
59 AttachChild(m_contents);
60 m_contents->MoveTo(GG::Pt(GG::X(m_indentation_level * INDENTATION), GG::Y0));
61 DoLayout();
62 }
63
SizeMove(const GG::Pt & ul,const GG::Pt & lr)64 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override {
65 const GG::Pt old_size = Size();
66 GG::Control::SizeMove(ul, lr);
67 if (old_size != Size())
68 DoLayout();
69 }
70
DoLayout()71 void DoLayout() {
72 if (m_contents) {
73 //std::cout << "RowContentsWnd::DoLayout()" << std::endl;
74 m_contents->SizeMove(GG::Pt(), Size());
75 }
76 }
77
Render()78 void Render() override {
79 //GG::FlatRectangle(UpperLeft(), LowerRight(), GG::CLR_DARK_RED, GG::CLR_PINK, 1);
80 }
81 private:
82 std::shared_ptr<Wnd> m_contents;
83 int m_indentation_level;
84 };
85
86 struct BrowseForPathButtonFunctor {
BrowseForPathButtonFunctor__anon3d68fa170111::BrowseForPathButtonFunctor87 BrowseForPathButtonFunctor(const fs::path& path, const std::vector<std::pair<std::string, std::string>>& filters,
88 std::shared_ptr<GG::Edit> edit, bool directory, bool return_relative_path) :
89 m_path(path),
90 m_filters(filters),
91 m_edit(std::forward<std::shared_ptr<GG::Edit>>(edit)),
92 m_directory(directory),
93 m_return_relative_path(return_relative_path)
94 {}
95
operator ()__anon3d68fa170111::BrowseForPathButtonFunctor96 void operator()() {
97 try {
98 auto dlg = GG::Wnd::Create<FileDlg>(m_path.string(), m_edit->Text(), false, false, m_filters);
99 if (m_directory)
100 dlg->SelectDirectories(true);
101 dlg->Run();
102 if (!dlg->Result().empty()) {
103 fs::path path = m_return_relative_path ?
104 RelativePath(m_path, fs::path(*(dlg->Result().begin()))) :
105 fs::absolute(*(dlg->Result().begin()));
106 *m_edit << path.string();
107 m_edit->EditedSignal(m_edit->Text());
108 }
109 } catch (const std::exception& e) {
110 ClientUI::MessageBox(e.what(), true);
111 }
112 }
113
114 fs::path m_path;
115 std::vector<std::pair<std::string, std::string>> m_filters;
116 std::shared_ptr<GG::Edit> m_edit;
117 bool m_directory;
118 bool m_return_relative_path;
119 };
120
ValidStringtableFile(const std::string & file)121 bool ValidStringtableFile(const std::string& file) {
122 // putting this in try-catch block prevents crash with error output along the lines of:
123 // main() caught exception(std::exception): boost::filesystem::path: invalid name ":" in path: ":\FreeOrion\default"
124 try {
125 fs::path path = FilenameToPath(file);
126 return boost::algorithm::ends_with(file, STRINGTABLE_FILE_SUFFIX) &&
127 fs::exists(path) && !fs::is_directory(path);
128 } catch (...) {
129 }
130 return false;
131 }
132
ValidFontFile(const std::string & file)133 bool ValidFontFile(const std::string& file) {
134 // putting this in try-catch block prevents crash with error output along the lines of:
135 // main() caught exception(std::exception): boost::filesystem::path: invalid name ":" in path: ":\FreeOrion\default"
136 try {
137 fs::path path = FilenameToPath(file);
138 return boost::algorithm::ends_with(file, FONT_FILE_SUFFIX) &&
139 fs::exists(path) && !fs::is_directory(path);
140 } catch (...) {
141 }
142 return false;
143 }
144
ValidMusicFile(const std::string & file)145 bool ValidMusicFile(const std::string& file) {
146 // putting this in try-catch block prevents crash with error output along the lines of:
147 // main() caught exception(std::exception): boost::filesystem::path: invalid name ":" in path: ":\FreeOrion\default"
148 try {
149 fs::path path = FilenameToPath(file);
150 return boost::algorithm::ends_with(file, MUSIC_FILE_SUFFIX) &&
151 fs::exists(path) && !fs::is_directory(path);
152 } catch (...) {
153 }
154 return false;
155 }
156
ValidSoundFile(const std::string & file)157 bool ValidSoundFile(const std::string& file) {
158 // putting this in try-catch block prevents crash with error output along the lines of:
159 // main() caught exception(std::exception): boost::filesystem::path: invalid name ":" in path: ":\FreeOrion\default"
160 try {
161 fs::path path = FilenameToPath(file);
162 return boost::algorithm::ends_with(file, SOUND_FILE_SUFFIX) &&
163 fs::exists(path) && !fs::is_directory(path);
164 } catch (...) {
165 }
166 return false;
167 }
168
ValidDirectory(const std::string & file)169 bool ValidDirectory(const std::string& file) {
170 // putting this in try-catch block prevents crash with error output along the lines of:
171 // main() caught exception(std::exception): boost::filesystem::path: invalid name ":" in path: ":\FreeOrion\default"
172 try {
173 fs::path path = FilenameToPath(file);
174 return fs::exists(path) && fs::is_directory(path);
175 } catch (...) {
176 }
177 return false;
178 }
179
180 // Small window that will grab a unique key press.
181 class KeyPressCatcher : public GG::Wnd {
182 GG::Key m_key;
183 std::uint32_t m_code_point;
184 GG::Flags<GG::ModKey> m_mods;
185
186 public:
KeyPressCatcher()187 KeyPressCatcher() :
188 Wnd(GG::X0, GG::Y0, GG::X0, GG::Y0, GG::Flags<GG::WndFlag>(GG::MODAL))
189 {};
190
Render()191 void Render() override
192 {}
193
KeyPress(GG::Key key,std::uint32_t key_code_point,GG::Flags<GG::ModKey> mod_keys)194 void KeyPress(GG::Key key, std::uint32_t key_code_point,
195 GG::Flags<GG::ModKey> mod_keys) override
196 {
197 m_key = key;
198 m_code_point = key_code_point;
199 m_mods = mod_keys;
200 // exit modal loop only if not a modifier
201 if (GG::GGK_LCONTROL > m_key || GG::GGK_RGUI < m_key)
202 m_done = true;
203
204 /// @todo Clean up, ie transform LCTRL or RCTRL into CTRL and
205 /// the like...
206 };
207
GetKeypress()208 static std::pair<GG::Key, GG::Flags<GG::ModKey>> GetKeypress() {
209 auto ct = GG::Wnd::Create<KeyPressCatcher>();
210 ct->Run();
211 return std::make_pair(ct->m_key, ct->m_mods);
212 };
213 };
214
215 // Displays current font textures
216 class FontTextureWnd : public CUIWnd {
217 public:
FontTextureWnd()218 FontTextureWnd() :
219 CUIWnd(UserString("OPTIONS_FONTS"),
220 GG::GUI::GetGUI()->AppWidth() / 6, GG::GUI::GetGUI()->AppHeight() / 6,
221 GG::GUI::GetGUI()->AppWidth() * 2 / 3, GG::GUI::GetGUI()->AppHeight() * 2 / 3,
222 GG::INTERACTIVE | GG::DRAGABLE | GG::MODAL | GG::RESIZABLE | CLOSABLE)
223 {}
224
CompleteConstruction()225 void CompleteConstruction() override {
226 CUIWnd::CompleteConstruction();
227
228 GG::Y top = GG::Y1;
229
230 std::shared_ptr<GG::Font> font = ClientUI::GetFont();
231 std::shared_ptr<GG::Texture> texture;
232 if (font)
233 texture = font->GetTexture();
234 if (texture) {
235 m_font_graphic = GG::Wnd::Create<GG::StaticGraphic>(texture);
236 m_font_graphic->MoveTo(GG::Pt(GG::X0, top));
237 m_font_graphic->Resize(GG::Pt(texture->Width(), texture->Height()));
238 AttachChild(m_font_graphic);
239 top += m_font_graphic->Height() + 1;
240 }
241
242 font = ClientUI::GetBoldFont();
243 if (font)
244 texture = font->GetTexture();
245 if (texture) {
246 m_bold_font_graphic = GG::Wnd::Create<GG::StaticGraphic>(texture);
247 m_bold_font_graphic->MoveTo(GG::Pt(GG::X0, top));
248 m_bold_font_graphic->Resize(GG::Pt(texture->Width(), texture->Height()));
249 AttachChild(m_bold_font_graphic);
250 top += m_bold_font_graphic->Height() + 1;
251 }
252
253 font = ClientUI::GetTitleFont();
254 texture.reset();
255 if (font)
256 texture = font->GetTexture();
257 if (texture) {
258 m_title_font_graphic = GG::Wnd::Create<GG::StaticGraphic>(texture);
259 m_title_font_graphic->MoveTo(GG::Pt(GG::X0, top));
260 m_title_font_graphic->Resize(GG::Pt(texture->Width(), texture->Height()));
261 AttachChild(m_title_font_graphic);
262 }
263
264
265 m_hscroll = GG::Wnd::Create<CUIScroll>(GG::HORIZONTAL);
266 AttachChild(m_hscroll);
267
268 #if BOOST_VERSION >= 106000
269 using boost::placeholders::_1;
270 using boost::placeholders::_2;
271 using boost::placeholders::_3;
272 using boost::placeholders::_4;
273 #endif
274
275 m_hscroll->ScrolledSignal.connect(
276 boost::bind(&FontTextureWnd::ScrolledSlot, this, _1, _2, _3, _4));
277 DoLayout();
278 }
279
280 public:
SizeMove(const GG::Pt & ul,const GG::Pt & lr)281 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override {
282 GG::Pt old_size = GG::Wnd::Size();
283
284 CUIWnd::SizeMove(ul, lr);
285
286 if (old_size != GG::Wnd::Size())
287 DoLayout();
288 }
289
DoLayout()290 void DoLayout() {
291 m_hscroll->SizeMove(GG::Pt(GG::X0, ClientHeight() - ClientUI::ScrollWidth()),
292 GG::Pt(ClientWidth(), ClientHeight()));
293
294 int texture_width = 1;
295 if (m_font_graphic)
296 texture_width = std::max(texture_width, Value(m_font_graphic->Width()));
297 if (m_title_font_graphic)
298 texture_width = std::max(texture_width, Value(m_title_font_graphic->Width()));
299
300 m_hscroll->SizeScroll(0, texture_width - Value(ClientWidth()) / 2, 1, 50);
301 }
302
ScrolledSlot(int tab_low,int tab_high,int low,int high)303 void ScrolledSlot(int tab_low, int tab_high, int low, int high) {
304 m_font_graphic->MoveTo( GG::Pt(GG::X(-tab_low), GG::Y1));
305 m_title_font_graphic->MoveTo(GG::Pt(GG::X(-tab_low), m_font_graphic->Height() + 2));
306 }
307
308 private:
309 std::shared_ptr<GG::StaticGraphic> m_font_graphic;
310 std::shared_ptr<GG::StaticGraphic> m_bold_font_graphic;
311 std::shared_ptr<GG::StaticGraphic> m_title_font_graphic;
312 std::shared_ptr<GG::Scroll> m_hscroll;
313 };
314
ShowFontTextureWnd()315 void ShowFontTextureWnd() {
316 auto font_wnd = GG::Wnd::Create<FontTextureWnd>();
317 font_wnd->Run();
318 }
319
320 class OptionsListRow : public GG::ListBox::Row {
321 public:
OptionsListRow(GG::X w,GG::Y h,std::shared_ptr<RowContentsWnd> contents)322 OptionsListRow(GG::X w, GG::Y h, std::shared_ptr<RowContentsWnd> contents) :
323 GG::ListBox::Row(w, h),
324 m_contents(std::forward<std::shared_ptr<RowContentsWnd>>(contents))
325 {
326 SetChildClippingMode(ClipToClient);
327 }
328
OptionsListRow(GG::X w,GG::Y h,std::shared_ptr<Wnd> contents,int indentation=0)329 OptionsListRow(GG::X w, GG::Y h, std::shared_ptr<Wnd> contents, int indentation = 0) :
330 GG::ListBox::Row(w, h)
331 {
332 SetChildClippingMode(ClipToClient);
333 if (contents)
334 m_contents = GG::Wnd::Create<RowContentsWnd>(w, h, contents, indentation);
335 }
336
CompleteConstruction()337 void CompleteConstruction() override {
338 GG::ListBox::Row::CompleteConstruction();
339 if (m_contents)
340 push_back(m_contents);
341 }
342
SizeMove(const GG::Pt & ul,const GG::Pt & lr)343 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override {
344 //std::cout << "OptionsListRow::SizeMove(" << ul << ", " << lr << ")" << std::endl;
345 const GG::Pt old_size = Size();
346 GG::ListBox::Row::SizeMove(ul, lr);
347 if (!empty() && old_size != Size() && m_contents)
348 m_contents->Resize(Size());
349 }
350
Render()351 void Render() override {
352 //GG::FlatRectangle(UpperLeft(), LowerRight(), GG::CLR_DARK_BLUE, GG::CLR_YELLOW, 1);
353 }
354 private:
355 std::shared_ptr<RowContentsWnd> m_contents;
356 };
357
358 class OptionsList : public CUIListBox {
359 public:
OptionsList()360 OptionsList() :
361 CUIListBox()
362 {
363 InitRowSizes();
364
365 SetColor(GG::CLR_ZERO);
366 SetStyle(GG::LIST_NOSORT | GG::LIST_NOSEL);
367 SetVScrollWheelIncrement(ClientUI::Pts() * 10);
368 }
369
SizeMove(const GG::Pt & ul,const GG::Pt & lr)370 void SizeMove(const GG::Pt& ul, const GG::Pt& lr) override {
371 const GG::Pt old_size = Size();
372 CUIListBox::SizeMove(ul, lr);
373 if (old_size != Size()) {
374 const GG::X row_width = ListRowWidth();
375 for (auto& row : *this)
376 row->Resize(GG::Pt(row_width, row->Height()));
377 }
378 }
379
380 private:
ListRowWidth() const381 GG::X ListRowWidth() const
382 { return Width() - RightMargin() - 5; }
383
InitRowSizes()384 void InitRowSizes() {
385 // preinitialize listbox/row column widths, because what
386 // ListBox::Insert does on default is not suitable for this case
387 SetNumCols(1);
388 SetColWidth(0, GG::X0);
389 LockColWidths();
390 }
391 };
392
393 /**Create UI controls for a logger level option.*/
LoggerLevelOption(GG::ListBox & page,bool is_sink,const std::string & label,const std::string & option_name)394 void LoggerLevelOption(GG::ListBox& page, bool is_sink,
395 const std::string& label, const std::string& option_name)
396 {
397 // Create the label
398 auto logger_label = GG::Wnd::Create<CUILabel>(label, GG::FORMAT_LEFT | GG::FORMAT_NOWRAP, GG::INTERACTIVE);
399
400 // Create a drop down list for the filtering levels
401 auto num_log_levels = 1 + static_cast<std::size_t>(LogLevel::max) - static_cast<std::size_t>(LogLevel::min);
402 auto drop_list = GG::Wnd::Create<CUIDropDownList>(num_log_levels);
403 drop_list->Resize(GG::Pt(drop_list->MinUsableSize().x, GG::Y(ClientUI::Pts() + 4)));
404 drop_list->SetMaxSize(GG::Pt(drop_list->MaxSize().x, drop_list->Size().y));
405 drop_list->SetStyle(GG::LIST_NOSORT);
406 drop_list->SetOnlyMouseScrollWhenDropped(true);
407
408 // Insert the levels into the list
409 for (auto ii = static_cast<std::size_t>(LogLevel::min); ii <= static_cast<std::size_t>(LogLevel::max); ++ii) {
410 auto level_name = to_string(static_cast<LogLevel>(ii));
411 auto priority_row = GG::Wnd::Create<CUISimpleDropDownListRow>(level_name);
412 // use the row's name to store the option value.
413 priority_row->SetName(level_name);
414 drop_list->Insert(priority_row);
415 }
416
417 // Select the current filtering level in the list
418 auto selected_level = static_cast<std::size_t>(to_LogLevel(GetOptionsDB().GetValueString(option_name)));
419 if (drop_list->NumRows() >= 1)
420 drop_list->Select(selected_level);
421
422 // Make a layout with a row etc. for this option
423 auto layout = GG::Wnd::Create<GG::Layout>(GG::X0, GG::Y0, GG::X1, GG::Y1, 1, 2, 0, LAYOUT_MARGIN);
424 layout->Add(logger_label, 0, 0);
425 layout->Add(drop_list, 0, 1, 1, 1, GG::ALIGN_VCENTER);
426
427 auto row = GG::Wnd::Create<GG::ListBox::Row>();
428 // row->Resize(GG::Pt(ROW_WIDTH, drop_list->MinUsableSize().y + LAYOUT_MARGIN + drop_list->MaxSize().y + 6));
429 row->Resize(GG::Pt(ROW_WIDTH, drop_list->MinUsableSize().y + LAYOUT_MARGIN));
430
431 auto row_wnd = GG::Wnd::Create<RowContentsWnd>(row->Width(), row->Height(), layout, 0);
432 row_wnd->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
433 row_wnd->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
434 row->push_back(row_wnd);
435
436 page.Insert(row);
437
438 // Connect to Options DB
439 drop_list->SelChangedSignal.connect(
440 [option_name, drop_list](const GG::ListBox::const_iterator& it) {
441 if (it == drop_list->end())
442 return;
443 const auto dropdown_row = dynamic_cast<CUISimpleDropDownListRow* const>(it->get());
444 const auto& option_value = dropdown_row->Name();
445 HumanClientApp::GetApp()->ChangeLoggerThreshold(option_name, to_LogLevel(option_value));
446 });
447 }
448 }
449
OptionsWnd(bool is_game_running_)450 OptionsWnd::OptionsWnd(bool is_game_running_):
451 CUIWnd(UserString("OPTIONS_TITLE"),
452 GG::INTERACTIVE | GG::DRAGABLE | GG::MODAL | GG::RESIZABLE,
453 OPTIONS_WND_NAME),
454 is_game_running(is_game_running_)
455 {}
456
CompleteConstruction()457 void OptionsWnd::CompleteConstruction() {
458 m_done_button = Wnd::Create<CUIButton>(UserString("DONE"));
459 // FIXME: PAGE_WIDTH is needed to prevent triggering an assert within the TabBar class.
460 // The placement of the tab register buttons assumes that the whole TabWnd is at least
461 // wider than the first tab button.
462 m_tabs = GG::Wnd::Create<GG::TabWnd>(GG::X0, GG::Y0, PAGE_WIDTH, GG::Y1,
463 ClientUI::GetFont(), ClientUI::WndColor(),
464 ClientUI::TextColor());
465
466 CUIWnd::CompleteConstruction();
467
468 ResetDefaultPosition();
469 SetMinSize(GG::Pt(PAGE_WIDTH + 20, PAGE_HEIGHT + 70));
470
471 AttachChild(m_done_button);
472 AttachChild(m_tabs);
473
474 bool UI_sound_enabled = GetOptionsDB().Get<bool>("audio.effects.enabled");
475 GG::ListBox* current_page = nullptr;
476
477 Sound::TempUISoundDisabler sound_disabler;
478
479 // Video settings tab
480 current_page = CreatePage(UserString("OPTIONS_PAGE_VIDEO"));
481 ResolutionOption(current_page, 0);
482 m_tabs->SetCurrentWnd(0);
483
484 // Audio settings tab
485 current_page = CreatePage(UserString("OPTIONS_PAGE_AUDIO"));
486 CreateSectionHeader(current_page, 0, UserString("OPTIONS_VOLUME_AND_MUSIC"));
487 MusicVolumeOption(current_page, 0, m_sound_feedback);
488 VolumeOption(current_page, 0, "audio.effects.enabled", "audio.effects.volume", UserString("OPTIONS_UI_SOUNDS"), UI_sound_enabled, m_sound_feedback);
489 FileOption(current_page, 0, "audio.music.path", UserString("OPTIONS_BACKGROUND_MUSIC"), ClientUI::SoundDir(),
490 {UserString("OPTIONS_MUSIC_FILE"), "*" + MUSIC_FILE_SUFFIX},
491 ValidMusicFile);
492
493 CreateSectionHeader(current_page, 0, UserString("OPTIONS_SOUNDS"));
494 CreateSectionHeader(current_page, 1, UserString("OPTIONS_UI_SOUNDS"));
495 SoundFileOption(current_page, 1, "ui.alert.sound.path", UserString("OPTIONS_SOUND_ALERT"));
496 SoundFileOption(current_page, 1, "ui.input.keyboard.sound.path", UserString("OPTIONS_SOUND_TYPING"));
497
498 CreateSectionHeader(current_page, 1, UserString("OPTIONS_SOUND_NEWTURN"));
499 BoolOption(current_page, 1, "ui.turn.start.sound.enabled", UserString("OPTIONS_SOUND_NEWTURN_TOGGLE"));
500 SoundFileOption(current_page, 1, "ui.turn.start.sound.path", UserString("OPTIONS_SOUND_NEWTURN_FILE"));
501
502 CreateSectionHeader(current_page, 1, UserString("OPTIONS_SOUND_WINDOW"));
503 SoundFileOption(current_page, 1, "ui.window.close.sound.path", UserString("OPTIONS_SOUND_CLOSE"));
504 SoundFileOption(current_page, 1, "ui.window.maximize.sound.path", UserString("OPTIONS_SOUND_MAXIMIZE"));
505 SoundFileOption(current_page, 1, "ui.window.minimize.sound.path", UserString("OPTIONS_SOUND_MINIMIZE"));
506 SoundFileOption(current_page, 1, "ui.map.sidepanel.open.sound.path", UserString("OPTIONS_SOUND_SIDEPANEL"));
507
508 CreateSectionHeader(current_page, 1, UserString("OPTIONS_SOUND_LIST"));
509 SoundFileOption(current_page, 1, "ui.listbox.drop.sound.path", UserString("OPTIONS_SOUND_DROP"));
510 SoundFileOption(current_page, 1, "ui.dropdownlist.select.sound.path", UserString("OPTIONS_SOUND_PULLDOWN"));
511 SoundFileOption(current_page, 1, "ui.listbox.select.sound.path", UserString("OPTIONS_SOUND_SELECT"));
512
513 CreateSectionHeader(current_page, 1, UserString("OPTIONS_SOUND_BUTTON"));
514 SoundFileOption(current_page, 1, "ui.button.press.sound.path", UserString("OPTIONS_SOUND_CLICK"));
515 SoundFileOption(current_page, 1, "ui.button.rollover.sound.path", UserString("OPTIONS_SOUND_ROLLOVER"));
516 SoundFileOption(current_page, 1, "ui.map.fleet.button.press.sound.path", UserString("OPTIONS_SOUND_FLEET_CLICK"));
517 SoundFileOption(current_page, 1, "ui.map.fleet.button.rollover.sound.path", UserString("OPTIONS_SOUND_FLEET_ROLLOVER"));
518 SoundFileOption(current_page, 1, "ui.map.system.icon.rollover.sound.path", UserString("OPTIONS_SOUND_SYSTEM_ROLLOVER"));
519 SoundFileOption(current_page, 1, "ui.button.turn.press.sound.path", UserString("OPTIONS_SOUND_TURN"));
520
521 m_tabs->SetCurrentWnd(0);
522
523 // UI settings tab
524 current_page = CreatePage(UserString("OPTIONS_PAGE_UI"));
525 CreateSectionHeader(current_page, 0, UserString("OPTIONS_MISC_UI"));
526
527 BoolOption(current_page, 0, "ui.pedia.search.articles.enabled", UserString("OPTIONS_PEDIA_SEARCH_ARTICLE_TEXT"));
528 BoolOption(current_page, 0, "ui.input.mouse.button.swap.enabled", UserString("OPTIONS_SWAP_MOUSE_LR"));
529 BoolOption(current_page, 0, "ui.fleet.multiple.enabled", UserString("OPTIONS_MULTIPLE_FLEET_WNDS"));
530 BoolOption(current_page, 0, "ui.quickclose.enabled", UserString("OPTIONS_QUICK_CLOSE_WNDS"));
531 BoolOption(current_page, 0, "ui.map.sidepanel.planet.shown", UserString("OPTIONS_SHOW_SIDEPANEL_PLANETS"));
532 BoolOption(current_page, 0, "ui.reposition.auto.enabled", UserString("OPTIONS_AUTO_REPOSITION_WINDOWS"));
533 BoolOption(current_page, 0, "ui.map.messages.timestamp.shown", UserString("OPTIONS_DISPLAY_TIMESTAMP"));
534
535 // manual reposition windows button
536 auto window_reset_button = Wnd::Create<CUIButton>(UserString("OPTIONS_WINDOW_RESET"));
537 auto row = GG::Wnd::Create<OptionsListRow>(
538 ROW_WIDTH, window_reset_button->MinUsableSize().y + LAYOUT_MARGIN + 6,
539 window_reset_button, 0);
540 current_page->Insert(row);
541 window_reset_button->LeftClickedSignal.connect(
542 HumanClientApp::GetApp()->RepositionWindowsSignal);
543
544 FileOption(current_page, 0, "resource.stringtable.path", UserString("OPTIONS_LANGUAGE"),
545 GetRootDataDir() / "default" / "stringtables",
546 {UserString("OPTIONS_LANGUAGE_FILE"), "*" + STRINGTABLE_FILE_SUFFIX},
547 &ValidStringtableFile);
548
549 // flush stringtable button
550 auto flush_button = Wnd::Create<CUIButton>(UserString("OPTIONS_FLUSH_STRINGTABLE"));
551 row = GG::Wnd::Create<OptionsListRow>(
552 ROW_WIDTH, flush_button->MinUsableSize().y + LAYOUT_MARGIN + 6,
553 flush_button, 0);
554 current_page->Insert(row);
555 flush_button->LeftClickedSignal.connect(&FlushLoadedStringTables);
556
557 IntOption(current_page, 0, "ui.tooltip.delay", UserString("OPTIONS_TOOLTIP_DELAY"));
558 IntOption(current_page, 0, "ui.input.keyboard.repeat.delay", UserString("OPTIONS_KEYPRESS_REPEAT_DELAY"));
559 IntOption(current_page, 0, "ui.input.keyboard.repeat.interval", UserString("OPTIONS_KEYPRESS_REPEAT_INTERVAL"));
560 IntOption(current_page, 0, "ui.input.mouse.button.repeat.delay", UserString("OPTIONS_MOUSE_REPEAT_DELAY"));
561 IntOption(current_page, 0, "ui.input.mouse.button.repeat.interval", UserString("OPTIONS_MOUSE_REPEAT_INTERVAL"));
562
563 CreateSectionHeader(current_page, 0, UserString("OPTIONS_RESEARCH_WND"));
564 DoubleOption(current_page, 0, "ui.research.tree.spacing.horizontal",UserString("OPTIONS_TECH_SPACING_HORIZONTAL"));
565 DoubleOption(current_page, 0, "ui.research.tree.spacing.vertical", UserString("OPTIONS_TECH_SPACING_VERTICAL"));
566 DoubleOption(current_page, 0, "ui.research.tree.zoom.scale", UserString("OPTIONS_TECH_LAYOUT_ZOOM"));
567 DoubleOption(current_page, 0, "ui.research.control.graphic.size", UserString("OPTIONS_TECH_CTRL_ICON_SIZE"));
568
569 CreateSectionHeader(current_page, 0, UserString("OPTIONS_QUEUES"));
570 IntOption(current_page, 0, "ui.queue.width", UserString("OPTIONS_UI_QUEUE_WIDTH"));
571 BoolOption(current_page, 0, "ui.queue.production_location.shown", UserString("OPTIONS_UI_PROD_QUEUE_LOCATION"));
572
573 CreateSectionHeader(current_page, 0, UserString("OPTIONS_DESCRIPTIONS"));
574 BoolOption(current_page, 0, "resource.effects.description.shown", UserString("OPTIONS_DUMP_EFFECTS_GROUPS_DESC"));
575 BoolOption(current_page, 0, "ui.map.sitrep.invalid.shown", UserString("OPTIONS_VERBOSE_SITREP_DESC"));
576 BoolOption(current_page, 0, "ui.name.id.shown", UserString("OPTIONS_SHOW_IDS_AFTER_NAMES"));
577
578 m_tabs->SetCurrentWnd(0);
579
580 // Font tab
581 current_page = CreatePage(UserString("OPTIONS_FONTS"));
582
583 CreateSectionHeader(current_page, 0, UserString("OPTIONS_FONTS"));
584 FontOption(current_page, 0, "ui.font.path", UserString("OPTIONS_FONT_TEXT"));
585 FontOption(current_page, 0, "ui.font.bold.path", UserString("OPTIONS_FONT_BOLD_TEXT"));
586 FontOption(current_page, 0, "ui.font.title.path", UserString("OPTIONS_FONT_TITLE"));
587
588 // show font texture button
589 auto show_font_texture_button = Wnd::Create<CUIButton>(UserString("SHOW_FONT_TEXTURES"));
590 row = GG::Wnd::Create<OptionsListRow>(
591 ROW_WIDTH, show_font_texture_button ->MinUsableSize().y + LAYOUT_MARGIN + 6,
592 show_font_texture_button , 0);
593 current_page->Insert(row);
594 show_font_texture_button->LeftClickedSignal.connect(
595 &ShowFontTextureWnd);
596
597 CreateSectionHeader(current_page, 0, UserString("OPTIONS_FONT_SIZES"));
598 IntOption(current_page, 0, "ui.font.size", UserString("OPTIONS_FONT_TEXT"));
599 IntOption(current_page, 0, "ui.font.title.size", UserString("OPTIONS_FONT_TITLE"));
600
601 m_tabs->SetCurrentWnd(0);
602
603 // Galaxy Map Page
604 current_page = CreatePage(UserString("OPTIONS_GALAXY_MAP"));
605 CreateSectionHeader(current_page, 0, UserString("OPTIONS_SYSTEM_ICONS"));
606 IntOption(current_page, 0, "ui.map.system.icon.size", UserString("OPTIONS_UI_SYSTEM_ICON_SIZE"));
607 BoolOption(current_page, 0, "ui.map.system.circle.shown", UserString("OPTIONS_UI_SYSTEM_CIRCLES"));
608 DoubleOption(current_page, 0, "ui.map.system.circle.size", UserString("OPTIONS_UI_SYSTEM_CIRCLE_SIZE"));
609 DoubleOption(current_page, 0, "ui.map.system.select.indicator.size",UserString("OPTIONS_UI_SYSTEM_SELECTION_INDICATOR_SIZE"));
610 IntOption(current_page, 0, "ui.map.system.select.indicator.rpm", UserString("OPTIONS_UI_SYSTEM_SELECTION_INDICATOR_FPS"));
611 IntOption(current_page, 0, "ui.map.system.icon.tiny.threshold", UserString("OPTIONS_UI_SYSTEM_TINY_ICON_SIZE_THRESHOLD"));
612 ColorOption(current_page, 0, "ui.map.system.unowned.name.color", UserString("OPTIONS_UI_SYSTEM_NAME_UNOWNED_COLOR"));
613 BoolOption(current_page, 0, "ui.map.scanlines.shown", UserString("OPTIONS_UI_SYSTEM_FOG"));
614 DoubleOption(current_page, 0, "ui.map.system.scanlines.spacing", UserString("OPTIONS_UI_SYSTEM_FOG_SPACING"));
615
616 CreateSectionHeader(current_page, 0, UserString("OPTIONS_FLEET_ICONS"));
617 DoubleOption(current_page, 0, "ui.map.fleet.button.tiny.zoom.threshold", UserString("OPTIONS_UI_TINY_FLEET_BUTTON_MIN_ZOOM"));
618 DoubleOption(current_page, 0, "ui.map.fleet.button.small.zoom.threshold", UserString("OPTIONS_UI_SMALL_FLEET_BUTTON_MIN_ZOOM"));
619 DoubleOption(current_page, 0, "ui.map.fleet.button.medium.zoom.threshold", UserString("OPTIONS_UI_MEDIUM_FLEET_BUTTON_MIN_ZOOM"));
620 DoubleOption(current_page, 0, "ui.map.fleet.select.indicator.size", UserString("OPTIONS_UI_FLEET_SELECTION_INDICATOR_SIZE"));
621
622 CreateSectionHeader(current_page, 0, UserString("OPTIONS_STARLANES"));
623 DoubleOption(current_page, 0, "ui.map.starlane.thickness", UserString("OPTIONS_STARLANE_THICKNESS"));
624 BoolOption(current_page, 0, "ui.map.starlane.empire.color.shown", UserString("OPTIONS_RESOURCE_STARLANE_COLOURING"));
625 DoubleOption(current_page, 0, "ui.map.starlane.thickness.factor", UserString("OPTIONS_DB_STARLANE_CORE"));
626 BoolOption(current_page, 0, "ui.map.fleet.supply.shown", UserString("OPTIONS_FLEET_SUPPLY_LINES"));
627 DoubleOption(current_page, 0, "ui.map.fleet.supply.width", UserString("OPTIONS_FLEET_SUPPLY_LINE_WIDTH"));
628 IntOption(current_page, 0, "ui.map.fleet.supply.dot.spacing", UserString("OPTIONS_FLEET_SUPPLY_LINE_DOT_SPACING"));
629 DoubleOption(current_page, 0, "ui.map.fleet.supply.dot.rate", UserString("OPTIONS_FLEET_SUPPLY_LINE_DOT_RATE"));
630 ColorOption(current_page, 0, "ui.map.starlane.color", UserString("OPTIONS_UNOWNED_STARLANE_COLOUR"));
631
632 CreateSectionHeader(current_page, 0, UserString("OPTIONS_GALAXY_MAP_GENERAL"));
633 BoolOption(current_page, 0, "ui.map.background.gas.shown", UserString("OPTIONS_GALAXY_MAP_GAS"));
634 BoolOption(current_page, 0, "ui.map.background.starfields.shown", UserString("OPTIONS_GALAXY_MAP_STARFIELDS"));
635 DoubleOption(current_page, 0, "ui.map.background.starfields.scale", UserString("OPTIONS_GALAXY_MAP_STARFIELDS_SCALE"));
636 BoolOption(current_page, 0, "ui.map.scale.legend.shown", UserString("OPTIONS_GALAXY_MAP_SCALE_LINE"));
637 BoolOption(current_page, 0, "ui.map.scale.circle.shown", UserString("OPTIONS_GALAXY_MAP_SCALE_CIRCLE"));
638 BoolOption(current_page, 0, "ui.map.zoom.slider.shown", UserString("OPTIONS_GALAXY_MAP_ZOOM_SLIDER"));
639 BoolOption(current_page, 0, "ui.map.detection.range.shown", UserString("OPTIONS_GALAXY_MAP_DETECTION_RANGE"));
640 IntOption(current_page, 0, "ui.map.detection.range.opacity", UserString("OPTIONS_GALAXY_MAP_DETECTION_RANGE_OPACITY"));
641 BoolOption(current_page, 0, "ui.map.menu.enabled", UserString("OPTIONS_GALAXY_MAP_POPUP"));
642 BoolOption(current_page, 0, "ui.map.system.unexplored.rollover.enabled", UserString("OPTIONS_UI_SYSTEM_UNEXPLORED_OVERLAY"));
643 BoolOption(current_page, 0, "ui.production.mappanels.removed", UserString("OPTIONS_UI_HIDE_MAP_PANELS"));
644
645 m_tabs->SetCurrentWnd(0);
646
647 // Objects List Page
648 current_page = CreatePage(UserString("OPTIONS_PAGE_OBJECTS_WINDOW"));
649 CreateSectionHeader(current_page, 0, UserString("OPTIONS_COLUMNS"));
650 for (unsigned int i = 0; i < 12u; ++i) {
651 std::string col_width_opt_name = "ui.objects.columns.c" + std::to_string(i) + ".width";
652 if (!GetOptionsDB().OptionExists(col_width_opt_name))
653 break;
654 std::string col_opt_name = "ui.objects.columns.c" + std::to_string(i) + ".stringkey";
655 if (!GetOptionsDB().OptionExists(col_opt_name))
656 break;
657 std::string col_contents = GetOptionsDB().GetValueString(col_opt_name);
658 const std::string& tx_contents = (col_contents.empty() ? "" : UserString(col_contents));
659
660 IntOption(current_page, 0, col_width_opt_name, tx_contents);
661 }
662
663 m_tabs->SetCurrentWnd(0);
664
665 // Colors tab
666 current_page = CreatePage(UserString("OPTIONS_PAGE_COLORS"));
667 CreateSectionHeader(current_page, 0, UserString("OPTIONS_GENERAL_COLORS"));
668 ColorOption(current_page, 0, "ui.font.color", UserString("OPTIONS_TEXT_COLOR"));
669 ColorOption(current_page, 0, "ui.font.link.color", UserString("OPTIONS_DEFAULT_LINK_COLOR"));
670 ColorOption(current_page, 0, "ui.font.link.rollover.color", UserString("OPTIONS_ROLLOVER_LINK_COLOR"));
671
672 CreateSectionHeader(current_page, 0, UserString("OPTIONS_WINDOW_COLORS"));
673 ColorOption(current_page, 0, "ui.window.background.color", UserString("OPTIONS_FILL_COLOR"));
674 ColorOption(current_page, 0, "ui.window.border.inner.color",UserString("OPTIONS_INNER_BORDER_COLOR"));
675 ColorOption(current_page, 0, "ui.window.border.outer.color",UserString("OPTIONS_OUTER_BORDER_COLOR"));
676
677 CreateSectionHeader(current_page, 0, UserString("OPTIONS_CONTROL_COLORS"));
678 ColorOption(current_page, 0, "ui.control.background.color", UserString("OPTIONS_FILL_COLOR"));
679 ColorOption(current_page, 0, "ui.control.border.color", UserString("OPTIONS_BORDER_COLOR"));
680 ColorOption(current_page, 0, "ui.control.edit.highlight.color", UserString("OPTIONS_HIGHLIGHT_COLOR"));
681 ColorOption(current_page, 0, "ui.dropdownlist.arrow.color", UserString("OPTIONS_DROPLIST_ARROW_COLOR"));
682 ColorOption(current_page, 0, "ui.button.state.color", UserString("OPTIONS_STATE_BUTTON_COLOR"));
683 ColorOption(current_page, 0, "ui.font.stat.increase.color", UserString("OPTIONS_STAT_INCREASE_COLOR"));
684 ColorOption(current_page, 0, "ui.font.stat.decrease.color", UserString("OPTIONS_STAT_DECREASE_COLOR"));
685
686 CreateSectionHeader(current_page, 0, UserString("OPTIONS_COMBAT_COLORS"));
687 ColorOption(current_page, 0, "ui.combat.summary.dead.color", UserString("OPTIONS_COMBAT_SUMMARY_DEAD_COLOR"));
688 ColorOption(current_page, 0, "ui.combat.summary.damaged.color", UserString("OPTIONS_COMBAT_SUMMARY_WOUND_COLOR"));
689 ColorOption(current_page, 0, "ui.combat.summary.undamaged.color", UserString("OPTIONS_COMBAT_SUMMARY_HEALTH_COLOR"));
690
691 CreateSectionHeader(current_page, 0, UserString("OPTIONS_TECH_COLORS"));
692 CreateSectionHeader(current_page, 1, UserString("OPTIONS_KNOWN_TECH_COLORS"));
693 ColorOption(current_page, 1, "ui.research.status.completed.background.color", UserString("OPTIONS_FILL_COLOR"));
694 ColorOption(current_page, 1, "ui.research.status.completed.border.color", UserString("OPTIONS_TEXT_AND_BORDER_COLOR"));
695
696 CreateSectionHeader(current_page, 1, UserString("OPTIONS_RESEARCHABLE_TECH_COLORS"));
697 ColorOption(current_page, 1, "ui.research.status.researchable.background.color",UserString("OPTIONS_FILL_COLOR"));
698 ColorOption(current_page, 1, "ui.research.status.researchable.border.color", UserString("OPTIONS_TEXT_AND_BORDER_COLOR"));
699
700 CreateSectionHeader(current_page, 1, UserString("OPTIONS_UNRESEARCHABLE_TECH_COLORS"));
701 ColorOption(current_page, 1, "ui.research.status.unresearchable.background.color", UserString("OPTIONS_FILL_COLOR"));
702 ColorOption(current_page, 1, "ui.research.status.unresearchable.border.color", UserString("OPTIONS_TEXT_AND_BORDER_COLOR"));
703
704 CreateSectionHeader(current_page, 1, UserString("OPTIONS_TECH_PROGRESS_COLORS"));
705 ColorOption(current_page, 1, "ui.research.status.progress.color", UserString("OPTIONS_PROGRESS_BAR_COLOR"));
706 ColorOption(current_page, 1, "ui.research.status.progress.background.color", UserString("OPTIONS_PROGRESS_BACKGROUND_COLOR"));
707 m_tabs->SetCurrentWnd(0);
708
709 // Ausosave settings tab
710 current_page = CreatePage(UserString("OPTIONS_PAGE_AUTOSAVE"));
711 BoolOption(current_page, 0, "save.auto.turn.start.enabled", UserString("OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER_TURN_START"));
712 BoolOption(current_page, 0, "save.auto.turn.end.enabled", UserString("OPTIONS_DB_AUTOSAVE_SINGLE_PLAYER_TURN_END"));
713 BoolOption(current_page, 0, "save.auto.turn.multiplayer.start.enabled", UserString("OPTIONS_DB_AUTOSAVE_MULTIPLAYER_TURN_START"));
714 IntOption(current_page, 0, "save.auto.turn.interval", UserString("OPTIONS_AUTOSAVE_TURNS_BETWEEN"));
715 IntOption(current_page, 0, "save.auto.file.limit", UserString("OPTIONS_AUTOSAVE_LIMIT"));
716 BoolOption(current_page, 0, "save.auto.initial.enabled", UserString("OPTIONS_DB_AUTOSAVE_GALAXY_CREATION"));
717 BoolOption(current_page, 0, "save.auto.exit.enabled", UserString("OPTIONS_DB_AUTOSAVE_GAME_CLOSE"));
718 m_tabs->SetCurrentWnd(0);
719
720 // Keyboard shortcuts tab
721 HotkeysPage();
722
723 // Directories tab
724 current_page = CreatePage(UserString("OPTIONS_PAGE_DIRECTORIES"));
725 /** GetRootDataDir() returns the default browse path when modifying this directory option.
726 * The actual default directory (before modifying) is gotten from the specified option name "resource.path" */
727 DirectoryOption(current_page, 0, "resource.path", UserString("OPTIONS_FOLDER_SETTINGS"), GetRootDataDir(), is_game_running);
728 DirectoryOption(current_page, 0, "save.path", UserString("OPTIONS_FOLDER_SAVE"), GetUserDataDir());
729 DirectoryOption(current_page, 0, "save.server.path", UserString("OPTIONS_SERVER_FOLDER_SAVE"), GetUserDataDir());
730 m_tabs->SetCurrentWnd(0);
731
732 // Logging page
733 current_page = CreatePage(UserString("OPTIONS_PAGE_LOGS"));
734 CreateSectionHeader(current_page, 0, UserString("OPTIONS_DB_UI_LOGGER_THRESHOLDS"),
735 UserString("OPTIONS_DB_UI_LOGGER_THRESHOLD_TOOLTIP"));
736
737 const auto log_file_sinks = LoggerOptionsLabelsAndLevels(LoggerTypes::exec);
738 for (const auto& sink : log_file_sinks) {
739 const auto& option = std::get<0>(sink);
740 const auto& option_label = std::get<1>(sink);
741 const auto&& full_label = str(FlexibleFormat(UserString("OPTIONS_DB_UI_LOGGER_PER_PROCESS_GENERAL")) % option_label);
742 LoggerLevelOption(*current_page, true, full_label, option);
743 }
744
745 const auto log_file_sources = LoggerOptionsLabelsAndLevels(LoggerTypes::named);
746 for (const auto& source : log_file_sources) {
747 const auto& option = std::get<0>(source);
748 const auto& option_label = std::get<1>(source);
749 LoggerLevelOption(*current_page, false, option_label, option);
750 }
751
752 // Misc
753 current_page = CreatePage(UserString("OPTIONS_PAGE_MISC"));
754 IntOption(current_page, 0, "effects.ui.threads", UserString("OPTIONS_EFFECTS_THREADS_UI"));
755 IntOption(current_page, 0, "effects.server.threads", UserString("OPTIONS_EFFECTS_THREADS_SERVER"));
756 IntOption(current_page, 0, "effects.ai.threads", UserString("OPTIONS_EFFECTS_THREADS_AI"));
757 BoolOption(current_page, 0, "resource.shipdesign.saved.enabled", UserString("OPTIONS_ADD_SAVED_DESIGNS"));
758 //BoolOption(current_page, 0, "resource.shipdesign.default.enabled", UserString("OPTIONS_ADD_DEFAULT_DESIGNS")); // hidden due to issues with implementation when not enabled preventing designs from being added or recreated
759 BoolOption(current_page, 0, "save.format.binary.enabled", UserString("OPTIONS_USE_BINARY_SERIALIZATION"));
760 BoolOption(current_page, 0, "save.format.xml.zlib.enabled", UserString("OPTIONS_USE_XML_ZLIB_SERIALIZATION"));
761 BoolOption(current_page, 0, "ui.map.sitrep.invalid.shown", UserString("OPTIONS_VERBOSE_SITREP_DESC"));
762 BoolOption(current_page, 0, "effects.accounting.enabled", UserString("OPTIONS_EFFECT_ACCOUNTING"));
763
764 // Create full state config button
765 auto all_config_button = GG::Wnd::Create<CUIButton>(UserString("OPTIONS_WRITE_ALL_CONFIG"));
766 all_config_button->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
767 all_config_button->SetBrowseInfoWnd(
768 GG::Wnd::Create<TextBrowseWnd>(UserString("OPTIONS_CREATE_ALL_CONFIG_TOOLTIP_TITLE"),
769 UserString("OPTIONS_CREATE_ALL_CONFIG_TOOLTIP_DESC"), ROW_WIDTH));
770 all_config_button->LeftClickedSignal.connect([]() {
771 if (GetOptionsDB().Commit(false, false))
772 ClientUI::MessageBox(UserString("OPTIONS_CREATE_ALL_CONFIG_SUCCESS"));
773 else
774 ClientUI::MessageBox(UserString("OPTIONS_CREATE_ALL_CONFIG_FAILURE"));
775 });
776 current_page->Insert(GG::Wnd::Create<OptionsListRow>(ROW_WIDTH,
777 all_config_button->MinUsableSize().y + LAYOUT_MARGIN + 6,
778 all_config_button, 0));
779
780 // Create persistent config button
781 auto persistent_config_button = GG::Wnd::Create<CUIButton>(UserString("OPTIONS_CREATE_PERSISTENT_CONFIG"));
782 persistent_config_button->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
783 persistent_config_button->SetBrowseInfoWnd(
784 GG::Wnd::Create<TextBrowseWnd>(UserString("OPTIONS_CREATE_PERSISTENT_CONFIG_TOOLTIP_TITLE"),
785 UserString("OPTIONS_CREATE_PERSISTENT_CONFIG_TOOLTIP_DESC"), ROW_WIDTH));
786 persistent_config_button->LeftClickedSignal.connect([]() {
787 if (GetOptionsDB().CommitPersistent())
788 ClientUI::MessageBox(UserString("OPTIONS_CREATE_PERSISTENT_CONFIG_SUCCESS"));
789 else
790 ClientUI::MessageBox(UserString("OPTIONS_CREATE_PERSISTENT_CONFIG_FAILURE"));
791 });
792 current_page->Insert(GG::Wnd::Create<OptionsListRow>(ROW_WIDTH,
793 persistent_config_button->MinUsableSize().y + LAYOUT_MARGIN + 6,
794 persistent_config_button, 0));
795 m_tabs->SetCurrentWnd(0);
796
797 DoLayout();
798 SaveDefaultedOptions();
799 SaveOptions();
800
801 // Connect the done and cancel button
802 m_done_button->LeftClickedSignal.connect(
803 boost::bind(&OptionsWnd::DoneClicked, this));
804 }
805
SizeMove(const GG::Pt & ul,const GG::Pt & lr)806 void OptionsWnd::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
807 const GG::Pt old_size = Size();
808 CUIWnd::SizeMove(ul, lr);
809 if (old_size != Size())
810 DoLayout();
811 }
812
DoLayout()813 void OptionsWnd::DoLayout() {
814 const GG::X BUTTON_WIDTH(75);
815 const GG::Y BUTTON_HEIGHT(ClientUI::GetFont()->Lineskip() + 6);
816
817 GG::Pt done_button_lr = ScreenToClient(ClientLowerRight()) - GG::Pt(GG::X(LAYOUT_MARGIN), GG::Y(LAYOUT_MARGIN));
818 GG::Pt done_button_ul = done_button_lr - GG::Pt(BUTTON_WIDTH, BUTTON_HEIGHT);
819
820 m_done_button->SizeMove(done_button_ul, done_button_lr);
821
822 GG::Pt tabs_lr = ScreenToClient(ClientLowerRight()) - GG::Pt(GG::X(LAYOUT_MARGIN), GG::Y(LAYOUT_MARGIN + BUTTON_HEIGHT + LAYOUT_MARGIN));
823 m_tabs->SizeMove(GG::Pt(GG::X(LAYOUT_MARGIN), GG::Y(LAYOUT_MARGIN)), tabs_lr);
824 }
825
CalculatePosition() const826 GG::Rect OptionsWnd::CalculatePosition() const {
827 GG::Pt ul((GG::GUI::GetGUI()->AppWidth() - (PAGE_WIDTH + 20)) / 2,
828 (GG::GUI::GetGUI()->AppHeight() - (PAGE_HEIGHT + 70)) / 2);
829 GG::Pt wh(PAGE_WIDTH + 20, PAGE_HEIGHT + 70);
830 return GG::Rect(ul, ul + wh);
831 }
832
CreatePage(const std::string & name)833 GG::ListBox* OptionsWnd::CreatePage(const std::string& name) {
834 auto page = GG::Wnd::Create<OptionsList>();
835 m_tabs->AddWnd(page, name);
836 m_tabs->SetCurrentWnd(m_tabs->NumWnds() - 1);
837 return page.get();
838 }
839
CreateSectionHeader(GG::ListBox * page,int indentation_level,const std::string & name,const std::string & tooltip)840 void OptionsWnd::CreateSectionHeader(GG::ListBox* page, int indentation_level,
841 const std::string& name, const std::string& tooltip)
842 {
843 assert(0 <= indentation_level);
844 auto heading_text = GG::Wnd::Create<CUILabel>(name, GG::FORMAT_LEFT | GG::FORMAT_NOWRAP);
845 heading_text->SetFont(ClientUI::GetFont(ClientUI::Pts() * 4 / 3));
846
847 auto row = GG::Wnd::Create<OptionsListRow>(ROW_WIDTH, heading_text->MinUsableSize().y + LAYOUT_MARGIN + 6,
848 heading_text, indentation_level);
849
850 if (!tooltip.empty()) {
851 row->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
852 row->SetBrowseText(tooltip);
853 }
854
855 page->Insert(row);
856 }
857
BoolOption(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text)858 GG::StateButton* OptionsWnd::BoolOption(GG::ListBox* page, int indentation_level, const std::string& option_name, const std::string& text) {
859 auto button = GG::Wnd::Create<CUIStateButton>(text, GG::FORMAT_LEFT, std::make_shared<CUICheckBoxRepresenter>());
860 auto row = GG::Wnd::Create<OptionsListRow>(ROW_WIDTH, button->MinUsableSize().y + LAYOUT_MARGIN + 6,
861 button, indentation_level);
862 page->Insert(row);
863 button->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
864 button->SetCheck(GetOptionsDB().Get<bool>(option_name));
865 button->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
866 button->CheckedSignal.connect(
867 [option_name](const bool& value){ GetOptionsDB().Set(option_name, value); });
868 return button.get();
869 }
870
871 namespace {
HandleSetHotkeyOption(const std::string & hk_name,GG::Button * button)872 void HandleSetHotkeyOption(const std::string & hk_name, GG::Button* button) {
873 std::pair<GG::Key, GG::Flags<GG::ModKey>> kp = KeyPressCatcher::GetKeypress();
874
875 // abort of escape was pressed...
876 if (kp.first == GG::GGK_ESCAPE)
877 return;
878
879 // check if pressed key is different from existing setting...
880 const Hotkey& hotkey = Hotkey::NamedHotkey(hk_name);
881 if (hotkey.m_key == kp.first && hotkey.m_mod_keys == kp.second)
882 return; // nothing to change
883
884
885 // set hotkey to new pressed key / modkey combination
886 Hotkey::SetHotkey(hotkey, kp.first, kp.second);
887
888 // indicate new hotkey on button
889 button->SetText(Hotkey::NamedHotkey(hk_name).PrettyPrint());
890
891 // update shortcuts for new hotkey
892 HotkeyManager::GetManager()->RebuildShortcuts();
893 }
894
HandleResetHotkeyOption(const std::string & hk_name,GG::Button * button)895 void HandleResetHotkeyOption(const std::string & hk_name, GG::Button* button) {
896 const Hotkey& hotkey = Hotkey::NamedHotkey(hk_name);
897 if (hotkey.IsDefault())
898 hotkey.ClearHotkey(hotkey);
899 else
900 hotkey.ResetHotkey(hotkey);
901
902 // indicate new hotkey on button
903 button->SetText(Hotkey::NamedHotkey(hk_name).PrettyPrint());
904
905 // update shortcuts for new hotkey
906 HotkeyManager::GetManager()->RebuildShortcuts();
907 }
908 }
909
HotkeyOption(GG::ListBox * page,int indentation_level,const std::string & hotkey_name)910 void OptionsWnd::HotkeyOption(GG::ListBox* page, int indentation_level, const std::string& hotkey_name) {
911 const Hotkey & hk = Hotkey::NamedHotkey(hotkey_name);
912 std::string text = UserString(hk.GetDescription());
913 auto text_control = GG::Wnd::Create<CUILabel>(text, GG::FORMAT_LEFT | GG::FORMAT_NOWRAP, GG::INTERACTIVE);
914 auto button = Wnd::Create<CUIButton>(hk.PrettyPrint());
915
916 auto layout = GG::Wnd::Create<GG::Layout>(GG::X0, GG::Y0, ROW_WIDTH, std::max(button->MinUsableSize().y, text_control->MinUsableSize().y),
917 1, 2, 0, 5);
918 layout->Add(text_control, 0, 0, GG::ALIGN_VCENTER | GG::ALIGN_LEFT);
919 layout->Add(button, 0, 1, GG::ALIGN_VCENTER | GG::ALIGN_RIGHT);
920
921 auto row = GG::Wnd::Create<OptionsListRow>(ROW_WIDTH, std::max(button->MinUsableSize().y, text_control->MinUsableSize().y) + 6,
922 layout, indentation_level);
923
924 button->LeftClickedSignal.connect(boost::bind(HandleSetHotkeyOption, hotkey_name, button.get()));
925 button->RightClickedSignal.connect(boost::bind(HandleResetHotkeyOption, hotkey_name, button.get()));
926
927 page->Insert(row);
928 }
929
IntOption(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text)930 GG::Spin<int>* OptionsWnd::IntOption(GG::ListBox* page, int indentation_level, const std::string& option_name, const std::string& text) {
931 auto text_control = GG::Wnd::Create<CUILabel>(text, GG::FORMAT_LEFT | GG::FORMAT_NOWRAP, GG::INTERACTIVE);
932 std::shared_ptr<const ValidatorBase> validator = GetOptionsDB().GetValidator(option_name);
933 std::shared_ptr<GG::Spin<int>> spin;
934 int value = GetOptionsDB().Get<int>(option_name);
935 if (std::shared_ptr<const RangedValidator<int>> ranged_validator = std::dynamic_pointer_cast<const RangedValidator<int>>(validator))
936 spin = GG::Wnd::Create<CUISpin<int>>(value, 1, ranged_validator->m_min, ranged_validator->m_max, true);
937 else if (std::shared_ptr<const StepValidator<int>> step_validator = std::dynamic_pointer_cast<const StepValidator<int>>(validator))
938 spin = GG::Wnd::Create<CUISpin<int>>(value, step_validator->m_step_size, -1000000, 1000000, true);
939 else if (std::shared_ptr<const RangedStepValidator<int>> ranged_step_validator = std::dynamic_pointer_cast<const RangedStepValidator<int>>(validator))
940 spin = GG::Wnd::Create<CUISpin<int>>(value, ranged_step_validator->m_step_size, ranged_step_validator->m_min, ranged_step_validator->m_max, true);
941 else if (std::shared_ptr<const Validator<int>> int_validator = std::dynamic_pointer_cast<const Validator<int>>(validator))
942 spin = GG::Wnd::Create<CUISpin<int>>(value, 1, -1000000, 1000000, true);
943 if (!spin) {
944 ErrorLogger() << "Unable to create IntOption spin";
945 return nullptr;
946 }
947 spin->Resize(GG::Pt(SPIN_WIDTH, spin->MinUsableSize().y));
948 auto layout = GG::Wnd::Create<GG::Layout>(GG::X0, GG::Y0, ROW_WIDTH, spin->MinUsableSize().y, 1, 2, 0, 5);
949 layout->Add(spin, 0, 0, GG::ALIGN_VCENTER | GG::ALIGN_LEFT);
950 layout->Add(text_control, 0, 1, GG::ALIGN_VCENTER | GG::ALIGN_LEFT);
951 layout->SetMinimumColumnWidth(0, SPIN_WIDTH);
952 layout->SetColumnStretch(1, 1.0);
953 layout->SetChildClippingMode(ClipToClient);
954
955 auto row = GG::Wnd::Create<OptionsListRow>(ROW_WIDTH, spin->MinUsableSize().y, layout, indentation_level);
956 page->Insert(row);
957
958 spin->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
959 spin->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
960 text_control->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
961 text_control->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
962 spin->ValueChangedSignal.connect(
963 [option_name](const int& new_value){ GetOptionsDB().Set(option_name, new_value); });
964 return spin.get();
965 }
966
DoubleOption(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text)967 GG::Spin<double>* OptionsWnd::DoubleOption(GG::ListBox* page, int indentation_level, const std::string& option_name, const std::string& text) {
968 auto text_control = GG::Wnd::Create<CUILabel>(text, GG::FORMAT_LEFT | GG::FORMAT_NOWRAP, GG::INTERACTIVE);
969 std::shared_ptr<const ValidatorBase> validator = GetOptionsDB().GetValidator(option_name);
970 std::shared_ptr<GG::Spin<double>> spin;
971 double value = GetOptionsDB().Get<double>(option_name);
972 if (std::shared_ptr<const RangedValidator<double>> ranged_validator = std::dynamic_pointer_cast<const RangedValidator<double>>(validator))
973 spin = GG::Wnd::Create<CUISpin<double>>(value, 1, ranged_validator->m_min, ranged_validator->m_max, true);
974 else if (std::shared_ptr<const StepValidator<double>> step_validator = std::dynamic_pointer_cast<const StepValidator<double>>(validator))
975 spin = GG::Wnd::Create<CUISpin<double>>(value, step_validator->m_step_size, -1000000, 1000000, true);
976 else if (std::shared_ptr<const RangedStepValidator<double>> ranged_step_validator = std::dynamic_pointer_cast<const RangedStepValidator<double>>(validator))
977 spin = GG::Wnd::Create<CUISpin<double>>(value, ranged_step_validator->m_step_size, ranged_step_validator->m_min, ranged_step_validator->m_max, true);
978 else if (std::shared_ptr<const Validator<double>> double_validator = std::dynamic_pointer_cast<const Validator<double>>(validator))
979 spin = GG::Wnd::Create<CUISpin<double>>(value, 1, -1000000, 1000000, true);
980 if (!spin) {
981 ErrorLogger() << "Unable to create DoubleOption spin";
982 return nullptr;
983 }
984 spin->Resize(GG::Pt(SPIN_WIDTH, spin->MinUsableSize().y));
985 auto layout = GG::Wnd::Create<GG::Layout>(GG::X0, GG::Y0, ROW_WIDTH, spin->MinUsableSize().y, 1, 2, 0, 5);
986 layout->Add(spin, 0, 0, GG::ALIGN_VCENTER | GG::ALIGN_LEFT);
987 layout->Add(text_control, 0, 1, GG::ALIGN_VCENTER | GG::ALIGN_LEFT);
988 layout->SetMinimumColumnWidth(0, SPIN_WIDTH);
989 layout->SetColumnStretch(1, 1.0);
990 layout->SetChildClippingMode(ClipToClient);
991
992 auto row = GG::Wnd::Create<OptionsListRow>(ROW_WIDTH, spin->MinUsableSize().y, layout, indentation_level);
993 page->Insert(row);
994
995 spin->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
996 spin->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
997 text_control->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
998 text_control->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
999 spin->ValueChangedSignal.connect(
1000 [option_name](const double& new_value){ GetOptionsDB().Set(option_name, new_value); });
1001 return spin.get();
1002 }
1003
MusicVolumeOption(GG::ListBox * page,int indentation_level,SoundOptionsFeedback & fb)1004 void OptionsWnd::MusicVolumeOption(GG::ListBox* page, int indentation_level, SoundOptionsFeedback &fb) {
1005 auto row = GG::Wnd::Create<GG::ListBox::Row>();
1006 auto button = GG::Wnd::Create<CUIStateButton>(UserString("OPTIONS_MUSIC"), GG::FORMAT_LEFT, std::make_shared<CUICheckBoxRepresenter>());
1007 button->Resize(button->MinUsableSize());
1008 button->SetCheck(GetOptionsDB().Get<bool>("audio.music.enabled"));
1009 std::shared_ptr<const RangedValidator<int>> validator = std::dynamic_pointer_cast<const RangedValidator<int>>(GetOptionsDB().GetValidator("audio.music.volume"));
1010 assert(validator);
1011 auto slider = GG::Wnd::Create<CUISlider<int>>(validator->m_min, validator->m_max, GG::HORIZONTAL);
1012 slider->SlideTo(GetOptionsDB().Get<int>("audio.music.volume"));
1013 auto layout = GG::Wnd::Create<GG::Layout>(GG::X0, GG::Y0, GG::X1, GG::Y1, 1, 2, 0, 5);
1014 layout->Add(button, 0, 0);
1015 layout->Add(slider, 0, 1);
1016 row->Resize(GG::Pt(ROW_WIDTH, std::max(button->MinUsableSize().y, slider->MinUsableSize().y) + 6));
1017 row->push_back(GG::Wnd::Create<RowContentsWnd>(row->Width(), row->Height(), layout, indentation_level));
1018 page->Insert(row);
1019 button->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1020 button->SetBrowseText(UserString(GetOptionsDB().GetDescription("audio.music.enabled")));
1021 slider->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1022 slider->SetBrowseText(UserString(GetOptionsDB().GetDescription("audio.music.volume")));
1023
1024 #if BOOST_VERSION >= 106000
1025 using boost::placeholders::_1;
1026 using boost::placeholders::_2;
1027 using boost::placeholders::_3;
1028 #endif
1029
1030 button->CheckedSignal.connect(
1031 boost::bind(&OptionsWnd::SoundOptionsFeedback::MusicClicked, &fb, _1));
1032 slider->SlidSignal.connect(
1033 boost::bind(&OptionsWnd::SoundOptionsFeedback::MusicVolumeSlid, &fb, _1, _2, _3));
1034 fb.SetMusicButton(std::move(button));
1035 }
1036
VolumeOption(GG::ListBox * page,int indentation_level,const std::string & toggle_option_name,const std::string & volume_option_name,const std::string & text,bool toggle_value,SoundOptionsFeedback & fb)1037 void OptionsWnd::VolumeOption(GG::ListBox* page, int indentation_level, const std::string& toggle_option_name,
1038 const std::string& volume_option_name, const std::string& text,
1039 bool toggle_value, SoundOptionsFeedback &fb)
1040 {
1041 auto row = GG::Wnd::Create<GG::ListBox::Row>();
1042 auto button = GG::Wnd::Create<CUIStateButton>(text, GG::FORMAT_LEFT, std::make_shared<CUICheckBoxRepresenter>());
1043 button->Resize(button->MinUsableSize());
1044 button->SetCheck(toggle_value);
1045 std::shared_ptr<const RangedValidator<int>> validator = std::dynamic_pointer_cast<const RangedValidator<int>>(GetOptionsDB().GetValidator(volume_option_name));
1046 assert(validator);
1047 auto slider = GG::Wnd::Create<CUISlider<int>>(validator->m_min, validator->m_max, GG::HORIZONTAL);
1048 slider->SlideTo(GetOptionsDB().Get<int>(volume_option_name));
1049 auto layout = GG::Wnd::Create<GG::Layout>(GG::X0, GG::Y0, GG::X1, GG::Y1, 1, 2, 0, 5);
1050 layout->Add(button, 0, 0);
1051 layout->Add(slider, 0, 1);
1052 row->Resize(GG::Pt(ROW_WIDTH, std::max(button->MinUsableSize().y, slider->MinUsableSize().y) + 6));
1053 row->push_back(GG::Wnd::Create<RowContentsWnd>(row->Width(), row->Height(), layout, indentation_level));
1054 page->Insert(row);
1055 button->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1056 button->SetBrowseText(UserString(GetOptionsDB().GetDescription(toggle_option_name)));
1057 slider->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1058 slider->SetBrowseText(UserString(GetOptionsDB().GetDescription(volume_option_name)));
1059
1060 #if BOOST_VERSION >= 106000
1061 using boost::placeholders::_1;
1062 using boost::placeholders::_2;
1063 using boost::placeholders::_3;
1064 #endif
1065
1066 button->CheckedSignal.connect(
1067 boost::bind(&OptionsWnd::SoundOptionsFeedback::SoundEffectsEnableClicked, &fb, _1));
1068 slider->SlidAndStoppedSignal.connect(
1069 boost::bind(&OptionsWnd::SoundOptionsFeedback::UISoundsVolumeSlid, &fb, _1, _2, _3));
1070 fb.SetEffectsButton(std::move(button));
1071 }
1072
FileOptionImpl(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text,const fs::path & path,const std::vector<std::pair<std::string,std::string>> & filters,std::function<bool (const std::string &)> string_validator,bool directory,bool relative_path,bool disabled)1073 void OptionsWnd::FileOptionImpl(GG::ListBox* page, int indentation_level, const std::string& option_name,
1074 const std::string& text, const fs::path& path,
1075 const std::vector<std::pair<std::string, std::string>>& filters,
1076 std::function<bool (const std::string&)> string_validator,
1077 bool directory, bool relative_path,
1078 bool disabled)
1079 {
1080 auto text_control = GG::Wnd::Create<CUILabel>(text, GG::FORMAT_LEFT | GG::FORMAT_NOWRAP, GG::INTERACTIVE);
1081 auto edit = GG::Wnd::Create<CUIEdit>(GetOptionsDB().Get<std::string>(option_name));
1082 edit->Resize(GG::Pt(50*SPIN_WIDTH, edit->Height())); // won't resize within layout bigger than its initial size, so giving a big initial size here
1083 auto button = Wnd::Create<CUIButton>("...");
1084 if (disabled) {
1085 edit->Disable();
1086 button->Disable();
1087 }
1088
1089 auto layout = GG::Wnd::Create<GG::Layout>(GG::X0, GG::Y0, ROW_WIDTH, button->MinUsableSize().y,
1090 1, 3, 0, 5);
1091
1092 layout->Add(text_control, 0, 0, GG::ALIGN_VCENTER | GG::ALIGN_LEFT);
1093 layout->Add(edit, 0, 1, GG::ALIGN_VCENTER | GG::ALIGN_LEFT);
1094 layout->Add(button, 0, 2, GG::ALIGN_VCENTER | GG::ALIGN_LEFT);
1095 layout->SetMinimumColumnWidth(0, SPIN_WIDTH);
1096 layout->SetMinimumColumnWidth(1, SPIN_WIDTH);
1097 layout->SetMinimumColumnWidth(2, button->Width());
1098 layout->SetColumnStretch(0, 0.5);
1099 layout->SetColumnStretch(1, 1.0);
1100 layout->SetColumnStretch(2, 0.0);
1101
1102 auto row = GG::Wnd::Create<OptionsListRow>(ROW_WIDTH, layout->Height() + 6, layout, indentation_level);
1103 page->Insert(row);
1104
1105 edit->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1106 edit->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
1107 button->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1108 button->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
1109 text_control->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1110 text_control->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
1111 edit->EditedSignal.connect(
1112 [option_name, edit, string_validator](const std::string& str) {
1113 if (string_validator && !string_validator(str)) {
1114 edit->SetTextColor(GG::CLR_RED);
1115 } else {
1116 edit->SetTextColor(ClientUI::TextColor());
1117 GetOptionsDB().Set<std::string>(option_name, str);
1118 }
1119 }
1120 );
1121 button->LeftClickedSignal.connect(
1122 BrowseForPathButtonFunctor(path, filters, edit, directory, relative_path));
1123 if (string_validator && !string_validator(edit->Text()))
1124 edit->SetTextColor(GG::CLR_RED);
1125 }
1126
FileOption(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text,const boost::filesystem::path & path,std::function<bool (const std::string &)> string_validator)1127 void OptionsWnd::FileOption(GG::ListBox* page, int indentation_level, const std::string& option_name, const std::string& text, const boost::filesystem::path& path,
1128 std::function<bool (const std::string&)> string_validator/* = 0*/)
1129 { FileOption(page, indentation_level, option_name, text, path, std::vector<std::pair<std::string, std::string>>(), string_validator); }
1130
FileOption(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text,const boost::filesystem::path & path,const std::pair<std::string,std::string> & filter,std::function<bool (const std::string &)> string_validator)1131 void OptionsWnd::FileOption(GG::ListBox* page, int indentation_level, const std::string& option_name, const std::string& text, const boost::filesystem::path& path,
1132 const std::pair<std::string, std::string>& filter, std::function<bool (const std::string&)> string_validator/* = 0*/)
1133 { FileOption(page, indentation_level, option_name, text, path, std::vector<std::pair<std::string, std::string>>(1, filter), string_validator); }
1134
FileOption(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text,const boost::filesystem::path & path,const std::vector<std::pair<std::string,std::string>> & filters,std::function<bool (const std::string &)> string_validator)1135 void OptionsWnd::FileOption(GG::ListBox* page, int indentation_level, const std::string& option_name, const std::string& text, const boost::filesystem::path& path,
1136 const std::vector<std::pair<std::string, std::string>>& filters, std::function<bool (const std::string&)> string_validator/* = 0*/)
1137 { FileOptionImpl(page, indentation_level, option_name, text, path, filters, string_validator, false, false, false); }
1138
SoundFileOption(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text)1139 void OptionsWnd::SoundFileOption(GG::ListBox* page, int indentation_level, const std::string& option_name, const std::string& text) {
1140 FileOption(page, indentation_level, option_name, text, ClientUI::SoundDir(),
1141 {UserString("OPTIONS_SOUND_FILE"), "*" + SOUND_FILE_SUFFIX}, ValidSoundFile);
1142 }
1143
DirectoryOption(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text,const fs::path & path,bool disabled)1144 void OptionsWnd::DirectoryOption(GG::ListBox* page, int indentation_level, const std::string& option_name,
1145 const std::string& text, const fs::path& path, bool disabled /*= false*/)
1146 {
1147 FileOptionImpl(page, indentation_level, option_name, text, path, std::vector<std::pair<std::string, std::string>>(),
1148 ValidDirectory, true, false, disabled);
1149 }
1150
ColorOption(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text)1151 void OptionsWnd::ColorOption(GG::ListBox* page, int indentation_level, const std::string& option_name, const std::string& text) {
1152 auto row = GG::Wnd::Create<GG::ListBox::Row>();
1153 auto text_control = GG::Wnd::Create<CUILabel>(text, GG::FORMAT_LEFT | GG::FORMAT_NOWRAP, GG::INTERACTIVE);
1154 auto color_selector = GG::Wnd::Create<ColorSelector>(GetOptionsDB().Get<GG::Clr>(option_name),
1155 GetOptionsDB().GetDefault<GG::Clr>(option_name));
1156 color_selector->Resize(GG::Pt(color_selector->Width(), GG::Y(ClientUI::Pts() + 4)));
1157 color_selector->SetMaxSize(GG::Pt(color_selector->MaxSize().x, color_selector->Size().y));
1158 auto layout = GG::Wnd::Create<GG::Layout>(GG::X0, GG::Y0, GG::X1, GG::Y1, 1, 2);
1159 layout->Add(text_control, 0, 0);
1160 layout->Add(color_selector, 0, 1, 1, 1, GG::ALIGN_VCENTER);
1161 layout->SetMinimumColumnWidth(1, COLOR_SELECTOR_WIDTH);
1162 layout->SetColumnStretch(0, 1.0);
1163 row->Resize(GG::Pt(ROW_WIDTH, std::max(text_control->MinUsableSize().y, color_selector->MinUsableSize().y) + 6));
1164 row->push_back(GG::Wnd::Create<RowContentsWnd>(row->Width(), row->Height(), layout, indentation_level));
1165 page->Insert(row);
1166 color_selector->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1167 color_selector->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
1168 text_control->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1169 text_control->SetBrowseText(UserString(GetOptionsDB().GetDescription(option_name)));
1170 color_selector->ColorChangedSignal.connect(
1171 [option_name](const GG::Clr& clr) { GetOptionsDB().Set<GG::Clr>(option_name, clr); });
1172 }
1173
FontOption(GG::ListBox * page,int indentation_level,const std::string & option_name,const std::string & text)1174 void OptionsWnd::FontOption(GG::ListBox* page, int indentation_level, const std::string& option_name, const std::string& text) {
1175 FileOption(page, indentation_level, option_name, text, GetRootDataDir() / "default",
1176 {std::string(option_name), "*" + FONT_FILE_SUFFIX},
1177 &ValidFontFile);
1178 }
1179
ResolutionOption(GG::ListBox * page,int indentation_level)1180 void OptionsWnd::ResolutionOption(GG::ListBox* page, int indentation_level) {
1181 std::shared_ptr<const RangedValidator<int>> width_validator =
1182 std::dynamic_pointer_cast<const RangedValidator<int>>(
1183 GetOptionsDB().GetValidator("video.fullscreen.width"));
1184 std::shared_ptr<const RangedValidator<int>> height_validator =
1185 std::dynamic_pointer_cast<const RangedValidator<int>>(
1186 GetOptionsDB().GetValidator("video.fullscreen.height"));
1187 std::shared_ptr<const RangedValidator<int>> windowed_width_validator =
1188 std::dynamic_pointer_cast<const RangedValidator<int>>(
1189 GetOptionsDB().GetValidator("video.windowed.width"));
1190 std::shared_ptr<const RangedValidator<int>> windowed_height_validator =
1191 std::dynamic_pointer_cast<const RangedValidator<int>>(
1192 GetOptionsDB().GetValidator("video.windowed.height"));
1193 std::shared_ptr<const RangedValidator<int>> windowed_left_validator =
1194 std::dynamic_pointer_cast<const RangedValidator<int>>(
1195 GetOptionsDB().GetValidator("video.windowed.left"));
1196 std::shared_ptr<const RangedValidator<int>> windowed_top_validator =
1197 std::dynamic_pointer_cast<const RangedValidator<int>>(
1198 GetOptionsDB().GetValidator("video.windowed.top"));
1199
1200 // compile list of resolutions available on this system
1201
1202 std::vector<std::string> resolutions = GG::GUI::GetGUI()->GetSupportedResolutions();
1203
1204 // find text representation of current fullscreen resolution selection
1205 int width = GetOptionsDB().Get<int>("video.fullscreen.width");
1206 int height = GetOptionsDB().Get<int>("video.fullscreen.height");
1207 std::string current_video_mode_str = boost::io::str(boost::format("%1% x %2%") % width % height);
1208
1209 // find which index in list, if any, represents current fullscreen resolution selection
1210 int current_resolution_index = -1, loop_res_index = 0;
1211 for (const std::string& resolution : resolutions) {
1212 if (resolution == current_video_mode_str)
1213 current_resolution_index = loop_res_index;
1214 ++loop_res_index;
1215 }
1216
1217
1218 // drop list and label
1219 auto drop_list_label = GG::Wnd::Create<CUILabel>(UserString("OPTIONS_VIDEO_MODE"), GG::FORMAT_LEFT | GG::FORMAT_NOWRAP, GG::INTERACTIVE);
1220 drop_list_label->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1221 drop_list_label->SetBrowseText(UserString("OPTIONS_VIDEO_MODE_LIST_DESCRIPTION"));
1222
1223 auto drop_list = GG::Wnd::Create<CUIDropDownList>(6);
1224 drop_list->Resize(GG::Pt(drop_list->MinUsableSize().x, GG::Y(ClientUI::Pts() + 4)));
1225 drop_list->SetMaxSize(GG::Pt(drop_list->MaxSize().x, drop_list->Size().y));
1226 drop_list->SetStyle(GG::LIST_NOSORT);
1227 drop_list->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1228 drop_list->SetBrowseText(UserString("OPTIONS_VIDEO_MODE_LIST_DESCRIPTION"));
1229 drop_list->SetOnlyMouseScrollWhenDropped(true);
1230
1231 auto layout = GG::Wnd::Create<GG::Layout>(GG::X0, GG::Y0, GG::X1, GG::Y1, 2, 1, 0, LAYOUT_MARGIN);
1232 layout->Add(drop_list_label, 0, 0);
1233 layout->Add(drop_list, 1, 0, 1, 1, GG::ALIGN_VCENTER);
1234
1235 auto row = GG::Wnd::Create<GG::ListBox::Row>();
1236 row->Resize(GG::Pt(ROW_WIDTH, drop_list_label->MinUsableSize().y + LAYOUT_MARGIN + drop_list->MaxSize().y + 6));
1237 row->push_back(GG::Wnd::Create<RowContentsWnd>(row->Width(), row->Height(), layout, indentation_level));
1238
1239 page->Insert(row);
1240
1241
1242 // selectable rows in video modes list box...
1243 for (const std::string& resolution : resolutions) {
1244 auto video_mode_row = GG::Wnd::Create<CUISimpleDropDownListRow>(resolution);
1245 video_mode_row->SetName(resolution);
1246 drop_list->Insert(video_mode_row);
1247 }
1248
1249 if (drop_list->NumRows() >= 1 && current_resolution_index != -1)
1250 drop_list->Select(current_resolution_index);
1251
1252 // fullscreen / windowed toggle
1253 BoolOption(page, indentation_level, "video.fullscreen.enabled", UserString("OPTIONS_FULLSCREEN"));
1254 // Fake mode change is not possible without the opengl frame buffer extension
1255 if (SDLGUI::GetGUI()->FramebuffersAvailable()) {
1256 BoolOption(page, indentation_level, "video.fullscreen.fake.enabled", UserString("OPTIONS_FAKE_MODE_CHANGE"));
1257 } else {
1258 GetOptionsDB().Set<bool>("video.fullscreen.fake.enabled", false);
1259 }
1260 IntOption(page, indentation_level, "video.monitor.id", UserString("OPTIONS_FULLSCREEN_MONITOR_ID"));
1261
1262
1263 // customizable windowed width and height
1264 auto windowed_spinner_label = GG::Wnd::Create<CUILabel>(UserString("OPTIONS_VIDEO_MODE_WINDOWED"), GG::FORMAT_LEFT | GG::FORMAT_NOWRAP, GG::INTERACTIVE);
1265 windowed_spinner_label->SetBrowseModeTime(GetOptionsDB().Get<int>("ui.tooltip.delay"));
1266 windowed_spinner_label->SetBrowseText(UserString("OPTIONS_VIDEO_MODE_WINDOWED_SPINNERS_DESCRIPTION"));
1267
1268 row = GG::Wnd::Create<GG::ListBox::Row>();
1269 row->Resize(GG::Pt(ROW_WIDTH, windowed_spinner_label->MinUsableSize().y + LAYOUT_MARGIN + 6));
1270 row->push_back(GG::Wnd::Create<RowContentsWnd>(row->Width(), row->Height(), windowed_spinner_label, indentation_level));
1271 page->Insert(row);
1272
1273 IntOption(page, indentation_level, "video.windowed.width", UserString("OPTIONS_APP_WIDTH_WINDOWED"));
1274 IntOption(page, indentation_level, "video.windowed.height", UserString("OPTIONS_APP_HEIGHT_WINDOWED"));
1275 IntOption(page, indentation_level, "video.windowed.left", UserString("OPTIONS_APP_LEFT_WINDOWED"));
1276 IntOption(page, indentation_level, "video.windowed.top", UserString("OPTIONS_APP_TOP_WINDOWED"));
1277
1278 // fps
1279 BoolOption(page, indentation_level, "video.fps.shown", UserString("OPTIONS_SHOW_FPS"));
1280
1281 //GG::StateButton* limit_FPS_button = BoolOption(page, indentation_level, "video.fps.max.enabled", UserString("OPTIONS_LIMIT_FPS"));
1282 //GG::Spin<double>* max_fps_spin =
1283 DoubleOption(page, indentation_level, "video.fps.max", UserString("OPTIONS_MAX_FPS"));
1284 //limit_FPS_button->CheckedSignal, [max_fps_spin](bool checked) { max_fps_spin->Disable(!checked); });
1285 //limit_FPS_button->SetCheck(GetOptionsDB().Get<bool>("video.fps.max.enabled"));
1286 //limit_FPS_button->CheckedSignal(limit_FPS_button->Checked());
1287
1288 //GG::StateButton* limit_FPS_nofocus_button = BoolOption(page, indentation_level, "video.fps.unfocused.enabled", UserString("OPTIONS_LIMIT_FPS_NO_FOCUS"));
1289 //GG::Spin<double>* max_fps_nofocus_spin =
1290 DoubleOption(page, indentation_level, "video.fps.unfocused", UserString("OPTIONS_MAX_FPS_NO_FOCUS"));
1291 //limit_FPS_nofocus_button->SetCheck(GetOptionsDB().Get<bool>("video.fps.unfocused.enabled"));
1292
1293
1294 // apply button, sized to fit text
1295 auto apply_button = Wnd::Create<CUIButton>(UserString("OPTIONS_APPLY"));
1296 row = GG::Wnd::Create<OptionsListRow>(ROW_WIDTH, apply_button->MinUsableSize().y + LAYOUT_MARGIN + 6,
1297 apply_button, indentation_level);
1298 page->Insert(row);
1299 apply_button->LeftClickedSignal.connect(
1300 boost::bind(&HumanClientApp::Reinitialize, HumanClientApp::GetApp()));
1301
1302 drop_list->SelChangedSignal.connect(
1303 [drop_list](GG::ListBox::iterator it) {
1304 if (it == drop_list->end())
1305 return;
1306 const auto& drop_list_row = *it;
1307 if (!drop_list_row)
1308 return;
1309 int w, h;
1310 namespace phx = boost::phoenix;
1311 namespace qi = boost::spirit::qi;
1312 qi::parse(
1313 drop_list_row->Name().begin(), drop_list_row->Name().end(),
1314 (qi::int_[phx::ref(w) = qi::_1] >> " x " >> qi::int_[phx::ref(h) = qi::_1])
1315 );
1316 GetOptionsDB().Set<int>("video.fullscreen.width", w);
1317 GetOptionsDB().Set<int>("video.fullscreen.height", h);
1318 }
1319 );
1320 }
1321
1322 namespace {
ValidSectionForHotkey(const std::string & hotkey_name)1323 std::string ValidSectionForHotkey(const std::string& hotkey_name) {
1324 std::string retval { "HOTKEYS_GENERAL" };
1325
1326 std::string name { hotkey_name };
1327 std::transform(name.begin(), name.end(), name.begin(), ::toupper);
1328
1329 const std::regex dot { "\\.+" };
1330 const std::vector<std::string> nodes {
1331 std::sregex_token_iterator(name.begin(), name.end(), dot, -1),
1332 std::sregex_token_iterator()
1333 };
1334
1335 std::string current_node { "" };
1336 for (const auto& node : nodes) {
1337 if (current_node == name)
1338 break;
1339
1340 current_node.append( "_" + node);
1341 if (UserStringExists("HOTKEYS" + current_node))
1342 retval = "HOTKEYS" + current_node;
1343 }
1344
1345 return retval;
1346 }
1347
HotkeysBySection()1348 std::map<std::string, std::set<std::string>> HotkeysBySection() {
1349 std::map<std::string, std::set<std::string>> retval;
1350 for (const auto& entry : Hotkey::DefinedHotkeys()) {
1351 retval[ValidSectionForHotkey(entry)].insert(entry);
1352 }
1353 return retval;
1354 }
1355 }
1356
HotkeysPage()1357 void OptionsWnd::HotkeysPage() {
1358
1359 GG::ListBox* page = CreatePage(UserString("OPTIONS_PAGE_HOTKEYS"));
1360 for (const auto& class_hotkeys : HotkeysBySection()) {
1361 CreateSectionHeader(page, 0, UserString(class_hotkeys.first));
1362 for (const std::string& hotkey : class_hotkeys.second)
1363 HotkeyOption(page, 0, hotkey);
1364 }
1365 m_tabs->SetCurrentWnd(0);
1366 }
1367
~OptionsWnd()1368 OptionsWnd::~OptionsWnd()
1369 {}
1370
KeyPress(GG::Key key,std::uint32_t key_code_point,GG::Flags<GG::ModKey> mod_keys)1371 void OptionsWnd::KeyPress(GG::Key key, std::uint32_t key_code_point,
1372 GG::Flags<GG::ModKey> mod_keys)
1373 {
1374 if (key == GG::GGK_ESCAPE || key == GG::GGK_RETURN || key == GG::GGK_KP_ENTER) // Same behaviour as if "done" was pressed
1375 DoneClicked();
1376 }
1377
DoneClicked()1378 void OptionsWnd::DoneClicked() {
1379 GetOptionsDB().Commit();
1380 m_done = true;
1381 }
1382
SoundEffectsEnableClicked(bool checked)1383 void OptionsWnd::SoundOptionsFeedback::SoundEffectsEnableClicked(bool checked) {
1384 if (checked) {
1385 try {
1386 Sound::GetSound().Enable();
1387 GetOptionsDB().Set("audio.effects.enabled", true);
1388 Sound::GetSound().PlaySound(GetOptionsDB().Get<std::string>("ui.button.press.sound.path"), true);
1389 } catch (Sound::InitializationFailureException const &e) {
1390 SoundInitializationFailure(e);
1391 }
1392 } else {
1393 GetOptionsDB().Set("audio.effects.enabled", false);
1394 if (!GetOptionsDB().Get<bool>("audio.music.enabled"))
1395 Sound::GetSound().Disable();
1396 }
1397 }
1398
MusicClicked(bool checked)1399 void OptionsWnd::SoundOptionsFeedback::MusicClicked(bool checked) {
1400 if (checked) {
1401 try {
1402 Sound::GetSound().Enable();
1403 GetOptionsDB().Set("audio.music.enabled", true);
1404 Sound::GetSound().PlayMusic(GetOptionsDB().Get<std::string>("audio.music.path"), -1);
1405 } catch (Sound::InitializationFailureException const &e) {
1406 SoundInitializationFailure(e);
1407 }
1408 } else {
1409 GetOptionsDB().Set("audio.music.enabled", false);
1410 Sound::GetSound().StopMusic();
1411 if (!GetOptionsDB().Get<bool>("audio.effects.enabled"))
1412 Sound::GetSound().Disable();
1413 }
1414 }
1415
MusicVolumeSlid(int pos,int low,int high) const1416 void OptionsWnd::SoundOptionsFeedback::MusicVolumeSlid(int pos, int low, int high) const {
1417 GetOptionsDB().Set("audio.music.volume", pos);
1418 Sound::GetSound().SetMusicVolume(pos);
1419 }
1420
UISoundsVolumeSlid(int pos,int low,int high) const1421 void OptionsWnd::SoundOptionsFeedback::UISoundsVolumeSlid(int pos, int low, int high) const {
1422 GetOptionsDB().Set("audio.effects.volume", pos);
1423 Sound::GetSound().SetUISoundsVolume(pos);
1424 Sound::GetSound().PlaySound(GetOptionsDB().Get<std::string>("ui.button.press.sound.path"), true);
1425 }
1426
SetMusicButton(std::shared_ptr<GG::StateButton> button)1427 void OptionsWnd::SoundOptionsFeedback::SetMusicButton(std::shared_ptr<GG::StateButton> button)
1428 { m_music_button = button; }
1429
SetEffectsButton(std::shared_ptr<GG::StateButton> button)1430 void OptionsWnd::SoundOptionsFeedback::SetEffectsButton(std::shared_ptr<GG::StateButton> button)
1431 { m_effects_button = button; }
1432
SoundInitializationFailure(Sound::InitializationFailureException const & e)1433 void OptionsWnd::SoundOptionsFeedback::SoundInitializationFailure(Sound::InitializationFailureException const &e) {
1434 GetOptionsDB().Set("audio.effects.enabled", false);
1435 GetOptionsDB().Set("audio.music.enabled", false);
1436 if (m_effects_button)
1437 m_effects_button->SetCheck(false);
1438 if (m_music_button)
1439 m_music_button->SetCheck(false);
1440 ClientUI::MessageBox(UserString(e.what()), false);
1441 }
1442