add command line options:
[xonotic/netradiant.git] / radiant / preferences.cpp
1 /*
2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 //
23 // User preferences
24 //
25 // Leonardo Zide (leo@lokigames.com)
26 //
27
28 #include "preferences.h"
29 #include "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  
467   CGameDescription* currentGameDescription = 0;
468
469   if (!m_bGamePrompt)
470   {
471     // search by .game name
472     std::list<CGameDescription *>::iterator iGame;
473     for(iGame=mGames.begin(); iGame!=mGames.end(); ++iGame)
474     {
475       if ((*iGame)->mGameFile == m_sGameFile)
476       {
477         currentGameDescription = (*iGame);
478         break;
479       }
480     }
481   }
482   if (m_bGamePrompt || !currentGameDescription)
483   {
484     Create();
485     DoGameDialog();
486     // use m_nComboSelect to identify the game to run as and set the globals
487     currentGameDescription = GameDescriptionForComboItem();
488     ASSERT_NOTNULL(currentGameDescription);
489   }
490   g_pGameDescription = currentGameDescription;
491
492   g_pGameDescription->Dump();
493 }
494
495 CGameDialog::~CGameDialog()
496 {
497   // free all the game descriptions
498   std::list<CGameDescription *>::iterator iGame;
499   for(iGame=mGames.begin(); iGame!=mGames.end(); ++iGame)
500   {
501     delete (*iGame);
502     *iGame = 0;
503   }
504   if(GetWidget() != 0)
505   {
506     Destroy();
507   }
508 }
509
510 inline const char* GameDescription_getIdentifier(const CGameDescription& gameDescription)
511 {
512   const char* identifier = gameDescription.getKeyValue("index");
513   if(string_empty(identifier))
514   {
515     identifier = "1";
516   }
517   return identifier;
518 }
519
520 void CGameDialog::AddPacksURL(StringOutputStream &URL)
521 {
522   // add the URLs for the list of game packs installed
523   // FIXME: this is kinda hardcoded for now..
524   std::list<CGameDescription *>::iterator iGame;
525   for(iGame=mGames.begin(); iGame!=mGames.end(); ++iGame)
526   {
527     URL << "&Games_dlup%5B%5D=" << GameDescription_getIdentifier(*(*iGame));
528   }
529 }
530
531 CGameDialog g_GamesDialog;
532
533
534 // =============================================================================
535 // Widget callbacks for PrefsDlg
536
537 static void OnButtonClean (GtkWidget *widget, gpointer data) 
538 {
539   // make sure this is what the user wants
540   if (gtk_MessageBox(GTK_WIDGET(g_Preferences.GetWidget()), "This will close Radiant and clean the corresponding registry entries.\n"
541       "Next time you start Radiant it will be good as new. Do you wish to continue?",
542       "Reset Registry", eMB_YESNO, eMB_ICONASTERISK) == eIDYES)
543   {
544     PrefsDlg *dlg = (PrefsDlg*)data;
545     dlg->EndModal (eIDCANCEL);
546
547     g_preferences_globals.disable_ini = true;
548     Preferences_Reset();
549     gtk_main_quit();
550   }
551 }
552
553 // =============================================================================
554 // PrefsDlg class
555
556 /*
557 ========
558
559 very first prefs init deals with selecting the game and the game tools path
560 then we can load .ini stuff
561
562 using prefs / ini settings:
563 those are per-game
564
565 look in ~/.radiant/<version>/gamename
566 ========
567 */
568
569 #define PREFS_LOCAL_FILENAME "local.pref"
570
571 void PrefsDlg::Init()
572 {
573   // m_global_rc_path has been set above
574   // m_rc_path is for game specific preferences
575   // takes the form: global-pref-path/gamename/prefs-file
576
577   // this is common to win32 and Linux init now
578   m_rc_path = g_string_new (m_global_rc_path->str);
579   
580   // game sub-dir
581   g_string_append (m_rc_path, g_pGameDescription->mGameFile.c_str());
582   g_string_append (m_rc_path, "/");
583   Q_mkdir (m_rc_path->str);
584   
585   // then the ini file
586   m_inipath = g_string_new (m_rc_path->str);
587   g_string_append (m_inipath, PREFS_LOCAL_FILENAME);
588 }
589
590 void notebook_set_page(GtkWidget* notebook, GtkWidget* page)
591 {
592   int pagenum = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), page);
593   if(gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook)) != pagenum)
594   {
595     gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), pagenum);
596   }
597 }
598
599 void PrefsDlg::showPrefPage(GtkWidget* prefpage)
600 {
601   notebook_set_page(m_notebook, prefpage);
602   return;
603 }
604
605 static void treeSelection(GtkTreeSelection* selection, gpointer data)
606 {
607   PrefsDlg *dlg = (PrefsDlg*)data;
608
609   GtkTreeModel* model;
610   GtkTreeIter selected;
611   if(gtk_tree_selection_get_selected(selection, &model, &selected))
612   {
613     GtkWidget* prefpage;
614     gtk_tree_model_get(model, &selected, 1, (gpointer*)&prefpage, -1);
615     dlg->showPrefPage(prefpage);
616   }
617 }
618
619 typedef std::list<PreferenceGroupCallback> PreferenceGroupCallbacks;
620
621 inline void PreferenceGroupCallbacks_constructGroup(const PreferenceGroupCallbacks& callbacks, PreferenceGroup& group)
622 {
623   for(PreferenceGroupCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i)
624   {
625     (*i)(group);
626   }
627 }
628
629
630 inline void PreferenceGroupCallbacks_pushBack(PreferenceGroupCallbacks& callbacks, const PreferenceGroupCallback& callback)
631 {
632   callbacks.push_back(callback);
633 }
634
635 typedef std::list<PreferencesPageCallback> PreferencesPageCallbacks;
636
637 inline void PreferencesPageCallbacks_constructPage(const PreferencesPageCallbacks& callbacks, PreferencesPage& page)
638 {
639   for(PreferencesPageCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i)
640   {
641     (*i)(page);
642   }
643 }
644
645 inline void PreferencesPageCallbacks_pushBack(PreferencesPageCallbacks& callbacks, const PreferencesPageCallback& callback)
646 {
647   callbacks.push_back(callback);
648 }
649
650 PreferencesPageCallbacks g_interfacePreferences;
651 void PreferencesDialog_addInterfacePreferences(const PreferencesPageCallback& callback)
652 {
653   PreferencesPageCallbacks_pushBack(g_interfacePreferences, callback);
654 }
655 PreferenceGroupCallbacks g_interfaceCallbacks;
656 void PreferencesDialog_addInterfacePage(const PreferenceGroupCallback& callback)
657 {
658   PreferenceGroupCallbacks_pushBack(g_interfaceCallbacks, callback);
659 }
660
661 PreferencesPageCallbacks g_displayPreferences;
662 void PreferencesDialog_addDisplayPreferences(const PreferencesPageCallback& callback)
663 {
664   PreferencesPageCallbacks_pushBack(g_displayPreferences, callback);
665 }
666 PreferenceGroupCallbacks g_displayCallbacks;
667 void PreferencesDialog_addDisplayPage(const PreferenceGroupCallback& callback)
668 {
669   PreferenceGroupCallbacks_pushBack(g_displayCallbacks, callback);
670 }
671
672 PreferencesPageCallbacks g_settingsPreferences;
673 void PreferencesDialog_addSettingsPreferences(const PreferencesPageCallback& callback)
674 {
675   PreferencesPageCallbacks_pushBack(g_settingsPreferences, callback);
676 }
677 PreferenceGroupCallbacks g_settingsCallbacks;
678 void PreferencesDialog_addSettingsPage(const PreferenceGroupCallback& callback)
679 {
680   PreferenceGroupCallbacks_pushBack(g_settingsCallbacks, callback);
681 }
682
683 void Widget_updateDependency(GtkWidget* self, GtkWidget* toggleButton)
684 {
685   gtk_widget_set_sensitive(self, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggleButton)) && GTK_WIDGET_IS_SENSITIVE(toggleButton));
686 }
687
688 void ToggleButton_toggled_Widget_updateDependency(GtkWidget *toggleButton, GtkWidget* self)
689 {
690   Widget_updateDependency(self, toggleButton);
691 }
692
693 void ToggleButton_state_changed_Widget_updateDependency(GtkWidget* toggleButton, GtkStateType state, GtkWidget* self)
694 {
695   if(state == GTK_STATE_INSENSITIVE)
696   {
697     Widget_updateDependency(self, toggleButton);
698   }
699 }
700
701 void Widget_connectToggleDependency(GtkWidget* self, GtkWidget* toggleButton)
702 {
703   g_signal_connect(G_OBJECT(toggleButton), "state_changed", G_CALLBACK(ToggleButton_state_changed_Widget_updateDependency), self);
704   g_signal_connect(G_OBJECT(toggleButton), "toggled", G_CALLBACK(ToggleButton_toggled_Widget_updateDependency), self);
705   Widget_updateDependency(self, toggleButton);
706 }
707
708
709 inline GtkWidget* getVBox(GtkWidget* page)
710 {
711   return gtk_bin_get_child(GTK_BIN(page));
712 }
713
714 GtkTreeIter PreferenceTree_appendPage(GtkTreeStore* store, GtkTreeIter* parent, const char* name, GtkWidget* page)
715 {
716   GtkTreeIter group;
717   gtk_tree_store_append(store, &group, parent);
718   gtk_tree_store_set(store, &group, 0, name, 1, page, -1);
719   return group;
720 }
721
722 GtkWidget* PreferencePages_addPage(GtkWidget* notebook, const char* name)
723 {
724   GtkWidget* preflabel = gtk_label_new(name);
725   gtk_widget_show(preflabel);
726
727   GtkWidget* pageframe = gtk_frame_new(name);
728   gtk_container_set_border_width(GTK_CONTAINER(pageframe), 4);
729   gtk_widget_show(pageframe);
730
731   GtkWidget* vbox = gtk_vbox_new(FALSE, 4);
732   gtk_widget_show(vbox);
733   gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
734   gtk_container_add(GTK_CONTAINER(pageframe), vbox);
735
736   // Add the page to the notebook
737   gtk_notebook_append_page(GTK_NOTEBOOK(notebook), pageframe, preflabel);
738
739   return pageframe;
740 }
741
742 class PreferenceTreeGroup : public PreferenceGroup
743 {
744   Dialog& m_dialog;
745   GtkWidget* m_notebook;
746   GtkTreeStore* m_store;
747   GtkTreeIter m_group;
748 public:
749   PreferenceTreeGroup(Dialog& dialog, GtkWidget* notebook, GtkTreeStore* store, GtkTreeIter group) :
750     m_dialog(dialog),
751     m_notebook(notebook),
752     m_store(store),
753     m_group(group)
754   {
755   }
756   PreferencesPage createPage(const char* treeName, const char* frameName)
757   {
758     GtkWidget* page = PreferencePages_addPage(m_notebook, frameName);
759     PreferenceTree_appendPage(m_store, &m_group, treeName, page);
760     return PreferencesPage(m_dialog, getVBox(page));
761   }
762 };
763
764 GtkWindow* PrefsDlg::BuildDialog()
765 {
766   PreferencesDialog_addInterfacePreferences(FreeCaller1<PreferencesPage&, Interface_constructPreferences>());
767   Mouse_registerPreferencesPage();
768
769   GtkWindow* dialog = create_floating_window("NetRadiant Preferences", m_parent);
770
771   {
772     GtkWidget* mainvbox = gtk_vbox_new(FALSE, 5);
773     gtk_container_add(GTK_CONTAINER(dialog), mainvbox);
774     gtk_container_set_border_width(GTK_CONTAINER(mainvbox), 5);
775     gtk_widget_show(mainvbox);
776   
777     {
778       GtkWidget* hbox = gtk_hbox_new(FALSE, 5);
779       gtk_widget_show(hbox);
780       gtk_box_pack_end(GTK_BOX(mainvbox), hbox, FALSE, TRUE, 0);
781
782       {
783         GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &m_modal);
784         gtk_box_pack_end(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
785       }
786       {
787         GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &m_modal);
788         gtk_box_pack_end(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
789       }
790       {
791         GtkButton* button = create_dialog_button("Clean", G_CALLBACK(OnButtonClean), this);
792         gtk_box_pack_end(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
793       }
794     }
795   
796     {
797       GtkWidget* hbox = gtk_hbox_new(FALSE, 5);
798       gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 0);
799       gtk_widget_show(hbox);
800   
801       {
802         GtkWidget* sc_win = gtk_scrolled_window_new(0, 0);
803         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sc_win), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
804         gtk_box_pack_start(GTK_BOX(hbox), sc_win, FALSE, FALSE, 0);
805         gtk_widget_show(sc_win);
806         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sc_win), GTK_SHADOW_IN);
807
808         // prefs pages notebook
809         m_notebook = gtk_notebook_new();
810         // hide the notebook tabs since its not supposed to look like a notebook
811         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(m_notebook), FALSE);
812         gtk_box_pack_start(GTK_BOX(hbox), m_notebook, TRUE, TRUE, 0);
813         gtk_widget_show(m_notebook);
814
815
816         {
817           GtkTreeStore* store = gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
818
819           GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
820           gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
821
822           {
823             GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
824             GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Preferences", renderer, "text", 0, NULL);
825             gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
826           }
827
828           {
829             GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
830             g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(treeSelection), this);
831           }
832
833           gtk_widget_show(view);
834
835           gtk_container_add(GTK_CONTAINER (sc_win), view);
836
837           {
838             /********************************************************************/
839             /* Add preference tree options                                      */
840             /********************************************************************/
841             // Front page... 
842             //GtkWidget* front =
843             PreferencePages_addPage(m_notebook, "Front Page");
844
845             {
846               GtkWidget* global = PreferencePages_addPage(m_notebook, "Global Preferences");
847               {
848                 PreferencesPage preferencesPage(*this, getVBox(global));
849                 Global_constructPreferences(preferencesPage);
850               }
851               GtkTreeIter group = PreferenceTree_appendPage(store, 0, "Global", global);
852               {
853                 GtkWidget* game = PreferencePages_addPage(m_notebook, "Game");
854                 PreferencesPage preferencesPage(*this, getVBox(game));
855                 g_GamesDialog.CreateGlobalFrame(preferencesPage);
856
857                 PreferenceTree_appendPage(store, &group, "Game", game);
858               }
859             }
860
861             {
862               GtkWidget* interfacePage = PreferencePages_addPage(m_notebook, "Interface Preferences");
863               {
864                 PreferencesPage preferencesPage(*this, getVBox(interfacePage));
865                 PreferencesPageCallbacks_constructPage(g_interfacePreferences, preferencesPage);
866               }
867
868               GtkTreeIter group = PreferenceTree_appendPage(store, 0, "Interface", interfacePage);
869               PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group);
870
871               PreferenceGroupCallbacks_constructGroup(g_interfaceCallbacks, preferenceGroup);
872             }
873
874             {
875               GtkWidget* display = PreferencePages_addPage(m_notebook, "Display Preferences");
876               {
877                 PreferencesPage preferencesPage(*this, getVBox(display));
878                 PreferencesPageCallbacks_constructPage(g_displayPreferences, preferencesPage);
879               }
880               GtkTreeIter group = PreferenceTree_appendPage(store, 0, "Display", display);
881               PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group);
882
883               PreferenceGroupCallbacks_constructGroup(g_displayCallbacks, preferenceGroup);
884             }
885
886             {
887               GtkWidget* settings = PreferencePages_addPage(m_notebook, "General Settings");
888               {
889                 PreferencesPage preferencesPage(*this, getVBox(settings));
890                 PreferencesPageCallbacks_constructPage(g_settingsPreferences, preferencesPage);
891               }
892
893               GtkTreeIter group = PreferenceTree_appendPage(store, 0, "Settings", settings);
894               PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group);
895
896               PreferenceGroupCallbacks_constructGroup(g_settingsCallbacks, preferenceGroup);
897             }
898           }
899
900           gtk_tree_view_expand_all(GTK_TREE_VIEW(view));
901     
902           g_object_unref(G_OBJECT(store));
903         }
904       }
905     }
906   }
907
908   gtk_notebook_set_page(GTK_NOTEBOOK(m_notebook), 0);
909
910   return dialog;
911 }
912
913 preferences_globals_t g_preferences_globals;
914
915 PrefsDlg g_Preferences;               // global prefs instance
916
917
918 void PreferencesDialog_constructWindow(GtkWindow* main_window)
919 {
920   g_Preferences.m_parent = main_window;
921   g_Preferences.Create();
922 }
923 void PreferencesDialog_destroyWindow()
924 {
925   g_Preferences.Destroy();
926 }
927
928
929 PreferenceDictionary g_preferences;
930
931 PreferenceSystem& GetPreferenceSystem()
932 {
933   return g_preferences;
934 }
935
936 class PreferenceSystemAPI
937 {
938   PreferenceSystem* m_preferencesystem;
939 public:
940   typedef PreferenceSystem Type;
941   STRING_CONSTANT(Name, "*");
942
943   PreferenceSystemAPI()
944   {
945     m_preferencesystem = &GetPreferenceSystem();
946   }
947   PreferenceSystem* getTable()
948   {
949     return m_preferencesystem;
950   }
951 };
952
953 #include "modulesystem/singletonmodule.h"
954 #include "modulesystem/moduleregistry.h"
955
956 typedef SingletonModule<PreferenceSystemAPI> PreferenceSystemModule;
957 typedef Static<PreferenceSystemModule> StaticPreferenceSystemModule;
958 StaticRegisterModule staticRegisterPreferenceSystem(StaticPreferenceSystemModule::instance());
959
960 void Preferences_Load()
961 {
962   g_GamesDialog.LoadPrefs();
963
964   globalOutputStream() << "loading local preferences from " << g_Preferences.m_inipath->str << "\n";
965
966   if(!Preferences_Load(g_preferences, g_Preferences.m_inipath->str, g_GamesDialog.m_sGameFile.c_str()))
967   {
968     globalOutputStream() << "failed to load local preferences from " << g_Preferences.m_inipath->str << "\n";
969   }
970 }
971
972 void Preferences_Save()
973 {
974   if (g_preferences_globals.disable_ini)
975     return;
976
977   g_GamesDialog.SavePrefs();
978
979   globalOutputStream() << "saving local preferences to " << g_Preferences.m_inipath->str << "\n";
980
981   if(!Preferences_Save_Safe(g_preferences, g_Preferences.m_inipath->str))
982   {
983     globalOutputStream() << "failed to save local preferences to " << g_Preferences.m_inipath->str << "\n";
984   }
985 }
986
987 void Preferences_Reset()
988 {
989   file_remove(g_Preferences.m_inipath->str);
990 }
991
992
993 void PrefsDlg::PostModal (EMessageBoxReturn code)
994 {
995   if (code == eIDOK)
996   {
997     Preferences_Save();
998     UpdateAllWindows();
999   }
1000 }
1001
1002 std::vector<const char*> g_restart_required;
1003
1004 void PreferencesDialog_restartRequired(const char* staticName)
1005 {
1006   g_restart_required.push_back(staticName);
1007 }
1008
1009 void PreferencesDialog_showDialog()
1010 {
1011   if(ConfirmModified("Edit Preferences") && g_Preferences.DoModal() == eIDOK)
1012   {
1013     if(!g_restart_required.empty())
1014     {
1015       StringOutputStream message(256);
1016       message << "Preference changes require a restart:\n";
1017       for(std::vector<const char*>::iterator i = g_restart_required.begin(); i != g_restart_required.end(); ++i)
1018       {
1019         message << (*i) << '\n';
1020       }
1021       gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), message.c_str());
1022       g_restart_required.clear();
1023     }
1024   }
1025 }
1026
1027
1028
1029
1030
1031 void GameName_importString(const char* value)
1032 {
1033   gamename_set(value);
1034 }
1035 typedef FreeCaller1<const char*, GameName_importString> GameNameImportStringCaller;
1036 void GameName_exportString(const StringImportCallback& importer)
1037 {
1038   importer(gamename_get());
1039 }
1040 typedef FreeCaller1<const StringImportCallback&, GameName_exportString> GameNameExportStringCaller;
1041
1042 void GameMode_importString(const char* value)
1043 {
1044   gamemode_set(value);
1045 }
1046 typedef FreeCaller1<const char*, GameMode_importString> GameModeImportStringCaller;
1047 void GameMode_exportString(const StringImportCallback& importer)
1048 {
1049   importer(gamemode_get());
1050 }
1051 typedef FreeCaller1<const StringImportCallback&, GameMode_exportString> GameModeExportStringCaller;
1052
1053
1054 void RegisterPreferences(PreferenceSystem& preferences)
1055 {
1056 #ifdef WIN32
1057   preferences.registerPreference("UseCustomShaderEditor", BoolImportStringCaller(g_TextEditor_useWin32Editor), BoolExportStringCaller(g_TextEditor_useWin32Editor));
1058 #else
1059   preferences.registerPreference("UseCustomShaderEditor", BoolImportStringCaller(g_TextEditor_useCustomEditor), BoolExportStringCaller(g_TextEditor_useCustomEditor));
1060   preferences.registerPreference("CustomShaderEditorCommand", CopiedStringImportStringCaller(g_TextEditor_editorCommand), CopiedStringExportStringCaller(g_TextEditor_editorCommand));
1061 #endif
1062
1063   preferences.registerPreference("GameName", GameNameImportStringCaller(), GameNameExportStringCaller());
1064   preferences.registerPreference("GameMode", GameModeImportStringCaller(), GameModeExportStringCaller());
1065 }
1066
1067 void Preferences_Init()
1068 {
1069   RegisterPreferences(GetPreferenceSystem());
1070 }