support for separators in build menu
[divverent/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 #define SEPARATOR_STRING "-"
341 static bool is_separator(const BuildPair &p)
342 {
343       if(!string_equal(p.first.c_str(), SEPARATOR_STRING))
344               return false;
345       for(Build::const_iterator j = p.second.begin(); j != p.second.end(); ++j)
346       {
347               if(!string_equal((*j).c_str(), ""))
348                       return false;
349       }
350       return true;
351 }
352
353
354 class BuildPairEqual
355 {
356   const char* m_name;
357 public:
358   BuildPairEqual(const char* name) : m_name(name)
359   {
360   }
361   bool operator()(const BuildPair& self) const
362   {
363     return string_equal(self.first.c_str(), m_name);
364   }
365 };
366
367 typedef std::list<BuildPair> Project;
368
369 Project::iterator Project_find(Project& project, const char* name)
370 {
371   return std::find_if(project.begin(), project.end(), BuildPairEqual(name));
372 }
373
374 Project::iterator Project_find(Project& project, std::size_t index)
375 {
376   Project::iterator i = project.begin();
377   while(index-- != 0 && i != project.end())
378   {
379     ++i;
380   }
381   return i;
382 }
383
384 Build& project_find(Project& project, const char* build)
385 {
386   Project::iterator i = Project_find(project, build);
387   ASSERT_MESSAGE(i != project.end(), "error finding build command");
388   return (*i).second;
389 }
390
391 Build::iterator Build_find(Build& build, std::size_t index)
392 {
393   Build::iterator i = build.begin();
394   while(index-- != 0 && i != build.end())
395   {
396     ++i;
397   }
398   return i;
399 }
400
401 typedef std::map<CopiedString, Tool> Tools;
402
403 class ProjectXMLConstructor : public XMLElementParser
404 {
405   ToolXMLConstructor* m_tool;
406   BuildXMLConstructor* m_build;
407   Project& m_project;
408   Tools& m_tools;
409 public:
410   ProjectXMLConstructor(Project& project, Tools& tools) : m_project(project), m_tools(tools)
411   {
412   }
413   std::size_t write(const char* buffer, std::size_t length)
414   {
415     return length;
416   }
417   XMLElementParser& pushElement(const XMLElement& element)
418   {
419     if(string_equal(element.name(), "var"))
420     {
421       Tools::iterator i = m_tools.insert(Tools::value_type(element.attribute("name"), Tool())).first;
422       m_tool = new ToolXMLConstructor((*i).second);
423       return *m_tool;
424     }
425     else if(string_equal(element.name(), "build"))
426     {
427       m_project.push_back(Project::value_type(element.attribute("name"), Build()));
428       m_build = new BuildXMLConstructor(m_project.back().second);
429       return *m_build;
430     }
431     else if(string_equal(element.name(), "separator"))
432     {
433       m_project.push_back(Project::value_type(SEPARATOR_STRING, Build()));
434       return *this;
435     }
436     else
437     {
438       ERROR_MESSAGE("parse error: invalid element");
439       return *this;
440     }
441   }
442   void popElement(const char* name)
443   {
444     if(string_equal(name, "var"))
445     {
446       delete m_tool;
447     }
448     else if(string_equal(name, "build"))
449     {
450       delete m_build;
451     }
452   }
453 };
454
455 class SkipAllParser : public XMLElementParser
456 {
457 public:
458   std::size_t write(const char* buffer, std::size_t length)
459   {
460     return length;
461   }
462   XMLElementParser& pushElement(const XMLElement& element)
463   {
464     return *this;
465   }
466   void popElement(const char* name)
467   {
468   }
469 };
470
471 class RootXMLConstructor : public XMLElementParser
472 {
473   CopiedString m_elementName;
474   XMLElementParser& m_parser;
475   SkipAllParser m_skip;
476   Version m_version;
477   bool m_compatible;
478 public:
479   RootXMLConstructor(const char* elementName, XMLElementParser& parser, const char* version) :
480     m_elementName(elementName),
481     m_parser(parser),
482     m_version(version_parse(version)),
483     m_compatible(false)
484   {
485   }
486   std::size_t write(const char* buffer, std::size_t length)
487   {
488     return length;
489   }
490   XMLElementParser& pushElement(const XMLElement& element)
491   {
492     if(string_equal(element.name(), m_elementName.c_str()))
493     {
494       Version dataVersion(version_parse(element.attribute("version")));
495       if(version_compatible(m_version, dataVersion))
496       {
497         m_compatible = true;
498         return m_parser;
499       }
500       else
501       {
502         return m_skip;
503       }
504     }
505     else
506     {
507       //ERROR_MESSAGE("parse error: invalid element \"" << element.name() << "\"");
508       return *this;
509     }
510   }
511   void popElement(const char* name)
512   {
513   }
514
515   bool versionCompatible() const
516   {
517     return m_compatible;
518   }
519 };
520
521 namespace
522 {
523   Project g_build_project;
524   Tools g_build_tools;
525   bool g_build_changed = false;
526 }
527
528 void build_error_undefined_tool(const char* build, const char* tool)
529 {
530   globalErrorStream() << "build " << makeQuoted(build) << " refers to undefined tool " << makeQuoted(tool) << '\n';
531 }
532
533 void project_verify(Project& project, Tools& tools)
534 {
535 #if 0
536   for(Project::iterator i = project.begin(); i != project.end(); ++i)
537   {
538     Build& build = (*i).second;
539     for(Build::iterator j = build.begin(); j != build.end(); ++j)
540     {
541       Tools::iterator k = tools.find((*j).first);
542       if(k == g_build_tools.end())
543       {
544         build_error_undefined_tool((*i).first.c_str(), (*j).first.c_str());
545       }
546     }
547   }
548 #endif
549 }
550
551 void build_run(const char* name, CommandListener& listener)
552 {
553   for(Tools::iterator i = g_build_tools.begin(); i != g_build_tools.end(); ++i)
554   {
555     StringBuffer output;
556     (*i).second.evaluate(output);
557     build_set_variable((*i).first.c_str(), output.c_str());
558   }
559
560   {
561     Project::iterator i = Project_find(g_build_project, name);
562     if(i != g_build_project.end())
563     {
564       Build& build = (*i).second;
565       for(Build::iterator j = build.begin(); j != build.end(); ++j)
566       {
567         StringBuffer output;
568         (*j).evaluate(output);
569         listener.execute(output.c_str());
570       }
571     }
572     else
573     {
574       globalErrorStream() << "build " << makeQuoted(name) << " not defined";
575     }
576   }
577 }
578
579
580 typedef std::vector<XMLElementParser*> XMLElementStack;
581
582 class XMLParser : public XMLImporter
583 {
584   XMLElementStack m_stack;
585 public:
586   XMLParser(XMLElementParser& parser)
587   {
588     m_stack.push_back(&parser);
589   }
590   std::size_t write(const char* buffer, std::size_t length)
591   {
592     return m_stack.back()->write(buffer, length);
593   }
594   void pushElement(const XMLElement& element)
595   {
596     m_stack.push_back(&m_stack.back()->pushElement(element));
597   }
598   void popElement(const char* name)
599   {
600     m_stack.pop_back();
601     m_stack.back()->popElement(name);
602   }
603 };
604
605 #include "stream/textfilestream.h"
606 #include "xml/xmlparser.h"
607
608 const char* const BUILDMENU_VERSION = "2.0";
609
610 bool build_commands_parse(const char* filename)
611 {
612   TextFileInputStream projectFile(filename);
613   if(!projectFile.failed())
614   {
615     ProjectXMLConstructor projectConstructor(g_build_project, g_build_tools);
616     RootXMLConstructor rootConstructor("project", projectConstructor, BUILDMENU_VERSION);
617     XMLParser importer(rootConstructor);
618     XMLStreamParser parser(projectFile);
619     parser.exportXML(importer);
620
621     if(rootConstructor.versionCompatible())
622     {
623       project_verify(g_build_project, g_build_tools);
624
625       return true;
626     }
627     globalErrorStream() << "failed to parse build menu: " << makeQuoted(filename) << "\n";
628   }
629   return false;
630 }
631
632 void build_commands_clear()
633 {
634   g_build_project.clear();
635   g_build_tools.clear();
636 }
637
638 class BuildXMLExporter
639 {
640   Build& m_build;
641 public:
642   BuildXMLExporter(Build& build) : m_build(build)
643   {
644   }
645   void exportXML(XMLImporter& importer)
646   {
647     importer << "\n";
648     for(Build::iterator i = m_build.begin(); i != m_build.end(); ++i)
649     {
650       StaticElement commandElement("command");
651       importer.pushElement(commandElement);
652       (*i).exportXML(importer);
653       importer.popElement(commandElement.name());
654       importer << "\n";
655     }
656   }
657 };
658
659 class ProjectXMLExporter
660 {
661   Project& m_project;
662   Tools& m_tools;
663 public:
664   ProjectXMLExporter(Project& project, Tools& tools) : m_project(project), m_tools(tools)
665   {
666   }
667   void exportXML(XMLImporter& importer)
668   {
669     StaticElement projectElement("project");
670     projectElement.insertAttribute("version", BUILDMENU_VERSION);
671     importer.pushElement(projectElement);
672     importer << "\n";
673
674     for(Tools::iterator i = m_tools.begin(); i != m_tools.end(); ++i)
675     {
676       StaticElement toolElement("var");
677       toolElement.insertAttribute("name", (*i).first.c_str());
678       importer.pushElement(toolElement);
679       (*i).second.exportXML(importer);
680       importer.popElement(toolElement.name());
681       importer << "\n";
682     }
683     for(Project::iterator i = m_project.begin(); i != m_project.end(); ++i)
684     {
685       if(is_separator(*i))
686       {
687         StaticElement buildElement("separator");
688         importer.pushElement(buildElement);
689         importer.popElement(buildElement.name());
690         importer << "\n";
691       }
692       else
693       {
694         StaticElement buildElement("build");
695         buildElement.insertAttribute("name", (*i).first.c_str());
696         importer.pushElement(buildElement);
697         BuildXMLExporter buildExporter((*i).second);
698         buildExporter.exportXML(importer);
699         importer.popElement(buildElement.name());
700         importer << "\n";
701       }
702     }
703     importer.popElement(projectElement.name());
704   }
705 };
706
707 #include "xml/xmlwriter.h"
708
709 void build_commands_write(const char* filename)
710 {
711   TextFileOutputStream projectFile(filename);
712   if(!projectFile.failed())
713   {
714     XMLStreamWriter writer(projectFile);
715     ProjectXMLExporter projectExporter(g_build_project, g_build_tools);
716     writer << "\n";
717     projectExporter.exportXML(writer);
718     writer << "\n";
719   }
720 }
721
722
723 #include <gdk/gdkkeysyms.h>
724 #include <gtk/gtkmain.h>
725 #include <gtk/gtkbox.h>
726 #include <gtk/gtktable.h>
727 #include <gtk/gtktreeview.h>
728 #include <gtk/gtkcellrenderertext.h>
729 #include <gtk/gtktreeselection.h>
730 #include <gtk/gtkliststore.h>
731 #include <gtk/gtkscrolledwindow.h>
732
733 #include "gtkutil/dialog.h"
734 #include "gtkutil/closure.h"
735 #include "gtkutil/window.h"
736 #include "gtkdlgs.h"
737
738 void Build_refreshMenu(GtkMenu* menu);
739
740
741 void BSPCommandList_Construct(GtkListStore* store, Project& project)
742 {
743   gtk_list_store_clear(store);
744
745   for(Project::iterator i = project.begin(); i != project.end(); ++i)
746   {
747     const char* buildName = (*i).first.c_str();
748
749     GtkTreeIter buildIter;
750     gtk_list_store_append(store, &buildIter);
751     gtk_list_store_set(store, &buildIter, 0, const_cast<char*>(buildName), -1);
752   }
753
754   GtkTreeIter lastIter;
755   gtk_list_store_append(store, &lastIter);
756 }
757
758 class ProjectList
759 {
760 public:
761   Project& m_project;
762   GtkListStore* m_store;
763   bool m_changed;
764   ProjectList(Project& project) : m_project(project), m_changed(false)
765   {
766   }
767 };
768
769 gboolean project_cell_edited(GtkCellRendererText* cell, gchar* path_string, gchar* new_text, ProjectList* projectList)
770 {
771   Project& project = projectList->m_project;
772
773   GtkTreePath* path = gtk_tree_path_new_from_string(path_string);
774
775   ASSERT_MESSAGE(gtk_tree_path_get_depth(path) == 1, "invalid path length");
776
777   GtkTreeIter iter;
778   gtk_tree_model_get_iter(GTK_TREE_MODEL(projectList->m_store), &iter, path);
779
780   Project::iterator i = Project_find(project, gtk_tree_path_get_indices(path)[0]);
781   if(i != project.end())
782   {
783     projectList->m_changed = true;
784     if(string_empty(new_text))
785     {
786       project.erase(i);
787       gtk_list_store_remove(projectList->m_store, &iter);
788     }
789     else
790     {
791       (*i).first = new_text;
792       gtk_list_store_set(projectList->m_store, &iter, 0, new_text, -1);
793     }
794   }
795   else if(!string_empty(new_text))
796   {
797     projectList->m_changed = true;
798     project.push_back(Project::value_type(new_text, Build()));
799
800     gtk_list_store_set(projectList->m_store, &iter, 0, new_text, -1);
801     GtkTreeIter lastIter;
802     gtk_list_store_append(projectList->m_store, &lastIter);
803   }
804
805   gtk_tree_path_free(path);
806
807   Build_refreshMenu(g_bsp_menu);
808
809   return FALSE;
810 }
811
812 gboolean project_key_press(GtkWidget* widget, GdkEventKey* event, ProjectList* projectList)
813 {
814   Project& project = projectList->m_project;
815
816   if(event->keyval == GDK_Delete)
817   {
818     GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
819     GtkTreeIter iter;
820     GtkTreeModel* model;
821     if(gtk_tree_selection_get_selected(selection, &model, &iter))
822     {
823       GtkTreePath* path = gtk_tree_model_get_path(model, &iter);
824       Project::iterator x = Project_find(project, gtk_tree_path_get_indices(path)[0]);
825       gtk_tree_path_free(path);
826
827       if(x != project.end())
828       {
829         projectList->m_changed = true;
830         project.erase(x);
831         Build_refreshMenu(g_bsp_menu);
832
833         gtk_list_store_remove(projectList->m_store, &iter);
834       }
835     }
836   }
837   return FALSE;
838 }
839
840
841 Build* g_current_build = 0;
842
843 gboolean project_selection_changed(GtkTreeSelection* selection, GtkListStore* store)
844 {
845   Project& project = g_build_project;
846
847   gtk_list_store_clear(store);
848
849   GtkTreeIter iter;
850   GtkTreeModel* model;
851   if(gtk_tree_selection_get_selected(selection, &model, &iter))
852   {
853     GtkTreePath* path = gtk_tree_model_get_path(model, &iter);
854     Project::iterator x = Project_find(project, gtk_tree_path_get_indices(path)[0]);
855     gtk_tree_path_free(path);
856
857     if(x != project.end())
858     {
859       Build& build = (*x).second;
860       g_current_build = &build;
861
862       for(Build::iterator i = build.begin(); i != build.end(); ++i)
863       {
864         GtkTreeIter commandIter;
865         gtk_list_store_append(store, &commandIter);
866         gtk_list_store_set(store, &commandIter, 0, const_cast<char*>((*i).c_str()), -1);
867       }
868       GtkTreeIter lastIter;
869       gtk_list_store_append(store, &lastIter);
870     }
871     else
872     {
873       g_current_build = 0;
874     }
875   }
876   else
877   {
878     g_current_build = 0;
879   }
880
881   return FALSE;
882 }
883
884 gboolean commands_cell_edited(GtkCellRendererText* cell, gchar* path_string, gchar* new_text, GtkListStore* store)
885 {
886   if(g_current_build == 0)
887   {
888     return FALSE;
889   }
890   Build& build = *g_current_build;
891
892   GtkTreePath* path = gtk_tree_path_new_from_string(path_string);
893
894   ASSERT_MESSAGE(gtk_tree_path_get_depth(path) == 1, "invalid path length");
895
896   GtkTreeIter iter;
897   gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path);
898
899   Build::iterator i = Build_find(build, gtk_tree_path_get_indices(path)[0]);
900   if(i != build.end())
901   {
902     g_build_changed = true;
903     (*i).setString(new_text);
904
905     gtk_list_store_set(store, &iter, 0, new_text, -1);
906   }
907   else if(!string_empty(new_text))
908   {
909     g_build_changed = true;
910     build.push_back(Build::value_type(VariableString(new_text)));
911
912     gtk_list_store_set(store, &iter, 0, new_text, -1);
913
914     GtkTreeIter lastIter;
915     gtk_list_store_append(store, &lastIter);
916   }
917
918   gtk_tree_path_free(path);
919
920   Build_refreshMenu(g_bsp_menu);
921
922   return FALSE;
923 }
924
925 gboolean commands_key_press(GtkWidget* widget, GdkEventKey* event, GtkListStore* store)
926 {
927   if(g_current_build == 0)
928   {
929     return FALSE;
930   }
931   Build& build = *g_current_build;
932
933   if(event->keyval == GDK_Delete)
934   {
935     GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
936     GtkTreeIter iter;
937     GtkTreeModel* model;
938     if(gtk_tree_selection_get_selected(selection, &model, &iter))
939     {
940       GtkTreePath* path = gtk_tree_model_get_path(model, &iter);
941       Build::iterator i = Build_find(build, gtk_tree_path_get_indices(path)[0]);
942       gtk_tree_path_free(path);
943
944       if(i != build.end())
945       {
946         g_build_changed = true;
947         build.erase(i);
948
949         gtk_list_store_remove(store, &iter);
950       }
951     }
952   }
953   return FALSE;
954 }
955
956
957 GtkWindow* BuildMenuDialog_construct(ModalDialog& modal, ProjectList& projectList)
958 {
959   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Build Menu", G_CALLBACK(dialog_delete_callback), &modal, -1, 400);
960
961   GtkWidget* buildView = 0;
962
963   {
964     GtkTable* table1 = create_dialog_table(2, 2, 4, 4, 4);
965     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(table1));
966     {
967       GtkVBox* vbox = create_dialog_vbox(4);
968       gtk_table_attach(table1, GTK_WIDGET(vbox), 1, 2, 0, 1,
969                         (GtkAttachOptions) (GTK_FILL),
970                         (GtkAttachOptions) (GTK_FILL), 0, 0);
971       {
972         GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &modal);
973         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
974       }
975       {
976         GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &modal);
977         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
978       }
979     }
980     {
981       GtkFrame* frame = create_dialog_frame("Build menu");
982       gtk_table_attach(table1, GTK_WIDGET(frame), 0, 1, 0, 1,
983                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
984                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
985       {
986         GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4);
987         gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(scr));
988
989         {
990           GtkListStore* store = gtk_list_store_new(1, G_TYPE_STRING);
991
992           GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
993           gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
994
995           GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
996           object_set_boolean_property(G_OBJECT(renderer), "editable", TRUE);
997           g_signal_connect(renderer, "edited", G_CALLBACK(project_cell_edited), &projectList);
998  
999           GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, 0);
1000           gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
1001
1002           GtkTreeSelection* selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
1003           gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
1004
1005           gtk_widget_show(view);
1006
1007           buildView = view;
1008           projectList.m_store = store;
1009           gtk_container_add(GTK_CONTAINER (scr), view);
1010   
1011           g_signal_connect(G_OBJECT(view), "key_press_event", G_CALLBACK(project_key_press), &projectList);
1012
1013           g_object_unref(G_OBJECT(store));
1014         }
1015       }
1016     }
1017     {
1018       GtkFrame* frame = create_dialog_frame("Commandline");
1019       gtk_table_attach(table1, GTK_WIDGET(frame), 0, 1, 1, 2,
1020                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
1021                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
1022       {
1023         GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4);
1024         gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(scr));
1025
1026         {
1027           GtkListStore* store = gtk_list_store_new(1, G_TYPE_STRING);
1028
1029           GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
1030           gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
1031
1032           GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
1033           object_set_boolean_property(G_OBJECT(renderer), "editable", TRUE);
1034           g_signal_connect(renderer, "edited", G_CALLBACK(commands_cell_edited), store);
1035  
1036           GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, 0);
1037           gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
1038
1039           GtkTreeSelection* selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
1040           gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
1041
1042           gtk_widget_show(view);
1043
1044           gtk_container_add(GTK_CONTAINER (scr), view);
1045   
1046           g_object_unref(G_OBJECT(store));
1047
1048           g_signal_connect(G_OBJECT(view), "key_press_event", G_CALLBACK(commands_key_press), store);
1049          
1050           g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(buildView))), "changed", G_CALLBACK(project_selection_changed), store);
1051         }
1052       }
1053     }
1054   }
1055
1056   BSPCommandList_Construct(projectList.m_store, g_build_project);
1057
1058   return window;
1059 }
1060
1061 namespace
1062 {
1063   CopiedString g_buildMenu;
1064 }
1065
1066 void LoadBuildMenu();
1067
1068 void DoBuildMenu()
1069 {
1070   ModalDialog modal;
1071
1072   ProjectList projectList(g_build_project);
1073
1074   GtkWindow* window = BuildMenuDialog_construct(modal, projectList);
1075
1076   if(modal_dialog_show(window, modal) == eIDCANCEL)
1077   {
1078     build_commands_clear();
1079     LoadBuildMenu();
1080
1081     Build_refreshMenu(g_bsp_menu);
1082   }
1083   else if(projectList.m_changed)
1084   {
1085     g_build_changed = true;
1086   }
1087
1088   gtk_widget_destroy(GTK_WIDGET(window));
1089 }
1090
1091
1092
1093 #include "gtkutil/menu.h"
1094 #include "mainframe.h"
1095 #include "preferences.h"
1096 #include "qe3.h"
1097
1098 typedef struct _GtkMenuItem GtkMenuItem;
1099
1100 class BuildMenuItem
1101 {
1102   const char* m_name;
1103 public:
1104   GtkMenuItem* m_item;
1105   BuildMenuItem(const char* name, GtkMenuItem* item)
1106     : m_name(name), m_item(item)
1107   {
1108   }
1109   void run()
1110   {
1111     RunBSP(m_name);
1112   }
1113   typedef MemberCaller<BuildMenuItem, &BuildMenuItem::run> RunCaller;
1114 };
1115
1116 typedef std::list<BuildMenuItem> BuildMenuItems;
1117 BuildMenuItems g_BuildMenuItems;
1118
1119
1120 GtkMenu* g_bsp_menu;
1121
1122 void Build_constructMenu(GtkMenu* menu)
1123 {
1124   for(Project::iterator i = g_build_project.begin(); i != g_build_project.end(); ++i)
1125   {
1126     g_BuildMenuItems.push_back(BuildMenuItem((*i).first.c_str(), 0));
1127     if(is_separator(*i))
1128     {
1129       g_BuildMenuItems.back().m_item = menu_separator(menu);
1130     }
1131     else
1132     {
1133       g_BuildMenuItems.back().m_item = create_menu_item_with_mnemonic(menu, (*i).first.c_str(), BuildMenuItem::RunCaller(g_BuildMenuItems.back()));
1134     }
1135   }
1136 }
1137
1138
1139 void Build_refreshMenu(GtkMenu* menu)
1140 {
1141   for(BuildMenuItems::iterator i = g_BuildMenuItems.begin(); i != g_BuildMenuItems.end(); ++i)
1142   {
1143     gtk_container_remove(GTK_CONTAINER(menu), GTK_WIDGET((*i).m_item));
1144   }
1145
1146   g_BuildMenuItems.clear();
1147
1148   Build_constructMenu(menu);
1149 }
1150
1151
1152 void LoadBuildMenu()
1153 {
1154   if(string_empty(g_buildMenu.c_str()) || !build_commands_parse(g_buildMenu.c_str()))
1155   {
1156     {
1157       StringOutputStream buffer(256);
1158       buffer << GameToolsPath_get() << "default_build_menu.xml";
1159
1160       bool success = build_commands_parse(buffer.c_str());
1161       ASSERT_MESSAGE(success, "failed to parse default build commands: " << buffer.c_str());
1162     }
1163     {
1164       StringOutputStream buffer(256);
1165       buffer << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/build_menu.xml";
1166
1167       g_buildMenu = buffer.c_str();
1168     }
1169   }
1170 }
1171
1172 void SaveBuildMenu()
1173 {
1174   if(g_build_changed)
1175   {
1176     g_build_changed = false;
1177     build_commands_write(g_buildMenu.c_str());
1178   }
1179 }
1180
1181 #include "preferencesystem.h"
1182 #include "stringio.h"
1183
1184 void BuildMenu_Construct()
1185 {
1186   GlobalPreferenceSystem().registerPreference("BuildMenu", CopiedStringImportStringCaller(g_buildMenu), CopiedStringExportStringCaller(g_buildMenu));
1187   LoadBuildMenu();
1188 }
1189 void BuildMenu_Destroy()
1190 {
1191   SaveBuildMenu();
1192 }