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