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