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