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