]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/preferences.cpp
gcc: appease the hardening warnings
[xonotic/netradiant.git] / radiant / preferences.cpp
1 /*
2    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 //
23 // User preferences
24 //
25 // Leonardo Zide (leo@lokigames.com)
26 //
27
28 #include "preferences.h"
29 #include "globaldefs.h"
30
31 #include <gtk/gtk.h>
32 #include "environment.h"
33
34 #include "debugging/debugging.h"
35
36 #include "generic/callback.h"
37 #include "math/vector.h"
38 #include "string/string.h"
39 #include "stream/stringstream.h"
40 #include "os/file.h"
41 #include "os/path.h"
42 #include "os/dir.h"
43 #include "gtkutil/filechooser.h"
44 #include "gtkutil/messagebox.h"
45 #include "cmdlib.h"
46
47 #include "error.h"
48 #include "console.h"
49 #include "xywindow.h"
50 #include "mainframe.h"
51 #include "qe3.h"
52 #include "gtkdlgs.h"
53
54
55 void Global_constructPreferences(PreferencesPage &page)
56 {
57     page.appendCheckBox("Console", "Enable Logging", g_Console_enableLogging);
58 }
59
60 void Interface_constructPreferences(PreferencesPage &page)
61 {
62 #if GDEF_OS_WINDOWS
63     page.appendCheckBox( "", "Default Text Editor", g_TextEditor_useWin32Editor );
64 #else
65     {
66         ui::CheckButton use_custom = page.appendCheckBox("Text Editor", "Custom", g_TextEditor_useCustomEditor);
67         ui::Widget custom_editor = page.appendPathEntry("Text Editor Command", g_TextEditor_editorCommand, true);
68         Widget_connectToggleDependency(custom_editor, use_custom);
69     }
70 #endif
71 }
72
73 void Mouse_constructPreferences(PreferencesPage &page)
74 {
75     {
76         const char *buttons[] = {"2 button", "3 button",};
77         page.appendRadio("Mouse Type", g_glwindow_globals.m_nMouseType, STRING_ARRAY_RANGE(buttons));
78     }
79     page.appendCheckBox("Right Button", "Activates Context Menu", g_xywindow_globals.m_bRightClick);
80 }
81
82 void Mouse_constructPage(PreferenceGroup &group)
83 {
84     PreferencesPage page(group.createPage("Mouse", "Mouse Preferences"));
85     Mouse_constructPreferences(page);
86 }
87
88 void Mouse_registerPreferencesPage()
89 {
90     PreferencesDialog_addInterfacePage(makeCallbackF(Mouse_constructPage));
91 }
92
93
94 /*!
95    =========================================================
96    Games selection dialog
97    =========================================================
98  */
99
100 #include <map>
101 #include <uilib/uilib.h>
102
103 inline const char *xmlAttr_getName(xmlAttrPtr attr)
104 {
105     return reinterpret_cast<const char *>( attr->name );
106 }
107
108 inline const char *xmlAttr_getValue(xmlAttrPtr attr)
109 {
110     return reinterpret_cast<const char *>( attr->children->content );
111 }
112
113 CGameDescription::CGameDescription(xmlDocPtr pDoc, const CopiedString &gameFile)
114 {
115     // read the user-friendly game name
116     xmlNodePtr pNode = pDoc->children;
117
118     while (strcmp((const char *) pNode->name, "game") && pNode != 0) {
119         pNode = pNode->next;
120     }
121     if (!pNode) {
122         Error("Didn't find 'game' node in the game description file '%s'\n", pDoc->URL);
123     }
124
125     for (xmlAttrPtr attr = pNode->properties; attr != 0; attr = attr->next) {
126         m_gameDescription.insert(GameDescription::value_type(xmlAttr_getName(attr), xmlAttr_getValue(attr)));
127     }
128
129     {
130         StringOutputStream path(256);
131         path << AppPath_get() << gameFile.c_str() << "/";
132         mGameToolsPath = path.c_str();
133     }
134
135     ASSERT_MESSAGE(file_exists(mGameToolsPath.c_str()),
136                    "game directory not found: " << makeQuoted(mGameToolsPath.c_str()));
137
138     mGameFile = gameFile;
139
140     {
141         GameDescription::iterator i = m_gameDescription.find("type");
142         if (i == m_gameDescription.end()) {
143             globalErrorStream() << "Warning, 'type' attribute not found in '"
144                                 << reinterpret_cast<const char *>( pDoc->URL ) << "'\n";
145             // default
146             mGameType = "q3";
147         } else {
148             mGameType = (*i).second.c_str();
149         }
150     }
151 }
152
153 void CGameDescription::Dump()
154 {
155     globalOutputStream() << "game description file: " << makeQuoted(mGameFile.c_str()) << "\n";
156     for (GameDescription::iterator i = m_gameDescription.begin(); i != m_gameDescription.end(); ++i) {
157         globalOutputStream() << (*i).first.c_str() << " = " << makeQuoted((*i).second.c_str()) << "\n";
158     }
159 }
160
161 CGameDescription *g_pGameDescription; ///< shortcut to g_GamesDialog.m_pCurrentDescription
162
163
164 #include "warnings.h"
165 #include "stream/textfilestream.h"
166 #include "container/array.h"
167 #include "xml/ixml.h"
168 #include "xml/xmlparser.h"
169 #include "xml/xmlwriter.h"
170
171 #include "preferencedictionary.h"
172 #include "stringio.h"
173
174 const char *const PREFERENCES_VERSION = "1.0";
175
176 bool Preferences_Load(PreferenceDictionary &preferences, const char *filename, const char *cmdline_prefix)
177 {
178     bool ret = false;
179     TextFileInputStream file(filename);
180     if (!file.failed()) {
181         XMLStreamParser parser(file);
182         XMLPreferenceDictionaryImporter importer(preferences, PREFERENCES_VERSION);
183         parser.exportXML(importer);
184         ret = true;
185     }
186
187     int l = strlen(cmdline_prefix);
188     for (int i = 1; i < g_argc - 1; ++i) {
189         if (g_argv[i][0] == '-') {
190             if (!strncmp(g_argv[i] + 1, cmdline_prefix, l)) {
191                 if (g_argv[i][l + 1] == '-') {
192                     preferences.importPref(g_argv[i] + l + 2, g_argv[i + 1]);
193                 }
194             }
195             ++i;
196         }
197     }
198
199     return ret;
200 }
201
202 bool Preferences_Save(PreferenceDictionary &preferences, const char *filename)
203 {
204     TextFileOutputStream file(filename);
205     if (!file.failed()) {
206         XMLStreamWriter writer(file);
207         XMLPreferenceDictionaryExporter exporter(preferences, PREFERENCES_VERSION);
208         exporter.exportXML(writer);
209         return true;
210     }
211     return false;
212 }
213
214 bool Preferences_Save_Safe(PreferenceDictionary &preferences, const char *filename)
215 {
216     Array<char> tmpName(filename, filename + strlen(filename) + 1 + 3);
217     *(tmpName.end() - 4) = 'T';
218     *(tmpName.end() - 3) = 'M';
219     *(tmpName.end() - 2) = 'P';
220     *(tmpName.end() - 1) = '\0';
221
222     return Preferences_Save(preferences, tmpName.data())
223            && (!file_exists(filename) || file_remove(filename))
224            && file_move(tmpName.data(), filename);
225 }
226
227
228 struct LogConsole {
229     static void Export(const Callback<void(bool)> &returnz)
230     {
231         returnz(g_Console_enableLogging);
232     }
233
234     static void Import(bool value)
235     {
236         g_Console_enableLogging = value;
237         Sys_LogFile(g_Console_enableLogging);
238     }
239 };
240
241
242 void RegisterGlobalPreferences(PreferenceSystem &preferences)
243 {
244     preferences.registerPreference("gamefile", make_property_string(g_GamesDialog.m_sGameFile));
245     preferences.registerPreference("gamePrompt", make_property_string(g_GamesDialog.m_bGamePrompt));
246     preferences.registerPreference("log console", make_property_string<LogConsole>());
247 }
248
249
250 PreferenceDictionary g_global_preferences;
251
252 void GlobalPreferences_Init()
253 {
254     RegisterGlobalPreferences(g_global_preferences);
255 }
256
257 void CGameDialog::LoadPrefs()
258 {
259     // load global .pref file
260     StringOutputStream strGlobalPref(256);
261     strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref";
262
263     globalOutputStream() << "loading global preferences from " << makeQuoted(strGlobalPref.c_str()) << "\n";
264
265     if (!Preferences_Load(g_global_preferences, strGlobalPref.c_str(), "global")) {
266         globalOutputStream() << "failed to load global preferences from " << strGlobalPref.c_str() << "\n";
267     }
268 }
269
270 void CGameDialog::SavePrefs()
271 {
272     StringOutputStream strGlobalPref(256);
273     strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref";
274
275     globalOutputStream() << "saving global preferences to " << strGlobalPref.c_str() << "\n";
276
277     if (!Preferences_Save_Safe(g_global_preferences, strGlobalPref.c_str())) {
278         globalOutputStream() << "failed to save global preferences to " << strGlobalPref.c_str() << "\n";
279     }
280 }
281
282 void CGameDialog::DoGameDialog()
283 {
284     // show the UI
285     DoModal();
286
287     // we save the prefs file
288     SavePrefs();
289 }
290
291 void CGameDialog::GameFileImport(int value)
292 {
293     m_nComboSelect = value;
294     // use value to set m_sGameFile
295     std::list<CGameDescription *>::iterator iGame = mGames.begin();
296     int i;
297     for (i = 0; i < value; i++) {
298         ++iGame;
299     }
300     m_sGameFile = (*iGame)->mGameFile;
301 }
302
303 void CGameDialog::GameFileExport(const Callback<void(int)> &importCallback) const
304 {
305     // use m_sGameFile to set value
306     std::list<CGameDescription *>::const_iterator iGame;
307     int i = 0;
308     for (iGame = mGames.begin(); iGame != mGames.end(); ++iGame) {
309         if ((*iGame)->mGameFile == m_sGameFile) {
310             m_nComboSelect = i;
311             break;
312         }
313         i++;
314     }
315     importCallback(m_nComboSelect);
316 }
317
318 struct CGameDialog_GameFile {
319     static void Export(const CGameDialog &self, const Callback<void(int)> &returnz)
320     {
321         self.GameFileExport(returnz);
322     }
323
324     static void Import(CGameDialog &self, int value)
325     {
326         self.GameFileImport(value);
327     }
328 };
329
330 void CGameDialog::CreateGlobalFrame(PreferencesPage &page)
331 {
332     std::vector<const char *> games;
333     games.reserve(mGames.size() + 1);
334     for (std::list<CGameDescription *>::iterator i = mGames.begin(); i != mGames.end(); ++i) {
335         games.push_back((*i)->getRequiredKeyValue("name"));
336     }
337     games.push_back(nullptr);
338     page.appendCombo(
339             "Select the game",
340             StringArrayRange(&(*games.begin()), &(*--games.end())),
341             make_property<CGameDialog_GameFile>(*this)
342     );
343     page.appendCheckBox("Startup", "Show Global Preferences", m_bGamePrompt);
344 }
345
346 ui::Window CGameDialog::BuildDialog()
347 {
348     auto frame = create_dialog_frame("Game settings", ui::Shadow::ETCHED_IN);
349
350     auto vbox2 = create_dialog_vbox(0, 4);
351     frame.add(vbox2);
352
353     {
354         PreferencesPage preferencesPage(*this, vbox2);
355         Global_constructPreferences(preferencesPage);
356         CreateGlobalFrame(preferencesPage);
357     }
358
359     return create_simple_modal_dialog_window("Global Preferences", m_modal, frame);
360 }
361
362 void CGameDialog::ScanForGames()
363 {
364     StringOutputStream strGamesPath(256);
365     strGamesPath << AppPath_get() << "games/";
366     const char *path = strGamesPath.c_str();
367
368     globalOutputStream() << "Scanning for game description files: " << path << '\n';
369
370     /*!
371        \todo FIXME LINUX:
372        do we put game description files below AppPath, or in ~/.radiant
373        i.e. read only or read/write?
374        my guess .. readonly cause it's an install
375        we will probably want to add ~/.radiant/<version>/games/ scanning on top of that for developers
376        (if that's really needed)
377      */
378
379     Directory_forEach(path, [&](const char *name) {
380         if (!extension_equal(path_get_extension(name), "game")) {
381             return;
382         }
383         StringOutputStream strPath(256);
384         strPath << path << name;
385         globalOutputStream() << strPath.c_str() << '\n';
386
387         xmlDocPtr pDoc = xmlParseFile(strPath.c_str());
388         if (pDoc) {
389             mGames.push_front(new CGameDescription(pDoc, name));
390             xmlFreeDoc(pDoc);
391         } else {
392             globalErrorStream() << "XML parser failed on '" << strPath.c_str() << "'\n";
393         }
394     });
395 }
396
397 CGameDescription *CGameDialog::GameDescriptionForComboItem()
398 {
399     std::list<CGameDescription *>::iterator iGame;
400     int i = 0;
401     for (iGame = mGames.begin(); iGame != mGames.end(); ++iGame, i++) {
402         if (i == m_nComboSelect) {
403             return (*iGame);
404         }
405     }
406     return 0; // not found
407 }
408
409 void CGameDialog::InitGlobalPrefPath()
410 {
411     g_Preferences.m_global_rc_path = g_string_new(SettingsPath_get());
412 }
413
414 void CGameDialog::Reset()
415 {
416     if (!g_Preferences.m_global_rc_path) {
417         InitGlobalPrefPath();
418     }
419     StringOutputStream strGlobalPref(256);
420     strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref";
421     file_remove(strGlobalPref.c_str());
422 }
423
424 void CGameDialog::Init()
425 {
426     InitGlobalPrefPath();
427     LoadPrefs();
428     ScanForGames();
429     if (mGames.empty()) {
430         Error("Didn't find any valid game file descriptions, aborting\n");
431     } else {
432         std::list<CGameDescription *>::iterator iGame, iPrevGame;
433         for (iGame = mGames.begin(), iPrevGame = mGames.end(); iGame != mGames.end(); iPrevGame = iGame, ++iGame) {
434             if (iPrevGame != mGames.end()) {
435                 if (strcmp((*iGame)->getRequiredKeyValue("name"), (*iPrevGame)->getRequiredKeyValue("name")) < 0) {
436                     CGameDescription *h = *iGame;
437                     *iGame = *iPrevGame;
438                     *iPrevGame = h;
439                 }
440             }
441         }
442     }
443
444     CGameDescription *currentGameDescription = 0;
445
446     if (!m_bGamePrompt) {
447         // search by .game name
448         std::list<CGameDescription *>::iterator iGame;
449         for (iGame = mGames.begin(); iGame != mGames.end(); ++iGame) {
450             if ((*iGame)->mGameFile == m_sGameFile) {
451                 currentGameDescription = (*iGame);
452                 break;
453             }
454         }
455     }
456     if (m_bGamePrompt || !currentGameDescription) {
457         Create();
458         DoGameDialog();
459         // use m_nComboSelect to identify the game to run as and set the globals
460         currentGameDescription = GameDescriptionForComboItem();
461         ASSERT_NOTNULL(currentGameDescription);
462     }
463     g_pGameDescription = currentGameDescription;
464
465     g_pGameDescription->Dump();
466 }
467
468 CGameDialog::~CGameDialog()
469 {
470     // free all the game descriptions
471     std::list<CGameDescription *>::iterator iGame;
472     for (iGame = mGames.begin(); iGame != mGames.end(); ++iGame) {
473         delete (*iGame);
474         *iGame = 0;
475     }
476     if (GetWidget()) {
477         Destroy();
478     }
479 }
480
481 inline const char *GameDescription_getIdentifier(const CGameDescription &gameDescription)
482 {
483     const char *identifier = gameDescription.getKeyValue("index");
484     if (string_empty(identifier)) {
485         identifier = "1";
486     }
487     return identifier;
488 }
489
490
491 CGameDialog g_GamesDialog;
492
493
494 // =============================================================================
495 // Widget callbacks for PrefsDlg
496
497 static void OnButtonClean(ui::Widget widget, gpointer data)
498 {
499     // make sure this is what the user wants
500     if (ui::alert(g_Preferences.GetWidget(), "This will close Radiant and clean the corresponding registry entries.\n"
501                           "Next time you start Radiant it will be good as new. Do you wish to continue?",
502                   "Reset Registry", ui::alert_type::YESNO, ui::alert_icon::Asterisk) == ui::alert_response::YES) {
503         PrefsDlg *dlg = (PrefsDlg *) data;
504         dlg->EndModal(eIDCANCEL);
505
506         g_preferences_globals.disable_ini = true;
507         Preferences_Reset();
508         gtk_main_quit();
509     }
510 }
511
512 // =============================================================================
513 // PrefsDlg class
514
515 /*
516    ========
517
518    very first prefs init deals with selecting the game and the game tools path
519    then we can load .ini stuff
520
521    using prefs / ini settings:
522    those are per-game
523
524    look in ~/.radiant/<version>/gamename
525    ========
526  */
527
528 const char *PREFS_LOCAL_FILENAME = "local.pref";
529
530 void PrefsDlg::Init()
531 {
532     // m_global_rc_path has been set above
533     // m_rc_path is for game specific preferences
534     // takes the form: global-pref-path/gamename/prefs-file
535
536     // this is common to win32 and Linux init now
537     m_rc_path = g_string_new(m_global_rc_path->str);
538
539     // game sub-dir
540     g_string_append(m_rc_path, g_pGameDescription->mGameFile.c_str());
541     g_string_append(m_rc_path, "/");
542     Q_mkdir(m_rc_path->str);
543
544     // then the ini file
545     m_inipath = g_string_new(m_rc_path->str);
546     g_string_append(m_inipath, PREFS_LOCAL_FILENAME);
547 }
548
549 void notebook_set_page(ui::Widget notebook, ui::Widget page)
550 {
551     int pagenum = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), page);
552     if (gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook)) != pagenum) {
553         gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), pagenum);
554     }
555 }
556
557 void PrefsDlg::showPrefPage(ui::Widget prefpage)
558 {
559     notebook_set_page(m_notebook, prefpage);
560     return;
561 }
562
563 static void treeSelection(ui::TreeSelection selection, gpointer data)
564 {
565     PrefsDlg *dlg = (PrefsDlg *) data;
566
567     GtkTreeModel *model;
568     GtkTreeIter selected;
569     if (gtk_tree_selection_get_selected(selection, &model, &selected)) {
570         ui::Widget prefpage{ui::null};
571         gtk_tree_model_get(model, &selected, 1, (gpointer *) &prefpage, -1);
572         dlg->showPrefPage(prefpage);
573     }
574 }
575
576 typedef std::list<PreferenceGroupCallback> PreferenceGroupCallbacks;
577
578 inline void PreferenceGroupCallbacks_constructGroup(const PreferenceGroupCallbacks &callbacks, PreferenceGroup &group)
579 {
580     for (PreferenceGroupCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i) {
581         (*i)(group);
582     }
583 }
584
585
586 inline void
587 PreferenceGroupCallbacks_pushBack(PreferenceGroupCallbacks &callbacks, const PreferenceGroupCallback &callback)
588 {
589     callbacks.push_back(callback);
590 }
591
592 typedef std::list<PreferencesPageCallback> PreferencesPageCallbacks;
593
594 inline void PreferencesPageCallbacks_constructPage(const PreferencesPageCallbacks &callbacks, PreferencesPage &page)
595 {
596     for (PreferencesPageCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i) {
597         (*i)(page);
598     }
599 }
600
601 inline void
602 PreferencesPageCallbacks_pushBack(PreferencesPageCallbacks &callbacks, const PreferencesPageCallback &callback)
603 {
604     callbacks.push_back(callback);
605 }
606
607 PreferencesPageCallbacks g_interfacePreferences;
608
609 void PreferencesDialog_addInterfacePreferences(const PreferencesPageCallback &callback)
610 {
611     PreferencesPageCallbacks_pushBack(g_interfacePreferences, callback);
612 }
613
614 PreferenceGroupCallbacks g_interfaceCallbacks;
615
616 void PreferencesDialog_addInterfacePage(const PreferenceGroupCallback &callback)
617 {
618     PreferenceGroupCallbacks_pushBack(g_interfaceCallbacks, callback);
619 }
620
621 PreferencesPageCallbacks g_displayPreferences;
622
623 void PreferencesDialog_addDisplayPreferences(const PreferencesPageCallback &callback)
624 {
625     PreferencesPageCallbacks_pushBack(g_displayPreferences, callback);
626 }
627
628 PreferenceGroupCallbacks g_displayCallbacks;
629
630 void PreferencesDialog_addDisplayPage(const PreferenceGroupCallback &callback)
631 {
632     PreferenceGroupCallbacks_pushBack(g_displayCallbacks, callback);
633 }
634
635 PreferencesPageCallbacks g_settingsPreferences;
636
637 void PreferencesDialog_addSettingsPreferences(const PreferencesPageCallback &callback)
638 {
639     PreferencesPageCallbacks_pushBack(g_settingsPreferences, callback);
640 }
641
642 PreferenceGroupCallbacks g_settingsCallbacks;
643
644 void PreferencesDialog_addSettingsPage(const PreferenceGroupCallback &callback)
645 {
646     PreferenceGroupCallbacks_pushBack(g_settingsCallbacks, callback);
647 }
648
649 void Widget_updateDependency(ui::Widget self, ui::Widget toggleButton)
650 {
651     gtk_widget_set_sensitive(self, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggleButton)) &&
652                                    gtk_widget_is_sensitive(toggleButton));
653 }
654
655 void ToggleButton_toggled_Widget_updateDependency(ui::Widget toggleButton, ui::Widget self)
656 {
657     Widget_updateDependency(self, toggleButton);
658 }
659
660 void ToggleButton_state_changed_Widget_updateDependency(ui::Widget toggleButton, GtkStateType state, ui::Widget self)
661 {
662     if (state == GTK_STATE_INSENSITIVE) {
663         Widget_updateDependency(self, toggleButton);
664     }
665 }
666
667 void Widget_connectToggleDependency(ui::Widget self, ui::Widget toggleButton)
668 {
669     toggleButton.connect("state_changed", G_CALLBACK(ToggleButton_state_changed_Widget_updateDependency), self);
670     toggleButton.connect("toggled", G_CALLBACK(ToggleButton_toggled_Widget_updateDependency), self);
671     Widget_updateDependency(self, toggleButton);
672 }
673
674
675 inline ui::VBox getVBox(ui::Bin page)
676 {
677     return ui::VBox::from(gtk_bin_get_child(page));
678 }
679
680 GtkTreeIter PreferenceTree_appendPage(ui::TreeStore store, GtkTreeIter *parent, const char *name, ui::Widget page)
681 {
682     GtkTreeIter group;
683     gtk_tree_store_append(store, &group, parent);
684     gtk_tree_store_set(store, &group, 0, name, 1, page, -1);
685     return group;
686 }
687
688 ui::Bin PreferencePages_addPage(ui::Widget notebook, const char *name)
689 {
690     ui::Widget preflabel = ui::Label(name);
691     preflabel.show();
692
693     auto pageframe = ui::Frame(name);
694     gtk_container_set_border_width(GTK_CONTAINER(pageframe), 4);
695     pageframe.show();
696
697     ui::Widget vbox = ui::VBox(FALSE, 4);
698     vbox.show();
699     gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
700     pageframe.add(vbox);
701
702     // Add the page to the notebook
703     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), pageframe, preflabel);
704
705     return pageframe;
706 }
707
708 class PreferenceTreeGroup : public PreferenceGroup {
709     Dialog &m_dialog;
710     ui::Widget m_notebook;
711     ui::TreeStore m_store;
712     GtkTreeIter m_group;
713 public:
714     PreferenceTreeGroup(Dialog &dialog, ui::Widget notebook, ui::TreeStore store, GtkTreeIter group) :
715             m_dialog(dialog),
716             m_notebook(notebook),
717             m_store(store),
718             m_group(group)
719     {
720     }
721
722     PreferencesPage createPage(const char *treeName, const char *frameName)
723     {
724         auto page = PreferencePages_addPage(m_notebook, frameName);
725         PreferenceTree_appendPage(m_store, &m_group, treeName, page);
726         return PreferencesPage(m_dialog, getVBox(page));
727     }
728 };
729
730 ui::Window PrefsDlg::BuildDialog()
731 {
732     PreferencesDialog_addInterfacePreferences(makeCallbackF(Interface_constructPreferences));
733     Mouse_registerPreferencesPage();
734
735     ui::Window dialog = ui::Window(create_floating_window("NetRadiant Preferences", m_parent));
736
737     {
738         auto mainvbox = ui::VBox(FALSE, 5);
739         dialog.add(mainvbox);
740         gtk_container_set_border_width(GTK_CONTAINER(mainvbox), 5);
741         mainvbox.show();
742
743         {
744             auto hbox = ui::HBox(FALSE, 5);
745             hbox.show();
746             mainvbox.pack_end(hbox, FALSE, TRUE, 0);
747
748             {
749                 auto button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &m_modal);
750                 hbox.pack_end(button, FALSE, FALSE, 0);
751             }
752             {
753                 auto button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &m_modal);
754                 hbox.pack_end(button, FALSE, FALSE, 0);
755             }
756             {
757                 auto button = create_dialog_button("Clean", G_CALLBACK(OnButtonClean), this);
758                 hbox.pack_end(button, FALSE, FALSE, 0);
759             }
760         }
761
762         {
763             auto hbox = ui::HBox(FALSE, 5);
764             mainvbox.pack_start(hbox, TRUE, TRUE, 0);
765             hbox.show();
766
767             {
768                 auto sc_win = ui::ScrolledWindow(ui::New);
769                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sc_win), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
770                 hbox.pack_start(sc_win, FALSE, FALSE, 0);
771                 sc_win.show();
772                 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sc_win), GTK_SHADOW_IN);
773
774                 // prefs pages notebook
775                 m_notebook = ui::Widget::from(gtk_notebook_new());
776                 // hide the notebook tabs since its not supposed to look like a notebook
777                 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(m_notebook), FALSE);
778                 hbox.pack_start(m_notebook, TRUE, TRUE, 0);
779                 m_notebook.show();
780
781
782                 {
783                     auto store = ui::TreeStore::from(gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_POINTER));
784
785                     auto view = ui::TreeView(ui::TreeModel::from(store._handle));
786                     gtk_tree_view_set_headers_visible(view, FALSE);
787
788                     {
789                         auto renderer = ui::CellRendererText(ui::New);
790                         auto column = ui::TreeViewColumn("Preferences", renderer, {{"text", 0}});
791                         gtk_tree_view_append_column(view, column);
792                     }
793
794                     {
795                         auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(view));
796                         selection.connect("changed", G_CALLBACK(treeSelection), this);
797                     }
798
799                     view.show();
800
801                     sc_win.add(view);
802
803                     {
804                         /********************************************************************/
805                         /* Add preference tree options                                      */
806                         /********************************************************************/
807                         // Front page...
808                         //GtkWidget* front =
809                         PreferencePages_addPage(m_notebook, "Front Page");
810
811                         {
812                             auto global = PreferencePages_addPage(m_notebook, "Global Preferences");
813                             {
814                                 PreferencesPage preferencesPage(*this, getVBox(global));
815                                 Global_constructPreferences(preferencesPage);
816                             }
817                             auto group = PreferenceTree_appendPage(store, 0, "Global", global);
818                             {
819                                 auto game = PreferencePages_addPage(m_notebook, "Game");
820                                 PreferencesPage preferencesPage(*this, getVBox(game));
821                                 g_GamesDialog.CreateGlobalFrame(preferencesPage);
822
823                                 PreferenceTree_appendPage(store, &group, "Game", game);
824                             }
825                         }
826
827                         {
828                             auto interfacePage = PreferencePages_addPage(m_notebook, "Interface Preferences");
829                             {
830                                 PreferencesPage preferencesPage(*this, getVBox(interfacePage));
831                                 PreferencesPageCallbacks_constructPage(g_interfacePreferences, preferencesPage);
832                             }
833
834                             auto group = PreferenceTree_appendPage(store, 0, "Interface", interfacePage);
835                             PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group);
836
837                             PreferenceGroupCallbacks_constructGroup(g_interfaceCallbacks, preferenceGroup);
838                         }
839
840                         {
841                             auto display = PreferencePages_addPage(m_notebook, "Display Preferences");
842                             {
843                                 PreferencesPage preferencesPage(*this, getVBox(display));
844                                 PreferencesPageCallbacks_constructPage(g_displayPreferences, preferencesPage);
845                             }
846                             auto group = PreferenceTree_appendPage(store, 0, "Display", display);
847                             PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group);
848
849                             PreferenceGroupCallbacks_constructGroup(g_displayCallbacks, preferenceGroup);
850                         }
851
852                         {
853                             auto settings = PreferencePages_addPage(m_notebook, "General Settings");
854                             {
855                                 PreferencesPage preferencesPage(*this, getVBox(settings));
856                                 PreferencesPageCallbacks_constructPage(g_settingsPreferences, preferencesPage);
857                             }
858
859                             auto group = PreferenceTree_appendPage(store, 0, "Settings", settings);
860                             PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group);
861
862                             PreferenceGroupCallbacks_constructGroup(g_settingsCallbacks, preferenceGroup);
863                         }
864                     }
865
866                     gtk_tree_view_expand_all(view);
867
868                     g_object_unref(G_OBJECT(store));
869                 }
870             }
871         }
872     }
873
874     gtk_notebook_set_current_page(GTK_NOTEBOOK(m_notebook), 0);
875
876     return dialog;
877 }
878
879 preferences_globals_t g_preferences_globals;
880
881 PrefsDlg g_Preferences;               // global prefs instance
882
883
884 void PreferencesDialog_constructWindow(ui::Window main_window)
885 {
886     g_Preferences.m_parent = main_window;
887     g_Preferences.Create();
888 }
889
890 void PreferencesDialog_destroyWindow()
891 {
892     g_Preferences.Destroy();
893 }
894
895
896 PreferenceDictionary g_preferences;
897
898 PreferenceSystem &GetPreferenceSystem()
899 {
900     return g_preferences;
901 }
902
903 class PreferenceSystemAPI {
904     PreferenceSystem *m_preferencesystem;
905 public:
906     typedef PreferenceSystem Type;
907
908     STRING_CONSTANT(Name, "*");
909
910     PreferenceSystemAPI()
911     {
912         m_preferencesystem = &GetPreferenceSystem();
913     }
914
915     PreferenceSystem *getTable()
916     {
917         return m_preferencesystem;
918     }
919 };
920
921 #include "modulesystem/singletonmodule.h"
922 #include "modulesystem/moduleregistry.h"
923
924 typedef SingletonModule<PreferenceSystemAPI> PreferenceSystemModule;
925 typedef Static<PreferenceSystemModule> StaticPreferenceSystemModule;
926 StaticRegisterModule staticRegisterPreferenceSystem(StaticPreferenceSystemModule::instance());
927
928 void Preferences_Load()
929 {
930     g_GamesDialog.LoadPrefs();
931
932     globalOutputStream() << "loading local preferences from " << g_Preferences.m_inipath->str << "\n";
933
934     if (!Preferences_Load(g_preferences, g_Preferences.m_inipath->str, g_GamesDialog.m_sGameFile.c_str())) {
935         globalOutputStream() << "failed to load local preferences from " << g_Preferences.m_inipath->str << "\n";
936     }
937 }
938
939 void Preferences_Save()
940 {
941     if (g_preferences_globals.disable_ini) {
942         return;
943     }
944
945     g_GamesDialog.SavePrefs();
946
947     globalOutputStream() << "saving local preferences to " << g_Preferences.m_inipath->str << "\n";
948
949     if (!Preferences_Save_Safe(g_preferences, g_Preferences.m_inipath->str)) {
950         globalOutputStream() << "failed to save local preferences to " << g_Preferences.m_inipath->str << "\n";
951     }
952 }
953
954 void Preferences_Reset()
955 {
956     file_remove(g_Preferences.m_inipath->str);
957 }
958
959
960 void PrefsDlg::PostModal(EMessageBoxReturn code)
961 {
962     if (code == eIDOK) {
963         Preferences_Save();
964         UpdateAllWindows();
965     }
966 }
967
968 std::vector<const char *> g_restart_required;
969
970 void PreferencesDialog_restartRequired(const char *staticName)
971 {
972     g_restart_required.push_back(staticName);
973 }
974
975 void PreferencesDialog_showDialog()
976 {
977     if (ConfirmModified("Edit Preferences") && g_Preferences.DoModal() == eIDOK) {
978         if (!g_restart_required.empty()) {
979             StringOutputStream message(256);
980             message << "Preference changes require a restart:\n";
981             for (std::vector<const char *>::iterator i = g_restart_required.begin();
982                  i != g_restart_required.end(); ++i) {
983                 message << (*i) << '\n';
984             }
985             ui::alert(MainFrame_getWindow(), message.c_str());
986             g_restart_required.clear();
987         }
988     }
989 }
990
991 struct GameName {
992     static void Export(const Callback<void(const char *)> &returnz)
993     {
994         returnz(gamename_get());
995     }
996
997     static void Import(const char *value)
998     {
999         gamename_set(value);
1000     }
1001 };
1002
1003 struct GameMode {
1004     static void Export(const Callback<void(const char *)> &returnz)
1005     {
1006         returnz(gamemode_get());
1007     }
1008
1009     static void Import(const char *value)
1010     {
1011         gamemode_set(value);
1012     }
1013 };
1014
1015 void RegisterPreferences(PreferenceSystem &preferences)
1016 {
1017 #if GDEF_OS_WINDOWS
1018     preferences.registerPreference( "UseCustomShaderEditor", make_property_string( g_TextEditor_useWin32Editor ) );
1019 #else
1020     preferences.registerPreference("UseCustomShaderEditor", make_property_string(g_TextEditor_useCustomEditor));
1021     preferences.registerPreference("CustomShaderEditorCommand", make_property_string(g_TextEditor_editorCommand));
1022 #endif
1023
1024     preferences.registerPreference("GameName", make_property<GameName>());
1025     preferences.registerPreference("GameMode", make_property<GameMode>());
1026 }
1027
1028 void Preferences_Init()
1029 {
1030     RegisterPreferences(GetPreferenceSystem());
1031 }