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