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