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