]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/build.cpp
Wrap GtkTreeView
[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/gtkmain.h>
638 #include <gtk/gtkbox.h>
639 #include <gtk/gtktable.h>
640 #include <gtk/gtktreeview.h>
641 #include <gtk/gtkcellrenderertext.h>
642 #include <gtk/gtktreeselection.h>
643 #include <gtk/gtkliststore.h>
644 #include <gtk/gtkscrolledwindow.h>
645
646 #include "gtkutil/dialog.h"
647 #include "gtkutil/closure.h"
648 #include "gtkutil/window.h"
649 #include "gtkdlgs.h"
650
651 void Build_refreshMenu( GtkMenu* menu );
652
653
654 void BSPCommandList_Construct( GtkListStore* store, Project& project ){
655         gtk_list_store_clear( store );
656
657         for ( Project::iterator i = project.begin(); i != project.end(); ++i )
658         {
659                 const char* buildName = ( *i ).first.c_str();
660
661                 GtkTreeIter buildIter;
662                 gtk_list_store_append( store, &buildIter );
663                 gtk_list_store_set( store, &buildIter, 0, const_cast<char*>( buildName ), -1 );
664         }
665
666         GtkTreeIter lastIter;
667         gtk_list_store_append( store, &lastIter );
668 }
669
670 class ProjectList
671 {
672 public:
673 Project& m_project;
674 GtkListStore* m_store;
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         }
711
712         gtk_tree_path_free( path );
713
714         Build_refreshMenu( g_bsp_menu );
715
716         return FALSE;
717 }
718
719 gboolean project_key_press( ui::Widget widget, GdkEventKey* event, ProjectList* projectList ){
720         Project& project = projectList->m_project;
721
722         if ( event->keyval == GDK_Delete ) {
723                 GtkTreeSelection* selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
724                 GtkTreeIter iter;
725                 GtkTreeModel* model;
726                 if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) {
727                         GtkTreePath* path = gtk_tree_model_get_path( model, &iter );
728                         Project::iterator x = Project_find( project, gtk_tree_path_get_indices( path )[0] );
729                         gtk_tree_path_free( path );
730
731                         if ( x != project.end() ) {
732                                 projectList->m_changed = true;
733                                 project.erase( x );
734                                 Build_refreshMenu( g_bsp_menu );
735
736                                 gtk_list_store_remove( projectList->m_store, &iter );
737                         }
738                 }
739         }
740         return FALSE;
741 }
742
743
744 Build* g_current_build = 0;
745
746 gboolean project_selection_changed( GtkTreeSelection* selection, GtkListStore* store ){
747         Project& project = g_build_project;
748
749         gtk_list_store_clear( store );
750
751         GtkTreeIter iter;
752         GtkTreeModel* model;
753         if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) {
754                 GtkTreePath* path = gtk_tree_model_get_path( model, &iter );
755                 Project::iterator x = Project_find( project, gtk_tree_path_get_indices( path )[0] );
756                 gtk_tree_path_free( path );
757
758                 if ( x != project.end() ) {
759                         Build& build = ( *x ).second;
760                         g_current_build = &build;
761
762                         for ( Build::iterator i = build.begin(); i != build.end(); ++i )
763                         {
764                                 GtkTreeIter commandIter;
765                                 gtk_list_store_append( store, &commandIter );
766                                 gtk_list_store_set( store, &commandIter, 0, const_cast<char*>( ( *i ).c_str() ), -1 );
767                         }
768                         GtkTreeIter lastIter;
769                         gtk_list_store_append( store, &lastIter );
770                 }
771                 else
772                 {
773                         g_current_build = 0;
774                 }
775         }
776         else
777         {
778                 g_current_build = 0;
779         }
780
781         return FALSE;
782 }
783
784 gboolean commands_cell_edited( GtkCellRendererText* cell, gchar* path_string, gchar* new_text, GtkListStore* store ){
785         if ( g_current_build == 0 ) {
786                 return FALSE;
787         }
788         Build& build = *g_current_build;
789
790         GtkTreePath* path = gtk_tree_path_new_from_string( path_string );
791
792         ASSERT_MESSAGE( gtk_tree_path_get_depth( path ) == 1, "invalid path length" );
793
794         GtkTreeIter iter;
795         gtk_tree_model_get_iter( GTK_TREE_MODEL( store ), &iter, path );
796
797         Build::iterator i = Build_find( build, gtk_tree_path_get_indices( path )[0] );
798         if ( i != build.end() ) {
799                 g_build_changed = true;
800                 ( *i ).setString( new_text );
801
802                 gtk_list_store_set( store, &iter, 0, new_text, -1 );
803         }
804         else if ( !string_empty( new_text ) ) {
805                 g_build_changed = true;
806                 build.push_back( Build::value_type( VariableString( new_text ) ) );
807
808                 gtk_list_store_set( store, &iter, 0, new_text, -1 );
809
810                 GtkTreeIter lastIter;
811                 gtk_list_store_append( store, &lastIter );
812         }
813
814         gtk_tree_path_free( path );
815
816         Build_refreshMenu( g_bsp_menu );
817
818         return FALSE;
819 }
820
821 gboolean commands_key_press( ui::Widget widget, GdkEventKey* event, GtkListStore* store ){
822         if ( g_current_build == 0 ) {
823                 return FALSE;
824         }
825         Build& build = *g_current_build;
826
827         if ( event->keyval == GDK_Delete ) {
828                 GtkTreeSelection* selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
829                 GtkTreeIter iter;
830                 GtkTreeModel* model;
831                 if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) {
832                         GtkTreePath* path = gtk_tree_model_get_path( model, &iter );
833                         Build::iterator i = Build_find( build, gtk_tree_path_get_indices( path )[0] );
834                         gtk_tree_path_free( path );
835
836                         if ( i != build.end() ) {
837                                 g_build_changed = true;
838                                 build.erase( i );
839
840                                 gtk_list_store_remove( store, &iter );
841                         }
842                 }
843         }
844         return FALSE;
845 }
846
847
848 ui::Window BuildMenuDialog_construct( ModalDialog& modal, ProjectList& projectList ){
849         ui::Window window = MainFrame_getWindow().create_dialog_window("Build Menu", G_CALLBACK(dialog_delete_callback ), &modal, -1, 400 );
850
851         ui::Widget buildView;
852
853         {
854                 GtkTable* table1 = create_dialog_table( 2, 2, 4, 4, 4 );
855                 gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( table1 ) );
856                 {
857                         GtkVBox* vbox = create_dialog_vbox( 4 );
858                         gtk_table_attach( table1, GTK_WIDGET( vbox ), 1, 2, 0, 1,
859                                                           (GtkAttachOptions) ( GTK_FILL ),
860                                                           (GtkAttachOptions) ( GTK_FILL ), 0, 0 );
861                         {
862                                 GtkButton* button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
863                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
864                         }
865                         {
866                                 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
867                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
868                         }
869                 }
870                 {
871                         GtkFrame* frame = create_dialog_frame( "Build menu" );
872                         gtk_table_attach( table1, GTK_WIDGET( frame ), 0, 1, 0, 1,
873                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
874                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), 0, 0 );
875                         {
876                                 GtkScrolledWindow* scr = create_scrolled_window( GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4 );
877                                 gtk_container_add( GTK_CONTAINER( frame ), GTK_WIDGET( scr ) );
878
879                                 {
880                                         GtkListStore* store = gtk_list_store_new( 1, G_TYPE_STRING );
881
882                                         ui::Widget view = ui::TreeView( ui::TreeModel(GTK_TREE_MODEL( store ) ));
883                                         gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( view ), FALSE );
884
885                                         GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
886                                         object_set_boolean_property( G_OBJECT( renderer ), "editable", TRUE );
887                                         g_signal_connect( renderer, "edited", G_CALLBACK( project_cell_edited ), &projectList );
888
889                                         GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes( "", renderer, "text", 0, 0 );
890                                         gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
891
892                                         GtkTreeSelection* selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
893                                         gtk_tree_selection_set_mode( selection, GTK_SELECTION_BROWSE );
894
895                                         gtk_widget_show( view );
896
897                                         buildView = view;
898                                         projectList.m_store = store;
899                                         gtk_container_add( GTK_CONTAINER( scr ), view );
900
901                                         g_signal_connect( G_OBJECT( view ), "key_press_event", G_CALLBACK( project_key_press ), &projectList );
902
903                                         g_object_unref( G_OBJECT( store ) );
904                                 }
905                         }
906                 }
907                 {
908                         GtkFrame* frame = create_dialog_frame( "Commandline" );
909                         gtk_table_attach( table1, GTK_WIDGET( frame ), 0, 1, 1, 2,
910                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
911                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), 0, 0 );
912                         {
913                                 GtkScrolledWindow* scr = create_scrolled_window( GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4 );
914                                 gtk_container_add( GTK_CONTAINER( frame ), GTK_WIDGET( scr ) );
915
916                                 {
917                                         GtkListStore* store = gtk_list_store_new( 1, G_TYPE_STRING );
918
919                                         ui::Widget view = ui::TreeView(ui::TreeModel( GTK_TREE_MODEL( store ) ));
920                                         gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( view ), FALSE );
921
922                                         GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
923                                         object_set_boolean_property( G_OBJECT( renderer ), "editable", TRUE );
924                                         g_signal_connect( renderer, "edited", G_CALLBACK( commands_cell_edited ), store );
925
926                                         GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes( "", renderer, "text", 0, 0 );
927                                         gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
928
929                                         GtkTreeSelection* selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
930                                         gtk_tree_selection_set_mode( selection, GTK_SELECTION_BROWSE );
931
932                                         gtk_widget_show( view );
933
934                                         gtk_container_add( GTK_CONTAINER( scr ), view );
935
936                                         g_object_unref( G_OBJECT( store ) );
937
938                                         g_signal_connect( G_OBJECT( view ), "key_press_event", G_CALLBACK( commands_key_press ), store );
939
940                                         g_signal_connect( G_OBJECT( gtk_tree_view_get_selection( GTK_TREE_VIEW( buildView ) ) ), "changed", G_CALLBACK( project_selection_changed ), store );
941                                 }
942                         }
943                 }
944         }
945
946         BSPCommandList_Construct( projectList.m_store, g_build_project );
947
948         return window;
949 }
950
951 namespace
952 {
953 std::string g_buildMenu;
954 }
955
956 void LoadBuildMenu();
957
958 void DoBuildMenu(){
959         ModalDialog modal;
960
961         ProjectList projectList( g_build_project );
962
963         ui::Window window = BuildMenuDialog_construct( modal, projectList );
964
965         if ( modal_dialog_show( window, modal ) == eIDCANCEL ) {
966                 build_commands_clear();
967                 LoadBuildMenu();
968
969                 Build_refreshMenu( g_bsp_menu );
970         }
971         else if ( projectList.m_changed ) {
972                 g_build_changed = true;
973         }
974
975         gtk_widget_destroy( GTK_WIDGET( window ) );
976 }
977
978
979
980 #include "gtkutil/menu.h"
981 #include "mainframe.h"
982 #include "preferences.h"
983 #include "qe3.h"
984
985 typedef struct _GtkMenuItem GtkMenuItem;
986
987 class BuildMenuItem
988 {
989 const char* m_name;
990 public:
991 GtkMenuItem* m_item;
992 BuildMenuItem( const char* name, GtkMenuItem* item )
993         : m_name( name ), m_item( item ){
994 }
995 void run(){
996         RunBSP( m_name );
997 }
998 typedef MemberCaller<BuildMenuItem, &BuildMenuItem::run> RunCaller;
999 };
1000
1001 typedef std::list<BuildMenuItem> BuildMenuItems;
1002 BuildMenuItems g_BuildMenuItems;
1003
1004
1005 GtkMenu* g_bsp_menu;
1006
1007 void Build_constructMenu( GtkMenu* menu ){
1008         for ( Project::iterator i = g_build_project.begin(); i != g_build_project.end(); ++i )
1009         {
1010                 g_BuildMenuItems.push_back( BuildMenuItem( ( *i ).first.c_str(), 0 ) );
1011                 if ( is_separator( *i ) ) {
1012                         g_BuildMenuItems.back().m_item = menu_separator( menu );
1013                 }
1014                 else
1015                 {
1016                         g_BuildMenuItems.back().m_item = create_menu_item_with_mnemonic( menu, ( *i ).first.c_str(), BuildMenuItem::RunCaller( g_BuildMenuItems.back() ) );
1017                 }
1018         }
1019 }
1020
1021
1022 void Build_refreshMenu( GtkMenu* menu ){
1023         for ( BuildMenuItems::iterator i = g_BuildMenuItems.begin(); i != g_BuildMenuItems.end(); ++i )
1024         {
1025                 gtk_container_remove( GTK_CONTAINER( menu ), GTK_WIDGET( ( *i ).m_item ) );
1026         }
1027
1028         g_BuildMenuItems.clear();
1029
1030         Build_constructMenu( menu );
1031 }
1032
1033
1034 void LoadBuildMenu(){
1035         if ( string_empty( g_buildMenu.c_str() ) || !build_commands_parse( g_buildMenu.c_str() ) ) {
1036                 {
1037                         StringOutputStream buffer( 256 );
1038                         buffer << GameToolsPath_get() << "default_build_menu.xml";
1039
1040                         bool success = build_commands_parse( buffer.c_str() );
1041                         ASSERT_MESSAGE( success, "failed to parse default build commands: " << buffer.c_str() );
1042                 }
1043                 {
1044                         StringOutputStream buffer( 256 );
1045                         buffer << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/build_menu.xml";
1046
1047                         g_buildMenu = buffer.c_str();
1048                 }
1049         }
1050 }
1051
1052 void SaveBuildMenu(){
1053         if ( g_build_changed ) {
1054                 g_build_changed = false;
1055                 build_commands_write( g_buildMenu.c_str() );
1056         }
1057 }
1058
1059 #include "preferencesystem.h"
1060 #include "stringio.h"
1061
1062 void BuildMenu_Construct(){
1063         GlobalPreferenceSystem().registerPreference( "BuildMenu", CopiedStringImportStringCaller( g_buildMenu ), CopiedStringExportStringCaller( g_buildMenu ) );
1064         LoadBuildMenu();
1065 }
1066 void BuildMenu_Destroy(){
1067         SaveBuildMenu();
1068 }