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