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