]> icculus.org git repositories - divverent/netradiant.git/blob - radiant/map.cpp
rename to toggleFormat
[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       for(i = 1; i < Brush_toggleFormatCount(); ++i)
1050       {
1051         Map_Free();
1052         Brush_toggleFormat(i);
1053         g_map.m_name = filename;
1054         Map_UpdateTitle(g_map);
1055         g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1056         g_map.m_resource->attach(g_map);
1057       }
1058     }
1059
1060     Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
1061   }
1062
1063   globalOutputStream() << "--- LoadMapFile ---\n";
1064   globalOutputStream() << g_map.m_name.c_str() << "\n";
1065   
1066   globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
1067   globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
1068
1069   //GlobalEntityCreator().printStatistics();
1070   
1071   //
1072   // move the view to a start position
1073   //
1074   Map_StartPosition();
1075
1076   g_currentMap = &g_map;
1077 }
1078
1079 class Excluder
1080 {
1081 public:
1082   virtual bool excluded(scene::Node& node) const = 0;
1083 };
1084
1085 class ExcludeWalker : public scene::Traversable::Walker
1086 {
1087   const scene::Traversable::Walker& m_walker;
1088   const Excluder* m_exclude;
1089   mutable bool m_skip;
1090 public:
1091   ExcludeWalker(const scene::Traversable::Walker& walker, const Excluder& exclude)
1092     : m_walker(walker), m_exclude(&exclude), m_skip(false)
1093   {
1094   }
1095   bool pre(scene::Node& node) const
1096   {
1097     if(m_exclude->excluded(node) || node.isRoot())
1098     {
1099       m_skip = true;
1100       return false;
1101     }
1102     else
1103     {
1104       m_walker.pre(node);
1105     }
1106     return true;
1107   }
1108   void post(scene::Node& node) const
1109   {
1110     if(m_skip)
1111     {
1112       m_skip = false;
1113     }
1114     else
1115     {
1116       m_walker.post(node);
1117     }
1118   }
1119 };
1120
1121 class AnyInstanceSelected : public scene::Instantiable::Visitor
1122 {
1123   bool& m_selected;
1124 public:
1125   AnyInstanceSelected(bool& selected) : m_selected(selected)
1126   {
1127     m_selected = false;
1128   }
1129   void visit(scene::Instance& instance) const
1130   {
1131     Selectable* selectable = Instance_getSelectable(instance);
1132     if(selectable != 0
1133       && selectable->isSelected())
1134     {
1135       m_selected = true;
1136     }
1137   }
1138 };
1139
1140 bool Node_instanceSelected(scene::Node& node)
1141 {
1142   scene::Instantiable* instantiable = Node_getInstantiable(node);
1143   ASSERT_NOTNULL(instantiable);
1144   bool selected;
1145   instantiable->forEachInstance(AnyInstanceSelected(selected));
1146   return selected;
1147 }
1148
1149 class SelectedDescendantWalker : public scene::Traversable::Walker
1150 {
1151   bool& m_selected;
1152 public:
1153   SelectedDescendantWalker(bool& selected) : m_selected(selected)
1154   {
1155     m_selected = false;
1156   }
1157
1158   bool pre(scene::Node& node) const
1159   {
1160     if(node.isRoot())
1161     {
1162       return false;
1163     }
1164
1165     if(Node_instanceSelected(node))
1166     {
1167       m_selected = true;
1168     }
1169
1170     return true;
1171   }
1172 };
1173
1174 bool Node_selectedDescendant(scene::Node& node)
1175 {
1176   bool selected;
1177   Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
1178   return selected;
1179 }
1180
1181 class SelectionExcluder : public Excluder
1182 {
1183 public:
1184   bool excluded(scene::Node& node) const
1185   {
1186     return !Node_selectedDescendant(node);
1187   }
1188 };
1189
1190 class IncludeSelectedWalker : public scene::Traversable::Walker
1191 {
1192   const scene::Traversable::Walker& m_walker;
1193   mutable std::size_t m_selected;
1194   mutable bool m_skip;
1195
1196   bool selectedParent() const
1197   {
1198     return m_selected != 0;
1199   }
1200 public:
1201   IncludeSelectedWalker(const scene::Traversable::Walker& walker)
1202     : m_walker(walker), m_selected(0), m_skip(false)
1203   {
1204   }
1205   bool pre(scene::Node& node) const
1206   {
1207     // include node if:
1208     // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1209     if(!node.isRoot() && (Node_selectedDescendant(node) || selectedParent()))
1210     {
1211       if(Node_instanceSelected(node))
1212       {
1213         ++m_selected;
1214       }
1215       m_walker.pre(node);
1216       return true;
1217     }
1218     else
1219     {
1220       m_skip = true;
1221       return false;
1222     }
1223   }
1224   void post(scene::Node& node) const
1225   {
1226     if(m_skip)
1227     {
1228       m_skip = false;
1229     }
1230     else
1231     {
1232       if(Node_instanceSelected(node))
1233       {
1234         --m_selected;
1235       }
1236       m_walker.post(node);
1237     }
1238   }
1239 };
1240
1241 void Map_Traverse_Selected(scene::Node& root, const scene::Traversable::Walker& walker)
1242 {
1243   scene::Traversable* traversable = Node_getTraversable(root);
1244   if(traversable != 0)
1245   {
1246 #if 0
1247     traversable->traverse(ExcludeWalker(walker, SelectionExcluder()));
1248 #else
1249     traversable->traverse(IncludeSelectedWalker(walker));
1250 #endif
1251   }
1252 }
1253
1254 void Map_ExportSelected(TextOutputStream& out, const MapFormat& format)
1255 {
1256   format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out);
1257 }
1258
1259 void Map_Traverse(scene::Node& root, const scene::Traversable::Walker& walker)
1260 {
1261   scene::Traversable* traversable = Node_getTraversable(root);
1262   if(traversable != 0)
1263   {
1264     traversable->traverse(walker);
1265   }
1266 }
1267
1268 class RegionExcluder : public Excluder
1269 {
1270 public:
1271   bool excluded(scene::Node& node) const
1272   {
1273     return node.excluded();
1274   }
1275 };
1276
1277 void Map_Traverse_Region(scene::Node& root, const scene::Traversable::Walker& walker)
1278 {
1279   scene::Traversable* traversable = Node_getTraversable(root);
1280   if(traversable != 0)
1281   {
1282     traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
1283   }
1284 }
1285
1286 bool Map_SaveRegion(const char *filename)
1287 {
1288   AddRegionBrushes();
1289
1290   bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region, filename); 
1291
1292   RemoveRegionBrushes();
1293
1294   return success;
1295 }
1296
1297
1298 void Map_RenameAbsolute(const char* absolute)
1299 {
1300   Resource* resource = GlobalReferenceCache().capture(absolute);
1301   NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
1302   resource->setNode(clone.get_pointer());
1303
1304   {
1305     //ScopeTimer timer("clone subgraph");
1306     Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
1307   }
1308
1309   g_map.m_resource->detach(g_map);
1310   GlobalReferenceCache().release(g_map.m_name.c_str());
1311
1312   g_map.m_resource = resource;
1313
1314   g_map.m_name = absolute;
1315   Map_UpdateTitle(g_map);
1316
1317   g_map.m_resource->attach(g_map);
1318 }
1319
1320 void Map_Rename(const char* filename)
1321 {
1322   if(!string_equal(g_map.m_name.c_str(), filename))
1323   {
1324     ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1325
1326     Map_RenameAbsolute(filename);
1327     
1328     SceneChangeNotify();
1329   }
1330   else
1331   {
1332     SaveReferences();
1333   }
1334 }
1335
1336 bool Map_Save()
1337 {
1338         Pointfile_Clear();
1339
1340   ScopeTimer timer("map save");
1341   SaveReferences();
1342   return true; // assume success..
1343 }
1344
1345 /*
1346 ===========
1347 Map_New
1348
1349 ===========
1350 */
1351 void Map_New()
1352 {
1353         //globalOutputStream() << "Map_New\n";
1354
1355         g_map.m_name = "unnamed.map";
1356   Map_UpdateTitle(g_map);
1357
1358   {
1359     g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1360 //    ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1361     g_map.m_resource->attach(g_map);
1362
1363     SceneChangeNotify();
1364   }
1365
1366   FocusViews(g_vector3_identity, 0);
1367
1368   g_currentMap = &g_map;
1369 }
1370
1371 extern void ConstructRegionBrushes(scene::Node* brushes[6], const Vector3& region_mins, const Vector3& region_maxs);
1372
1373 void ConstructRegionStartpoint(scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs)
1374 {
1375   /*! 
1376   \todo we need to make sure that the player start IS inside the region and bail out if it's not
1377   the compiler will refuse to compile a map with a player_start somewhere in empty space..
1378   for now, let's just print an error
1379   */
1380   
1381   Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
1382
1383   for (int i=0 ; i<3 ; i++)
1384   {
1385     if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i])
1386     {
1387       globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1388       break;
1389     }
1390   }
1391   
1392   // write the info_playerstart
1393   char sTmp[1024];
1394   sprintf(sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2]);
1395   Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
1396   sprintf(sTmp, "%d", (int)Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
1397   Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
1398 }
1399
1400 /*
1401 ===========================================================
1402
1403   REGION
1404
1405 ===========================================================
1406 */
1407 bool    region_active;
1408 Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
1409 Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
1410
1411 scene::Node* region_sides[6];
1412 scene::Node* region_startpoint = 0;
1413
1414 /*
1415 ===========
1416 AddRegionBrushes
1417 a regioned map will have temp walls put up at the region boundary
1418 \todo TODO TTimo old implementation of region brushes
1419   we still add them straight in the worldspawn and take them out after the map is saved
1420   with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1421 ===========
1422 */
1423 void AddRegionBrushes (void)
1424 {
1425         int             i;
1426
1427   for(i=0; i<6; i++)
1428   {
1429     region_sides[i] = &GlobalBrushCreator().createBrush();
1430     Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
1431   }
1432
1433   region_startpoint = &GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("info_player_start", false));
1434
1435   ConstructRegionBrushes(region_sides, region_mins, region_maxs);
1436   ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
1437
1438   Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
1439 }
1440
1441 void RemoveRegionBrushes (void)
1442 {
1443   for(std::size_t i=0; i<6; i++)
1444   {
1445     Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
1446   }
1447   Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
1448 }
1449
1450 inline void exclude_node(scene::Node& node, bool exclude)
1451 {
1452   exclude
1453     ? node.enable(scene::Node::eExcluded)
1454     : node.disable(scene::Node::eExcluded);
1455 }
1456
1457 class ExcludeAllWalker : public scene::Graph::Walker
1458 {
1459   bool m_exclude;
1460 public:
1461   ExcludeAllWalker(bool exclude)
1462     : m_exclude(exclude)
1463   {
1464   }
1465   bool pre(const scene::Path& path, scene::Instance& instance) const
1466   {
1467     exclude_node(path.top(), m_exclude);
1468
1469     return true;
1470   }
1471 };
1472
1473 void Scene_Exclude_All(bool exclude)
1474 {
1475   GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
1476 }
1477
1478 bool Instance_isSelected(const scene::Instance& instance)
1479 {
1480   const Selectable* selectable = Instance_getSelectable(instance);
1481   return selectable != 0 && selectable->isSelected();
1482 }
1483
1484 class ExcludeSelectedWalker : public scene::Graph::Walker
1485 {
1486   bool m_exclude;
1487 public:
1488   ExcludeSelectedWalker(bool exclude)
1489     : m_exclude(exclude)
1490   {
1491   }
1492   bool pre(const scene::Path& path, scene::Instance& instance) const
1493   {
1494     exclude_node(path.top(), (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
1495     return true;
1496   }
1497 };
1498
1499 void Scene_Exclude_Selected(bool exclude)
1500 {
1501   GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
1502 }
1503
1504 class ExcludeRegionedWalker : public scene::Graph::Walker
1505 {
1506   bool m_exclude;
1507 public:
1508   ExcludeRegionedWalker(bool exclude)
1509     : m_exclude(exclude)
1510   {
1511   }
1512   bool pre(const scene::Path& path, scene::Instance& instance) const
1513   {
1514     exclude_node(
1515       path.top(),
1516       !(
1517         (
1518           aabb_intersects_aabb(
1519             instance.worldAABB(),
1520             aabb_for_minmax(region_mins, region_maxs)
1521           ) != 0
1522         ) ^ m_exclude
1523       )
1524     );
1525
1526     return true;
1527   }
1528 };
1529
1530 void Scene_Exclude_Region(bool exclude)
1531 {
1532   GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
1533 }
1534
1535 /*
1536 ===========
1537 Map_RegionOff
1538
1539 Other filtering options may still be on
1540 ===========
1541 */
1542 void Map_RegionOff()
1543 {
1544         region_active = false;
1545
1546         region_maxs[0] = g_MaxWorldCoord - 64;
1547         region_mins[0] = g_MinWorldCoord + 64;
1548         region_maxs[1] = g_MaxWorldCoord - 64;
1549         region_mins[1] = g_MinWorldCoord + 64;
1550         region_maxs[2] = g_MaxWorldCoord - 64;
1551         region_mins[2] = g_MinWorldCoord + 64;
1552         
1553         Scene_Exclude_All(false);
1554 }
1555
1556 void Map_ApplyRegion (void)
1557 {
1558         region_active = true;
1559
1560         Scene_Exclude_Region(false);
1561 }
1562
1563
1564 /*
1565 ========================
1566 Map_RegionSelectedBrushes
1567 ========================
1568 */
1569 void Map_RegionSelectedBrushes (void)
1570 {
1571         Map_RegionOff();
1572
1573   if(GlobalSelectionSystem().countSelected() != 0
1574     && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive)
1575   {
1576           region_active = true;
1577           Select_GetBounds (region_mins, region_maxs);
1578
1579           Scene_Exclude_Selected(false);
1580     
1581     GlobalSelectionSystem().setSelectedAll(false);
1582   }
1583 }
1584
1585
1586 /*
1587 ===========
1588 Map_RegionXY
1589 ===========
1590 */
1591 void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
1592 {
1593         Map_RegionOff();
1594
1595         region_mins[0] = x_min;
1596   region_maxs[0] = x_max;
1597   region_mins[1] = y_min;
1598   region_maxs[1] = y_max;
1599   region_mins[2] = g_MinWorldCoord + 64;
1600         region_maxs[2] = g_MaxWorldCoord - 64;
1601
1602         Map_ApplyRegion();
1603 }
1604
1605 void Map_RegionBounds(const AABB& bounds)
1606 {
1607   Map_RegionOff();
1608
1609   region_mins = vector3_subtracted(bounds.origin, bounds.extents);
1610   region_maxs = vector3_added(bounds.origin, bounds.extents);
1611
1612   deleteSelection();
1613
1614   Map_ApplyRegion();
1615 }
1616
1617 /*
1618 ===========
1619 Map_RegionBrush
1620 ===========
1621 */
1622 void Map_RegionBrush (void)
1623 {
1624   if(GlobalSelectionSystem().countSelected() != 0)
1625   {
1626     scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1627     Map_RegionBounds(instance.worldAABB());
1628   }
1629 }
1630
1631 //
1632 //================
1633 //Map_ImportFile
1634 //================
1635 //
1636 bool Map_ImportFile(const char* filename)
1637 {
1638   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1639
1640   bool success = false;
1641   {
1642     Resource* resource = GlobalReferenceCache().capture(filename);
1643     resource->refresh(); // avoid loading old version if map has changed on disk since last import
1644     if(resource->load())
1645     {
1646       NodeSmartReference clone(NewMapRoot(""));
1647
1648       {
1649         //ScopeTimer timer("clone subgraph");
1650         Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1651       }
1652
1653       Map_gatherNamespaced(clone);
1654       Map_mergeClonedNames();
1655       MergeMap(clone);
1656       success = true;
1657     }
1658     GlobalReferenceCache().release(filename);
1659   }
1660
1661         SceneChangeNotify();
1662
1663   return success;
1664 }
1665
1666 /*
1667 ===========
1668 Map_SaveFile
1669 ===========
1670 */
1671 bool Map_SaveFile(const char* filename)
1672 {
1673   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1674   return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename); 
1675 }
1676
1677 //
1678 //===========
1679 //Map_SaveSelected
1680 //===========
1681 //
1682 // Saves selected world brushes and whole entities with partial/full selections
1683 //
1684 bool Map_SaveSelected(const char* filename)
1685 {
1686   return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected, filename); 
1687 }
1688
1689
1690 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1691 {
1692   scene::Node& m_parent;
1693 public:
1694   ParentSelectedBrushesToEntityWalker(scene::Node& parent) : m_parent(parent)
1695   {
1696   }
1697   bool pre(const scene::Path& path, scene::Instance& instance) const
1698   {
1699     if(path.top().get_pointer() != &m_parent
1700       && Node_isPrimitive(path.top()))
1701     {
1702       Selectable* selectable = Instance_getSelectable(instance);
1703       if(selectable != 0
1704         && selectable->isSelected()
1705         && path.size() > 1)
1706       {
1707         return false;
1708       }
1709     }
1710     return true;
1711   }
1712   void post(const scene::Path& path, scene::Instance& instance) const
1713   {
1714     if(path.top().get_pointer() != &m_parent
1715       && Node_isPrimitive(path.top()))
1716     {
1717       Selectable* selectable = Instance_getSelectable(instance);
1718       if(selectable != 0
1719         && selectable->isSelected()
1720         && path.size() > 1)
1721       {
1722         scene::Node& parent = path.parent();
1723         if(&parent != &m_parent)
1724         {
1725           NodeSmartReference node(path.top().get());
1726           Node_getTraversable(parent)->erase(node);
1727           Node_getTraversable(m_parent)->insert(node);
1728         }
1729       }
1730     }
1731   }
1732 };
1733
1734 void Scene_parentSelectedBrushesToEntity(scene::Graph& graph, scene::Node& parent)
1735 {
1736   graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1737 }
1738
1739 class CountSelectedBrushes : public scene::Graph::Walker
1740 {
1741   std::size_t& m_count;
1742   mutable std::size_t m_depth;
1743 public:
1744   CountSelectedBrushes(std::size_t& count) : m_count(count), m_depth(0)
1745   {
1746     m_count = 0;
1747   }
1748   bool pre(const scene::Path& path, scene::Instance& instance) const
1749   {
1750     if(++m_depth != 1 && path.top().get().isRoot())
1751     {
1752       return false;
1753     }
1754     Selectable* selectable = Instance_getSelectable(instance);
1755     if(selectable != 0
1756       && selectable->isSelected()
1757       && Node_isPrimitive(path.top()))
1758     {
1759       ++m_count;
1760     }
1761     return true;
1762   }
1763   void post(const scene::Path& path, scene::Instance& instance) const
1764   {
1765     --m_depth;
1766   }
1767 };
1768
1769 std::size_t Scene_countSelectedBrushes(scene::Graph& graph)
1770 {
1771   std::size_t count;
1772   graph.traverse(CountSelectedBrushes(count));
1773   return count;
1774 }
1775
1776 enum ENodeType
1777 {
1778   eNodeUnknown,
1779   eNodeMap,
1780   eNodeEntity,
1781   eNodePrimitive,
1782 };
1783
1784 const char* nodetype_get_name(ENodeType type)
1785 {
1786   if(type == eNodeMap)
1787     return "map";
1788   if(type == eNodeEntity)
1789     return "entity";
1790   if(type == eNodePrimitive)
1791     return "primitive";
1792   return "unknown";
1793 }
1794
1795 ENodeType node_get_nodetype(scene::Node& node)
1796 {
1797   if(Node_isEntity(node))
1798   {
1799     return eNodeEntity;
1800   }
1801   if(Node_isPrimitive(node))
1802   {
1803     return eNodePrimitive;
1804   }
1805   return eNodeUnknown;
1806 }
1807
1808 bool contains_entity(scene::Node& node)
1809 {
1810   return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1811 }
1812
1813 bool contains_primitive(scene::Node& node)
1814 {
1815   return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1816 }
1817
1818 ENodeType node_get_contains(scene::Node& node)
1819 {
1820   if(contains_entity(node))
1821   {
1822     return eNodeEntity;
1823   }
1824   if(contains_primitive(node))
1825   {
1826     return eNodePrimitive;
1827   }
1828   return eNodeUnknown;
1829 }
1830
1831 void Path_parent(const scene::Path& parent, const scene::Path& child)
1832 {
1833   ENodeType contains = node_get_contains(parent.top());
1834   ENodeType type = node_get_nodetype(child.top());
1835
1836   if(contains != eNodeUnknown && contains == type)
1837   {
1838     NodeSmartReference node(child.top().get());
1839     Path_deleteTop(child);
1840     Node_getTraversable(parent.top())->insert(node);
1841     SceneChangeNotify();
1842   }
1843   else
1844   {
1845     globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to " << nodetype_get_name(contains) << " container.\n";
1846   }
1847 }
1848
1849 void Scene_parentSelected()
1850 {
1851   UndoableCommand undo("parentSelected");
1852
1853   if(GlobalSelectionSystem().countSelected() > 1)
1854   {
1855     class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1856     {
1857       const scene::Path& m_parent;
1858     public:
1859       ParentSelectedBrushesToEntityWalker(const scene::Path& parent) : m_parent(parent)
1860       {
1861       }
1862       void visit(scene::Instance& instance) const
1863       {
1864         if(&m_parent != &instance.path())
1865         {
1866           Path_parent(m_parent, instance.path());
1867         }
1868       }
1869     };
1870     
1871     ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1872     GlobalSelectionSystem().foreachSelected(visitor);
1873   }
1874   else
1875   {
1876     globalOutputStream() << "failed - did not find two selected nodes.\n";
1877   }
1878 }
1879
1880
1881
1882 void NewMap()
1883 {
1884   if (ConfirmModified("New Map"))
1885   {
1886     Map_RegionOff();
1887           Map_Free();
1888     Map_New();
1889   }
1890 }
1891
1892 CopiedString g_mapsPath;
1893
1894 const char* getMapsPath()
1895 {
1896   return g_mapsPath.c_str();
1897 }
1898
1899 const char* map_open(const char* title)
1900 {
1901   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), TRUE, title, getMapsPath(), MapFormat::Name());
1902 }
1903
1904 const char* map_save(const char* title)
1905 {
1906   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), FALSE, title, getMapsPath(), MapFormat::Name());
1907 }
1908
1909 void OpenMap()
1910 {
1911   if (!ConfirmModified("Open Map"))
1912     return;
1913
1914   const char* filename = map_open("Open Map");
1915
1916   if (filename != 0)
1917   {
1918     MRU_AddFile(filename);
1919     Map_RegionOff();
1920     Map_Free();
1921     Map_LoadFile(filename);
1922   }
1923 }
1924
1925 void ImportMap()
1926 {
1927   const char* filename = map_open("Import Map");
1928
1929   if(filename != 0)
1930   {
1931     UndoableCommand undo("mapImport");
1932     Map_ImportFile(filename);
1933   }
1934 }
1935
1936 bool Map_SaveAs()
1937 {
1938   const char* filename = map_save("Save Map");
1939   
1940   if(filename != 0)
1941   {
1942     MRU_AddFile(filename);
1943     Map_Rename(filename);
1944     return Map_Save();
1945   }
1946   return false;
1947 }
1948
1949 void SaveMapAs()
1950 {
1951   Map_SaveAs();
1952 }
1953
1954 void SaveMap()
1955 {
1956   if(Map_Unnamed(g_map))
1957   {
1958     SaveMapAs();
1959   }
1960   else if(Map_Modified(g_map))
1961   {
1962     Map_Save();
1963   }
1964 }
1965
1966 void ExportMap()
1967 {
1968   const char* filename = map_save("Export Selection");
1969
1970   if(filename != 0)
1971   {
1972     Map_SaveSelected(filename);
1973   }
1974 }
1975
1976 void SaveRegion()
1977 {
1978   const char* filename = map_save("Export Region");
1979   
1980   if(filename != 0)
1981   {
1982     Map_SaveRegion(filename);
1983   }
1984 }
1985
1986
1987 void RegionOff()
1988 {
1989   Map_RegionOff();
1990   SceneChangeNotify();
1991 }
1992
1993 void RegionXY()
1994 {
1995   Map_RegionXY(
1996     g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1997     g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1998     g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1999     g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2000     );
2001   SceneChangeNotify();
2002 }
2003
2004 void RegionBrush()
2005 {
2006   Map_RegionBrush();
2007   SceneChangeNotify();
2008 }
2009
2010 void RegionSelected()
2011 {
2012   Map_RegionSelectedBrushes();
2013   SceneChangeNotify();
2014 }
2015
2016
2017
2018
2019
2020 class BrushFindByIndexWalker : public scene::Traversable::Walker
2021 {
2022   mutable std::size_t m_index;
2023   scene::Path& m_path;
2024 public:
2025   BrushFindByIndexWalker(std::size_t index, scene::Path& path)
2026     : m_index(index), m_path(path)
2027   {
2028   }
2029   bool pre(scene::Node& node) const
2030   {
2031     if(Node_isPrimitive(node) && m_index-- == 0)
2032     {
2033       m_path.push(makeReference(node));
2034     }
2035     return false;
2036   }
2037 };
2038
2039 class EntityFindByIndexWalker : public scene::Traversable::Walker
2040 {
2041   mutable std::size_t m_index;
2042   scene::Path& m_path;
2043 public:
2044   EntityFindByIndexWalker(std::size_t index, scene::Path& path)
2045     : m_index(index), m_path(path)
2046   {
2047   }
2048   bool pre(scene::Node& node) const
2049   {
2050     if(Node_isEntity(node) && m_index-- == 0)
2051     {
2052       m_path.push(makeReference(node));
2053     }
2054     return false;
2055   }
2056 };
2057
2058 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path& path)
2059 {
2060   path.push(makeReference(GlobalSceneGraph().root()));
2061   {
2062     Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2063   }
2064   if(path.size() == 2)
2065   {
2066     scene::Traversable* traversable = Node_getTraversable(path.top());
2067     if(traversable != 0)
2068     {
2069       traversable->traverse(BrushFindByIndexWalker(brush, path));
2070     }
2071   }
2072 }
2073
2074 inline bool Node_hasChildren(scene::Node& node)
2075 {
2076   scene::Traversable* traversable = Node_getTraversable(node);
2077   return traversable != 0 && !traversable->empty();
2078 }
2079
2080 void SelectBrush (int entitynum, int brushnum)
2081 {
2082   scene::Path path;
2083   Scene_FindEntityBrush(entitynum, brushnum, path);
2084   if(path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top())))
2085   {
2086     scene::Instance* instance = GlobalSceneGraph().find(path);
2087     ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2088     Selectable* selectable = Instance_getSelectable(*instance);
2089     ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2090     selectable->setSelected(true);
2091     g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2092   }
2093 }
2094
2095
2096 class BrushFindIndexWalker : public scene::Graph::Walker
2097 {
2098   mutable const scene::Node* m_node;
2099   std::size_t& m_count;
2100 public:
2101   BrushFindIndexWalker(const scene::Node& node, std::size_t& count)
2102     : m_node(&node), m_count(count)
2103   {
2104   }
2105   bool pre(const scene::Path& path, scene::Instance& instance) const
2106   {
2107     if(Node_isPrimitive(path.top()))
2108     {
2109       if(m_node == path.top().get_pointer())
2110       {
2111         m_node = 0;
2112       }
2113       if(m_node)
2114       {
2115         ++m_count;
2116       }
2117     }
2118     return true;
2119   }
2120 };
2121
2122 class EntityFindIndexWalker : public scene::Graph::Walker
2123 {
2124   mutable const scene::Node* m_node;
2125   std::size_t& m_count;
2126 public:
2127   EntityFindIndexWalker(const scene::Node& node, std::size_t& count)
2128     : m_node(&node), m_count(count)
2129   {
2130   }
2131   bool pre(const scene::Path& path, scene::Instance& instance) const
2132   {
2133     if(Node_isEntity(path.top()))
2134     {
2135       if(m_node == path.top().get_pointer())
2136       {
2137         m_node = 0;
2138       }
2139       if(m_node)
2140       {
2141         ++m_count;
2142       }
2143     }
2144     return true;
2145   }
2146 };
2147
2148 static void GetSelectionIndex (int *ent, int *brush)
2149 {
2150   std::size_t count_brush = 0;
2151   std::size_t count_entity = 0;
2152   if(GlobalSelectionSystem().countSelected() != 0)
2153   {
2154     const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2155
2156     GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2157     GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2158   }
2159   *brush = int(count_brush);
2160   *ent = int(count_entity);
2161 }
2162
2163 void DoFind()
2164 {
2165   ModalDialog dialog;
2166   GtkEntry* entity;
2167   GtkEntry* brush;
2168
2169   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Find Brush", G_CALLBACK(dialog_delete_callback), &dialog);
2170
2171   GtkAccelGroup* accel = gtk_accel_group_new();
2172   gtk_window_add_accel_group(window, accel);
2173
2174   {
2175     GtkVBox* vbox = create_dialog_vbox(4, 4);
2176     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
2177     {
2178       GtkTable* table = create_dialog_table(2, 2, 4, 4);
2179       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), TRUE, TRUE, 0);
2180       {
2181         GtkWidget* label = gtk_label_new ("Entity number");
2182         gtk_widget_show (label);
2183         gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
2184                           (GtkAttachOptions) (0),
2185                           (GtkAttachOptions) (0), 0, 0);
2186       }
2187       {
2188         GtkWidget* label = gtk_label_new ("Brush number");
2189         gtk_widget_show (label);
2190         gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
2191                           (GtkAttachOptions) (0),
2192                           (GtkAttachOptions) (0), 0, 0);
2193       }
2194       {
2195         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
2196         gtk_widget_show(GTK_WIDGET(entry));
2197         gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1,
2198                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
2199                           (GtkAttachOptions) (0), 0, 0);
2200         gtk_widget_grab_focus(GTK_WIDGET(entry));
2201         entity = entry;
2202       }
2203       {
2204         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
2205         gtk_widget_show(GTK_WIDGET(entry));
2206         gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2,
2207                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
2208                           (GtkAttachOptions) (0), 0, 0);
2209
2210         brush = entry;
2211       }
2212     }
2213     {
2214       GtkHBox* hbox = create_dialog_hbox(4);
2215       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0);
2216       {
2217         GtkButton* button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2218         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
2219         widget_make_default(GTK_WIDGET(button));
2220         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
2221       }
2222       {
2223         GtkButton* button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2224         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
2225         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
2226       }
2227     }
2228   }
2229
2230   // Initialize dialog
2231   char buf[16];
2232   int ent, br;
2233
2234   GetSelectionIndex (&ent, &br);
2235   sprintf (buf, "%i", ent);
2236   gtk_entry_set_text(entity, buf);
2237   sprintf (buf, "%i", br);
2238   gtk_entry_set_text(brush, buf);
2239
2240   if(modal_dialog_show(window, dialog) == eIDOK)
2241   {
2242     const char *entstr = gtk_entry_get_text(entity);
2243     const char *brushstr = gtk_entry_get_text(brush);
2244     SelectBrush (atoi(entstr), atoi(brushstr));
2245   }
2246
2247   gtk_widget_destroy(GTK_WIDGET(window));
2248 }
2249
2250 void Map_constructPreferences(PreferencesPage& page)
2251 {
2252   page.appendCheckBox("", "Load last map on open", g_bLoadLastMap);
2253 }
2254
2255
2256 class MapEntityClasses : public ModuleObserver
2257 {
2258   std::size_t m_unrealised;
2259 public:
2260   MapEntityClasses() : m_unrealised(1)
2261   {
2262   }
2263   void realise()
2264   {
2265     if(--m_unrealised == 0)
2266     {
2267       if(g_map.m_resource != 0)
2268       {
2269         ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2270               g_map.m_resource->realise();
2271       }
2272     }
2273   }
2274   void unrealise()
2275   {
2276     if(++m_unrealised == 1)
2277     {
2278       if(g_map.m_resource != 0)
2279       {
2280         g_map.m_resource->flush();
2281               g_map.m_resource->unrealise();
2282       }
2283     }
2284   }
2285 };
2286
2287 MapEntityClasses g_MapEntityClasses;
2288
2289
2290 class MapModuleObserver : public ModuleObserver
2291 {
2292   std::size_t m_unrealised;
2293 public:
2294   MapModuleObserver() : m_unrealised(1)
2295   {
2296   }
2297   void realise()
2298   {
2299     if(--m_unrealised == 0)
2300     {
2301       ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()), "maps_directory: user-game-path is empty");
2302       StringOutputStream buffer(256);
2303       buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2304       Q_mkdir(buffer.c_str());
2305       g_mapsPath = buffer.c_str();
2306     }
2307   }
2308   void unrealise()
2309   {
2310     if(++m_unrealised == 1)
2311     {
2312       g_mapsPath = "";
2313     }
2314   }
2315 };
2316
2317 MapModuleObserver g_MapModuleObserver;
2318
2319 #include "preferencesystem.h"
2320
2321 CopiedString g_strLastMap;
2322 bool g_bLoadLastMap = false;
2323
2324 void Map_Construct()
2325 {
2326   GlobalCommands_insert("RegionOff", FreeCaller<RegionOff>());
2327   GlobalCommands_insert("RegionSetXY", FreeCaller<RegionXY>());
2328   GlobalCommands_insert("RegionSetBrush", FreeCaller<RegionBrush>());
2329   GlobalCommands_insert("RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator('R', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK)));
2330
2331   GlobalPreferenceSystem().registerPreference("LastMap", CopiedStringImportStringCaller(g_strLastMap), CopiedStringExportStringCaller(g_strLastMap));
2332   GlobalPreferenceSystem().registerPreference("LoadLastMap", BoolImportStringCaller(g_bLoadLastMap), BoolExportStringCaller(g_bLoadLastMap));
2333   GlobalPreferenceSystem().registerPreference("MapInfoDlg", WindowPositionImportStringCaller(g_posMapInfoWnd), WindowPositionExportStringCaller(g_posMapInfoWnd));
2334   
2335   PreferencesDialog_addSettingsPreferences(FreeCaller1<PreferencesPage&, Map_constructPreferences>());
2336
2337   GlobalEntityClassManager().attach(g_MapEntityClasses);
2338   Radiant_attachHomePathsObserver(g_MapModuleObserver);
2339 }
2340
2341 void Map_Destroy()
2342 {
2343   Radiant_detachHomePathsObserver(g_MapModuleObserver);
2344   GlobalEntityClassManager().detach(g_MapEntityClasses);
2345 }