]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
Merge branch 'pakpath' into 'master'
[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                 // extra switches
1555                 if ( g_disableEnginePath ) {
1556                         output.push_string( " -fs_nobasepath " );
1557                 }
1558
1559                 if ( g_disableHomePath ) {
1560                         output.push_string( " -fs_nohomepath " );
1561                 }
1562
1563                 output.push_string( " -fs_game " );
1564                 output.push_string( gamename_get() );
1565                 output.push_string( " -convert -format " );
1566                 output.push_string( Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map" );
1567                 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1568                         output.push_string( " -readmap " );
1569                 }
1570                 output.push_string( " \"" );
1571                 output.push_string( filename );
1572                 output.push_string( "\"" );
1573
1574                 // run
1575                 Q_Exec( NULL, output.c_str(), NULL, false, true );
1576
1577                 // rebuild filename as "filenamewithoutext_converted.map"
1578                 output.clear();
1579                 output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
1580                 output.push_string( "_converted.map" );
1581                 filename = output.c_str();
1582
1583                 // open
1584                 Resource* resource = GlobalReferenceCache().capture( filename );
1585                 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1586                 if ( !resource->load() ) {
1587                         GlobalReferenceCache().release( filename );
1588                         goto tryDecompile;
1589                 }
1590                 NodeSmartReference clone( NewMapRoot( "" ) );
1591                 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1592                 Map_gatherNamespaced( clone );
1593                 Map_mergeClonedNames();
1594                 MergeMap( clone );
1595                 success = true;
1596                 GlobalReferenceCache().release( filename );
1597         }
1598
1599         SceneChangeNotify();
1600         return success;
1601 }
1602
1603 /*
1604    ===========
1605    Map_SaveFile
1606    ===========
1607  */
1608 bool Map_SaveFile( const char* filename ){
1609         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1610         bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1611         if ( success ) {
1612                 // refresh VFS to apply new pak filtering based on mapname
1613                 // needed for daemon DPK VFS
1614                 VFS_Refresh();
1615         }
1616         return success;
1617 }
1618
1619 //
1620 //===========
1621 //Map_SaveSelected
1622 //===========
1623 //
1624 // Saves selected world brushes and whole entities with partial/full selections
1625 //
1626 bool Map_SaveSelected( const char* filename ){
1627         return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1628 }
1629
1630
1631 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1632 {
1633 scene::Node& m_parent;
1634 public:
1635 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ){
1636 }
1637 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1638         if ( path.top().get_pointer() != &m_parent
1639                  && Node_isPrimitive( path.top() ) ) {
1640                 Selectable* selectable = Instance_getSelectable( instance );
1641                 if ( selectable != 0
1642                          && selectable->isSelected()
1643                          && path.size() > 1 ) {
1644                         return false;
1645                 }
1646         }
1647         return true;
1648 }
1649 void post( const scene::Path& path, scene::Instance& instance ) const {
1650         if ( path.top().get_pointer() != &m_parent
1651                  && Node_isPrimitive( path.top() ) ) {
1652                 Selectable* selectable = Instance_getSelectable( instance );
1653                 if ( selectable != 0
1654                          && selectable->isSelected()
1655                          && path.size() > 1 ) {
1656                         scene::Node& parent = path.parent();
1657                         if ( &parent != &m_parent ) {
1658                                 NodeSmartReference node( path.top().get() );
1659                                 Node_getTraversable( parent )->erase( node );
1660                                 Node_getTraversable( m_parent )->insert( node );
1661                         }
1662                 }
1663         }
1664 }
1665 };
1666
1667 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1668         graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1669 }
1670
1671 class CountSelectedBrushes : public scene::Graph::Walker
1672 {
1673 std::size_t& m_count;
1674 mutable std::size_t m_depth;
1675 public:
1676 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1677         m_count = 0;
1678 }
1679 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1680         if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1681                 return false;
1682         }
1683         Selectable* selectable = Instance_getSelectable( instance );
1684         if ( selectable != 0
1685                  && selectable->isSelected()
1686                  && Node_isPrimitive( path.top() ) ) {
1687                 ++m_count;
1688         }
1689         return true;
1690 }
1691 void post( const scene::Path& path, scene::Instance& instance ) const {
1692         --m_depth;
1693 }
1694 };
1695
1696 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1697         std::size_t count;
1698         graph.traverse( CountSelectedBrushes( count ) );
1699         return count;
1700 }
1701
1702 enum ENodeType
1703 {
1704         eNodeUnknown,
1705         eNodeMap,
1706         eNodeEntity,
1707         eNodePrimitive,
1708 };
1709
1710 const char* nodetype_get_name( ENodeType type ){
1711         if ( type == eNodeMap ) {
1712                 return "map";
1713         }
1714         if ( type == eNodeEntity ) {
1715                 return "entity";
1716         }
1717         if ( type == eNodePrimitive ) {
1718                 return "primitive";
1719         }
1720         return "unknown";
1721 }
1722
1723 ENodeType node_get_nodetype( scene::Node& node ){
1724         if ( Node_isEntity( node ) ) {
1725                 return eNodeEntity;
1726         }
1727         if ( Node_isPrimitive( node ) ) {
1728                 return eNodePrimitive;
1729         }
1730         return eNodeUnknown;
1731 }
1732
1733 bool contains_entity( scene::Node& node ){
1734         return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1735 }
1736
1737 bool contains_primitive( scene::Node& node ){
1738         return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1739 }
1740
1741 ENodeType node_get_contains( scene::Node& node ){
1742         if ( contains_entity( node ) ) {
1743                 return eNodeEntity;
1744         }
1745         if ( contains_primitive( node ) ) {
1746                 return eNodePrimitive;
1747         }
1748         return eNodeUnknown;
1749 }
1750
1751 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1752         ENodeType contains = node_get_contains( parent.top() );
1753         ENodeType type = node_get_nodetype( child.top() );
1754
1755         if ( contains != eNodeUnknown && contains == type ) {
1756                 NodeSmartReference node( child.top().get() );
1757                 Path_deleteTop( child );
1758                 Node_getTraversable( parent.top() )->insert( node );
1759                 SceneChangeNotify();
1760         }
1761         else
1762         {
1763                 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1764         }
1765 }
1766
1767 void Scene_parentSelected(){
1768         UndoableCommand undo( "parentSelected" );
1769
1770         if ( GlobalSelectionSystem().countSelected() > 1 ) {
1771                 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1772                 {
1773                 const scene::Path& m_parent;
1774 public:
1775                 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1776                 }
1777                 void visit( scene::Instance& instance ) const {
1778                         if ( &m_parent != &instance.path() ) {
1779                                 Path_parent( m_parent, instance.path() );
1780                         }
1781                 }
1782                 };
1783
1784                 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1785                 GlobalSelectionSystem().foreachSelected( visitor );
1786         }
1787         else
1788         {
1789                 globalOutputStream() << "failed - did not find two selected nodes.\n";
1790         }
1791 }
1792
1793
1794
1795 void NewMap(){
1796         if ( ConfirmModified( "New Map" ) ) {
1797                 Map_RegionOff();
1798                 Map_Free();
1799                 Map_New();
1800         }
1801 }
1802
1803 CopiedString g_mapsPath;
1804
1805 const char* getMapsPath(){
1806         return g_mapsPath.c_str();
1807 }
1808
1809 const char* getLastFolderPath(){
1810         if (g_strLastFolder.empty()) {
1811                 GlobalPreferenceSystem().registerPreference( "LastFolder", make_property_string( g_strLastFolder ) );
1812                 if (g_strLastFolder.empty()) {
1813                         g_strLastFolder = g_qeglobals.m_userGamePath;
1814                 }
1815         }
1816         return g_strLastFolder.c_str();
1817 }
1818
1819 const char* map_open( const char* title ){
1820         return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false );
1821 }
1822
1823 const char* map_import( const char* title ){
1824         return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false );
1825 }
1826
1827 const char* map_save( const char* title ){
1828         return MainFrame_getWindow().file_dialog( FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true );
1829 }
1830
1831 void OpenMap(){
1832         if ( !ConfirmModified( "Open Map" ) ) {
1833                 return;
1834         }
1835
1836         const char* filename = map_open( "Open Map" );
1837
1838         if ( filename != NULL ) {
1839                 MRU_AddFile( filename );
1840                 Map_RegionOff();
1841                 Map_Free();
1842                 Map_LoadFile( filename );
1843         }
1844 }
1845
1846 void ImportMap(){
1847         const char* filename = map_import( "Import Map" );
1848
1849         if ( filename != NULL ) {
1850                 UndoableCommand undo( "mapImport" );
1851                 Map_ImportFile( filename );
1852         }
1853 }
1854
1855 bool Map_SaveAs(){
1856         const char* filename = map_save( "Save Map" );
1857
1858         if ( filename != NULL ) {
1859                 g_strLastFolder = g_path_get_dirname( filename );
1860                 MRU_AddFile( filename );
1861                 Map_Rename( filename );
1862                 return Map_Save();
1863         }
1864         return false;
1865 }
1866
1867 void SaveMapAs(){
1868         Map_SaveAs();
1869 }
1870
1871 void SaveMap(){
1872         if ( Map_Unnamed( g_map ) ) {
1873                 SaveMapAs();
1874         }
1875         else if ( Map_Modified( g_map ) ) {
1876                 Map_Save();
1877         }
1878 }
1879
1880 void ExportMap(){
1881         const char* filename = map_save( "Export Selection" );
1882
1883         if ( filename != NULL ) {
1884                 g_strLastFolder = g_path_get_dirname( filename );
1885                 Map_SaveSelected( filename );
1886         }
1887 }
1888
1889 void SaveRegion(){
1890         const char* filename = map_save( "Export Region" );
1891
1892         if ( filename != NULL ) {
1893                 g_strLastFolder = g_path_get_dirname( filename );
1894                 Map_SaveRegion( filename );
1895         }
1896 }
1897
1898
1899 void RegionOff(){
1900         Map_RegionOff();
1901         SceneChangeNotify();
1902 }
1903
1904 void RegionXY(){
1905         Map_RegionXY(
1906                 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1907                 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1908                 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1909                 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1910                 );
1911         SceneChangeNotify();
1912 }
1913
1914 void RegionBrush(){
1915         Map_RegionBrush();
1916         SceneChangeNotify();
1917 }
1918
1919 void RegionSelected(){
1920         Map_RegionSelectedBrushes();
1921         SceneChangeNotify();
1922 }
1923
1924
1925
1926
1927
1928 class BrushFindByIndexWalker : public scene::Traversable::Walker
1929 {
1930 mutable std::size_t m_index;
1931 scene::Path& m_path;
1932 public:
1933 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
1934         : m_index( index ), m_path( path ){
1935 }
1936 bool pre( scene::Node& node ) const {
1937         if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
1938                 m_path.push( makeReference( node ) );
1939         }
1940         return false;
1941 }
1942 };
1943
1944 class EntityFindByIndexWalker : public scene::Traversable::Walker
1945 {
1946 mutable std::size_t m_index;
1947 scene::Path& m_path;
1948 public:
1949 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
1950         : m_index( index ), m_path( path ){
1951 }
1952 bool pre( scene::Node& node ) const {
1953         if ( Node_isEntity( node ) && m_index-- == 0 ) {
1954                 m_path.push( makeReference( node ) );
1955         }
1956         return false;
1957 }
1958 };
1959
1960 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
1961         path.push( makeReference( GlobalSceneGraph().root() ) );
1962         {
1963                 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
1964         }
1965         if ( path.size() == 2 ) {
1966                 scene::Traversable* traversable = Node_getTraversable( path.top() );
1967                 if ( traversable != 0 ) {
1968                         traversable->traverse( BrushFindByIndexWalker( brush, path ) );
1969                 }
1970         }
1971 }
1972
1973 inline bool Node_hasChildren( scene::Node& node ){
1974         scene::Traversable* traversable = Node_getTraversable( node );
1975         return traversable != 0 && !traversable->empty();
1976 }
1977
1978 void SelectBrush( int entitynum, int brushnum ){
1979         scene::Path path;
1980         Scene_FindEntityBrush( entitynum, brushnum, path );
1981         if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
1982                 scene::Instance* instance = GlobalSceneGraph().find( path );
1983                 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
1984                 Selectable* selectable = Instance_getSelectable( *instance );
1985                 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
1986                 selectable->setSelected( true );
1987                 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
1988         }
1989 }
1990
1991
1992 class BrushFindIndexWalker : public scene::Graph::Walker
1993 {
1994 mutable const scene::Node* m_node;
1995 std::size_t& m_count;
1996 public:
1997 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
1998         : m_node( &node ), m_count( count ){
1999 }
2000 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2001         if ( Node_isPrimitive( path.top() ) ) {
2002                 if ( m_node == path.top().get_pointer() ) {
2003                         m_node = 0;
2004                 }
2005                 if ( m_node ) {
2006                         ++m_count;
2007                 }
2008         }
2009         return true;
2010 }
2011 };
2012
2013 class EntityFindIndexWalker : public scene::Graph::Walker
2014 {
2015 mutable const scene::Node* m_node;
2016 std::size_t& m_count;
2017 public:
2018 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2019         : m_node( &node ), m_count( count ){
2020 }
2021 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2022         if ( Node_isEntity( path.top() ) ) {
2023                 if ( m_node == path.top().get_pointer() ) {
2024                         m_node = 0;
2025                 }
2026                 if ( m_node ) {
2027                         ++m_count;
2028                 }
2029         }
2030         return true;
2031 }
2032 };
2033
2034 static void GetSelectionIndex( int *ent, int *brush ){
2035         std::size_t count_brush = 0;
2036         std::size_t count_entity = 0;
2037         if ( GlobalSelectionSystem().countSelected() != 0 ) {
2038                 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2039
2040                 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2041                 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2042         }
2043         *brush = int(count_brush);
2044         *ent = int(count_entity);
2045 }
2046
2047 void DoFind(){
2048         ModalDialog dialog;
2049         ui::Entry entity{ui::null};
2050         ui::Entry brush{ui::null};
2051
2052         ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2053
2054         auto accel = ui::AccelGroup(ui::New);
2055         window.add_accel_group( accel );
2056
2057         {
2058                 auto vbox = create_dialog_vbox( 4, 4 );
2059                 window.add(vbox);
2060                 {
2061                         auto table = create_dialog_table( 2, 2, 4, 4 );
2062                         vbox.pack_start( table, TRUE, TRUE, 0 );
2063                         {
2064                                 ui::Widget label = ui::Label( "Entity number" );
2065                                 label.show();
2066                 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2067                         }
2068                         {
2069                                 ui::Widget label = ui::Label( "Brush number" );
2070                                 label.show();
2071                 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2072                         }
2073                         {
2074                                 auto entry = ui::Entry(ui::New);
2075                                 entry.show();
2076                 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2077                                 gtk_widget_grab_focus( entry  );
2078                                 entity = entry;
2079                         }
2080                         {
2081                                 auto entry = ui::Entry(ui::New);
2082                                 entry.show();
2083                 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2084
2085                                 brush = entry;
2086                         }
2087                 }
2088                 {
2089                         auto hbox = create_dialog_hbox( 4 );
2090                         vbox.pack_start( hbox, TRUE, TRUE, 0 );
2091                         {
2092                                 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2093                                 hbox.pack_start( button, FALSE, FALSE, 0 );
2094                                 widget_make_default( button );
2095                                 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2096                         }
2097                         {
2098                                 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2099                                 hbox.pack_start( button, FALSE, FALSE, 0 );
2100                                 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2101                         }
2102                 }
2103         }
2104
2105         // Initialize dialog
2106         char buf[16];
2107         int ent, br;
2108
2109         GetSelectionIndex( &ent, &br );
2110         sprintf( buf, "%i", ent );
2111         entity.text(buf);
2112         sprintf( buf, "%i", br );
2113         brush.text(buf);
2114
2115         if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2116                 const char *entstr = gtk_entry_get_text( entity );
2117                 const char *brushstr = gtk_entry_get_text( brush );
2118                 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2119         }
2120
2121     window.destroy();
2122 }
2123
2124 void Map_constructPreferences( PreferencesPage& page ){
2125         page.appendCheckBox( "", "Load last map on open", g_bLoadLastMap );
2126 }
2127
2128
2129 class MapEntityClasses : public ModuleObserver
2130 {
2131 std::size_t m_unrealised;
2132 public:
2133 MapEntityClasses() : m_unrealised( 1 ){
2134 }
2135 void realise(){
2136         if ( --m_unrealised == 0 ) {
2137                 if ( g_map.m_resource != 0 ) {
2138                         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2139                         g_map.m_resource->realise();
2140                 }
2141         }
2142 }
2143 void unrealise(){
2144         if ( ++m_unrealised == 1 ) {
2145                 if ( g_map.m_resource != 0 ) {
2146                         g_map.m_resource->flush();
2147                         g_map.m_resource->unrealise();
2148                 }
2149         }
2150 }
2151 };
2152
2153 MapEntityClasses g_MapEntityClasses;
2154
2155
2156 class MapModuleObserver : public ModuleObserver
2157 {
2158 std::size_t m_unrealised;
2159 public:
2160 MapModuleObserver() : m_unrealised( 1 ){
2161 }
2162 void realise(){
2163         if ( --m_unrealised == 0 ) {
2164                 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2165                 StringOutputStream buffer( 256 );
2166                 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2167                 Q_mkdir( buffer.c_str() );
2168                 g_mapsPath = buffer.c_str();
2169         }
2170 }
2171 void unrealise(){
2172         if ( ++m_unrealised == 1 ) {
2173                 g_mapsPath = "";
2174         }
2175 }
2176 };
2177
2178 MapModuleObserver g_MapModuleObserver;
2179
2180 CopiedString g_strLastMap;
2181 bool g_bLoadLastMap = false;
2182
2183 void Map_Construct(){
2184         GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2185         GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2186         GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2187         GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2188
2189         GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2190         GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2191         GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2192
2193         PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2194
2195         GlobalEntityClassManager().attach( g_MapEntityClasses );
2196         Radiant_attachHomePathsObserver( g_MapModuleObserver );
2197 }
2198
2199 void Map_Destroy(){
2200         Radiant_detachHomePathsObserver( g_MapModuleObserver );
2201         GlobalEntityClassManager().detach( g_MapEntityClasses );
2202 }