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