]> icculus.org git repositories - divverent/netradiant.git/blob - radiant/map.cpp
453f7c790793b4e0a2b8f06bb7a51d426c73cd0e
[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 #include "brush.h"
89
90 class NameObserver
91 {
92   UniqueNames& m_names;
93   CopiedString m_name;
94
95   void construct()
96   {
97     if(!empty())
98     {
99       //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
100       m_names.insert(name_read(c_str()));
101     }
102   }
103   void destroy()
104   {
105     if(!empty())
106     {
107       //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
108       m_names.erase(name_read(c_str()));
109     }
110   }
111
112   NameObserver& operator=(const NameObserver& other);
113 public:
114   NameObserver(UniqueNames& names) : m_names(names)
115   {
116     construct();
117   }
118   NameObserver(const NameObserver& other) : m_names(other.m_names), m_name(other.m_name)
119   {
120     construct();
121   }
122   ~NameObserver()
123   {
124     destroy();
125   }
126   bool empty() const
127   {
128     return string_empty(c_str()); 
129   }
130   const char* c_str() const
131   {
132     return m_name.c_str();
133   }
134   void nameChanged(const char* name)
135   {
136     destroy();
137     m_name = name;
138     construct();
139   }
140   typedef MemberCaller1<NameObserver, const char*, &NameObserver::nameChanged> NameChangedCaller;
141 };
142
143 class BasicNamespace : public Namespace
144 {
145   typedef std::map<NameCallback, NameObserver> Names;
146   Names m_names;
147   UniqueNames m_uniqueNames;
148 public:
149   ~BasicNamespace()
150   {
151     ASSERT_MESSAGE(m_names.empty(), "namespace: names still registered at shutdown");
152   }
153   void attach(const NameCallback& setName, const NameCallbackCallback& attachObserver)
154   {
155     std::pair<Names::iterator, bool> result = m_names.insert(Names::value_type(setName, m_uniqueNames));
156     ASSERT_MESSAGE(result.second, "cannot attach name");
157     attachObserver(NameObserver::NameChangedCaller((*result.first).second));
158     //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
159   }
160   void detach(const NameCallback& setName, const NameCallbackCallback& detachObserver)
161   {
162     Names::iterator i = m_names.find(setName);
163     ASSERT_MESSAGE(i != m_names.end(), "cannot detach name");
164     //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
165     detachObserver(NameObserver::NameChangedCaller((*i).second));
166     m_names.erase(i);
167   }
168
169   void makeUnique(const char* name, const NameCallback& setName) const
170   {
171     char buffer[1024];
172     name_write(buffer, m_uniqueNames.make_unique(name_read(name)));
173     setName(buffer);
174   }
175
176   void mergeNames(const BasicNamespace& other) const
177   {
178     typedef std::list<NameCallback> SetNameCallbacks;
179     typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
180     NameGroups groups;
181
182     UniqueNames uniqueNames(other.m_uniqueNames);
183
184     for(Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i)
185     {
186       groups[(*i).second.c_str()].push_back((*i).first);
187     }
188
189     for(NameGroups::iterator i = groups.begin(); i != groups.end(); ++i)
190     {
191       name_t uniqueName(uniqueNames.make_unique(name_read((*i).first.c_str())));
192       uniqueNames.insert(uniqueName);
193
194       char buffer[1024];
195       name_write(buffer, uniqueName);
196
197       //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
198
199       SetNameCallbacks& setNameCallbacks = (*i).second;
200
201       for(SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j)
202       {
203         (*j)(buffer);
204       }
205     }
206   }
207 };
208
209 BasicNamespace g_defaultNamespace;
210 BasicNamespace g_cloneNamespace;
211
212 class NamespaceAPI
213 {
214   Namespace* m_namespace;
215 public:
216   typedef Namespace Type;
217   STRING_CONSTANT(Name, "*");
218
219   NamespaceAPI()
220   {
221     m_namespace = &g_defaultNamespace;
222   }
223   Namespace* getTable()
224   {
225     return m_namespace;
226   }
227 };
228
229 typedef SingletonModule<NamespaceAPI> NamespaceModule;
230 typedef Static<NamespaceModule> StaticNamespaceModule;
231 StaticRegisterModule staticRegisterDefaultNamespace(StaticNamespaceModule::instance());
232
233
234 std::list<Namespaced*> g_cloned;
235
236 inline Namespaced* Node_getNamespaced(scene::Node& node)
237 {
238   return NodeTypeCast<Namespaced>::cast(node);
239 }
240
241 void Node_gatherNamespaced(scene::Node& node)
242 {
243   Namespaced* namespaced = Node_getNamespaced(node);
244   if(namespaced != 0)
245   {
246     g_cloned.push_back(namespaced);
247   }
248 }
249
250 class GatherNamespaced : public scene::Traversable::Walker
251 {
252 public:
253   bool pre(scene::Node& node) const
254   {
255     Node_gatherNamespaced(node);
256     return true;
257   }
258 };
259
260 void Map_gatherNamespaced(scene::Node& root)
261 {
262   Node_traverseSubgraph(root, GatherNamespaced());
263 }
264
265 void Map_mergeClonedNames()
266 {
267   for(std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i)
268   {
269     (*i)->setNamespace(g_cloneNamespace);
270   }
271   g_cloneNamespace.mergeNames(g_defaultNamespace);
272   for(std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i)
273   {
274     (*i)->setNamespace(g_defaultNamespace);
275   }
276
277   g_cloned.clear();
278 }
279
280 class WorldNode
281 {
282   scene::Node* m_node;
283 public:
284   WorldNode()
285     : m_node(0)
286   {
287   }
288   void set(scene::Node* node)
289   {
290     if(m_node != 0)
291       m_node->DecRef();
292     m_node = node;
293     if(m_node != 0)
294       m_node->IncRef();
295   }
296   scene::Node* get() const
297   {
298     return m_node;
299   }
300 };
301
302 class Map;
303 void Map_SetValid(Map& map, bool valid);
304 void Map_UpdateTitle(const Map& map);
305 void Map_SetWorldspawn(Map& map, scene::Node* node);
306
307
308 class Map : public ModuleObserver
309 {
310 public:
311   CopiedString m_name;
312   Resource* m_resource;
313   bool m_valid;
314
315   bool m_modified;
316   void (*m_modified_changed)(const Map&);
317
318   Signal0 m_mapValidCallbacks;
319
320   WorldNode m_world_node; // "classname" "worldspawn" !
321
322   Map() : m_resource(0), m_valid(false), m_modified_changed(Map_UpdateTitle)
323   {
324   }
325
326   void realise()
327   {
328     if(m_resource != 0)
329     {
330       if(Map_Unnamed(*this))
331       {
332         g_map.m_resource->setNode(NewMapRoot("").get_pointer());
333         MapFile* map = Node_getMapFile(*g_map.m_resource->getNode());
334         if(map != 0)
335         {
336           map->save();
337         }
338       }
339       else
340       {
341         m_resource->load();
342       }
343
344       GlobalSceneGraph().insert_root(*m_resource->getNode());
345
346       AutoSave_clear();
347
348       Map_SetValid(g_map, true);
349     }
350   }
351   void unrealise()
352   {
353     if(m_resource != 0)
354     {
355       Map_SetValid(g_map, false);
356       Map_SetWorldspawn(g_map, 0);
357
358
359       GlobalUndoSystem().clear();
360
361       GlobalSceneGraph().erase_root();
362     }
363   }
364 };
365
366 Map g_map;
367 Map* g_currentMap = 0;
368
369 void Map_addValidCallback(Map& map, const SignalHandler& handler)
370 {
371   map.m_mapValidCallbacks.connectLast(handler);
372 }
373
374 bool Map_Valid(const Map& map)
375 {
376   return map.m_valid;
377 }
378
379 void Map_SetValid(Map& map, bool valid)
380 {
381   map.m_valid = valid;
382   map.m_mapValidCallbacks();
383 }
384
385
386 const char* Map_Name(const Map& map)
387 {
388   return map.m_name.c_str();
389 }
390
391 bool Map_Unnamed(const Map& map)
392 {
393   return string_equal(Map_Name(map), "unnamed.map");
394 }
395
396 inline const MapFormat& MapFormat_forFile(const char* filename)
397 {
398   const char* moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), path_get_extension(filename));
399   MapFormat* format = Radiant_getMapModules().findModule(moduleName);
400   ASSERT_MESSAGE(format != 0, "map format not found for file " << makeQuoted(filename));
401   return *format;
402 }
403
404 const MapFormat& Map_getFormat(const Map& map)
405 {
406   return MapFormat_forFile(Map_Name(map));
407 }
408
409
410 bool Map_Modified(const Map& map)
411 {
412   return map.m_modified;
413 }
414
415 void Map_SetModified(Map& map, bool modified)
416 {
417   if(map.m_modified ^ modified)
418   {
419     map.m_modified = modified;
420
421     map.m_modified_changed(map);
422   }
423 }
424
425 void Map_UpdateTitle(const Map& map)
426 {
427   Sys_SetTitle(map.m_name.c_str(), Map_Modified(map));
428 }
429
430
431
432 scene::Node* Map_GetWorldspawn(const Map& map)
433 {
434   return map.m_world_node.get();
435 }
436
437 void Map_SetWorldspawn(Map& map, scene::Node* node)
438 {
439   map.m_world_node.set(node);
440 }
441
442
443 // TTimo
444 // need that in a variable, will have to tweak depending on the game
445 float g_MaxWorldCoord = 64*1024;
446 float g_MinWorldCoord = -64*1024;
447
448 void AddRegionBrushes (void);
449 void RemoveRegionBrushes (void);
450
451
452 /*
453 ================
454 Map_Free
455 free all map elements, reinitialize the structures that depend on them
456 ================
457 */
458 void Map_Free()
459 {
460         Pointfile_Clear();
461
462   g_map.m_resource->detach(g_map);
463   GlobalReferenceCache().release(g_map.m_name.c_str());
464   g_map.m_resource = 0;
465
466   FlushReferences();
467
468   g_currentMap = 0;
469   Brush_unlatchPreferences();
470 }
471
472 class EntityFindByClassname : public scene::Graph::Walker
473 {
474   const char* m_name;
475   Entity*& m_entity;
476 public:
477   EntityFindByClassname(const char* name, Entity*& entity) : m_name(name), m_entity(entity)
478   {
479     m_entity = 0;
480   }
481   bool pre(const scene::Path& path, scene::Instance& instance) const
482   {
483     if(m_entity == 0)
484     {
485       Entity* entity = Node_getEntity(path.top());
486       if(entity != 0
487         && string_equal(m_name, entity->getKeyValue("classname")))
488       {
489         m_entity = entity;
490       }
491     }
492     return true;
493   }
494 };
495
496 Entity* Scene_FindEntityByClass(const char* name)
497 {
498   Entity* entity;
499   GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
500   return entity;
501 }
502
503 Entity *Scene_FindPlayerStart()
504 {
505   typedef const char* StaticString;
506   StaticString strings[] = {
507     "info_player_start",
508     "info_player_deathmatch",
509     "team_CTF_redplayer",
510     "team_CTF_blueplayer",
511     "team_CTF_redspawn",
512     "team_CTF_bluespawn",
513   };
514   typedef const StaticString* StaticStringIterator;
515   for(StaticStringIterator i = strings, end = strings+(sizeof(strings)/sizeof(StaticString)); i != end; ++i)
516   {
517     Entity* entity = Scene_FindEntityByClass(*i);
518     if(entity != 0)
519     {
520       return entity;
521     }
522   }
523   return 0;
524 }
525
526 //
527 // move the view to a start position
528 //
529
530
531 void FocusViews(const Vector3& point, float angle)
532 {
533   CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
534   Camera_setOrigin(camwnd, point);
535   Vector3 angles(Camera_getAngles(camwnd));
536   angles[CAMERA_PITCH] = 0;
537   angles[CAMERA_YAW] = angle;
538   Camera_setAngles(camwnd, angles);
539
540   XYWnd* xywnd = g_pParentWnd->GetXYWnd();
541   xywnd->SetOrigin(point);
542 }
543
544 #include "stringio.h"
545
546 void Map_StartPosition()
547 {
548   Entity* entity = Scene_FindPlayerStart();
549
550   if (entity)
551   {
552     Vector3 origin;
553     string_parse_vector3(entity->getKeyValue("origin"), origin);
554     FocusViews(origin, string_read_float(entity->getKeyValue("angle")));
555   }
556   else
557   {
558     FocusViews(g_vector3_identity, 0);
559   }
560 }
561
562
563 inline bool node_is_worldspawn(scene::Node& node)
564 {
565   Entity* entity = Node_getEntity(node);
566   return entity != 0 && string_equal(entity->getKeyValue("classname"), "worldspawn");
567 }
568
569
570 // use first worldspawn
571 class entity_updateworldspawn : public scene::Traversable::Walker
572 {
573 public:
574   bool pre(scene::Node& node) const
575   {
576     if(node_is_worldspawn(node))
577     {
578       if(Map_GetWorldspawn(g_map) == 0)
579       {
580         Map_SetWorldspawn(g_map, &node);
581       }
582     }
583     return false;
584   }
585 };
586
587 scene::Node* Map_FindWorldspawn(Map& map)
588 {
589   Map_SetWorldspawn(map, 0);
590
591   Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
592
593   return Map_GetWorldspawn(map);
594 }
595
596
597 class CollectAllWalker : public scene::Traversable::Walker
598 {
599   scene::Node& m_root;
600   UnsortedNodeSet& m_nodes;
601 public:
602   CollectAllWalker(scene::Node& root, UnsortedNodeSet& nodes) : m_root(root), m_nodes(nodes)
603   {
604   }
605   bool pre(scene::Node& node) const
606   {
607     m_nodes.insert(NodeSmartReference(node));
608     Node_getTraversable(m_root)->erase(node);
609     return false;
610   }
611 };
612
613 void Node_insertChildFirst(scene::Node& parent, scene::Node& child)
614 {
615   UnsortedNodeSet nodes;
616   Node_getTraversable(parent)->traverse(CollectAllWalker(parent, nodes));
617   Node_getTraversable(parent)->insert(child);
618
619   for(UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i)
620   {
621     Node_getTraversable(parent)->insert((*i));
622   }
623 }
624
625 scene::Node& createWorldspawn()
626 {
627   NodeSmartReference worldspawn(GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("worldspawn", true)));
628   Node_insertChildFirst(GlobalSceneGraph().root(), worldspawn);
629   return worldspawn;
630 }
631
632 void Map_UpdateWorldspawn(Map& map)
633 {
634   if(Map_FindWorldspawn(map) == 0)
635   {
636     Map_SetWorldspawn(map, &createWorldspawn());
637   }
638 }
639
640 scene::Node& Map_FindOrInsertWorldspawn(Map& map)
641 {
642   Map_UpdateWorldspawn(map);
643   return *Map_GetWorldspawn(map);
644 }
645
646
647 class MapMergeAll : public scene::Traversable::Walker
648 {
649   mutable scene::Path m_path;
650 public:
651   MapMergeAll(const scene::Path& root)
652     : m_path(root)
653   {
654   }
655   bool pre(scene::Node& node) const
656   {
657     Node_getTraversable(m_path.top())->insert(node);
658     m_path.push(makeReference(node));
659     selectPath(m_path, true);
660     return false;
661   }
662   void post(scene::Node& node) const
663   {
664     m_path.pop();
665   }
666 };
667
668 class MapMergeEntities : public scene::Traversable::Walker
669 {
670   mutable scene::Path m_path;
671 public:
672   MapMergeEntities(const scene::Path& root)
673     : m_path(root)
674   {
675   }
676   bool pre(scene::Node& node) const
677   {
678     if(node_is_worldspawn(node))
679     {
680       scene::Node* world_node = Map_FindWorldspawn(g_map);
681       if(world_node == 0)
682       {
683         Map_SetWorldspawn(g_map, &node);
684         Node_getTraversable(m_path.top().get())->insert(node);
685         m_path.push(makeReference(node));
686         Node_getTraversable(node)->traverse(SelectChildren(m_path));
687       }
688       else
689       {
690         m_path.push(makeReference(*world_node));
691         Node_getTraversable(node)->traverse(MapMergeAll(m_path));
692       }
693     }
694     else
695     {
696       Node_getTraversable(m_path.top())->insert(node);
697       m_path.push(makeReference(node));
698       if(node_is_group(node))
699       {
700         Node_getTraversable(node)->traverse(SelectChildren(m_path));
701       }
702       else
703       {
704         selectPath(m_path, true);
705       }
706     }
707     return false;
708   }
709   void post(scene::Node& node) const
710   {
711     m_path.pop();
712   }
713 };
714
715 class BasicContainer : public scene::Node::Symbiot
716 {
717   class TypeCasts
718   {
719     NodeTypeCastTable m_casts;
720   public:
721     TypeCasts()
722     {
723       NodeContainedCast<BasicContainer, scene::Traversable>::install(m_casts);
724     }
725     NodeTypeCastTable& get()
726     {
727       return m_casts;
728     }
729   };
730
731   scene::Node m_node;
732   TraversableNodeSet m_traverse;
733 public:
734
735   typedef LazyStatic<TypeCasts> StaticTypeCasts;
736
737   scene::Traversable& get(NullType<scene::Traversable>)
738   {
739     return m_traverse;
740   }
741
742   BasicContainer() : m_node(this, this, StaticTypeCasts::instance().get())
743   {
744   }
745   void release()
746   {
747     delete this;
748   }
749   scene::Node& node()
750   {
751     return m_node;
752   }
753 };
754
755 /// Merges the map graph rooted at \p node into the global scene-graph.
756 void MergeMap(scene::Node& node)
757 {
758   Node_getTraversable(node)->traverse(MapMergeEntities(scene::Path(makeReference(GlobalSceneGraph().root()))));
759 }
760 void Map_ImportSelected(TextInputStream& in, const MapFormat& format)
761 {
762   NodeSmartReference node((new BasicContainer)->node());
763   format.readGraph(node, in, GlobalEntityCreator());
764   Map_gatherNamespaced(node);
765   Map_mergeClonedNames();
766   MergeMap(node);
767 }
768
769 inline scene::Cloneable* Node_getCloneable(scene::Node& node)
770 {
771   return NodeTypeCast<scene::Cloneable>::cast(node);
772 }
773
774 inline scene::Node& node_clone(scene::Node& node)
775 {
776   scene::Cloneable* cloneable = Node_getCloneable(node);
777   if(cloneable != 0)
778   {
779     return cloneable->clone();
780   }
781   
782   return (new scene::NullNode)->node();
783 }
784
785 class CloneAll : public scene::Traversable::Walker
786 {
787   mutable scene::Path m_path;
788 public:
789   CloneAll(scene::Node& root)
790     : m_path(makeReference(root))
791   {
792   }
793   bool pre(scene::Node& node) const
794   {
795     if(node.isRoot())
796     {
797       return false;
798     }
799     
800     m_path.push(makeReference(node_clone(node)));
801     m_path.top().get().IncRef();
802
803     return true;
804   }
805   void post(scene::Node& node) const
806   {
807     if(node.isRoot())
808     {
809       return;
810     }
811
812     Node_getTraversable(m_path.parent())->insert(m_path.top());
813
814     m_path.top().get().DecRef();
815     m_path.pop();
816   }
817 };
818
819 scene::Node& Node_Clone(scene::Node& node)
820 {
821   scene::Node& clone = node_clone(node);
822   scene::Traversable* traversable = Node_getTraversable(node);
823   if(traversable != 0)
824   {
825     traversable->traverse(CloneAll(clone));
826   }
827   return clone;
828 }
829
830
831 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
832
833 class EntityBreakdownWalker : public scene::Graph::Walker
834 {
835   EntityBreakdown& m_entitymap;
836 public:
837   EntityBreakdownWalker(EntityBreakdown& entitymap)
838     : m_entitymap(entitymap)
839   {
840   }
841   bool pre(const scene::Path& path, scene::Instance& instance) const
842   {
843     Entity* entity = Node_getEntity(path.top());
844     if(entity != 0)
845     {
846       const EntityClass& eclass = entity->getEntityClass();
847       if(m_entitymap.find(eclass.name()) == m_entitymap.end())
848       {
849         m_entitymap[eclass.name()] = 1;
850       }
851       else ++m_entitymap[eclass.name()];
852     }
853     return true;
854   }
855 };
856
857 void Scene_EntityBreakdown(EntityBreakdown& entitymap)
858 {
859   GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap));
860 }
861
862
863 WindowPosition g_posMapInfoWnd(c_default_window_pos);
864
865 void DoMapInfo()
866 {
867   ModalDialog dialog;
868   GtkEntry* brushes_entry;
869   GtkEntry* entities_entry;
870   GtkListStore* EntityBreakdownWalker;
871
872   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Map Info", G_CALLBACK(dialog_delete_callback), &dialog);
873
874   window_set_position(window, g_posMapInfoWnd);
875
876   {
877     GtkVBox* vbox = create_dialog_vbox(4, 4);
878     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
879
880     {
881       GtkHBox* hbox = create_dialog_hbox(4);
882       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, TRUE, 0);
883
884       {
885         GtkTable* table = create_dialog_table(2, 2, 4, 4);
886         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0);
887
888         {
889           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
890           gtk_widget_show(GTK_WIDGET(entry));
891           gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1,
892                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
893                             (GtkAttachOptions) (0), 0, 0);
894           gtk_entry_set_editable(entry, FALSE);
895
896           brushes_entry = entry;
897         }
898         {
899           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
900           gtk_widget_show(GTK_WIDGET(entry));
901           gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2,
902                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
903                             (GtkAttachOptions) (0), 0, 0);
904           gtk_entry_set_editable(entry, FALSE);
905
906           entities_entry = entry;
907         }
908         {
909           GtkWidget* label = gtk_label_new ("Total Brushes");
910           gtk_widget_show (label);
911           gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
912                             (GtkAttachOptions) (GTK_FILL),
913                             (GtkAttachOptions) (0), 0, 0);
914           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
915         }
916         {
917           GtkWidget* label = gtk_label_new ("Total Entities");
918           gtk_widget_show (label);
919           gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
920                             (GtkAttachOptions) (GTK_FILL),
921                             (GtkAttachOptions) (0), 0, 0);
922           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
923         }
924       }
925       {
926         GtkVBox* vbox2 = create_dialog_vbox(4);
927         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox2), FALSE, FALSE, 0);
928
929         {
930           GtkButton* button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog);
931           gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(button), FALSE, FALSE, 0);
932         }
933       }
934     }
935     {
936       GtkWidget* label = gtk_label_new ("Entity breakdown");
937       gtk_widget_show (label);
938       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, TRUE, 0);
939       gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
940     }
941     {
942       GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4);
943       gtk_box_pack_start(GTK_BOX (vbox), GTK_WIDGET(scr), TRUE, TRUE, 0);
944
945       {
946         GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
947
948         GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
949         gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(view), TRUE);
950
951         {
952           GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
953           GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Entity", renderer, "text", 0, 0);
954           gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
955           gtk_tree_view_column_set_sort_column_id(column, 0);
956         }
957
958         {
959           GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
960           GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Count", renderer, "text", 1, 0);
961           gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
962           gtk_tree_view_column_set_sort_column_id(column, 1);
963         }
964
965         gtk_widget_show(view);
966
967         gtk_container_add(GTK_CONTAINER(scr), view);
968         
969         EntityBreakdownWalker = store;
970       }
971     }
972   }
973
974   // Initialize fields
975
976   {
977     EntityBreakdown entitymap;
978     Scene_EntityBreakdown(entitymap);
979
980     for(EntityBreakdown::iterator i=entitymap.begin(); i != entitymap.end(); ++i)
981     {
982       char tmp[16];
983       sprintf (tmp, "%u", Unsigned((*i).second));
984       GtkTreeIter iter;
985       gtk_list_store_append(GTK_LIST_STORE(EntityBreakdownWalker), &iter);
986       gtk_list_store_set(GTK_LIST_STORE(EntityBreakdownWalker), &iter, 0, (*i).first.c_str(), 1, tmp, -1);
987     }
988   }
989
990   g_object_unref(G_OBJECT(EntityBreakdownWalker));
991
992   char tmp[16];
993   sprintf (tmp, "%u", Unsigned(g_brushCount.get()));
994   gtk_entry_set_text (GTK_ENTRY (brushes_entry), tmp);
995   sprintf (tmp, "%u", Unsigned(g_entityCount.get()));
996   gtk_entry_set_text (GTK_ENTRY (entities_entry), tmp);
997
998   modal_dialog_show(window, dialog);
999
1000   // save before exit
1001   window_get_position(window, g_posMapInfoWnd);
1002
1003   gtk_widget_destroy(GTK_WIDGET(window));
1004 }
1005
1006
1007
1008 class ScopeTimer
1009 {
1010   Timer m_timer;
1011   const char* m_message;
1012 public:
1013   ScopeTimer(const char* message)
1014     : m_message(message)
1015   {
1016     m_timer.start();
1017   }
1018   ~ScopeTimer()
1019   {
1020     double elapsed_time = m_timer.elapsed_msec() / 1000.f;
1021     globalOutputStream() << m_message << " timer: " << FloatFormat(elapsed_time, 5, 2) << " second(s) elapsed\n";
1022   }
1023 };
1024
1025 /*
1026 ================
1027 Map_LoadFile
1028 ================
1029 */
1030
1031 void Map_LoadFile (const char *filename)
1032 {
1033   globalOutputStream() << "Loading map from " << filename << "\n";
1034   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1035
1036   {
1037     ScopeTimer timer("map load");
1038
1039     const MapFormat* format = NULL;
1040     const char* moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1041     if(string_not_empty(moduleName))
1042       format = ReferenceAPI_getMapModules().findModule(moduleName);
1043
1044     for(int i = 0; i < Brush_toggleFormatCount(); ++i)
1045     {
1046       if(i)
1047         Map_Free();
1048       Brush_toggleFormat(i);
1049       g_map.m_name = filename;
1050       Map_UpdateTitle(g_map);
1051       g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1052       if(format)
1053         format->wrongFormat = false;
1054       g_map.m_resource->attach(g_map);
1055       if(format)
1056         if(!format->wrongFormat)
1057           break;
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   if(extension_equal(path_get_extension(filename), "bsp"))
1643     goto tryDecompile;
1644
1645   {
1646     Resource* resource = GlobalReferenceCache().capture(filename);
1647     resource->refresh(); // avoid loading old version if map has changed on disk since last import
1648     if(!resource->load())
1649     {
1650       GlobalReferenceCache().release(filename);
1651       goto tryDecompile;
1652     }
1653     NodeSmartReference clone(NewMapRoot(""));
1654     Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1655     Map_gatherNamespaced(clone);
1656     Map_mergeClonedNames();
1657     MergeMap(clone);
1658     success = true;
1659     GlobalReferenceCache().release(filename);
1660   }
1661
1662   SceneChangeNotify();
1663
1664   return success;
1665
1666 tryDecompile:
1667
1668   const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue("q3map2_type");
1669   int n = string_length(path_get_extension(filename));
1670   if(n && (extension_equal(path_get_extension(filename), "bsp") || extension_equal(path_get_extension(filename), "map")))
1671   {
1672     StringBuffer output;
1673     output.push_string(AppPath_get());
1674     output.push_string("q3map2.");
1675     output.push_string(RADIANT_EXECUTABLE);
1676     output.push_string(" -v -game ");
1677     output.push_string((type && *type) ? type : "quake3");
1678     output.push_string(" -fs_basepath \"");
1679     output.push_string(EnginePath_get());
1680     output.push_string("\" -fs_game ");
1681     output.push_string(gamename_get());
1682     output.push_string(" -convert -format ");
1683     output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
1684     output.push_string(" -readmap \"");
1685     output.push_string(filename);
1686     output.push_string("\"");
1687
1688     // run
1689     Q_Exec(NULL, output.c_str(), NULL, false, true);
1690
1691     // rebuild filename as "filenamewithoutext_converted.map"
1692     output.clear();
1693     output.push_range(filename, filename + string_length(filename) - (n + 1));
1694     output.push_string("_converted.map");
1695     filename = output.c_str();
1696
1697     // open
1698     Resource* resource = GlobalReferenceCache().capture(filename);
1699     resource->refresh(); // avoid loading old version if map has changed on disk since last import
1700     if(!resource->load())
1701     {
1702       GlobalReferenceCache().release(filename);
1703       goto tryDecompile;
1704     }
1705     NodeSmartReference clone(NewMapRoot(""));
1706     Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1707     Map_gatherNamespaced(clone);
1708     Map_mergeClonedNames();
1709     MergeMap(clone);
1710     success = true;
1711     GlobalReferenceCache().release(filename);
1712   }
1713   
1714   SceneChangeNotify();
1715   return success;
1716 }
1717
1718 /*
1719 ===========
1720 Map_SaveFile
1721 ===========
1722 */
1723 bool Map_SaveFile(const char* filename)
1724 {
1725   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1726   return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename); 
1727 }
1728
1729 //
1730 //===========
1731 //Map_SaveSelected
1732 //===========
1733 //
1734 // Saves selected world brushes and whole entities with partial/full selections
1735 //
1736 bool Map_SaveSelected(const char* filename)
1737 {
1738   return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected, filename); 
1739 }
1740
1741
1742 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1743 {
1744   scene::Node& m_parent;
1745 public:
1746   ParentSelectedBrushesToEntityWalker(scene::Node& parent) : m_parent(parent)
1747   {
1748   }
1749   bool pre(const scene::Path& path, scene::Instance& instance) const
1750   {
1751     if(path.top().get_pointer() != &m_parent
1752       && Node_isPrimitive(path.top()))
1753     {
1754       Selectable* selectable = Instance_getSelectable(instance);
1755       if(selectable != 0
1756         && selectable->isSelected()
1757         && path.size() > 1)
1758       {
1759         return false;
1760       }
1761     }
1762     return true;
1763   }
1764   void post(const scene::Path& path, scene::Instance& instance) const
1765   {
1766     if(path.top().get_pointer() != &m_parent
1767       && Node_isPrimitive(path.top()))
1768     {
1769       Selectable* selectable = Instance_getSelectable(instance);
1770       if(selectable != 0
1771         && selectable->isSelected()
1772         && path.size() > 1)
1773       {
1774         scene::Node& parent = path.parent();
1775         if(&parent != &m_parent)
1776         {
1777           NodeSmartReference node(path.top().get());
1778           Node_getTraversable(parent)->erase(node);
1779           Node_getTraversable(m_parent)->insert(node);
1780         }
1781       }
1782     }
1783   }
1784 };
1785
1786 void Scene_parentSelectedBrushesToEntity(scene::Graph& graph, scene::Node& parent)
1787 {
1788   graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1789 }
1790
1791 class CountSelectedBrushes : public scene::Graph::Walker
1792 {
1793   std::size_t& m_count;
1794   mutable std::size_t m_depth;
1795 public:
1796   CountSelectedBrushes(std::size_t& count) : m_count(count), m_depth(0)
1797   {
1798     m_count = 0;
1799   }
1800   bool pre(const scene::Path& path, scene::Instance& instance) const
1801   {
1802     if(++m_depth != 1 && path.top().get().isRoot())
1803     {
1804       return false;
1805     }
1806     Selectable* selectable = Instance_getSelectable(instance);
1807     if(selectable != 0
1808       && selectable->isSelected()
1809       && Node_isPrimitive(path.top()))
1810     {
1811       ++m_count;
1812     }
1813     return true;
1814   }
1815   void post(const scene::Path& path, scene::Instance& instance) const
1816   {
1817     --m_depth;
1818   }
1819 };
1820
1821 std::size_t Scene_countSelectedBrushes(scene::Graph& graph)
1822 {
1823   std::size_t count;
1824   graph.traverse(CountSelectedBrushes(count));
1825   return count;
1826 }
1827
1828 enum ENodeType
1829 {
1830   eNodeUnknown,
1831   eNodeMap,
1832   eNodeEntity,
1833   eNodePrimitive,
1834 };
1835
1836 const char* nodetype_get_name(ENodeType type)
1837 {
1838   if(type == eNodeMap)
1839     return "map";
1840   if(type == eNodeEntity)
1841     return "entity";
1842   if(type == eNodePrimitive)
1843     return "primitive";
1844   return "unknown";
1845 }
1846
1847 ENodeType node_get_nodetype(scene::Node& node)
1848 {
1849   if(Node_isEntity(node))
1850   {
1851     return eNodeEntity;
1852   }
1853   if(Node_isPrimitive(node))
1854   {
1855     return eNodePrimitive;
1856   }
1857   return eNodeUnknown;
1858 }
1859
1860 bool contains_entity(scene::Node& node)
1861 {
1862   return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1863 }
1864
1865 bool contains_primitive(scene::Node& node)
1866 {
1867   return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1868 }
1869
1870 ENodeType node_get_contains(scene::Node& node)
1871 {
1872   if(contains_entity(node))
1873   {
1874     return eNodeEntity;
1875   }
1876   if(contains_primitive(node))
1877   {
1878     return eNodePrimitive;
1879   }
1880   return eNodeUnknown;
1881 }
1882
1883 void Path_parent(const scene::Path& parent, const scene::Path& child)
1884 {
1885   ENodeType contains = node_get_contains(parent.top());
1886   ENodeType type = node_get_nodetype(child.top());
1887
1888   if(contains != eNodeUnknown && contains == type)
1889   {
1890     NodeSmartReference node(child.top().get());
1891     Path_deleteTop(child);
1892     Node_getTraversable(parent.top())->insert(node);
1893     SceneChangeNotify();
1894   }
1895   else
1896   {
1897     globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to " << nodetype_get_name(contains) << " container.\n";
1898   }
1899 }
1900
1901 void Scene_parentSelected()
1902 {
1903   UndoableCommand undo("parentSelected");
1904
1905   if(GlobalSelectionSystem().countSelected() > 1)
1906   {
1907     class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1908     {
1909       const scene::Path& m_parent;
1910     public:
1911       ParentSelectedBrushesToEntityWalker(const scene::Path& parent) : m_parent(parent)
1912       {
1913       }
1914       void visit(scene::Instance& instance) const
1915       {
1916         if(&m_parent != &instance.path())
1917         {
1918           Path_parent(m_parent, instance.path());
1919         }
1920       }
1921     };
1922     
1923     ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1924     GlobalSelectionSystem().foreachSelected(visitor);
1925   }
1926   else
1927   {
1928     globalOutputStream() << "failed - did not find two selected nodes.\n";
1929   }
1930 }
1931
1932
1933
1934 void NewMap()
1935 {
1936   if (ConfirmModified("New Map"))
1937   {
1938     Map_RegionOff();
1939           Map_Free();
1940     Map_New();
1941   }
1942 }
1943
1944 CopiedString g_mapsPath;
1945
1946 const char* getMapsPath()
1947 {
1948   return g_mapsPath.c_str();
1949 }
1950
1951 const char* map_open(const char* title)
1952 {
1953   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), TRUE, title, getMapsPath(), MapFormat::Name(), true, false, false);
1954 }
1955
1956 const char* map_import(const char* title)
1957 {
1958   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), TRUE, title, getMapsPath(), MapFormat::Name(), false, true, false);
1959 }
1960
1961 const char* map_save(const char* title)
1962 {
1963   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), FALSE, title, getMapsPath(), MapFormat::Name(), false, false, true);
1964 }
1965
1966 void OpenMap()
1967 {
1968   if (!ConfirmModified("Open Map"))
1969     return;
1970
1971   const char* filename = map_open("Open Map");
1972
1973   if (filename != 0)
1974   {
1975     MRU_AddFile(filename);
1976     Map_RegionOff();
1977     Map_Free();
1978     Map_LoadFile(filename);
1979   }
1980 }
1981
1982 void ImportMap()
1983 {
1984   const char* filename = map_import("Import Map");
1985
1986   if(filename != 0)
1987   {
1988     UndoableCommand undo("mapImport");
1989     Map_ImportFile(filename);
1990   }
1991 }
1992
1993 bool Map_SaveAs()
1994 {
1995   const char* filename = map_save("Save Map");
1996   
1997   if(filename != 0)
1998   {
1999     MRU_AddFile(filename);
2000     Map_Rename(filename);
2001     return Map_Save();
2002   }
2003   return false;
2004 }
2005
2006 void SaveMapAs()
2007 {
2008   Map_SaveAs();
2009 }
2010
2011 void SaveMap()
2012 {
2013   if(Map_Unnamed(g_map))
2014   {
2015     SaveMapAs();
2016   }
2017   else if(Map_Modified(g_map))
2018   {
2019     Map_Save();
2020   }
2021 }
2022
2023 void ExportMap()
2024 {
2025   const char* filename = map_save("Export Selection");
2026
2027   if(filename != 0)
2028   {
2029     Map_SaveSelected(filename);
2030   }
2031 }
2032
2033 void SaveRegion()
2034 {
2035   const char* filename = map_save("Export Region");
2036   
2037   if(filename != 0)
2038   {
2039     Map_SaveRegion(filename);
2040   }
2041 }
2042
2043
2044 void RegionOff()
2045 {
2046   Map_RegionOff();
2047   SceneChangeNotify();
2048 }
2049
2050 void RegionXY()
2051 {
2052   Map_RegionXY(
2053     g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2054     g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2055     g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2056     g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2057     );
2058   SceneChangeNotify();
2059 }
2060
2061 void RegionBrush()
2062 {
2063   Map_RegionBrush();
2064   SceneChangeNotify();
2065 }
2066
2067 void RegionSelected()
2068 {
2069   Map_RegionSelectedBrushes();
2070   SceneChangeNotify();
2071 }
2072
2073
2074
2075
2076
2077 class BrushFindByIndexWalker : public scene::Traversable::Walker
2078 {
2079   mutable std::size_t m_index;
2080   scene::Path& m_path;
2081 public:
2082   BrushFindByIndexWalker(std::size_t index, scene::Path& path)
2083     : m_index(index), m_path(path)
2084   {
2085   }
2086   bool pre(scene::Node& node) const
2087   {
2088     if(Node_isPrimitive(node) && m_index-- == 0)
2089     {
2090       m_path.push(makeReference(node));
2091     }
2092     return false;
2093   }
2094 };
2095
2096 class EntityFindByIndexWalker : public scene::Traversable::Walker
2097 {
2098   mutable std::size_t m_index;
2099   scene::Path& m_path;
2100 public:
2101   EntityFindByIndexWalker(std::size_t index, scene::Path& path)
2102     : m_index(index), m_path(path)
2103   {
2104   }
2105   bool pre(scene::Node& node) const
2106   {
2107     if(Node_isEntity(node) && m_index-- == 0)
2108     {
2109       m_path.push(makeReference(node));
2110     }
2111     return false;
2112   }
2113 };
2114
2115 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path& path)
2116 {
2117   path.push(makeReference(GlobalSceneGraph().root()));
2118   {
2119     Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2120   }
2121   if(path.size() == 2)
2122   {
2123     scene::Traversable* traversable = Node_getTraversable(path.top());
2124     if(traversable != 0)
2125     {
2126       traversable->traverse(BrushFindByIndexWalker(brush, path));
2127     }
2128   }
2129 }
2130
2131 inline bool Node_hasChildren(scene::Node& node)
2132 {
2133   scene::Traversable* traversable = Node_getTraversable(node);
2134   return traversable != 0 && !traversable->empty();
2135 }
2136
2137 void SelectBrush (int entitynum, int brushnum)
2138 {
2139   scene::Path path;
2140   Scene_FindEntityBrush(entitynum, brushnum, path);
2141   if(path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top())))
2142   {
2143     scene::Instance* instance = GlobalSceneGraph().find(path);
2144     ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2145     Selectable* selectable = Instance_getSelectable(*instance);
2146     ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2147     selectable->setSelected(true);
2148     g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2149   }
2150 }
2151
2152
2153 class BrushFindIndexWalker : public scene::Graph::Walker
2154 {
2155   mutable const scene::Node* m_node;
2156   std::size_t& m_count;
2157 public:
2158   BrushFindIndexWalker(const scene::Node& node, std::size_t& count)
2159     : m_node(&node), m_count(count)
2160   {
2161   }
2162   bool pre(const scene::Path& path, scene::Instance& instance) const
2163   {
2164     if(Node_isPrimitive(path.top()))
2165     {
2166       if(m_node == path.top().get_pointer())
2167       {
2168         m_node = 0;
2169       }
2170       if(m_node)
2171       {
2172         ++m_count;
2173       }
2174     }
2175     return true;
2176   }
2177 };
2178
2179 class EntityFindIndexWalker : public scene::Graph::Walker
2180 {
2181   mutable const scene::Node* m_node;
2182   std::size_t& m_count;
2183 public:
2184   EntityFindIndexWalker(const scene::Node& node, std::size_t& count)
2185     : m_node(&node), m_count(count)
2186   {
2187   }
2188   bool pre(const scene::Path& path, scene::Instance& instance) const
2189   {
2190     if(Node_isEntity(path.top()))
2191     {
2192       if(m_node == path.top().get_pointer())
2193       {
2194         m_node = 0;
2195       }
2196       if(m_node)
2197       {
2198         ++m_count;
2199       }
2200     }
2201     return true;
2202   }
2203 };
2204
2205 static void GetSelectionIndex (int *ent, int *brush)
2206 {
2207   std::size_t count_brush = 0;
2208   std::size_t count_entity = 0;
2209   if(GlobalSelectionSystem().countSelected() != 0)
2210   {
2211     const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2212
2213     GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2214     GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2215   }
2216   *brush = int(count_brush);
2217   *ent = int(count_entity);
2218 }
2219
2220 void DoFind()
2221 {
2222   ModalDialog dialog;
2223   GtkEntry* entity;
2224   GtkEntry* brush;
2225
2226   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Find Brush", G_CALLBACK(dialog_delete_callback), &dialog);
2227
2228   GtkAccelGroup* accel = gtk_accel_group_new();
2229   gtk_window_add_accel_group(window, accel);
2230
2231   {
2232     GtkVBox* vbox = create_dialog_vbox(4, 4);
2233     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
2234     {
2235       GtkTable* table = create_dialog_table(2, 2, 4, 4);
2236       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), TRUE, TRUE, 0);
2237       {
2238         GtkWidget* label = gtk_label_new ("Entity number");
2239         gtk_widget_show (label);
2240         gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
2241                           (GtkAttachOptions) (0),
2242                           (GtkAttachOptions) (0), 0, 0);
2243       }
2244       {
2245         GtkWidget* label = gtk_label_new ("Brush number");
2246         gtk_widget_show (label);
2247         gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
2248                           (GtkAttachOptions) (0),
2249                           (GtkAttachOptions) (0), 0, 0);
2250       }
2251       {
2252         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
2253         gtk_widget_show(GTK_WIDGET(entry));
2254         gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1,
2255                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
2256                           (GtkAttachOptions) (0), 0, 0);
2257         gtk_widget_grab_focus(GTK_WIDGET(entry));
2258         entity = entry;
2259       }
2260       {
2261         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
2262         gtk_widget_show(GTK_WIDGET(entry));
2263         gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2,
2264                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
2265                           (GtkAttachOptions) (0), 0, 0);
2266
2267         brush = entry;
2268       }
2269     }
2270     {
2271       GtkHBox* hbox = create_dialog_hbox(4);
2272       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0);
2273       {
2274         GtkButton* button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2275         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
2276         widget_make_default(GTK_WIDGET(button));
2277         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
2278       }
2279       {
2280         GtkButton* button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2281         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
2282         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
2283       }
2284     }
2285   }
2286
2287   // Initialize dialog
2288   char buf[16];
2289   int ent, br;
2290
2291   GetSelectionIndex (&ent, &br);
2292   sprintf (buf, "%i", ent);
2293   gtk_entry_set_text(entity, buf);
2294   sprintf (buf, "%i", br);
2295   gtk_entry_set_text(brush, buf);
2296
2297   if(modal_dialog_show(window, dialog) == eIDOK)
2298   {
2299     const char *entstr = gtk_entry_get_text(entity);
2300     const char *brushstr = gtk_entry_get_text(brush);
2301     SelectBrush (atoi(entstr), atoi(brushstr));
2302   }
2303
2304   gtk_widget_destroy(GTK_WIDGET(window));
2305 }
2306
2307 void Map_constructPreferences(PreferencesPage& page)
2308 {
2309   page.appendCheckBox("", "Load last map on open", g_bLoadLastMap);
2310 }
2311
2312
2313 class MapEntityClasses : public ModuleObserver
2314 {
2315   std::size_t m_unrealised;
2316 public:
2317   MapEntityClasses() : m_unrealised(1)
2318   {
2319   }
2320   void realise()
2321   {
2322     if(--m_unrealised == 0)
2323     {
2324       if(g_map.m_resource != 0)
2325       {
2326         ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2327               g_map.m_resource->realise();
2328       }
2329     }
2330   }
2331   void unrealise()
2332   {
2333     if(++m_unrealised == 1)
2334     {
2335       if(g_map.m_resource != 0)
2336       {
2337         g_map.m_resource->flush();
2338               g_map.m_resource->unrealise();
2339       }
2340     }
2341   }
2342 };
2343
2344 MapEntityClasses g_MapEntityClasses;
2345
2346
2347 class MapModuleObserver : public ModuleObserver
2348 {
2349   std::size_t m_unrealised;
2350 public:
2351   MapModuleObserver() : m_unrealised(1)
2352   {
2353   }
2354   void realise()
2355   {
2356     if(--m_unrealised == 0)
2357     {
2358       ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()), "maps_directory: user-game-path is empty");
2359       StringOutputStream buffer(256);
2360       buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2361       Q_mkdir(buffer.c_str());
2362       g_mapsPath = buffer.c_str();
2363     }
2364   }
2365   void unrealise()
2366   {
2367     if(++m_unrealised == 1)
2368     {
2369       g_mapsPath = "";
2370     }
2371   }
2372 };
2373
2374 MapModuleObserver g_MapModuleObserver;
2375
2376 #include "preferencesystem.h"
2377
2378 CopiedString g_strLastMap;
2379 bool g_bLoadLastMap = false;
2380
2381 void Map_Construct()
2382 {
2383   GlobalCommands_insert("RegionOff", FreeCaller<RegionOff>());
2384   GlobalCommands_insert("RegionSetXY", FreeCaller<RegionXY>());
2385   GlobalCommands_insert("RegionSetBrush", FreeCaller<RegionBrush>());
2386   GlobalCommands_insert("RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator('R', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK)));
2387
2388   GlobalPreferenceSystem().registerPreference("LastMap", CopiedStringImportStringCaller(g_strLastMap), CopiedStringExportStringCaller(g_strLastMap));
2389   GlobalPreferenceSystem().registerPreference("LoadLastMap", BoolImportStringCaller(g_bLoadLastMap), BoolExportStringCaller(g_bLoadLastMap));
2390   GlobalPreferenceSystem().registerPreference("MapInfoDlg", WindowPositionImportStringCaller(g_posMapInfoWnd), WindowPositionExportStringCaller(g_posMapInfoWnd));
2391   
2392   PreferencesDialog_addSettingsPreferences(FreeCaller1<PreferencesPage&, Map_constructPreferences>());
2393
2394   GlobalEntityClassManager().attach(g_MapEntityClasses);
2395   Radiant_attachHomePathsObserver(g_MapModuleObserver);
2396 }
2397
2398 void Map_Destroy()
2399 {
2400   Radiant_detachHomePathsObserver(g_MapModuleObserver);
2401   GlobalEntityClassManager().detach(g_MapEntityClasses);
2402 }