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