]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/preferences.cpp
Initial python support
[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());
334     for (std::list<CGameDescription *>::iterator i = mGames.begin(); i != mGames.end(); ++i) {
335         games.push_back((*i)->getRequiredKeyValue("name"));
336     }
337     page.appendCombo(
338             "Select the game",
339             StringArrayRange(&(*games.begin()), &(*games.end())),
340             make_property<CGameDialog_GameFile>(*this)
341     );
342     page.appendCheckBox("Startup", "Show Global Preferences", m_bGamePrompt);
343 }
344
345 ui::Window CGameDialog::BuildDialog()
346 {
347     auto frame = create_dialog_frame("Game settings", ui::Shadow::ETCHED_IN);
348
349     auto vbox2 = create_dialog_vbox(0, 4);
350     frame.add(vbox2);
351
352     {
353         PreferencesPage preferencesPage(*this, vbox2);
354         Global_constructPreferences(preferencesPage);
355         CreateGlobalFrame(preferencesPage);
356     }
357
358     return create_simple_modal_dialog_window("Global Preferences", m_modal, frame);
359 }
360
361 void CGameDialog::ScanForGames()
362 {
363     StringOutputStream strGamesPath(256);
364     strGamesPath << AppPath_get() << "games/";
365     const char *path = strGamesPath.c_str();
366
367     globalOutputStream() << "Scanning for game description files: " << path << '\n';
368
369     /*!
370        \todo FIXME LINUX:
371        do we put game description files below AppPath, or in ~/.radiant
372        i.e. read only or read/write?
373        my guess .. readonly cause it's an install
374        we will probably want to add ~/.radiant/<version>/games/ scanning on top of that for developers
375        (if that's really needed)
376      */
377
378     Directory_forEach(path, [&](const char *name) {
379         if (!extension_equal(path_get_extension(name), "game")) {
380             return;
381         }
382         StringOutputStream strPath(256);
383         strPath << path << name;
384         globalOutputStream() << strPath.c_str() << '\n';
385
386         xmlDocPtr pDoc = xmlParseFile(strPath.c_str());
387         if (pDoc) {
388             mGames.push_front(new CGameDescription(pDoc, name));
389             xmlFreeDoc(pDoc);
390         } else {
391             globalErrorStream() << "XML parser failed on '" << strPath.c_str() << "'\n";
392         }
393     });
394 }
395
396 CGameDescription *CGameDialog::GameDescriptionForComboItem()
397 {
398     std::list<CGameDescription *>::iterator iGame;
399     int i = 0;
400     for (iGame = mGames.begin(); iGame != mGames.end(); ++iGame, i++) {
401         if (i == m_nComboSelect) {
402             return (*iGame);
403         }
404     }
405     return 0; // not found
406 }
407
408 void CGameDialog::InitGlobalPrefPath()
409 {
410     g_Preferences.m_global_rc_path = g_string_new(SettingsPath_get());
411 }
412
413 void CGameDialog::Reset()
414 {
415     if (!g_Preferences.m_global_rc_path) {
416         InitGlobalPrefPath();
417     }
418     StringOutputStream strGlobalPref(256);
419     strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref";
420     file_remove(strGlobalPref.c_str());
421 }
422
423 void CGameDialog::Init()
424 {
425     InitGlobalPrefPath();
426     LoadPrefs();
427     ScanForGames();
428     if (mGames.empty()) {
429         Error("Didn't find any valid game file descriptions, aborting\n");
430     } else {
431         std::list<CGameDescription *>::iterator iGame, iPrevGame;
432         for (iGame = mGames.begin(), iPrevGame = mGames.end(); iGame != mGames.end(); iPrevGame = iGame, ++iGame) {
433             if (iPrevGame != mGames.end()) {
434                 if (strcmp((*iGame)->getRequiredKeyValue("name"), (*iPrevGame)->getRequiredKeyValue("name")) < 0) {
435                     CGameDescription *h = *iGame;
436                     *iGame = *iPrevGame;
437                     *iPrevGame = h;
438                 }
439             }
440         }
441     }
442
443     CGameDescription *currentGameDescription = 0;
444
445     if (!m_bGamePrompt) {
446         // search by .game name
447         std::list<CGameDescription *>::iterator iGame;
448         for (iGame = mGames.begin(); iGame != mGames.end(); ++iGame) {
449             if ((*iGame)->mGameFile == m_sGameFile) {
450                 currentGameDescription = (*iGame);
451                 break;
452             }
453         }
454     }
455     if (m_bGamePrompt || !currentGameDescription) {
456         Create();
457         DoGameDialog();
458         // use m_nComboSelect to identify the game to run as and set the globals
459         currentGameDescription = GameDescriptionForComboItem();
460         ASSERT_NOTNULL(currentGameDescription);
461     }
462     g_pGameDescription = currentGameDescription;
463
464     g_pGameDescription->Dump();
465 }
466
467 CGameDialog::~CGameDialog()
468 {
469     // free all the game descriptions
470     std::list<CGameDescription *>::iterator iGame;
471     for (iGame = mGames.begin(); iGame != mGames.end(); ++iGame) {
472         delete (*iGame);
473         *iGame = 0;
474     }
475     if (GetWidget()) {
476         Destroy();
477     }
478 }
479
480 inline const char *GameDescription_getIdentifier(const CGameDescription &gameDescription)
481 {
482     const char *identifier = gameDescription.getKeyValue("index");
483     if (string_empty(identifier)) {
484         identifier = "1";
485     }
486     return identifier;
487 }
488
489
490 CGameDialog g_GamesDialog;
491
492
493 // =============================================================================
494 // Widget callbacks for PrefsDlg
495
496 static void OnButtonClean(ui::Widget widget, gpointer data)
497 {
498     // make sure this is what the user wants
499     if (ui::alert(g_Preferences.GetWidget(), "This will close Radiant and clean the corresponding registry entries.\n"
500                           "Next time you start Radiant it will be good as new. Do you wish to continue?",
501                   "Reset Registry", ui::alert_type::YESNO, ui::alert_icon::Asterisk) == ui::alert_response::YES) {
502         PrefsDlg *dlg = (PrefsDlg *) data;
503         dlg->EndModal(eIDCANCEL);
504
505         g_preferences_globals.disable_ini = true;
506         Preferences_Reset();
507         gtk_main_quit();
508     }
509 }
510
511 // =============================================================================
512 // PrefsDlg class
513
514 /*
515    ========
516
517    very first prefs init deals with selecting the game and the game tools path
518    then we can load .ini stuff
519
520    using prefs / ini settings:
521    those are per-game
522
523    look in ~/.radiant/<version>/gamename
524    ========
525  */
526
527 const char *PREFS_LOCAL_FILENAME = "local.pref";
528
529 void PrefsDlg::Init()
530 {
531     // m_global_rc_path has been set above
532     // m_rc_path is for game specific preferences
533     // takes the form: global-pref-path/gamename/prefs-file
534
535     // this is common to win32 and Linux init now
536     m_rc_path = g_string_new(m_global_rc_path->str);
537
538     // game sub-dir
539     g_string_append(m_rc_path, g_pGameDescription->mGameFile.c_str());
540     g_string_append(m_rc_path, "/");
541     Q_mkdir(m_rc_path->str);
542
543     // then the ini file
544     m_inipath = g_string_new(m_rc_path->str);
545     g_string_append(m_inipath, PREFS_LOCAL_FILENAME);
546 }
547
548 void notebook_set_page(ui::Widget notebook, ui::Widget page)
549 {
550     int pagenum = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), page);
551     if (gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook)) != pagenum) {
552         gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), pagenum);
553     }
554 }
555
556 void PrefsDlg::showPrefPage(ui::Widget prefpage)
557 {
558     notebook_set_page(m_notebook, prefpage);
559     return;
560 }
561
562 static void treeSelection(ui::TreeSelection selection, gpointer data)
563 {
564     PrefsDlg *dlg = (PrefsDlg *) data;
565
566     GtkTreeModel *model;
567     GtkTreeIter selected;
568     if (gtk_tree_selection_get_selected(selection, &model, &selected)) {
569         ui::Widget prefpage{ui::null};
570         gtk_tree_model_get(model, &selected, 1, (gpointer *) &prefpage, -1);
571         dlg->showPrefPage(prefpage);
572     }
573 }
574
575 typedef std::list<PreferenceGroupCallback> PreferenceGroupCallbacks;
576
577 inline void PreferenceGroupCallbacks_constructGroup(const PreferenceGroupCallbacks &callbacks, PreferenceGroup &group)
578 {
579     for (PreferenceGroupCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i) {
580         (*i)(group);
581     }
582 }
583
584
585 inline void
586 PreferenceGroupCallbacks_pushBack(PreferenceGroupCallbacks &callbacks, const PreferenceGroupCallback &callback)
587 {
588     callbacks.push_back(callback);
589 }
590
591 typedef std::list<PreferencesPageCallback> PreferencesPageCallbacks;
592
593 inline void PreferencesPageCallbacks_constructPage(const PreferencesPageCallbacks &callbacks, PreferencesPage &page)
594 {
595     for (PreferencesPageCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i) {
596         (*i)(page);
597     }
598 }
599
600 inline void
601 PreferencesPageCallbacks_pushBack(PreferencesPageCallbacks &callbacks, const PreferencesPageCallback &callback)
602 {
603     callbacks.push_back(callback);
604 }
605
606 PreferencesPageCallbacks g_interfacePreferences;
607
608 void PreferencesDialog_addInterfacePreferences(const PreferencesPageCallback &callback)
609 {
610     PreferencesPageCallbacks_pushBack(g_interfacePreferences, callback);
611 }
612
613 PreferenceGroupCallbacks g_interfaceCallbacks;
614
615 void PreferencesDialog_addInterfacePage(const PreferenceGroupCallback &callback)
616 {
617     PreferenceGroupCallbacks_pushBack(g_interfaceCallbacks, callback);
618 }
619
620 PreferencesPageCallbacks g_displayPreferences;
621
622 void PreferencesDialog_addDisplayPreferences(const PreferencesPageCallback &callback)
623 {
624     PreferencesPageCallbacks_pushBack(g_displayPreferences, callback);
625 }
626
627 PreferenceGroupCallbacks g_displayCallbacks;
628
629 void PreferencesDialog_addDisplayPage(const PreferenceGroupCallback &callback)
630 {
631     PreferenceGroupCallbacks_pushBack(g_displayCallbacks, callback);
632 }
633
634 PreferencesPageCallbacks g_settingsPreferences;
635
636 void PreferencesDialog_addSettingsPreferences(const PreferencesPageCallback &callback)
637 {
638     PreferencesPageCallbacks_pushBack(g_settingsPreferences, callback);
639 }
640
641 PreferenceGroupCallbacks g_settingsCallbacks;
642
643 void PreferencesDialog_addSettingsPage(const PreferenceGroupCallback &callback)
644 {
645     PreferenceGroupCallbacks_pushBack(g_settingsCallbacks, callback);
646 }
647
648 void Widget_updateDependency(ui::Widget self, ui::Widget toggleButton)
649 {
650     gtk_widget_set_sensitive(self, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggleButton)) &&
651                                    gtk_widget_is_sensitive(toggleButton));
652 }
653
654 void ToggleButton_toggled_Widget_updateDependency(ui::Widget toggleButton, ui::Widget self)
655 {
656     Widget_updateDependency(self, toggleButton);
657 }
658
659 void ToggleButton_state_changed_Widget_updateDependency(ui::Widget toggleButton, GtkStateType state, ui::Widget self)
660 {
661     if (state == GTK_STATE_INSENSITIVE) {
662         Widget_updateDependency(self, toggleButton);
663     }
664 }
665
666 void Widget_connectToggleDependency(ui::Widget self, ui::Widget toggleButton)
667 {
668     toggleButton.connect("state_changed", G_CALLBACK(ToggleButton_state_changed_Widget_updateDependency), self);
669     toggleButton.connect("toggled", G_CALLBACK(ToggleButton_toggled_Widget_updateDependency), self);
670     Widget_updateDependency(self, toggleButton);
671 }
672
673
674 inline ui::VBox getVBox(ui::Bin page)
675 {
676     return ui::VBox::from(gtk_bin_get_child(page));
677 }
678
679 GtkTreeIter PreferenceTree_appendPage(ui::TreeStore store, GtkTreeIter *parent, const char *name, ui::Widget page)
680 {
681     GtkTreeIter group;
682     gtk_tree_store_append(store, &group, parent);
683     gtk_tree_store_set(store, &group, 0, name, 1, page, -1);
684     return group;
685 }
686
687 ui::Bin PreferencePages_addPage(ui::Widget notebook, const char *name)
688 {
689     ui::Widget preflabel = ui::Label(name);
690     preflabel.show();
691
692     auto pageframe = ui::Frame(name);
693     gtk_container_set_border_width(GTK_CONTAINER(pageframe), 4);
694     pageframe.show();
695
696     ui::Widget vbox = ui::VBox(FALSE, 4);
697     vbox.show();
698     gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
699     pageframe.add(vbox);
700
701     // Add the page to the notebook
702     gtk_notebook_append_page(GTK_NOTEBOOK(notebook), pageframe, preflabel);
703
704     return pageframe;
705 }
706
707 class PreferenceTreeGroup : public PreferenceGroup {
708     Dialog &m_dialog;
709     ui::Widget m_notebook;
710     ui::TreeStore m_store;
711     GtkTreeIter m_group;
712 public:
713     PreferenceTreeGroup(Dialog &dialog, ui::Widget notebook, ui::TreeStore store, GtkTreeIter group) :
714             m_dialog(dialog),
715             m_notebook(notebook),
716             m_store(store),
717             m_group(group)
718     {
719     }
720
721     PreferencesPage createPage(const char *treeName, const char *frameName)
722     {
723         auto page = PreferencePages_addPage(m_notebook, frameName);
724         PreferenceTree_appendPage(m_store, &m_group, treeName, page);
725         return PreferencesPage(m_dialog, getVBox(page));
726     }
727 };
728
729 ui::Window PrefsDlg::BuildDialog()
730 {
731     PreferencesDialog_addInterfacePreferences(makeCallbackF(Interface_constructPreferences));
732     Mouse_registerPreferencesPage();
733
734     ui::Window dialog = ui::Window(create_floating_window("NetRadiant Preferences", m_parent));
735
736     {
737         auto mainvbox = ui::VBox(FALSE, 5);
738         dialog.add(mainvbox);
739         gtk_container_set_border_width(GTK_CONTAINER(mainvbox), 5);
740         mainvbox.show();
741
742         {
743             auto hbox = ui::HBox(FALSE, 5);
744             hbox.show();
745             mainvbox.pack_end(hbox, FALSE, TRUE, 0);
746
747             {
748                 auto button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &m_modal);
749                 hbox.pack_end(button, FALSE, FALSE, 0);
750             }
751             {
752                 auto button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &m_modal);
753                 hbox.pack_end(button, FALSE, FALSE, 0);
754             }
755             {
756                 auto button = create_dialog_button("Clean", G_CALLBACK(OnButtonClean), this);
757                 hbox.pack_end(button, FALSE, FALSE, 0);
758             }
759         }
760
761         {
762             auto hbox = ui::HBox(FALSE, 5);
763             mainvbox.pack_start(hbox, TRUE, TRUE, 0);
764             hbox.show();
765
766             {
767                 auto sc_win = ui::ScrolledWindow(ui::New);
768                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sc_win), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
769                 hbox.pack_start(sc_win, FALSE, FALSE, 0);
770                 sc_win.show();
771                 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sc_win), GTK_SHADOW_IN);
772
773                 // prefs pages notebook
774                 m_notebook = ui::Widget::from(gtk_notebook_new());
775                 // hide the notebook tabs since its not supposed to look like a notebook
776                 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(m_notebook), FALSE);
777                 hbox.pack_start(m_notebook, TRUE, TRUE, 0);
778                 m_notebook.show();
779
780
781                 {
782                     auto store = ui::TreeStore::from(gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_POINTER));
783
784                     auto view = ui::TreeView(ui::TreeModel::from(store._handle));
785                     gtk_tree_view_set_headers_visible(view, FALSE);
786
787                     {
788                         auto renderer = ui::CellRendererText(ui::New);
789                         auto column = ui::TreeViewColumn("Preferences", renderer, {{"text", 0}});
790                         gtk_tree_view_append_column(view, column);
791                     }
792
793                     {
794                         auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(view));
795                         selection.connect("changed", G_CALLBACK(treeSelection), this);
796                     }
797
798                     view.show();
799
800                     sc_win.add(view);
801
802                     {
803                         /********************************************************************/
804                         /* Add preference tree options                                      */
805                         /********************************************************************/
806                         // Front page...
807                         //GtkWidget* front =
808                         PreferencePages_addPage(m_notebook, "Front Page");
809
810                         {
811                             auto global = PreferencePages_addPage(m_notebook, "Global Preferences");
812                             {
813                                 PreferencesPage preferencesPage(*this, getVBox(global));
814                                 Global_constructPreferences(preferencesPage);
815                             }
816                             auto group = PreferenceTree_appendPage(store, 0, "Global", global);
817                             {
818                                 auto game = PreferencePages_addPage(m_notebook, "Game");
819                                 PreferencesPage preferencesPage(*this, getVBox(game));
820                                 g_GamesDialog.CreateGlobalFrame(preferencesPage);
821
822                                 PreferenceTree_appendPage(store, &group, "Game", game);
823                             }
824                         }
825
826                         {
827                             auto interfacePage = PreferencePages_addPage(m_notebook, "Interface Preferences");
828                             {
829                                 PreferencesPage preferencesPage(*this, getVBox(interfacePage));
830                                 PreferencesPageCallbacks_constructPage(g_interfacePreferences, preferencesPage);
831                             }
832
833                             auto group = PreferenceTree_appendPage(store, 0, "Interface", interfacePage);
834                             PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group);
835
836                             PreferenceGroupCallbacks_constructGroup(g_interfaceCallbacks, preferenceGroup);
837                         }
838
839                         {
840                             auto display = PreferencePages_addPage(m_notebook, "Display Preferences");
841                             {
842                                 PreferencesPage preferencesPage(*this, getVBox(display));
843                                 PreferencesPageCallbacks_constructPage(g_displayPreferences, preferencesPage);
844                             }
845                             auto group = PreferenceTree_appendPage(store, 0, "Display", display);
846                             PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group);
847
848                             PreferenceGroupCallbacks_constructGroup(g_displayCallbacks, preferenceGroup);
849                         }
850
851                         {
852                             auto settings = PreferencePages_addPage(m_notebook, "General Settings");
853                             {
854                                 PreferencesPage preferencesPage(*this, getVBox(settings));
855                                 PreferencesPageCallbacks_constructPage(g_settingsPreferences, preferencesPage);
856                             }
857
858                             auto group = PreferenceTree_appendPage(store, 0, "Settings", settings);
859                             PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group);
860
861                             PreferenceGroupCallbacks_constructGroup(g_settingsCallbacks, preferenceGroup);
862                         }
863                     }
864
865                     gtk_tree_view_expand_all(view);
866
867                     g_object_unref(G_OBJECT(store));
868                 }
869             }
870         }
871     }
872
873     gtk_notebook_set_current_page(GTK_NOTEBOOK(m_notebook), 0);
874
875     return dialog;
876 }
877
878 preferences_globals_t g_preferences_globals;
879
880 PrefsDlg g_Preferences;               // global prefs instance
881
882
883 void PreferencesDialog_constructWindow(ui::Window main_window)
884 {
885     g_Preferences.m_parent = main_window;
886     g_Preferences.Create();
887 }
888
889 void PreferencesDialog_destroyWindow()
890 {
891     g_Preferences.Destroy();
892 }
893
894
895 PreferenceDictionary g_preferences;
896
897 PreferenceSystem &GetPreferenceSystem()
898 {
899     return g_preferences;
900 }
901
902 class PreferenceSystemAPI {
903     PreferenceSystem *m_preferencesystem;
904 public:
905     typedef PreferenceSystem Type;
906
907     STRING_CONSTANT(Name, "*");
908
909     PreferenceSystemAPI()
910     {
911         m_preferencesystem = &GetPreferenceSystem();
912     }
913
914     PreferenceSystem *getTable()
915     {
916         return m_preferencesystem;
917     }
918 };
919
920 #include "modulesystem/singletonmodule.h"
921 #include "modulesystem/moduleregistry.h"
922
923 typedef SingletonModule<PreferenceSystemAPI> PreferenceSystemModule;
924 typedef Static<PreferenceSystemModule> StaticPreferenceSystemModule;
925 StaticRegisterModule staticRegisterPreferenceSystem(StaticPreferenceSystemModule::instance());
926
927 void Preferences_Load()
928 {
929     g_GamesDialog.LoadPrefs();
930
931     globalOutputStream() << "loading local preferences from " << g_Preferences.m_inipath->str << "\n";
932
933     if (!Preferences_Load(g_preferences, g_Preferences.m_inipath->str, g_GamesDialog.m_sGameFile.c_str())) {
934         globalOutputStream() << "failed to load local preferences from " << g_Preferences.m_inipath->str << "\n";
935     }
936 }
937
938 void Preferences_Save()
939 {
940     if (g_preferences_globals.disable_ini) {
941         return;
942     }
943
944     g_GamesDialog.SavePrefs();
945
946     globalOutputStream() << "saving local preferences to " << g_Preferences.m_inipath->str << "\n";
947
948     if (!Preferences_Save_Safe(g_preferences, g_Preferences.m_inipath->str)) {
949         globalOutputStream() << "failed to save local preferences to " << g_Preferences.m_inipath->str << "\n";
950     }
951 }
952
953 void Preferences_Reset()
954 {
955     file_remove(g_Preferences.m_inipath->str);
956 }
957
958
959 void PrefsDlg::PostModal(EMessageBoxReturn code)
960 {
961     if (code == eIDOK) {
962         Preferences_Save();
963         UpdateAllWindows();
964     }
965 }
966
967 std::vector<const char *> g_restart_required;
968
969 void PreferencesDialog_restartRequired(const char *staticName)
970 {
971     g_restart_required.push_back(staticName);
972 }
973
974 void PreferencesDialog_showDialog()
975 {
976     if (ConfirmModified("Edit Preferences") && g_Preferences.DoModal() == eIDOK) {
977         if (!g_restart_required.empty()) {
978             StringOutputStream message(256);
979             message << "Preference changes require a restart:\n";
980             for (std::vector<const char *>::iterator i = g_restart_required.begin();
981                  i != g_restart_required.end(); ++i) {
982                 message << (*i) << '\n';
983             }
984             ui::alert(MainFrame_getWindow(), message.c_str());
985             g_restart_required.clear();
986         }
987     }
988 }
989
990 struct GameName {
991     static void Export(const Callback<void(const char *)> &returnz)
992     {
993         returnz(gamename_get());
994     }
995
996     static void Import(const char *value)
997     {
998         gamename_set(value);
999     }
1000 };
1001
1002 struct GameMode {
1003     static void Export(const Callback<void(const char *)> &returnz)
1004     {
1005         returnz(gamemode_get());
1006     }
1007
1008     static void Import(const char *value)
1009     {
1010         gamemode_set(value);
1011     }
1012 };
1013
1014 void RegisterPreferences(PreferenceSystem &preferences)
1015 {
1016 #if GDEF_OS_WINDOWS
1017     preferences.registerPreference( "UseCustomShaderEditor", make_property_string( g_TextEditor_useWin32Editor ) );
1018 #else
1019     preferences.registerPreference("UseCustomShaderEditor", make_property_string(g_TextEditor_useCustomEditor));
1020     preferences.registerPreference("CustomShaderEditorCommand", make_property_string(g_TextEditor_editorCommand));
1021 #endif
1022
1023     preferences.registerPreference("GameName", make_property<GameName>());
1024     preferences.registerPreference("GameMode", make_property<GameMode>());
1025 }
1026
1027 void Preferences_Init()
1028 {
1029     RegisterPreferences(GetPreferenceSystem());
1030 }