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