]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
adf69f46d974fac354bf0e3a097ae90775ac7596
[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 <gtk/gtk.h>
25
26 #include "debugging/debugging.h"
27
28 #include "imap.h"
29
30 MapModules &ReferenceAPI_getMapModules();
31
32 #include "iselection.h"
33 #include "iundo.h"
34 #include "ibrush.h"
35 #include "ifilter.h"
36 #include "ireference.h"
37 #include "ifiletypes.h"
38 #include "ieclass.h"
39 #include "irender.h"
40 #include "ientity.h"
41 #include "editable.h"
42 #include "iarchive.h"
43 #include "ifilesystem.h"
44 #include "namespace.h"
45 #include "moduleobserver.h"
46
47 #include <set>
48
49 #include <gdk/gdkkeysyms.h>
50 #include "uilib/uilib.h"
51
52 #include "scenelib.h"
53 #include "transformlib.h"
54 #include "selectionlib.h"
55 #include "instancelib.h"
56 #include "traverselib.h"
57 #include "maplib.h"
58 #include "eclasslib.h"
59 #include "cmdlib.h"
60 #include "stream/textfilestream.h"
61 #include "os/path.h"
62 #include "uniquenames.h"
63 #include "modulesystem/singletonmodule.h"
64 #include "modulesystem/moduleregistry.h"
65 #include "stream/stringstream.h"
66 #include "signal/signal.h"
67
68 #include "gtkutil/filechooser.h"
69 #include "timer.h"
70 #include "select.h"
71 #include "plugin.h"
72 #include "filetypes.h"
73 #include "gtkdlgs.h"
74 #include "entityinspector.h"
75 #include "points.h"
76 #include "qe3.h"
77 #include "camwindow.h"
78 #include "xywindow.h"
79 #include "mainframe.h"
80 #include "preferences.h"
81 #include "preferencesystem.h"
82 #include "referencecache.h"
83 #include "mru.h"
84 #include "commands.h"
85 #include "autosave.h"
86 #include "brushmodule.h"
87 #include "brush.h"
88
89 class NameObserver {
90     UniqueNames &m_names;
91     CopiedString m_name;
92
93     void construct()
94     {
95         if (!empty()) {
96             //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
97             m_names.insert(name_read(c_str()));
98         }
99     }
100
101     void destroy()
102     {
103         if (!empty()) {
104             //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
105             m_names.erase(name_read(c_str()));
106         }
107     }
108
109     NameObserver &operator=(const NameObserver &other);
110
111 public:
112     NameObserver(UniqueNames &names) : m_names(names)
113     {
114         construct();
115     }
116
117     NameObserver(const NameObserver &other) : m_names(other.m_names), m_name(other.m_name)
118     {
119         construct();
120     }
121
122     ~NameObserver()
123     {
124         destroy();
125     }
126
127     bool empty() const
128     {
129         return string_empty(c_str());
130     }
131
132     const char *c_str() const
133     {
134         return m_name.c_str();
135     }
136
137     void nameChanged(const char *name)
138     {
139         destroy();
140         m_name = name;
141         construct();
142     }
143
144     typedef MemberCaller<NameObserver, void(const char *), &NameObserver::nameChanged> NameChangedCaller;
145 };
146
147 class BasicNamespace : public Namespace {
148     typedef std::map<NameCallback, NameObserver> Names;
149     Names m_names;
150     UniqueNames m_uniqueNames;
151 public:
152     ~BasicNamespace()
153     {
154         ASSERT_MESSAGE(m_names.empty(), "namespace: names still registered at shutdown");
155     }
156
157     void attach(const NameCallback &setName, const NameCallbackCallback &attachObserver)
158     {
159         std::pair<Names::iterator, bool> result = m_names.insert(Names::value_type(setName, m_uniqueNames));
160         ASSERT_MESSAGE(result.second, "cannot attach name");
161         attachObserver(NameObserver::NameChangedCaller((*result.first).second));
162         //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
163     }
164
165     void detach(const NameCallback &setName, const NameCallbackCallback &detachObserver)
166     {
167         Names::iterator i = m_names.find(setName);
168         ASSERT_MESSAGE(i != m_names.end(), "cannot detach name");
169         //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
170         detachObserver(NameObserver::NameChangedCaller((*i).second));
171         m_names.erase(i);
172     }
173
174     void makeUnique(const char *name, const NameCallback &setName) const
175     {
176         char buffer[1024];
177         name_write(buffer, m_uniqueNames.make_unique(name_read(name)));
178         setName(buffer);
179     }
180
181     void mergeNames(const BasicNamespace &other) const
182     {
183         typedef std::list<NameCallback> SetNameCallbacks;
184         typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
185         NameGroups groups;
186
187         UniqueNames uniqueNames(other.m_uniqueNames);
188
189         for (Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i) {
190             groups[(*i).second.c_str()].push_back((*i).first);
191         }
192
193         for (NameGroups::iterator i = groups.begin(); i != groups.end(); ++i) {
194             name_t uniqueName(uniqueNames.make_unique(name_read((*i).first.c_str())));
195             uniqueNames.insert(uniqueName);
196
197             char buffer[1024];
198             name_write(buffer, uniqueName);
199
200             //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
201
202             SetNameCallbacks &setNameCallbacks = (*i).second;
203
204             for (SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j) {
205                 (*j)(buffer);
206             }
207         }
208     }
209 };
210
211 BasicNamespace g_defaultNamespace;
212 BasicNamespace g_cloneNamespace;
213
214 class NamespaceAPI {
215     Namespace *m_namespace;
216 public:
217     typedef Namespace Type;
218
219     STRING_CONSTANT(Name, "*");
220
221     NamespaceAPI()
222     {
223         m_namespace = &g_defaultNamespace;
224     }
225
226     Namespace *getTable()
227     {
228         return m_namespace;
229     }
230 };
231
232 typedef SingletonModule<NamespaceAPI> NamespaceModule;
233 typedef Static<NamespaceModule> StaticNamespaceModule;
234 StaticRegisterModule staticRegisterDefaultNamespace(StaticNamespaceModule::instance());
235
236
237 std::list<Namespaced *> g_cloned;
238
239 inline Namespaced *Node_getNamespaced(scene::Node &node)
240 {
241     return NodeTypeCast<Namespaced>::cast(node);
242 }
243
244 void Node_gatherNamespaced(scene::Node &node)
245 {
246     Namespaced *namespaced = Node_getNamespaced(node);
247     if (namespaced != 0) {
248         g_cloned.push_back(namespaced);
249     }
250 }
251
252 class GatherNamespaced : public scene::Traversable::Walker {
253 public:
254     bool pre(scene::Node &node) const
255     {
256         Node_gatherNamespaced(node);
257         return true;
258     }
259 };
260
261 void Map_gatherNamespaced(scene::Node &root)
262 {
263     Node_traverseSubgraph(root, GatherNamespaced());
264 }
265
266 void Map_mergeClonedNames()
267 {
268     for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
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         (*i)->setNamespace(g_defaultNamespace);
274     }
275
276     g_cloned.clear();
277 }
278
279 class WorldNode {
280     scene::Node *m_node;
281 public:
282     WorldNode()
283             : m_node(0)
284     {
285     }
286
287     void set(scene::Node *node)
288     {
289         if (m_node != 0) {
290             m_node->DecRef();
291         }
292         m_node = node;
293         if (m_node != 0) {
294             m_node->IncRef();
295         }
296     }
297
298     scene::Node *get() const
299     {
300         return m_node;
301     }
302 };
303
304 class Map;
305
306 void Map_SetValid(Map &map, bool valid);
307
308 void Map_UpdateTitle(const Map &map);
309
310 void Map_SetWorldspawn(Map &map, scene::Node *node);
311
312
313 class Map : public ModuleObserver {
314 public:
315     CopiedString m_name;
316     Resource *m_resource;
317     bool m_valid;
318
319     bool m_modified;
320
321     void ( *m_modified_changed )(const Map &);
322
323     Signal0 m_mapValidCallbacks;
324
325     WorldNode m_world_node;   // "classname" "worldspawn" !
326
327     Map() : m_resource(0), m_valid(false), m_modified_changed(Map_UpdateTitle)
328     {
329     }
330
331     void realise()
332     {
333         if (m_resource != 0) {
334             if (Map_Unnamed(*this)) {
335                 g_map.m_resource->setNode(NewMapRoot("").get_pointer());
336                 MapFile *map = Node_getMapFile(*g_map.m_resource->getNode());
337                 if (map != 0) {
338                     map->save();
339                 }
340             } else {
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
352     void unrealise()
353     {
354         if (m_resource != 0) {
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         map.m_modified = modified;
419
420         map.m_modified_changed(map);
421     }
422 }
423
424 void Map_UpdateTitle(const Map &map)
425 {
426     Sys_SetTitle(map.m_name.c_str(), Map_Modified(map));
427 }
428
429
430 scene::Node *Map_GetWorldspawn(const Map &map)
431 {
432     return map.m_world_node.get();
433 }
434
435 void Map_SetWorldspawn(Map &map, scene::Node *node)
436 {
437     map.m_world_node.set(node);
438 }
439
440
441 // TTimo
442 // need that in a variable, will have to tweak depending on the game
443 float g_MaxWorldCoord = 64 * 1024;
444 float g_MinWorldCoord = -64 * 1024;
445
446 void AddRegionBrushes(void);
447
448 void RemoveRegionBrushes(void);
449
450
451 /*
452    ================
453    Map_Free
454    free all map elements, reinitialize the structures that depend on them
455    ================
456  */
457 void Map_Free()
458 {
459     Pointfile_Clear();
460
461     g_map.m_resource->detach(g_map);
462     GlobalReferenceCache().release(g_map.m_name.c_str());
463     g_map.m_resource = 0;
464
465     FlushReferences();
466
467     g_currentMap = 0;
468     Brush_unlatchPreferences();
469 }
470
471 class EntityFindByClassname : public scene::Graph::Walker {
472     const char *m_name;
473     Entity *&m_entity;
474 public:
475     EntityFindByClassname(const char *name, Entity *&entity) : m_name(name), m_entity(entity)
476     {
477         m_entity = 0;
478     }
479
480     bool pre(const scene::Path &path, scene::Instance &instance) const
481     {
482         if (m_entity == 0) {
483             Entity *entity = Node_getEntity(path.top());
484             if (entity != 0
485                 && string_equal(m_name, entity->getKeyValue("classname"))) {
486                 m_entity = entity;
487             }
488         }
489         return true;
490     }
491 };
492
493 Entity *Scene_FindEntityByClass(const char *name)
494 {
495     Entity *entity;
496     GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
497     return entity;
498 }
499
500 Entity *Scene_FindPlayerStart()
501 {
502     typedef const char *StaticString;
503     StaticString strings[] = {
504             "info_player_start",
505             "info_player_deathmatch",
506             "team_CTF_redplayer",
507             "team_CTF_blueplayer",
508             "team_CTF_redspawn",
509             "team_CTF_bluespawn",
510     };
511     typedef const StaticString *StaticStringIterator;
512     for (StaticStringIterator i = strings, end = strings + (sizeof(strings) / sizeof(StaticString)); i != end; ++i) {
513         Entity *entity = Scene_FindEntityByClass(*i);
514         if (entity != 0) {
515             return entity;
516         }
517     }
518     return 0;
519 }
520
521 //
522 // move the view to a start position
523 //
524
525
526 void FocusViews(const Vector3 &point, float angle)
527 {
528     CamWnd &camwnd = *g_pParentWnd->GetCamWnd();
529     Camera_setOrigin(camwnd, point);
530     Vector3 angles(Camera_getAngles(camwnd));
531     angles[CAMERA_PITCH] = 0;
532     angles[CAMERA_YAW] = angle;
533     Camera_setAngles(camwnd, angles);
534
535     XYWnd *xywnd = g_pParentWnd->GetXYWnd();
536     xywnd->SetOrigin(point);
537 }
538
539 #include "stringio.h"
540
541 void Map_StartPosition()
542 {
543     Entity *entity = Scene_FindPlayerStart();
544
545     if (entity) {
546         Vector3 origin;
547         string_parse_vector3(entity->getKeyValue("origin"), origin);
548         FocusViews(origin, string_read_float(entity->getKeyValue("angle")));
549     } else {
550         FocusViews(g_vector3_identity, 0);
551     }
552 }
553
554
555 inline bool node_is_worldspawn(scene::Node &node)
556 {
557     Entity *entity = Node_getEntity(node);
558     return entity != 0 && string_equal(entity->getKeyValue("classname"), "worldspawn");
559 }
560
561
562 // use first worldspawn
563 class entity_updateworldspawn : public scene::Traversable::Walker {
564 public:
565     bool pre(scene::Node &node) const
566     {
567         if (node_is_worldspawn(node)) {
568             if (Map_GetWorldspawn(g_map) == 0) {
569                 Map_SetWorldspawn(g_map, &node);
570             }
571         }
572         return false;
573     }
574 };
575
576 scene::Node *Map_FindWorldspawn(Map &map)
577 {
578     Map_SetWorldspawn(map, 0);
579
580     Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
581
582     return Map_GetWorldspawn(map);
583 }
584
585
586 class CollectAllWalker : public scene::Traversable::Walker {
587     scene::Node &m_root;
588     UnsortedNodeSet &m_nodes;
589 public:
590     CollectAllWalker(scene::Node &root, UnsortedNodeSet &nodes) : m_root(root), m_nodes(nodes)
591     {
592     }
593
594     bool pre(scene::Node &node) const
595     {
596         m_nodes.insert(NodeSmartReference(node));
597         Node_getTraversable(m_root)->erase(node);
598         return false;
599     }
600 };
601
602 void Node_insertChildFirst(scene::Node &parent, scene::Node &child)
603 {
604     UnsortedNodeSet nodes;
605     Node_getTraversable(parent)->traverse(CollectAllWalker(parent, nodes));
606     Node_getTraversable(parent)->insert(child);
607
608     for (UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i) {
609         Node_getTraversable(parent)->insert((*i));
610     }
611 }
612
613 scene::Node &createWorldspawn()
614 {
615     NodeSmartReference worldspawn(
616             GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("worldspawn", true)));
617     Node_insertChildFirst(GlobalSceneGraph().root(), worldspawn);
618     return worldspawn;
619 }
620
621 void Map_UpdateWorldspawn(Map &map)
622 {
623     if (Map_FindWorldspawn(map) == 0) {
624         Map_SetWorldspawn(map, &createWorldspawn());
625     }
626 }
627
628 scene::Node &Map_FindOrInsertWorldspawn(Map &map)
629 {
630     Map_UpdateWorldspawn(map);
631     return *Map_GetWorldspawn(map);
632 }
633
634
635 class MapMergeAll : public scene::Traversable::Walker {
636     mutable scene::Path m_path;
637 public:
638     MapMergeAll(const scene::Path &root)
639             : m_path(root)
640     {
641     }
642
643     bool pre(scene::Node &node) const
644     {
645         Node_getTraversable(m_path.top())->insert(node);
646         m_path.push(makeReference(node));
647         selectPath(m_path, true);
648         return false;
649     }
650
651     void post(scene::Node &node) const
652     {
653         m_path.pop();
654     }
655 };
656
657 class MapMergeEntities : public scene::Traversable::Walker {
658     mutable scene::Path m_path;
659 public:
660     MapMergeEntities(const scene::Path &root)
661             : m_path(root)
662     {
663     }
664
665     bool pre(scene::Node &node) const
666     {
667         if (node_is_worldspawn(node)) {
668             scene::Node *world_node = Map_FindWorldspawn(g_map);
669             if (world_node == 0) {
670                 Map_SetWorldspawn(g_map, &node);
671                 Node_getTraversable(m_path.top().get())->insert(node);
672                 m_path.push(makeReference(node));
673                 Node_getTraversable(node)->traverse(SelectChildren(m_path));
674             } else {
675                 m_path.push(makeReference(*world_node));
676                 Node_getTraversable(node)->traverse(MapMergeAll(m_path));
677             }
678         } else {
679             Node_getTraversable(m_path.top())->insert(node);
680             m_path.push(makeReference(node));
681             if (node_is_group(node)) {
682                 Node_getTraversable(node)->traverse(SelectChildren(m_path));
683             } else {
684                 selectPath(m_path, true);
685             }
686         }
687         return false;
688     }
689
690     void post(scene::Node &node) const
691     {
692         m_path.pop();
693     }
694 };
695
696 class BasicContainer : public scene::Node::Symbiot {
697     class TypeCasts {
698         NodeTypeCastTable m_casts;
699     public:
700         TypeCasts()
701         {
702             NodeContainedCast<BasicContainer, scene::Traversable>::install(m_casts);
703         }
704
705         NodeTypeCastTable &get()
706         {
707             return m_casts;
708         }
709     };
710
711     scene::Node m_node;
712     TraversableNodeSet m_traverse;
713 public:
714
715     typedef LazyStatic<TypeCasts> StaticTypeCasts;
716
717     scene::Traversable &get(NullType<scene::Traversable>)
718     {
719         return m_traverse;
720     }
721
722     BasicContainer() : m_node(this, this, StaticTypeCasts::instance().get())
723     {
724     }
725
726     void release()
727     {
728         delete this;
729     }
730
731     scene::Node &node()
732     {
733         return m_node;
734     }
735 };
736
737 /// Merges the map graph rooted at \p node into the global scene-graph.
738 void MergeMap(scene::Node &node)
739 {
740     Node_getTraversable(node)->traverse(MapMergeEntities(scene::Path(makeReference(GlobalSceneGraph().root()))));
741 }
742
743 void Map_ImportSelected(TextInputStream &in, const MapFormat &format)
744 {
745     NodeSmartReference node((new BasicContainer)->node());
746     format.readGraph(node, in, GlobalEntityCreator());
747     Map_gatherNamespaced(node);
748     Map_mergeClonedNames();
749     MergeMap(node);
750 }
751
752 inline scene::Cloneable *Node_getCloneable(scene::Node &node)
753 {
754     return NodeTypeCast<scene::Cloneable>::cast(node);
755 }
756
757 inline scene::Node &node_clone(scene::Node &node)
758 {
759     scene::Cloneable *cloneable = Node_getCloneable(node);
760     if (cloneable != 0) {
761         return cloneable->clone();
762     }
763
764     return (new scene::NullNode)->node();
765 }
766
767 class CloneAll : public scene::Traversable::Walker {
768     mutable scene::Path m_path;
769 public:
770     CloneAll(scene::Node &root)
771             : m_path(makeReference(root))
772     {
773     }
774
775     bool pre(scene::Node &node) const
776     {
777         if (node.isRoot()) {
778             return false;
779         }
780
781         m_path.push(makeReference(node_clone(node)));
782         m_path.top().get().IncRef();
783
784         return true;
785     }
786
787     void post(scene::Node &node) const
788     {
789         if (node.isRoot()) {
790             return;
791         }
792
793         Node_getTraversable(m_path.parent())->insert(m_path.top());
794
795         m_path.top().get().DecRef();
796         m_path.pop();
797     }
798 };
799
800 scene::Node &Node_Clone(scene::Node &node)
801 {
802     scene::Node &clone = node_clone(node);
803     scene::Traversable *traversable = Node_getTraversable(node);
804     if (traversable != 0) {
805         traversable->traverse(CloneAll(clone));
806     }
807     return clone;
808 }
809
810
811 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
812
813 class EntityBreakdownWalker : public scene::Graph::Walker {
814     EntityBreakdown &m_entitymap;
815 public:
816     EntityBreakdownWalker(EntityBreakdown &entitymap)
817             : m_entitymap(entitymap)
818     {
819     }
820
821     bool pre(const scene::Path &path, scene::Instance &instance) const
822     {
823         Entity *entity = Node_getEntity(path.top());
824         if (entity != 0) {
825             const EntityClass &eclass = entity->getEntityClass();
826             if (m_entitymap.find(eclass.name()) == m_entitymap.end()) {
827                 m_entitymap[eclass.name()] = 1;
828             } else { ++m_entitymap[eclass.name()]; }
829         }
830         return true;
831     }
832 };
833
834 void Scene_EntityBreakdown(EntityBreakdown &entitymap)
835 {
836     GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap));
837 }
838
839
840 WindowPosition g_posMapInfoWnd(c_default_window_pos);
841
842 void DoMapInfo()
843 {
844     ModalDialog dialog;
845     ui::Entry brushes_entry{ui::null};
846     ui::Entry entities_entry{ui::null};
847     ui::ListStore EntityBreakdownWalker{ui::null};
848
849     ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback),
850                                                                    &dialog);
851
852     window_set_position(window, g_posMapInfoWnd);
853
854     {
855         auto vbox = create_dialog_vbox(4, 4);
856         window.add(vbox);
857
858         {
859             auto hbox = create_dialog_hbox(4);
860             vbox.pack_start(hbox, FALSE, TRUE, 0);
861
862             {
863                 auto table = create_dialog_table(2, 2, 4, 4);
864                 hbox.pack_start(table, TRUE, TRUE, 0);
865
866                 {
867                     auto entry = ui::Entry(ui::New);
868                     entry.show();
869                     table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
870                     gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
871
872                     brushes_entry = entry;
873                 }
874                 {
875                     auto entry = ui::Entry(ui::New);
876                     entry.show();
877                     table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
878                     gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
879
880                     entities_entry = entry;
881                 }
882                 {
883                     ui::Widget label = ui::Label("Total Brushes");
884                     label.show();
885                     table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
886                     gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
887                 }
888                 {
889                     ui::Widget label = ui::Label("Total Entities");
890                     label.show();
891                     table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
892                     gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
893                 }
894             }
895             {
896                 auto vbox2 = create_dialog_vbox(4);
897                 hbox.pack_start(vbox2, FALSE, FALSE, 0);
898
899                 {
900                     auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog);
901                     vbox2.pack_start(button, FALSE, FALSE, 0);
902                 }
903             }
904         }
905         {
906             ui::Widget label = ui::Label("Entity breakdown");
907             label.show();
908             vbox.pack_start(label, FALSE, TRUE, 0);
909             gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
910         }
911         {
912             auto scr = create_scrolled_window(ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4);
913             vbox.pack_start(scr, TRUE, TRUE, 0);
914
915             {
916                 auto store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING));
917
918                 auto view = ui::TreeView(ui::TreeModel::from(store._handle));
919                 gtk_tree_view_set_headers_clickable(view, TRUE);
920
921                 {
922                     auto renderer = ui::CellRendererText(ui::New);
923                     auto column = ui::TreeViewColumn("Entity", renderer, {{"text", 0}});
924                     gtk_tree_view_append_column(view, column);
925                     gtk_tree_view_column_set_sort_column_id(column, 0);
926                 }
927
928                 {
929                     auto renderer = ui::CellRendererText(ui::New);
930                     auto column = ui::TreeViewColumn("Count", renderer, {{"text", 1}});
931                     gtk_tree_view_append_column(view, column);
932                     gtk_tree_view_column_set_sort_column_id(column, 1);
933                 }
934
935                 view.show();
936
937                 scr.add(view);
938
939                 EntityBreakdownWalker = store;
940             }
941         }
942     }
943
944     // Initialize fields
945
946     {
947         EntityBreakdown entitymap;
948         Scene_EntityBreakdown(entitymap);
949
950         for (EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i) {
951             char tmp[16];
952             sprintf(tmp, "%u", Unsigned((*i).second));
953             EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
954         }
955     }
956
957     EntityBreakdownWalker.unref();
958
959     char tmp[16];
960     sprintf(tmp, "%u", Unsigned(g_brushCount.get()));
961     brushes_entry.text(tmp);
962     sprintf(tmp, "%u", Unsigned(g_entityCount.get()));
963     entities_entry.text(tmp);
964
965     modal_dialog_show(window, dialog);
966
967     // save before exit
968     window_get_position(window, g_posMapInfoWnd);
969
970     window.destroy();
971 }
972
973
974 class ScopeTimer {
975     Timer m_timer;
976     const char *m_message;
977 public:
978     ScopeTimer(const char *message)
979             : m_message(message)
980     {
981         m_timer.start();
982     }
983
984     ~ScopeTimer()
985     {
986         double elapsed_time = m_timer.elapsed_msec() / 1000.f;
987         globalOutputStream() << m_message << " timer: " << FloatFormat(elapsed_time, 5, 2) << " second(s) elapsed\n";
988     }
989 };
990
991 CopiedString g_strLastFolder = "";
992
993 /*
994    ================
995    Map_LoadFile
996    ================
997  */
998
999 void Map_LoadFile(const char *filename)
1000 {
1001     globalOutputStream() << "Loading map from " << filename << "\n";
1002     ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1003
1004     MRU_AddFile(filename);
1005     g_strLastFolder = g_path_get_dirname(filename);
1006
1007     {
1008         ScopeTimer timer("map load");
1009
1010         const MapFormat *format = NULL;
1011         const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1012         if (string_not_empty(moduleName)) {
1013             format = ReferenceAPI_getMapModules().findModule(moduleName);
1014         }
1015
1016         for (int i = 0; i < Brush_toggleFormatCount(); ++i) {
1017             if (i) {
1018                 Map_Free();
1019             }
1020             Brush_toggleFormat(i);
1021             g_map.m_name = filename;
1022             Map_UpdateTitle(g_map);
1023             g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1024             if (format) {
1025                 format->wrongFormat = false;
1026             }
1027             g_map.m_resource->attach(g_map);
1028             if (format) {
1029                 if (!format->wrongFormat) {
1030                     break;
1031                 }
1032             }
1033         }
1034
1035         Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
1036     }
1037
1038     globalOutputStream() << "--- LoadMapFile ---\n";
1039     globalOutputStream() << g_map.m_name.c_str() << "\n";
1040
1041     globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
1042     globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
1043
1044     //GlobalEntityCreator().printStatistics();
1045
1046     //
1047     // move the view to a start position
1048     //
1049     Map_StartPosition();
1050
1051     g_currentMap = &g_map;
1052
1053     // restart VFS to apply new pak filtering based on mapname
1054     // needed for daemon DPK VFS
1055     VFS_Restart();
1056 }
1057
1058 class Excluder {
1059 public:
1060     virtual bool excluded(scene::Node &node) const = 0;
1061 };
1062
1063 class ExcludeWalker : public scene::Traversable::Walker {
1064     const scene::Traversable::Walker &m_walker;
1065     const Excluder *m_exclude;
1066     mutable bool m_skip;
1067 public:
1068     ExcludeWalker(const scene::Traversable::Walker &walker, const Excluder &exclude)
1069             : m_walker(walker), m_exclude(&exclude), m_skip(false)
1070     {
1071     }
1072
1073     bool pre(scene::Node &node) const
1074     {
1075         if (m_exclude->excluded(node) || node.isRoot()) {
1076             m_skip = true;
1077             return false;
1078         } else {
1079             m_walker.pre(node);
1080         }
1081         return true;
1082     }
1083
1084     void post(scene::Node &node) const
1085     {
1086         if (m_skip) {
1087             m_skip = false;
1088         } else {
1089             m_walker.post(node);
1090         }
1091     }
1092 };
1093
1094 class AnyInstanceSelected : public scene::Instantiable::Visitor {
1095     bool &m_selected;
1096 public:
1097     AnyInstanceSelected(bool &selected) : m_selected(selected)
1098     {
1099         m_selected = false;
1100     }
1101
1102     void visit(scene::Instance &instance) const
1103     {
1104         Selectable *selectable = Instance_getSelectable(instance);
1105         if (selectable != 0
1106             && selectable->isSelected()) {
1107             m_selected = true;
1108         }
1109     }
1110 };
1111
1112 bool Node_instanceSelected(scene::Node &node)
1113 {
1114     scene::Instantiable *instantiable = Node_getInstantiable(node);
1115     ASSERT_NOTNULL(instantiable);
1116     bool selected;
1117     instantiable->forEachInstance(AnyInstanceSelected(selected));
1118     return selected;
1119 }
1120
1121 class SelectedDescendantWalker : public scene::Traversable::Walker {
1122     bool &m_selected;
1123 public:
1124     SelectedDescendantWalker(bool &selected) : m_selected(selected)
1125     {
1126         m_selected = false;
1127     }
1128
1129     bool pre(scene::Node &node) const
1130     {
1131         if (node.isRoot()) {
1132             return false;
1133         }
1134
1135         if (Node_instanceSelected(node)) {
1136             m_selected = true;
1137         }
1138
1139         return true;
1140     }
1141 };
1142
1143 bool Node_selectedDescendant(scene::Node &node)
1144 {
1145     bool selected;
1146     Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
1147     return selected;
1148 }
1149
1150 class SelectionExcluder : public Excluder {
1151 public:
1152     bool excluded(scene::Node &node) const
1153     {
1154         return !Node_selectedDescendant(node);
1155     }
1156 };
1157
1158 class IncludeSelectedWalker : public scene::Traversable::Walker {
1159     const scene::Traversable::Walker &m_walker;
1160     mutable std::size_t m_selected;
1161     mutable bool m_skip;
1162
1163     bool selectedParent() const
1164     {
1165         return m_selected != 0;
1166     }
1167
1168 public:
1169     IncludeSelectedWalker(const scene::Traversable::Walker &walker)
1170             : m_walker(walker), m_selected(0), m_skip(false)
1171     {
1172     }
1173
1174     bool pre(scene::Node &node) const
1175     {
1176         // include node if:
1177         // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1178         if (!node.isRoot() && (Node_selectedDescendant(node) || selectedParent())) {
1179             if (Node_instanceSelected(node)) {
1180                 ++m_selected;
1181             }
1182             m_walker.pre(node);
1183             return true;
1184         } else {
1185             m_skip = true;
1186             return false;
1187         }
1188     }
1189
1190     void post(scene::Node &node) const
1191     {
1192         if (m_skip) {
1193             m_skip = false;
1194         } else {
1195             if (Node_instanceSelected(node)) {
1196                 --m_selected;
1197             }
1198             m_walker.post(node);
1199         }
1200     }
1201 };
1202
1203 void Map_Traverse_Selected(scene::Node &root, const scene::Traversable::Walker &walker)
1204 {
1205     scene::Traversable *traversable = Node_getTraversable(root);
1206     if (traversable != 0) {
1207 #if 0
1208         traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1209 #else
1210         traversable->traverse(IncludeSelectedWalker(walker));
1211 #endif
1212     }
1213 }
1214
1215 void Map_ExportSelected(TextOutputStream &out, const MapFormat &format)
1216 {
1217     format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out);
1218 }
1219
1220 void Map_Traverse(scene::Node &root, const scene::Traversable::Walker &walker)
1221 {
1222     scene::Traversable *traversable = Node_getTraversable(root);
1223     if (traversable != 0) {
1224         traversable->traverse(walker);
1225     }
1226 }
1227
1228 class RegionExcluder : public Excluder {
1229 public:
1230     bool excluded(scene::Node &node) const
1231     {
1232         return node.excluded();
1233     }
1234 };
1235
1236 void Map_Traverse_Region(scene::Node &root, const scene::Traversable::Walker &walker)
1237 {
1238     scene::Traversable *traversable = Node_getTraversable(root);
1239     if (traversable != 0) {
1240         traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
1241     }
1242 }
1243
1244 bool Map_SaveRegion(const char *filename)
1245 {
1246     AddRegionBrushes();
1247
1248     bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region,
1249                                         filename);
1250
1251     RemoveRegionBrushes();
1252
1253     return success;
1254 }
1255
1256
1257 void Map_RenameAbsolute(const char *absolute)
1258 {
1259     Resource *resource = GlobalReferenceCache().capture(absolute);
1260     NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
1261     resource->setNode(clone.get_pointer());
1262
1263     {
1264         //ScopeTimer timer("clone subgraph");
1265         Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
1266     }
1267
1268     g_map.m_resource->detach(g_map);
1269     GlobalReferenceCache().release(g_map.m_name.c_str());
1270
1271     g_map.m_resource = resource;
1272
1273     g_map.m_name = absolute;
1274     Map_UpdateTitle(g_map);
1275
1276     g_map.m_resource->attach(g_map);
1277     // refresh VFS to apply new pak filtering based on mapname
1278     // needed for daemon DPK VFS
1279     VFS_Refresh();
1280 }
1281
1282 void Map_Rename(const char *filename)
1283 {
1284     if (!string_equal(g_map.m_name.c_str(), filename)) {
1285         ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1286
1287         Map_RenameAbsolute(filename);
1288
1289         SceneChangeNotify();
1290     } else {
1291         SaveReferences();
1292     }
1293 }
1294
1295 bool Map_Save()
1296 {
1297     Pointfile_Clear();
1298
1299     ScopeTimer timer("map save");
1300     SaveReferences();
1301     return true; // assume success..
1302 }
1303
1304 /*
1305    ===========
1306    Map_New
1307
1308    ===========
1309  */
1310 void Map_New()
1311 {
1312     //globalOutputStream() << "Map_New\n";
1313
1314     g_map.m_name = "unnamed.map";
1315     Map_UpdateTitle(g_map);
1316
1317     {
1318         g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1319 //    ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1320         g_map.m_resource->attach(g_map);
1321
1322         SceneChangeNotify();
1323     }
1324
1325     FocusViews(g_vector3_identity, 0);
1326
1327     g_currentMap = &g_map;
1328
1329     // restart VFS to apply new pak filtering based on mapname
1330     // needed for daemon DPK VFS
1331     VFS_Restart();
1332 }
1333
1334 extern void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 &region_mins, const Vector3 &region_maxs);
1335
1336 void ConstructRegionStartpoint(scene::Node *startpoint, const Vector3 &region_mins, const Vector3 &region_maxs)
1337 {
1338     /*!
1339            \todo we need to make sure that the player start IS inside the region and bail out if it's not
1340            the compiler will refuse to compile a map with a player_start somewhere in empty space..
1341            for now, let's just print an error
1342          */
1343
1344     Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
1345
1346     for (int i = 0; i < 3; i++) {
1347         if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) {
1348             globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1349             break;
1350         }
1351     }
1352
1353     // write the info_playerstart
1354     char sTmp[1024];
1355     sprintf(sTmp, "%d %d %d", (int) vOrig[0], (int) vOrig[1], (int) vOrig[2]);
1356     Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
1357     sprintf(sTmp, "%d", (int) Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
1358     Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
1359 }
1360
1361 /*
1362    ===========================================================
1363
1364    REGION
1365
1366    ===========================================================
1367  */
1368 bool region_active;
1369 Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
1370 Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
1371
1372 scene::Node *region_sides[6];
1373 scene::Node *region_startpoint = 0;
1374
1375 /*
1376    ===========
1377    AddRegionBrushes
1378    a regioned map will have temp walls put up at the region boundary
1379    \todo TODO TTimo old implementation of region brushes
1380    we still add them straight in the worldspawn and take them out after the map is saved
1381    with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1382    ===========
1383  */
1384 void AddRegionBrushes(void)
1385 {
1386     int i;
1387
1388     for (i = 0; i < 6; i++) {
1389         region_sides[i] = &GlobalBrushCreator().createBrush();
1390         Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
1391     }
1392
1393     region_startpoint = &GlobalEntityCreator().createEntity(
1394             GlobalEntityClassManager().findOrInsert("info_player_start", false));
1395
1396     ConstructRegionBrushes(region_sides, region_mins, region_maxs);
1397     ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
1398
1399     Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
1400 }
1401
1402 void RemoveRegionBrushes(void)
1403 {
1404     for (std::size_t i = 0; i < 6; i++) {
1405         Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
1406     }
1407     Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
1408 }
1409
1410 inline void exclude_node(scene::Node &node, bool exclude)
1411 {
1412     exclude
1413     ? node.enable(scene::Node::eExcluded)
1414     : node.disable(scene::Node::eExcluded);
1415 }
1416
1417 class ExcludeAllWalker : public scene::Graph::Walker {
1418     bool m_exclude;
1419 public:
1420     ExcludeAllWalker(bool exclude)
1421             : m_exclude(exclude)
1422     {
1423     }
1424
1425     bool pre(const scene::Path &path, scene::Instance &instance) const
1426     {
1427         exclude_node(path.top(), m_exclude);
1428
1429         return true;
1430     }
1431 };
1432
1433 void Scene_Exclude_All(bool exclude)
1434 {
1435     GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
1436 }
1437
1438 bool Instance_isSelected(const scene::Instance &instance)
1439 {
1440     const Selectable *selectable = Instance_getSelectable(instance);
1441     return selectable != 0 && selectable->isSelected();
1442 }
1443
1444 class ExcludeSelectedWalker : public scene::Graph::Walker {
1445     bool m_exclude;
1446 public:
1447     ExcludeSelectedWalker(bool exclude)
1448             : m_exclude(exclude)
1449     {
1450     }
1451
1452     bool pre(const scene::Path &path, scene::Instance &instance) const
1453     {
1454         exclude_node(path.top(),
1455                      (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
1456         return true;
1457     }
1458 };
1459
1460 void Scene_Exclude_Selected(bool exclude)
1461 {
1462     GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
1463 }
1464
1465 class ExcludeRegionedWalker : public scene::Graph::Walker {
1466     bool m_exclude;
1467 public:
1468     ExcludeRegionedWalker(bool exclude)
1469             : m_exclude(exclude)
1470     {
1471     }
1472
1473     bool pre(const scene::Path &path, scene::Instance &instance) const
1474     {
1475         exclude_node(
1476                 path.top(),
1477                 !(
1478                         (
1479                                 aabb_intersects_aabb(
1480                                         instance.worldAABB(),
1481                                         aabb_for_minmax(region_mins, region_maxs)
1482                                 ) != 0
1483                         ) ^ m_exclude
1484                 )
1485         );
1486
1487         return true;
1488     }
1489 };
1490
1491 void Scene_Exclude_Region(bool exclude)
1492 {
1493     GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
1494 }
1495
1496 /*
1497    ===========
1498    Map_RegionOff
1499
1500    Other filtering options may still be on
1501    ===========
1502  */
1503 void Map_RegionOff()
1504 {
1505     region_active = false;
1506
1507     region_maxs[0] = g_MaxWorldCoord - 64;
1508     region_mins[0] = g_MinWorldCoord + 64;
1509     region_maxs[1] = g_MaxWorldCoord - 64;
1510     region_mins[1] = g_MinWorldCoord + 64;
1511     region_maxs[2] = g_MaxWorldCoord - 64;
1512     region_mins[2] = g_MinWorldCoord + 64;
1513
1514     Scene_Exclude_All(false);
1515 }
1516
1517 void Map_ApplyRegion(void)
1518 {
1519     region_active = true;
1520
1521     Scene_Exclude_Region(false);
1522 }
1523
1524
1525 /*
1526    ========================
1527    Map_RegionSelectedBrushes
1528    ========================
1529  */
1530 void Map_RegionSelectedBrushes(void)
1531 {
1532     Map_RegionOff();
1533
1534     if (GlobalSelectionSystem().countSelected() != 0
1535         && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) {
1536         region_active = true;
1537         Select_GetBounds(region_mins, region_maxs);
1538
1539         Scene_Exclude_Selected(false);
1540
1541         GlobalSelectionSystem().setSelectedAll(false);
1542     }
1543 }
1544
1545
1546 /*
1547    ===========
1548    Map_RegionXY
1549    ===========
1550  */
1551 void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
1552 {
1553     Map_RegionOff();
1554
1555     region_mins[0] = x_min;
1556     region_maxs[0] = x_max;
1557     region_mins[1] = y_min;
1558     region_maxs[1] = y_max;
1559     region_mins[2] = g_MinWorldCoord + 64;
1560     region_maxs[2] = g_MaxWorldCoord - 64;
1561
1562     Map_ApplyRegion();
1563 }
1564
1565 void Map_RegionBounds(const AABB &bounds)
1566 {
1567     Map_RegionOff();
1568
1569     region_mins = vector3_subtracted(bounds.origin, bounds.extents);
1570     region_maxs = vector3_added(bounds.origin, bounds.extents);
1571
1572     deleteSelection();
1573
1574     Map_ApplyRegion();
1575 }
1576
1577 /*
1578    ===========
1579    Map_RegionBrush
1580    ===========
1581  */
1582 void Map_RegionBrush(void)
1583 {
1584     if (GlobalSelectionSystem().countSelected() != 0) {
1585         scene::Instance &instance = GlobalSelectionSystem().ultimateSelected();
1586         Map_RegionBounds(instance.worldAABB());
1587     }
1588 }
1589
1590 //
1591 //================
1592 //Map_ImportFile
1593 //================
1594 //
1595 bool Map_ImportFile(const char *filename)
1596 {
1597     ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1598
1599     g_strLastFolder = g_path_get_dirname(filename);
1600
1601     bool success = false;
1602
1603     if (extension_equal(path_get_extension(filename), "bsp")) {
1604         goto tryDecompile;
1605     }
1606
1607     {
1608         const MapFormat *format = NULL;
1609         const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1610         if (string_not_empty(moduleName)) {
1611             format = ReferenceAPI_getMapModules().findModule(moduleName);
1612         }
1613
1614         if (format) {
1615             format->wrongFormat = false;
1616         }
1617         Resource *resource = GlobalReferenceCache().capture(filename);
1618         resource->refresh(); // avoid loading old version if map has changed on disk since last import
1619         if (!resource->load()) {
1620             GlobalReferenceCache().release(filename);
1621             goto tryDecompile;
1622         }
1623         if (format) {
1624             if (format->wrongFormat) {
1625                 GlobalReferenceCache().release(filename);
1626                 goto tryDecompile;
1627             }
1628         }
1629         NodeSmartReference clone(NewMapRoot(""));
1630         Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1631         Map_gatherNamespaced(clone);
1632         Map_mergeClonedNames();
1633         MergeMap(clone);
1634         success = true;
1635         GlobalReferenceCache().release(filename);
1636     }
1637
1638     SceneChangeNotify();
1639
1640     return success;
1641
1642     tryDecompile:
1643
1644     const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue("q3map2_type");
1645     int n = string_length(path_get_extension(filename));
1646     if (n && (extension_equal(path_get_extension(filename), "bsp") ||
1647               extension_equal(path_get_extension(filename), "map"))) {
1648         StringBuffer output;
1649         output.push_string(AppPath_get());
1650         output.push_string("q3map2.");
1651         output.push_string(RADIANT_EXECUTABLE);
1652         output.push_string(" -v -game ");
1653         output.push_string((type && *type) ? type : "quake3");
1654         output.push_string(" -fs_basepath \"");
1655         output.push_string(EnginePath_get());
1656         output.push_string("\" -fs_homepath \"");
1657         output.push_string(g_qeglobals.m_userEnginePath.c_str());
1658         output.push_string("\"");
1659
1660         // extra pakpaths
1661         for (int i = 0; i < g_pakPathCount; i++) {
1662             if (g_strcmp0(g_strPakPath[i].c_str(), "")) {
1663                 output.push_string(" -fs_pakpath \"");
1664                 output.push_string(g_strPakPath[i].c_str());
1665                 output.push_string("\"");
1666             }
1667         }
1668
1669         // extra switches
1670         if (g_disableEnginePath) {
1671             output.push_string(" -fs_nobasepath ");
1672         }
1673
1674         if (g_disableHomePath) {
1675             output.push_string(" -fs_nohomepath ");
1676         }
1677
1678         output.push_string(" -fs_game ");
1679         output.push_string(gamename_get());
1680         output.push_string(" -convert -format ");
1681         output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
1682         if (extension_equal(path_get_extension(filename), "map")) {
1683             output.push_string(" -readmap ");
1684         }
1685         output.push_string(" \"");
1686         output.push_string(filename);
1687         output.push_string("\"");
1688
1689         // run
1690         Q_Exec(NULL, output.c_str(), NULL, false, true);
1691
1692         // rebuild filename as "filenamewithoutext_converted.map"
1693         output.clear();
1694         output.push_range(filename, filename + string_length(filename) - (n + 1));
1695         output.push_string("_converted.map");
1696         filename = output.c_str();
1697
1698         // open
1699         Resource *resource = GlobalReferenceCache().capture(filename);
1700         resource->refresh(); // avoid loading old version if map has changed on disk since last import
1701         if (!resource->load()) {
1702             GlobalReferenceCache().release(filename);
1703             goto tryDecompile;
1704         }
1705         NodeSmartReference clone(NewMapRoot(""));
1706         Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1707         Map_gatherNamespaced(clone);
1708         Map_mergeClonedNames();
1709         MergeMap(clone);
1710         success = true;
1711         GlobalReferenceCache().release(filename);
1712     }
1713
1714     SceneChangeNotify();
1715     return success;
1716 }
1717
1718 /*
1719    ===========
1720    Map_SaveFile
1721    ===========
1722  */
1723 bool Map_SaveFile(const char *filename)
1724 {
1725     ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1726     bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename);
1727     if (success) {
1728         // refresh VFS to apply new pak filtering based on mapname
1729         // needed for daemon DPK VFS
1730         VFS_Refresh();
1731     }
1732     return success;
1733 }
1734
1735 //
1736 //===========
1737 //Map_SaveSelected
1738 //===========
1739 //
1740 // Saves selected world brushes and whole entities with partial/full selections
1741 //
1742 bool Map_SaveSelected(const char *filename)
1743 {
1744     return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected,
1745                                 filename);
1746 }
1747
1748
1749 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker {
1750     scene::Node &m_parent;
1751 public:
1752     ParentSelectedBrushesToEntityWalker(scene::Node &parent) : m_parent(parent)
1753     {
1754     }
1755
1756     bool pre(const scene::Path &path, scene::Instance &instance) const
1757     {
1758         if (path.top().get_pointer() != &m_parent
1759             && Node_isPrimitive(path.top())) {
1760             Selectable *selectable = Instance_getSelectable(instance);
1761             if (selectable != 0
1762                 && selectable->isSelected()
1763                 && path.size() > 1) {
1764                 return false;
1765             }
1766         }
1767         return true;
1768     }
1769
1770     void post(const scene::Path &path, scene::Instance &instance) const
1771     {
1772         if (path.top().get_pointer() != &m_parent
1773             && Node_isPrimitive(path.top())) {
1774             Selectable *selectable = Instance_getSelectable(instance);
1775             if (selectable != 0
1776                 && selectable->isSelected()
1777                 && path.size() > 1) {
1778                 scene::Node &parent = path.parent();
1779                 if (&parent != &m_parent) {
1780                     NodeSmartReference node(path.top().get());
1781                     Node_getTraversable(parent)->erase(node);
1782                     Node_getTraversable(m_parent)->insert(node);
1783                 }
1784             }
1785         }
1786     }
1787 };
1788
1789 void Scene_parentSelectedBrushesToEntity(scene::Graph &graph, scene::Node &parent)
1790 {
1791     graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1792 }
1793
1794 class CountSelectedBrushes : public scene::Graph::Walker {
1795     std::size_t &m_count;
1796     mutable std::size_t m_depth;
1797 public:
1798     CountSelectedBrushes(std::size_t &count) : m_count(count), m_depth(0)
1799     {
1800         m_count = 0;
1801     }
1802
1803     bool pre(const scene::Path &path, scene::Instance &instance) const
1804     {
1805         if (++m_depth != 1 && path.top().get().isRoot()) {
1806             return false;
1807         }
1808         Selectable *selectable = Instance_getSelectable(instance);
1809         if (selectable != 0
1810             && selectable->isSelected()
1811             && Node_isPrimitive(path.top())) {
1812             ++m_count;
1813         }
1814         return true;
1815     }
1816
1817     void post(const scene::Path &path, scene::Instance &instance) const
1818     {
1819         --m_depth;
1820     }
1821 };
1822
1823 std::size_t Scene_countSelectedBrushes(scene::Graph &graph)
1824 {
1825     std::size_t count;
1826     graph.traverse(CountSelectedBrushes(count));
1827     return count;
1828 }
1829
1830 enum ENodeType {
1831     eNodeUnknown,
1832     eNodeMap,
1833     eNodeEntity,
1834     eNodePrimitive,
1835 };
1836
1837 const char *nodetype_get_name(ENodeType type)
1838 {
1839     if (type == eNodeMap) {
1840         return "map";
1841     }
1842     if (type == eNodeEntity) {
1843         return "entity";
1844     }
1845     if (type == eNodePrimitive) {
1846         return "primitive";
1847     }
1848     return "unknown";
1849 }
1850
1851 ENodeType node_get_nodetype(scene::Node &node)
1852 {
1853     if (Node_isEntity(node)) {
1854         return eNodeEntity;
1855     }
1856     if (Node_isPrimitive(node)) {
1857         return eNodePrimitive;
1858     }
1859     return eNodeUnknown;
1860 }
1861
1862 bool contains_entity(scene::Node &node)
1863 {
1864     return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1865 }
1866
1867 bool contains_primitive(scene::Node &node)
1868 {
1869     return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1870 }
1871
1872 ENodeType node_get_contains(scene::Node &node)
1873 {
1874     if (contains_entity(node)) {
1875         return eNodeEntity;
1876     }
1877     if (contains_primitive(node)) {
1878         return eNodePrimitive;
1879     }
1880     return eNodeUnknown;
1881 }
1882
1883 void Path_parent(const scene::Path &parent, const scene::Path &child)
1884 {
1885     ENodeType contains = node_get_contains(parent.top());
1886     ENodeType type = node_get_nodetype(child.top());
1887
1888     if (contains != eNodeUnknown && contains == type) {
1889         NodeSmartReference node(child.top().get());
1890         Path_deleteTop(child);
1891         Node_getTraversable(parent.top())->insert(node);
1892         SceneChangeNotify();
1893     } else {
1894         globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to "
1895                             << nodetype_get_name(contains) << " container.\n";
1896     }
1897 }
1898
1899 void Scene_parentSelected()
1900 {
1901     UndoableCommand undo("parentSelected");
1902
1903     if (GlobalSelectionSystem().countSelected() > 1) {
1904         class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor {
1905             const scene::Path &m_parent;
1906         public:
1907             ParentSelectedBrushesToEntityWalker(const scene::Path &parent) : m_parent(parent)
1908             {
1909             }
1910
1911             void visit(scene::Instance &instance) const
1912             {
1913                 if (&m_parent != &instance.path()) {
1914                     Path_parent(m_parent, instance.path());
1915                 }
1916             }
1917         };
1918
1919         ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1920         GlobalSelectionSystem().foreachSelected(visitor);
1921     } else {
1922         globalOutputStream() << "failed - did not find two selected nodes.\n";
1923     }
1924 }
1925
1926
1927 void NewMap()
1928 {
1929     if (ConfirmModified("New Map")) {
1930         Map_RegionOff();
1931         Map_Free();
1932         Map_New();
1933     }
1934 }
1935
1936 CopiedString g_mapsPath;
1937
1938 const char *getMapsPath()
1939 {
1940     return g_mapsPath.c_str();
1941 }
1942
1943 const char *getLastFolderPath()
1944 {
1945     if (g_strLastFolder.empty()) {
1946         GlobalPreferenceSystem().registerPreference("LastFolder", make_property_string(g_strLastFolder));
1947         if (g_strLastFolder.empty()) {
1948             g_strLastFolder = g_qeglobals.m_userGamePath;
1949         }
1950     }
1951     return g_strLastFolder.c_str();
1952 }
1953
1954 const char *map_open(const char *title)
1955 {
1956     return MainFrame_getWindow().file_dialog(TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false);
1957 }
1958
1959 const char *map_import(const char *title)
1960 {
1961     return MainFrame_getWindow().file_dialog(TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false);
1962 }
1963
1964 const char *map_save(const char *title)
1965 {
1966     return MainFrame_getWindow().file_dialog(FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true);
1967 }
1968
1969 void OpenMap()
1970 {
1971     if (!ConfirmModified("Open Map")) {
1972         return;
1973     }
1974
1975     const char *filename = map_open("Open Map");
1976
1977     if (filename != NULL) {
1978         MRU_AddFile(filename);
1979         Map_RegionOff();
1980         Map_Free();
1981         Map_LoadFile(filename);
1982     }
1983 }
1984
1985 void ImportMap()
1986 {
1987     const char *filename = map_import("Import Map");
1988
1989     if (filename != NULL) {
1990         UndoableCommand undo("mapImport");
1991         Map_ImportFile(filename);
1992     }
1993 }
1994
1995 bool Map_SaveAs()
1996 {
1997     const char *filename = map_save("Save Map");
1998
1999     if (filename != NULL) {
2000         g_strLastFolder = g_path_get_dirname(filename);
2001         MRU_AddFile(filename);
2002         Map_Rename(filename);
2003         return Map_Save();
2004     }
2005     return false;
2006 }
2007
2008 void SaveMapAs()
2009 {
2010     Map_SaveAs();
2011 }
2012
2013 void SaveMap()
2014 {
2015     if (Map_Unnamed(g_map)) {
2016         SaveMapAs();
2017     } else if (Map_Modified(g_map)) {
2018         Map_Save();
2019     }
2020 }
2021
2022 void ExportMap()
2023 {
2024     const char *filename = map_save("Export Selection");
2025
2026     if (filename != NULL) {
2027         g_strLastFolder = g_path_get_dirname(filename);
2028         Map_SaveSelected(filename);
2029     }
2030 }
2031
2032 void SaveRegion()
2033 {
2034     const char *filename = map_save("Export Region");
2035
2036     if (filename != NULL) {
2037         g_strLastFolder = g_path_get_dirname(filename);
2038         Map_SaveRegion(filename);
2039     }
2040 }
2041
2042
2043 void RegionOff()
2044 {
2045     Map_RegionOff();
2046     SceneChangeNotify();
2047 }
2048
2049 void RegionXY()
2050 {
2051     Map_RegionXY(
2052             g_pParentWnd->GetXYWnd()->GetOrigin()[0] -
2053             0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2054             g_pParentWnd->GetXYWnd()->GetOrigin()[1] -
2055             0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2056             g_pParentWnd->GetXYWnd()->GetOrigin()[0] +
2057             0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2058             g_pParentWnd->GetXYWnd()->GetOrigin()[1] +
2059             0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2060     );
2061     SceneChangeNotify();
2062 }
2063
2064 void RegionBrush()
2065 {
2066     Map_RegionBrush();
2067     SceneChangeNotify();
2068 }
2069
2070 void RegionSelected()
2071 {
2072     Map_RegionSelectedBrushes();
2073     SceneChangeNotify();
2074 }
2075
2076
2077 class BrushFindByIndexWalker : public scene::Traversable::Walker {
2078     mutable std::size_t m_index;
2079     scene::Path &m_path;
2080 public:
2081     BrushFindByIndexWalker(std::size_t index, scene::Path &path)
2082             : m_index(index), m_path(path)
2083     {
2084     }
2085
2086     bool pre(scene::Node &node) const
2087     {
2088         if (Node_isPrimitive(node) && m_index-- == 0) {
2089             m_path.push(makeReference(node));
2090         }
2091         return false;
2092     }
2093 };
2094
2095 class EntityFindByIndexWalker : public scene::Traversable::Walker {
2096     mutable std::size_t m_index;
2097     scene::Path &m_path;
2098 public:
2099     EntityFindByIndexWalker(std::size_t index, scene::Path &path)
2100             : m_index(index), m_path(path)
2101     {
2102     }
2103
2104     bool pre(scene::Node &node) const
2105     {
2106         if (Node_isEntity(node) && m_index-- == 0) {
2107             m_path.push(makeReference(node));
2108         }
2109         return false;
2110     }
2111 };
2112
2113 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path &path)
2114 {
2115     path.push(makeReference(GlobalSceneGraph().root()));
2116     {
2117         Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2118     }
2119     if (path.size() == 2) {
2120         scene::Traversable *traversable = Node_getTraversable(path.top());
2121         if (traversable != 0) {
2122             traversable->traverse(BrushFindByIndexWalker(brush, path));
2123         }
2124     }
2125 }
2126
2127 inline bool Node_hasChildren(scene::Node &node)
2128 {
2129     scene::Traversable *traversable = Node_getTraversable(node);
2130     return traversable != 0 && !traversable->empty();
2131 }
2132
2133 void SelectBrush(int entitynum, int brushnum)
2134 {
2135     scene::Path path;
2136     Scene_FindEntityBrush(entitynum, brushnum, path);
2137     if (path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top()))) {
2138         scene::Instance *instance = GlobalSceneGraph().find(path);
2139         ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2140         Selectable *selectable = Instance_getSelectable(*instance);
2141         ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2142         selectable->setSelected(true);
2143         g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2144     }
2145 }
2146
2147
2148 class BrushFindIndexWalker : public scene::Graph::Walker {
2149     mutable const scene::Node *m_node;
2150     std::size_t &m_count;
2151 public:
2152     BrushFindIndexWalker(const scene::Node &node, std::size_t &count)
2153             : m_node(&node), m_count(count)
2154     {
2155     }
2156
2157     bool pre(const scene::Path &path, scene::Instance &instance) const
2158     {
2159         if (Node_isPrimitive(path.top())) {
2160             if (m_node == path.top().get_pointer()) {
2161                 m_node = 0;
2162             }
2163             if (m_node) {
2164                 ++m_count;
2165             }
2166         }
2167         return true;
2168     }
2169 };
2170
2171 class EntityFindIndexWalker : public scene::Graph::Walker {
2172     mutable const scene::Node *m_node;
2173     std::size_t &m_count;
2174 public:
2175     EntityFindIndexWalker(const scene::Node &node, std::size_t &count)
2176             : m_node(&node), m_count(count)
2177     {
2178     }
2179
2180     bool pre(const scene::Path &path, scene::Instance &instance) const
2181     {
2182         if (Node_isEntity(path.top())) {
2183             if (m_node == path.top().get_pointer()) {
2184                 m_node = 0;
2185             }
2186             if (m_node) {
2187                 ++m_count;
2188             }
2189         }
2190         return true;
2191     }
2192 };
2193
2194 static void GetSelectionIndex(int *ent, int *brush)
2195 {
2196     std::size_t count_brush = 0;
2197     std::size_t count_entity = 0;
2198     if (GlobalSelectionSystem().countSelected() != 0) {
2199         const scene::Path &path = GlobalSelectionSystem().ultimateSelected().path();
2200
2201         GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2202         GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2203     }
2204     *brush = int(count_brush);
2205     *ent = int(count_entity);
2206 }
2207
2208 void DoFind()
2209 {
2210     ModalDialog dialog;
2211     ui::Entry entity{ui::null};
2212     ui::Entry brush{ui::null};
2213
2214     ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback),
2215                                                                    &dialog);
2216
2217     auto accel = ui::AccelGroup(ui::New);
2218     window.add_accel_group(accel);
2219
2220     {
2221         auto vbox = create_dialog_vbox(4, 4);
2222         window.add(vbox);
2223         {
2224             auto table = create_dialog_table(2, 2, 4, 4);
2225             vbox.pack_start(table, TRUE, TRUE, 0);
2226             {
2227                 ui::Widget label = ui::Label("Entity number");
2228                 label.show();
2229                 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2230             }
2231             {
2232                 ui::Widget label = ui::Label("Brush number");
2233                 label.show();
2234                 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2235             }
2236             {
2237                 auto entry = ui::Entry(ui::New);
2238                 entry.show();
2239                 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2240                 gtk_widget_grab_focus(entry);
2241                 entity = entry;
2242             }
2243             {
2244                 auto entry = ui::Entry(ui::New);
2245                 entry.show();
2246                 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2247
2248                 brush = entry;
2249             }
2250         }
2251         {
2252             auto hbox = create_dialog_hbox(4);
2253             vbox.pack_start(hbox, TRUE, TRUE, 0);
2254             {
2255                 auto button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2256                 hbox.pack_start(button, FALSE, FALSE, 0);
2257                 widget_make_default(button);
2258                 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0,
2259                                            (GtkAccelFlags) 0);
2260             }
2261             {
2262                 auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2263                 hbox.pack_start(button, FALSE, FALSE, 0);
2264                 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0,
2265                                            (GtkAccelFlags) 0);
2266             }
2267         }
2268     }
2269
2270     // Initialize dialog
2271     char buf[16];
2272     int ent, br;
2273
2274     GetSelectionIndex(&ent, &br);
2275     sprintf(buf, "%i", ent);
2276     entity.text(buf);
2277     sprintf(buf, "%i", br);
2278     brush.text(buf);
2279
2280     if (modal_dialog_show(window, dialog) == eIDOK) {
2281         const char *entstr = gtk_entry_get_text(entity);
2282         const char *brushstr = gtk_entry_get_text(brush);
2283         SelectBrush(atoi(entstr), atoi(brushstr));
2284     }
2285
2286     window.destroy();
2287 }
2288
2289 void Map_constructPreferences(PreferencesPage &page)
2290 {
2291     page.appendCheckBox("", "Load last map on open", g_bLoadLastMap);
2292 }
2293
2294
2295 class MapEntityClasses : public ModuleObserver {
2296     std::size_t m_unrealised;
2297 public:
2298     MapEntityClasses() : m_unrealised(1)
2299     {
2300     }
2301
2302     void realise()
2303     {
2304         if (--m_unrealised == 0) {
2305             if (g_map.m_resource != 0) {
2306                 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2307                 g_map.m_resource->realise();
2308             }
2309         }
2310     }
2311
2312     void unrealise()
2313     {
2314         if (++m_unrealised == 1) {
2315             if (g_map.m_resource != 0) {
2316                 g_map.m_resource->flush();
2317                 g_map.m_resource->unrealise();
2318             }
2319         }
2320     }
2321 };
2322
2323 MapEntityClasses g_MapEntityClasses;
2324
2325
2326 class MapModuleObserver : public ModuleObserver {
2327     std::size_t m_unrealised;
2328 public:
2329     MapModuleObserver() : m_unrealised(1)
2330     {
2331     }
2332
2333     void realise()
2334     {
2335         if (--m_unrealised == 0) {
2336             ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()),
2337                            "maps_directory: user-game-path is empty");
2338             StringOutputStream buffer(256);
2339             buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2340             Q_mkdir(buffer.c_str());
2341             g_mapsPath = buffer.c_str();
2342         }
2343     }
2344
2345     void unrealise()
2346     {
2347         if (++m_unrealised == 1) {
2348             g_mapsPath = "";
2349         }
2350     }
2351 };
2352
2353 MapModuleObserver g_MapModuleObserver;
2354
2355 CopiedString g_strLastMap;
2356 bool g_bLoadLastMap = false;
2357
2358 void Map_Construct()
2359 {
2360     GlobalCommands_insert("RegionOff", makeCallbackF(RegionOff));
2361     GlobalCommands_insert("RegionSetXY", makeCallbackF(RegionXY));
2362     GlobalCommands_insert("RegionSetBrush", makeCallbackF(RegionBrush));
2363     GlobalCommands_insert("RegionSetSelection", makeCallbackF(RegionSelected),
2364                           Accelerator('R', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
2365
2366     GlobalPreferenceSystem().registerPreference("LastMap", make_property_string(g_strLastMap));
2367     GlobalPreferenceSystem().registerPreference("LoadLastMap", make_property_string(g_bLoadLastMap));
2368     GlobalPreferenceSystem().registerPreference("MapInfoDlg", make_property<WindowPosition_String>(g_posMapInfoWnd));
2369
2370     PreferencesDialog_addSettingsPreferences(makeCallbackF(Map_constructPreferences));
2371
2372     GlobalEntityClassManager().attach(g_MapEntityClasses);
2373     Radiant_attachHomePathsObserver(g_MapModuleObserver);
2374 }
2375
2376 void Map_Destroy()
2377 {
2378     Radiant_detachHomePathsObserver(g_MapModuleObserver);
2379     GlobalEntityClassManager().detach(g_MapEntityClasses);
2380 }