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