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