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