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