]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
fix type cast
[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 #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     const MapFormat* format = NULL;
1647     const char* moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1648     if(string_not_empty(moduleName))
1649       format = ReferenceAPI_getMapModules().findModule(moduleName);
1650
1651     if(format)
1652       format->wrongFormat = false;
1653     Resource* resource = GlobalReferenceCache().capture(filename);
1654     resource->refresh(); // avoid loading old version if map has changed on disk since last import
1655     if(!resource->load())
1656     {
1657       GlobalReferenceCache().release(filename);
1658       goto tryDecompile;
1659     }
1660     if(format)
1661       if(format->wrongFormat)
1662       {
1663         GlobalReferenceCache().release(filename);
1664         goto tryDecompile;
1665       }
1666     NodeSmartReference clone(NewMapRoot(""));
1667     Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1668     Map_gatherNamespaced(clone);
1669     Map_mergeClonedNames();
1670     MergeMap(clone);
1671     success = true;
1672     GlobalReferenceCache().release(filename);
1673   }
1674
1675   SceneChangeNotify();
1676
1677   return success;
1678
1679 tryDecompile:
1680
1681   const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue("q3map2_type");
1682   int n = string_length(path_get_extension(filename));
1683   if(n && (extension_equal(path_get_extension(filename), "bsp") || extension_equal(path_get_extension(filename), "map")))
1684   {
1685     StringBuffer output;
1686     output.push_string(AppPath_get());
1687     output.push_string("q3map2.");
1688     output.push_string(RADIANT_EXECUTABLE);
1689     output.push_string(" -v -game ");
1690     output.push_string((type && *type) ? type : "quake3");
1691     output.push_string(" -fs_basepath \"");
1692     output.push_string(EnginePath_get());
1693     output.push_string(" -fs_homepath \"");
1694     output.push_string(g_qeglobals.m_userEnginePath.c_str());
1695     output.push_string("\" -fs_game ");
1696     output.push_string(gamename_get());
1697     output.push_string(" -convert -format ");
1698     output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
1699     output.push_string(" -readmap \"");
1700     output.push_string(filename);
1701     output.push_string("\"");
1702
1703     // run
1704     Q_Exec(NULL, output.c_str(), NULL, false, true);
1705
1706     // rebuild filename as "filenamewithoutext_converted.map"
1707     output.clear();
1708     output.push_range(filename, filename + string_length(filename) - (n + 1));
1709     output.push_string("_converted.map");
1710     filename = output.c_str();
1711
1712     // open
1713     Resource* resource = GlobalReferenceCache().capture(filename);
1714     resource->refresh(); // avoid loading old version if map has changed on disk since last import
1715     if(!resource->load())
1716     {
1717       GlobalReferenceCache().release(filename);
1718       goto tryDecompile;
1719     }
1720     NodeSmartReference clone(NewMapRoot(""));
1721     Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1722     Map_gatherNamespaced(clone);
1723     Map_mergeClonedNames();
1724     MergeMap(clone);
1725     success = true;
1726     GlobalReferenceCache().release(filename);
1727   }
1728   
1729   SceneChangeNotify();
1730   return success;
1731 }
1732
1733 /*
1734 ===========
1735 Map_SaveFile
1736 ===========
1737 */
1738 bool Map_SaveFile(const char* filename)
1739 {
1740   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1741   return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename); 
1742 }
1743
1744 //
1745 //===========
1746 //Map_SaveSelected
1747 //===========
1748 //
1749 // Saves selected world brushes and whole entities with partial/full selections
1750 //
1751 bool Map_SaveSelected(const char* filename)
1752 {
1753   return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected, filename); 
1754 }
1755
1756
1757 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1758 {
1759   scene::Node& m_parent;
1760 public:
1761   ParentSelectedBrushesToEntityWalker(scene::Node& parent) : m_parent(parent)
1762   {
1763   }
1764   bool pre(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         return false;
1775       }
1776     }
1777     return true;
1778   }
1779   void post(const scene::Path& path, scene::Instance& instance) const
1780   {
1781     if(path.top().get_pointer() != &m_parent
1782       && Node_isPrimitive(path.top()))
1783     {
1784       Selectable* selectable = Instance_getSelectable(instance);
1785       if(selectable != 0
1786         && selectable->isSelected()
1787         && path.size() > 1)
1788       {
1789         scene::Node& parent = path.parent();
1790         if(&parent != &m_parent)
1791         {
1792           NodeSmartReference node(path.top().get());
1793           Node_getTraversable(parent)->erase(node);
1794           Node_getTraversable(m_parent)->insert(node);
1795         }
1796       }
1797     }
1798   }
1799 };
1800
1801 void Scene_parentSelectedBrushesToEntity(scene::Graph& graph, scene::Node& parent)
1802 {
1803   graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1804 }
1805
1806 class CountSelectedBrushes : public scene::Graph::Walker
1807 {
1808   std::size_t& m_count;
1809   mutable std::size_t m_depth;
1810 public:
1811   CountSelectedBrushes(std::size_t& count) : m_count(count), m_depth(0)
1812   {
1813     m_count = 0;
1814   }
1815   bool pre(const scene::Path& path, scene::Instance& instance) const
1816   {
1817     if(++m_depth != 1 && path.top().get().isRoot())
1818     {
1819       return false;
1820     }
1821     Selectable* selectable = Instance_getSelectable(instance);
1822     if(selectable != 0
1823       && selectable->isSelected()
1824       && Node_isPrimitive(path.top()))
1825     {
1826       ++m_count;
1827     }
1828     return true;
1829   }
1830   void post(const scene::Path& path, scene::Instance& instance) const
1831   {
1832     --m_depth;
1833   }
1834 };
1835
1836 std::size_t Scene_countSelectedBrushes(scene::Graph& graph)
1837 {
1838   std::size_t count;
1839   graph.traverse(CountSelectedBrushes(count));
1840   return count;
1841 }
1842
1843 enum ENodeType
1844 {
1845   eNodeUnknown,
1846   eNodeMap,
1847   eNodeEntity,
1848   eNodePrimitive,
1849 };
1850
1851 const char* nodetype_get_name(ENodeType type)
1852 {
1853   if(type == eNodeMap)
1854     return "map";
1855   if(type == eNodeEntity)
1856     return "entity";
1857   if(type == eNodePrimitive)
1858     return "primitive";
1859   return "unknown";
1860 }
1861
1862 ENodeType node_get_nodetype(scene::Node& node)
1863 {
1864   if(Node_isEntity(node))
1865   {
1866     return eNodeEntity;
1867   }
1868   if(Node_isPrimitive(node))
1869   {
1870     return eNodePrimitive;
1871   }
1872   return eNodeUnknown;
1873 }
1874
1875 bool contains_entity(scene::Node& node)
1876 {
1877   return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1878 }
1879
1880 bool contains_primitive(scene::Node& node)
1881 {
1882   return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1883 }
1884
1885 ENodeType node_get_contains(scene::Node& node)
1886 {
1887   if(contains_entity(node))
1888   {
1889     return eNodeEntity;
1890   }
1891   if(contains_primitive(node))
1892   {
1893     return eNodePrimitive;
1894   }
1895   return eNodeUnknown;
1896 }
1897
1898 void Path_parent(const scene::Path& parent, const scene::Path& child)
1899 {
1900   ENodeType contains = node_get_contains(parent.top());
1901   ENodeType type = node_get_nodetype(child.top());
1902
1903   if(contains != eNodeUnknown && contains == type)
1904   {
1905     NodeSmartReference node(child.top().get());
1906     Path_deleteTop(child);
1907     Node_getTraversable(parent.top())->insert(node);
1908     SceneChangeNotify();
1909   }
1910   else
1911   {
1912     globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to " << nodetype_get_name(contains) << " container.\n";
1913   }
1914 }
1915
1916 void Scene_parentSelected()
1917 {
1918   UndoableCommand undo("parentSelected");
1919
1920   if(GlobalSelectionSystem().countSelected() > 1)
1921   {
1922     class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1923     {
1924       const scene::Path& m_parent;
1925     public:
1926       ParentSelectedBrushesToEntityWalker(const scene::Path& parent) : m_parent(parent)
1927       {
1928       }
1929       void visit(scene::Instance& instance) const
1930       {
1931         if(&m_parent != &instance.path())
1932         {
1933           Path_parent(m_parent, instance.path());
1934         }
1935       }
1936     };
1937     
1938     ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1939     GlobalSelectionSystem().foreachSelected(visitor);
1940   }
1941   else
1942   {
1943     globalOutputStream() << "failed - did not find two selected nodes.\n";
1944   }
1945 }
1946
1947
1948
1949 void NewMap()
1950 {
1951   if (ConfirmModified("New Map"))
1952   {
1953     Map_RegionOff();
1954           Map_Free();
1955     Map_New();
1956   }
1957 }
1958
1959 CopiedString g_mapsPath;
1960
1961 const char* getMapsPath()
1962 {
1963   return g_mapsPath.c_str();
1964 }
1965
1966 const char* map_open(const char* title)
1967 {
1968   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), TRUE, title, getMapsPath(), MapFormat::Name(), true, false, false);
1969 }
1970
1971 const char* map_import(const char* title)
1972 {
1973   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), TRUE, title, getMapsPath(), MapFormat::Name(), false, true, false);
1974 }
1975
1976 const char* map_save(const char* title)
1977 {
1978   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), FALSE, title, getMapsPath(), MapFormat::Name(), false, false, true);
1979 }
1980
1981 void OpenMap()
1982 {
1983   if (!ConfirmModified("Open Map"))
1984     return;
1985
1986   const char* filename = map_open("Open Map");
1987
1988   if (filename != 0)
1989   {
1990     MRU_AddFile(filename);
1991     Map_RegionOff();
1992     Map_Free();
1993     Map_LoadFile(filename);
1994   }
1995 }
1996
1997 void ImportMap()
1998 {
1999   const char* filename = map_import("Import Map");
2000
2001   if(filename != 0)
2002   {
2003     UndoableCommand undo("mapImport");
2004     Map_ImportFile(filename);
2005   }
2006 }
2007
2008 bool Map_SaveAs()
2009 {
2010   const char* filename = map_save("Save Map");
2011   
2012   if(filename != 0)
2013   {
2014     MRU_AddFile(filename);
2015     Map_Rename(filename);
2016     return Map_Save();
2017   }
2018   return false;
2019 }
2020
2021 void SaveMapAs()
2022 {
2023   Map_SaveAs();
2024 }
2025
2026 void SaveMap()
2027 {
2028   if(Map_Unnamed(g_map))
2029   {
2030     SaveMapAs();
2031   }
2032   else if(Map_Modified(g_map))
2033   {
2034     Map_Save();
2035   }
2036 }
2037
2038 void ExportMap()
2039 {
2040   const char* filename = map_save("Export Selection");
2041
2042   if(filename != 0)
2043   {
2044     Map_SaveSelected(filename);
2045   }
2046 }
2047
2048 void SaveRegion()
2049 {
2050   const char* filename = map_save("Export Region");
2051   
2052   if(filename != 0)
2053   {
2054     Map_SaveRegion(filename);
2055   }
2056 }
2057
2058
2059 void RegionOff()
2060 {
2061   Map_RegionOff();
2062   SceneChangeNotify();
2063 }
2064
2065 void RegionXY()
2066 {
2067   Map_RegionXY(
2068     g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2069     g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2070     g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2071     g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2072     );
2073   SceneChangeNotify();
2074 }
2075
2076 void RegionBrush()
2077 {
2078   Map_RegionBrush();
2079   SceneChangeNotify();
2080 }
2081
2082 void RegionSelected()
2083 {
2084   Map_RegionSelectedBrushes();
2085   SceneChangeNotify();
2086 }
2087
2088
2089
2090
2091
2092 class BrushFindByIndexWalker : public scene::Traversable::Walker
2093 {
2094   mutable std::size_t m_index;
2095   scene::Path& m_path;
2096 public:
2097   BrushFindByIndexWalker(std::size_t index, scene::Path& path)
2098     : m_index(index), m_path(path)
2099   {
2100   }
2101   bool pre(scene::Node& node) const
2102   {
2103     if(Node_isPrimitive(node) && m_index-- == 0)
2104     {
2105       m_path.push(makeReference(node));
2106     }
2107     return false;
2108   }
2109 };
2110
2111 class EntityFindByIndexWalker : public scene::Traversable::Walker
2112 {
2113   mutable std::size_t m_index;
2114   scene::Path& m_path;
2115 public:
2116   EntityFindByIndexWalker(std::size_t index, scene::Path& path)
2117     : m_index(index), m_path(path)
2118   {
2119   }
2120   bool pre(scene::Node& node) const
2121   {
2122     if(Node_isEntity(node) && m_index-- == 0)
2123     {
2124       m_path.push(makeReference(node));
2125     }
2126     return false;
2127   }
2128 };
2129
2130 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path& path)
2131 {
2132   path.push(makeReference(GlobalSceneGraph().root()));
2133   {
2134     Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2135   }
2136   if(path.size() == 2)
2137   {
2138     scene::Traversable* traversable = Node_getTraversable(path.top());
2139     if(traversable != 0)
2140     {
2141       traversable->traverse(BrushFindByIndexWalker(brush, path));
2142     }
2143   }
2144 }
2145
2146 inline bool Node_hasChildren(scene::Node& node)
2147 {
2148   scene::Traversable* traversable = Node_getTraversable(node);
2149   return traversable != 0 && !traversable->empty();
2150 }
2151
2152 void SelectBrush (int entitynum, int brushnum)
2153 {
2154   scene::Path path;
2155   Scene_FindEntityBrush(entitynum, brushnum, path);
2156   if(path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top())))
2157   {
2158     scene::Instance* instance = GlobalSceneGraph().find(path);
2159     ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2160     Selectable* selectable = Instance_getSelectable(*instance);
2161     ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2162     selectable->setSelected(true);
2163     g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2164   }
2165 }
2166
2167
2168 class BrushFindIndexWalker : public scene::Graph::Walker
2169 {
2170   mutable const scene::Node* m_node;
2171   std::size_t& m_count;
2172 public:
2173   BrushFindIndexWalker(const scene::Node& node, std::size_t& count)
2174     : m_node(&node), m_count(count)
2175   {
2176   }
2177   bool pre(const scene::Path& path, scene::Instance& instance) const
2178   {
2179     if(Node_isPrimitive(path.top()))
2180     {
2181       if(m_node == path.top().get_pointer())
2182       {
2183         m_node = 0;
2184       }
2185       if(m_node)
2186       {
2187         ++m_count;
2188       }
2189     }
2190     return true;
2191   }
2192 };
2193
2194 class EntityFindIndexWalker : public scene::Graph::Walker
2195 {
2196   mutable const scene::Node* m_node;
2197   std::size_t& m_count;
2198 public:
2199   EntityFindIndexWalker(const scene::Node& node, std::size_t& count)
2200     : m_node(&node), m_count(count)
2201   {
2202   }
2203   bool pre(const scene::Path& path, scene::Instance& instance) const
2204   {
2205     if(Node_isEntity(path.top()))
2206     {
2207       if(m_node == path.top().get_pointer())
2208       {
2209         m_node = 0;
2210       }
2211       if(m_node)
2212       {
2213         ++m_count;
2214       }
2215     }
2216     return true;
2217   }
2218 };
2219
2220 static void GetSelectionIndex (int *ent, int *brush)
2221 {
2222   std::size_t count_brush = 0;
2223   std::size_t count_entity = 0;
2224   if(GlobalSelectionSystem().countSelected() != 0)
2225   {
2226     const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2227
2228     GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2229     GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2230   }
2231   *brush = int(count_brush);
2232   *ent = int(count_entity);
2233 }
2234
2235 void DoFind()
2236 {
2237   ModalDialog dialog;
2238   GtkEntry* entity;
2239   GtkEntry* brush;
2240
2241   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Find Brush", G_CALLBACK(dialog_delete_callback), &dialog);
2242
2243   GtkAccelGroup* accel = gtk_accel_group_new();
2244   gtk_window_add_accel_group(window, accel);
2245
2246   {
2247     GtkVBox* vbox = create_dialog_vbox(4, 4);
2248     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
2249     {
2250       GtkTable* table = create_dialog_table(2, 2, 4, 4);
2251       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), TRUE, TRUE, 0);
2252       {
2253         GtkWidget* label = gtk_label_new ("Entity number");
2254         gtk_widget_show (label);
2255         gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
2256                           (GtkAttachOptions) (0),
2257                           (GtkAttachOptions) (0), 0, 0);
2258       }
2259       {
2260         GtkWidget* label = gtk_label_new ("Brush number");
2261         gtk_widget_show (label);
2262         gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
2263                           (GtkAttachOptions) (0),
2264                           (GtkAttachOptions) (0), 0, 0);
2265       }
2266       {
2267         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
2268         gtk_widget_show(GTK_WIDGET(entry));
2269         gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1,
2270                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
2271                           (GtkAttachOptions) (0), 0, 0);
2272         gtk_widget_grab_focus(GTK_WIDGET(entry));
2273         entity = entry;
2274       }
2275       {
2276         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
2277         gtk_widget_show(GTK_WIDGET(entry));
2278         gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2,
2279                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
2280                           (GtkAttachOptions) (0), 0, 0);
2281
2282         brush = entry;
2283       }
2284     }
2285     {
2286       GtkHBox* hbox = create_dialog_hbox(4);
2287       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0);
2288       {
2289         GtkButton* button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2290         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
2291         widget_make_default(GTK_WIDGET(button));
2292         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
2293       }
2294       {
2295         GtkButton* button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2296         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
2297         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
2298       }
2299     }
2300   }
2301
2302   // Initialize dialog
2303   char buf[16];
2304   int ent, br;
2305
2306   GetSelectionIndex (&ent, &br);
2307   sprintf (buf, "%i", ent);
2308   gtk_entry_set_text(entity, buf);
2309   sprintf (buf, "%i", br);
2310   gtk_entry_set_text(brush, buf);
2311
2312   if(modal_dialog_show(window, dialog) == eIDOK)
2313   {
2314     const char *entstr = gtk_entry_get_text(entity);
2315     const char *brushstr = gtk_entry_get_text(brush);
2316     SelectBrush (atoi(entstr), atoi(brushstr));
2317   }
2318
2319   gtk_widget_destroy(GTK_WIDGET(window));
2320 }
2321
2322 void Map_constructPreferences(PreferencesPage& page)
2323 {
2324   page.appendCheckBox("", "Load last map on open", g_bLoadLastMap);
2325 }
2326
2327
2328 class MapEntityClasses : public ModuleObserver
2329 {
2330   std::size_t m_unrealised;
2331 public:
2332   MapEntityClasses() : m_unrealised(1)
2333   {
2334   }
2335   void realise()
2336   {
2337     if(--m_unrealised == 0)
2338     {
2339       if(g_map.m_resource != 0)
2340       {
2341         ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2342               g_map.m_resource->realise();
2343       }
2344     }
2345   }
2346   void unrealise()
2347   {
2348     if(++m_unrealised == 1)
2349     {
2350       if(g_map.m_resource != 0)
2351       {
2352         g_map.m_resource->flush();
2353               g_map.m_resource->unrealise();
2354       }
2355     }
2356   }
2357 };
2358
2359 MapEntityClasses g_MapEntityClasses;
2360
2361
2362 class MapModuleObserver : public ModuleObserver
2363 {
2364   std::size_t m_unrealised;
2365 public:
2366   MapModuleObserver() : m_unrealised(1)
2367   {
2368   }
2369   void realise()
2370   {
2371     if(--m_unrealised == 0)
2372     {
2373       ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()), "maps_directory: user-game-path is empty");
2374       StringOutputStream buffer(256);
2375       buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2376       Q_mkdir(buffer.c_str());
2377       g_mapsPath = buffer.c_str();
2378     }
2379   }
2380   void unrealise()
2381   {
2382     if(++m_unrealised == 1)
2383     {
2384       g_mapsPath = "";
2385     }
2386   }
2387 };
2388
2389 MapModuleObserver g_MapModuleObserver;
2390
2391 #include "preferencesystem.h"
2392
2393 CopiedString g_strLastMap;
2394 bool g_bLoadLastMap = false;
2395
2396 void Map_Construct()
2397 {
2398   GlobalCommands_insert("RegionOff", FreeCaller<RegionOff>());
2399   GlobalCommands_insert("RegionSetXY", FreeCaller<RegionXY>());
2400   GlobalCommands_insert("RegionSetBrush", FreeCaller<RegionBrush>());
2401   GlobalCommands_insert("RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator('R', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK)));
2402
2403   GlobalPreferenceSystem().registerPreference("LastMap", CopiedStringImportStringCaller(g_strLastMap), CopiedStringExportStringCaller(g_strLastMap));
2404   GlobalPreferenceSystem().registerPreference("LoadLastMap", BoolImportStringCaller(g_bLoadLastMap), BoolExportStringCaller(g_bLoadLastMap));
2405   GlobalPreferenceSystem().registerPreference("MapInfoDlg", WindowPositionImportStringCaller(g_posMapInfoWnd), WindowPositionExportStringCaller(g_posMapInfoWnd));
2406   
2407   PreferencesDialog_addSettingsPreferences(FreeCaller1<PreferencesPage&, Map_constructPreferences>());
2408
2409   GlobalEntityClassManager().attach(g_MapEntityClasses);
2410   Radiant_attachHomePathsObserver(g_MapModuleObserver);
2411 }
2412
2413 void Map_Destroy()
2414 {
2415   Radiant_detachHomePathsObserver(g_MapModuleObserver);
2416   GlobalEntityClassManager().detach(g_MapEntityClasses);
2417 }