fff85c1f2ebe397ba7cfc91ad0d6b17885c856fe
[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 MemberCaller1<NameObserver, 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                         GtkHBox* hbox = create_dialog_hbox( 4 );
780                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
781
782                         {
783                                 GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
784                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
785
786                                 {
787                                         auto entry = ui::Entry(ui::New);
788                                         entry.show();
789                                         gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
790                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
791                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
792                                         gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
793
794                                         brushes_entry = entry;
795                                 }
796                                 {
797                                         auto entry = ui::Entry(ui::New);
798                                         entry.show();
799                                         gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
800                                                                           (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
801                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
802                                         gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
803
804                                         entities_entry = entry;
805                                 }
806                                 {
807                                         ui::Widget label = ui::Label( "Total Brushes" );
808                                         label.show();
809                                         gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
810                                                                           (GtkAttachOptions) ( GTK_FILL ),
811                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
812                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
813                                 }
814                                 {
815                                         ui::Widget label = ui::Label( "Total Entities" );
816                                         label.show();
817                                         gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
818                                                                           (GtkAttachOptions) ( GTK_FILL ),
819                                                                           (GtkAttachOptions) ( 0 ), 0, 0 );
820                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
821                                 }
822                         }
823                         {
824                                 GtkVBox* vbox2 = create_dialog_vbox( 4 );
825                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), FALSE, FALSE, 0 );
826
827                                 {
828                                         GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
829                                         gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
830                                 }
831                         }
832                 }
833                 {
834                         ui::Widget label = ui::Label( "Entity breakdown" );
835                         label.show();
836                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, TRUE, 0 );
837                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
838                 }
839                 {
840                         auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
841                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( scr ), TRUE, TRUE, 0 );
842
843                         {
844                                 ui::ListStore store = ui::ListStore(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ));
845
846                                 ui::Widget view = ui::TreeView(ui::TreeModel( GTK_TREE_MODEL( store ) ));
847                                 gtk_tree_view_set_headers_clickable( GTK_TREE_VIEW( view ), TRUE );
848
849                                 {
850                                         auto renderer = ui::CellRendererText(ui::New);
851                                         GtkTreeViewColumn* column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
852                                         gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
853                                         gtk_tree_view_column_set_sort_column_id( column, 0 );
854                                 }
855
856                                 {
857                                         auto renderer = ui::CellRendererText(ui::New);
858                                         GtkTreeViewColumn* column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
859                                         gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
860                                         gtk_tree_view_column_set_sort_column_id( column, 1 );
861                                 }
862
863                                 view.show();
864
865                                 scr.add(view);
866
867                                 EntityBreakdownWalker = store;
868                         }
869                 }
870         }
871
872         // Initialize fields
873
874         {
875                 EntityBreakdown entitymap;
876                 Scene_EntityBreakdown( entitymap );
877
878                 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
879                 {
880                         char tmp[16];
881                         sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
882                         EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
883                 }
884         }
885
886         EntityBreakdownWalker.unref();
887
888         char tmp[16];
889         sprintf( tmp, "%u", Unsigned( g_brushCount.get() ) );
890         brushes_entry.text(tmp);
891         sprintf( tmp, "%u", Unsigned( g_entityCount.get() ) );
892         entities_entry.text(tmp);
893
894         modal_dialog_show( window, dialog );
895
896         // save before exit
897         window_get_position( window, g_posMapInfoWnd );
898
899     window.destroy();
900 }
901
902
903
904 class ScopeTimer
905 {
906 Timer m_timer;
907 const char* m_message;
908 public:
909 ScopeTimer( const char* message )
910         : m_message( message ){
911         m_timer.start();
912 }
913 ~ScopeTimer(){
914         double elapsed_time = m_timer.elapsed_msec() / 1000.f;
915         globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
916 }
917 };
918
919 CopiedString g_strLastFolder = "";
920
921 /*
922    ================
923    Map_LoadFile
924    ================
925  */
926
927 void Map_LoadFile( const char *filename ){
928         globalOutputStream() << "Loading map from " << filename << "\n";
929         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
930
931         MRU_AddFile( filename );
932         g_strLastFolder = g_path_get_dirname( filename );
933
934         {
935                 ScopeTimer timer( "map load" );
936
937                 const MapFormat* format = NULL;
938                 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
939                 if ( string_not_empty( moduleName ) ) {
940                         format = ReferenceAPI_getMapModules().findModule( moduleName );
941                 }
942
943                 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
944                 {
945                         if ( i ) {
946                                 Map_Free();
947                         }
948                         Brush_toggleFormat( i );
949                         g_map.m_name = filename;
950                         Map_UpdateTitle( g_map );
951                         g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
952                         if ( format ) {
953                                 format->wrongFormat = false;
954                         }
955                         g_map.m_resource->attach( g_map );
956                         if ( format ) {
957                                 if ( !format->wrongFormat ) {
958                                         break;
959                                 }
960                         }
961                 }
962
963                 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
964         }
965
966         globalOutputStream() << "--- LoadMapFile ---\n";
967         globalOutputStream() << g_map.m_name.c_str() << "\n";
968
969         globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
970         globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
971
972         //GlobalEntityCreator().printStatistics();
973
974         //
975         // move the view to a start position
976         //
977         Map_StartPosition();
978
979         g_currentMap = &g_map;
980
981         // restart VFS to apply new pak filtering based on mapname
982         // needed for daemon DPK VFS
983         VFS_Restart();
984 }
985
986 class Excluder
987 {
988 public:
989 virtual bool excluded( scene::Node& node ) const = 0;
990 };
991
992 class ExcludeWalker : public scene::Traversable::Walker
993 {
994 const scene::Traversable::Walker& m_walker;
995 const Excluder* m_exclude;
996 mutable bool m_skip;
997 public:
998 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
999         : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1000 }
1001 bool pre( scene::Node& node ) const {
1002         if ( m_exclude->excluded( node ) || node.isRoot() ) {
1003                 m_skip = true;
1004                 return false;
1005         }
1006         else
1007         {
1008                 m_walker.pre( node );
1009         }
1010         return true;
1011 }
1012 void post( scene::Node& node ) const {
1013         if ( m_skip ) {
1014                 m_skip = false;
1015         }
1016         else
1017         {
1018                 m_walker.post( node );
1019         }
1020 }
1021 };
1022
1023 class AnyInstanceSelected : public scene::Instantiable::Visitor
1024 {
1025 bool& m_selected;
1026 public:
1027 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1028         m_selected = false;
1029 }
1030 void visit( scene::Instance& instance ) const {
1031         Selectable* selectable = Instance_getSelectable( instance );
1032         if ( selectable != 0
1033                  && selectable->isSelected() ) {
1034                 m_selected = true;
1035         }
1036 }
1037 };
1038
1039 bool Node_instanceSelected( scene::Node& node ){
1040         scene::Instantiable* instantiable = Node_getInstantiable( node );
1041         ASSERT_NOTNULL( instantiable );
1042         bool selected;
1043         instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1044         return selected;
1045 }
1046
1047 class SelectedDescendantWalker : public scene::Traversable::Walker
1048 {
1049 bool& m_selected;
1050 public:
1051 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1052         m_selected = false;
1053 }
1054
1055 bool pre( scene::Node& node ) const {
1056         if ( node.isRoot() ) {
1057                 return false;
1058         }
1059
1060         if ( Node_instanceSelected( node ) ) {
1061                 m_selected = true;
1062         }
1063
1064         return true;
1065 }
1066 };
1067
1068 bool Node_selectedDescendant( scene::Node& node ){
1069         bool selected;
1070         Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1071         return selected;
1072 }
1073
1074 class SelectionExcluder : public Excluder
1075 {
1076 public:
1077 bool excluded( scene::Node& node ) const {
1078         return !Node_selectedDescendant( node );
1079 }
1080 };
1081
1082 class IncludeSelectedWalker : public scene::Traversable::Walker
1083 {
1084 const scene::Traversable::Walker& m_walker;
1085 mutable std::size_t m_selected;
1086 mutable bool m_skip;
1087
1088 bool selectedParent() const {
1089         return m_selected != 0;
1090 }
1091 public:
1092 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1093         : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1094 }
1095 bool pre( scene::Node& node ) const {
1096         // include node if:
1097         // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1098         if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1099                 if ( Node_instanceSelected( node ) ) {
1100                         ++m_selected;
1101                 }
1102                 m_walker.pre( node );
1103                 return true;
1104         }
1105         else
1106         {
1107                 m_skip = true;
1108                 return false;
1109         }
1110 }
1111 void post( scene::Node& node ) const {
1112         if ( m_skip ) {
1113                 m_skip = false;
1114         }
1115         else
1116         {
1117                 if ( Node_instanceSelected( node ) ) {
1118                         --m_selected;
1119                 }
1120                 m_walker.post( node );
1121         }
1122 }
1123 };
1124
1125 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1126         scene::Traversable* traversable = Node_getTraversable( root );
1127         if ( traversable != 0 ) {
1128 #if 0
1129                 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1130 #else
1131                 traversable->traverse( IncludeSelectedWalker( walker ) );
1132 #endif
1133         }
1134 }
1135
1136 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1137         format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out );
1138 }
1139
1140 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1141         scene::Traversable* traversable = Node_getTraversable( root );
1142         if ( traversable != 0 ) {
1143                 traversable->traverse( walker );
1144         }
1145 }
1146
1147 class RegionExcluder : public Excluder
1148 {
1149 public:
1150 bool excluded( scene::Node& node ) const {
1151         return node.excluded();
1152 }
1153 };
1154
1155 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1156         scene::Traversable* traversable = Node_getTraversable( root );
1157         if ( traversable != 0 ) {
1158                 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1159         }
1160 }
1161
1162 bool Map_SaveRegion( const char *filename ){
1163         AddRegionBrushes();
1164
1165         bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1166
1167         RemoveRegionBrushes();
1168
1169         return success;
1170 }
1171
1172
1173 void Map_RenameAbsolute( const char* absolute ){
1174         Resource* resource = GlobalReferenceCache().capture( absolute );
1175         NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1176         resource->setNode( clone.get_pointer() );
1177
1178         {
1179                 //ScopeTimer timer("clone subgraph");
1180                 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1181         }
1182
1183         g_map.m_resource->detach( g_map );
1184         GlobalReferenceCache().release( g_map.m_name.c_str() );
1185
1186         g_map.m_resource = resource;
1187
1188         g_map.m_name = absolute;
1189         Map_UpdateTitle( g_map );
1190
1191         g_map.m_resource->attach( g_map );
1192         // refresh VFS to apply new pak filtering based on mapname
1193         // needed for daemon DPK VFS
1194         VFS_Refresh();
1195 }
1196
1197 void Map_Rename( const char* filename ){
1198         if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1199                 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1200
1201                 Map_RenameAbsolute( filename );
1202
1203                 SceneChangeNotify();
1204         }
1205         else
1206         {
1207                 SaveReferences();
1208         }
1209 }
1210
1211 bool Map_Save(){
1212         Pointfile_Clear();
1213
1214         ScopeTimer timer( "map save" );
1215         SaveReferences();
1216         return true; // assume success..
1217 }
1218
1219 /*
1220    ===========
1221    Map_New
1222
1223    ===========
1224  */
1225 void Map_New(){
1226         //globalOutputStream() << "Map_New\n";
1227
1228         g_map.m_name = "unnamed.map";
1229         Map_UpdateTitle( g_map );
1230
1231         {
1232                 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1233 //    ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1234                 g_map.m_resource->attach( g_map );
1235
1236                 SceneChangeNotify();
1237         }
1238
1239         FocusViews( g_vector3_identity, 0 );
1240
1241         g_currentMap = &g_map;
1242
1243         // restart VFS to apply new pak filtering based on mapname
1244         // needed for daemon DPK VFS
1245         VFS_Restart();
1246 }
1247
1248 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 &region_mins, const Vector3 &region_maxs );
1249
1250 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1251         /*!
1252            \todo we need to make sure that the player start IS inside the region and bail out if it's not
1253            the compiler will refuse to compile a map with a player_start somewhere in empty space..
1254            for now, let's just print an error
1255          */
1256
1257         Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1258
1259         for ( int i = 0 ; i < 3 ; i++ )
1260         {
1261                 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1262                         globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1263                         break;
1264                 }
1265         }
1266
1267         // write the info_playerstart
1268         char sTmp[1024];
1269         sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1270         Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1271         sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1272         Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1273 }
1274
1275 /*
1276    ===========================================================
1277
1278    REGION
1279
1280    ===========================================================
1281  */
1282 bool region_active;
1283 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1284 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1285
1286 scene::Node* region_sides[6];
1287 scene::Node* region_startpoint = 0;
1288
1289 /*
1290    ===========
1291    AddRegionBrushes
1292    a regioned map will have temp walls put up at the region boundary
1293    \todo TODO TTimo old implementation of region brushes
1294    we still add them straight in the worldspawn and take them out after the map is saved
1295    with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1296    ===========
1297  */
1298 void AddRegionBrushes( void ){
1299         int i;
1300
1301         for ( i = 0; i < 6; i++ )
1302         {
1303                 region_sides[i] = &GlobalBrushCreator().createBrush();
1304                 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1305         }
1306
1307         region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1308
1309         ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1310         ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1311
1312         Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1313 }
1314
1315 void RemoveRegionBrushes( void ){
1316         for ( std::size_t i = 0; i < 6; i++ )
1317         {
1318                 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1319         }
1320         Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1321 }
1322
1323 inline void exclude_node( scene::Node& node, bool exclude ){
1324         exclude
1325         ? node.enable( scene::Node::eExcluded )
1326         : node.disable( scene::Node::eExcluded );
1327 }
1328
1329 class ExcludeAllWalker : public scene::Graph::Walker
1330 {
1331 bool m_exclude;
1332 public:
1333 ExcludeAllWalker( bool exclude )
1334         : m_exclude( exclude ){
1335 }
1336 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1337         exclude_node( path.top(), m_exclude );
1338
1339         return true;
1340 }
1341 };
1342
1343 void Scene_Exclude_All( bool exclude ){
1344         GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1345 }
1346
1347 bool Instance_isSelected( const scene::Instance& instance ){
1348         const Selectable* selectable = Instance_getSelectable( instance );
1349         return selectable != 0 && selectable->isSelected();
1350 }
1351
1352 class ExcludeSelectedWalker : public scene::Graph::Walker
1353 {
1354 bool m_exclude;
1355 public:
1356 ExcludeSelectedWalker( bool exclude )
1357         : m_exclude( exclude ){
1358 }
1359 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1360         exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1361         return true;
1362 }
1363 };
1364
1365 void Scene_Exclude_Selected( bool exclude ){
1366         GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1367 }
1368
1369 class ExcludeRegionedWalker : public scene::Graph::Walker
1370 {
1371 bool m_exclude;
1372 public:
1373 ExcludeRegionedWalker( bool exclude )
1374         : m_exclude( exclude ){
1375 }
1376 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1377         exclude_node(
1378                 path.top(),
1379                 !(
1380                         (
1381                                 aabb_intersects_aabb(
1382                                         instance.worldAABB(),
1383                                         aabb_for_minmax( region_mins, region_maxs )
1384                                         ) != 0
1385                         ) ^ m_exclude
1386                         )
1387                 );
1388
1389         return true;
1390 }
1391 };
1392
1393 void Scene_Exclude_Region( bool exclude ){
1394         GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1395 }
1396
1397 /*
1398    ===========
1399    Map_RegionOff
1400
1401    Other filtering options may still be on
1402    ===========
1403  */
1404 void Map_RegionOff(){
1405         region_active = false;
1406
1407         region_maxs[0] = g_MaxWorldCoord - 64;
1408         region_mins[0] = g_MinWorldCoord + 64;
1409         region_maxs[1] = g_MaxWorldCoord - 64;
1410         region_mins[1] = g_MinWorldCoord + 64;
1411         region_maxs[2] = g_MaxWorldCoord - 64;
1412         region_mins[2] = g_MinWorldCoord + 64;
1413
1414         Scene_Exclude_All( false );
1415 }
1416
1417 void Map_ApplyRegion( void ){
1418         region_active = true;
1419
1420         Scene_Exclude_Region( false );
1421 }
1422
1423
1424 /*
1425    ========================
1426    Map_RegionSelectedBrushes
1427    ========================
1428  */
1429 void Map_RegionSelectedBrushes( void ){
1430         Map_RegionOff();
1431
1432         if ( GlobalSelectionSystem().countSelected() != 0
1433                  && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1434                 region_active = true;
1435                 Select_GetBounds( region_mins, region_maxs );
1436
1437                 Scene_Exclude_Selected( false );
1438
1439                 GlobalSelectionSystem().setSelectedAll( false );
1440         }
1441 }
1442
1443
1444 /*
1445    ===========
1446    Map_RegionXY
1447    ===========
1448  */
1449 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1450         Map_RegionOff();
1451
1452         region_mins[0] = x_min;
1453         region_maxs[0] = x_max;
1454         region_mins[1] = y_min;
1455         region_maxs[1] = y_max;
1456         region_mins[2] = g_MinWorldCoord + 64;
1457         region_maxs[2] = g_MaxWorldCoord - 64;
1458
1459         Map_ApplyRegion();
1460 }
1461
1462 void Map_RegionBounds( const AABB& bounds ){
1463         Map_RegionOff();
1464
1465         region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1466         region_maxs = vector3_added( bounds.origin, bounds.extents );
1467
1468         deleteSelection();
1469
1470         Map_ApplyRegion();
1471 }
1472
1473 /*
1474    ===========
1475    Map_RegionBrush
1476    ===========
1477  */
1478 void Map_RegionBrush( void ){
1479         if ( GlobalSelectionSystem().countSelected() != 0 ) {
1480                 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1481                 Map_RegionBounds( instance.worldAABB() );
1482         }
1483 }
1484
1485 //
1486 //================
1487 //Map_ImportFile
1488 //================
1489 //
1490 bool Map_ImportFile( const char* filename ){
1491         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1492
1493         g_strLastFolder = g_path_get_dirname( filename );
1494
1495         bool success = false;
1496
1497         if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1498                 goto tryDecompile;
1499         }
1500
1501         {
1502                 const MapFormat* format = NULL;
1503                 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1504                 if ( string_not_empty( moduleName ) ) {
1505                         format = ReferenceAPI_getMapModules().findModule( moduleName );
1506                 }
1507
1508                 if ( format ) {
1509                         format->wrongFormat = false;
1510                 }
1511                 Resource* resource = GlobalReferenceCache().capture( filename );
1512                 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1513                 if ( !resource->load() ) {
1514                         GlobalReferenceCache().release( filename );
1515                         goto tryDecompile;
1516                 }
1517                 if ( format ) {
1518                         if ( format->wrongFormat ) {
1519                                 GlobalReferenceCache().release( filename );
1520                                 goto tryDecompile;
1521                         }
1522                 }
1523                 NodeSmartReference clone( NewMapRoot( "" ) );
1524                 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1525                 Map_gatherNamespaced( clone );
1526                 Map_mergeClonedNames();
1527                 MergeMap( clone );
1528                 success = true;
1529                 GlobalReferenceCache().release( filename );
1530         }
1531
1532         SceneChangeNotify();
1533
1534         return success;
1535
1536 tryDecompile:
1537
1538         const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue( "q3map2_type" );
1539         int n = string_length( path_get_extension( filename ) );
1540         if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1541                 StringBuffer output;
1542                 output.push_string( AppPath_get() );
1543                 output.push_string( "q3map2." );
1544                 output.push_string( RADIANT_EXECUTABLE );
1545                 output.push_string( " -v -game " );
1546                 output.push_string( ( type && *type ) ? type : "quake3" );
1547                 output.push_string( " -fs_basepath \"" );
1548                 output.push_string( EnginePath_get() );
1549                 output.push_string( "\" -fs_homepath \"" );
1550                 output.push_string( g_qeglobals.m_userEnginePath.c_str() );
1551                 output.push_string( "\" -fs_game " );
1552                 output.push_string( gamename_get() );
1553                 output.push_string( " -convert -format " );
1554                 output.push_string( Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map" );
1555                 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1556                         output.push_string( " -readmap " );
1557                 }
1558                 output.push_string( " \"" );
1559                 output.push_string( filename );
1560                 output.push_string( "\"" );
1561
1562                 // run
1563                 Q_Exec( NULL, output.c_str(), NULL, false, true );
1564
1565                 // rebuild filename as "filenamewithoutext_converted.map"
1566                 output.clear();
1567                 output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
1568                 output.push_string( "_converted.map" );
1569                 filename = output.c_str();
1570
1571                 // open
1572                 Resource* resource = GlobalReferenceCache().capture( filename );
1573                 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1574                 if ( !resource->load() ) {
1575                         GlobalReferenceCache().release( filename );
1576                         goto tryDecompile;
1577                 }
1578                 NodeSmartReference clone( NewMapRoot( "" ) );
1579                 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1580                 Map_gatherNamespaced( clone );
1581                 Map_mergeClonedNames();
1582                 MergeMap( clone );
1583                 success = true;
1584                 GlobalReferenceCache().release( filename );
1585         }
1586
1587         SceneChangeNotify();
1588         return success;
1589 }
1590
1591 /*
1592    ===========
1593    Map_SaveFile
1594    ===========
1595  */
1596 bool Map_SaveFile( const char* filename ){
1597         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1598         bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1599         if ( success ) {
1600                 // refresh VFS to apply new pak filtering based on mapname
1601                 // needed for daemon DPK VFS
1602                 VFS_Refresh();
1603         }
1604         return success;
1605 }
1606
1607 //
1608 //===========
1609 //Map_SaveSelected
1610 //===========
1611 //
1612 // Saves selected world brushes and whole entities with partial/full selections
1613 //
1614 bool Map_SaveSelected( const char* filename ){
1615         return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1616 }
1617
1618
1619 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1620 {
1621 scene::Node& m_parent;
1622 public:
1623 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ){
1624 }
1625 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1626         if ( path.top().get_pointer() != &m_parent
1627                  && Node_isPrimitive( path.top() ) ) {
1628                 Selectable* selectable = Instance_getSelectable( instance );
1629                 if ( selectable != 0
1630                          && selectable->isSelected()
1631                          && path.size() > 1 ) {
1632                         return false;
1633                 }
1634         }
1635         return true;
1636 }
1637 void post( 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                         scene::Node& parent = path.parent();
1645                         if ( &parent != &m_parent ) {
1646                                 NodeSmartReference node( path.top().get() );
1647                                 Node_getTraversable( parent )->erase( node );
1648                                 Node_getTraversable( m_parent )->insert( node );
1649                         }
1650                 }
1651         }
1652 }
1653 };
1654
1655 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1656         graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1657 }
1658
1659 class CountSelectedBrushes : public scene::Graph::Walker
1660 {
1661 std::size_t& m_count;
1662 mutable std::size_t m_depth;
1663 public:
1664 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1665         m_count = 0;
1666 }
1667 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1668         if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1669                 return false;
1670         }
1671         Selectable* selectable = Instance_getSelectable( instance );
1672         if ( selectable != 0
1673                  && selectable->isSelected()
1674                  && Node_isPrimitive( path.top() ) ) {
1675                 ++m_count;
1676         }
1677         return true;
1678 }
1679 void post( const scene::Path& path, scene::Instance& instance ) const {
1680         --m_depth;
1681 }
1682 };
1683
1684 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1685         std::size_t count;
1686         graph.traverse( CountSelectedBrushes( count ) );
1687         return count;
1688 }
1689
1690 enum ENodeType
1691 {
1692         eNodeUnknown,
1693         eNodeMap,
1694         eNodeEntity,
1695         eNodePrimitive,
1696 };
1697
1698 const char* nodetype_get_name( ENodeType type ){
1699         if ( type == eNodeMap ) {
1700                 return "map";
1701         }
1702         if ( type == eNodeEntity ) {
1703                 return "entity";
1704         }
1705         if ( type == eNodePrimitive ) {
1706                 return "primitive";
1707         }
1708         return "unknown";
1709 }
1710
1711 ENodeType node_get_nodetype( scene::Node& node ){
1712         if ( Node_isEntity( node ) ) {
1713                 return eNodeEntity;
1714         }
1715         if ( Node_isPrimitive( node ) ) {
1716                 return eNodePrimitive;
1717         }
1718         return eNodeUnknown;
1719 }
1720
1721 bool contains_entity( scene::Node& node ){
1722         return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1723 }
1724
1725 bool contains_primitive( scene::Node& node ){
1726         return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1727 }
1728
1729 ENodeType node_get_contains( scene::Node& node ){
1730         if ( contains_entity( node ) ) {
1731                 return eNodeEntity;
1732         }
1733         if ( contains_primitive( node ) ) {
1734                 return eNodePrimitive;
1735         }
1736         return eNodeUnknown;
1737 }
1738
1739 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1740         ENodeType contains = node_get_contains( parent.top() );
1741         ENodeType type = node_get_nodetype( child.top() );
1742
1743         if ( contains != eNodeUnknown && contains == type ) {
1744                 NodeSmartReference node( child.top().get() );
1745                 Path_deleteTop( child );
1746                 Node_getTraversable( parent.top() )->insert( node );
1747                 SceneChangeNotify();
1748         }
1749         else
1750         {
1751                 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1752         }
1753 }
1754
1755 void Scene_parentSelected(){
1756         UndoableCommand undo( "parentSelected" );
1757
1758         if ( GlobalSelectionSystem().countSelected() > 1 ) {
1759                 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1760                 {
1761                 const scene::Path& m_parent;
1762 public:
1763                 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1764                 }
1765                 void visit( scene::Instance& instance ) const {
1766                         if ( &m_parent != &instance.path() ) {
1767                                 Path_parent( m_parent, instance.path() );
1768                         }
1769                 }
1770                 };
1771
1772                 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1773                 GlobalSelectionSystem().foreachSelected( visitor );
1774         }
1775         else
1776         {
1777                 globalOutputStream() << "failed - did not find two selected nodes.\n";
1778         }
1779 }
1780
1781
1782
1783 void NewMap(){
1784         if ( ConfirmModified( "New Map" ) ) {
1785                 Map_RegionOff();
1786                 Map_Free();
1787                 Map_New();
1788         }
1789 }
1790
1791 CopiedString g_mapsPath;
1792
1793 const char* getMapsPath(){
1794         return g_mapsPath.c_str();
1795 }
1796
1797 const char* getLastFolderPath(){
1798         if (g_strLastFolder.empty()) {
1799                 GlobalPreferenceSystem().registerPreference( "LastFolder", CopiedStringImportStringCaller( g_strLastFolder ), CopiedStringExportStringCaller( g_strLastFolder ) );
1800                 if (g_strLastFolder.empty()) {
1801                         g_strLastFolder = g_qeglobals.m_userGamePath;
1802                 }
1803         }
1804         return g_strLastFolder.c_str();
1805 }
1806
1807 const char* map_open( const char* title ){
1808         return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false );
1809 }
1810
1811 const char* map_import( const char* title ){
1812         return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false );
1813 }
1814
1815 const char* map_save( const char* title ){
1816         return MainFrame_getWindow().file_dialog( FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true );
1817 }
1818
1819 void OpenMap(){
1820         if ( !ConfirmModified( "Open Map" ) ) {
1821                 return;
1822         }
1823
1824         const char* filename = map_open( "Open Map" );
1825
1826         if ( filename != NULL ) {
1827                 MRU_AddFile( filename );
1828                 Map_RegionOff();
1829                 Map_Free();
1830                 Map_LoadFile( filename );
1831         }
1832 }
1833
1834 void ImportMap(){
1835         const char* filename = map_import( "Import Map" );
1836
1837         if ( filename != NULL ) {
1838                 UndoableCommand undo( "mapImport" );
1839                 Map_ImportFile( filename );
1840         }
1841 }
1842
1843 bool Map_SaveAs(){
1844         const char* filename = map_save( "Save Map" );
1845
1846         if ( filename != NULL ) {
1847                 g_strLastFolder = g_path_get_dirname( filename );
1848                 MRU_AddFile( filename );
1849                 Map_Rename( filename );
1850                 return Map_Save();
1851         }
1852         return false;
1853 }
1854
1855 void SaveMapAs(){
1856         Map_SaveAs();
1857 }
1858
1859 void SaveMap(){
1860         if ( Map_Unnamed( g_map ) ) {
1861                 SaveMapAs();
1862         }
1863         else if ( Map_Modified( g_map ) ) {
1864                 Map_Save();
1865         }
1866 }
1867
1868 void ExportMap(){
1869         const char* filename = map_save( "Export Selection" );
1870
1871         if ( filename != NULL ) {
1872                 g_strLastFolder = g_path_get_dirname( filename );
1873                 Map_SaveSelected( filename );
1874         }
1875 }
1876
1877 void SaveRegion(){
1878         const char* filename = map_save( "Export Region" );
1879
1880         if ( filename != NULL ) {
1881                 g_strLastFolder = g_path_get_dirname( filename );
1882                 Map_SaveRegion( filename );
1883         }
1884 }
1885
1886
1887 void RegionOff(){
1888         Map_RegionOff();
1889         SceneChangeNotify();
1890 }
1891
1892 void RegionXY(){
1893         Map_RegionXY(
1894                 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1895                 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1896                 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1897                 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1898                 );
1899         SceneChangeNotify();
1900 }
1901
1902 void RegionBrush(){
1903         Map_RegionBrush();
1904         SceneChangeNotify();
1905 }
1906
1907 void RegionSelected(){
1908         Map_RegionSelectedBrushes();
1909         SceneChangeNotify();
1910 }
1911
1912
1913
1914
1915
1916 class BrushFindByIndexWalker : public scene::Traversable::Walker
1917 {
1918 mutable std::size_t m_index;
1919 scene::Path& m_path;
1920 public:
1921 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
1922         : m_index( index ), m_path( path ){
1923 }
1924 bool pre( scene::Node& node ) const {
1925         if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
1926                 m_path.push( makeReference( node ) );
1927         }
1928         return false;
1929 }
1930 };
1931
1932 class EntityFindByIndexWalker : public scene::Traversable::Walker
1933 {
1934 mutable std::size_t m_index;
1935 scene::Path& m_path;
1936 public:
1937 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
1938         : m_index( index ), m_path( path ){
1939 }
1940 bool pre( scene::Node& node ) const {
1941         if ( Node_isEntity( node ) && m_index-- == 0 ) {
1942                 m_path.push( makeReference( node ) );
1943         }
1944         return false;
1945 }
1946 };
1947
1948 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
1949         path.push( makeReference( GlobalSceneGraph().root() ) );
1950         {
1951                 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
1952         }
1953         if ( path.size() == 2 ) {
1954                 scene::Traversable* traversable = Node_getTraversable( path.top() );
1955                 if ( traversable != 0 ) {
1956                         traversable->traverse( BrushFindByIndexWalker( brush, path ) );
1957                 }
1958         }
1959 }
1960
1961 inline bool Node_hasChildren( scene::Node& node ){
1962         scene::Traversable* traversable = Node_getTraversable( node );
1963         return traversable != 0 && !traversable->empty();
1964 }
1965
1966 void SelectBrush( int entitynum, int brushnum ){
1967         scene::Path path;
1968         Scene_FindEntityBrush( entitynum, brushnum, path );
1969         if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
1970                 scene::Instance* instance = GlobalSceneGraph().find( path );
1971                 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
1972                 Selectable* selectable = Instance_getSelectable( *instance );
1973                 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
1974                 selectable->setSelected( true );
1975                 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
1976         }
1977 }
1978
1979
1980 class BrushFindIndexWalker : public scene::Graph::Walker
1981 {
1982 mutable const scene::Node* m_node;
1983 std::size_t& m_count;
1984 public:
1985 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
1986         : m_node( &node ), m_count( count ){
1987 }
1988 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1989         if ( Node_isPrimitive( path.top() ) ) {
1990                 if ( m_node == path.top().get_pointer() ) {
1991                         m_node = 0;
1992                 }
1993                 if ( m_node ) {
1994                         ++m_count;
1995                 }
1996         }
1997         return true;
1998 }
1999 };
2000
2001 class EntityFindIndexWalker : public scene::Graph::Walker
2002 {
2003 mutable const scene::Node* m_node;
2004 std::size_t& m_count;
2005 public:
2006 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2007         : m_node( &node ), m_count( count ){
2008 }
2009 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2010         if ( Node_isEntity( path.top() ) ) {
2011                 if ( m_node == path.top().get_pointer() ) {
2012                         m_node = 0;
2013                 }
2014                 if ( m_node ) {
2015                         ++m_count;
2016                 }
2017         }
2018         return true;
2019 }
2020 };
2021
2022 static void GetSelectionIndex( int *ent, int *brush ){
2023         std::size_t count_brush = 0;
2024         std::size_t count_entity = 0;
2025         if ( GlobalSelectionSystem().countSelected() != 0 ) {
2026                 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2027
2028                 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2029                 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2030         }
2031         *brush = int(count_brush);
2032         *ent = int(count_entity);
2033 }
2034
2035 void DoFind(){
2036         ModalDialog dialog;
2037         ui::Entry entity{ui::null};
2038         ui::Entry brush{ui::null};
2039
2040         ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2041
2042         auto accel = ui::AccelGroup(ui::New);
2043         window.add_accel_group( accel );
2044
2045         {
2046                 auto vbox = create_dialog_vbox( 4, 4 );
2047                 window.add(vbox);
2048                 {
2049                         GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
2050                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
2051                         {
2052                                 ui::Widget label = ui::Label( "Entity number" );
2053                                 label.show();
2054                                 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
2055                                                                   (GtkAttachOptions) ( 0 ),
2056                                                                   (GtkAttachOptions) ( 0 ), 0, 0 );
2057                         }
2058                         {
2059                                 ui::Widget label = ui::Label( "Brush number" );
2060                                 label.show();
2061                                 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
2062                                                                   (GtkAttachOptions) ( 0 ),
2063                                                                   (GtkAttachOptions) ( 0 ), 0, 0 );
2064                         }
2065                         {
2066                                 auto entry = ui::Entry(ui::New);
2067                                 entry.show();
2068                                 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
2069                                                                   (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2070                                                                   (GtkAttachOptions) ( 0 ), 0, 0 );
2071                                 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
2072                                 entity = entry;
2073                         }
2074                         {
2075                                 auto entry = ui::Entry(ui::New);
2076                                 entry.show();
2077                                 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
2078                                                                   (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2079                                                                   (GtkAttachOptions) ( 0 ), 0, 0 );
2080
2081                                 brush = entry;
2082                         }
2083                 }
2084                 {
2085                         GtkHBox* hbox = create_dialog_hbox( 4 );
2086                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), TRUE, TRUE, 0 );
2087                         {
2088                                 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2089                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
2090                                 widget_make_default( button );
2091                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2092                         }
2093                         {
2094                                 GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2095                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
2096                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2097                         }
2098                 }
2099         }
2100
2101         // Initialize dialog
2102         char buf[16];
2103         int ent, br;
2104
2105         GetSelectionIndex( &ent, &br );
2106         sprintf( buf, "%i", ent );
2107         entity.text(buf);
2108         sprintf( buf, "%i", br );
2109         brush.text(buf);
2110
2111         if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2112                 const char *entstr = gtk_entry_get_text( entity );
2113                 const char *brushstr = gtk_entry_get_text( brush );
2114                 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2115         }
2116
2117     window.destroy();
2118 }
2119
2120 void Map_constructPreferences( PreferencesPage& page ){
2121         page.appendCheckBox( "", "Load last map on open", g_bLoadLastMap );
2122 }
2123
2124
2125 class MapEntityClasses : public ModuleObserver
2126 {
2127 std::size_t m_unrealised;
2128 public:
2129 MapEntityClasses() : m_unrealised( 1 ){
2130 }
2131 void realise(){
2132         if ( --m_unrealised == 0 ) {
2133                 if ( g_map.m_resource != 0 ) {
2134                         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2135                         g_map.m_resource->realise();
2136                 }
2137         }
2138 }
2139 void unrealise(){
2140         if ( ++m_unrealised == 1 ) {
2141                 if ( g_map.m_resource != 0 ) {
2142                         g_map.m_resource->flush();
2143                         g_map.m_resource->unrealise();
2144                 }
2145         }
2146 }
2147 };
2148
2149 MapEntityClasses g_MapEntityClasses;
2150
2151
2152 class MapModuleObserver : public ModuleObserver
2153 {
2154 std::size_t m_unrealised;
2155 public:
2156 MapModuleObserver() : m_unrealised( 1 ){
2157 }
2158 void realise(){
2159         if ( --m_unrealised == 0 ) {
2160                 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2161                 StringOutputStream buffer( 256 );
2162                 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2163                 Q_mkdir( buffer.c_str() );
2164                 g_mapsPath = buffer.c_str();
2165         }
2166 }
2167 void unrealise(){
2168         if ( ++m_unrealised == 1 ) {
2169                 g_mapsPath = "";
2170         }
2171 }
2172 };
2173
2174 MapModuleObserver g_MapModuleObserver;
2175
2176 CopiedString g_strLastMap;
2177 bool g_bLoadLastMap = false;
2178
2179 void Map_Construct(){
2180         GlobalCommands_insert( "RegionOff", FreeCaller<RegionOff>() );
2181         GlobalCommands_insert( "RegionSetXY", FreeCaller<RegionXY>() );
2182         GlobalCommands_insert( "RegionSetBrush", FreeCaller<RegionBrush>() );
2183         GlobalCommands_insert( "RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2184
2185         GlobalPreferenceSystem().registerPreference( "LastMap", CopiedStringImportStringCaller( g_strLastMap ), CopiedStringExportStringCaller( g_strLastMap ) );
2186         GlobalPreferenceSystem().registerPreference( "LoadLastMap", BoolImportStringCaller( g_bLoadLastMap ), BoolExportStringCaller( g_bLoadLastMap ) );
2187         GlobalPreferenceSystem().registerPreference( "MapInfoDlg", WindowPositionImportStringCaller( g_posMapInfoWnd ), WindowPositionExportStringCaller( g_posMapInfoWnd ) );
2188
2189         PreferencesDialog_addSettingsPreferences( FreeCaller1<PreferencesPage&, Map_constructPreferences>() );
2190
2191         GlobalEntityClassManager().attach( g_MapEntityClasses );
2192         Radiant_attachHomePathsObserver( g_MapModuleObserver );
2193 }
2194
2195 void Map_Destroy(){
2196         Radiant_detachHomePathsObserver( g_MapModuleObserver );
2197         GlobalEntityClassManager().detach( g_MapEntityClasses );
2198 }