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