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