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