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