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