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