]> icculus.org git repositories - divverent/netradiant.git/blob - radiant/map.cpp
somewhat unclean code, sorry... but it allows opening brushPrimitives maps in nonBrus...
[divverent/netradiant.git] / radiant / map.cpp
1 /*
2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
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 "map.h"
23
24 #include "debugging/debugging.h"
25
26 #include "imap.h"
27 MapModules& ReferenceAPI_getMapModules();
28 #include "iselection.h"
29 #include "iundo.h"
30 #include "ibrush.h"
31 #include "ifilter.h"
32 #include "ireference.h"
33 #include "ifiletypes.h"
34 #include "ieclass.h"
35 #include "irender.h"
36 #include "ientity.h"
37 #include "editable.h"
38 #include "ifilesystem.h"
39 #include "namespace.h"
40 #include "moduleobserver.h"
41
42 #include <set>
43
44 #include <gtk/gtkmain.h>
45 #include <gtk/gtkbox.h>
46 #include <gtk/gtkentry.h>
47 #include <gtk/gtklabel.h>
48 #include <gtk/gtktable.h>
49 #include <gtk/gtktreemodel.h>
50 #include <gtk/gtktreeview.h>
51 #include <gtk/gtkliststore.h>
52 #include <gtk/gtkcellrenderertext.h>
53
54 #include "scenelib.h"
55 #include "transformlib.h"
56 #include "selectionlib.h"
57 #include "instancelib.h"
58 #include "traverselib.h"
59 #include "maplib.h"
60 #include "eclasslib.h"
61 #include "cmdlib.h"
62 #include "stream/textfilestream.h"
63 #include "os/path.h"
64 #include "uniquenames.h"
65 #include "modulesystem/singletonmodule.h"
66 #include "modulesystem/moduleregistry.h"
67 #include "stream/stringstream.h"
68 #include "signal/signal.h"
69
70 #include "gtkutil/filechooser.h"
71 #include "timer.h"
72 #include "select.h"
73 #include "plugin.h"
74 #include "filetypes.h"
75 #include "gtkdlgs.h"
76 #include "entityinspector.h"
77 #include "points.h"
78 #include "qe3.h"
79 #include "camwindow.h"
80 #include "xywindow.h"
81 #include "mainframe.h"
82 #include "preferences.h"
83 #include "referencecache.h"
84 #include "mru.h"
85 #include "commands.h"
86 #include "autosave.h"
87 #include "brushmodule.h"
88
89 class NameObserver
90 {
91   UniqueNames& m_names;
92   CopiedString m_name;
93
94   void construct()
95   {
96     if(!empty())
97     {
98       //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
99       m_names.insert(name_read(c_str()));
100     }
101   }
102   void destroy()
103   {
104     if(!empty())
105     {
106       //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
107       m_names.erase(name_read(c_str()));
108     }
109   }
110
111   NameObserver& operator=(const NameObserver& other);
112 public:
113   NameObserver(UniqueNames& names) : m_names(names)
114   {
115     construct();
116   }
117   NameObserver(const NameObserver& other) : m_names(other.m_names), m_name(other.m_name)
118   {
119     construct();
120   }
121   ~NameObserver()
122   {
123     destroy();
124   }
125   bool empty() const
126   {
127     return string_empty(c_str()); 
128   }
129   const char* c_str() const
130   {
131     return m_name.c_str();
132   }
133   void nameChanged(const char* name)
134   {
135     destroy();
136     m_name = name;
137     construct();
138   }
139   typedef MemberCaller1<NameObserver, const char*, &NameObserver::nameChanged> NameChangedCaller;
140 };
141
142 class BasicNamespace : public Namespace
143 {
144   typedef std::map<NameCallback, NameObserver> Names;
145   Names m_names;
146   UniqueNames m_uniqueNames;
147 public:
148   ~BasicNamespace()
149   {
150     ASSERT_MESSAGE(m_names.empty(), "namespace: names still registered at shutdown");
151   }
152   void attach(const NameCallback& setName, const NameCallbackCallback& attachObserver)
153   {
154     std::pair<Names::iterator, bool> result = m_names.insert(Names::value_type(setName, m_uniqueNames));
155     ASSERT_MESSAGE(result.second, "cannot attach name");
156     attachObserver(NameObserver::NameChangedCaller((*result.first).second));
157     //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
158   }
159   void detach(const NameCallback& setName, const NameCallbackCallback& detachObserver)
160   {
161     Names::iterator i = m_names.find(setName);
162     ASSERT_MESSAGE(i != m_names.end(), "cannot detach name");
163     //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
164     detachObserver(NameObserver::NameChangedCaller((*i).second));
165     m_names.erase(i);
166   }
167
168   void makeUnique(const char* name, const NameCallback& setName) const
169   {
170     char buffer[1024];
171     name_write(buffer, m_uniqueNames.make_unique(name_read(name)));
172     setName(buffer);
173   }
174
175   void mergeNames(const BasicNamespace& other) const
176   {
177     typedef std::list<NameCallback> SetNameCallbacks;
178     typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
179     NameGroups groups;
180
181     UniqueNames uniqueNames(other.m_uniqueNames);
182
183     for(Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i)
184     {
185       groups[(*i).second.c_str()].push_back((*i).first);
186     }
187
188     for(NameGroups::iterator i = groups.begin(); i != groups.end(); ++i)
189     {
190       name_t uniqueName(uniqueNames.make_unique(name_read((*i).first.c_str())));
191       uniqueNames.insert(uniqueName);
192
193       char buffer[1024];
194       name_write(buffer, uniqueName);
195
196       //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
197
198       SetNameCallbacks& setNameCallbacks = (*i).second;
199
200       for(SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j)
201       {
202         (*j)(buffer);
203       }
204     }
205   }
206 };
207
208 BasicNamespace g_defaultNamespace;
209 BasicNamespace g_cloneNamespace;
210
211 class NamespaceAPI
212 {
213   Namespace* m_namespace;
214 public:
215   typedef Namespace Type;
216   STRING_CONSTANT(Name, "*");
217
218   NamespaceAPI()
219   {
220     m_namespace = &g_defaultNamespace;
221   }
222   Namespace* getTable()
223   {
224     return m_namespace;
225   }
226 };
227
228 typedef SingletonModule<NamespaceAPI> NamespaceModule;
229 typedef Static<NamespaceModule> StaticNamespaceModule;
230 StaticRegisterModule staticRegisterDefaultNamespace(StaticNamespaceModule::instance());
231
232
233 std::list<Namespaced*> g_cloned;
234
235 inline Namespaced* Node_getNamespaced(scene::Node& node)
236 {
237   return NodeTypeCast<Namespaced>::cast(node);
238 }
239
240 void Node_gatherNamespaced(scene::Node& node)
241 {
242   Namespaced* namespaced = Node_getNamespaced(node);
243   if(namespaced != 0)
244   {
245     g_cloned.push_back(namespaced);
246   }
247 }
248
249 class GatherNamespaced : public scene::Traversable::Walker
250 {
251 public:
252   bool pre(scene::Node& node) const
253   {
254     Node_gatherNamespaced(node);
255     return true;
256   }
257 };
258
259 void Map_gatherNamespaced(scene::Node& root)
260 {
261   Node_traverseSubgraph(root, GatherNamespaced());
262 }
263
264 void Map_mergeClonedNames()
265 {
266   for(std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i)
267   {
268     (*i)->setNamespace(g_cloneNamespace);
269   }
270   g_cloneNamespace.mergeNames(g_defaultNamespace);
271   for(std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i)
272   {
273     (*i)->setNamespace(g_defaultNamespace);
274   }
275
276   g_cloned.clear();
277 }
278
279 class WorldNode
280 {
281   scene::Node* m_node;
282 public:
283   WorldNode()
284     : m_node(0)
285   {
286   }
287   void set(scene::Node* node)
288   {
289     if(m_node != 0)
290       m_node->DecRef();
291     m_node = node;
292     if(m_node != 0)
293       m_node->IncRef();
294   }
295   scene::Node* get() const
296   {
297     return m_node;
298   }
299 };
300
301 class Map;
302 void Map_SetValid(Map& map, bool valid);
303 void Map_UpdateTitle(const Map& map);
304 void Map_SetWorldspawn(Map& map, scene::Node* node);
305
306
307 class Map : public ModuleObserver
308 {
309 public:
310   CopiedString m_name;
311   Resource* m_resource;
312   bool m_valid;
313
314   bool m_modified;
315   void (*m_modified_changed)(const Map&);
316
317   Signal0 m_mapValidCallbacks;
318
319   WorldNode m_world_node; // "classname" "worldspawn" !
320
321   Map() : m_resource(0), m_valid(false), m_modified_changed(Map_UpdateTitle)
322   {
323   }
324
325   void realise()
326   {
327     if(m_resource != 0)
328     {
329       if(Map_Unnamed(*this))
330       {
331         g_map.m_resource->setNode(NewMapRoot("").get_pointer());
332         MapFile* map = Node_getMapFile(*g_map.m_resource->getNode());
333         if(map != 0)
334         {
335           map->save();
336         }
337       }
338       else
339       {
340         m_resource->load();
341       }
342
343       GlobalSceneGraph().insert_root(*m_resource->getNode());
344
345       AutoSave_clear();
346
347       Map_SetValid(g_map, true);
348     }
349   }
350   void unrealise()
351   {
352     if(m_resource != 0)
353     {
354       Map_SetValid(g_map, false);
355       Map_SetWorldspawn(g_map, 0);
356
357
358       GlobalUndoSystem().clear();
359
360       GlobalSceneGraph().erase_root();
361     }
362   }
363 };
364
365 Map g_map;
366 Map* g_currentMap = 0;
367
368 void Map_addValidCallback(Map& map, const SignalHandler& handler)
369 {
370   map.m_mapValidCallbacks.connectLast(handler);
371 }
372
373 bool Map_Valid(const Map& map)
374 {
375   return map.m_valid;
376 }
377
378 void Map_SetValid(Map& map, bool valid)
379 {
380   map.m_valid = valid;
381   map.m_mapValidCallbacks();
382 }
383
384
385 const char* Map_Name(const Map& map)
386 {
387   return map.m_name.c_str();
388 }
389
390 bool Map_Unnamed(const Map& map)
391 {
392   return string_equal(Map_Name(map), "unnamed.map");
393 }
394
395 inline const MapFormat& MapFormat_forFile(const char* filename)
396 {
397   const char* moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), path_get_extension(filename));
398   MapFormat* format = Radiant_getMapModules().findModule(moduleName);
399   ASSERT_MESSAGE(format != 0, "map format not found for file " << makeQuoted(filename));
400   return *format;
401 }
402
403 const MapFormat& Map_getFormat(const Map& map)
404 {
405   return MapFormat_forFile(Map_Name(map));
406 }
407
408
409 bool Map_Modified(const Map& map)
410 {
411   return map.m_modified;
412 }
413
414 void Map_SetModified(Map& map, bool modified)
415 {
416   if(map.m_modified ^ modified)
417   {
418     map.m_modified = modified;
419
420     map.m_modified_changed(map);
421   }
422 }
423
424 void Map_UpdateTitle(const Map& map)
425 {
426   Sys_SetTitle(map.m_name.c_str(), Map_Modified(map));
427 }
428
429
430
431 scene::Node* Map_GetWorldspawn(const Map& map)
432 {
433   return map.m_world_node.get();
434 }
435
436 void Map_SetWorldspawn(Map& map, scene::Node* node)
437 {
438   map.m_world_node.set(node);
439 }
440
441
442 // TTimo
443 // need that in a variable, will have to tweak depending on the game
444 float g_MaxWorldCoord = 64*1024;
445 float g_MinWorldCoord = -64*1024;
446
447 void AddRegionBrushes (void);
448 void RemoveRegionBrushes (void);
449
450
451 /*
452 ================
453 Map_Free
454 free all map elements, reinitialize the structures that depend on them
455 ================
456 */
457 void Map_Free()
458 {
459         Pointfile_Clear();
460
461   g_map.m_resource->detach(g_map);
462   GlobalReferenceCache().release(g_map.m_name.c_str());
463   g_map.m_resource = 0;
464
465   FlushReferences();
466
467   g_currentMap = 0;
468   Brush_unlatchPreferences();
469 }
470
471 class EntityFindByClassname : public scene::Graph::Walker
472 {
473   const char* m_name;
474   Entity*& m_entity;
475 public:
476   EntityFindByClassname(const char* name, Entity*& entity) : m_name(name), m_entity(entity)
477   {
478     m_entity = 0;
479   }
480   bool pre(const scene::Path& path, scene::Instance& instance) const
481   {
482     if(m_entity == 0)
483     {
484       Entity* entity = Node_getEntity(path.top());
485       if(entity != 0
486         && string_equal(m_name, entity->getKeyValue("classname")))
487       {
488         m_entity = entity;
489       }
490     }
491     return true;
492   }
493 };
494
495 Entity* Scene_FindEntityByClass(const char* name)
496 {
497   Entity* entity;
498   GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
499   return entity;
500 }
501
502 Entity *Scene_FindPlayerStart()
503 {
504   typedef const char* StaticString;
505   StaticString strings[] = {
506     "info_player_start",
507     "info_player_deathmatch",
508     "team_CTF_redplayer",
509     "team_CTF_blueplayer",
510     "team_CTF_redspawn",
511     "team_CTF_bluespawn",
512   };
513   typedef const StaticString* StaticStringIterator;
514   for(StaticStringIterator i = strings, end = strings+(sizeof(strings)/sizeof(StaticString)); i != end; ++i)
515   {
516     Entity* entity = Scene_FindEntityByClass(*i);
517     if(entity != 0)
518     {
519       return entity;
520     }
521   }
522   return 0;
523 }
524
525 //
526 // move the view to a start position
527 //
528
529
530 void FocusViews(const Vector3& point, float angle)
531 {
532   CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
533   Camera_setOrigin(camwnd, point);
534   Vector3 angles(Camera_getAngles(camwnd));
535   angles[CAMERA_PITCH] = 0;
536   angles[CAMERA_YAW] = angle;
537   Camera_setAngles(camwnd, angles);
538
539   XYWnd* xywnd = g_pParentWnd->GetXYWnd();
540   xywnd->SetOrigin(point);
541 }
542
543 #include "stringio.h"
544
545 void Map_StartPosition()
546 {
547   Entity* entity = Scene_FindPlayerStart();
548
549   if (entity)
550   {
551     Vector3 origin;
552     string_parse_vector3(entity->getKeyValue("origin"), origin);
553     FocusViews(origin, string_read_float(entity->getKeyValue("angle")));
554   }
555   else
556   {
557     FocusViews(g_vector3_identity, 0);
558   }
559 }
560
561
562 inline bool node_is_worldspawn(scene::Node& node)
563 {
564   Entity* entity = Node_getEntity(node);
565   return entity != 0 && string_equal(entity->getKeyValue("classname"), "worldspawn");
566 }
567
568
569 // use first worldspawn
570 class entity_updateworldspawn : public scene::Traversable::Walker
571 {
572 public:
573   bool pre(scene::Node& node) const
574   {
575     if(node_is_worldspawn(node))
576     {
577       if(Map_GetWorldspawn(g_map) == 0)
578       {
579         Map_SetWorldspawn(g_map, &node);
580       }
581     }
582     return false;
583   }
584 };
585
586 scene::Node* Map_FindWorldspawn(Map& map)
587 {
588   Map_SetWorldspawn(map, 0);
589
590   Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
591
592   return Map_GetWorldspawn(map);
593 }
594
595
596 class CollectAllWalker : public scene::Traversable::Walker
597 {
598   scene::Node& m_root;
599   UnsortedNodeSet& m_nodes;
600 public:
601   CollectAllWalker(scene::Node& root, UnsortedNodeSet& nodes) : m_root(root), m_nodes(nodes)
602   {
603   }
604   bool pre(scene::Node& node) const
605   {
606     m_nodes.insert(NodeSmartReference(node));
607     Node_getTraversable(m_root)->erase(node);
608     return false;
609   }
610 };
611
612 void Node_insertChildFirst(scene::Node& parent, scene::Node& child)
613 {
614   UnsortedNodeSet nodes;
615   Node_getTraversable(parent)->traverse(CollectAllWalker(parent, nodes));
616   Node_getTraversable(parent)->insert(child);
617
618   for(UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i)
619   {
620     Node_getTraversable(parent)->insert((*i));
621   }
622 }
623
624 scene::Node& createWorldspawn()
625 {
626   NodeSmartReference worldspawn(GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("worldspawn", true)));
627   Node_insertChildFirst(GlobalSceneGraph().root(), worldspawn);
628   return worldspawn;
629 }
630
631 void Map_UpdateWorldspawn(Map& map)
632 {
633   if(Map_FindWorldspawn(map) == 0)
634   {
635     Map_SetWorldspawn(map, &createWorldspawn());
636   }
637 }
638
639 scene::Node& Map_FindOrInsertWorldspawn(Map& map)
640 {
641   Map_UpdateWorldspawn(map);
642   return *Map_GetWorldspawn(map);
643 }
644
645
646 class MapMergeAll : public scene::Traversable::Walker
647 {
648   mutable scene::Path m_path;
649 public:
650   MapMergeAll(const scene::Path& root)
651     : m_path(root)
652   {
653   }
654   bool pre(scene::Node& node) const
655   {
656     Node_getTraversable(m_path.top())->insert(node);
657     m_path.push(makeReference(node));
658     selectPath(m_path, true);
659     return false;
660   }
661   void post(scene::Node& node) const
662   {
663     m_path.pop();
664   }
665 };
666
667 class MapMergeEntities : public scene::Traversable::Walker
668 {
669   mutable scene::Path m_path;
670 public:
671   MapMergeEntities(const scene::Path& root)
672     : m_path(root)
673   {
674   }
675   bool pre(scene::Node& node) const
676   {
677     if(node_is_worldspawn(node))
678     {
679       scene::Node* world_node = Map_FindWorldspawn(g_map);
680       if(world_node == 0)
681       {
682         Map_SetWorldspawn(g_map, &node);
683         Node_getTraversable(m_path.top().get())->insert(node);
684         m_path.push(makeReference(node));
685         Node_getTraversable(node)->traverse(SelectChildren(m_path));
686       }
687       else
688       {
689         m_path.push(makeReference(*world_node));
690         Node_getTraversable(node)->traverse(MapMergeAll(m_path));
691       }
692     }
693     else
694     {
695       Node_getTraversable(m_path.top())->insert(node);
696       m_path.push(makeReference(node));
697       if(node_is_group(node))
698       {
699         Node_getTraversable(node)->traverse(SelectChildren(m_path));
700       }
701       else
702       {
703         selectPath(m_path, true);
704       }
705     }
706     return false;
707   }
708   void post(scene::Node& node) const
709   {
710     m_path.pop();
711   }
712 };
713
714 class BasicContainer : public scene::Node::Symbiot
715 {
716   class TypeCasts
717   {
718     NodeTypeCastTable m_casts;
719   public:
720     TypeCasts()
721     {
722       NodeContainedCast<BasicContainer, scene::Traversable>::install(m_casts);
723     }
724     NodeTypeCastTable& get()
725     {
726       return m_casts;
727     }
728   };
729
730   scene::Node m_node;
731   TraversableNodeSet m_traverse;
732 public:
733
734   typedef LazyStatic<TypeCasts> StaticTypeCasts;
735
736   scene::Traversable& get(NullType<scene::Traversable>)
737   {
738     return m_traverse;
739   }
740
741   BasicContainer() : m_node(this, this, StaticTypeCasts::instance().get())
742   {
743   }
744   void release()
745   {
746     delete this;
747   }
748   scene::Node& node()
749   {
750     return m_node;
751   }
752 };
753
754 /// Merges the map graph rooted at \p node into the global scene-graph.
755 void MergeMap(scene::Node& node)
756 {
757   Node_getTraversable(node)->traverse(MapMergeEntities(scene::Path(makeReference(GlobalSceneGraph().root()))));
758 }
759 void Map_ImportSelected(TextInputStream& in, const MapFormat& format)
760 {
761   NodeSmartReference node((new BasicContainer)->node());
762   format.readGraph(node, in, GlobalEntityCreator());
763   Map_gatherNamespaced(node);
764   Map_mergeClonedNames();
765   MergeMap(node);
766 }
767
768 inline scene::Cloneable* Node_getCloneable(scene::Node& node)
769 {
770   return NodeTypeCast<scene::Cloneable>::cast(node);
771 }
772
773 inline scene::Node& node_clone(scene::Node& node)
774 {
775   scene::Cloneable* cloneable = Node_getCloneable(node);
776   if(cloneable != 0)
777   {
778     return cloneable->clone();
779   }
780   
781   return (new scene::NullNode)->node();
782 }
783
784 class CloneAll : public scene::Traversable::Walker
785 {
786   mutable scene::Path m_path;
787 public:
788   CloneAll(scene::Node& root)
789     : m_path(makeReference(root))
790   {
791   }
792   bool pre(scene::Node& node) const
793   {
794     if(node.isRoot())
795     {
796       return false;
797     }
798     
799     m_path.push(makeReference(node_clone(node)));
800     m_path.top().get().IncRef();
801
802     return true;
803   }
804   void post(scene::Node& node) const
805   {
806     if(node.isRoot())
807     {
808       return;
809     }
810
811     Node_getTraversable(m_path.parent())->insert(m_path.top());
812
813     m_path.top().get().DecRef();
814     m_path.pop();
815   }
816 };
817
818 scene::Node& Node_Clone(scene::Node& node)
819 {
820   scene::Node& clone = node_clone(node);
821   scene::Traversable* traversable = Node_getTraversable(node);
822   if(traversable != 0)
823   {
824     traversable->traverse(CloneAll(clone));
825   }
826   return clone;
827 }
828
829
830 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
831
832 class EntityBreakdownWalker : public scene::Graph::Walker
833 {
834   EntityBreakdown& m_entitymap;
835 public:
836   EntityBreakdownWalker(EntityBreakdown& entitymap)
837     : m_entitymap(entitymap)
838   {
839   }
840   bool pre(const scene::Path& path, scene::Instance& instance) const
841   {
842     Entity* entity = Node_getEntity(path.top());
843     if(entity != 0)
844     {
845       const EntityClass& eclass = entity->getEntityClass();
846       if(m_entitymap.find(eclass.name()) == m_entitymap.end())
847       {
848         m_entitymap[eclass.name()] = 1;
849       }
850       else ++m_entitymap[eclass.name()];
851     }
852     return true;
853   }
854 };
855
856 void Scene_EntityBreakdown(EntityBreakdown& entitymap)
857 {
858   GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap));
859 }
860
861
862 WindowPosition g_posMapInfoWnd(c_default_window_pos);
863
864 void DoMapInfo()
865 {
866   ModalDialog dialog;
867   GtkEntry* brushes_entry;
868   GtkEntry* entities_entry;
869   GtkListStore* EntityBreakdownWalker;
870
871   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Map Info", G_CALLBACK(dialog_delete_callback), &dialog);
872
873   window_set_position(window, g_posMapInfoWnd);
874
875   {
876     GtkVBox* vbox = create_dialog_vbox(4, 4);
877     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
878
879     {
880       GtkHBox* hbox = create_dialog_hbox(4);
881       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, TRUE, 0);
882
883       {
884         GtkTable* table = create_dialog_table(2, 2, 4, 4);
885         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0);
886
887         {
888           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
889           gtk_widget_show(GTK_WIDGET(entry));
890           gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1,
891                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
892                             (GtkAttachOptions) (0), 0, 0);
893           gtk_entry_set_editable(entry, FALSE);
894
895           brushes_entry = entry;
896         }
897         {
898           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
899           gtk_widget_show(GTK_WIDGET(entry));
900           gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2,
901                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
902                             (GtkAttachOptions) (0), 0, 0);
903           gtk_entry_set_editable(entry, FALSE);
904
905           entities_entry = entry;
906         }
907         {
908           GtkWidget* label = gtk_label_new ("Total Brushes");
909           gtk_widget_show (label);
910           gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
911                             (GtkAttachOptions) (GTK_FILL),
912                             (GtkAttachOptions) (0), 0, 0);
913           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
914         }
915         {
916           GtkWidget* label = gtk_label_new ("Total Entities");
917           gtk_widget_show (label);
918           gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
919                             (GtkAttachOptions) (GTK_FILL),
920                             (GtkAttachOptions) (0), 0, 0);
921           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
922         }
923       }
924       {
925         GtkVBox* vbox2 = create_dialog_vbox(4);
926         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox2), FALSE, FALSE, 0);
927
928         {
929           GtkButton* button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog);
930           gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(button), FALSE, FALSE, 0);
931         }
932       }
933     }
934     {
935       GtkWidget* label = gtk_label_new ("Entity breakdown");
936       gtk_widget_show (label);
937       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, TRUE, 0);
938       gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
939     }
940     {
941       GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4);
942       gtk_box_pack_start(GTK_BOX (vbox), GTK_WIDGET(scr), TRUE, TRUE, 0);
943
944       {
945         GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
946
947         GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
948         gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(view), TRUE);
949
950         {
951           GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
952           GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Entity", renderer, "text", 0, 0);
953           gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
954           gtk_tree_view_column_set_sort_column_id(column, 0);
955         }
956
957         {
958           GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
959           GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Count", renderer, "text", 1, 0);
960           gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
961           gtk_tree_view_column_set_sort_column_id(column, 1);
962         }
963
964         gtk_widget_show(view);
965
966         gtk_container_add(GTK_CONTAINER(scr), view);
967         
968         EntityBreakdownWalker = store;
969       }
970     }
971   }
972
973   // Initialize fields
974
975   {
976     EntityBreakdown entitymap;
977     Scene_EntityBreakdown(entitymap);
978
979     for(EntityBreakdown::iterator i=entitymap.begin(); i != entitymap.end(); ++i)
980     {
981       char tmp[16];
982       sprintf (tmp, "%u", Unsigned((*i).second));
983       GtkTreeIter iter;
984       gtk_list_store_append(GTK_LIST_STORE(EntityBreakdownWalker), &iter);
985       gtk_list_store_set(GTK_LIST_STORE(EntityBreakdownWalker), &iter, 0, (*i).first.c_str(), 1, tmp, -1);
986     }
987   }
988
989   g_object_unref(G_OBJECT(EntityBreakdownWalker));
990
991   char tmp[16];
992   sprintf (tmp, "%u", Unsigned(g_brushCount.get()));
993   gtk_entry_set_text (GTK_ENTRY (brushes_entry), tmp);
994   sprintf (tmp, "%u", Unsigned(g_entityCount.get()));
995   gtk_entry_set_text (GTK_ENTRY (entities_entry), tmp);
996
997   modal_dialog_show(window, dialog);
998
999   // save before exit
1000   window_get_position(window, g_posMapInfoWnd);
1001
1002   gtk_widget_destroy(GTK_WIDGET(window));
1003 }
1004
1005
1006
1007 class ScopeTimer
1008 {
1009   Timer m_timer;
1010   const char* m_message;
1011 public:
1012   ScopeTimer(const char* message)
1013     : m_message(message)
1014   {
1015     m_timer.start();
1016   }
1017   ~ScopeTimer()
1018   {
1019     double elapsed_time = m_timer.elapsed_msec() / 1000.f;
1020     globalOutputStream() << m_message << " timer: " << FloatFormat(elapsed_time, 5, 2) << " second(s) elapsed\n";
1021   }
1022 };
1023
1024 /*
1025 ================
1026 Map_LoadFile
1027 ================
1028 */
1029
1030 void Map_LoadFile (const char *filename)
1031 {
1032   globalOutputStream() << "Loading map from " << filename << "\n";
1033   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1034
1035   g_map.m_name = filename;
1036   Map_UpdateTitle(g_map);
1037
1038   {
1039     ScopeTimer timer("map load");
1040
1041     g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1042
1043     const MapFormat* format = ReferenceAPI_getMapModules().findModule("mapq3");
1044     format->wrongFormat = false;
1045     g_map.m_resource->attach(g_map);
1046     if(format->wrongFormat)
1047     {
1048       // try toggling BrushPrimitives
1049       Map_Free();
1050       Brush_toggleProjection();
1051       g_map.m_name = filename;
1052       Map_UpdateTitle(g_map);
1053       g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1054       g_map.m_resource->attach(g_map);
1055     }
1056
1057     Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
1058   }
1059
1060   globalOutputStream() << "--- LoadMapFile ---\n";
1061   globalOutputStream() << g_map.m_name.c_str() << "\n";
1062   
1063   globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
1064   globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
1065
1066   //GlobalEntityCreator().printStatistics();
1067   
1068   //
1069   // move the view to a start position
1070   //
1071   Map_StartPosition();
1072
1073   g_currentMap = &g_map;
1074 }
1075
1076 class Excluder
1077 {
1078 public:
1079   virtual bool excluded(scene::Node& node) const = 0;
1080 };
1081
1082 class ExcludeWalker : public scene::Traversable::Walker
1083 {
1084   const scene::Traversable::Walker& m_walker;
1085   const Excluder* m_exclude;
1086   mutable bool m_skip;
1087 public:
1088   ExcludeWalker(const scene::Traversable::Walker& walker, const Excluder& exclude)
1089     : m_walker(walker), m_exclude(&exclude), m_skip(false)
1090   {
1091   }
1092   bool pre(scene::Node& node) const
1093   {
1094     if(m_exclude->excluded(node) || node.isRoot())
1095     {
1096       m_skip = true;
1097       return false;
1098     }
1099     else
1100     {
1101       m_walker.pre(node);
1102     }
1103     return true;
1104   }
1105   void post(scene::Node& node) const
1106   {
1107     if(m_skip)
1108     {
1109       m_skip = false;
1110     }
1111     else
1112     {
1113       m_walker.post(node);
1114     }
1115   }
1116 };
1117
1118 class AnyInstanceSelected : public scene::Instantiable::Visitor
1119 {
1120   bool& m_selected;
1121 public:
1122   AnyInstanceSelected(bool& selected) : m_selected(selected)
1123   {
1124     m_selected = false;
1125   }
1126   void visit(scene::Instance& instance) const
1127   {
1128     Selectable* selectable = Instance_getSelectable(instance);
1129     if(selectable != 0
1130       && selectable->isSelected())
1131     {
1132       m_selected = true;
1133     }
1134   }
1135 };
1136
1137 bool Node_instanceSelected(scene::Node& node)
1138 {
1139   scene::Instantiable* instantiable = Node_getInstantiable(node);
1140   ASSERT_NOTNULL(instantiable);
1141   bool selected;
1142   instantiable->forEachInstance(AnyInstanceSelected(selected));
1143   return selected;
1144 }
1145
1146 class SelectedDescendantWalker : public scene::Traversable::Walker
1147 {
1148   bool& m_selected;
1149 public:
1150   SelectedDescendantWalker(bool& selected) : m_selected(selected)
1151   {
1152     m_selected = false;
1153   }
1154
1155   bool pre(scene::Node& node) const
1156   {
1157     if(node.isRoot())
1158     {
1159       return false;
1160     }
1161
1162     if(Node_instanceSelected(node))
1163     {
1164       m_selected = true;
1165     }
1166
1167     return true;
1168   }
1169 };
1170
1171 bool Node_selectedDescendant(scene::Node& node)
1172 {
1173   bool selected;
1174   Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
1175   return selected;
1176 }
1177
1178 class SelectionExcluder : public Excluder
1179 {
1180 public:
1181   bool excluded(scene::Node& node) const
1182   {
1183     return !Node_selectedDescendant(node);
1184   }
1185 };
1186
1187 class IncludeSelectedWalker : public scene::Traversable::Walker
1188 {
1189   const scene::Traversable::Walker& m_walker;
1190   mutable std::size_t m_selected;
1191   mutable bool m_skip;
1192
1193   bool selectedParent() const
1194   {
1195     return m_selected != 0;
1196   }
1197 public:
1198   IncludeSelectedWalker(const scene::Traversable::Walker& walker)
1199     : m_walker(walker), m_selected(0), m_skip(false)
1200   {
1201   }
1202   bool pre(scene::Node& node) const
1203   {
1204     // include node if:
1205     // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1206     if(!node.isRoot() && (Node_selectedDescendant(node) || selectedParent()))
1207     {
1208       if(Node_instanceSelected(node))
1209       {
1210         ++m_selected;
1211       }
1212       m_walker.pre(node);
1213       return true;
1214     }
1215     else
1216     {
1217       m_skip = true;
1218       return false;
1219     }
1220   }
1221   void post(scene::Node& node) const
1222   {
1223     if(m_skip)
1224     {
1225       m_skip = false;
1226     }
1227     else
1228     {
1229       if(Node_instanceSelected(node))
1230       {
1231         --m_selected;
1232       }
1233       m_walker.post(node);
1234     }
1235   }
1236 };
1237
1238 void Map_Traverse_Selected(scene::Node& root, const scene::Traversable::Walker& walker)
1239 {
1240   scene::Traversable* traversable = Node_getTraversable(root);
1241   if(traversable != 0)
1242   {
1243 #if 0
1244     traversable->traverse(ExcludeWalker(walker, SelectionExcluder()));
1245 #else
1246     traversable->traverse(IncludeSelectedWalker(walker));
1247 #endif
1248   }
1249 }
1250
1251 void Map_ExportSelected(TextOutputStream& out, const MapFormat& format)
1252 {
1253   format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out);
1254 }
1255
1256 void Map_Traverse(scene::Node& root, const scene::Traversable::Walker& walker)
1257 {
1258   scene::Traversable* traversable = Node_getTraversable(root);
1259   if(traversable != 0)
1260   {
1261     traversable->traverse(walker);
1262   }
1263 }
1264
1265 class RegionExcluder : public Excluder
1266 {
1267 public:
1268   bool excluded(scene::Node& node) const
1269   {
1270     return node.excluded();
1271   }
1272 };
1273
1274 void Map_Traverse_Region(scene::Node& root, const scene::Traversable::Walker& walker)
1275 {
1276   scene::Traversable* traversable = Node_getTraversable(root);
1277   if(traversable != 0)
1278   {
1279     traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
1280   }
1281 }
1282
1283 bool Map_SaveRegion(const char *filename)
1284 {
1285   AddRegionBrushes();
1286
1287   bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region, filename); 
1288
1289   RemoveRegionBrushes();
1290
1291   return success;
1292 }
1293
1294
1295 void Map_RenameAbsolute(const char* absolute)
1296 {
1297   Resource* resource = GlobalReferenceCache().capture(absolute);
1298   NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
1299   resource->setNode(clone.get_pointer());
1300
1301   {
1302     //ScopeTimer timer("clone subgraph");
1303     Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
1304   }
1305
1306   g_map.m_resource->detach(g_map);
1307   GlobalReferenceCache().release(g_map.m_name.c_str());
1308
1309   g_map.m_resource = resource;
1310
1311   g_map.m_name = absolute;
1312   Map_UpdateTitle(g_map);
1313
1314   g_map.m_resource->attach(g_map);
1315 }
1316
1317 void Map_Rename(const char* filename)
1318 {
1319   if(!string_equal(g_map.m_name.c_str(), filename))
1320   {
1321     ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1322
1323     Map_RenameAbsolute(filename);
1324     
1325     SceneChangeNotify();
1326   }
1327   else
1328   {
1329     SaveReferences();
1330   }
1331 }
1332
1333 bool Map_Save()
1334 {
1335         Pointfile_Clear();
1336
1337   ScopeTimer timer("map save");
1338   SaveReferences();
1339   return true; // assume success..
1340 }
1341
1342 /*
1343 ===========
1344 Map_New
1345
1346 ===========
1347 */
1348 void Map_New()
1349 {
1350         //globalOutputStream() << "Map_New\n";
1351
1352         g_map.m_name = "unnamed.map";
1353   Map_UpdateTitle(g_map);
1354
1355   {
1356     g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1357 //    ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1358     g_map.m_resource->attach(g_map);
1359
1360     SceneChangeNotify();
1361   }
1362
1363   FocusViews(g_vector3_identity, 0);
1364
1365   g_currentMap = &g_map;
1366 }
1367
1368 extern void ConstructRegionBrushes(scene::Node* brushes[6], const Vector3& region_mins, const Vector3& region_maxs);
1369
1370 void ConstructRegionStartpoint(scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs)
1371 {
1372   /*! 
1373   \todo we need to make sure that the player start IS inside the region and bail out if it's not
1374   the compiler will refuse to compile a map with a player_start somewhere in empty space..
1375   for now, let's just print an error
1376   */
1377   
1378   Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
1379
1380   for (int i=0 ; i<3 ; i++)
1381   {
1382     if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i])
1383     {
1384       globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1385       break;
1386     }
1387   }
1388   
1389   // write the info_playerstart
1390   char sTmp[1024];
1391   sprintf(sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2]);
1392   Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
1393   sprintf(sTmp, "%d", (int)Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
1394   Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
1395 }
1396
1397 /*
1398 ===========================================================
1399
1400   REGION
1401
1402 ===========================================================
1403 */
1404 bool    region_active;
1405 Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
1406 Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
1407
1408 scene::Node* region_sides[6];
1409 scene::Node* region_startpoint = 0;
1410
1411 /*
1412 ===========
1413 AddRegionBrushes
1414 a regioned map will have temp walls put up at the region boundary
1415 \todo TODO TTimo old implementation of region brushes
1416   we still add them straight in the worldspawn and take them out after the map is saved
1417   with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1418 ===========
1419 */
1420 void AddRegionBrushes (void)
1421 {
1422         int             i;
1423
1424   for(i=0; i<6; i++)
1425   {
1426     region_sides[i] = &GlobalBrushCreator().createBrush();
1427     Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
1428   }
1429
1430   region_startpoint = &GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("info_player_start", false));
1431
1432   ConstructRegionBrushes(region_sides, region_mins, region_maxs);
1433   ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
1434
1435   Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
1436 }
1437
1438 void RemoveRegionBrushes (void)
1439 {
1440   for(std::size_t i=0; i<6; i++)
1441   {
1442     Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
1443   }
1444   Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
1445 }
1446
1447 inline void exclude_node(scene::Node& node, bool exclude)
1448 {
1449   exclude
1450     ? node.enable(scene::Node::eExcluded)
1451     : node.disable(scene::Node::eExcluded);
1452 }
1453
1454 class ExcludeAllWalker : public scene::Graph::Walker
1455 {
1456   bool m_exclude;
1457 public:
1458   ExcludeAllWalker(bool exclude)
1459     : m_exclude(exclude)
1460   {
1461   }
1462   bool pre(const scene::Path& path, scene::Instance& instance) const
1463   {
1464     exclude_node(path.top(), m_exclude);
1465
1466     return true;
1467   }
1468 };
1469
1470 void Scene_Exclude_All(bool exclude)
1471 {
1472   GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
1473 }
1474
1475 bool Instance_isSelected(const scene::Instance& instance)
1476 {
1477   const Selectable* selectable = Instance_getSelectable(instance);
1478   return selectable != 0 && selectable->isSelected();
1479 }
1480
1481 class ExcludeSelectedWalker : public scene::Graph::Walker
1482 {
1483   bool m_exclude;
1484 public:
1485   ExcludeSelectedWalker(bool exclude)
1486     : m_exclude(exclude)
1487   {
1488   }
1489   bool pre(const scene::Path& path, scene::Instance& instance) const
1490   {
1491     exclude_node(path.top(), (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
1492     return true;
1493   }
1494 };
1495
1496 void Scene_Exclude_Selected(bool exclude)
1497 {
1498   GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
1499 }
1500
1501 class ExcludeRegionedWalker : public scene::Graph::Walker
1502 {
1503   bool m_exclude;
1504 public:
1505   ExcludeRegionedWalker(bool exclude)
1506     : m_exclude(exclude)
1507   {
1508   }
1509   bool pre(const scene::Path& path, scene::Instance& instance) const
1510   {
1511     exclude_node(
1512       path.top(),
1513       !(
1514         (
1515           aabb_intersects_aabb(
1516             instance.worldAABB(),
1517             aabb_for_minmax(region_mins, region_maxs)
1518           ) != 0
1519         ) ^ m_exclude
1520       )
1521     );
1522
1523     return true;
1524   }
1525 };
1526
1527 void Scene_Exclude_Region(bool exclude)
1528 {
1529   GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
1530 }
1531
1532 /*
1533 ===========
1534 Map_RegionOff
1535
1536 Other filtering options may still be on
1537 ===========
1538 */
1539 void Map_RegionOff()
1540 {
1541         region_active = false;
1542
1543         region_maxs[0] = g_MaxWorldCoord - 64;
1544         region_mins[0] = g_MinWorldCoord + 64;
1545         region_maxs[1] = g_MaxWorldCoord - 64;
1546         region_mins[1] = g_MinWorldCoord + 64;
1547         region_maxs[2] = g_MaxWorldCoord - 64;
1548         region_mins[2] = g_MinWorldCoord + 64;
1549         
1550         Scene_Exclude_All(false);
1551 }
1552
1553 void Map_ApplyRegion (void)
1554 {
1555         region_active = true;
1556
1557         Scene_Exclude_Region(false);
1558 }
1559
1560
1561 /*
1562 ========================
1563 Map_RegionSelectedBrushes
1564 ========================
1565 */
1566 void Map_RegionSelectedBrushes (void)
1567 {
1568         Map_RegionOff();
1569
1570   if(GlobalSelectionSystem().countSelected() != 0
1571     && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive)
1572   {
1573           region_active = true;
1574           Select_GetBounds (region_mins, region_maxs);
1575
1576           Scene_Exclude_Selected(false);
1577     
1578     GlobalSelectionSystem().setSelectedAll(false);
1579   }
1580 }
1581
1582
1583 /*
1584 ===========
1585 Map_RegionXY
1586 ===========
1587 */
1588 void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
1589 {
1590         Map_RegionOff();
1591
1592         region_mins[0] = x_min;
1593   region_maxs[0] = x_max;
1594   region_mins[1] = y_min;
1595   region_maxs[1] = y_max;
1596   region_mins[2] = g_MinWorldCoord + 64;
1597         region_maxs[2] = g_MaxWorldCoord - 64;
1598
1599         Map_ApplyRegion();
1600 }
1601
1602 void Map_RegionBounds(const AABB& bounds)
1603 {
1604   Map_RegionOff();
1605
1606   region_mins = vector3_subtracted(bounds.origin, bounds.extents);
1607   region_maxs = vector3_added(bounds.origin, bounds.extents);
1608
1609   deleteSelection();
1610
1611   Map_ApplyRegion();
1612 }
1613
1614 /*
1615 ===========
1616 Map_RegionBrush
1617 ===========
1618 */
1619 void Map_RegionBrush (void)
1620 {
1621   if(GlobalSelectionSystem().countSelected() != 0)
1622   {
1623     scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1624     Map_RegionBounds(instance.worldAABB());
1625   }
1626 }
1627
1628 //
1629 //================
1630 //Map_ImportFile
1631 //================
1632 //
1633 bool Map_ImportFile(const char* filename)
1634 {
1635   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1636
1637   bool success = false;
1638   {
1639     Resource* resource = GlobalReferenceCache().capture(filename);
1640     resource->refresh(); // avoid loading old version if map has changed on disk since last import
1641     if(resource->load())
1642     {
1643       NodeSmartReference clone(NewMapRoot(""));
1644
1645       {
1646         //ScopeTimer timer("clone subgraph");
1647         Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1648       }
1649
1650       Map_gatherNamespaced(clone);
1651       Map_mergeClonedNames();
1652       MergeMap(clone);
1653       success = true;
1654     }
1655     GlobalReferenceCache().release(filename);
1656   }
1657
1658         SceneChangeNotify();
1659
1660   return success;
1661 }
1662
1663 /*
1664 ===========
1665 Map_SaveFile
1666 ===========
1667 */
1668 bool Map_SaveFile(const char* filename)
1669 {
1670   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1671   return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename); 
1672 }
1673
1674 //
1675 //===========
1676 //Map_SaveSelected
1677 //===========
1678 //
1679 // Saves selected world brushes and whole entities with partial/full selections
1680 //
1681 bool Map_SaveSelected(const char* filename)
1682 {
1683   return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected, filename); 
1684 }
1685
1686
1687 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1688 {
1689   scene::Node& m_parent;
1690 public:
1691   ParentSelectedBrushesToEntityWalker(scene::Node& parent) : m_parent(parent)
1692   {
1693   }
1694   bool pre(const scene::Path& path, scene::Instance& instance) const
1695   {
1696     if(path.top().get_pointer() != &m_parent
1697       && Node_isPrimitive(path.top()))
1698     {
1699       Selectable* selectable = Instance_getSelectable(instance);
1700       if(selectable != 0
1701         && selectable->isSelected()
1702         && path.size() > 1)
1703       {
1704         return false;
1705       }
1706     }
1707     return true;
1708   }
1709   void post(const scene::Path& path, scene::Instance& instance) const
1710   {
1711     if(path.top().get_pointer() != &m_parent
1712       && Node_isPrimitive(path.top()))
1713     {
1714       Selectable* selectable = Instance_getSelectable(instance);
1715       if(selectable != 0
1716         && selectable->isSelected()
1717         && path.size() > 1)
1718       {
1719         scene::Node& parent = path.parent();
1720         if(&parent != &m_parent)
1721         {
1722           NodeSmartReference node(path.top().get());
1723           Node_getTraversable(parent)->erase(node);
1724           Node_getTraversable(m_parent)->insert(node);
1725         }
1726       }
1727     }
1728   }
1729 };
1730
1731 void Scene_parentSelectedBrushesToEntity(scene::Graph& graph, scene::Node& parent)
1732 {
1733   graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1734 }
1735
1736 class CountSelectedBrushes : public scene::Graph::Walker
1737 {
1738   std::size_t& m_count;
1739   mutable std::size_t m_depth;
1740 public:
1741   CountSelectedBrushes(std::size_t& count) : m_count(count), m_depth(0)
1742   {
1743     m_count = 0;
1744   }
1745   bool pre(const scene::Path& path, scene::Instance& instance) const
1746   {
1747     if(++m_depth != 1 && path.top().get().isRoot())
1748     {
1749       return false;
1750     }
1751     Selectable* selectable = Instance_getSelectable(instance);
1752     if(selectable != 0
1753       && selectable->isSelected()
1754       && Node_isPrimitive(path.top()))
1755     {
1756       ++m_count;
1757     }
1758     return true;
1759   }
1760   void post(const scene::Path& path, scene::Instance& instance) const
1761   {
1762     --m_depth;
1763   }
1764 };
1765
1766 std::size_t Scene_countSelectedBrushes(scene::Graph& graph)
1767 {
1768   std::size_t count;
1769   graph.traverse(CountSelectedBrushes(count));
1770   return count;
1771 }
1772
1773 enum ENodeType
1774 {
1775   eNodeUnknown,
1776   eNodeMap,
1777   eNodeEntity,
1778   eNodePrimitive,
1779 };
1780
1781 const char* nodetype_get_name(ENodeType type)
1782 {
1783   if(type == eNodeMap)
1784     return "map";
1785   if(type == eNodeEntity)
1786     return "entity";
1787   if(type == eNodePrimitive)
1788     return "primitive";
1789   return "unknown";
1790 }
1791
1792 ENodeType node_get_nodetype(scene::Node& node)
1793 {
1794   if(Node_isEntity(node))
1795   {
1796     return eNodeEntity;
1797   }
1798   if(Node_isPrimitive(node))
1799   {
1800     return eNodePrimitive;
1801   }
1802   return eNodeUnknown;
1803 }
1804
1805 bool contains_entity(scene::Node& node)
1806 {
1807   return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1808 }
1809
1810 bool contains_primitive(scene::Node& node)
1811 {
1812   return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1813 }
1814
1815 ENodeType node_get_contains(scene::Node& node)
1816 {
1817   if(contains_entity(node))
1818   {
1819     return eNodeEntity;
1820   }
1821   if(contains_primitive(node))
1822   {
1823     return eNodePrimitive;
1824   }
1825   return eNodeUnknown;
1826 }
1827
1828 void Path_parent(const scene::Path& parent, const scene::Path& child)
1829 {
1830   ENodeType contains = node_get_contains(parent.top());
1831   ENodeType type = node_get_nodetype(child.top());
1832
1833   if(contains != eNodeUnknown && contains == type)
1834   {
1835     NodeSmartReference node(child.top().get());
1836     Path_deleteTop(child);
1837     Node_getTraversable(parent.top())->insert(node);
1838     SceneChangeNotify();
1839   }
1840   else
1841   {
1842     globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to " << nodetype_get_name(contains) << " container.\n";
1843   }
1844 }
1845
1846 void Scene_parentSelected()
1847 {
1848   UndoableCommand undo("parentSelected");
1849
1850   if(GlobalSelectionSystem().countSelected() > 1)
1851   {
1852     class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1853     {
1854       const scene::Path& m_parent;
1855     public:
1856       ParentSelectedBrushesToEntityWalker(const scene::Path& parent) : m_parent(parent)
1857       {
1858       }
1859       void visit(scene::Instance& instance) const
1860       {
1861         if(&m_parent != &instance.path())
1862         {
1863           Path_parent(m_parent, instance.path());
1864         }
1865       }
1866     };
1867     
1868     ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1869     GlobalSelectionSystem().foreachSelected(visitor);
1870   }
1871   else
1872   {
1873     globalOutputStream() << "failed - did not find two selected nodes.\n";
1874   }
1875 }
1876
1877
1878
1879 void NewMap()
1880 {
1881   if (ConfirmModified("New Map"))
1882   {
1883     Map_RegionOff();
1884           Map_Free();
1885     Map_New();
1886   }
1887 }
1888
1889 CopiedString g_mapsPath;
1890
1891 const char* getMapsPath()
1892 {
1893   return g_mapsPath.c_str();
1894 }
1895
1896 const char* map_open(const char* title)
1897 {
1898   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), TRUE, title, getMapsPath(), MapFormat::Name());
1899 }
1900
1901 const char* map_save(const char* title)
1902 {
1903   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), FALSE, title, getMapsPath(), MapFormat::Name());
1904 }
1905
1906 void OpenMap()
1907 {
1908   if (!ConfirmModified("Open Map"))
1909     return;
1910
1911   const char* filename = map_open("Open Map");
1912
1913   if (filename != 0)
1914   {
1915     MRU_AddFile(filename);
1916     Map_RegionOff();
1917     Map_Free();
1918     Map_LoadFile(filename);
1919   }
1920 }
1921
1922 void ImportMap()
1923 {
1924   const char* filename = map_open("Import Map");
1925
1926   if(filename != 0)
1927   {
1928     UndoableCommand undo("mapImport");
1929     Map_ImportFile(filename);
1930   }
1931 }
1932
1933 bool Map_SaveAs()
1934 {
1935   const char* filename = map_save("Save Map");
1936   
1937   if(filename != 0)
1938   {
1939     MRU_AddFile(filename);
1940     Map_Rename(filename);
1941     return Map_Save();
1942   }
1943   return false;
1944 }
1945
1946 void SaveMapAs()
1947 {
1948   Map_SaveAs();
1949 }
1950
1951 void SaveMap()
1952 {
1953   if(Map_Unnamed(g_map))
1954   {
1955     SaveMapAs();
1956   }
1957   else if(Map_Modified(g_map))
1958   {
1959     Map_Save();
1960   }
1961 }
1962
1963 void ExportMap()
1964 {
1965   const char* filename = map_save("Export Selection");
1966
1967   if(filename != 0)
1968   {
1969     Map_SaveSelected(filename);
1970   }
1971 }
1972
1973 void SaveRegion()
1974 {
1975   const char* filename = map_save("Export Region");
1976   
1977   if(filename != 0)
1978   {
1979     Map_SaveRegion(filename);
1980   }
1981 }
1982
1983
1984 void RegionOff()
1985 {
1986   Map_RegionOff();
1987   SceneChangeNotify();
1988 }
1989
1990 void RegionXY()
1991 {
1992   Map_RegionXY(
1993     g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1994     g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1995     g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1996     g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1997     );
1998   SceneChangeNotify();
1999 }
2000
2001 void RegionBrush()
2002 {
2003   Map_RegionBrush();
2004   SceneChangeNotify();
2005 }
2006
2007 void RegionSelected()
2008 {
2009   Map_RegionSelectedBrushes();
2010   SceneChangeNotify();
2011 }
2012
2013
2014
2015
2016
2017 class BrushFindByIndexWalker : public scene::Traversable::Walker
2018 {
2019   mutable std::size_t m_index;
2020   scene::Path& m_path;
2021 public:
2022   BrushFindByIndexWalker(std::size_t index, scene::Path& path)
2023     : m_index(index), m_path(path)
2024   {
2025   }
2026   bool pre(scene::Node& node) const
2027   {
2028     if(Node_isPrimitive(node) && m_index-- == 0)
2029     {
2030       m_path.push(makeReference(node));
2031     }
2032     return false;
2033   }
2034 };
2035
2036 class EntityFindByIndexWalker : public scene::Traversable::Walker
2037 {
2038   mutable std::size_t m_index;
2039   scene::Path& m_path;
2040 public:
2041   EntityFindByIndexWalker(std::size_t index, scene::Path& path)
2042     : m_index(index), m_path(path)
2043   {
2044   }
2045   bool pre(scene::Node& node) const
2046   {
2047     if(Node_isEntity(node) && m_index-- == 0)
2048     {
2049       m_path.push(makeReference(node));
2050     }
2051     return false;
2052   }
2053 };
2054
2055 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path& path)
2056 {
2057   path.push(makeReference(GlobalSceneGraph().root()));
2058   {
2059     Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2060   }
2061   if(path.size() == 2)
2062   {
2063     scene::Traversable* traversable = Node_getTraversable(path.top());
2064     if(traversable != 0)
2065     {
2066       traversable->traverse(BrushFindByIndexWalker(brush, path));
2067     }
2068   }
2069 }
2070
2071 inline bool Node_hasChildren(scene::Node& node)
2072 {
2073   scene::Traversable* traversable = Node_getTraversable(node);
2074   return traversable != 0 && !traversable->empty();
2075 }
2076
2077 void SelectBrush (int entitynum, int brushnum)
2078 {
2079   scene::Path path;
2080   Scene_FindEntityBrush(entitynum, brushnum, path);
2081   if(path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top())))
2082   {
2083     scene::Instance* instance = GlobalSceneGraph().find(path);
2084     ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2085     Selectable* selectable = Instance_getSelectable(*instance);
2086     ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2087     selectable->setSelected(true);
2088     g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2089   }
2090 }
2091
2092
2093 class BrushFindIndexWalker : public scene::Graph::Walker
2094 {
2095   mutable const scene::Node* m_node;
2096   std::size_t& m_count;
2097 public:
2098   BrushFindIndexWalker(const scene::Node& node, std::size_t& count)
2099     : m_node(&node), m_count(count)
2100   {
2101   }
2102   bool pre(const scene::Path& path, scene::Instance& instance) const
2103   {
2104     if(Node_isPrimitive(path.top()))
2105     {
2106       if(m_node == path.top().get_pointer())
2107       {
2108         m_node = 0;
2109       }
2110       if(m_node)
2111       {
2112         ++m_count;
2113       }
2114     }
2115     return true;
2116   }
2117 };
2118
2119 class EntityFindIndexWalker : public scene::Graph::Walker
2120 {
2121   mutable const scene::Node* m_node;
2122   std::size_t& m_count;
2123 public:
2124   EntityFindIndexWalker(const scene::Node& node, std::size_t& count)
2125     : m_node(&node), m_count(count)
2126   {
2127   }
2128   bool pre(const scene::Path& path, scene::Instance& instance) const
2129   {
2130     if(Node_isEntity(path.top()))
2131     {
2132       if(m_node == path.top().get_pointer())
2133       {
2134         m_node = 0;
2135       }
2136       if(m_node)
2137       {
2138         ++m_count;
2139       }
2140     }
2141     return true;
2142   }
2143 };
2144
2145 static void GetSelectionIndex (int *ent, int *brush)
2146 {
2147   std::size_t count_brush = 0;
2148   std::size_t count_entity = 0;
2149   if(GlobalSelectionSystem().countSelected() != 0)
2150   {
2151     const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2152
2153     GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2154     GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2155   }
2156   *brush = int(count_brush);
2157   *ent = int(count_entity);
2158 }
2159
2160 void DoFind()
2161 {
2162   ModalDialog dialog;
2163   GtkEntry* entity;
2164   GtkEntry* brush;
2165
2166   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Find Brush", G_CALLBACK(dialog_delete_callback), &dialog);
2167
2168   GtkAccelGroup* accel = gtk_accel_group_new();
2169   gtk_window_add_accel_group(window, accel);
2170
2171   {
2172     GtkVBox* vbox = create_dialog_vbox(4, 4);
2173     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
2174     {
2175       GtkTable* table = create_dialog_table(2, 2, 4, 4);
2176       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), TRUE, TRUE, 0);
2177       {
2178         GtkWidget* label = gtk_label_new ("Entity number");
2179         gtk_widget_show (label);
2180         gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
2181                           (GtkAttachOptions) (0),
2182                           (GtkAttachOptions) (0), 0, 0);
2183       }
2184       {
2185         GtkWidget* label = gtk_label_new ("Brush number");
2186         gtk_widget_show (label);
2187         gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
2188                           (GtkAttachOptions) (0),
2189                           (GtkAttachOptions) (0), 0, 0);
2190       }
2191       {
2192         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
2193         gtk_widget_show(GTK_WIDGET(entry));
2194         gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1,
2195                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
2196                           (GtkAttachOptions) (0), 0, 0);
2197         gtk_widget_grab_focus(GTK_WIDGET(entry));
2198         entity = entry;
2199       }
2200       {
2201         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
2202         gtk_widget_show(GTK_WIDGET(entry));
2203         gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2,
2204                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
2205                           (GtkAttachOptions) (0), 0, 0);
2206
2207         brush = entry;
2208       }
2209     }
2210     {
2211       GtkHBox* hbox = create_dialog_hbox(4);
2212       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0);
2213       {
2214         GtkButton* button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2215         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
2216         widget_make_default(GTK_WIDGET(button));
2217         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
2218       }
2219       {
2220         GtkButton* button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2221         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
2222         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
2223       }
2224     }
2225   }
2226
2227   // Initialize dialog
2228   char buf[16];
2229   int ent, br;
2230
2231   GetSelectionIndex (&ent, &br);
2232   sprintf (buf, "%i", ent);
2233   gtk_entry_set_text(entity, buf);
2234   sprintf (buf, "%i", br);
2235   gtk_entry_set_text(brush, buf);
2236
2237   if(modal_dialog_show(window, dialog) == eIDOK)
2238   {
2239     const char *entstr = gtk_entry_get_text(entity);
2240     const char *brushstr = gtk_entry_get_text(brush);
2241     SelectBrush (atoi(entstr), atoi(brushstr));
2242   }
2243
2244   gtk_widget_destroy(GTK_WIDGET(window));
2245 }
2246
2247 void Map_constructPreferences(PreferencesPage& page)
2248 {
2249   page.appendCheckBox("", "Load last map on open", g_bLoadLastMap);
2250 }
2251
2252
2253 class MapEntityClasses : public ModuleObserver
2254 {
2255   std::size_t m_unrealised;
2256 public:
2257   MapEntityClasses() : m_unrealised(1)
2258   {
2259   }
2260   void realise()
2261   {
2262     if(--m_unrealised == 0)
2263     {
2264       if(g_map.m_resource != 0)
2265       {
2266         ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2267               g_map.m_resource->realise();
2268       }
2269     }
2270   }
2271   void unrealise()
2272   {
2273     if(++m_unrealised == 1)
2274     {
2275       if(g_map.m_resource != 0)
2276       {
2277         g_map.m_resource->flush();
2278               g_map.m_resource->unrealise();
2279       }
2280     }
2281   }
2282 };
2283
2284 MapEntityClasses g_MapEntityClasses;
2285
2286
2287 class MapModuleObserver : public ModuleObserver
2288 {
2289   std::size_t m_unrealised;
2290 public:
2291   MapModuleObserver() : m_unrealised(1)
2292   {
2293   }
2294   void realise()
2295   {
2296     if(--m_unrealised == 0)
2297     {
2298       ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()), "maps_directory: user-game-path is empty");
2299       StringOutputStream buffer(256);
2300       buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2301       Q_mkdir(buffer.c_str());
2302       g_mapsPath = buffer.c_str();
2303     }
2304   }
2305   void unrealise()
2306   {
2307     if(++m_unrealised == 1)
2308     {
2309       g_mapsPath = "";
2310     }
2311   }
2312 };
2313
2314 MapModuleObserver g_MapModuleObserver;
2315
2316 #include "preferencesystem.h"
2317
2318 CopiedString g_strLastMap;
2319 bool g_bLoadLastMap = false;
2320
2321 void Map_Construct()
2322 {
2323   GlobalCommands_insert("RegionOff", FreeCaller<RegionOff>());
2324   GlobalCommands_insert("RegionSetXY", FreeCaller<RegionXY>());
2325   GlobalCommands_insert("RegionSetBrush", FreeCaller<RegionBrush>());
2326   GlobalCommands_insert("RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator('R', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK)));
2327
2328   GlobalPreferenceSystem().registerPreference("LastMap", CopiedStringImportStringCaller(g_strLastMap), CopiedStringExportStringCaller(g_strLastMap));
2329   GlobalPreferenceSystem().registerPreference("LoadLastMap", BoolImportStringCaller(g_bLoadLastMap), BoolExportStringCaller(g_bLoadLastMap));
2330   GlobalPreferenceSystem().registerPreference("MapInfoDlg", WindowPositionImportStringCaller(g_posMapInfoWnd), WindowPositionExportStringCaller(g_posMapInfoWnd));
2331   
2332   PreferencesDialog_addSettingsPreferences(FreeCaller1<PreferencesPage&, Map_constructPreferences>());
2333
2334   GlobalEntityClassManager().attach(g_MapEntityClasses);
2335   Radiant_attachHomePathsObserver(g_MapModuleObserver);
2336 }
2337
2338 void Map_Destroy()
2339 {
2340   Radiant_detachHomePathsObserver(g_MapModuleObserver);
2341   GlobalEntityClassManager().detach(g_MapEntityClasses);
2342 }