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