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