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