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