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
26 #include "debugging/debugging.h"
29 MapModules& ReferenceAPI_getMapModules();
30 #include "iselection.h"
34 #include "ireference.h"
35 #include "ifiletypes.h"
41 #include "ifilesystem.h"
42 #include "namespace.h"
43 #include "moduleobserver.h"
47 #include <gdk/gdkkeysyms.h>
48 #include <util/buffer.h>
49 #include "uilib/uilib.h"
52 #include "transformlib.h"
53 #include "selectionlib.h"
54 #include "instancelib.h"
55 #include "traverselib.h"
57 #include "eclasslib.h"
59 #include "stream/textfilestream.h"
61 #include "uniquenames.h"
62 #include "modulesystem/singletonmodule.h"
63 #include "modulesystem/moduleregistry.h"
64 #include "stream/stringstream.h"
65 #include "signal/signal.h"
67 #include "gtkutil/filechooser.h"
71 #include "filetypes.h"
73 #include "entityinspector.h"
76 #include "camwindow.h"
78 #include "mainframe.h"
79 #include "preferences.h"
80 #include "preferencesystem.h"
81 #include "referencecache.h"
85 #include "brushmodule.h"
95 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
96 m_names.insert( name_read( c_str() ) );
101 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
102 m_names.erase( name_read( c_str() ) );
106 NameObserver& operator=( const NameObserver& other );
108 NameObserver( UniqueNames& names ) : m_names( names ){
111 NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
118 return string_empty( c_str() );
120 const char* c_str() const {
121 return m_name.c_str();
123 void nameChanged( const char* name ){
128 typedef MemberCaller1<NameObserver, const char*, &NameObserver::nameChanged> NameChangedCaller;
131 class BasicNamespace : public Namespace
133 typedef std::map<NameCallback, NameObserver> Names;
135 UniqueNames m_uniqueNames;
138 ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
140 void attach( const NameCallback& setName, const NameCallbackCallback& attachObserver ){
141 std::pair<Names::iterator, bool> result = m_names.insert( Names::value_type( setName, m_uniqueNames ) );
142 ASSERT_MESSAGE( result.second, "cannot attach name" );
143 attachObserver( NameObserver::NameChangedCaller( ( *result.first ).second ) );
144 //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
146 void detach( const NameCallback& setName, const NameCallbackCallback& detachObserver ){
147 Names::iterator i = m_names.find( setName );
148 ASSERT_MESSAGE( i != m_names.end(), "cannot detach name" );
149 //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
150 detachObserver( NameObserver::NameChangedCaller( ( *i ).second ) );
154 void makeUnique( const char* name, const NameCallback& setName ) const {
155 auto buffer = u::buffer<1024>();
156 name_write( buffer.mut(), m_uniqueNames.make_unique( name_read( name ) ) );
160 void mergeNames( const BasicNamespace& other ) const {
161 typedef std::list<NameCallback> SetNameCallbacks;
162 typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
165 UniqueNames uniqueNames( other.m_uniqueNames );
167 for ( Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
169 groups[( *i ).second.c_str()].push_back( ( *i ).first );
172 for ( NameGroups::iterator i = groups.begin(); i != groups.end(); ++i )
174 name_t uniqueName( uniqueNames.make_unique( name_read( ( *i ).first.c_str() ) ) );
175 uniqueNames.insert( uniqueName );
177 auto buffer = u::buffer<1024>();
178 name_write( buffer.mut(), uniqueName );
180 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
182 SetNameCallbacks& setNameCallbacks = ( *i ).second;
184 for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j )
192 BasicNamespace g_defaultNamespace;
193 BasicNamespace g_cloneNamespace;
197 Namespace* m_namespace;
199 typedef Namespace Type;
200 STRING_CONSTANT( Name, "*" );
203 m_namespace = &g_defaultNamespace;
205 Namespace* getTable(){
210 typedef SingletonModule<NamespaceAPI> NamespaceModule;
211 typedef Static<NamespaceModule> StaticNamespaceModule;
212 StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() );
215 std::list<Namespaced*> g_cloned;
217 inline Namespaced* Node_getNamespaced( scene::Node& node ){
218 return NodeTypeCast<Namespaced>::cast( node );
221 void Node_gatherNamespaced( scene::Node& node ){
222 Namespaced* namespaced = Node_getNamespaced( node );
223 if ( namespaced != 0 ) {
224 g_cloned.push_back( namespaced );
228 class GatherNamespaced : public scene::Traversable::Walker
231 bool pre( scene::Node& node ) const {
232 Node_gatherNamespaced( node );
237 void Map_gatherNamespaced( scene::Node& root ){
238 Node_traverseSubgraph( root, GatherNamespaced() );
241 void Map_mergeClonedNames(){
242 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
244 ( *i )->setNamespace( g_cloneNamespace );
246 g_cloneNamespace.mergeNames( g_defaultNamespace );
247 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
249 ( *i )->setNamespace( g_defaultNamespace );
262 void set( scene::Node* node ){
271 scene::Node* get() const {
277 void Map_SetValid( Map& map, bool valid );
278 void Map_UpdateTitle( const Map& map );
279 void Map_SetWorldspawn( Map& map, scene::Node* node );
282 class Map : public ModuleObserver
286 Resource* m_resource;
290 void ( *m_modified_changed )( const Map& );
292 Signal0 m_mapValidCallbacks;
294 WorldNode m_world_node; // "classname" "worldspawn" !
296 Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){
300 if ( m_resource != 0 ) {
301 if ( Map_Unnamed( *this ) ) {
302 g_map.m_resource->setNode( NewMapRoot( "" ).get_pointer() );
303 MapFile* map = Node_getMapFile( *g_map.m_resource->getNode() );
313 GlobalSceneGraph().insert_root( *m_resource->getNode() );
317 Map_SetValid( g_map, true );
321 if ( m_resource != 0 ) {
322 Map_SetValid( g_map, false );
323 Map_SetWorldspawn( g_map, 0 );
326 GlobalUndoSystem().clear();
328 GlobalSceneGraph().erase_root();
334 Map* g_currentMap = 0;
336 void Map_addValidCallback( Map& map, const SignalHandler& handler ){
337 map.m_mapValidCallbacks.connectLast( handler );
340 bool Map_Valid( const Map& map ){
344 void Map_SetValid( Map& map, bool valid ){
346 map.m_mapValidCallbacks();
350 const char* Map_Name( const Map& map ){
351 return map.m_name.c_str();
354 bool Map_Unnamed( const Map& map ){
355 return string_equal( Map_Name( map ), "unnamed.map" );
358 inline const MapFormat& MapFormat_forFile( const char* filename ){
359 const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), path_get_extension( filename ) );
360 MapFormat* format = Radiant_getMapModules().findModule( moduleName );
361 ASSERT_MESSAGE( format != 0, "map format not found for file " << makeQuoted( filename ) );
365 const MapFormat& Map_getFormat( const Map& map ){
366 return MapFormat_forFile( Map_Name( map ) );
370 bool Map_Modified( const Map& map ){
371 return map.m_modified;
374 void Map_SetModified( Map& map, bool modified ){
375 if ( map.m_modified ^ modified ) {
376 map.m_modified = modified;
378 map.m_modified_changed( map );
382 void Map_UpdateTitle( const Map& map ){
383 Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) );
388 scene::Node* Map_GetWorldspawn( const Map& map ){
389 return map.m_world_node.get();
392 void Map_SetWorldspawn( Map& map, scene::Node* node ){
393 map.m_world_node.set( node );
398 // need that in a variable, will have to tweak depending on the game
399 float g_MaxWorldCoord = 64 * 1024;
400 float g_MinWorldCoord = -64 * 1024;
402 void AddRegionBrushes( void );
403 void RemoveRegionBrushes( void );
410 free all map elements, reinitialize the structures that depend on them
416 g_map.m_resource->detach( g_map );
417 GlobalReferenceCache().release( g_map.m_name.c_str() );
418 g_map.m_resource = 0;
423 Brush_unlatchPreferences();
426 class EntityFindByClassname : public scene::Graph::Walker
431 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
434 bool pre( const scene::Path& path, scene::Instance& instance ) const {
435 if ( m_entity == 0 ) {
436 Entity* entity = Node_getEntity( path.top() );
438 && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
446 Entity* Scene_FindEntityByClass( const char* name ){
448 GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
452 Entity *Scene_FindPlayerStart(){
453 typedef const char* StaticString;
454 StaticString strings[] = {
456 "info_player_deathmatch",
457 "team_CTF_redplayer",
458 "team_CTF_blueplayer",
460 "team_CTF_bluespawn",
462 typedef const StaticString* StaticStringIterator;
463 for ( StaticStringIterator i = strings, end = strings + ( sizeof( strings ) / sizeof( StaticString ) ); i != end; ++i )
465 Entity* entity = Scene_FindEntityByClass( *i );
474 // move the view to a start position
478 void FocusViews( const Vector3& point, float angle ){
479 CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
480 Camera_setOrigin( camwnd, point );
481 Vector3 angles( Camera_getAngles( camwnd ) );
482 angles[CAMERA_PITCH] = 0;
483 angles[CAMERA_YAW] = angle;
484 Camera_setAngles( camwnd, angles );
486 XYWnd* xywnd = g_pParentWnd->GetXYWnd();
487 xywnd->SetOrigin( point );
490 #include "stringio.h"
492 void Map_StartPosition(){
493 Entity* entity = Scene_FindPlayerStart();
497 string_parse_vector3( entity->getKeyValue( "origin" ), origin );
498 FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) );
502 FocusViews( g_vector3_identity, 0 );
507 inline bool node_is_worldspawn( scene::Node& node ){
508 Entity* entity = Node_getEntity( node );
509 return entity != 0 && string_equal( entity->getKeyValue( "classname" ), "worldspawn" );
513 // use first worldspawn
514 class entity_updateworldspawn : public scene::Traversable::Walker
517 bool pre( scene::Node& node ) const {
518 if ( node_is_worldspawn( node ) ) {
519 if ( Map_GetWorldspawn( g_map ) == 0 ) {
520 Map_SetWorldspawn( g_map, &node );
527 scene::Node* Map_FindWorldspawn( Map& map ){
528 Map_SetWorldspawn( map, 0 );
530 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
532 return Map_GetWorldspawn( map );
536 class CollectAllWalker : public scene::Traversable::Walker
539 UnsortedNodeSet& m_nodes;
541 CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
543 bool pre( scene::Node& node ) const {
544 m_nodes.insert( NodeSmartReference( node ) );
545 Node_getTraversable( m_root )->erase( node );
550 void Node_insertChildFirst( scene::Node& parent, scene::Node& child ){
551 UnsortedNodeSet nodes;
552 Node_getTraversable( parent )->traverse( CollectAllWalker( parent, nodes ) );
553 Node_getTraversable( parent )->insert( child );
555 for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i )
557 Node_getTraversable( parent )->insert( ( *i ) );
561 scene::Node& createWorldspawn(){
562 NodeSmartReference worldspawn( GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "worldspawn", true ) ) );
563 Node_insertChildFirst( GlobalSceneGraph().root(), worldspawn );
567 void Map_UpdateWorldspawn( Map& map ){
568 if ( Map_FindWorldspawn( map ) == 0 ) {
569 Map_SetWorldspawn( map, &createWorldspawn() );
573 scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
574 Map_UpdateWorldspawn( map );
575 return *Map_GetWorldspawn( map );
579 class MapMergeAll : public scene::Traversable::Walker
581 mutable scene::Path m_path;
583 MapMergeAll( const scene::Path& root )
586 bool pre( scene::Node& node ) const {
587 Node_getTraversable( m_path.top() )->insert( node );
588 m_path.push( makeReference( node ) );
589 selectPath( m_path, true );
592 void post( scene::Node& node ) const {
597 class MapMergeEntities : public scene::Traversable::Walker
599 mutable scene::Path m_path;
601 MapMergeEntities( const scene::Path& root )
604 bool pre( scene::Node& node ) const {
605 if ( node_is_worldspawn( node ) ) {
606 scene::Node* world_node = Map_FindWorldspawn( g_map );
607 if ( world_node == 0 ) {
608 Map_SetWorldspawn( g_map, &node );
609 Node_getTraversable( m_path.top().get() )->insert( node );
610 m_path.push( makeReference( node ) );
611 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
615 m_path.push( makeReference( *world_node ) );
616 Node_getTraversable( node )->traverse( MapMergeAll( m_path ) );
621 Node_getTraversable( m_path.top() )->insert( node );
622 m_path.push( makeReference( node ) );
623 if ( node_is_group( node ) ) {
624 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
628 selectPath( m_path, true );
633 void post( scene::Node& node ) const {
638 class BasicContainer : public scene::Node::Symbiot
642 NodeTypeCastTable m_casts;
645 NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
647 NodeTypeCastTable& get(){
653 TraversableNodeSet m_traverse;
656 typedef LazyStatic<TypeCasts> StaticTypeCasts;
658 scene::Traversable& get( NullType<scene::Traversable>){
662 BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
672 /// Merges the map graph rooted at \p node into the global scene-graph.
673 void MergeMap( scene::Node& node ){
674 Node_getTraversable( node )->traverse( MapMergeEntities( scene::Path( makeReference( GlobalSceneGraph().root() ) ) ) );
676 void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
677 NodeSmartReference node( ( new BasicContainer )->node() );
678 format.readGraph( node, in, GlobalEntityCreator() );
679 Map_gatherNamespaced( node );
680 Map_mergeClonedNames();
684 inline scene::Cloneable* Node_getCloneable( scene::Node& node ){
685 return NodeTypeCast<scene::Cloneable>::cast( node );
688 inline scene::Node& node_clone( scene::Node& node ){
689 scene::Cloneable* cloneable = Node_getCloneable( node );
690 if ( cloneable != 0 ) {
691 return cloneable->clone();
694 return ( new scene::NullNode )->node();
697 class CloneAll : public scene::Traversable::Walker
699 mutable scene::Path m_path;
701 CloneAll( scene::Node& root )
702 : m_path( makeReference( root ) ){
704 bool pre( scene::Node& node ) const {
705 if ( node.isRoot() ) {
709 m_path.push( makeReference( node_clone( node ) ) );
710 m_path.top().get().IncRef();
714 void post( scene::Node& node ) const {
715 if ( node.isRoot() ) {
719 Node_getTraversable( m_path.parent() )->insert( m_path.top() );
721 m_path.top().get().DecRef();
726 scene::Node& Node_Clone( scene::Node& node ){
727 scene::Node& clone = node_clone( node );
728 scene::Traversable* traversable = Node_getTraversable( node );
729 if ( traversable != 0 ) {
730 traversable->traverse( CloneAll( clone ) );
736 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
738 class EntityBreakdownWalker : public scene::Graph::Walker
740 EntityBreakdown& m_entitymap;
742 EntityBreakdownWalker( EntityBreakdown& entitymap )
743 : m_entitymap( entitymap ){
745 bool pre( const scene::Path& path, scene::Instance& instance ) const {
746 Entity* entity = Node_getEntity( path.top() );
748 const EntityClass& eclass = entity->getEntityClass();
749 if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
750 m_entitymap[eclass.name()] = 1;
752 else{ ++m_entitymap[eclass.name()]; }
758 void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
759 GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
763 WindowPosition g_posMapInfoWnd( c_default_window_pos );
767 ui::Entry brushes_entry{ui::null};
768 ui::Entry entities_entry{ui::null};
769 ui::ListStore EntityBreakdownWalker{ui::null};
771 ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
773 window_set_position( window, g_posMapInfoWnd );
776 auto vbox = create_dialog_vbox( 4, 4 );
780 GtkHBox* hbox = create_dialog_hbox( 4 );
781 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
784 GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
785 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
788 auto entry = ui::Entry(ui::New);
790 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
791 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
792 (GtkAttachOptions) ( 0 ), 0, 0 );
793 gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
795 brushes_entry = entry;
798 auto entry = ui::Entry(ui::New);
800 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
801 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
802 (GtkAttachOptions) ( 0 ), 0, 0 );
803 gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
805 entities_entry = entry;
808 ui::Widget label = ui::Label( "Total Brushes" );
810 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
811 (GtkAttachOptions) ( GTK_FILL ),
812 (GtkAttachOptions) ( 0 ), 0, 0 );
813 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
816 ui::Widget label = ui::Label( "Total Entities" );
818 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
819 (GtkAttachOptions) ( GTK_FILL ),
820 (GtkAttachOptions) ( 0 ), 0, 0 );
821 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
825 GtkVBox* vbox2 = create_dialog_vbox( 4 );
826 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), FALSE, FALSE, 0 );
829 GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
830 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
835 ui::Widget label = ui::Label( "Entity breakdown" );
837 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, TRUE, 0 );
838 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
841 auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
842 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( scr ), TRUE, TRUE, 0 );
845 ui::ListStore store = ui::ListStore(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ));
847 ui::Widget view = ui::TreeView(ui::TreeModel( GTK_TREE_MODEL( store ) ));
848 gtk_tree_view_set_headers_clickable( GTK_TREE_VIEW( view ), TRUE );
851 auto renderer = ui::CellRendererText(ui::New);
852 GtkTreeViewColumn* column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
853 gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
854 gtk_tree_view_column_set_sort_column_id( column, 0 );
858 auto renderer = ui::CellRendererText(ui::New);
859 GtkTreeViewColumn* column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
860 gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
861 gtk_tree_view_column_set_sort_column_id( column, 1 );
868 EntityBreakdownWalker = store;
876 EntityBreakdown entitymap;
877 Scene_EntityBreakdown( entitymap );
879 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
881 auto tmp = u::buffer<16>();
882 tmp.sprintf( "%u", Unsigned( ( *i ).second ) );
884 gtk_list_store_append( GTK_LIST_STORE( EntityBreakdownWalker ), &iter );
885 gtk_list_store_set( GTK_LIST_STORE( EntityBreakdownWalker ), &iter, 0, ( *i ).first.c_str(), 1, tmp, -1 );
889 EntityBreakdownWalker.unref();
891 auto tmp = u::buffer<16>();
892 tmp.sprintf( "%u", Unsigned( g_brushCount.get() ) );
893 brushes_entry.text(tmp);
894 tmp.sprintf( "%u", Unsigned( g_entityCount.get() ) );
895 entities_entry.text(tmp);
897 modal_dialog_show( window, dialog );
900 window_get_position( window, g_posMapInfoWnd );
902 gtk_widget_destroy( GTK_WIDGET( window ) );
910 const char* m_message;
912 ScopeTimer( const char* message )
913 : m_message( message ){
917 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
918 globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
922 CopiedString g_strLastFolder = "";
930 void Map_LoadFile( const char *filename ){
931 globalOutputStream() << "Loading map from " << filename << "\n";
932 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
934 MRU_AddFile( filename );
935 g_strLastFolder = g_path_get_dirname( filename );
938 ScopeTimer timer( "map load" );
940 const MapFormat* format = NULL;
941 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
942 if ( string_not_empty( moduleName ) ) {
943 format = ReferenceAPI_getMapModules().findModule( moduleName );
946 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
951 Brush_toggleFormat( i );
952 g_map.m_name = filename;
953 Map_UpdateTitle( g_map );
954 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
956 format->wrongFormat = false;
958 g_map.m_resource->attach( g_map );
960 if ( !format->wrongFormat ) {
966 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
969 globalOutputStream() << "--- LoadMapFile ---\n";
970 globalOutputStream() << g_map.m_name.c_str() << "\n";
972 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
973 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
975 //GlobalEntityCreator().printStatistics();
978 // move the view to a start position
982 g_currentMap = &g_map;
984 // restart VFS to apply new pak filtering based on mapname
985 // needed for daemon DPK VFS
992 virtual bool excluded( scene::Node& node ) const = 0;
995 class ExcludeWalker : public scene::Traversable::Walker
997 const scene::Traversable::Walker& m_walker;
998 const Excluder* m_exclude;
1001 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1002 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1004 bool pre( scene::Node& node ) const {
1005 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1011 m_walker.pre( node );
1015 void post( scene::Node& node ) const {
1021 m_walker.post( node );
1026 class AnyInstanceSelected : public scene::Instantiable::Visitor
1030 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1033 void visit( scene::Instance& instance ) const {
1034 Selectable* selectable = Instance_getSelectable( instance );
1035 if ( selectable != 0
1036 && selectable->isSelected() ) {
1042 bool Node_instanceSelected( scene::Node& node ){
1043 scene::Instantiable* instantiable = Node_getInstantiable( node );
1044 ASSERT_NOTNULL( instantiable );
1046 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1050 class SelectedDescendantWalker : public scene::Traversable::Walker
1054 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1058 bool pre( scene::Node& node ) const {
1059 if ( node.isRoot() ) {
1063 if ( Node_instanceSelected( node ) ) {
1071 bool Node_selectedDescendant( scene::Node& node ){
1073 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1077 class SelectionExcluder : public Excluder
1080 bool excluded( scene::Node& node ) const {
1081 return !Node_selectedDescendant( node );
1085 class IncludeSelectedWalker : public scene::Traversable::Walker
1087 const scene::Traversable::Walker& m_walker;
1088 mutable std::size_t m_selected;
1089 mutable bool m_skip;
1091 bool selectedParent() const {
1092 return m_selected != 0;
1095 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1096 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1098 bool pre( scene::Node& node ) const {
1100 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1101 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1102 if ( Node_instanceSelected( node ) ) {
1105 m_walker.pre( node );
1114 void post( scene::Node& node ) const {
1120 if ( Node_instanceSelected( node ) ) {
1123 m_walker.post( node );
1128 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1129 scene::Traversable* traversable = Node_getTraversable( root );
1130 if ( traversable != 0 ) {
1132 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1134 traversable->traverse( IncludeSelectedWalker( walker ) );
1139 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1140 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out );
1143 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1144 scene::Traversable* traversable = Node_getTraversable( root );
1145 if ( traversable != 0 ) {
1146 traversable->traverse( walker );
1150 class RegionExcluder : public Excluder
1153 bool excluded( scene::Node& node ) const {
1154 return node.excluded();
1158 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1159 scene::Traversable* traversable = Node_getTraversable( root );
1160 if ( traversable != 0 ) {
1161 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1165 bool Map_SaveRegion( const char *filename ){
1168 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1170 RemoveRegionBrushes();
1176 void Map_RenameAbsolute( const char* absolute ){
1177 Resource* resource = GlobalReferenceCache().capture( absolute );
1178 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1179 resource->setNode( clone.get_pointer() );
1182 //ScopeTimer timer("clone subgraph");
1183 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1186 g_map.m_resource->detach( g_map );
1187 GlobalReferenceCache().release( g_map.m_name.c_str() );
1189 g_map.m_resource = resource;
1191 g_map.m_name = absolute;
1192 Map_UpdateTitle( g_map );
1194 g_map.m_resource->attach( g_map );
1195 // refresh VFS to apply new pak filtering based on mapname
1196 // needed for daemon DPK VFS
1200 void Map_Rename( const char* filename ){
1201 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1202 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1204 Map_RenameAbsolute( filename );
1206 SceneChangeNotify();
1217 ScopeTimer timer( "map save" );
1219 return true; // assume success..
1229 //globalOutputStream() << "Map_New\n";
1231 g_map.m_name = "unnamed.map";
1232 Map_UpdateTitle( g_map );
1235 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1236 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1237 g_map.m_resource->attach( g_map );
1239 SceneChangeNotify();
1242 FocusViews( g_vector3_identity, 0 );
1244 g_currentMap = &g_map;
1246 // restart VFS to apply new pak filtering based on mapname
1247 // needed for daemon DPK VFS
1251 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1253 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1255 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1256 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1257 for now, let's just print an error
1260 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1262 for ( int i = 0 ; i < 3 ; i++ )
1264 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1265 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1270 // write the info_playerstart
1271 auto sTmp = u::buffer<1024>();
1272 sTmp.sprintf( "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1273 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1274 sTmp.sprintf( "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1275 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1279 ===========================================================
1283 ===========================================================
1286 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1287 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1289 scene::Node* region_sides[6];
1290 scene::Node* region_startpoint = 0;
1295 a regioned map will have temp walls put up at the region boundary
1296 \todo TODO TTimo old implementation of region brushes
1297 we still add them straight in the worldspawn and take them out after the map is saved
1298 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1301 void AddRegionBrushes( void ){
1304 for ( i = 0; i < 6; i++ )
1306 region_sides[i] = &GlobalBrushCreator().createBrush();
1307 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1310 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1312 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1313 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1315 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1318 void RemoveRegionBrushes( void ){
1319 for ( std::size_t i = 0; i < 6; i++ )
1321 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1323 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1326 inline void exclude_node( scene::Node& node, bool exclude ){
1328 ? node.enable( scene::Node::eExcluded )
1329 : node.disable( scene::Node::eExcluded );
1332 class ExcludeAllWalker : public scene::Graph::Walker
1336 ExcludeAllWalker( bool exclude )
1337 : m_exclude( exclude ){
1339 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1340 exclude_node( path.top(), m_exclude );
1346 void Scene_Exclude_All( bool exclude ){
1347 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1350 bool Instance_isSelected( const scene::Instance& instance ){
1351 const Selectable* selectable = Instance_getSelectable( instance );
1352 return selectable != 0 && selectable->isSelected();
1355 class ExcludeSelectedWalker : public scene::Graph::Walker
1359 ExcludeSelectedWalker( bool exclude )
1360 : m_exclude( exclude ){
1362 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1363 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1368 void Scene_Exclude_Selected( bool exclude ){
1369 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1372 class ExcludeRegionedWalker : public scene::Graph::Walker
1376 ExcludeRegionedWalker( bool exclude )
1377 : m_exclude( exclude ){
1379 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1384 aabb_intersects_aabb(
1385 instance.worldAABB(),
1386 aabb_for_minmax( region_mins, region_maxs )
1396 void Scene_Exclude_Region( bool exclude ){
1397 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1404 Other filtering options may still be on
1407 void Map_RegionOff(){
1408 region_active = false;
1410 region_maxs[0] = g_MaxWorldCoord - 64;
1411 region_mins[0] = g_MinWorldCoord + 64;
1412 region_maxs[1] = g_MaxWorldCoord - 64;
1413 region_mins[1] = g_MinWorldCoord + 64;
1414 region_maxs[2] = g_MaxWorldCoord - 64;
1415 region_mins[2] = g_MinWorldCoord + 64;
1417 Scene_Exclude_All( false );
1420 void Map_ApplyRegion( void ){
1421 region_active = true;
1423 Scene_Exclude_Region( false );
1428 ========================
1429 Map_RegionSelectedBrushes
1430 ========================
1432 void Map_RegionSelectedBrushes( void ){
1435 if ( GlobalSelectionSystem().countSelected() != 0
1436 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1437 region_active = true;
1438 Select_GetBounds( region_mins, region_maxs );
1440 Scene_Exclude_Selected( false );
1442 GlobalSelectionSystem().setSelectedAll( false );
1452 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1455 region_mins[0] = x_min;
1456 region_maxs[0] = x_max;
1457 region_mins[1] = y_min;
1458 region_maxs[1] = y_max;
1459 region_mins[2] = g_MinWorldCoord + 64;
1460 region_maxs[2] = g_MaxWorldCoord - 64;
1465 void Map_RegionBounds( const AABB& bounds ){
1468 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1469 region_maxs = vector3_added( bounds.origin, bounds.extents );
1481 void Map_RegionBrush( void ){
1482 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1483 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1484 Map_RegionBounds( instance.worldAABB() );
1493 bool Map_ImportFile( const char* filename ){
1494 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1496 g_strLastFolder = g_path_get_dirname( filename );
1498 bool success = false;
1500 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1505 const MapFormat* format = NULL;
1506 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1507 if ( string_not_empty( moduleName ) ) {
1508 format = ReferenceAPI_getMapModules().findModule( moduleName );
1512 format->wrongFormat = false;
1514 Resource* resource = GlobalReferenceCache().capture( filename );
1515 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1516 if ( !resource->load() ) {
1517 GlobalReferenceCache().release( filename );
1521 if ( format->wrongFormat ) {
1522 GlobalReferenceCache().release( filename );
1526 NodeSmartReference clone( NewMapRoot( "" ) );
1527 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1528 Map_gatherNamespaced( clone );
1529 Map_mergeClonedNames();
1532 GlobalReferenceCache().release( filename );
1535 SceneChangeNotify();
1541 const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue( "q3map2_type" );
1542 int n = string_length( path_get_extension( filename ) );
1543 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1544 StringBuffer output;
1545 output.push_string( AppPath_get() );
1546 output.push_string( "q3map2." );
1547 output.push_string( RADIANT_EXECUTABLE );
1548 output.push_string( " -v -game " );
1549 output.push_string( ( type && *type ) ? type : "quake3" );
1550 output.push_string( " -fs_basepath \"" );
1551 output.push_string( EnginePath_get() );
1552 output.push_string( "\" -fs_homepath \"" );
1553 output.push_string( g_qeglobals.m_userEnginePath.c_str() );
1554 output.push_string( "\" -fs_game " );
1555 output.push_string( gamename_get() );
1556 output.push_string( " -convert -format " );
1557 output.push_string( Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map" );
1558 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1559 output.push_string( " -readmap " );
1561 output.push_string( " \"" );
1562 output.push_string( filename );
1563 output.push_string( "\"" );
1566 Q_Exec( NULL, output.c_str(), NULL, false, true );
1568 // rebuild filename as "filenamewithoutext_converted.map"
1570 output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
1571 output.push_string( "_converted.map" );
1572 filename = output.c_str();
1575 Resource* resource = GlobalReferenceCache().capture( filename );
1576 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1577 if ( !resource->load() ) {
1578 GlobalReferenceCache().release( filename );
1581 NodeSmartReference clone( NewMapRoot( "" ) );
1582 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1583 Map_gatherNamespaced( clone );
1584 Map_mergeClonedNames();
1587 GlobalReferenceCache().release( filename );
1590 SceneChangeNotify();
1599 bool Map_SaveFile( const char* filename ){
1600 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1601 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1603 // refresh VFS to apply new pak filtering based on mapname
1604 // needed for daemon DPK VFS
1615 // Saves selected world brushes and whole entities with partial/full selections
1617 bool Map_SaveSelected( const char* filename ){
1618 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1622 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1624 scene::Node& m_parent;
1626 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ){
1628 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1629 if ( path.top().get_pointer() != &m_parent
1630 && Node_isPrimitive( path.top() ) ) {
1631 Selectable* selectable = Instance_getSelectable( instance );
1632 if ( selectable != 0
1633 && selectable->isSelected()
1634 && path.size() > 1 ) {
1640 void post( const scene::Path& path, scene::Instance& instance ) const {
1641 if ( path.top().get_pointer() != &m_parent
1642 && Node_isPrimitive( path.top() ) ) {
1643 Selectable* selectable = Instance_getSelectable( instance );
1644 if ( selectable != 0
1645 && selectable->isSelected()
1646 && path.size() > 1 ) {
1647 scene::Node& parent = path.parent();
1648 if ( &parent != &m_parent ) {
1649 NodeSmartReference node( path.top().get() );
1650 Node_getTraversable( parent )->erase( node );
1651 Node_getTraversable( m_parent )->insert( node );
1658 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1659 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1662 class CountSelectedBrushes : public scene::Graph::Walker
1664 std::size_t& m_count;
1665 mutable std::size_t m_depth;
1667 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1670 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1671 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1674 Selectable* selectable = Instance_getSelectable( instance );
1675 if ( selectable != 0
1676 && selectable->isSelected()
1677 && Node_isPrimitive( path.top() ) ) {
1682 void post( const scene::Path& path, scene::Instance& instance ) const {
1687 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1689 graph.traverse( CountSelectedBrushes( count ) );
1701 const char* nodetype_get_name( ENodeType type ){
1702 if ( type == eNodeMap ) {
1705 if ( type == eNodeEntity ) {
1708 if ( type == eNodePrimitive ) {
1714 ENodeType node_get_nodetype( scene::Node& node ){
1715 if ( Node_isEntity( node ) ) {
1718 if ( Node_isPrimitive( node ) ) {
1719 return eNodePrimitive;
1721 return eNodeUnknown;
1724 bool contains_entity( scene::Node& node ){
1725 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1728 bool contains_primitive( scene::Node& node ){
1729 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1732 ENodeType node_get_contains( scene::Node& node ){
1733 if ( contains_entity( node ) ) {
1736 if ( contains_primitive( node ) ) {
1737 return eNodePrimitive;
1739 return eNodeUnknown;
1742 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1743 ENodeType contains = node_get_contains( parent.top() );
1744 ENodeType type = node_get_nodetype( child.top() );
1746 if ( contains != eNodeUnknown && contains == type ) {
1747 NodeSmartReference node( child.top().get() );
1748 Path_deleteTop( child );
1749 Node_getTraversable( parent.top() )->insert( node );
1750 SceneChangeNotify();
1754 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1758 void Scene_parentSelected(){
1759 UndoableCommand undo( "parentSelected" );
1761 if ( GlobalSelectionSystem().countSelected() > 1 ) {
1762 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1764 const scene::Path& m_parent;
1766 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1768 void visit( scene::Instance& instance ) const {
1769 if ( &m_parent != &instance.path() ) {
1770 Path_parent( m_parent, instance.path() );
1775 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1776 GlobalSelectionSystem().foreachSelected( visitor );
1780 globalOutputStream() << "failed - did not find two selected nodes.\n";
1787 if ( ConfirmModified( "New Map" ) ) {
1794 CopiedString g_mapsPath;
1796 const char* getMapsPath(){
1797 return g_mapsPath.c_str();
1800 const char* getLastFolderPath(){
1801 if (g_strLastFolder.empty()) {
1802 GlobalPreferenceSystem().registerPreference( "LastFolder", CopiedStringImportStringCaller( g_strLastFolder ), CopiedStringExportStringCaller( g_strLastFolder ) );
1803 if (g_strLastFolder.empty()) {
1804 g_strLastFolder = g_qeglobals.m_userGamePath;
1807 return g_strLastFolder.c_str();
1810 const char* map_open( const char* title ){
1811 return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false );
1814 const char* map_import( const char* title ){
1815 return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false );
1818 const char* map_save( const char* title ){
1819 return MainFrame_getWindow().file_dialog( FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true );
1823 if ( !ConfirmModified( "Open Map" ) ) {
1827 const char* filename = map_open( "Open Map" );
1829 if ( filename != NULL ) {
1830 MRU_AddFile( filename );
1833 Map_LoadFile( filename );
1838 const char* filename = map_import( "Import Map" );
1840 if ( filename != NULL ) {
1841 UndoableCommand undo( "mapImport" );
1842 Map_ImportFile( filename );
1847 const char* filename = map_save( "Save Map" );
1849 if ( filename != NULL ) {
1850 g_strLastFolder = g_path_get_dirname( filename );
1851 MRU_AddFile( filename );
1852 Map_Rename( filename );
1863 if ( Map_Unnamed( g_map ) ) {
1866 else if ( Map_Modified( g_map ) ) {
1872 const char* filename = map_save( "Export Selection" );
1874 if ( filename != NULL ) {
1875 g_strLastFolder = g_path_get_dirname( filename );
1876 Map_SaveSelected( filename );
1881 const char* filename = map_save( "Export Region" );
1883 if ( filename != NULL ) {
1884 g_strLastFolder = g_path_get_dirname( filename );
1885 Map_SaveRegion( filename );
1892 SceneChangeNotify();
1897 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1898 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1899 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1900 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1902 SceneChangeNotify();
1907 SceneChangeNotify();
1910 void RegionSelected(){
1911 Map_RegionSelectedBrushes();
1912 SceneChangeNotify();
1919 class BrushFindByIndexWalker : public scene::Traversable::Walker
1921 mutable std::size_t m_index;
1922 scene::Path& m_path;
1924 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
1925 : m_index( index ), m_path( path ){
1927 bool pre( scene::Node& node ) const {
1928 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
1929 m_path.push( makeReference( node ) );
1935 class EntityFindByIndexWalker : public scene::Traversable::Walker
1937 mutable std::size_t m_index;
1938 scene::Path& m_path;
1940 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
1941 : m_index( index ), m_path( path ){
1943 bool pre( scene::Node& node ) const {
1944 if ( Node_isEntity( node ) && m_index-- == 0 ) {
1945 m_path.push( makeReference( node ) );
1951 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
1952 path.push( makeReference( GlobalSceneGraph().root() ) );
1954 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
1956 if ( path.size() == 2 ) {
1957 scene::Traversable* traversable = Node_getTraversable( path.top() );
1958 if ( traversable != 0 ) {
1959 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
1964 inline bool Node_hasChildren( scene::Node& node ){
1965 scene::Traversable* traversable = Node_getTraversable( node );
1966 return traversable != 0 && !traversable->empty();
1969 void SelectBrush( int entitynum, int brushnum ){
1971 Scene_FindEntityBrush( entitynum, brushnum, path );
1972 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
1973 scene::Instance* instance = GlobalSceneGraph().find( path );
1974 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
1975 Selectable* selectable = Instance_getSelectable( *instance );
1976 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
1977 selectable->setSelected( true );
1978 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
1983 class BrushFindIndexWalker : public scene::Graph::Walker
1985 mutable const scene::Node* m_node;
1986 std::size_t& m_count;
1988 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
1989 : m_node( &node ), m_count( count ){
1991 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1992 if ( Node_isPrimitive( path.top() ) ) {
1993 if ( m_node == path.top().get_pointer() ) {
2004 class EntityFindIndexWalker : public scene::Graph::Walker
2006 mutable const scene::Node* m_node;
2007 std::size_t& m_count;
2009 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2010 : m_node( &node ), m_count( count ){
2012 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2013 if ( Node_isEntity( path.top() ) ) {
2014 if ( m_node == path.top().get_pointer() ) {
2025 static void GetSelectionIndex( int *ent, int *brush ){
2026 std::size_t count_brush = 0;
2027 std::size_t count_entity = 0;
2028 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2029 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2031 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2032 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2034 *brush = int(count_brush);
2035 *ent = int(count_entity);
2040 ui::Entry entity{ui::null};
2041 ui::Entry brush{ui::null};
2043 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2045 auto accel = ui::AccelGroup(ui::New);
2046 window.add_accel_group( accel );
2049 auto vbox = create_dialog_vbox( 4, 4 );
2052 GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
2053 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
2055 ui::Widget label = ui::Label( "Entity number" );
2057 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
2058 (GtkAttachOptions) ( 0 ),
2059 (GtkAttachOptions) ( 0 ), 0, 0 );
2062 ui::Widget label = ui::Label( "Brush number" );
2064 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
2065 (GtkAttachOptions) ( 0 ),
2066 (GtkAttachOptions) ( 0 ), 0, 0 );
2069 auto entry = ui::Entry(ui::New);
2071 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
2072 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2073 (GtkAttachOptions) ( 0 ), 0, 0 );
2074 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
2078 auto entry = ui::Entry(ui::New);
2080 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
2081 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2082 (GtkAttachOptions) ( 0 ), 0, 0 );
2088 GtkHBox* hbox = create_dialog_hbox( 4 );
2089 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), TRUE, TRUE, 0 );
2091 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2092 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
2093 widget_make_default( button );
2094 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2097 GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2098 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
2099 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2104 // Initialize dialog
2105 auto buf = u::buffer<16>();
2108 GetSelectionIndex( &ent, &br );
2109 buf.sprintf( "%i", ent );
2111 buf.sprintf( "%i", br );
2114 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2115 const char *entstr = gtk_entry_get_text( entity );
2116 const char *brushstr = gtk_entry_get_text( brush );
2117 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2120 gtk_widget_destroy( GTK_WIDGET( window ) );
2123 void Map_constructPreferences( PreferencesPage& page ){
2124 page.appendCheckBox( "", "Load last map on open", g_bLoadLastMap );
2128 class MapEntityClasses : public ModuleObserver
2130 std::size_t m_unrealised;
2132 MapEntityClasses() : m_unrealised( 1 ){
2135 if ( --m_unrealised == 0 ) {
2136 if ( g_map.m_resource != 0 ) {
2137 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2138 g_map.m_resource->realise();
2143 if ( ++m_unrealised == 1 ) {
2144 if ( g_map.m_resource != 0 ) {
2145 g_map.m_resource->flush();
2146 g_map.m_resource->unrealise();
2152 MapEntityClasses g_MapEntityClasses;
2155 class MapModuleObserver : public ModuleObserver
2157 std::size_t m_unrealised;
2159 MapModuleObserver() : m_unrealised( 1 ){
2162 if ( --m_unrealised == 0 ) {
2163 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2164 StringOutputStream buffer( 256 );
2165 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2166 Q_mkdir( buffer.c_str() );
2167 g_mapsPath = buffer.c_str();
2171 if ( ++m_unrealised == 1 ) {
2177 MapModuleObserver g_MapModuleObserver;
2179 CopiedString g_strLastMap;
2180 bool g_bLoadLastMap = false;
2182 void Map_Construct(){
2183 GlobalCommands_insert( "RegionOff", FreeCaller<RegionOff>() );
2184 GlobalCommands_insert( "RegionSetXY", FreeCaller<RegionXY>() );
2185 GlobalCommands_insert( "RegionSetBrush", FreeCaller<RegionBrush>() );
2186 GlobalCommands_insert( "RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2188 GlobalPreferenceSystem().registerPreference( "LastMap", CopiedStringImportStringCaller( g_strLastMap ), CopiedStringExportStringCaller( g_strLastMap ) );
2189 GlobalPreferenceSystem().registerPreference( "LoadLastMap", BoolImportStringCaller( g_bLoadLastMap ), BoolExportStringCaller( g_bLoadLastMap ) );
2190 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", WindowPositionImportStringCaller( g_posMapInfoWnd ), WindowPositionExportStringCaller( g_posMapInfoWnd ) );
2192 PreferencesDialog_addSettingsPreferences( FreeCaller1<PreferencesPage&, Map_constructPreferences>() );
2194 GlobalEntityClassManager().attach( g_MapEntityClasses );
2195 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2199 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2200 GlobalEntityClassManager().detach( g_MapEntityClasses );