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