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