]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/build.cpp
added plugins to win32/linux installers; fixed crash when plugins are disabled
[xonotic/netradiant.git] / radiant / build.cpp
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
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 #include "build.h"
23 #include "debugging/debugging.h"
24
25 #include <map>
26 #include <list>
27 #include "stream/stringstream.h"
28 #include "versionlib.h"
29
30 #include "mainframe.h"
31
32 typedef std::map<CopiedString, CopiedString> Variables;
33 Variables g_build_variables;
34
35 void build_clear_variables()
36 {
37   g_build_variables.clear();
38 }
39
40 void build_set_variable(const char* name, const char* value)
41 {
42   g_build_variables[name] = value;
43 }
44
45 const char* build_get_variable(const char* name)
46 {
47   Variables::iterator i = g_build_variables.find(name);
48   if(i != g_build_variables.end())
49   {
50     return (*i).second.c_str();
51   }
52   globalErrorStream() << "undefined build variable: " << makeQuoted(name) << "\n";
53   return "";
54 }
55
56 #include "xml/ixml.h"
57 #include "xml/xmlelement.h"
58
59 class Evaluatable
60 {
61 public:
62   virtual void evaluate(StringBuffer& output) = 0;
63   virtual void exportXML(XMLImporter& importer) = 0;
64 };
65
66 class VariableString : public Evaluatable
67 {
68   CopiedString m_string;
69 public:
70   VariableString() : m_string()
71   {
72   }
73   VariableString(const char* string) : m_string(string)
74   {
75   }
76   const char* c_str() const
77   {
78     return m_string.c_str();
79   }
80   void setString(const char* string)
81   {
82     m_string = string;
83   }
84   void evaluate(StringBuffer& output)
85   {
86     StringBuffer variable;
87     bool in_variable = false;
88     for(const char* i = m_string.c_str(); *i != '\0'; ++i)
89     {
90       if(!in_variable)
91       {
92         switch(*i)
93         {
94         case '[':
95           in_variable = true;
96           break;
97         default:
98           output.push_back(*i);
99           break;
100         }
101       }
102       else
103       {
104         switch(*i)
105         {
106         case ']':
107           in_variable = false;
108           output.push_string(build_get_variable(variable.c_str()));
109           variable.clear();
110           break;
111         default:
112           variable.push_back(*i);
113           break;
114         }
115       }
116     }
117   }
118   void exportXML(XMLImporter& importer)
119   {
120     importer << c_str();
121   }
122 };
123
124 class Conditional : public Evaluatable
125 {
126   VariableString* m_test;
127 public:
128   Evaluatable* m_result;
129   Conditional(VariableString* test) : m_test(test)
130   {
131   }
132   ~Conditional()
133   {
134     delete m_test;
135     delete m_result;
136   }
137   void evaluate(StringBuffer& output)
138   {
139     StringBuffer buffer;
140     m_test->evaluate(buffer);
141     if(!string_empty(buffer.c_str()))
142     {
143       m_result->evaluate(output);
144     }
145   }
146   void exportXML(XMLImporter& importer)
147   {
148     StaticElement conditionElement("cond");
149     conditionElement.insertAttribute("value", m_test->c_str());
150     importer.pushElement(conditionElement);
151     m_result->exportXML(importer);
152     importer.popElement(conditionElement.name());
153   }
154 };
155
156 typedef std::vector<Evaluatable*> Evaluatables;
157
158 class Tool : public Evaluatable
159 {
160   Evaluatables m_evaluatables;
161 public:
162   ~Tool()
163   {
164     for(Evaluatables::iterator i = m_evaluatables.begin(); i != m_evaluatables.end(); ++i)
165     {
166       delete (*i);
167     }
168   }
169   void push_back(Evaluatable* evaluatable)
170   {
171     m_evaluatables.push_back(evaluatable);
172   }
173   void evaluate(StringBuffer& output)
174   {
175     for(Evaluatables::iterator i = m_evaluatables.begin(); i != m_evaluatables.end(); ++i)
176     {
177       (*i)->evaluate(output);
178     }
179   }
180   void exportXML(XMLImporter& importer)
181   {
182     for(Evaluatables::iterator i = m_evaluatables.begin(); i != m_evaluatables.end(); ++i)
183     {
184       (*i)->exportXML(importer);
185     }
186   }
187 };
188
189 #include "xml/ixml.h"
190
191 class XMLElementParser : public TextOutputStream
192 {
193 public:
194   virtual XMLElementParser& pushElement(const XMLElement& element) = 0;
195   virtual void popElement(const char* name) = 0;
196 };
197
198 class VariableStringXMLConstructor : public XMLElementParser
199 {
200   StringBuffer m_buffer;
201   VariableString& m_variableString;
202 public:
203   VariableStringXMLConstructor(VariableString& variableString) : m_variableString(variableString)
204   {
205   }
206   ~VariableStringXMLConstructor()
207   {
208     m_variableString.setString(m_buffer.c_str());
209   }
210   std::size_t write(const char* buffer, std::size_t length)
211   {
212     m_buffer.push_range(buffer, buffer + length);
213     return length;
214   }
215   XMLElementParser& pushElement(const XMLElement& element)
216   {
217     ERROR_MESSAGE("parse error: invalid element \"" << element.name() << "\"");
218     return *this;
219   }
220   void popElement(const char* name)
221   {
222   }
223 };
224
225 class ConditionalXMLConstructor : public XMLElementParser
226 {
227   StringBuffer m_buffer;
228   Conditional& m_conditional;
229 public:
230   ConditionalXMLConstructor(Conditional& conditional) : m_conditional(conditional)
231   {
232   }
233   ~ConditionalXMLConstructor()
234   {
235     m_conditional.m_result = new VariableString(m_buffer.c_str());
236   }
237   std::size_t write(const char* buffer, std::size_t length)
238   {
239     m_buffer.push_range(buffer, buffer + length);
240     return length;
241   }
242   XMLElementParser& pushElement(const XMLElement& element)
243   {
244     ERROR_MESSAGE("parse error: invalid element \"" << element.name() << "\"");
245     return *this;
246   }
247   void popElement(const char* name)
248   {
249   }
250 };
251
252 class ToolXMLConstructor : public XMLElementParser
253 {
254   StringBuffer m_buffer;
255   Tool& m_tool;
256   ConditionalXMLConstructor* m_conditional;
257 public:
258   ToolXMLConstructor(Tool& tool) : m_tool(tool)
259   {
260   }
261   ~ToolXMLConstructor()
262   {
263     flush();
264   }
265   std::size_t write(const char* buffer, std::size_t length)
266   {
267     m_buffer.push_range(buffer, buffer + length);
268     return length;
269   }
270   XMLElementParser& pushElement(const XMLElement& element)
271   {
272     if(string_equal(element.name(), "cond"))
273     {
274       flush();
275       Conditional* conditional = new Conditional(new VariableString(element.attribute("value")));
276       m_tool.push_back(conditional);
277       m_conditional = new ConditionalXMLConstructor(*conditional);
278       return *m_conditional;
279     }
280     else
281     {
282       ERROR_MESSAGE("parse error: invalid element \"" << element.name() << "\"");
283       return *this;
284     }
285   }
286   void popElement(const char* name)
287   {
288     if(string_equal(name, "cond"))
289     {
290       delete m_conditional;
291     }
292   }
293
294   void flush()
295   {
296     if(!m_buffer.empty())
297     {
298       m_tool.push_back(new VariableString(m_buffer.c_str()));
299       m_buffer.clear();
300     }
301   }
302 };
303
304 typedef VariableString BuildCommand;
305 typedef std::list<BuildCommand> Build;
306
307 class BuildXMLConstructor : public XMLElementParser
308 {
309   VariableStringXMLConstructor* m_variableString;
310   Build& m_build;
311 public:
312   BuildXMLConstructor(Build& build) : m_build(build)
313   {
314   }
315   std::size_t write(const char* buffer, std::size_t length)
316   {
317     return length;
318   }
319   XMLElementParser& pushElement(const XMLElement& element)
320   {
321     if(string_equal(element.name(), "command"))
322     {
323       m_build.push_back(BuildCommand());
324       m_variableString = new VariableStringXMLConstructor(m_build.back());
325       return *m_variableString;
326     }
327     else
328     {
329       ERROR_MESSAGE("parse error: invalid element");
330       return *this;
331     }
332   }
333   void popElement(const char* name)
334   {
335     delete m_variableString;
336   }
337 };
338
339 typedef std::pair<CopiedString, Build> BuildPair;
340
341 class BuildPairEqual
342 {
343   const char* m_name;
344 public:
345   BuildPairEqual(const char* name) : m_name(name)
346   {
347   }
348   bool operator()(const BuildPair& self) const
349   {
350     return string_equal(self.first.c_str(), m_name);
351   }
352 };
353
354 typedef std::list<BuildPair> Project;
355
356 Project::iterator Project_find(Project& project, const char* name)
357 {
358   return std::find_if(project.begin(), project.end(), BuildPairEqual(name));
359 }
360
361 Project::iterator Project_find(Project& project, std::size_t index)
362 {
363   Project::iterator i = project.begin();
364   while(index-- != 0 && i != project.end())
365   {
366     ++i;
367   }
368   return i;
369 }
370
371 Build& project_find(Project& project, const char* build)
372 {
373   Project::iterator i = Project_find(project, build);
374   ASSERT_MESSAGE(i != project.end(), "error finding build command");
375   return (*i).second;
376 }
377
378 Build::iterator Build_find(Build& build, std::size_t index)
379 {
380   Build::iterator i = build.begin();
381   while(index-- != 0 && i != build.end())
382   {
383     ++i;
384   }
385   return i;
386 }
387
388 typedef std::map<CopiedString, Tool> Tools;
389
390 class ProjectXMLConstructor : public XMLElementParser
391 {
392   ToolXMLConstructor* m_tool;
393   BuildXMLConstructor* m_build;
394   Project& m_project;
395   Tools& m_tools;
396 public:
397   ProjectXMLConstructor(Project& project, Tools& tools) : m_project(project), m_tools(tools)
398   {
399   }
400   std::size_t write(const char* buffer, std::size_t length)
401   {
402     return length;
403   }
404   XMLElementParser& pushElement(const XMLElement& element)
405   {
406     if(string_equal(element.name(), "var"))
407     {
408       Tools::iterator i = m_tools.insert(Tools::value_type(element.attribute("name"), Tool())).first;
409       m_tool = new ToolXMLConstructor((*i).second);
410       return *m_tool;
411     }
412     else if(string_equal(element.name(), "build"))
413     {
414       m_project.push_back(Project::value_type(element.attribute("name"), Build()));
415       m_build = new BuildXMLConstructor(m_project.back().second);
416       return *m_build;
417     }
418     else
419     {
420       ERROR_MESSAGE("parse error: invalid element");
421       return *this;
422     }
423   }
424   void popElement(const char* name)
425   {
426     if(string_equal(name, "var"))
427     {
428       delete m_tool;
429     }
430     else if(string_equal(name, "build"))
431     {
432       delete m_build;
433     }
434   }
435 };
436
437 class SkipAllParser : public XMLElementParser
438 {
439 public:
440   std::size_t write(const char* buffer, std::size_t length)
441   {
442     return length;
443   }
444   XMLElementParser& pushElement(const XMLElement& element)
445   {
446     return *this;
447   }
448   void popElement(const char* name)
449   {
450   }
451 };
452
453 class RootXMLConstructor : public XMLElementParser
454 {
455   CopiedString m_elementName;
456   XMLElementParser& m_parser;
457   SkipAllParser m_skip;
458   Version m_version;
459   bool m_compatible;
460 public:
461   RootXMLConstructor(const char* elementName, XMLElementParser& parser, const char* version) :
462     m_elementName(elementName),
463     m_parser(parser),
464     m_version(version_parse(version)),
465     m_compatible(false)
466   {
467   }
468   std::size_t write(const char* buffer, std::size_t length)
469   {
470     return length;
471   }
472   XMLElementParser& pushElement(const XMLElement& element)
473   {
474     if(string_equal(element.name(), m_elementName.c_str()))
475     {
476       Version dataVersion(version_parse(element.attribute("version")));
477       if(version_compatible(m_version, dataVersion))
478       {
479         m_compatible = true;
480         return m_parser;
481       }
482       else
483       {
484         return m_skip;
485       }
486     }
487     else
488     {
489       //ERROR_MESSAGE("parse error: invalid element \"" << element.name() << "\"");
490       return *this;
491     }
492   }
493   void popElement(const char* name)
494   {
495   }
496
497   bool versionCompatible() const
498   {
499     return m_compatible;
500   }
501 };
502
503 namespace
504 {
505   Project g_build_project;
506   Tools g_build_tools;
507   bool g_build_changed = false;
508 }
509
510 void build_error_undefined_tool(const char* build, const char* tool)
511 {
512   globalErrorStream() << "build " << makeQuoted(build) << " refers to undefined tool " << makeQuoted(tool) << '\n';
513 }
514
515 void project_verify(Project& project, Tools& tools)
516 {
517 #if 0
518   for(Project::iterator i = project.begin(); i != project.end(); ++i)
519   {
520     Build& build = (*i).second;
521     for(Build::iterator j = build.begin(); j != build.end(); ++j)
522     {
523       Tools::iterator k = tools.find((*j).first);
524       if(k == g_build_tools.end())
525       {
526         build_error_undefined_tool((*i).first.c_str(), (*j).first.c_str());
527       }
528     }
529   }
530 #endif
531 }
532
533 void build_run(const char* name, CommandListener& listener)
534 {
535   for(Tools::iterator i = g_build_tools.begin(); i != g_build_tools.end(); ++i)
536   {
537     StringBuffer output;
538     (*i).second.evaluate(output);
539     build_set_variable((*i).first.c_str(), output.c_str());
540   }
541
542   {
543     Project::iterator i = Project_find(g_build_project, name);
544     if(i != g_build_project.end())
545     {
546       Build& build = (*i).second;
547       for(Build::iterator j = build.begin(); j != build.end(); ++j)
548       {
549         StringBuffer output;
550         (*j).evaluate(output);
551         listener.execute(output.c_str());
552       }
553     }
554     else
555     {
556       globalErrorStream() << "build " << makeQuoted(name) << " not defined";
557     }
558   }
559 }
560
561
562 typedef std::vector<XMLElementParser*> XMLElementStack;
563
564 class XMLParser : public XMLImporter
565 {
566   XMLElementStack m_stack;
567 public:
568   XMLParser(XMLElementParser& parser)
569   {
570     m_stack.push_back(&parser);
571   }
572   std::size_t write(const char* buffer, std::size_t length)
573   {
574     return m_stack.back()->write(buffer, length);
575   }
576   void pushElement(const XMLElement& element)
577   {
578     m_stack.push_back(&m_stack.back()->pushElement(element));
579   }
580   void popElement(const char* name)
581   {
582     m_stack.pop_back();
583     m_stack.back()->popElement(name);
584   }
585 };
586
587 #include "stream/textfilestream.h"
588 #include "xml/xmlparser.h"
589
590 const char* const BUILDMENU_VERSION = "2.0";
591
592 bool build_commands_parse(const char* filename)
593 {
594   TextFileInputStream projectFile(filename);
595   if(!projectFile.failed())
596   {
597     ProjectXMLConstructor projectConstructor(g_build_project, g_build_tools);
598     RootXMLConstructor rootConstructor("project", projectConstructor, BUILDMENU_VERSION);
599     XMLParser importer(rootConstructor);
600     XMLStreamParser parser(projectFile);
601     parser.exportXML(importer);
602
603     if(rootConstructor.versionCompatible())
604     {
605       project_verify(g_build_project, g_build_tools);
606
607       return true;
608     }
609     globalErrorStream() << "failed to parse build menu: " << makeQuoted(filename);
610   }
611   return false;
612 }
613
614 void build_commands_clear()
615 {
616   g_build_project.clear();
617   g_build_tools.clear();
618 }
619
620 class BuildXMLExporter
621 {
622   Build& m_build;
623 public:
624   BuildXMLExporter(Build& build) : m_build(build)
625   {
626   }
627   void exportXML(XMLImporter& importer)
628   {
629     importer << "\n";
630     for(Build::iterator i = m_build.begin(); i != m_build.end(); ++i)
631     {
632       StaticElement commandElement("command");
633       importer.pushElement(commandElement);
634       (*i).exportXML(importer);
635       importer.popElement(commandElement.name());
636       importer << "\n";
637     }
638   }
639 };
640
641 class ProjectXMLExporter
642 {
643   Project& m_project;
644   Tools& m_tools;
645 public:
646   ProjectXMLExporter(Project& project, Tools& tools) : m_project(project), m_tools(tools)
647   {
648   }
649   void exportXML(XMLImporter& importer)
650   {
651     StaticElement projectElement("project");
652     projectElement.insertAttribute("version", BUILDMENU_VERSION);
653     importer.pushElement(projectElement);
654     importer << "\n";
655
656     for(Tools::iterator i = m_tools.begin(); i != m_tools.end(); ++i)
657     {
658       StaticElement toolElement("var");
659       toolElement.insertAttribute("name", (*i).first.c_str());
660       importer.pushElement(toolElement);
661       (*i).second.exportXML(importer);
662       importer.popElement(toolElement.name());
663       importer << "\n";
664     }
665     for(Project::iterator i = m_project.begin(); i != m_project.end(); ++i)
666     {
667       StaticElement buildElement("build");
668       buildElement.insertAttribute("name", (*i).first.c_str());
669       importer.pushElement(buildElement);
670       BuildXMLExporter buildExporter((*i).second);
671       buildExporter.exportXML(importer);
672       importer.popElement(buildElement.name());
673       importer << "\n";
674     }
675     importer.popElement(projectElement.name());
676   }
677 };
678
679 #include "xml/xmlwriter.h"
680
681 void build_commands_write(const char* filename)
682 {
683   TextFileOutputStream projectFile(filename);
684   if(!projectFile.failed())
685   {
686     XMLStreamWriter writer(projectFile);
687     ProjectXMLExporter projectExporter(g_build_project, g_build_tools);
688     writer << "\n";
689     projectExporter.exportXML(writer);
690     writer << "\n";
691   }
692 }
693
694
695 #include <gdk/gdkkeysyms.h>
696 #include <gtk/gtkmain.h>
697 #include <gtk/gtkbox.h>
698 #include <gtk/gtktable.h>
699 #include <gtk/gtktreeview.h>
700 #include <gtk/gtkcellrenderertext.h>
701 #include <gtk/gtktreeselection.h>
702 #include <gtk/gtkliststore.h>
703 #include <gtk/gtkscrolledwindow.h>
704
705 #include "gtkutil/dialog.h"
706 #include "gtkutil/closure.h"
707 #include "gtkutil/window.h"
708 #include "gtkdlgs.h"
709
710 void Build_refreshMenu(GtkMenu* menu);
711
712
713 void BSPCommandList_Construct(GtkListStore* store, Project& project)
714 {
715   gtk_list_store_clear(store);
716
717   for(Project::iterator i = project.begin(); i != project.end(); ++i)
718   {
719     const char* buildName = (*i).first.c_str();
720
721     GtkTreeIter buildIter;
722     gtk_list_store_append(store, &buildIter);
723     gtk_list_store_set(store, &buildIter, 0, const_cast<char*>(buildName), -1);
724   }
725
726   GtkTreeIter lastIter;
727   gtk_list_store_append(store, &lastIter);
728 }
729
730 class ProjectList
731 {
732 public:
733   Project& m_project;
734   GtkListStore* m_store;
735   bool m_changed;
736   ProjectList(Project& project) : m_project(project), m_changed(false)
737   {
738   }
739 };
740
741 gboolean project_cell_edited(GtkCellRendererText* cell, gchar* path_string, gchar* new_text, ProjectList* projectList)
742 {
743   Project& project = projectList->m_project;
744
745   GtkTreePath* path = gtk_tree_path_new_from_string(path_string);
746
747   ASSERT_MESSAGE(gtk_tree_path_get_depth(path) == 1, "invalid path length");
748
749   GtkTreeIter iter;
750   gtk_tree_model_get_iter(GTK_TREE_MODEL(projectList->m_store), &iter, path);
751
752   Project::iterator i = Project_find(project, gtk_tree_path_get_indices(path)[0]);
753   if(i != project.end())
754   {
755     projectList->m_changed = true;
756     if(string_empty(new_text))
757     {
758       project.erase(i);
759       Build_refreshMenu(g_bsp_menu);
760
761       gtk_list_store_remove(projectList->m_store, &iter);
762     }
763     else
764     {
765       (*i).first = new_text;
766       Build_refreshMenu(g_bsp_menu);
767
768       gtk_list_store_set(projectList->m_store, &iter, 0, new_text, -1);
769     }
770   }
771   else if(!string_empty(new_text))
772   {
773     projectList->m_changed = true;
774     project.push_back(Project::value_type(new_text, Build()));
775     Build_refreshMenu(g_bsp_menu);
776
777     gtk_list_store_set(projectList->m_store, &iter, 0, new_text, -1);
778     GtkTreeIter lastIter;
779     gtk_list_store_append(projectList->m_store, &lastIter);
780   }
781
782   gtk_tree_path_free(path);
783
784   return FALSE;
785 }
786
787 gboolean project_key_press(GtkWidget* widget, GdkEventKey* event, GtkListStore* store, ProjectList* projectList)
788 {
789   Project& project = projectList->m_project;
790
791   if(event->keyval == GDK_Delete)
792   {
793     GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
794     GtkTreeIter iter;
795     GtkTreeModel* model;
796     if(gtk_tree_selection_get_selected(selection, &model, &iter))
797     {
798       GtkTreePath* path = gtk_tree_model_get_path(model, &iter);
799       Project::iterator x = Project_find(project, gtk_tree_path_get_indices(path)[0]);
800       gtk_tree_path_free(path);
801
802       if(x != project.end())
803       {
804         projectList->m_changed = true;
805         project.erase(x);
806         Build_refreshMenu(g_bsp_menu);
807
808         gtk_list_store_remove(projectList->m_store, &iter);
809       }
810     }
811   }
812   return FALSE;
813 }
814
815
816 Build* g_current_build = 0;
817
818 gboolean project_selection_changed(GtkTreeSelection* selection, GtkListStore* store)
819 {
820   Project& project = g_build_project;
821
822   gtk_list_store_clear(store);
823
824   GtkTreeIter iter;
825   GtkTreeModel* model;
826   if(gtk_tree_selection_get_selected(selection, &model, &iter))
827   {
828     GtkTreePath* path = gtk_tree_model_get_path(model, &iter);
829     Project::iterator x = Project_find(project, gtk_tree_path_get_indices(path)[0]);
830     gtk_tree_path_free(path);
831
832     if(x != project.end())
833     {
834       Build& build = (*x).second;
835       g_current_build = &build;
836
837       for(Build::iterator i = build.begin(); i != build.end(); ++i)
838       {
839         GtkTreeIter commandIter;
840         gtk_list_store_append(store, &commandIter);
841         gtk_list_store_set(store, &commandIter, 0, const_cast<char*>((*i).c_str()), -1);
842       }
843       GtkTreeIter lastIter;
844       gtk_list_store_append(store, &lastIter);
845     }
846     else
847     {
848       g_current_build = 0;
849     }
850   }
851   else
852   {
853     g_current_build = 0;
854   }
855
856   return FALSE;
857 }
858
859 gboolean commands_cell_edited(GtkCellRendererText* cell, gchar* path_string, gchar* new_text, GtkListStore* store)
860 {
861   if(g_current_build == 0)
862   {
863     return FALSE;
864   }
865   Build& build = *g_current_build;
866
867   GtkTreePath* path = gtk_tree_path_new_from_string(path_string);
868
869   ASSERT_MESSAGE(gtk_tree_path_get_depth(path) == 1, "invalid path length");
870
871   GtkTreeIter iter;
872   gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path);
873
874   Build::iterator i = Build_find(build, gtk_tree_path_get_indices(path)[0]);
875   if(i != build.end())
876   {
877     g_build_changed = true;
878     (*i).setString(new_text);
879
880     gtk_list_store_set(store, &iter, 0, new_text, -1);
881   }
882   else if(!string_empty(new_text))
883   {
884     g_build_changed = true;
885     build.push_back(Build::value_type(VariableString(new_text)));
886
887     gtk_list_store_set(store, &iter, 0, new_text, -1);
888
889     GtkTreeIter lastIter;
890     gtk_list_store_append(store, &lastIter);
891   }
892
893   gtk_tree_path_free(path);
894
895   return FALSE;
896 }
897
898 gboolean commands_key_press(GtkWidget* widget, GdkEventKey* event, GtkListStore* store)
899 {
900   if(g_current_build == 0)
901   {
902     return FALSE;
903   }
904   Build& build = *g_current_build;
905
906   if(event->keyval == GDK_Delete)
907   {
908     GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
909     GtkTreeIter iter;
910     GtkTreeModel* model;
911     if(gtk_tree_selection_get_selected(selection, &model, &iter))
912     {
913       GtkTreePath* path = gtk_tree_model_get_path(model, &iter);
914       Build::iterator i = Build_find(build, gtk_tree_path_get_indices(path)[0]);
915       gtk_tree_path_free(path);
916
917       if(i != build.end())
918       {
919         g_build_changed = true;
920         build.erase(i);
921
922         gtk_list_store_remove(store, &iter);
923       }
924     }
925   }
926   return FALSE;
927 }
928
929
930 GtkWindow* BuildMenuDialog_construct(ModalDialog& modal, ProjectList& projectList)
931 {
932   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Build Menu", G_CALLBACK(dialog_delete_callback), &modal, -1, 400);
933
934   GtkWidget* buildView = 0;
935
936   {
937     GtkTable* table1 = create_dialog_table(2, 2, 4, 4, 4);
938     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(table1));
939     {
940       GtkVBox* vbox = create_dialog_vbox(4);
941       gtk_table_attach(table1, GTK_WIDGET(vbox), 1, 2, 0, 1,
942                         (GtkAttachOptions) (GTK_FILL),
943                         (GtkAttachOptions) (GTK_FILL), 0, 0);
944       {
945         GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &modal);
946         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
947       }
948       {
949         GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &modal);
950         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
951       }
952     }
953     {
954       GtkFrame* frame = create_dialog_frame("Build menu");
955       gtk_table_attach(table1, GTK_WIDGET(frame), 0, 1, 0, 1,
956                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
957                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
958       {
959         GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4);
960         gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(scr));
961
962         {
963           GtkListStore* store = gtk_list_store_new(1, G_TYPE_STRING);
964
965           GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
966           gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
967
968           GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
969           object_set_boolean_property(G_OBJECT(renderer), "editable", TRUE);
970           g_signal_connect(renderer, "edited", G_CALLBACK(project_cell_edited), &projectList);
971  
972           GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, 0);
973           gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
974
975           GtkTreeSelection* selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
976           gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
977
978           gtk_widget_show(view);
979
980           buildView = view;
981           projectList.m_store = store;
982           gtk_container_add(GTK_CONTAINER (scr), view);
983   
984           g_signal_connect(G_OBJECT(view), "key_press_event", G_CALLBACK(project_key_press), &projectList);
985
986           g_object_unref(G_OBJECT(store));
987         }
988       }
989     }
990     {
991       GtkFrame* frame = create_dialog_frame("Commandline");
992       gtk_table_attach(table1, GTK_WIDGET(frame), 0, 1, 1, 2,
993                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
994                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
995       {
996         GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4);
997         gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(scr));
998
999         {
1000           GtkListStore* store = gtk_list_store_new(1, G_TYPE_STRING);
1001
1002           GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1003           gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
1004
1005           GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
1006           object_set_boolean_property(G_OBJECT(renderer), "editable", TRUE);
1007           g_signal_connect(renderer, "edited", G_CALLBACK(commands_cell_edited), store);
1008  
1009           GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, 0);
1010           gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
1011
1012           GtkTreeSelection* selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
1013           gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
1014
1015           gtk_widget_show(view);
1016
1017           gtk_container_add(GTK_CONTAINER (scr), view);
1018   
1019           g_object_unref(G_OBJECT(store));
1020
1021           g_signal_connect(G_OBJECT(view), "key_press_event", G_CALLBACK(commands_key_press), store);
1022          
1023           g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(buildView))), "changed", G_CALLBACK(project_selection_changed), store);
1024         }
1025       }
1026     }
1027   }
1028
1029   BSPCommandList_Construct(projectList.m_store, g_build_project);
1030
1031   return window;
1032 }
1033
1034 namespace
1035 {
1036   CopiedString g_buildMenu;
1037 }
1038
1039 void LoadBuildMenu();
1040
1041 void DoBuildMenu()
1042 {
1043   ModalDialog modal;
1044
1045   ProjectList projectList(g_build_project);
1046
1047   GtkWindow* window = BuildMenuDialog_construct(modal, projectList);
1048
1049   if(modal_dialog_show(window, modal) == eIDCANCEL)
1050   {
1051     build_commands_clear();
1052     LoadBuildMenu();
1053
1054     Build_refreshMenu(g_bsp_menu);
1055   }
1056   else if(projectList.m_changed)
1057   {
1058     g_build_changed = true;
1059   }
1060
1061   gtk_widget_destroy(GTK_WIDGET(window));
1062 }
1063
1064
1065
1066 #include "gtkutil/menu.h"
1067 #include "mainframe.h"
1068 #include "preferences.h"
1069 #include "qe3.h"
1070
1071 typedef struct _GtkMenuItem GtkMenuItem;
1072
1073 class BuildMenuItem
1074 {
1075   const char* m_name;
1076 public:
1077   GtkMenuItem* m_item;
1078   BuildMenuItem(const char* name, GtkMenuItem* item)
1079     : m_name(name), m_item(item)
1080   {
1081   }
1082   void run()
1083   {
1084     RunBSP(m_name);
1085   }
1086   typedef MemberCaller<BuildMenuItem, &BuildMenuItem::run> RunCaller;
1087 };
1088
1089 typedef std::list<BuildMenuItem> BuildMenuItems;
1090 BuildMenuItems g_BuildMenuItems;
1091
1092
1093 GtkMenu* g_bsp_menu;
1094
1095 void Build_constructMenu(GtkMenu* menu)
1096 {
1097   for(Project::iterator i = g_build_project.begin(); i != g_build_project.end(); ++i)
1098   {
1099     g_BuildMenuItems.push_back(BuildMenuItem((*i).first.c_str(), 0));
1100     g_BuildMenuItems.back().m_item = create_menu_item_with_mnemonic(menu, (*i).first.c_str(), BuildMenuItem::RunCaller(g_BuildMenuItems.back()));
1101   }
1102 }
1103
1104
1105 void Build_refreshMenu(GtkMenu* menu)
1106 {
1107   for(BuildMenuItems::iterator i = g_BuildMenuItems.begin(); i != g_BuildMenuItems.end(); ++i)
1108   {
1109     gtk_container_remove(GTK_CONTAINER(menu), GTK_WIDGET((*i).m_item));
1110   }
1111
1112   g_BuildMenuItems.clear();
1113
1114   Build_constructMenu(menu);
1115 }
1116
1117
1118 void LoadBuildMenu()
1119 {
1120   if(string_empty(g_buildMenu.c_str()) || !build_commands_parse(g_buildMenu.c_str()))
1121   {
1122     {
1123       StringOutputStream buffer(256);
1124       buffer << GameToolsPath_get() << "default_build_menu.xml";
1125
1126       bool success = build_commands_parse(buffer.c_str());
1127       ASSERT_MESSAGE(success, "failed to parse default build commands: " << buffer.c_str());
1128     }
1129     {
1130       StringOutputStream buffer(256);
1131       buffer << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/build_menu.xml";
1132
1133       g_buildMenu = buffer.c_str();
1134     }
1135   }
1136 }
1137
1138 void SaveBuildMenu()
1139 {
1140   if(g_build_changed)
1141   {
1142     g_build_changed = false;
1143     build_commands_write(g_buildMenu.c_str());
1144   }
1145 }
1146
1147 #include "preferencesystem.h"
1148 #include "stringio.h"
1149
1150 void BuildMenu_Construct()
1151 {
1152   GlobalPreferenceSystem().registerPreference("BuildMenu", CopiedStringImportStringCaller(g_buildMenu), CopiedStringExportStringCaller(g_buildMenu));
1153   LoadBuildMenu();
1154 }
1155 void BuildMenu_Destroy()
1156 {
1157   SaveBuildMenu();
1158 }