]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
Initial python support
[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     g_map.m_name = filename;
1005
1006     // refresh VFS to apply new pak filtering based on mapname
1007     // needed for daemon DPK VFS
1008     VFS_Refresh();
1009
1010     globalOutputStream() << "Loading map from " << filename << "\n";
1011     ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1012
1013     MRU_AddFile(filename);
1014     g_strLastMapFolder = g_path_get_dirname(filename);
1015
1016     {
1017         ScopeTimer timer("map load");
1018
1019         const MapFormat *format = NULL;
1020         const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1021         if (string_not_empty(moduleName)) {
1022             format = ReferenceAPI_getMapModules().findModule(moduleName);
1023         }
1024
1025         for (int i = 0; i < Brush_toggleFormatCount(); ++i) {
1026             if (i) {
1027                 Map_Free();
1028             }
1029             Brush_toggleFormat(i);
1030             Map_UpdateTitle(g_map);
1031
1032             g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1033             if (format) {
1034                 format->wrongFormat = false;
1035             }
1036             g_map.m_resource->attach(g_map);
1037             if (format) {
1038                 if (!format->wrongFormat) {
1039                     break;
1040                 }
1041             }
1042         }
1043
1044         Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
1045     }
1046
1047     globalOutputStream() << "--- LoadMapFile ---\n";
1048     globalOutputStream() << g_map.m_name.c_str() << "\n";
1049
1050     globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
1051     globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
1052
1053     //GlobalEntityCreator().printStatistics();
1054
1055     //
1056     // move the view to a start position
1057     //
1058     Map_StartPosition();
1059
1060     g_currentMap = &g_map;
1061 }
1062
1063 class Excluder {
1064 public:
1065     virtual bool excluded(scene::Node &node) const = 0;
1066 };
1067
1068 class ExcludeWalker : public scene::Traversable::Walker {
1069     const scene::Traversable::Walker &m_walker;
1070     const Excluder *m_exclude;
1071     mutable bool m_skip;
1072 public:
1073     ExcludeWalker(const scene::Traversable::Walker &walker, const Excluder &exclude)
1074             : m_walker(walker), m_exclude(&exclude), m_skip(false)
1075     {
1076     }
1077
1078     bool pre(scene::Node &node) const
1079     {
1080         if (m_exclude->excluded(node) || node.isRoot()) {
1081             m_skip = true;
1082             return false;
1083         } else {
1084             m_walker.pre(node);
1085         }
1086         return true;
1087     }
1088
1089     void post(scene::Node &node) const
1090     {
1091         if (m_skip) {
1092             m_skip = false;
1093         } else {
1094             m_walker.post(node);
1095         }
1096     }
1097 };
1098
1099 class AnyInstanceSelected : public scene::Instantiable::Visitor {
1100     bool &m_selected;
1101 public:
1102     AnyInstanceSelected(bool &selected) : m_selected(selected)
1103     {
1104         m_selected = false;
1105     }
1106
1107     void visit(scene::Instance &instance) const
1108     {
1109         Selectable *selectable = Instance_getSelectable(instance);
1110         if (selectable != 0
1111             && selectable->isSelected()) {
1112             m_selected = true;
1113         }
1114     }
1115 };
1116
1117 bool Node_instanceSelected(scene::Node &node)
1118 {
1119     scene::Instantiable *instantiable = Node_getInstantiable(node);
1120     ASSERT_NOTNULL(instantiable);
1121     bool selected;
1122     instantiable->forEachInstance(AnyInstanceSelected(selected));
1123     return selected;
1124 }
1125
1126 class SelectedDescendantWalker : public scene::Traversable::Walker {
1127     bool &m_selected;
1128 public:
1129     SelectedDescendantWalker(bool &selected) : m_selected(selected)
1130     {
1131         m_selected = false;
1132     }
1133
1134     bool pre(scene::Node &node) const
1135     {
1136         if (node.isRoot()) {
1137             return false;
1138         }
1139
1140         if (Node_instanceSelected(node)) {
1141             m_selected = true;
1142         }
1143
1144         return true;
1145     }
1146 };
1147
1148 bool Node_selectedDescendant(scene::Node &node)
1149 {
1150     bool selected;
1151     Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
1152     return selected;
1153 }
1154
1155 class SelectionExcluder : public Excluder {
1156 public:
1157     bool excluded(scene::Node &node) const
1158     {
1159         return !Node_selectedDescendant(node);
1160     }
1161 };
1162
1163 class IncludeSelectedWalker : public scene::Traversable::Walker {
1164     const scene::Traversable::Walker &m_walker;
1165     mutable std::size_t m_selected;
1166     mutable bool m_skip;
1167
1168     bool selectedParent() const
1169     {
1170         return m_selected != 0;
1171     }
1172
1173 public:
1174     IncludeSelectedWalker(const scene::Traversable::Walker &walker)
1175             : m_walker(walker), m_selected(0), m_skip(false)
1176     {
1177     }
1178
1179     bool pre(scene::Node &node) const
1180     {
1181         // include node if:
1182         // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1183         if (!node.isRoot() && (Node_selectedDescendant(node) || selectedParent())) {
1184             if (Node_instanceSelected(node)) {
1185                 ++m_selected;
1186             }
1187             m_walker.pre(node);
1188             return true;
1189         } else {
1190             m_skip = true;
1191             return false;
1192         }
1193     }
1194
1195     void post(scene::Node &node) const
1196     {
1197         if (m_skip) {
1198             m_skip = false;
1199         } else {
1200             if (Node_instanceSelected(node)) {
1201                 --m_selected;
1202             }
1203             m_walker.post(node);
1204         }
1205     }
1206 };
1207
1208 void Map_Traverse_Selected(scene::Node &root, const scene::Traversable::Walker &walker)
1209 {
1210     scene::Traversable *traversable = Node_getTraversable(root);
1211     if (traversable != 0) {
1212 #if 0
1213         traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1214 #else
1215         traversable->traverse(IncludeSelectedWalker(walker));
1216 #endif
1217     }
1218 }
1219
1220 void Map_ExportSelected(TextOutputStream &out, const MapFormat &format)
1221 {
1222     format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments);
1223 }
1224
1225 void Map_Traverse(scene::Node &root, const scene::Traversable::Walker &walker)
1226 {
1227     scene::Traversable *traversable = Node_getTraversable(root);
1228     if (traversable != 0) {
1229         traversable->traverse(walker);
1230     }
1231 }
1232
1233 class RegionExcluder : public Excluder {
1234 public:
1235     bool excluded(scene::Node &node) const
1236     {
1237         return node.excluded();
1238     }
1239 };
1240
1241 void Map_Traverse_Region(scene::Node &root, const scene::Traversable::Walker &walker)
1242 {
1243     scene::Traversable *traversable = Node_getTraversable(root);
1244     if (traversable != 0) {
1245         traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
1246     }
1247 }
1248
1249 bool Map_SaveRegion(const char *filename)
1250 {
1251     AddRegionBrushes();
1252
1253     bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region,
1254                                         filename);
1255
1256     RemoveRegionBrushes();
1257
1258     return success;
1259 }
1260
1261
1262 void Map_RenameAbsolute(const char *absolute)
1263 {
1264     Resource *resource = GlobalReferenceCache().capture(absolute);
1265     NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
1266     resource->setNode(clone.get_pointer());
1267
1268     {
1269         //ScopeTimer timer("clone subgraph");
1270         Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
1271     }
1272
1273     g_map.m_resource->detach(g_map);
1274     GlobalReferenceCache().release(g_map.m_name.c_str());
1275
1276     g_map.m_resource = resource;
1277
1278     g_map.m_name = absolute;
1279     Map_UpdateTitle(g_map);
1280
1281     g_map.m_resource->attach(g_map);
1282     // refresh VFS to apply new pak filtering based on mapname
1283     // needed for daemon DPK VFS
1284     VFS_Refresh();
1285 }
1286
1287 void Map_Rename(const char *filename)
1288 {
1289     if (!string_equal(g_map.m_name.c_str(), filename)) {
1290         ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1291
1292         Map_RenameAbsolute(filename);
1293
1294         SceneChangeNotify();
1295     } else {
1296         SaveReferences();
1297     }
1298 }
1299
1300 bool Map_Save()
1301 {
1302     Pointfile_Clear();
1303
1304     ScopeTimer timer("map save");
1305     SaveReferences();
1306     return true; // assume success..
1307 }
1308
1309 /*
1310    ===========
1311    Map_New
1312
1313    ===========
1314  */
1315 void Map_New()
1316 {
1317     //globalOutputStream() << "Map_New\n";
1318
1319     g_map.m_name = "unnamed.map";
1320     Map_UpdateTitle(g_map);
1321
1322     {
1323         g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1324 //    ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1325         g_map.m_resource->attach(g_map);
1326
1327         SceneChangeNotify();
1328     }
1329
1330     FocusViews(g_vector3_identity, 0);
1331
1332     g_currentMap = &g_map;
1333
1334     // restart VFS to apply new pak filtering based on mapname
1335     // needed for daemon DPK VFS
1336     VFS_Restart();
1337 }
1338
1339 extern void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 &region_mins, const Vector3 &region_maxs);
1340
1341 void ConstructRegionStartpoint(scene::Node *startpoint, const Vector3 &region_mins, const Vector3 &region_maxs)
1342 {
1343     /*!
1344            \todo we need to make sure that the player start IS inside the region and bail out if it's not
1345            the compiler will refuse to compile a map with a player_start somewhere in empty space..
1346            for now, let's just print an error
1347          */
1348
1349     Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
1350
1351     for (int i = 0; i < 3; i++) {
1352         if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) {
1353             globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1354             break;
1355         }
1356     }
1357
1358     // write the info_playerstart
1359     char sTmp[1024];
1360     sprintf(sTmp, "%d %d %d", (int) vOrig[0], (int) vOrig[1], (int) vOrig[2]);
1361     Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
1362     sprintf(sTmp, "%d", (int) Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
1363     Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
1364 }
1365
1366 /*
1367    ===========================================================
1368
1369    REGION
1370
1371    ===========================================================
1372  */
1373 bool region_active;
1374 Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
1375 Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
1376
1377 scene::Node *region_sides[6];
1378 scene::Node *region_startpoint = 0;
1379
1380 /*
1381    ===========
1382    AddRegionBrushes
1383    a regioned map will have temp walls put up at the region boundary
1384    \todo TODO TTimo old implementation of region brushes
1385    we still add them straight in the worldspawn and take them out after the map is saved
1386    with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1387    ===========
1388  */
1389 void AddRegionBrushes(void)
1390 {
1391     int i;
1392
1393     for (i = 0; i < 6; i++) {
1394         region_sides[i] = &GlobalBrushCreator().createBrush();
1395         Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
1396     }
1397
1398     region_startpoint = &GlobalEntityCreator().createEntity(
1399             GlobalEntityClassManager().findOrInsert("info_player_start", false));
1400
1401     ConstructRegionBrushes(region_sides, region_mins, region_maxs);
1402     ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
1403
1404     Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
1405 }
1406
1407 void RemoveRegionBrushes(void)
1408 {
1409     for (std::size_t i = 0; i < 6; i++) {
1410         Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
1411     }
1412     Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
1413 }
1414
1415 inline void exclude_node(scene::Node &node, bool exclude)
1416 {
1417     exclude
1418     ? node.enable(scene::Node::eExcluded)
1419     : node.disable(scene::Node::eExcluded);
1420 }
1421
1422 class ExcludeAllWalker : public scene::Graph::Walker {
1423     bool m_exclude;
1424 public:
1425     ExcludeAllWalker(bool exclude)
1426             : m_exclude(exclude)
1427     {
1428     }
1429
1430     bool pre(const scene::Path &path, scene::Instance &instance) const
1431     {
1432         exclude_node(path.top(), m_exclude);
1433
1434         return true;
1435     }
1436 };
1437
1438 void Scene_Exclude_All(bool exclude)
1439 {
1440     GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
1441 }
1442
1443 bool Instance_isSelected(const scene::Instance &instance)
1444 {
1445     const Selectable *selectable = Instance_getSelectable(instance);
1446     return selectable != 0 && selectable->isSelected();
1447 }
1448
1449 class ExcludeSelectedWalker : public scene::Graph::Walker {
1450     bool m_exclude;
1451 public:
1452     ExcludeSelectedWalker(bool exclude)
1453             : m_exclude(exclude)
1454     {
1455     }
1456
1457     bool pre(const scene::Path &path, scene::Instance &instance) const
1458     {
1459         exclude_node(path.top(),
1460                      (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
1461         return true;
1462     }
1463 };
1464
1465 void Scene_Exclude_Selected(bool exclude)
1466 {
1467     GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
1468 }
1469
1470 class ExcludeRegionedWalker : public scene::Graph::Walker {
1471     bool m_exclude;
1472 public:
1473     ExcludeRegionedWalker(bool exclude)
1474             : m_exclude(exclude)
1475     {
1476     }
1477
1478     bool pre(const scene::Path &path, scene::Instance &instance) const
1479     {
1480         exclude_node(
1481                 path.top(),
1482                 !(
1483                         (
1484                                 aabb_intersects_aabb(
1485                                         instance.worldAABB(),
1486                                         aabb_for_minmax(region_mins, region_maxs)
1487                                 ) != 0
1488                         ) ^ m_exclude
1489                 )
1490         );
1491
1492         return true;
1493     }
1494 };
1495
1496 void Scene_Exclude_Region(bool exclude)
1497 {
1498     GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
1499 }
1500
1501 /*
1502    ===========
1503    Map_RegionOff
1504
1505    Other filtering options may still be on
1506    ===========
1507  */
1508 void Map_RegionOff()
1509 {
1510     region_active = false;
1511
1512     region_maxs[0] = g_MaxWorldCoord - 64;
1513     region_mins[0] = g_MinWorldCoord + 64;
1514     region_maxs[1] = g_MaxWorldCoord - 64;
1515     region_mins[1] = g_MinWorldCoord + 64;
1516     region_maxs[2] = g_MaxWorldCoord - 64;
1517     region_mins[2] = g_MinWorldCoord + 64;
1518
1519     Scene_Exclude_All(false);
1520 }
1521
1522 void Map_ApplyRegion(void)
1523 {
1524     region_active = true;
1525
1526     Scene_Exclude_Region(false);
1527 }
1528
1529
1530 /*
1531    ========================
1532    Map_RegionSelectedBrushes
1533    ========================
1534  */
1535 void Map_RegionSelectedBrushes(void)
1536 {
1537     Map_RegionOff();
1538
1539     if (GlobalSelectionSystem().countSelected() != 0
1540         && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) {
1541         region_active = true;
1542         Select_GetBounds(region_mins, region_maxs);
1543
1544         Scene_Exclude_Selected(false);
1545
1546         GlobalSelectionSystem().setSelectedAll(false);
1547     }
1548 }
1549
1550
1551 /*
1552    ===========
1553    Map_RegionXY
1554    ===========
1555  */
1556 void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
1557 {
1558     Map_RegionOff();
1559
1560     region_mins[0] = x_min;
1561     region_maxs[0] = x_max;
1562     region_mins[1] = y_min;
1563     region_maxs[1] = y_max;
1564     region_mins[2] = g_MinWorldCoord + 64;
1565     region_maxs[2] = g_MaxWorldCoord - 64;
1566
1567     Map_ApplyRegion();
1568 }
1569
1570 void Map_RegionBounds(const AABB &bounds)
1571 {
1572     Map_RegionOff();
1573
1574     region_mins = vector3_subtracted(bounds.origin, bounds.extents);
1575     region_maxs = vector3_added(bounds.origin, bounds.extents);
1576
1577     deleteSelection();
1578
1579     Map_ApplyRegion();
1580 }
1581
1582 /*
1583    ===========
1584    Map_RegionBrush
1585    ===========
1586  */
1587 void Map_RegionBrush(void)
1588 {
1589     if (GlobalSelectionSystem().countSelected() != 0) {
1590         scene::Instance &instance = GlobalSelectionSystem().ultimateSelected();
1591         Map_RegionBounds(instance.worldAABB());
1592     }
1593 }
1594
1595 //
1596 //================
1597 //Map_ImportFile
1598 //================
1599 //
1600 bool Map_ImportFile(const char *filename)
1601 {
1602     ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1603
1604     g_strLastMapFolder = g_path_get_dirname(filename);
1605
1606     bool success = false;
1607
1608     if (extension_equal(path_get_extension(filename), "bsp")) {
1609         goto tryDecompile;
1610     }
1611
1612     {
1613         const MapFormat *format = NULL;
1614         const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1615         if (string_not_empty(moduleName)) {
1616             format = ReferenceAPI_getMapModules().findModule(moduleName);
1617         }
1618
1619         if (format) {
1620             format->wrongFormat = false;
1621         }
1622         Resource *resource = GlobalReferenceCache().capture(filename);
1623         resource->refresh(); // avoid loading old version if map has changed on disk since last import
1624         if (!resource->load()) {
1625             GlobalReferenceCache().release(filename);
1626             goto tryDecompile;
1627         }
1628         if (format) {
1629             if (format->wrongFormat) {
1630                 GlobalReferenceCache().release(filename);
1631                 goto tryDecompile;
1632             }
1633         }
1634         NodeSmartReference clone(NewMapRoot(""));
1635         Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1636         Map_gatherNamespaced(clone);
1637         Map_mergeClonedNames();
1638         MergeMap(clone);
1639         success = true;
1640         GlobalReferenceCache().release(filename);
1641     }
1642
1643     SceneChangeNotify();
1644
1645     return success;
1646
1647     tryDecompile:
1648
1649     const char *type = GlobalRadiant().getGameDescriptionKeyValue("q3map2_type");
1650     int n = string_length(path_get_extension(filename));
1651     if (n && (extension_equal(path_get_extension(filename), "bsp") ||
1652               extension_equal(path_get_extension(filename), "map"))) {
1653         StringBuffer output;
1654         output.push_string(AppPath_get());
1655         output.push_string("q3map2.");
1656         output.push_string(RADIANT_EXECUTABLE);
1657         output.push_string(" -v -game ");
1658         output.push_string((type && *type) ? type : "quake3");
1659         output.push_string(" -fs_basepath \"");
1660         output.push_string(EnginePath_get());
1661         output.push_string("\" -fs_homepath \"");
1662         output.push_string(g_qeglobals.m_userEnginePath.c_str());
1663         output.push_string("\"");
1664
1665         // extra pakpaths
1666         for (int i = 0; i < g_pakPathCount; i++) {
1667             if (g_strcmp0(g_strPakPath[i].c_str(), "")) {
1668                 output.push_string(" -fs_pakpath \"");
1669                 output.push_string(g_strPakPath[i].c_str());
1670                 output.push_string("\"");
1671             }
1672         }
1673
1674         // extra switches
1675         if (g_disableEnginePath) {
1676             output.push_string(" -fs_nobasepath ");
1677         }
1678
1679         if (g_disableHomePath) {
1680             output.push_string(" -fs_nohomepath ");
1681         }
1682
1683         output.push_string(" -fs_game ");
1684         output.push_string(gamename_get());
1685         output.push_string(" -convert -format ");
1686         output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
1687         if (extension_equal(path_get_extension(filename), "map")) {
1688             output.push_string(" -readmap ");
1689         }
1690         output.push_string(" \"");
1691         output.push_string(filename);
1692         output.push_string("\"");
1693
1694         // run
1695         Q_Exec(NULL, output.c_str(), NULL, false, true);
1696
1697         // rebuild filename as "filenamewithoutext_converted.map"
1698         output.clear();
1699         output.push_range(filename, filename + string_length(filename) - (n + 1));
1700         output.push_string("_converted.map");
1701         filename = output.c_str();
1702
1703         // open
1704         Resource *resource = GlobalReferenceCache().capture(filename);
1705         resource->refresh(); // avoid loading old version if map has changed on disk since last import
1706         if (!resource->load()) {
1707             GlobalReferenceCache().release(filename);
1708             goto tryDecompile;
1709         }
1710         NodeSmartReference clone(NewMapRoot(""));
1711         Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1712         Map_gatherNamespaced(clone);
1713         Map_mergeClonedNames();
1714         MergeMap(clone);
1715         success = true;
1716         GlobalReferenceCache().release(filename);
1717     }
1718
1719     SceneChangeNotify();
1720     return success;
1721 }
1722
1723 /*
1724    ===========
1725    Map_SaveFile
1726    ===========
1727  */
1728 bool Map_SaveFile(const char *filename)
1729 {
1730     ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1731     bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename);
1732     if (success) {
1733         // refresh VFS to apply new pak filtering based on mapname
1734         // needed for daemon DPK VFS
1735         VFS_Refresh();
1736     }
1737     return success;
1738 }
1739
1740 //
1741 //===========
1742 //Map_SaveSelected
1743 //===========
1744 //
1745 // Saves selected world brushes and whole entities with partial/full selections
1746 //
1747 bool Map_SaveSelected(const char *filename)
1748 {
1749     return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected,
1750                                 filename);
1751 }
1752
1753
1754 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker {
1755     scene::Node &m_parent;
1756 public:
1757     ParentSelectedBrushesToEntityWalker(scene::Node &parent) : m_parent(parent)
1758     {
1759     }
1760
1761     bool pre(const scene::Path &path, scene::Instance &instance) const
1762     {
1763         if (path.top().get_pointer() != &m_parent
1764             && Node_isPrimitive(path.top())) {
1765             Selectable *selectable = Instance_getSelectable(instance);
1766             if (selectable != 0
1767                 && selectable->isSelected()
1768                 && path.size() > 1) {
1769                 return false;
1770             }
1771         }
1772         return true;
1773     }
1774
1775     void post(const scene::Path &path, scene::Instance &instance) const
1776     {
1777         if (path.top().get_pointer() != &m_parent
1778             && Node_isPrimitive(path.top())) {
1779             Selectable *selectable = Instance_getSelectable(instance);
1780             if (selectable != 0
1781                 && selectable->isSelected()
1782                 && path.size() > 1) {
1783                 scene::Node &parent = path.parent();
1784                 if (&parent != &m_parent) {
1785                     NodeSmartReference node(path.top().get());
1786                     Node_getTraversable(parent)->erase(node);
1787                     Node_getTraversable(m_parent)->insert(node);
1788                 }
1789             }
1790         }
1791     }
1792 };
1793
1794 void Scene_parentSelectedBrushesToEntity(scene::Graph &graph, scene::Node &parent)
1795 {
1796     graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1797 }
1798
1799 class CountSelectedBrushes : public scene::Graph::Walker {
1800     std::size_t &m_count;
1801     mutable std::size_t m_depth;
1802 public:
1803     CountSelectedBrushes(std::size_t &count) : m_count(count), m_depth(0)
1804     {
1805         m_count = 0;
1806     }
1807
1808     bool pre(const scene::Path &path, scene::Instance &instance) const
1809     {
1810         if (++m_depth != 1 && path.top().get().isRoot()) {
1811             return false;
1812         }
1813         Selectable *selectable = Instance_getSelectable(instance);
1814         if (selectable != 0
1815             && selectable->isSelected()
1816             && Node_isPrimitive(path.top())) {
1817             ++m_count;
1818         }
1819         return true;
1820     }
1821
1822     void post(const scene::Path &path, scene::Instance &instance) const
1823     {
1824         --m_depth;
1825     }
1826 };
1827
1828 std::size_t Scene_countSelectedBrushes(scene::Graph &graph)
1829 {
1830     std::size_t count;
1831     graph.traverse(CountSelectedBrushes(count));
1832     return count;
1833 }
1834
1835 enum ENodeType {
1836     eNodeUnknown,
1837     eNodeMap,
1838     eNodeEntity,
1839     eNodePrimitive,
1840 };
1841
1842 const char *nodetype_get_name(ENodeType type)
1843 {
1844     if (type == eNodeMap) {
1845         return "map";
1846     }
1847     if (type == eNodeEntity) {
1848         return "entity";
1849     }
1850     if (type == eNodePrimitive) {
1851         return "primitive";
1852     }
1853     return "unknown";
1854 }
1855
1856 ENodeType node_get_nodetype(scene::Node &node)
1857 {
1858     if (Node_isEntity(node)) {
1859         return eNodeEntity;
1860     }
1861     if (Node_isPrimitive(node)) {
1862         return eNodePrimitive;
1863     }
1864     return eNodeUnknown;
1865 }
1866
1867 bool contains_entity(scene::Node &node)
1868 {
1869     return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1870 }
1871
1872 bool contains_primitive(scene::Node &node)
1873 {
1874     return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1875 }
1876
1877 ENodeType node_get_contains(scene::Node &node)
1878 {
1879     if (contains_entity(node)) {
1880         return eNodeEntity;
1881     }
1882     if (contains_primitive(node)) {
1883         return eNodePrimitive;
1884     }
1885     return eNodeUnknown;
1886 }
1887
1888 void Path_parent(const scene::Path &parent, const scene::Path &child)
1889 {
1890     ENodeType contains = node_get_contains(parent.top());
1891     ENodeType type = node_get_nodetype(child.top());
1892
1893     if (contains != eNodeUnknown && contains == type) {
1894         NodeSmartReference node(child.top().get());
1895         Path_deleteTop(child);
1896         Node_getTraversable(parent.top())->insert(node);
1897         SceneChangeNotify();
1898     } else {
1899         globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to "
1900                             << nodetype_get_name(contains) << " container.\n";
1901     }
1902 }
1903
1904 void Scene_parentSelected()
1905 {
1906     UndoableCommand undo("parentSelected");
1907
1908     if (GlobalSelectionSystem().countSelected() > 1) {
1909         class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor {
1910             const scene::Path &m_parent;
1911         public:
1912             ParentSelectedBrushesToEntityWalker(const scene::Path &parent) : m_parent(parent)
1913             {
1914             }
1915
1916             void visit(scene::Instance &instance) const
1917             {
1918                 if (&m_parent != &instance.path()) {
1919                     Path_parent(m_parent, instance.path());
1920                 }
1921             }
1922         };
1923
1924         ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1925         GlobalSelectionSystem().foreachSelected(visitor);
1926     } else {
1927         globalOutputStream() << "failed - did not find two selected nodes.\n";
1928     }
1929 }
1930
1931
1932 void NewMap()
1933 {
1934     if (ConfirmModified("New Map")) {
1935         Map_RegionOff();
1936         Map_Free();
1937         Map_New();
1938     }
1939 }
1940
1941 CopiedString g_mapsPath;
1942
1943 const char *getMapsPath()
1944 {
1945     return g_mapsPath.c_str();
1946 }
1947
1948 const char *getLastMapFolderPath()
1949 {
1950     if (g_strLastMapFolder.empty()) {
1951         GlobalPreferenceSystem().registerPreference("LastMapFolder", make_property_string(g_strLastMapFolder));
1952         if (g_strLastMapFolder.empty()) {
1953             StringOutputStream buffer(1024);
1954             buffer << getMapsPath();
1955             if (!file_readable(buffer.c_str())) {
1956                 buffer.clear();
1957                 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
1958             }
1959             g_strLastMapFolder = buffer.c_str();
1960         }
1961     }
1962     return g_strLastMapFolder.c_str();
1963 }
1964
1965 const char *map_open(const char *title)
1966 {
1967     return MainFrame_getWindow().file_dialog(TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false);
1968 }
1969
1970 const char *map_import(const char *title)
1971 {
1972     return MainFrame_getWindow().file_dialog(TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false);
1973 }
1974
1975 const char *map_save(const char *title)
1976 {
1977     return MainFrame_getWindow().file_dialog(FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true);
1978 }
1979
1980 void OpenMap()
1981 {
1982     if (!ConfirmModified("Open Map")) {
1983         return;
1984     }
1985
1986     const char *filename = map_open("Open Map");
1987
1988     if (filename != NULL) {
1989         MRU_AddFile(filename);
1990         Map_RegionOff();
1991         Map_Free();
1992         Map_LoadFile(filename);
1993     }
1994 }
1995
1996 void ImportMap()
1997 {
1998     const char *filename = map_import("Import Map");
1999
2000     if (filename != NULL) {
2001         UndoableCommand undo("mapImport");
2002         Map_ImportFile(filename);
2003     }
2004 }
2005
2006 bool Map_SaveAs()
2007 {
2008     const char *filename = map_save("Save Map");
2009
2010     if (filename != NULL) {
2011         g_strLastMapFolder = g_path_get_dirname(filename);
2012         MRU_AddFile(filename);
2013         Map_Rename(filename);
2014         return Map_Save();
2015     }
2016     return false;
2017 }
2018
2019 void SaveMapAs()
2020 {
2021     Map_SaveAs();
2022 }
2023
2024 void SaveMap()
2025 {
2026     if (Map_Unnamed(g_map)) {
2027         SaveMapAs();
2028     } else if (Map_Modified(g_map)) {
2029         Map_Save();
2030     }
2031 }
2032
2033 void ExportMap()
2034 {
2035     const char *filename = map_save("Export Selection");
2036
2037     if (filename != NULL) {
2038         g_strLastMapFolder = g_path_get_dirname(filename);
2039         Map_SaveSelected(filename);
2040     }
2041 }
2042
2043 void SaveRegion()
2044 {
2045     const char *filename = map_save("Export Region");
2046
2047     if (filename != NULL) {
2048         g_strLastMapFolder = g_path_get_dirname(filename);
2049         Map_SaveRegion(filename);
2050     }
2051 }
2052
2053
2054 void RegionOff()
2055 {
2056     Map_RegionOff();
2057     SceneChangeNotify();
2058 }
2059
2060 void RegionXY()
2061 {
2062     Map_RegionXY(
2063             g_pParentWnd->GetXYWnd()->GetOrigin()[0] -
2064             0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2065             g_pParentWnd->GetXYWnd()->GetOrigin()[1] -
2066             0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2067             g_pParentWnd->GetXYWnd()->GetOrigin()[0] +
2068             0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2069             g_pParentWnd->GetXYWnd()->GetOrigin()[1] +
2070             0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2071     );
2072     SceneChangeNotify();
2073 }
2074
2075 void RegionBrush()
2076 {
2077     Map_RegionBrush();
2078     SceneChangeNotify();
2079 }
2080
2081 void RegionSelected()
2082 {
2083     Map_RegionSelectedBrushes();
2084     SceneChangeNotify();
2085 }
2086
2087
2088 class BrushFindByIndexWalker : public scene::Traversable::Walker {
2089     mutable std::size_t m_index;
2090     scene::Path &m_path;
2091 public:
2092     BrushFindByIndexWalker(std::size_t index, scene::Path &path)
2093             : m_index(index), m_path(path)
2094     {
2095     }
2096
2097     bool pre(scene::Node &node) const
2098     {
2099         if (Node_isPrimitive(node) && m_index-- == 0) {
2100             m_path.push(makeReference(node));
2101         }
2102         return false;
2103     }
2104 };
2105
2106 class EntityFindByIndexWalker : public scene::Traversable::Walker {
2107     mutable std::size_t m_index;
2108     scene::Path &m_path;
2109 public:
2110     EntityFindByIndexWalker(std::size_t index, scene::Path &path)
2111             : m_index(index), m_path(path)
2112     {
2113     }
2114
2115     bool pre(scene::Node &node) const
2116     {
2117         if (Node_isEntity(node) && m_index-- == 0) {
2118             m_path.push(makeReference(node));
2119         }
2120         return false;
2121     }
2122 };
2123
2124 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path &path)
2125 {
2126     path.push(makeReference(GlobalSceneGraph().root()));
2127     {
2128         Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2129     }
2130     if (path.size() == 2) {
2131         scene::Traversable *traversable = Node_getTraversable(path.top());
2132         if (traversable != 0) {
2133             traversable->traverse(BrushFindByIndexWalker(brush, path));
2134         }
2135     }
2136 }
2137
2138 inline bool Node_hasChildren(scene::Node &node)
2139 {
2140     scene::Traversable *traversable = Node_getTraversable(node);
2141     return traversable != 0 && !traversable->empty();
2142 }
2143
2144 void SelectBrush(int entitynum, int brushnum)
2145 {
2146     scene::Path path;
2147     Scene_FindEntityBrush(entitynum, brushnum, path);
2148     if (path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top()))) {
2149         scene::Instance *instance = GlobalSceneGraph().find(path);
2150         ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2151         Selectable *selectable = Instance_getSelectable(*instance);
2152         ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2153         selectable->setSelected(true);
2154         g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2155     }
2156 }
2157
2158
2159 class BrushFindIndexWalker : public scene::Graph::Walker {
2160     mutable const scene::Node *m_node;
2161     std::size_t &m_count;
2162 public:
2163     BrushFindIndexWalker(const scene::Node &node, std::size_t &count)
2164             : m_node(&node), m_count(count)
2165     {
2166     }
2167
2168     bool pre(const scene::Path &path, scene::Instance &instance) const
2169     {
2170         if (Node_isPrimitive(path.top())) {
2171             if (m_node == path.top().get_pointer()) {
2172                 m_node = 0;
2173             }
2174             if (m_node) {
2175                 ++m_count;
2176             }
2177         }
2178         return true;
2179     }
2180 };
2181
2182 class EntityFindIndexWalker : public scene::Graph::Walker {
2183     mutable const scene::Node *m_node;
2184     std::size_t &m_count;
2185 public:
2186     EntityFindIndexWalker(const scene::Node &node, std::size_t &count)
2187             : m_node(&node), m_count(count)
2188     {
2189     }
2190
2191     bool pre(const scene::Path &path, scene::Instance &instance) const
2192     {
2193         if (Node_isEntity(path.top())) {
2194             if (m_node == path.top().get_pointer()) {
2195                 m_node = 0;
2196             }
2197             if (m_node) {
2198                 ++m_count;
2199             }
2200         }
2201         return true;
2202     }
2203 };
2204
2205 static void GetSelectionIndex(int *ent, int *brush)
2206 {
2207     std::size_t count_brush = 0;
2208     std::size_t count_entity = 0;
2209     if (GlobalSelectionSystem().countSelected() != 0) {
2210         const scene::Path &path = GlobalSelectionSystem().ultimateSelected().path();
2211
2212         GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2213         GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2214     }
2215     *brush = int(count_brush);
2216     *ent = int(count_entity);
2217 }
2218
2219 void DoFind()
2220 {
2221     ModalDialog dialog;
2222     ui::Entry entity{ui::null};
2223     ui::Entry brush{ui::null};
2224
2225     ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback),
2226                                                                    &dialog);
2227
2228     auto accel = ui::AccelGroup(ui::New);
2229     window.add_accel_group(accel);
2230
2231     {
2232         auto vbox = create_dialog_vbox(4, 4);
2233         window.add(vbox);
2234         {
2235             auto table = create_dialog_table(2, 2, 4, 4);
2236             vbox.pack_start(table, TRUE, TRUE, 0);
2237             {
2238                 ui::Widget label = ui::Label("Entity number");
2239                 label.show();
2240                 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2241             }
2242             {
2243                 ui::Widget label = ui::Label("Brush number");
2244                 label.show();
2245                 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2246             }
2247             {
2248                 auto entry = ui::Entry(ui::New);
2249                 entry.show();
2250                 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2251                 gtk_widget_grab_focus(entry);
2252                 entity = entry;
2253             }
2254             {
2255                 auto entry = ui::Entry(ui::New);
2256                 entry.show();
2257                 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2258
2259                 brush = entry;
2260             }
2261         }
2262         {
2263             auto hbox = create_dialog_hbox(4);
2264             vbox.pack_start(hbox, TRUE, TRUE, 0);
2265             {
2266                 auto button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2267                 hbox.pack_start(button, FALSE, FALSE, 0);
2268                 widget_make_default(button);
2269                 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0,
2270                                            (GtkAccelFlags) 0);
2271             }
2272             {
2273                 auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2274                 hbox.pack_start(button, FALSE, FALSE, 0);
2275                 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0,
2276                                            (GtkAccelFlags) 0);
2277             }
2278         }
2279     }
2280
2281     // Initialize dialog
2282     char buf[16];
2283     int ent, br;
2284
2285     GetSelectionIndex(&ent, &br);
2286     sprintf(buf, "%i", ent);
2287     entity.text(buf);
2288     sprintf(buf, "%i", br);
2289     brush.text(buf);
2290
2291     if (modal_dialog_show(window, dialog) == eIDOK) {
2292         const char *entstr = gtk_entry_get_text(entity);
2293         const char *brushstr = gtk_entry_get_text(brush);
2294         SelectBrush(atoi(entstr), atoi(brushstr));
2295     }
2296
2297     window.destroy();
2298 }
2299
2300 void Map_constructPreferences(PreferencesPage &page)
2301 {
2302     page.appendCheckBox("", "Load last map at startup", g_bLoadLastMap);
2303     page.appendCheckBox("", "Add entity and brush number comments on map write", g_writeMapComments);
2304 }
2305
2306
2307 class MapEntityClasses : public ModuleObserver {
2308     std::size_t m_unrealised;
2309 public:
2310     MapEntityClasses() : m_unrealised(1)
2311     {
2312     }
2313
2314     void realise()
2315     {
2316         if (--m_unrealised == 0) {
2317             if (g_map.m_resource != 0) {
2318                 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2319                 g_map.m_resource->realise();
2320             }
2321         }
2322     }
2323
2324     void unrealise()
2325     {
2326         if (++m_unrealised == 1) {
2327             if (g_map.m_resource != 0) {
2328                 g_map.m_resource->flush();
2329                 g_map.m_resource->unrealise();
2330             }
2331         }
2332     }
2333 };
2334
2335 MapEntityClasses g_MapEntityClasses;
2336
2337
2338 class MapModuleObserver : public ModuleObserver {
2339     std::size_t m_unrealised;
2340 public:
2341     MapModuleObserver() : m_unrealised(1)
2342     {
2343     }
2344
2345     void realise()
2346     {
2347         if (--m_unrealised == 0) {
2348             ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()),
2349                            "maps_directory: user-game-path is empty");
2350             StringOutputStream buffer(256);
2351             buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2352             Q_mkdir(buffer.c_str());
2353             g_mapsPath = buffer.c_str();
2354         }
2355     }
2356
2357     void unrealise()
2358     {
2359         if (++m_unrealised == 1) {
2360             g_mapsPath = "";
2361         }
2362     }
2363 };
2364
2365 MapModuleObserver g_MapModuleObserver;
2366
2367 CopiedString g_strLastMap;
2368 bool g_bLoadLastMap = false;
2369
2370 void Map_Construct()
2371 {
2372     GlobalCommands_insert("RegionOff", makeCallbackF(RegionOff));
2373     GlobalCommands_insert("RegionSetXY", makeCallbackF(RegionXY));
2374     GlobalCommands_insert("RegionSetBrush", makeCallbackF(RegionBrush));
2375     GlobalCommands_insert("RegionSetSelection", makeCallbackF(RegionSelected),
2376                           Accelerator('R', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
2377
2378     GlobalPreferenceSystem().registerPreference("LastMap", make_property_string(g_strLastMap));
2379     GlobalPreferenceSystem().registerPreference("LoadLastMap", make_property_string(g_bLoadLastMap));
2380     GlobalPreferenceSystem().registerPreference("MapInfoDlg", make_property<WindowPosition_String>(g_posMapInfoWnd));
2381         GlobalPreferenceSystem().registerPreference("WriteMapComments", make_property_string(g_writeMapComments));
2382
2383     PreferencesDialog_addSettingsPreferences(makeCallbackF(Map_constructPreferences));
2384
2385     GlobalEntityClassManager().attach(g_MapEntityClasses);
2386     Radiant_attachHomePathsObserver(g_MapModuleObserver);
2387 }
2388
2389 void Map_Destroy()
2390 {
2391     Radiant_detachHomePathsObserver(g_MapModuleObserver);
2392     GlobalEntityClassManager().detach(g_MapEntityClasses);
2393 }