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