]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/build.cpp
7d9d630002f0249760481f76630d0870c9b0849c
[xonotic/netradiant.git] / radiant / build.cpp
1 /*
2    Copyright (C) 2001-2006, William Joseph.
3    All Rights Reserved.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #include "build.h"
23 #include "debugging/debugging.h"
24
25 #include <gtk/gtk.h>
26 #include <map>
27 #include <list>
28 #include "stream/stringstream.h"
29 #include "versionlib.h"
30
31 #include "mainframe.h"
32
33 typedef std::map<CopiedString, CopiedString> Variables;
34 Variables g_build_variables;
35
36 void build_clear_variables(){
37         g_build_variables.clear();
38 }
39
40 void build_set_variable( const char* name, const char* value ){
41         g_build_variables[name] = value;
42 }
43
44 const char* build_get_variable( const char* name ){
45         Variables::iterator i = g_build_variables.find( name );
46         if ( i != g_build_variables.end() ) {
47                 return ( *i ).second.c_str();
48         }
49         globalErrorStream() << "undefined build variable: " << makeQuoted( name ) << "\n";
50         return "";
51 }
52
53 #include "xml/ixml.h"
54 #include "xml/xmlelement.h"
55
56 class Evaluatable
57 {
58 public:
59 virtual void evaluate( StringBuffer& output ) = 0;
60 virtual void exportXML( XMLImporter& importer ) = 0;
61 };
62
63 class VariableString : public Evaluatable
64 {
65 CopiedString 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<CopiedString, 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<CopiedString, 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 CopiedString 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
638 #include "gtkutil/dialog.h"
639 #include "gtkutil/closure.h"
640 #include "gtkutil/window.h"
641 #include "gtkdlgs.h"
642
643 void Build_refreshMenu( ui::Menu menu );
644
645
646 void BSPCommandList_Construct( ui::ListStore store, Project& project ){
647         store.clear();
648
649         for ( Project::iterator i = project.begin(); i != project.end(); ++i )
650         {
651                 const char* buildName = ( *i ).first.c_str();
652
653                 GtkTreeIter buildIter;
654                 gtk_list_store_append( store, &buildIter );
655                 gtk_list_store_set( store, &buildIter, 0, const_cast<char*>( buildName ), -1 );
656         }
657
658         GtkTreeIter lastIter;
659         gtk_list_store_append( store, &lastIter );
660 }
661
662 class ProjectList
663 {
664 public:
665 Project& m_project;
666 ui::ListStore m_store{nullptr};
667 bool m_changed;
668 ProjectList( Project& project ) : m_project( project ), m_changed( false ){
669 }
670 };
671
672 gboolean project_cell_edited( GtkCellRendererText* cell, gchar* path_string, gchar* new_text, ProjectList* projectList ){
673         Project& project = projectList->m_project;
674
675         GtkTreePath* path = ui::TreePath( path_string );
676
677         ASSERT_MESSAGE( gtk_tree_path_get_depth( path ) == 1, "invalid path length" );
678
679         GtkTreeIter iter;
680         gtk_tree_model_get_iter( GTK_TREE_MODEL( projectList->m_store ), &iter, path );
681
682         Project::iterator i = Project_find( project, gtk_tree_path_get_indices( path )[0] );
683         if ( i != project.end() ) {
684                 projectList->m_changed = true;
685                 if ( string_empty( new_text ) ) {
686                         project.erase( i );
687                         gtk_list_store_remove( projectList->m_store, &iter );
688                 }
689                 else
690                 {
691                         ( *i ).first = new_text;
692                         gtk_list_store_set( projectList->m_store, &iter, 0, new_text, -1 );
693                 }
694         }
695         else if ( !string_empty( new_text ) ) {
696                 projectList->m_changed = true;
697                 project.push_back( Project::value_type( new_text, Build() ) );
698
699                 gtk_list_store_set( projectList->m_store, &iter, 0, new_text, -1 );
700                 GtkTreeIter lastIter;
701                 gtk_list_store_append( projectList->m_store, &lastIter );
702         }
703
704         gtk_tree_path_free( path );
705
706         Build_refreshMenu( g_bsp_menu );
707
708         return FALSE;
709 }
710
711 gboolean project_key_press( ui::Widget widget, GdkEventKey* event, ProjectList* projectList ){
712         Project& project = projectList->m_project;
713
714         if ( event->keyval == GDK_Delete ) {
715                 GtkTreeSelection* selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
716                 GtkTreeIter iter;
717                 GtkTreeModel* model;
718                 if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) {
719                         GtkTreePath* path = gtk_tree_model_get_path( model, &iter );
720                         Project::iterator x = Project_find( project, gtk_tree_path_get_indices( path )[0] );
721                         gtk_tree_path_free( path );
722
723                         if ( x != project.end() ) {
724                                 projectList->m_changed = true;
725                                 project.erase( x );
726                                 Build_refreshMenu( g_bsp_menu );
727
728                                 gtk_list_store_remove( projectList->m_store, &iter );
729                         }
730                 }
731         }
732         return FALSE;
733 }
734
735
736 Build* g_current_build = 0;
737
738 gboolean project_selection_changed( GtkTreeSelection* selection, ui::ListStore store ){
739         Project& project = g_build_project;
740
741         gtk_list_store_clear( store );
742
743         GtkTreeIter iter;
744         GtkTreeModel* model;
745         if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) {
746                 GtkTreePath* path = gtk_tree_model_get_path( model, &iter );
747                 Project::iterator x = Project_find( project, gtk_tree_path_get_indices( path )[0] );
748                 gtk_tree_path_free( path );
749
750                 if ( x != project.end() ) {
751                         Build& build = ( *x ).second;
752                         g_current_build = &build;
753
754                         for ( Build::iterator i = build.begin(); i != build.end(); ++i )
755                         {
756                                 GtkTreeIter commandIter;
757                                 gtk_list_store_append( store, &commandIter );
758                                 gtk_list_store_set( store, &commandIter, 0, const_cast<char*>( ( *i ).c_str() ), -1 );
759                         }
760                         GtkTreeIter lastIter;
761                         gtk_list_store_append( store, &lastIter );
762                 }
763                 else
764                 {
765                         g_current_build = 0;
766                 }
767         }
768         else
769         {
770                 g_current_build = 0;
771         }
772
773         return FALSE;
774 }
775
776 gboolean commands_cell_edited( GtkCellRendererText* cell, gchar* path_string, gchar* new_text, ui::ListStore store ){
777         if ( g_current_build == 0 ) {
778                 return FALSE;
779         }
780         Build& build = *g_current_build;
781
782         GtkTreePath* path = ui::TreePath( path_string );
783
784         ASSERT_MESSAGE( gtk_tree_path_get_depth( path ) == 1, "invalid path length" );
785
786         GtkTreeIter iter;
787         gtk_tree_model_get_iter( GTK_TREE_MODEL( store ), &iter, path );
788
789         Build::iterator i = Build_find( build, gtk_tree_path_get_indices( path )[0] );
790         if ( i != build.end() ) {
791                 g_build_changed = true;
792                 ( *i ).setString( new_text );
793
794                 gtk_list_store_set( store, &iter, 0, new_text, -1 );
795         }
796         else if ( !string_empty( new_text ) ) {
797                 g_build_changed = true;
798                 build.push_back( Build::value_type( VariableString( new_text ) ) );
799
800                 gtk_list_store_set( store, &iter, 0, new_text, -1 );
801
802                 GtkTreeIter lastIter;
803                 gtk_list_store_append( store, &lastIter );
804         }
805
806         gtk_tree_path_free( path );
807
808         Build_refreshMenu( g_bsp_menu );
809
810         return FALSE;
811 }
812
813 gboolean commands_key_press( ui::Widget widget, GdkEventKey* event, ui::ListStore store ){
814         if ( g_current_build == 0 ) {
815                 return FALSE;
816         }
817         Build& build = *g_current_build;
818
819         if ( event->keyval == GDK_Delete ) {
820                 GtkTreeSelection* selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( widget ) );
821                 GtkTreeIter iter;
822                 GtkTreeModel* model;
823                 if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) {
824                         GtkTreePath* path = gtk_tree_model_get_path( model, &iter );
825                         Build::iterator i = Build_find( build, gtk_tree_path_get_indices( path )[0] );
826                         gtk_tree_path_free( path );
827
828                         if ( i != build.end() ) {
829                                 g_build_changed = true;
830                                 build.erase( i );
831
832                                 gtk_list_store_remove( store, &iter );
833                         }
834                 }
835         }
836         return FALSE;
837 }
838
839
840 ui::Window BuildMenuDialog_construct( ModalDialog& modal, ProjectList& projectList ){
841         ui::Window window = MainFrame_getWindow().create_dialog_window("Build Menu", G_CALLBACK(dialog_delete_callback ), &modal, -1, 400 );
842
843         ui::Widget buildView;
844
845         {
846                 auto table1 = create_dialog_table( 2, 2, 4, 4, 4 );
847                 window.add(table1);
848                 {
849                         GtkVBox* vbox = create_dialog_vbox( 4 );
850                         gtk_table_attach( table1, GTK_WIDGET( vbox ), 1, 2, 0, 1,
851                                                           (GtkAttachOptions) ( GTK_FILL ),
852                                                           (GtkAttachOptions) ( GTK_FILL ), 0, 0 );
853                         {
854                                 GtkButton* button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
855                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
856                         }
857                         {
858                                 GtkButton* button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
859                                 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
860                         }
861                 }
862                 {
863                         auto frame = create_dialog_frame( "Build menu" );
864                         gtk_table_attach( table1, GTK_WIDGET( frame ), 0, 1, 0, 1,
865                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
866                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), 0, 0 );
867                         {
868                                 auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
869                                 frame.add(scr);
870
871                                 {
872                                         auto store = ui::ListStore(gtk_list_store_new( 1, G_TYPE_STRING ));
873
874                                         ui::Widget view = ui::TreeView( ui::TreeModel(GTK_TREE_MODEL( store ) ));
875                                         gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( view ), FALSE );
876
877                                         auto renderer = ui::CellRendererText();
878                                         object_set_boolean_property( G_OBJECT( renderer ), "editable", TRUE );
879                                         g_signal_connect( G_OBJECT(renderer), "edited", G_CALLBACK( project_cell_edited ), &projectList );
880
881                                         GtkTreeViewColumn* column = ui::TreeViewColumn( "", renderer, {{"text", 0}} );
882                                         gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
883
884                                         GtkTreeSelection* selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
885                                         gtk_tree_selection_set_mode( selection, GTK_SELECTION_BROWSE );
886
887                                         view.show();
888
889                                         buildView = view;
890                                         projectList.m_store = store;
891                                         scr.add(view);
892
893                                         g_signal_connect( G_OBJECT( view ), "key_press_event", G_CALLBACK( project_key_press ), &projectList );
894
895                                         g_object_unref( G_OBJECT( store ) );
896                                 }
897                         }
898                 }
899                 {
900                         auto frame = create_dialog_frame( "Commandline" );
901                         gtk_table_attach( table1, GTK_WIDGET( frame ), 0, 1, 1, 2,
902                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
903                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), 0, 0 );
904                         {
905                                 auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
906                                 frame.add(scr);
907
908                                 {
909                                         ui::ListStore store = ui::ListStore(gtk_list_store_new( 1, G_TYPE_STRING ));
910
911                                         ui::Widget view = ui::TreeView(ui::TreeModel( GTK_TREE_MODEL( store ) ));
912                                         gtk_tree_view_set_headers_visible( GTK_TREE_VIEW( view ), FALSE );
913
914                                         auto renderer = ui::CellRendererText();
915                                         object_set_boolean_property( G_OBJECT( renderer ), "editable", TRUE );
916                                         g_signal_connect( G_OBJECT(renderer), "edited", G_CALLBACK( commands_cell_edited ), store );
917
918                                         GtkTreeViewColumn* column = ui::TreeViewColumn( "", renderer, {{"text", 0}} );
919                                         gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
920
921                                         GtkTreeSelection* selection = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
922                                         gtk_tree_selection_set_mode( selection, GTK_SELECTION_BROWSE );
923
924                                         view.show();
925
926                                         scr.add(view);
927
928                                         g_object_unref( G_OBJECT( store ) );
929
930                                         g_signal_connect( G_OBJECT( view ), "key_press_event", G_CALLBACK( commands_key_press ), store );
931
932                                         g_signal_connect( G_OBJECT( gtk_tree_view_get_selection( GTK_TREE_VIEW( buildView ) ) ), "changed", G_CALLBACK( project_selection_changed ), store );
933                                 }
934                         }
935                 }
936         }
937
938         BSPCommandList_Construct( projectList.m_store, g_build_project );
939
940         return window;
941 }
942
943 namespace
944 {
945 CopiedString g_buildMenu;
946 }
947
948 void LoadBuildMenu();
949
950 void DoBuildMenu(){
951         ModalDialog modal;
952
953         ProjectList projectList( g_build_project );
954
955         ui::Window window = BuildMenuDialog_construct( modal, projectList );
956
957         if ( modal_dialog_show( window, modal ) == eIDCANCEL ) {
958                 build_commands_clear();
959                 LoadBuildMenu();
960
961                 Build_refreshMenu( g_bsp_menu );
962         }
963         else if ( projectList.m_changed ) {
964                 g_build_changed = true;
965         }
966
967         gtk_widget_destroy( GTK_WIDGET( window ) );
968 }
969
970
971
972 #include "gtkutil/menu.h"
973 #include "mainframe.h"
974 #include "preferences.h"
975 #include "qe3.h"
976
977 class BuildMenuItem
978 {
979 const char* m_name;
980 public:
981 GtkMenuItem* m_item;
982 BuildMenuItem( const char* name, GtkMenuItem* item )
983         : m_name( name ), m_item( item ){
984 }
985 void run(){
986         RunBSP( m_name );
987 }
988 typedef MemberCaller<BuildMenuItem, &BuildMenuItem::run> RunCaller;
989 };
990
991 typedef std::list<BuildMenuItem> BuildMenuItems;
992 BuildMenuItems g_BuildMenuItems;
993
994
995 ui::Menu g_bsp_menu{nullptr};
996
997 void Build_constructMenu( ui::Menu menu ){
998         for ( Project::iterator i = g_build_project.begin(); i != g_build_project.end(); ++i )
999         {
1000                 g_BuildMenuItems.push_back( BuildMenuItem( ( *i ).first.c_str(), 0 ) );
1001                 if ( is_separator( *i ) ) {
1002                         g_BuildMenuItems.back().m_item = menu_separator( menu );
1003                 }
1004                 else
1005                 {
1006                         g_BuildMenuItems.back().m_item = create_menu_item_with_mnemonic( menu, ( *i ).first.c_str(), BuildMenuItem::RunCaller( g_BuildMenuItems.back() ) );
1007                 }
1008         }
1009 }
1010
1011
1012 void Build_refreshMenu( ui::Menu menu ){
1013         for ( BuildMenuItems::iterator i = g_BuildMenuItems.begin(); i != g_BuildMenuItems.end(); ++i )
1014         {
1015                 gtk_container_remove( menu, GTK_WIDGET( ( *i ).m_item ) );
1016         }
1017
1018         g_BuildMenuItems.clear();
1019
1020         Build_constructMenu( menu );
1021 }
1022
1023
1024 void LoadBuildMenu(){
1025         if ( string_empty( g_buildMenu.c_str() ) || !build_commands_parse( g_buildMenu.c_str() ) ) {
1026                 {
1027                         StringOutputStream buffer( 256 );
1028                         buffer << GameToolsPath_get() << "default_build_menu.xml";
1029
1030                         bool success = build_commands_parse( buffer.c_str() );
1031                         ASSERT_MESSAGE( success, "failed to parse default build commands: " << buffer.c_str() );
1032                 }
1033                 {
1034                         StringOutputStream buffer( 256 );
1035                         buffer << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/build_menu.xml";
1036
1037                         g_buildMenu = buffer.c_str();
1038                 }
1039         }
1040 }
1041
1042 void SaveBuildMenu(){
1043         if ( g_build_changed ) {
1044                 g_build_changed = false;
1045                 build_commands_write( g_buildMenu.c_str() );
1046         }
1047 }
1048
1049 #include "preferencesystem.h"
1050 #include "stringio.h"
1051
1052 void BuildMenu_Construct(){
1053         GlobalPreferenceSystem().registerPreference( "BuildMenu", CopiedStringImportStringCaller( g_buildMenu ), CopiedStringExportStringCaller( g_buildMenu ) );
1054         LoadBuildMenu();
1055 }
1056 void BuildMenu_Destroy(){
1057         SaveBuildMenu();
1058 }