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