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