2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
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.
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.
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
24 #include "debugging/debugging.h"
27 MapModules& ReferenceAPI_getMapModules();
28 #include "iselection.h"
32 #include "ireference.h"
33 #include "ifiletypes.h"
39 #include "ifilesystem.h"
40 #include "namespace.h"
41 #include "moduleobserver.h"
45 #include <gdk/gdkkeysyms.h>
46 #include "uilib/uilib.h"
49 #include "transformlib.h"
50 #include "selectionlib.h"
51 #include "instancelib.h"
52 #include "traverselib.h"
54 #include "eclasslib.h"
56 #include "stream/textfilestream.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"
64 #include "gtkutil/filechooser.h"
68 #include "filetypes.h"
70 #include "entityinspector.h"
73 #include "camwindow.h"
75 #include "mainframe.h"
76 #include "preferences.h"
77 #include "preferencesystem.h"
78 #include "referencecache.h"
82 #include "brushmodule.h"
92 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
93 m_names.insert( name_read( c_str() ) );
98 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
99 m_names.erase( name_read( c_str() ) );
103 NameObserver& operator=( const NameObserver& other );
105 NameObserver( UniqueNames& names ) : m_names( names ){
108 NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
115 return string_empty( c_str() );
117 const char* c_str() const {
118 return m_name.c_str();
120 void nameChanged( const char* name ){
125 typedef MemberCaller1<NameObserver, const char*, &NameObserver::nameChanged> NameChangedCaller;
128 class BasicNamespace : public Namespace
130 typedef std::map<NameCallback, NameObserver> Names;
132 UniqueNames m_uniqueNames;
135 ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
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";
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 ) );
151 void makeUnique( const char* name, const NameCallback& setName ) const {
153 name_write( buffer, m_uniqueNames.make_unique( name_read( name ) ) );
157 void mergeNames( const BasicNamespace& other ) const {
158 typedef std::list<NameCallback> SetNameCallbacks;
159 typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
162 UniqueNames uniqueNames( other.m_uniqueNames );
164 for ( Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
166 groups[( *i ).second.c_str()].push_back( ( *i ).first );
169 for ( NameGroups::iterator i = groups.begin(); i != groups.end(); ++i )
171 name_t uniqueName( uniqueNames.make_unique( name_read( ( *i ).first.c_str() ) ) );
172 uniqueNames.insert( uniqueName );
175 name_write( buffer, uniqueName );
177 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
179 SetNameCallbacks& setNameCallbacks = ( *i ).second;
181 for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j )
189 BasicNamespace g_defaultNamespace;
190 BasicNamespace g_cloneNamespace;
194 Namespace* m_namespace;
196 typedef Namespace Type;
197 STRING_CONSTANT( Name, "*" );
200 m_namespace = &g_defaultNamespace;
202 Namespace* getTable(){
207 typedef SingletonModule<NamespaceAPI> NamespaceModule;
208 typedef Static<NamespaceModule> StaticNamespaceModule;
209 StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() );
212 std::list<Namespaced*> g_cloned;
214 inline Namespaced* Node_getNamespaced( scene::Node& node ){
215 return NodeTypeCast<Namespaced>::cast( node );
218 void Node_gatherNamespaced( scene::Node& node ){
219 Namespaced* namespaced = Node_getNamespaced( node );
220 if ( namespaced != 0 ) {
221 g_cloned.push_back( namespaced );
225 class GatherNamespaced : public scene::Traversable::Walker
228 bool pre( scene::Node& node ) const {
229 Node_gatherNamespaced( node );
234 void Map_gatherNamespaced( scene::Node& root ){
235 Node_traverseSubgraph( root, GatherNamespaced() );
238 void Map_mergeClonedNames(){
239 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
241 ( *i )->setNamespace( g_cloneNamespace );
243 g_cloneNamespace.mergeNames( g_defaultNamespace );
244 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
246 ( *i )->setNamespace( g_defaultNamespace );
259 void set( scene::Node* node ){
268 scene::Node* get() const {
274 void Map_SetValid( Map& map, bool valid );
275 void Map_UpdateTitle( const Map& map );
276 void Map_SetWorldspawn( Map& map, scene::Node* node );
279 class Map : public ModuleObserver
283 Resource* m_resource;
287 void ( *m_modified_changed )( const Map& );
289 Signal0 m_mapValidCallbacks;
291 WorldNode m_world_node; // "classname" "worldspawn" !
293 Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){
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() );
310 GlobalSceneGraph().insert_root( *m_resource->getNode() );
314 Map_SetValid( g_map, true );
318 if ( m_resource != 0 ) {
319 Map_SetValid( g_map, false );
320 Map_SetWorldspawn( g_map, 0 );
323 GlobalUndoSystem().clear();
325 GlobalSceneGraph().erase_root();
331 Map* g_currentMap = 0;
333 void Map_addValidCallback( Map& map, const SignalHandler& handler ){
334 map.m_mapValidCallbacks.connectLast( handler );
337 bool Map_Valid( const Map& map ){
341 void Map_SetValid( Map& map, bool valid ){
343 map.m_mapValidCallbacks();
347 const char* Map_Name( const Map& map ){
348 return map.m_name.c_str();
351 bool Map_Unnamed( const Map& map ){
352 return string_equal( Map_Name( map ), "unnamed.map" );
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 ) );
362 const MapFormat& Map_getFormat( const Map& map ){
363 return MapFormat_forFile( Map_Name( map ) );
367 bool Map_Modified( const Map& map ){
368 return map.m_modified;
371 void Map_SetModified( Map& map, bool modified ){
372 if ( map.m_modified ^ modified ) {
373 map.m_modified = modified;
375 map.m_modified_changed( map );
379 void Map_UpdateTitle( const Map& map ){
380 Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) );
385 scene::Node* Map_GetWorldspawn( const Map& map ){
386 return map.m_world_node.get();
389 void Map_SetWorldspawn( Map& map, scene::Node* node ){
390 map.m_world_node.set( node );
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;
399 void AddRegionBrushes( void );
400 void RemoveRegionBrushes( void );
407 free all map elements, reinitialize the structures that depend on them
413 g_map.m_resource->detach( g_map );
414 GlobalReferenceCache().release( g_map.m_name.c_str() );
415 g_map.m_resource = 0;
420 Brush_unlatchPreferences();
423 class EntityFindByClassname : public scene::Graph::Walker
428 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
431 bool pre( const scene::Path& path, scene::Instance& instance ) const {
432 if ( m_entity == 0 ) {
433 Entity* entity = Node_getEntity( path.top() );
435 && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
443 Entity* Scene_FindEntityByClass( const char* name ){
445 GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
449 Entity *Scene_FindPlayerStart(){
450 typedef const char* StaticString;
451 StaticString strings[] = {
453 "info_player_deathmatch",
454 "team_CTF_redplayer",
455 "team_CTF_blueplayer",
457 "team_CTF_bluespawn",
459 typedef const StaticString* StaticStringIterator;
460 for ( StaticStringIterator i = strings, end = strings + ( sizeof( strings ) / sizeof( StaticString ) ); i != end; ++i )
462 Entity* entity = Scene_FindEntityByClass( *i );
471 // move the view to a start position
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 );
483 XYWnd* xywnd = g_pParentWnd->GetXYWnd();
484 xywnd->SetOrigin( point );
487 #include "stringio.h"
489 void Map_StartPosition(){
490 Entity* entity = Scene_FindPlayerStart();
494 string_parse_vector3( entity->getKeyValue( "origin" ), origin );
495 FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) );
499 FocusViews( g_vector3_identity, 0 );
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" );
510 // use first worldspawn
511 class entity_updateworldspawn : public scene::Traversable::Walker
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 );
524 scene::Node* Map_FindWorldspawn( Map& map ){
525 Map_SetWorldspawn( map, 0 );
527 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
529 return Map_GetWorldspawn( map );
533 class CollectAllWalker : public scene::Traversable::Walker
536 UnsortedNodeSet& m_nodes;
538 CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
540 bool pre( scene::Node& node ) const {
541 m_nodes.insert( NodeSmartReference( node ) );
542 Node_getTraversable( m_root )->erase( node );
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 );
552 for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i )
554 Node_getTraversable( parent )->insert( ( *i ) );
558 scene::Node& createWorldspawn(){
559 NodeSmartReference worldspawn( GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "worldspawn", true ) ) );
560 Node_insertChildFirst( GlobalSceneGraph().root(), worldspawn );
564 void Map_UpdateWorldspawn( Map& map ){
565 if ( Map_FindWorldspawn( map ) == 0 ) {
566 Map_SetWorldspawn( map, &createWorldspawn() );
570 scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
571 Map_UpdateWorldspawn( map );
572 return *Map_GetWorldspawn( map );
576 class MapMergeAll : public scene::Traversable::Walker
578 mutable scene::Path m_path;
580 MapMergeAll( const scene::Path& root )
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 );
589 void post( scene::Node& node ) const {
594 class MapMergeEntities : public scene::Traversable::Walker
596 mutable scene::Path m_path;
598 MapMergeEntities( const scene::Path& root )
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 ) );
612 m_path.push( makeReference( *world_node ) );
613 Node_getTraversable( node )->traverse( MapMergeAll( m_path ) );
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 ) );
625 selectPath( m_path, true );
630 void post( scene::Node& node ) const {
635 class BasicContainer : public scene::Node::Symbiot
639 NodeTypeCastTable m_casts;
642 NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
644 NodeTypeCastTable& get(){
650 TraversableNodeSet m_traverse;
653 typedef LazyStatic<TypeCasts> StaticTypeCasts;
655 scene::Traversable& get( NullType<scene::Traversable>){
659 BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
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() ) ) ) );
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();
681 inline scene::Cloneable* Node_getCloneable( scene::Node& node ){
682 return NodeTypeCast<scene::Cloneable>::cast( node );
685 inline scene::Node& node_clone( scene::Node& node ){
686 scene::Cloneable* cloneable = Node_getCloneable( node );
687 if ( cloneable != 0 ) {
688 return cloneable->clone();
691 return ( new scene::NullNode )->node();
694 class CloneAll : public scene::Traversable::Walker
696 mutable scene::Path m_path;
698 CloneAll( scene::Node& root )
699 : m_path( makeReference( root ) ){
701 bool pre( scene::Node& node ) const {
702 if ( node.isRoot() ) {
706 m_path.push( makeReference( node_clone( node ) ) );
707 m_path.top().get().IncRef();
711 void post( scene::Node& node ) const {
712 if ( node.isRoot() ) {
716 Node_getTraversable( m_path.parent() )->insert( m_path.top() );
718 m_path.top().get().DecRef();
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 ) );
733 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
735 class EntityBreakdownWalker : public scene::Graph::Walker
737 EntityBreakdown& m_entitymap;
739 EntityBreakdownWalker( EntityBreakdown& entitymap )
740 : m_entitymap( entitymap ){
742 bool pre( const scene::Path& path, scene::Instance& instance ) const {
743 Entity* entity = Node_getEntity( path.top() );
745 const EntityClass& eclass = entity->getEntityClass();
746 if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
747 m_entitymap[eclass.name()] = 1;
749 else{ ++m_entitymap[eclass.name()]; }
755 void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
756 GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
760 WindowPosition g_posMapInfoWnd( c_default_window_pos );
764 GtkEntry* brushes_entry;
765 GtkEntry* entities_entry;
766 ui::ListStore EntityBreakdownWalker{nullptr};
768 ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
770 window_set_position( window, g_posMapInfoWnd );
773 auto vbox = create_dialog_vbox( 4, 4 );
777 GtkHBox* hbox = create_dialog_hbox( 4 );
778 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
781 GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
782 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
785 auto entry = ui::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 );
792 brushes_entry = entry;
795 auto entry = ui::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 );
802 entities_entry = entry;
805 ui::Widget label = ui::Label( "Total Brushes" );
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 );
813 ui::Widget label = ui::Label( "Total Entities" );
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 );
822 GtkVBox* vbox2 = create_dialog_vbox( 4 );
823 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), FALSE, FALSE, 0 );
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 );
832 ui::Widget label = ui::Label( "Entity breakdown" );
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 );
838 auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
839 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( scr ), TRUE, TRUE, 0 );
842 ui::ListStore store = ui::ListStore(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ));
844 ui::Widget view = ui::TreeView(ui::TreeModel( GTK_TREE_MODEL( store ) ));
845 gtk_tree_view_set_headers_clickable( GTK_TREE_VIEW( view ), TRUE );
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 );
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 );
865 EntityBreakdownWalker = store;
873 EntityBreakdown entitymap;
874 Scene_EntityBreakdown( entitymap );
876 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
879 sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
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 );
886 g_object_unref( G_OBJECT( EntityBreakdownWalker ) );
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 );
894 modal_dialog_show( window, dialog );
897 window_get_position( window, g_posMapInfoWnd );
899 gtk_widget_destroy( GTK_WIDGET( window ) );
907 const char* m_message;
909 ScopeTimer( const char* message )
910 : m_message( message ){
914 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
915 globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
919 CopiedString g_strLastFolder = "";
927 void Map_LoadFile( const char *filename ){
928 globalOutputStream() << "Loading map from " << filename << "\n";
929 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
931 MRU_AddFile( filename );
932 g_strLastFolder = g_path_get_dirname( filename );
935 ScopeTimer timer( "map load" );
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 );
943 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
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() );
953 format->wrongFormat = false;
955 g_map.m_resource->attach( g_map );
957 if ( !format->wrongFormat ) {
963 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
966 globalOutputStream() << "--- LoadMapFile ---\n";
967 globalOutputStream() << g_map.m_name.c_str() << "\n";
969 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
970 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
972 //GlobalEntityCreator().printStatistics();
975 // move the view to a start position
979 g_currentMap = &g_map;
981 // restart VFS to apply new pak filtering based on mapname
982 // needed for daemon DPK VFS
989 virtual bool excluded( scene::Node& node ) const = 0;
992 class ExcludeWalker : public scene::Traversable::Walker
994 const scene::Traversable::Walker& m_walker;
995 const Excluder* m_exclude;
998 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
999 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1001 bool pre( scene::Node& node ) const {
1002 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1008 m_walker.pre( node );
1012 void post( scene::Node& node ) const {
1018 m_walker.post( node );
1023 class AnyInstanceSelected : public scene::Instantiable::Visitor
1027 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1030 void visit( scene::Instance& instance ) const {
1031 Selectable* selectable = Instance_getSelectable( instance );
1032 if ( selectable != 0
1033 && selectable->isSelected() ) {
1039 bool Node_instanceSelected( scene::Node& node ){
1040 scene::Instantiable* instantiable = Node_getInstantiable( node );
1041 ASSERT_NOTNULL( instantiable );
1043 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1047 class SelectedDescendantWalker : public scene::Traversable::Walker
1051 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1055 bool pre( scene::Node& node ) const {
1056 if ( node.isRoot() ) {
1060 if ( Node_instanceSelected( node ) ) {
1068 bool Node_selectedDescendant( scene::Node& node ){
1070 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1074 class SelectionExcluder : public Excluder
1077 bool excluded( scene::Node& node ) const {
1078 return !Node_selectedDescendant( node );
1082 class IncludeSelectedWalker : public scene::Traversable::Walker
1084 const scene::Traversable::Walker& m_walker;
1085 mutable std::size_t m_selected;
1086 mutable bool m_skip;
1088 bool selectedParent() const {
1089 return m_selected != 0;
1092 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1093 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1095 bool pre( scene::Node& node ) const {
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 ) ) {
1102 m_walker.pre( node );
1111 void post( scene::Node& node ) const {
1117 if ( Node_instanceSelected( node ) ) {
1120 m_walker.post( node );
1125 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1126 scene::Traversable* traversable = Node_getTraversable( root );
1127 if ( traversable != 0 ) {
1129 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1131 traversable->traverse( IncludeSelectedWalker( walker ) );
1136 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1137 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out );
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 );
1147 class RegionExcluder : public Excluder
1150 bool excluded( scene::Node& node ) const {
1151 return node.excluded();
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() ) );
1162 bool Map_SaveRegion( const char *filename ){
1165 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1167 RemoveRegionBrushes();
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() );
1179 //ScopeTimer timer("clone subgraph");
1180 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1183 g_map.m_resource->detach( g_map );
1184 GlobalReferenceCache().release( g_map.m_name.c_str() );
1186 g_map.m_resource = resource;
1188 g_map.m_name = absolute;
1189 Map_UpdateTitle( g_map );
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
1197 void Map_Rename( const char* filename ){
1198 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1199 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1201 Map_RenameAbsolute( filename );
1203 SceneChangeNotify();
1214 ScopeTimer timer( "map save" );
1216 return true; // assume success..
1226 //globalOutputStream() << "Map_New\n";
1228 g_map.m_name = "unnamed.map";
1229 Map_UpdateTitle( g_map );
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 );
1236 SceneChangeNotify();
1239 FocusViews( g_vector3_identity, 0 );
1241 g_currentMap = &g_map;
1243 // restart VFS to apply new pak filtering based on mapname
1244 // needed for daemon DPK VFS
1248 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1250 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
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
1257 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1259 for ( int i = 0 ; i < 3 ; i++ )
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";
1267 // write the info_playerstart
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 );
1276 ===========================================================
1280 ===========================================================
1283 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1284 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1286 scene::Node* region_sides[6];
1287 scene::Node* region_startpoint = 0;
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
1298 void AddRegionBrushes( void ){
1301 for ( i = 0; i < 6; i++ )
1303 region_sides[i] = &GlobalBrushCreator().createBrush();
1304 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1307 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1309 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1310 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1312 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1315 void RemoveRegionBrushes( void ){
1316 for ( std::size_t i = 0; i < 6; i++ )
1318 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1320 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1323 inline void exclude_node( scene::Node& node, bool exclude ){
1325 ? node.enable( scene::Node::eExcluded )
1326 : node.disable( scene::Node::eExcluded );
1329 class ExcludeAllWalker : public scene::Graph::Walker
1333 ExcludeAllWalker( bool exclude )
1334 : m_exclude( exclude ){
1336 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1337 exclude_node( path.top(), m_exclude );
1343 void Scene_Exclude_All( bool exclude ){
1344 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1347 bool Instance_isSelected( const scene::Instance& instance ){
1348 const Selectable* selectable = Instance_getSelectable( instance );
1349 return selectable != 0 && selectable->isSelected();
1352 class ExcludeSelectedWalker : public scene::Graph::Walker
1356 ExcludeSelectedWalker( bool exclude )
1357 : m_exclude( exclude ){
1359 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1360 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1365 void Scene_Exclude_Selected( bool exclude ){
1366 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1369 class ExcludeRegionedWalker : public scene::Graph::Walker
1373 ExcludeRegionedWalker( bool exclude )
1374 : m_exclude( exclude ){
1376 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1381 aabb_intersects_aabb(
1382 instance.worldAABB(),
1383 aabb_for_minmax( region_mins, region_maxs )
1393 void Scene_Exclude_Region( bool exclude ){
1394 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1401 Other filtering options may still be on
1404 void Map_RegionOff(){
1405 region_active = false;
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;
1414 Scene_Exclude_All( false );
1417 void Map_ApplyRegion( void ){
1418 region_active = true;
1420 Scene_Exclude_Region( false );
1425 ========================
1426 Map_RegionSelectedBrushes
1427 ========================
1429 void Map_RegionSelectedBrushes( void ){
1432 if ( GlobalSelectionSystem().countSelected() != 0
1433 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1434 region_active = true;
1435 Select_GetBounds( region_mins, region_maxs );
1437 Scene_Exclude_Selected( false );
1439 GlobalSelectionSystem().setSelectedAll( false );
1449 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
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;
1462 void Map_RegionBounds( const AABB& bounds ){
1465 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1466 region_maxs = vector3_added( bounds.origin, bounds.extents );
1478 void Map_RegionBrush( void ){
1479 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1480 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1481 Map_RegionBounds( instance.worldAABB() );
1490 bool Map_ImportFile( const char* filename ){
1491 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1493 g_strLastFolder = g_path_get_dirname( filename );
1495 bool success = false;
1497 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
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 );
1509 format->wrongFormat = false;
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 );
1518 if ( format->wrongFormat ) {
1519 GlobalReferenceCache().release( filename );
1523 NodeSmartReference clone( NewMapRoot( "" ) );
1524 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1525 Map_gatherNamespaced( clone );
1526 Map_mergeClonedNames();
1529 GlobalReferenceCache().release( filename );
1532 SceneChangeNotify();
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 " );
1558 output.push_string( " \"" );
1559 output.push_string( filename );
1560 output.push_string( "\"" );
1563 Q_Exec( NULL, output.c_str(), NULL, false, true );
1565 // rebuild filename as "filenamewithoutext_converted.map"
1567 output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
1568 output.push_string( "_converted.map" );
1569 filename = output.c_str();
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 );
1578 NodeSmartReference clone( NewMapRoot( "" ) );
1579 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1580 Map_gatherNamespaced( clone );
1581 Map_mergeClonedNames();
1584 GlobalReferenceCache().release( filename );
1587 SceneChangeNotify();
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 );
1600 // refresh VFS to apply new pak filtering based on mapname
1601 // needed for daemon DPK VFS
1612 // Saves selected world brushes and whole entities with partial/full selections
1614 bool Map_SaveSelected( const char* filename ){
1615 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1619 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1621 scene::Node& m_parent;
1623 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ){
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 ) {
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 );
1655 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1656 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1659 class CountSelectedBrushes : public scene::Graph::Walker
1661 std::size_t& m_count;
1662 mutable std::size_t m_depth;
1664 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1667 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1668 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1671 Selectable* selectable = Instance_getSelectable( instance );
1672 if ( selectable != 0
1673 && selectable->isSelected()
1674 && Node_isPrimitive( path.top() ) ) {
1679 void post( const scene::Path& path, scene::Instance& instance ) const {
1684 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1686 graph.traverse( CountSelectedBrushes( count ) );
1698 const char* nodetype_get_name( ENodeType type ){
1699 if ( type == eNodeMap ) {
1702 if ( type == eNodeEntity ) {
1705 if ( type == eNodePrimitive ) {
1711 ENodeType node_get_nodetype( scene::Node& node ){
1712 if ( Node_isEntity( node ) ) {
1715 if ( Node_isPrimitive( node ) ) {
1716 return eNodePrimitive;
1718 return eNodeUnknown;
1721 bool contains_entity( scene::Node& node ){
1722 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1725 bool contains_primitive( scene::Node& node ){
1726 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1729 ENodeType node_get_contains( scene::Node& node ){
1730 if ( contains_entity( node ) ) {
1733 if ( contains_primitive( node ) ) {
1734 return eNodePrimitive;
1736 return eNodeUnknown;
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() );
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();
1751 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1755 void Scene_parentSelected(){
1756 UndoableCommand undo( "parentSelected" );
1758 if ( GlobalSelectionSystem().countSelected() > 1 ) {
1759 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1761 const scene::Path& m_parent;
1763 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1765 void visit( scene::Instance& instance ) const {
1766 if ( &m_parent != &instance.path() ) {
1767 Path_parent( m_parent, instance.path() );
1772 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1773 GlobalSelectionSystem().foreachSelected( visitor );
1777 globalOutputStream() << "failed - did not find two selected nodes.\n";
1784 if ( ConfirmModified( "New Map" ) ) {
1791 CopiedString g_mapsPath;
1793 const char* getMapsPath(){
1794 return g_mapsPath.c_str();
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;
1804 return g_strLastFolder.c_str();
1807 const char* map_open( const char* title ){
1808 return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false );
1811 const char* map_import( const char* title ){
1812 return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false );
1815 const char* map_save( const char* title ){
1816 return MainFrame_getWindow().file_dialog( FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true );
1820 if ( !ConfirmModified( "Open Map" ) ) {
1824 const char* filename = map_open( "Open Map" );
1826 if ( filename != NULL ) {
1827 MRU_AddFile( filename );
1830 Map_LoadFile( filename );
1835 const char* filename = map_import( "Import Map" );
1837 if ( filename != NULL ) {
1838 UndoableCommand undo( "mapImport" );
1839 Map_ImportFile( filename );
1844 const char* filename = map_save( "Save Map" );
1846 if ( filename != NULL ) {
1847 g_strLastFolder = g_path_get_dirname( filename );
1848 MRU_AddFile( filename );
1849 Map_Rename( filename );
1860 if ( Map_Unnamed( g_map ) ) {
1863 else if ( Map_Modified( g_map ) ) {
1869 const char* filename = map_save( "Export Selection" );
1871 if ( filename != NULL ) {
1872 g_strLastFolder = g_path_get_dirname( filename );
1873 Map_SaveSelected( filename );
1878 const char* filename = map_save( "Export Region" );
1880 if ( filename != NULL ) {
1881 g_strLastFolder = g_path_get_dirname( filename );
1882 Map_SaveRegion( filename );
1889 SceneChangeNotify();
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()
1899 SceneChangeNotify();
1904 SceneChangeNotify();
1907 void RegionSelected(){
1908 Map_RegionSelectedBrushes();
1909 SceneChangeNotify();
1916 class BrushFindByIndexWalker : public scene::Traversable::Walker
1918 mutable std::size_t m_index;
1919 scene::Path& m_path;
1921 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
1922 : m_index( index ), m_path( path ){
1924 bool pre( scene::Node& node ) const {
1925 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
1926 m_path.push( makeReference( node ) );
1932 class EntityFindByIndexWalker : public scene::Traversable::Walker
1934 mutable std::size_t m_index;
1935 scene::Path& m_path;
1937 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
1938 : m_index( index ), m_path( path ){
1940 bool pre( scene::Node& node ) const {
1941 if ( Node_isEntity( node ) && m_index-- == 0 ) {
1942 m_path.push( makeReference( node ) );
1948 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
1949 path.push( makeReference( GlobalSceneGraph().root() ) );
1951 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
1953 if ( path.size() == 2 ) {
1954 scene::Traversable* traversable = Node_getTraversable( path.top() );
1955 if ( traversable != 0 ) {
1956 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
1961 inline bool Node_hasChildren( scene::Node& node ){
1962 scene::Traversable* traversable = Node_getTraversable( node );
1963 return traversable != 0 && !traversable->empty();
1966 void SelectBrush( int entitynum, int brushnum ){
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 );
1980 class BrushFindIndexWalker : public scene::Graph::Walker
1982 mutable const scene::Node* m_node;
1983 std::size_t& m_count;
1985 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
1986 : m_node( &node ), m_count( count ){
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() ) {
2001 class EntityFindIndexWalker : public scene::Graph::Walker
2003 mutable const scene::Node* m_node;
2004 std::size_t& m_count;
2006 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2007 : m_node( &node ), m_count( count ){
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() ) {
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();
2028 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2029 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2031 *brush = int(count_brush);
2032 *ent = int(count_entity);
2040 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2042 auto accel = ui::AccelGroup();
2043 window.add_accel_group( accel );
2046 auto vbox = create_dialog_vbox( 4, 4 );
2049 GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
2050 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
2052 ui::Widget label = ui::Label( "Entity number" );
2054 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
2055 (GtkAttachOptions) ( 0 ),
2056 (GtkAttachOptions) ( 0 ), 0, 0 );
2059 ui::Widget label = ui::Label( "Brush number" );
2061 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
2062 (GtkAttachOptions) ( 0 ),
2063 (GtkAttachOptions) ( 0 ), 0, 0 );
2066 auto entry = ui::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 ) );
2075 auto entry = ui::Entry();
2077 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
2078 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2079 (GtkAttachOptions) ( 0 ), 0, 0 );
2085 GtkHBox* hbox = create_dialog_hbox( 4 );
2086 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), TRUE, TRUE, 0 );
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 );
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 );
2101 // Initialize dialog
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 );
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 ) );
2117 gtk_widget_destroy( GTK_WIDGET( window ) );
2120 void Map_constructPreferences( PreferencesPage& page ){
2121 page.appendCheckBox( "", "Load last map on open", g_bLoadLastMap );
2125 class MapEntityClasses : public ModuleObserver
2127 std::size_t m_unrealised;
2129 MapEntityClasses() : m_unrealised( 1 ){
2132 if ( --m_unrealised == 0 ) {
2133 if ( g_map.m_resource != 0 ) {
2134 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2135 g_map.m_resource->realise();
2140 if ( ++m_unrealised == 1 ) {
2141 if ( g_map.m_resource != 0 ) {
2142 g_map.m_resource->flush();
2143 g_map.m_resource->unrealise();
2149 MapEntityClasses g_MapEntityClasses;
2152 class MapModuleObserver : public ModuleObserver
2154 std::size_t m_unrealised;
2156 MapModuleObserver() : m_unrealised( 1 ){
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();
2168 if ( ++m_unrealised == 1 ) {
2174 MapModuleObserver g_MapModuleObserver;
2176 CopiedString g_strLastMap;
2177 bool g_bLoadLastMap = false;
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 ) ) );
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 ) );
2189 PreferencesDialog_addSettingsPreferences( FreeCaller1<PreferencesPage&, Map_constructPreferences>() );
2191 GlobalEntityClassManager().attach( g_MapEntityClasses );
2192 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2196 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2197 GlobalEntityClassManager().detach( g_MapEntityClasses );