]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
Merge commit '0709fce07d9c630ca0455ebeb58e3806427ca8ce' into garux-merge
[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         {
970                 ScopeTimer timer( "map load" );
971
972                 const MapFormat* format = NULL;
973                 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
974                 if ( string_not_empty( moduleName ) ) {
975                         format = ReferenceAPI_getMapModules().findModule( moduleName );
976                 }
977
978                 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
979                 {
980                         if ( i ) {
981                                 Map_Free();
982                         }
983                         Brush_toggleFormat( i );
984                         Map_UpdateTitle( g_map );
985
986                         g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
987                         if ( format ) {
988                                 format->wrongFormat = false;
989                         }
990                         g_map.m_resource->attach( g_map );
991                         if ( format ) {
992                                 if ( !format->wrongFormat ) {
993                                         break;
994                                 }
995                         }
996                 }
997
998                 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
999         }
1000
1001         globalOutputStream() << "--- LoadMapFile ---\n";
1002         globalOutputStream() << g_map.m_name.c_str() << "\n";
1003
1004         globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
1005         globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
1006
1007         //GlobalEntityCreator().printStatistics();
1008
1009         //
1010         // move the view to a start position
1011         //
1012         Map_StartPosition();
1013
1014         g_currentMap = &g_map;
1015 }
1016
1017 class Excluder
1018 {
1019 public:
1020 virtual bool excluded( scene::Node& node ) const = 0;
1021 };
1022
1023 class ExcludeWalker : public scene::Traversable::Walker
1024 {
1025 const scene::Traversable::Walker& m_walker;
1026 const Excluder* m_exclude;
1027 mutable bool m_skip;
1028 public:
1029 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1030         : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1031 }
1032
1033 bool pre( scene::Node& node ) const {
1034         if ( m_exclude->excluded( node ) || node.isRoot() ) {
1035                 m_skip = true;
1036                 return false;
1037         }
1038         else
1039         {
1040                 m_walker.pre( node );
1041         }
1042         return true;
1043 }
1044
1045 void post( scene::Node& node ) const {
1046         if ( m_skip ) {
1047                 m_skip = false;
1048         }
1049         else
1050         {
1051                 m_walker.post( node );
1052         }
1053 }
1054 };
1055
1056 class AnyInstanceSelected : public scene::Instantiable::Visitor
1057 {
1058 bool& m_selected;
1059 public:
1060 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1061         m_selected = false;
1062 }
1063
1064 void visit( scene::Instance& instance ) const {
1065         Selectable* selectable = Instance_getSelectable( instance );
1066         if ( selectable != 0
1067                  && selectable->isSelected() ) {
1068                 m_selected = true;
1069         }
1070 }
1071 };
1072
1073 bool Node_instanceSelected( scene::Node& node ){
1074         scene::Instantiable* instantiable = Node_getInstantiable( node );
1075         ASSERT_NOTNULL( instantiable );
1076         bool selected;
1077         instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1078         return selected;
1079 }
1080
1081 class SelectedDescendantWalker : public scene::Traversable::Walker
1082 {
1083 bool& m_selected;
1084 public:
1085 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1086         m_selected = false;
1087 }
1088
1089 bool pre( scene::Node& node ) const {
1090         if ( node.isRoot() ) {
1091                 return false;
1092         }
1093
1094         if ( Node_instanceSelected( node ) ) {
1095                 m_selected = true;
1096         }
1097
1098         return true;
1099 }
1100 };
1101
1102 bool Node_selectedDescendant( scene::Node& node ){
1103         bool selected;
1104         Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1105         return selected;
1106 }
1107
1108 class SelectionExcluder : public Excluder
1109 {
1110 public:
1111 bool excluded( scene::Node& node ) const {
1112         return !Node_selectedDescendant( node );
1113 }
1114 };
1115
1116 class IncludeSelectedWalker : public scene::Traversable::Walker
1117 {
1118 const scene::Traversable::Walker& m_walker;
1119 mutable std::size_t m_selected;
1120 mutable bool m_skip;
1121
1122 bool selectedParent() const {
1123         return m_selected != 0;
1124 }
1125
1126 public:
1127 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1128         : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1129 }
1130
1131 bool pre( scene::Node& node ) const {
1132         // include node if:
1133         // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1134         if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1135                 if ( Node_instanceSelected( node ) ) {
1136                         ++m_selected;
1137                 }
1138                 m_walker.pre( node );
1139                 return true;
1140         }
1141         else
1142         {
1143                 m_skip = true;
1144                 return false;
1145         }
1146 }
1147
1148 void post( scene::Node& node ) const {
1149         if ( m_skip ) {
1150                 m_skip = false;
1151         }
1152         else
1153         {
1154                 if ( Node_instanceSelected( node ) ) {
1155                         --m_selected;
1156                 }
1157                 m_walker.post( node );
1158         }
1159 }
1160 };
1161
1162 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1163         scene::Traversable* traversable = Node_getTraversable( root );
1164         if ( traversable != 0 ) {
1165 #if 0
1166                 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1167 #else
1168                 traversable->traverse( IncludeSelectedWalker( walker ) );
1169 #endif
1170         }
1171 }
1172
1173 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1174         format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
1175 }
1176
1177 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1178         scene::Traversable* traversable = Node_getTraversable( root );
1179         if ( traversable != 0 ) {
1180                 traversable->traverse( walker );
1181         }
1182 }
1183
1184 class RegionExcluder : public Excluder
1185 {
1186 public:
1187 bool excluded( scene::Node& node ) const {
1188         return node.excluded();
1189 }
1190 };
1191
1192 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1193         scene::Traversable* traversable = Node_getTraversable( root );
1194         if ( traversable != 0 ) {
1195                 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1196         }
1197 }
1198
1199 bool Map_SaveRegion( const char *filename ){
1200         AddRegionBrushes();
1201
1202         bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1203
1204         RemoveRegionBrushes();
1205
1206         return success;
1207 }
1208
1209
1210 void Map_RenameAbsolute( const char* absolute ){
1211         Resource* resource = GlobalReferenceCache().capture( absolute );
1212         NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1213         resource->setNode( clone.get_pointer() );
1214
1215         {
1216                 //ScopeTimer timer("clone subgraph");
1217                 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1218         }
1219
1220         g_map.m_resource->detach( g_map );
1221         GlobalReferenceCache().release( g_map.m_name.c_str() );
1222
1223         g_map.m_resource = resource;
1224
1225         g_map.m_name = absolute;
1226         Map_UpdateTitle( g_map );
1227
1228         g_map.m_resource->attach( g_map );
1229         // refresh VFS to apply new pak filtering based on mapname
1230         // needed for daemon DPK VFS
1231         VFS_Refresh();
1232 }
1233
1234 void Map_Rename( const char* filename ){
1235         if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1236                 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1237
1238                 Map_RenameAbsolute( filename );
1239
1240                 SceneChangeNotify();
1241         }
1242         else
1243         {
1244                 SaveReferences();
1245         }
1246 }
1247
1248 bool Map_Save(){
1249         Pointfile_Clear();
1250
1251         ScopeTimer timer( "map save" );
1252         SaveReferences();
1253         return true; // assume success..
1254 }
1255
1256 /*
1257    ===========
1258    Map_New
1259
1260    ===========
1261  */
1262 void Map_New(){
1263         //globalOutputStream() << "Map_New\n";
1264
1265         g_map.m_name = "unnamed.map";
1266         Map_UpdateTitle( g_map );
1267
1268         {
1269                 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1270 //    ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1271                 g_map.m_resource->attach( g_map );
1272
1273                 SceneChangeNotify();
1274         }
1275
1276         FocusViews( g_vector3_identity, 0 );
1277
1278         g_currentMap = &g_map;
1279
1280         // restart VFS to apply new pak filtering based on mapname
1281         // needed for daemon DPK VFS
1282         VFS_Restart();
1283 }
1284
1285 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 &region_mins, const Vector3 &region_maxs );
1286
1287 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1288         /*!
1289            \todo we need to make sure that the player start IS inside the region and bail out if it's not
1290            the compiler will refuse to compile a map with a player_start somewhere in empty space..
1291            for now, let's just print an error
1292          */
1293
1294         Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1295
1296         for ( int i = 0 ; i < 3 ; i++ )
1297         {
1298                 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1299                         globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1300                         break;
1301                 }
1302         }
1303
1304         // write the info_playerstart
1305         char sTmp[1024];
1306         sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1307         Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1308         sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1309         Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1310 }
1311
1312 /*
1313    ===========================================================
1314
1315    REGION
1316
1317    ===========================================================
1318  */
1319 bool region_active = false;
1320
1321 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_region_caller( region_active );
1322
1323 ToggleItem g_region_item( g_region_caller );
1324
1325 /*void Map_ToggleRegion(){
1326         region_active = !region_active;
1327         g_region_item.update();
1328 }*/
1329
1330 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1331 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1332
1333 scene::Node* region_sides[6];
1334 scene::Node* region_startpoint = 0;
1335
1336 /*
1337    ===========
1338    AddRegionBrushes
1339    a regioned map will have temp walls put up at the region boundary
1340    \todo TODO TTimo old implementation of region brushes
1341    we still add them straight in the worldspawn and take them out after the map is saved
1342    with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1343    ===========
1344  */
1345 void AddRegionBrushes( void ){
1346         int i;
1347
1348         for ( i = 0; i < 6; i++ )
1349         {
1350                 region_sides[i] = &GlobalBrushCreator().createBrush();
1351                 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1352         }
1353
1354         region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1355
1356         ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1357         ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1358
1359         Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1360 }
1361
1362 void RemoveRegionBrushes( void ){
1363         for ( std::size_t i = 0; i < 6; i++ )
1364         {
1365                 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1366         }
1367         Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1368 }
1369
1370 inline void exclude_node( scene::Node& node, bool exclude ){
1371         exclude
1372         ? node.enable( scene::Node::eExcluded )
1373         : node.disable( scene::Node::eExcluded );
1374 }
1375
1376 class ExcludeAllWalker : public scene::Graph::Walker
1377 {
1378 bool m_exclude;
1379 public:
1380 ExcludeAllWalker( bool exclude )
1381         : m_exclude( exclude ){
1382 }
1383
1384 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1385         exclude_node( path.top(), m_exclude );
1386
1387         return true;
1388 }
1389 };
1390
1391 void Scene_Exclude_All( bool exclude ){
1392         GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1393 }
1394
1395 bool Instance_isSelected( const scene::Instance& instance ){
1396         const Selectable* selectable = Instance_getSelectable( instance );
1397         return selectable != 0 && selectable->isSelected();
1398 }
1399
1400 class ExcludeSelectedWalker : public scene::Graph::Walker
1401 {
1402 bool m_exclude;
1403 public:
1404 ExcludeSelectedWalker( bool exclude )
1405         : m_exclude( exclude ){
1406 }
1407
1408 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1409         exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1410         return true;
1411 }
1412 };
1413
1414 void Scene_Exclude_Selected( bool exclude ){
1415         GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1416 }
1417
1418 class ExcludeRegionedWalker : public scene::Graph::Walker
1419 {
1420 bool m_exclude;
1421 public:
1422 ExcludeRegionedWalker( bool exclude )
1423         : m_exclude( exclude ){
1424 }
1425
1426 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1427         exclude_node(
1428                 path.top(),
1429                 !(
1430                         (
1431                                 aabb_intersects_aabb(
1432                                         instance.worldAABB(),
1433                                         aabb_for_minmax( region_mins, region_maxs )
1434                                         ) != 0
1435                         ) ^ m_exclude
1436                         )
1437                 );
1438
1439         return true;
1440 }
1441 };
1442
1443 void Scene_Exclude_Region( bool exclude ){
1444         GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1445 }
1446
1447 /*
1448    ===========
1449    Map_RegionOff
1450
1451    Other filtering options may still be on
1452    ===========
1453  */
1454 void Map_RegionOff(){
1455         region_active = false;
1456         g_region_item.update();
1457
1458         region_maxs[0] = g_MaxWorldCoord - 64;
1459         region_mins[0] = g_MinWorldCoord + 64;
1460         region_maxs[1] = g_MaxWorldCoord - 64;
1461         region_mins[1] = g_MinWorldCoord + 64;
1462         region_maxs[2] = g_MaxWorldCoord - 64;
1463         region_mins[2] = g_MinWorldCoord + 64;
1464
1465         Scene_Exclude_All( false );
1466 }
1467
1468 void Map_ApplyRegion( void ){
1469         region_active = true;
1470         g_region_item.update();
1471
1472         Scene_Exclude_Region( false );
1473 }
1474
1475
1476 /*
1477    ========================
1478    Map_RegionSelectedBrushes
1479    ========================
1480  */
1481 void Map_RegionSelectedBrushes( void ){
1482         Map_RegionOff();
1483
1484         if ( GlobalSelectionSystem().countSelected() != 0
1485                  && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1486                 region_active = true;
1487                 g_region_item.update();
1488                 Select_GetBounds( region_mins, region_maxs );
1489
1490                 Scene_Exclude_Selected( false );
1491
1492                 GlobalSelectionSystem().setSelectedAll( false );
1493         }
1494 }
1495
1496
1497 /*
1498    ===========
1499    Map_RegionXY
1500    ===========
1501  */
1502 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1503         Map_RegionOff();
1504
1505         region_mins[0] = x_min;
1506         region_maxs[0] = x_max;
1507         region_mins[1] = y_min;
1508         region_maxs[1] = y_max;
1509         region_mins[2] = g_MinWorldCoord + 64;
1510         region_maxs[2] = g_MaxWorldCoord - 64;
1511
1512         Map_ApplyRegion();
1513 }
1514
1515 void Map_RegionBounds( const AABB& bounds ){
1516         Map_RegionOff();
1517
1518         region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1519         region_maxs = vector3_added( bounds.origin, bounds.extents );
1520
1521         deleteSelection();
1522
1523         Map_ApplyRegion();
1524 }
1525
1526 /*
1527    ===========
1528    Map_RegionBrush
1529    ===========
1530  */
1531 void Map_RegionBrush( void ){
1532         if ( GlobalSelectionSystem().countSelected() != 0 ) {
1533                 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1534                 Map_RegionBounds( instance.worldAABB() );
1535         }
1536 }
1537
1538 //
1539 //================
1540 //Map_ImportFile
1541 //================
1542 //
1543 bool Map_ImportFile( const char* filename ){
1544         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1545
1546         g_strLastMapFolder = g_path_get_dirname( filename );
1547
1548         bool success = false;
1549
1550         if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1551                 goto tryDecompile;
1552         }
1553
1554         {
1555                 const MapFormat* format = NULL;
1556                 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1557                 if ( string_not_empty( moduleName ) ) {
1558                         format = ReferenceAPI_getMapModules().findModule( moduleName );
1559                 }
1560
1561                 if ( format ) {
1562                         format->wrongFormat = false;
1563                 }
1564                 Resource* resource = GlobalReferenceCache().capture( filename );
1565                 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1566                 if ( !resource->load() ) {
1567                         GlobalReferenceCache().release( filename );
1568                         goto tryDecompile;
1569                 }
1570                 if ( format ) {
1571                         if ( format->wrongFormat ) {
1572                                 GlobalReferenceCache().release( filename );
1573                                 goto tryDecompile;
1574                         }
1575                 }
1576                 NodeSmartReference clone( NewMapRoot( "" ) );
1577                 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1578                 Map_gatherNamespaced( clone );
1579                 Map_mergeClonedNames();
1580                 MergeMap( clone );
1581                 success = true;
1582                 GlobalReferenceCache().release( filename );
1583         }
1584
1585         SceneChangeNotify();
1586
1587         return success;
1588
1589 tryDecompile:
1590
1591         const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
1592         int n = string_length( path_get_extension( filename ) );
1593         if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1594                 StringBuffer output;
1595                 output.push_string( AppPath_get() );
1596                 output.push_string( "q3map2." );
1597                 output.push_string( RADIANT_EXECUTABLE );
1598                 output.push_string( " -v -game " );
1599                 output.push_string( ( type && *type ) ? type : "quake3" );
1600                 output.push_string( " -fs_basepath \"" );
1601                 output.push_string( EnginePath_get() );
1602                 output.push_string( "\" -fs_homepath \"" );
1603                 output.push_string( g_qeglobals.m_userEnginePath.c_str() );
1604                 output.push_string( "\"" );
1605
1606                 // extra pakpaths
1607                 for ( int i = 0; i < g_pakPathCount; i++ ) {
1608                         if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
1609                                 output.push_string( " -fs_pakpath \"" );
1610                                 output.push_string( g_strPakPath[i].c_str() );
1611                                 output.push_string( "\"" );
1612                         }
1613                 }
1614
1615                 // extra switches
1616                 if ( g_disableEnginePath ) {
1617                         output.push_string( " -fs_nobasepath " );
1618                 }
1619
1620                 if ( g_disableHomePath ) {
1621                         output.push_string( " -fs_nohomepath " );
1622                 }
1623
1624                 output.push_string( " -fs_game " );
1625                 output.push_string( gamename_get() );
1626                 output.push_string( " -convert -format " );
1627                 output.push_string( Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map" );
1628                 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1629                         output.push_string( " -readmap " );
1630                 }
1631                 output.push_string( " \"" );
1632                 output.push_string( filename );
1633                 output.push_string( "\"" );
1634
1635                 // run
1636                 Q_Exec( NULL, output.c_str(), NULL, false, true );
1637
1638                 // rebuild filename as "filenamewithoutext_converted.map"
1639                 output.clear();
1640                 output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
1641                 output.push_string( "_converted.map" );
1642                 filename = output.c_str();
1643
1644                 // open
1645                 Resource* resource = GlobalReferenceCache().capture( filename );
1646                 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1647                 if ( !resource->load() ) {
1648                         GlobalReferenceCache().release( filename );
1649                         goto tryDecompile;
1650                 }
1651                 NodeSmartReference clone( NewMapRoot( "" ) );
1652                 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1653                 Map_gatherNamespaced( clone );
1654                 Map_mergeClonedNames();
1655                 MergeMap( clone );
1656                 success = true;
1657                 GlobalReferenceCache().release( filename );
1658         }
1659
1660         SceneChangeNotify();
1661         return success;
1662 }
1663
1664 /*
1665    ===========
1666    Map_SaveFile
1667    ===========
1668  */
1669 bool Map_SaveFile( const char* filename ){
1670         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1671         bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1672         if ( success ) {
1673                 // refresh VFS to apply new pak filtering based on mapname
1674                 // needed for daemon DPK VFS
1675                 VFS_Refresh();
1676         }
1677         return success;
1678 }
1679
1680 //
1681 //===========
1682 //Map_SaveSelected
1683 //===========
1684 //
1685 // Saves selected world brushes and whole entities with partial/full selections
1686 //
1687 bool Map_SaveSelected( const char* filename ){
1688         return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1689 }
1690
1691 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1692 {
1693         scene::Node& m_parent;
1694         mutable bool m_emptyOldParent;
1695
1696 public:
1697 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
1698 }
1699
1700 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1701         if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
1702                 Selectable* selectable = Instance_getSelectable( instance );
1703                 if ( selectable && selectable->isSelected() && path.size() > 1 ) {
1704                         return false;
1705                 }
1706         }
1707         return true;
1708 }
1709
1710 void post( const scene::Path& path, scene::Instance& instance ) const {
1711         if ( path.top().get_pointer() == &m_parent )
1712                 return;
1713
1714         if ( Node_isPrimitive( path.top() ) ){
1715                 m_emptyOldParent = false;
1716                 Selectable* selectable = Instance_getSelectable( instance );
1717
1718                 if ( selectable && selectable->isSelected() && path.size() > 1 ){
1719                         scene::Node& parent = path.parent();
1720                         if ( &parent != &m_parent ){
1721                                 NodeSmartReference node( path.top().get() );
1722                                 scene::Traversable* traversable_parent = Node_getTraversable( parent );
1723                                 traversable_parent->erase( node );
1724                                 Node_getTraversable( m_parent )->insert( node );
1725                                 if ( traversable_parent->empty() )
1726                                         m_emptyOldParent = true;
1727                         }
1728                 }
1729         }
1730         else if ( m_emptyOldParent ){
1731                 m_emptyOldParent = false;
1732                 // delete empty entities
1733                 Entity* entity = Node_getEntity( path.top() );
1734                 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )     && Node_getTraversable( path.top() )->empty() ) {
1735                         Path_deleteTop( path );
1736                 }
1737         }
1738 }
1739 };
1740
1741 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1742         graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1743 }
1744
1745 class CountSelectedBrushes : public scene::Graph::Walker
1746 {
1747 std::size_t& m_count;
1748 mutable std::size_t m_depth;
1749 public:
1750 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1751         m_count = 0;
1752 }
1753
1754 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1755         if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1756                 return false;
1757         }
1758         Selectable* selectable = Instance_getSelectable( instance );
1759         if ( selectable != 0
1760                  && selectable->isSelected()
1761                  && Node_isPrimitive( path.top() ) ) {
1762                 ++m_count;
1763         }
1764         return true;
1765 }
1766
1767 void post( const scene::Path& path, scene::Instance& instance ) const {
1768         --m_depth;
1769 }
1770 };
1771
1772 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1773         std::size_t count;
1774         graph.traverse( CountSelectedBrushes( count ) );
1775         return count;
1776 }
1777
1778 enum ENodeType
1779 {
1780         eNodeUnknown,
1781         eNodeMap,
1782         eNodeEntity,
1783         eNodePrimitive,
1784 };
1785
1786 const char* nodetype_get_name( ENodeType type ){
1787         if ( type == eNodeMap ) {
1788                 return "map";
1789         }
1790         if ( type == eNodeEntity ) {
1791                 return "entity";
1792         }
1793         if ( type == eNodePrimitive ) {
1794                 return "primitive";
1795         }
1796         return "unknown";
1797 }
1798
1799 ENodeType node_get_nodetype( scene::Node& node ){
1800         if ( Node_isEntity( node ) ) {
1801                 return eNodeEntity;
1802         }
1803         if ( Node_isPrimitive( node ) ) {
1804                 return eNodePrimitive;
1805         }
1806         return eNodeUnknown;
1807 }
1808
1809 bool contains_entity( scene::Node& node ){
1810         return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1811 }
1812
1813 bool contains_primitive( scene::Node& node ){
1814         return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1815 }
1816
1817 ENodeType node_get_contains( scene::Node& node ){
1818         if ( contains_entity( node ) ) {
1819                 return eNodeEntity;
1820         }
1821         if ( contains_primitive( node ) ) {
1822                 return eNodePrimitive;
1823         }
1824         return eNodeUnknown;
1825 }
1826
1827 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1828         ENodeType contains = node_get_contains( parent.top() );
1829         ENodeType type = node_get_nodetype( child.top() );
1830
1831         if ( contains != eNodeUnknown && contains == type ) {
1832                 NodeSmartReference node( child.top().get() );
1833                 Path_deleteTop( child );
1834                 Node_getTraversable( parent.top() )->insert( node );
1835                 SceneChangeNotify();
1836         }
1837         else
1838         {
1839                 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1840         }
1841 }
1842
1843 void Scene_parentSelected(){
1844         UndoableCommand undo( "parentSelected" );
1845
1846         if ( GlobalSelectionSystem().countSelected() > 1 ) {
1847                 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1848                 {
1849                 const scene::Path& m_parent;
1850 public:
1851                 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1852                 }
1853
1854                 void visit( scene::Instance& instance ) const {
1855                         if ( &m_parent != &instance.path() ) {
1856                                 Path_parent( m_parent, instance.path() );
1857                         }
1858                 }
1859                 };
1860
1861                 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1862                 GlobalSelectionSystem().foreachSelected( visitor );
1863         }
1864         else
1865         {
1866                 globalOutputStream() << "failed - did not find two selected nodes.\n";
1867         }
1868 }
1869
1870
1871 void NewMap(){
1872         if ( ConfirmModified( "New Map" ) ) {
1873                 Map_RegionOff();
1874                 Map_Free();
1875                 Map_New();
1876         }
1877 }
1878
1879 CopiedString g_mapsPath;
1880
1881 const char* getMapsPath(){
1882         return g_mapsPath.c_str();
1883 }
1884
1885 const char* getLastMapFolderPath(){
1886         if (g_strLastMapFolder.empty()) {
1887                 GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
1888                 if (g_strLastMapFolder.empty()) {
1889                         StringOutputStream buffer( 1024 );
1890                         buffer << getMapsPath();
1891                         if ( !file_readable( buffer.c_str() ) ) {
1892                                 buffer.clear();
1893                                 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
1894                         }
1895                         g_strLastMapFolder = buffer.c_str();
1896                 }
1897         }
1898         return g_strLastMapFolder.c_str();
1899 }
1900
1901 const char* map_open( const char* title ){
1902         return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
1903 }
1904
1905 const char* map_import( const char* title ){
1906         return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
1907 }
1908
1909 const char* map_save( const char* title ){
1910         return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
1911 }
1912
1913 void OpenMap(){
1914         if ( !ConfirmModified( "Open Map" ) ) {
1915                 return;
1916         }
1917
1918         const char* filename = map_open( "Open Map" );
1919
1920         if ( filename != NULL ) {
1921                 MRU_AddFile( filename );
1922                 Map_RegionOff();
1923                 Map_Free();
1924                 Map_LoadFile( filename );
1925         }
1926 }
1927
1928 void ImportMap(){
1929         const char* filename = map_import( "Import Map" );
1930
1931         if ( filename != NULL ) {
1932                 UndoableCommand undo( "mapImport" );
1933                 Map_ImportFile( filename );
1934         }
1935 }
1936
1937 bool Map_SaveAs(){
1938         const char* filename = map_save( "Save Map" );
1939
1940         if ( filename != NULL ) {
1941                 g_strLastMapFolder = g_path_get_dirname( filename );
1942                 MRU_AddFile( filename );
1943                 Map_Rename( filename );
1944                 return Map_Save();
1945         }
1946         return false;
1947 }
1948
1949 void SaveMapAs(){
1950         Map_SaveAs();
1951 }
1952
1953 void SaveMap(){
1954         if ( Map_Unnamed( g_map ) ) {
1955                 SaveMapAs();
1956         }
1957         else if ( Map_Modified( g_map ) ) {
1958                 Map_Save();
1959                 MRU_AddFile( g_map.m_name.c_str() );    //add on saving, but not opening via cmd line: spoils the list
1960         }
1961 }
1962
1963 void ExportMap(){
1964         const char* filename = map_save( "Export Selection" );
1965
1966         if ( filename != NULL ) {
1967                 g_strLastMapFolder = g_path_get_dirname( filename );
1968                 Map_SaveSelected( filename );
1969         }
1970 }
1971
1972 void SaveRegion(){
1973         const char* filename = map_save( "Export Region" );
1974
1975         if ( filename != NULL ) {
1976                 g_strLastMapFolder = g_path_get_dirname( filename );
1977                 Map_SaveRegion( filename );
1978         }
1979 }
1980
1981
1982 void RegionOff(){
1983         Map_RegionOff();
1984         SceneChangeNotify();
1985 }
1986
1987 void RegionXY(){
1988         Map_RegionXY(
1989                 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1990                 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1991                 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1992                 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1993                 );
1994         SceneChangeNotify();
1995 }
1996
1997 void RegionBrush(){
1998         Map_RegionBrush();
1999         SceneChangeNotify();
2000 }
2001
2002 void RegionSelected(){
2003         Map_RegionSelectedBrushes();
2004         SceneChangeNotify();
2005 }
2006
2007
2008
2009
2010
2011 class BrushFindByIndexWalker : public scene::Traversable::Walker
2012 {
2013 mutable std::size_t m_index;
2014 scene::Path& m_path;
2015 public:
2016 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
2017         : m_index( index ), m_path( path ){
2018 }
2019
2020 bool pre( scene::Node& node ) const {
2021         if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
2022                 m_path.push( makeReference( node ) );
2023         }
2024         return false;
2025 }
2026 };
2027
2028 class EntityFindByIndexWalker : public scene::Traversable::Walker
2029 {
2030 mutable std::size_t m_index;
2031 scene::Path& m_path;
2032 public:
2033 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
2034         : m_index( index ), m_path( path ){
2035 }
2036
2037 bool pre( scene::Node& node ) const {
2038         if ( Node_isEntity( node ) && m_index-- == 0 ) {
2039                 m_path.push( makeReference( node ) );
2040         }
2041         return false;
2042 }
2043 };
2044
2045 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
2046         path.push( makeReference( GlobalSceneGraph().root() ) );
2047         {
2048                 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
2049         }
2050         if ( path.size() == 2 ) {
2051                 scene::Traversable* traversable = Node_getTraversable( path.top() );
2052                 if ( traversable != 0 ) {
2053                         traversable->traverse( BrushFindByIndexWalker( brush, path ) );
2054                 }
2055         }
2056 }
2057
2058 inline bool Node_hasChildren( scene::Node& node ){
2059         scene::Traversable* traversable = Node_getTraversable( node );
2060         return traversable != 0 && !traversable->empty();
2061 }
2062
2063 void SelectBrush( int entitynum, int brushnum ){
2064         scene::Path path;
2065         Scene_FindEntityBrush( entitynum, brushnum, path );
2066         if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
2067                 scene::Instance* instance = GlobalSceneGraph().find( path );
2068                 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
2069                 Selectable* selectable = Instance_getSelectable( *instance );
2070                 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
2071                 selectable->setSelected( true );
2072                 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
2073         }
2074 }
2075
2076
2077 class BrushFindIndexWalker : public scene::Graph::Walker
2078 {
2079 mutable const scene::Node* m_node;
2080 std::size_t& m_count;
2081 public:
2082 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
2083         : m_node( &node ), m_count( count ){
2084 }
2085
2086 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2087         if ( Node_isPrimitive( path.top() ) ) {
2088                 if ( m_node == path.top().get_pointer() ) {
2089                         m_node = 0;
2090                 }
2091                 if ( m_node ) {
2092                         ++m_count;
2093                 }
2094         }
2095         return true;
2096 }
2097 };
2098
2099 class EntityFindIndexWalker : public scene::Graph::Walker
2100 {
2101 mutable const scene::Node* m_node;
2102 std::size_t& m_count;
2103 public:
2104 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2105         : m_node( &node ), m_count( count ){
2106 }
2107
2108 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2109         if ( Node_isEntity( path.top() ) ) {
2110                 if ( m_node == path.top().get_pointer() ) {
2111                         m_node = 0;
2112                 }
2113                 if ( m_node ) {
2114                         ++m_count;
2115                 }
2116         }
2117         return true;
2118 }
2119 };
2120
2121 static void GetSelectionIndex( int *ent, int *brush ){
2122         std::size_t count_brush = 0;
2123         std::size_t count_entity = 0;
2124         if ( GlobalSelectionSystem().countSelected() != 0 ) {
2125                 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2126
2127                 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2128                 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2129         }
2130         *brush = int(count_brush);
2131         *ent = int(count_entity);
2132 }
2133
2134 void DoFind(){
2135         ModalDialog dialog;
2136         ui::Entry entity{ui::null};
2137         ui::Entry brush{ui::null};
2138
2139         ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2140
2141         auto accel = ui::AccelGroup(ui::New);
2142         window.add_accel_group( accel );
2143
2144         {
2145                 auto vbox = create_dialog_vbox( 4, 4 );
2146                 window.add(vbox);
2147                 {
2148                         auto table = create_dialog_table( 2, 2, 4, 4 );
2149                         vbox.pack_start( table, TRUE, TRUE, 0 );
2150                         {
2151                                 ui::Widget label = ui::Label( "Entity number" );
2152                                 label.show();
2153                 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2154                         }
2155                         {
2156                                 ui::Widget label = ui::Label( "Brush number" );
2157                                 label.show();
2158                 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2159                         }
2160                         {
2161                                 auto entry = ui::Entry(ui::New);
2162                                 entry.show();
2163                 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2164                                 gtk_widget_grab_focus( entry  );
2165                                 entity = entry;
2166                         }
2167                         {
2168                                 auto entry = ui::Entry(ui::New);
2169                                 entry.show();
2170                 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2171
2172                                 brush = entry;
2173                         }
2174                 }
2175                 {
2176                         auto hbox = create_dialog_hbox( 4 );
2177                         vbox.pack_start( hbox, TRUE, TRUE, 0 );
2178                         {
2179                                 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2180                                 hbox.pack_start( button, FALSE, FALSE, 0 );
2181                                 widget_make_default( button );
2182                                 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2183                         }
2184                         {
2185                                 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2186                                 hbox.pack_start( button, FALSE, FALSE, 0 );
2187                                 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2188                         }
2189                 }
2190         }
2191
2192         // Initialize dialog
2193         char buf[16];
2194         int ent, br;
2195
2196         GetSelectionIndex( &ent, &br );
2197         sprintf( buf, "%i", ent );
2198         entity.text(buf);
2199         sprintf( buf, "%i", br );
2200         brush.text(buf);
2201
2202         if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2203                 const char *entstr = gtk_entry_get_text( entity );
2204                 const char *brushstr = gtk_entry_get_text( brush );
2205                 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2206         }
2207
2208     window.destroy();
2209 }
2210
2211 void Map_constructPreferences( PreferencesPage& page ){
2212         page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
2213         page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
2214 }
2215
2216
2217 class MapEntityClasses : public ModuleObserver
2218 {
2219 std::size_t m_unrealised;
2220 public:
2221 MapEntityClasses() : m_unrealised( 1 ){
2222 }
2223
2224 void realise(){
2225         if ( --m_unrealised == 0 ) {
2226                 if ( g_map.m_resource != 0 ) {
2227                         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2228                         g_map.m_resource->realise();
2229                 }
2230         }
2231 }
2232
2233 void unrealise(){
2234         if ( ++m_unrealised == 1 ) {
2235                 if ( g_map.m_resource != 0 ) {
2236                         g_map.m_resource->flush();
2237                         g_map.m_resource->unrealise();
2238                 }
2239         }
2240 }
2241 };
2242
2243 MapEntityClasses g_MapEntityClasses;
2244
2245
2246 class MapModuleObserver : public ModuleObserver
2247 {
2248 std::size_t m_unrealised;
2249 public:
2250 MapModuleObserver() : m_unrealised( 1 ){
2251 }
2252
2253 void realise(){
2254         if ( --m_unrealised == 0 ) {
2255                 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2256                 StringOutputStream buffer( 256 );
2257                 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2258                 Q_mkdir( buffer.c_str() );
2259                 g_mapsPath = buffer.c_str();
2260         }
2261 }
2262
2263 void unrealise(){
2264         if ( ++m_unrealised == 1 ) {
2265                 g_mapsPath = "";
2266         }
2267 }
2268 };
2269
2270 MapModuleObserver g_MapModuleObserver;
2271
2272 CopiedString g_strLastMap;
2273 bool g_bLoadLastMap = false;
2274
2275 void Map_Construct(){
2276         GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2277         GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2278         GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2279         //GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2280         GlobalToggles_insert( "RegionSetSelection", makeCallbackF(RegionSelected), ToggleItem::AddCallbackCaller( g_region_item ), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2281
2282         GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2283         GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2284         GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2285         GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
2286
2287         PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2288
2289         GlobalEntityClassManager().attach( g_MapEntityClasses );
2290         Radiant_attachHomePathsObserver( g_MapModuleObserver );
2291 }
2292
2293 void Map_Destroy(){
2294         Radiant_detachHomePathsObserver( g_MapModuleObserver );
2295         GlobalEntityClassManager().detach( g_MapEntityClasses );
2296 }