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