]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
Radiant:
[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().getGameDescriptionKeyValue( "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 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1599 {
1600         scene::Node& m_parent;
1601         mutable bool m_emptyOldParent;
1602
1603 public:
1604 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
1605 }
1606 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1607         if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
1608                 Selectable* selectable = Instance_getSelectable( instance );
1609                 if ( selectable && selectable->isSelected() && path.size() > 1 ) {
1610                         return false;
1611                 }
1612         }
1613         return true;
1614 }
1615 void post( const scene::Path& path, scene::Instance& instance ) const {
1616         if ( path.top().get_pointer() == &m_parent )
1617                 return;
1618
1619         if ( Node_isPrimitive( path.top() ) ){
1620                 m_emptyOldParent = false;
1621                 Selectable* selectable = Instance_getSelectable( instance );
1622
1623                 if ( selectable && selectable->isSelected() && path.size() > 1 ){
1624                         scene::Node& parent = path.parent();
1625                         if ( &parent != &m_parent ){
1626                                 NodeSmartReference node( path.top().get() );
1627                                 scene::Traversable* traversable_parent = Node_getTraversable( parent );
1628                                 traversable_parent->erase( node );
1629                                 Node_getTraversable( m_parent )->insert( node );
1630                                 if ( traversable_parent->empty() )
1631                                         m_emptyOldParent = true;
1632                         }
1633                 }
1634         }
1635         else if ( m_emptyOldParent ){
1636                 m_emptyOldParent = false;
1637                 // delete empty entities
1638                 Entity* entity = Node_getEntity( path.top() );
1639                 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )     && Node_getTraversable( path.top() )->empty() ) {
1640                         Path_deleteTop( path );
1641                 }
1642         }
1643 }
1644 };
1645
1646 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1647         graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1648 }
1649
1650 class CountSelectedBrushes : public scene::Graph::Walker
1651 {
1652 std::size_t& m_count;
1653 mutable std::size_t m_depth;
1654 public:
1655 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1656         m_count = 0;
1657 }
1658 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1659         if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1660                 return false;
1661         }
1662         Selectable* selectable = Instance_getSelectable( instance );
1663         if ( selectable != 0
1664                  && selectable->isSelected()
1665                  && Node_isPrimitive( path.top() ) ) {
1666                 ++m_count;
1667         }
1668         return true;
1669 }
1670 void post( const scene::Path& path, scene::Instance& instance ) const {
1671         --m_depth;
1672 }
1673 };
1674
1675 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1676         std::size_t count;
1677         graph.traverse( CountSelectedBrushes( count ) );
1678         return count;
1679 }
1680
1681 enum ENodeType
1682 {
1683         eNodeUnknown,
1684         eNodeMap,
1685         eNodeEntity,
1686         eNodePrimitive,
1687 };
1688
1689 const char* nodetype_get_name( ENodeType type ){
1690         if ( type == eNodeMap ) {
1691                 return "map";
1692         }
1693         if ( type == eNodeEntity ) {
1694                 return "entity";
1695         }
1696         if ( type == eNodePrimitive ) {
1697                 return "primitive";
1698         }
1699         return "unknown";
1700 }
1701
1702 ENodeType node_get_nodetype( scene::Node& node ){
1703         if ( Node_isEntity( node ) ) {
1704                 return eNodeEntity;
1705         }
1706         if ( Node_isPrimitive( node ) ) {
1707                 return eNodePrimitive;
1708         }
1709         return eNodeUnknown;
1710 }
1711
1712 bool contains_entity( scene::Node& node ){
1713         return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1714 }
1715
1716 bool contains_primitive( scene::Node& node ){
1717         return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1718 }
1719
1720 ENodeType node_get_contains( scene::Node& node ){
1721         if ( contains_entity( node ) ) {
1722                 return eNodeEntity;
1723         }
1724         if ( contains_primitive( node ) ) {
1725                 return eNodePrimitive;
1726         }
1727         return eNodeUnknown;
1728 }
1729
1730 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1731         ENodeType contains = node_get_contains( parent.top() );
1732         ENodeType type = node_get_nodetype( child.top() );
1733
1734         if ( contains != eNodeUnknown && contains == type ) {
1735                 NodeSmartReference node( child.top().get() );
1736                 Path_deleteTop( child );
1737                 Node_getTraversable( parent.top() )->insert( node );
1738                 SceneChangeNotify();
1739         }
1740         else
1741         {
1742                 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1743         }
1744 }
1745
1746 void Scene_parentSelected(){
1747         UndoableCommand undo( "parentSelected" );
1748
1749         if ( GlobalSelectionSystem().countSelected() > 1 ) {
1750                 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1751                 {
1752                 const scene::Path& m_parent;
1753 public:
1754                 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1755                 }
1756                 void visit( scene::Instance& instance ) const {
1757                         if ( &m_parent != &instance.path() ) {
1758                                 Path_parent( m_parent, instance.path() );
1759                         }
1760                 }
1761                 };
1762
1763                 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1764                 GlobalSelectionSystem().foreachSelected( visitor );
1765         }
1766         else
1767         {
1768                 globalOutputStream() << "failed - did not find two selected nodes.\n";
1769         }
1770 }
1771
1772
1773
1774 void NewMap(){
1775         if ( ConfirmModified( "New Map" ) ) {
1776                 Map_RegionOff();
1777                 Map_Free();
1778                 Map_New();
1779         }
1780 }
1781
1782 CopiedString g_mapsPath;
1783
1784 const char* getMapsPath(){
1785         return g_mapsPath.c_str();
1786 }
1787
1788 const char* map_open( const char* title ){
1789         return file_dialog( GTK_WIDGET( MainFrame_getWindow() ), TRUE, title, getMapsPath(), MapFormat::Name(), true, false, false );
1790 }
1791
1792 const char* map_import( const char* title ){
1793         return file_dialog( GTK_WIDGET( MainFrame_getWindow() ), TRUE, title, getMapsPath(), MapFormat::Name(), false, true, false );
1794 }
1795
1796 const char* map_save( const char* title ){
1797         return file_dialog( GTK_WIDGET( MainFrame_getWindow() ), FALSE, title, getMapsPath(), MapFormat::Name(), false, false, true );
1798 }
1799
1800 void OpenMap(){
1801         if ( !ConfirmModified( "Open Map" ) ) {
1802                 return;
1803         }
1804
1805         const char* filename = map_open( "Open Map" );
1806
1807         if ( filename != 0 ) {
1808                 MRU_AddFile( filename );
1809                 Map_RegionOff();
1810                 Map_Free();
1811                 Map_LoadFile( filename );
1812         }
1813 }
1814
1815 void ImportMap(){
1816         const char* filename = map_import( "Import Map" );
1817
1818         if ( filename != 0 ) {
1819                 UndoableCommand undo( "mapImport" );
1820                 Map_ImportFile( filename );
1821         }
1822 }
1823
1824 bool Map_SaveAs(){
1825         const char* filename = map_save( "Save Map" );
1826
1827         if ( filename != 0 ) {
1828                 MRU_AddFile( filename );
1829                 Map_Rename( filename );
1830                 return Map_Save();
1831         }
1832         return false;
1833 }
1834
1835 void SaveMapAs(){
1836         Map_SaveAs();
1837 }
1838
1839 void SaveMap(){
1840         if ( Map_Unnamed( g_map ) ) {
1841                 SaveMapAs();
1842         }
1843         else if ( Map_Modified( g_map ) ) {
1844                 Map_Save();
1845         }
1846 }
1847
1848 void ExportMap(){
1849         const char* filename = map_save( "Export Selection" );
1850
1851         if ( filename != 0 ) {
1852                 Map_SaveSelected( filename );
1853         }
1854 }
1855
1856 void SaveRegion(){
1857         const char* filename = map_save( "Export Region" );
1858
1859         if ( filename != 0 ) {
1860                 Map_SaveRegion( filename );
1861         }
1862 }
1863
1864
1865 void RegionOff(){
1866         Map_RegionOff();
1867         SceneChangeNotify();
1868 }
1869
1870 void RegionXY(){
1871         Map_RegionXY(
1872                 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1873                 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1874                 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1875                 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1876                 );
1877         SceneChangeNotify();
1878 }
1879
1880 void RegionBrush(){
1881         Map_RegionBrush();
1882         SceneChangeNotify();
1883 }
1884
1885 void RegionSelected(){
1886         Map_RegionSelectedBrushes();
1887         SceneChangeNotify();
1888 }
1889
1890
1891
1892
1893
1894 class BrushFindByIndexWalker : public scene::Traversable::Walker
1895 {
1896 mutable std::size_t m_index;
1897 scene::Path& m_path;
1898 public:
1899 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
1900         : m_index( index ), m_path( path ){
1901 }
1902 bool pre( scene::Node& node ) const {
1903         if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
1904                 m_path.push( makeReference( node ) );
1905         }
1906         return false;
1907 }
1908 };
1909
1910 class EntityFindByIndexWalker : public scene::Traversable::Walker
1911 {
1912 mutable std::size_t m_index;
1913 scene::Path& m_path;
1914 public:
1915 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
1916         : m_index( index ), m_path( path ){
1917 }
1918 bool pre( scene::Node& node ) const {
1919         if ( Node_isEntity( node ) && m_index-- == 0 ) {
1920                 m_path.push( makeReference( node ) );
1921         }
1922         return false;
1923 }
1924 };
1925
1926 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
1927         path.push( makeReference( GlobalSceneGraph().root() ) );
1928         {
1929                 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
1930         }
1931         if ( path.size() == 2 ) {
1932                 scene::Traversable* traversable = Node_getTraversable( path.top() );
1933                 if ( traversable != 0 ) {
1934                         traversable->traverse( BrushFindByIndexWalker( brush, path ) );
1935                 }
1936         }
1937 }
1938
1939 inline bool Node_hasChildren( scene::Node& node ){
1940         scene::Traversable* traversable = Node_getTraversable( node );
1941         return traversable != 0 && !traversable->empty();
1942 }
1943
1944 void SelectBrush( int entitynum, int brushnum ){
1945         scene::Path path;
1946         Scene_FindEntityBrush( entitynum, brushnum, path );
1947         if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
1948                 scene::Instance* instance = GlobalSceneGraph().find( path );
1949                 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
1950                 Selectable* selectable = Instance_getSelectable( *instance );
1951                 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
1952                 selectable->setSelected( true );
1953                 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
1954         }
1955 }
1956
1957
1958 class BrushFindIndexWalker : public scene::Graph::Walker
1959 {
1960 mutable const scene::Node* m_node;
1961 std::size_t& m_count;
1962 public:
1963 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
1964         : m_node( &node ), m_count( count ){
1965 }
1966 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1967         if ( Node_isPrimitive( path.top() ) ) {
1968                 if ( m_node == path.top().get_pointer() ) {
1969                         m_node = 0;
1970                 }
1971                 if ( m_node ) {
1972                         ++m_count;
1973                 }
1974         }
1975         return true;
1976 }
1977 };
1978
1979 class EntityFindIndexWalker : public scene::Graph::Walker
1980 {
1981 mutable const scene::Node* m_node;
1982 std::size_t& m_count;
1983 public:
1984 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
1985         : m_node( &node ), m_count( count ){
1986 }
1987 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1988         if ( Node_isEntity( path.top() ) ) {
1989                 if ( m_node == path.top().get_pointer() ) {
1990                         m_node = 0;
1991                 }
1992                 if ( m_node ) {
1993                         ++m_count;
1994                 }
1995         }
1996         return true;
1997 }
1998 };
1999
2000 static void GetSelectionIndex( int *ent, int *brush ){
2001         std::size_t count_brush = 0;
2002         std::size_t count_entity = 0;
2003         if ( GlobalSelectionSystem().countSelected() != 0 ) {
2004                 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2005
2006                 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2007                 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2008         }
2009         *brush = int(count_brush);
2010         *ent = int(count_entity);
2011 }
2012
2013 void DoFind(){
2014         ModalDialog dialog;
2015         GtkEntry* entity;
2016         GtkEntry* brush;
2017
2018         GtkWindow* window = create_dialog_window( MainFrame_getWindow(), "Find Brush", G_CALLBACK( dialog_delete_callback ), &dialog );
2019
2020         GtkAccelGroup* accel = gtk_accel_group_new();
2021         gtk_window_add_accel_group( window, accel );
2022
2023         {
2024                 GtkVBox* vbox = create_dialog_vbox( 4, 4 );
2025                 gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( vbox ) );
2026                 {
2027                         GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
2028                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
2029                         {
2030                                 GtkWidget* label = gtk_label_new( "Entity number" );
2031                                 gtk_widget_show( label );
2032                                 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
2033                                                                   (GtkAttachOptions) ( 0 ),
2034                                                                   (GtkAttachOptions) ( 0 ), 0, 0 );
2035                         }
2036                         {
2037                                 GtkWidget* label = gtk_label_new( "Brush number" );
2038                                 gtk_widget_show( label );
2039                                 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
2040                                                                   (GtkAttachOptions) ( 0 ),
2041                                                                   (GtkAttachOptions) ( 0 ), 0, 0 );
2042                         }
2043                         {
2044                                 GtkEntry* entry = GTK_ENTRY( gtk_entry_new() );
2045                                 gtk_widget_show( GTK_WIDGET( entry ) );
2046                                 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
2047                                                                   (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2048                                                                   (GtkAttachOptions) ( 0 ), 0, 0 );
2049                                 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
2050                                 entity = entry;
2051                         }
2052                         {
2053                                 GtkEntry* entry = GTK_ENTRY( gtk_entry_new() );
2054                                 gtk_widget_show( GTK_WIDGET( entry ) );
2055                                 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
2056                                                                   (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2057                                                                   (GtkAttachOptions) ( 0 ), 0, 0 );
2058
2059                                 brush = entry;
2060                         }
2061                 }
2062                 {
2063                         GtkHBox* hbox = create_dialog_hbox( 4 );
2064                         gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), TRUE, TRUE, 0 );
2065                         {
2066                                 GtkButton* button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2067                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
2068                                 widget_make_default( GTK_WIDGET( button ) );
2069                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2070                         }
2071                         {
2072                                 GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2073                                 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
2074                                 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2075                         }
2076                 }
2077         }
2078
2079         // Initialize dialog
2080         char buf[16];
2081         int ent, br;
2082
2083         GetSelectionIndex( &ent, &br );
2084         sprintf( buf, "%i", ent );
2085         gtk_entry_set_text( entity, buf );
2086         sprintf( buf, "%i", br );
2087         gtk_entry_set_text( brush, buf );
2088
2089         if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2090                 const char *entstr = gtk_entry_get_text( entity );
2091                 const char *brushstr = gtk_entry_get_text( brush );
2092                 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2093         }
2094
2095         gtk_widget_destroy( GTK_WIDGET( window ) );
2096 }
2097
2098 void Map_constructPreferences( PreferencesPage& page ){
2099         page.appendCheckBox( "", "Load last map on open", g_bLoadLastMap );
2100 }
2101
2102
2103 class MapEntityClasses : public ModuleObserver
2104 {
2105 std::size_t m_unrealised;
2106 public:
2107 MapEntityClasses() : m_unrealised( 1 ){
2108 }
2109 void realise(){
2110         if ( --m_unrealised == 0 ) {
2111                 if ( g_map.m_resource != 0 ) {
2112                         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2113                         g_map.m_resource->realise();
2114                 }
2115         }
2116 }
2117 void unrealise(){
2118         if ( ++m_unrealised == 1 ) {
2119                 if ( g_map.m_resource != 0 ) {
2120                         g_map.m_resource->flush();
2121                         g_map.m_resource->unrealise();
2122                 }
2123         }
2124 }
2125 };
2126
2127 MapEntityClasses g_MapEntityClasses;
2128
2129
2130 class MapModuleObserver : public ModuleObserver
2131 {
2132 std::size_t m_unrealised;
2133 public:
2134 MapModuleObserver() : m_unrealised( 1 ){
2135 }
2136 void realise(){
2137         if ( --m_unrealised == 0 ) {
2138                 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2139                 StringOutputStream buffer( 256 );
2140                 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2141                 Q_mkdir( buffer.c_str() );
2142                 g_mapsPath = buffer.c_str();
2143         }
2144 }
2145 void unrealise(){
2146         if ( ++m_unrealised == 1 ) {
2147                 g_mapsPath = "";
2148         }
2149 }
2150 };
2151
2152 MapModuleObserver g_MapModuleObserver;
2153
2154 #include "preferencesystem.h"
2155
2156 CopiedString g_strLastMap;
2157 bool g_bLoadLastMap = false;
2158
2159 void Map_Construct(){
2160         GlobalCommands_insert( "RegionOff", FreeCaller<RegionOff>() );
2161         GlobalCommands_insert( "RegionSetXY", FreeCaller<RegionXY>() );
2162         GlobalCommands_insert( "RegionSetBrush", FreeCaller<RegionBrush>() );
2163         GlobalCommands_insert( "RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2164
2165         GlobalPreferenceSystem().registerPreference( "LastMap", CopiedStringImportStringCaller( g_strLastMap ), CopiedStringExportStringCaller( g_strLastMap ) );
2166         GlobalPreferenceSystem().registerPreference( "LoadLastMap", BoolImportStringCaller( g_bLoadLastMap ), BoolExportStringCaller( g_bLoadLastMap ) );
2167         GlobalPreferenceSystem().registerPreference( "MapInfoDlg", WindowPositionImportStringCaller( g_posMapInfoWnd ), WindowPositionExportStringCaller( g_posMapInfoWnd ) );
2168
2169         PreferencesDialog_addSettingsPreferences( FreeCaller1<PreferencesPage&, Map_constructPreferences>() );
2170
2171         GlobalEntityClassManager().attach( g_MapEntityClasses );
2172         Radiant_attachHomePathsObserver( g_MapModuleObserver );
2173 }
2174
2175 void Map_Destroy(){
2176         Radiant_detachHomePathsObserver( g_MapModuleObserver );
2177         GlobalEntityClassManager().detach( g_MapEntityClasses );
2178 }