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