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