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"
30 MapModules& ReferenceAPI_getMapModules();
32 #include "iselection.h"
36 #include "ireference.h"
37 #include "ifiletypes.h"
43 #include "ifilesystem.h"
44 #include "namespace.h"
45 #include "moduleobserver.h"
49 #include <gdk/gdkkeysyms.h>
50 #include "uilib/uilib.h"
53 #include "transformlib.h"
54 #include "selectionlib.h"
55 #include "instancelib.h"
56 #include "traverselib.h"
58 #include "eclasslib.h"
60 #include "stream/textfilestream.h"
63 #include "uniquenames.h"
64 #include "modulesystem/singletonmodule.h"
65 #include "modulesystem/moduleregistry.h"
66 #include "stream/stringstream.h"
67 #include "signal/signal.h"
69 #include "gtkutil/filechooser.h"
73 #include "filetypes.h"
75 #include "entityinspector.h"
78 #include "camwindow.h"
80 #include "mainframe.h"
81 #include "preferences.h"
82 #include "preferencesystem.h"
83 #include "referencecache.h"
87 #include "brushmodule.h"
91 bool g_writeMapComments = true;
100 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
101 m_names.insert( name_read( c_str() ) );
107 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
108 m_names.erase( name_read( c_str() ) );
112 NameObserver& operator=( const NameObserver& other );
115 NameObserver( UniqueNames& names ) : m_names( names ){
118 NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
127 return string_empty( c_str() );
130 const char* c_str() const {
131 return m_name.c_str();
134 void nameChanged( const char* name ){
140 typedef MemberCaller<NameObserver, void(const char*), &NameObserver::nameChanged> NameChangedCaller;
143 class BasicNamespace : public Namespace
145 typedef std::map<NameCallback, NameObserver> Names;
147 UniqueNames m_uniqueNames;
150 ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
153 void attach( const NameCallback& setName, const NameCallbackCallback& attachObserver ){
154 std::pair<Names::iterator, bool> result = m_names.insert( Names::value_type( setName, m_uniqueNames ) );
155 ASSERT_MESSAGE( result.second, "cannot attach name" );
156 attachObserver( NameObserver::NameChangedCaller( ( *result.first ).second ) );
157 //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
160 void detach( const NameCallback& setName, const NameCallbackCallback& detachObserver ){
161 Names::iterator i = m_names.find( setName );
162 ASSERT_MESSAGE( i != m_names.end(), "cannot detach name" );
163 //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
164 detachObserver( NameObserver::NameChangedCaller( ( *i ).second ) );
168 void makeUnique( const char* name, const NameCallback& setName ) const {
170 name_write( buffer, m_uniqueNames.make_unique( name_read( name ) ) );
174 void mergeNames( const BasicNamespace& other ) const {
175 typedef std::list<NameCallback> SetNameCallbacks;
176 typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
179 UniqueNames uniqueNames( other.m_uniqueNames );
181 for ( Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
183 groups[( *i ).second.c_str()].push_back( ( *i ).first );
186 for ( NameGroups::iterator i = groups.begin(); i != groups.end(); ++i )
188 name_t uniqueName( uniqueNames.make_unique( name_read( ( *i ).first.c_str() ) ) );
189 uniqueNames.insert( uniqueName );
192 name_write( buffer, uniqueName );
194 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
196 SetNameCallbacks& setNameCallbacks = ( *i ).second;
198 for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j )
206 BasicNamespace g_defaultNamespace;
207 BasicNamespace g_cloneNamespace;
211 Namespace* m_namespace;
213 typedef Namespace Type;
215 STRING_CONSTANT( Name, "*" );
218 m_namespace = &g_defaultNamespace;
221 Namespace* getTable(){
226 typedef SingletonModule<NamespaceAPI> NamespaceModule;
227 typedef Static<NamespaceModule> StaticNamespaceModule;
228 StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() );
231 std::list<Namespaced*> g_cloned;
233 inline Namespaced* Node_getNamespaced( scene::Node& node ){
234 return NodeTypeCast<Namespaced>::cast( node );
237 void Node_gatherNamespaced( scene::Node& node ){
238 Namespaced* namespaced = Node_getNamespaced( node );
239 if ( namespaced != 0 ) {
240 g_cloned.push_back( namespaced );
244 class GatherNamespaced : public scene::Traversable::Walker
247 bool pre( scene::Node& node ) const {
248 Node_gatherNamespaced( node );
253 void Map_gatherNamespaced( scene::Node& root ){
254 Node_traverseSubgraph( root, GatherNamespaced() );
257 void Map_mergeClonedNames(){
258 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
260 ( *i )->setNamespace( g_cloneNamespace );
262 g_cloneNamespace.mergeNames( g_defaultNamespace );
263 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
265 ( *i )->setNamespace( g_defaultNamespace );
279 void set( scene::Node* node ){
289 scene::Node* get() const {
295 void Map_SetValid( Map& map, bool valid );
297 void Map_UpdateTitle( const Map& map );
299 void Map_SetWorldspawn( Map& map, scene::Node* node );
302 class Map : public ModuleObserver
306 Resource* m_resource;
311 void ( *m_modified_changed )( const Map& );
313 Signal0 m_mapValidCallbacks;
315 WorldNode m_world_node; // "classname" "worldspawn" !
317 Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){
321 if ( m_resource != 0 ) {
322 if ( Map_Unnamed( *this ) ) {
323 g_map.m_resource->setNode( NewMapRoot( "" ).get_pointer() );
324 MapFile* map = Node_getMapFile( *g_map.m_resource->getNode() );
334 GlobalSceneGraph().insert_root( *m_resource->getNode() );
338 Map_SetValid( g_map, true );
343 if ( m_resource != 0 ) {
344 Map_SetValid( g_map, false );
345 Map_SetWorldspawn( g_map, 0 );
348 GlobalUndoSystem().clear();
350 GlobalSceneGraph().erase_root();
356 Map* g_currentMap = 0;
358 void Map_addValidCallback( Map& map, const SignalHandler& handler ){
359 map.m_mapValidCallbacks.connectLast( handler );
362 bool Map_Valid( const Map& map ){
366 void Map_SetValid( Map& map, bool valid ){
368 map.m_mapValidCallbacks();
372 const char* Map_Name( const Map& map ){
373 return map.m_name.c_str();
376 bool Map_Unnamed( const Map& map ){
377 return string_equal( Map_Name( map ), "unnamed.map" );
380 inline const MapFormat& MapFormat_forFile( const char* filename ){
381 const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), path_get_extension( filename ) );
382 MapFormat* format = Radiant_getMapModules().findModule( moduleName );
383 ASSERT_MESSAGE( format != 0, "map format not found for file " << makeQuoted( filename ) );
387 const MapFormat& Map_getFormat( const Map& map ){
388 return MapFormat_forFile( Map_Name( map ) );
392 bool Map_Modified( const Map& map ){
393 return map.m_modified;
396 void Map_SetModified( Map& map, bool modified ){
397 if ( map.m_modified ^ modified ) {
398 map.m_modified = modified;
400 map.m_modified_changed( map );
404 void Map_UpdateTitle( const Map& map ){
405 Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) );
409 scene::Node* Map_GetWorldspawn( const Map& map ){
410 return map.m_world_node.get();
413 void Map_SetWorldspawn( Map& map, scene::Node* node ){
414 map.m_world_node.set( node );
419 // need that in a variable, will have to tweak depending on the game
420 float g_MaxWorldCoord = 64 * 1024;
421 float g_MinWorldCoord = -64 * 1024;
423 void AddRegionBrushes( void );
425 void RemoveRegionBrushes( void );
431 free all map elements, reinitialize the structures that depend on them
437 g_map.m_resource->detach( g_map );
438 GlobalReferenceCache().release( g_map.m_name.c_str() );
439 g_map.m_resource = 0;
444 Brush_unlatchPreferences();
447 class EntityFindByClassname : public scene::Graph::Walker
452 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
456 bool pre( const scene::Path& path, scene::Instance& instance ) const {
457 if ( m_entity == 0 ) {
458 Entity* entity = Node_getEntity( path.top() );
460 && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
468 Entity* Scene_FindEntityByClass( const char* name ){
470 GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
474 Entity *Scene_FindPlayerStart(){
475 typedef const char* StaticString;
476 StaticString strings[] = {
478 "info_player_deathmatch",
479 "team_CTF_redplayer",
480 "team_CTF_blueplayer",
482 "team_CTF_bluespawn",
484 typedef const StaticString* StaticStringIterator;
485 for ( StaticStringIterator i = strings, end = strings + ( sizeof( strings ) / sizeof( StaticString ) ); i != end; ++i )
487 Entity* entity = Scene_FindEntityByClass( *i );
496 // move the view to a start position
500 void FocusViews( const Vector3& point, float angle ){
501 CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
502 Camera_setOrigin( camwnd, point );
503 Vector3 angles( Camera_getAngles( camwnd ) );
504 angles[CAMERA_PITCH] = 0;
505 angles[CAMERA_YAW] = angle;
506 Camera_setAngles( camwnd, angles );
508 XYWnd* xywnd = g_pParentWnd->GetXYWnd();
509 xywnd->SetOrigin( point );
512 #include "stringio.h"
514 void Map_StartPosition(){
515 Entity* entity = Scene_FindPlayerStart();
519 string_parse_vector3( entity->getKeyValue( "origin" ), origin );
520 FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) );
524 FocusViews( g_vector3_identity, 0 );
529 inline bool node_is_worldspawn( scene::Node& node ){
530 Entity* entity = Node_getEntity( node );
531 return entity != 0 && string_equal( entity->getKeyValue( "classname" ), "worldspawn" );
535 // use first worldspawn
536 class entity_updateworldspawn : public scene::Traversable::Walker
539 bool pre( scene::Node& node ) const {
540 if ( node_is_worldspawn( node ) ) {
541 if ( Map_GetWorldspawn( g_map ) == 0 ) {
542 Map_SetWorldspawn( g_map, &node );
549 scene::Node* Map_FindWorldspawn( Map& map ){
550 Map_SetWorldspawn( map, 0 );
552 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
554 return Map_GetWorldspawn( map );
558 class CollectAllWalker : public scene::Traversable::Walker
561 UnsortedNodeSet& m_nodes;
563 CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
566 bool pre( scene::Node& node ) const {
567 m_nodes.insert( NodeSmartReference( node ) );
568 Node_getTraversable( m_root )->erase( node );
573 void Node_insertChildFirst( scene::Node& parent, scene::Node& child ){
574 UnsortedNodeSet nodes;
575 Node_getTraversable( parent )->traverse( CollectAllWalker( parent, nodes ) );
576 Node_getTraversable( parent )->insert( child );
578 for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i )
580 Node_getTraversable( parent )->insert( ( *i ) );
584 scene::Node& createWorldspawn(){
585 NodeSmartReference worldspawn( GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "worldspawn", true ) ) );
586 Node_insertChildFirst( GlobalSceneGraph().root(), worldspawn );
590 void Map_UpdateWorldspawn( Map& map ){
591 if ( Map_FindWorldspawn( map ) == 0 ) {
592 Map_SetWorldspawn( map, &createWorldspawn() );
596 scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
597 Map_UpdateWorldspawn( map );
598 return *Map_GetWorldspawn( map );
602 class MapMergeAll : public scene::Traversable::Walker
604 mutable scene::Path m_path;
606 MapMergeAll( const scene::Path& root )
610 bool pre( scene::Node& node ) const {
611 Node_getTraversable( m_path.top() )->insert( node );
612 m_path.push( makeReference( node ) );
613 selectPath( m_path, true );
617 void post( scene::Node& node ) const {
622 class MapMergeEntities : public scene::Traversable::Walker
624 mutable scene::Path m_path;
626 MapMergeEntities( const scene::Path& root )
630 bool pre( scene::Node& node ) const {
631 if ( node_is_worldspawn( node ) ) {
632 scene::Node* world_node = Map_FindWorldspawn( g_map );
633 if ( world_node == 0 ) {
634 Map_SetWorldspawn( g_map, &node );
635 Node_getTraversable( m_path.top().get() )->insert( node );
636 m_path.push( makeReference( node ) );
637 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
641 m_path.push( makeReference( *world_node ) );
642 Node_getTraversable( node )->traverse( MapMergeAll( m_path ) );
647 Node_getTraversable( m_path.top() )->insert( node );
648 m_path.push( makeReference( node ) );
649 if ( node_is_group( node ) ) {
650 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
654 selectPath( m_path, true );
660 void post( scene::Node& node ) const {
665 class BasicContainer : public scene::Node::Symbiot
669 NodeTypeCastTable m_casts;
672 NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
675 NodeTypeCastTable& get(){
681 TraversableNodeSet m_traverse;
684 typedef LazyStatic<TypeCasts> StaticTypeCasts;
686 scene::Traversable& get( NullType<scene::Traversable>){
690 BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
702 /// Merges the map graph rooted at \p node into the global scene-graph.
703 void MergeMap( scene::Node& node ){
704 Node_getTraversable( node )->traverse( MapMergeEntities( scene::Path( makeReference( GlobalSceneGraph().root() ) ) ) );
707 void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
708 NodeSmartReference node( ( new BasicContainer )->node() );
709 format.readGraph( node, in, GlobalEntityCreator() );
710 Map_gatherNamespaced( node );
711 Map_mergeClonedNames();
715 inline scene::Cloneable* Node_getCloneable( scene::Node& node ){
716 return NodeTypeCast<scene::Cloneable>::cast( node );
719 inline scene::Node& node_clone( scene::Node& node ){
720 scene::Cloneable* cloneable = Node_getCloneable( node );
721 if ( cloneable != 0 ) {
722 return cloneable->clone();
725 return ( new scene::NullNode )->node();
728 class CloneAll : public scene::Traversable::Walker
730 mutable scene::Path m_path;
732 CloneAll( scene::Node& root )
733 : m_path( makeReference( root ) ){
736 bool pre( scene::Node& node ) const {
737 if ( node.isRoot() ) {
741 m_path.push( makeReference( node_clone( node ) ) );
742 m_path.top().get().IncRef();
747 void post( scene::Node& node ) const {
748 if ( node.isRoot() ) {
752 Node_getTraversable( m_path.parent() )->insert( m_path.top() );
754 m_path.top().get().DecRef();
759 scene::Node& Node_Clone( scene::Node& node ){
760 scene::Node& clone = node_clone( node );
761 scene::Traversable* traversable = Node_getTraversable( node );
762 if ( traversable != 0 ) {
763 traversable->traverse( CloneAll( clone ) );
768 bool Node_instanceSelected( scene::Node& node );
770 class CloneAllSelected : public scene::Traversable::Walker
772 mutable scene::Path m_path;
774 CloneAllSelected( scene::Node& root )
775 : m_path( makeReference( root ) ){
777 bool pre( scene::Node& node ) const {
778 if ( node.isRoot() ) {
782 if( Node_instanceSelected( node ) ){
783 m_path.push( makeReference( node_clone( node ) ) );
784 m_path.top().get().IncRef();
789 void post( scene::Node& node ) const {
790 if ( node.isRoot() ) {
794 if( Node_instanceSelected( node ) ){
795 Node_getTraversable( m_path.parent() )->insert( m_path.top() );
797 m_path.top().get().DecRef();
803 scene::Node& Node_Clone_Selected( scene::Node& node ){
804 scene::Node& clone = node_clone( node );
805 scene::Traversable* traversable = Node_getTraversable( node );
806 if ( traversable != 0 ) {
807 traversable->traverse( CloneAllSelected( clone ) );
813 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
815 class EntityBreakdownWalker : public scene::Graph::Walker
817 EntityBreakdown& m_entitymap;
819 EntityBreakdownWalker( EntityBreakdown& entitymap )
820 : m_entitymap( entitymap ){
823 bool pre( const scene::Path& path, scene::Instance& instance ) const {
824 Entity* entity = Node_getEntity( path.top() );
826 const EntityClass& eclass = entity->getEntityClass();
827 if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
828 m_entitymap[eclass.name()] = 1;
831 ++m_entitymap[eclass.name()];
838 void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
839 GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
842 class CountStuffWalker : public scene::Graph::Walker
847 int& m_groupents_ingame;
849 CountStuffWalker( int& patches, int& ents_ingame, int& groupents, int& groupents_ingame )
850 : m_patches( patches ), m_ents_ingame( ents_ingame ), m_groupents( groupents ), m_groupents_ingame( groupents_ingame ){
852 bool pre( const scene::Path& path, scene::Instance& instance ) const {
853 Patch* patch = Node_getPatch( path.top() );
857 Entity* entity = Node_getEntity( path.top() );
859 if( entity->isContainer() ){
861 if( !string_equal_nocase( "func_group", entity->getKeyValue( "classname" ) ) &&
862 !string_equal_nocase( "_decal", entity->getKeyValue( "classname" ) ) ){
863 ++m_groupents_ingame;
868 if( !string_equal_nocase_n( "light", entity->getKeyValue( "classname" ), 5 ) &&
869 !string_equal_nocase( "misc_model", entity->getKeyValue( "classname" ) ) ){
877 void Scene_CountStuff( int& patches, int& ents_ingame, int& groupents, int& groupents_ingame ){
878 GlobalSceneGraph().traverse( CountStuffWalker( patches, ents_ingame, groupents, groupents_ingame ) );
881 WindowPosition g_posMapInfoWnd( c_default_window_pos );
885 ui::Widget w_brushes{ui::null};
886 ui::Widget w_patches{ui::null};
887 ui::Widget w_ents{ui::null};
888 ui::Widget w_ents_ingame{ui::null};
889 ui::Widget w_groupents{ui::null};
890 ui::Widget w_groupents_ingame{ui::null};
892 ui::ListStore EntityBreakdownWalker{ui::null};
894 ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
896 window_set_position( window, g_posMapInfoWnd );
899 auto vbox = create_dialog_vbox( 4, 4 );
903 auto hbox = create_dialog_hbox( 4 );
904 vbox.pack_start( hbox, FALSE, FALSE, 0 );
907 auto table = create_dialog_table( 3, 4, 4, 4 );
908 hbox.pack_start( table, TRUE, TRUE, 0 );
911 auto label = ui::Label( "Total Brushes:" );
913 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
914 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
917 auto label = ui::Label( "" );
919 table.attach(label, {1, 2, 0, 1}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
923 auto label = ui::Label( "Total Patches" );
925 table.attach(label, {2, 3, 0, 1}, {GTK_FILL, 0});
926 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
929 auto label = ui::Label( "" );
931 table.attach(label, {3, 4, 0, 1}, {GTK_FILL, 0}, {3, 0});
932 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
936 auto label = ui::Label( "Total Entities:" );
938 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
939 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
942 auto label = ui::Label( "" );
944 table.attach(label, {1, 2, 1, 2}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
945 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
949 auto label = ui::Label( "Ingame Entities:" );
951 table.attach(label, {2, 3, 1, 2}, {GTK_FILL, 0});
952 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
955 auto label = ui::Label( "" );
957 table.attach(label, {3, 4, 1, 2}, {GTK_FILL | GTK_EXPAND, 0 }, {3, 0});
958 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
959 w_ents_ingame = label;
962 auto label = ui::Label( "Group Entities:" );
964 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
965 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
968 auto label = ui::Label( "" );
970 table.attach(label, {1, 2, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
971 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
975 auto label = ui::Label( "Ingame Group Entities:" );
977 table.attach(label, {2, 3, 2, 3}, {GTK_FILL, 0});
978 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
981 auto label = ui::Label( "" );
983 table.attach(label, {3, 4, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
984 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
985 w_groupents_ingame = label;
990 auto vbox2 = create_dialog_vbox( 4 );
991 hbox.pack_start( vbox2, FALSE, FALSE, 0 );
994 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
995 vbox2.pack_start( button, FALSE, FALSE, 0 );
1000 ui::Widget label = ui::Label( "*** Entity breakdown ***" );
1002 vbox.pack_start( label, FALSE, TRUE, 0 );
1003 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
1006 auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
1007 vbox.pack_start( scr, TRUE, TRUE, 0 );
1010 auto store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_UINT ));
1012 auto view = ui::TreeView(ui::TreeModel::from(store._handle));
1013 gtk_tree_view_set_headers_clickable(view, TRUE );
1016 auto renderer = ui::CellRendererText(ui::New);
1017 auto column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
1018 gtk_tree_view_append_column(view, column );
1019 gtk_tree_view_column_set_sort_column_id( column, 0 );
1023 auto renderer = ui::CellRendererText(ui::New);
1024 auto column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
1025 gtk_tree_view_append_column(view, column );
1026 gtk_tree_view_column_set_sort_column_id( column, 1 );
1033 EntityBreakdownWalker = store;
1038 // Initialize fields
1041 EntityBreakdown entitymap;
1042 Scene_EntityBreakdown( entitymap );
1044 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
1046 EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, Unsigned( ( *i ).second ));
1050 EntityBreakdownWalker.unref();
1053 int n_ents_ingame = 0;
1054 int n_groupents = 0;
1055 int n_groupents_ingame = 0;
1056 Scene_CountStuff( n_patches, n_ents_ingame, n_groupents, n_groupents_ingame );
1057 //globalOutputStream() << n_patches << n_ents_ingame << n_groupents << n_groupents_ingame << "\n";
1061 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span> ", Unsigned( g_brushCount.get() ) );
1062 gtk_label_set_markup( GTK_LABEL( w_brushes ), markup );
1065 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_patches );
1066 gtk_label_set_markup( GTK_LABEL( w_patches ), markup );
1069 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span> ", Unsigned( g_entityCount.get() ) );
1070 gtk_label_set_markup( GTK_LABEL( w_ents ), markup );
1073 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_ents_ingame );
1074 gtk_label_set_markup( GTK_LABEL( w_ents_ingame ), markup );
1077 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_groupents );
1078 gtk_label_set_markup( GTK_LABEL( w_groupents ), markup );
1081 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_groupents_ingame );
1082 gtk_label_set_markup( GTK_LABEL( w_groupents_ingame ), markup );
1086 modal_dialog_show( window, dialog );
1089 window_get_position( window, g_posMapInfoWnd );
1099 const char* m_message;
1101 ScopeTimer( const char* message )
1102 : m_message( message ){
1107 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
1108 globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
1112 CopiedString g_strLastMapFolder = "";
1120 void Map_LoadFile( const char *filename ){
1121 g_map.m_name = filename;
1123 // refresh VFS to apply new pak filtering based on mapname
1124 // needed for daemon DPK VFS
1127 globalOutputStream() << "Loading map from " << filename << "\n";
1128 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1130 MRU_AddFile( filename );
1131 g_strLastMapFolder = g_path_get_dirname( filename );
1133 bool switch_format = false;
1136 ScopeTimer timer( "map load" );
1138 const MapFormat* format = NULL;
1139 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1140 if ( string_not_empty( moduleName ) ) {
1141 format = ReferenceAPI_getMapModules().findModule( moduleName );
1144 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
1149 Brush_toggleFormat( i );
1150 Map_UpdateTitle( g_map );
1152 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1154 format->wrongFormat = false;
1156 g_map.m_resource->attach( g_map );
1158 if ( !format->wrongFormat ) {
1161 switch_format = !switch_format;
1165 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
1168 globalOutputStream() << "--- LoadMapFile ---\n";
1169 globalOutputStream() << g_map.m_name.c_str() << "\n";
1171 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
1172 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
1174 //GlobalEntityCreator().printStatistics();
1177 // move the view to a start position
1179 Map_StartPosition();
1181 g_currentMap = &g_map;
1183 Brush_switchFormat( switch_format );
1189 virtual bool excluded( scene::Node& node ) const = 0;
1192 class ExcludeWalker : public scene::Traversable::Walker
1194 const scene::Traversable::Walker& m_walker;
1195 const Excluder* m_exclude;
1196 mutable bool m_skip;
1198 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1199 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1202 bool pre( scene::Node& node ) const {
1203 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1209 m_walker.pre( node );
1214 void post( scene::Node& node ) const {
1220 m_walker.post( node );
1225 class AnyInstanceSelected : public scene::Instantiable::Visitor
1229 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1233 void visit( scene::Instance& instance ) const {
1234 Selectable* selectable = Instance_getSelectable( instance );
1235 if ( selectable != 0
1236 && selectable->isSelected() ) {
1242 bool Node_instanceSelected( scene::Node& node ){
1243 scene::Instantiable* instantiable = Node_getInstantiable( node );
1244 ASSERT_NOTNULL( instantiable );
1246 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1250 class SelectedDescendantWalker : public scene::Traversable::Walker
1254 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1258 bool pre( scene::Node& node ) const {
1259 if ( node.isRoot() ) {
1263 if ( Node_instanceSelected( node ) ) {
1271 bool Node_selectedDescendant( scene::Node& node ){
1273 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1277 class SelectionExcluder : public Excluder
1280 bool excluded( scene::Node& node ) const {
1281 return !Node_selectedDescendant( node );
1285 class IncludeSelectedWalker : public scene::Traversable::Walker
1287 const scene::Traversable::Walker& m_walker;
1288 mutable std::size_t m_selected;
1289 mutable bool m_skip;
1291 bool selectedParent() const {
1292 return m_selected != 0;
1296 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1297 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1300 bool pre( scene::Node& node ) const {
1302 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1303 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1304 if ( Node_instanceSelected( node ) ) {
1307 m_walker.pre( node );
1317 void post( scene::Node& node ) const {
1323 if ( Node_instanceSelected( node ) ) {
1326 m_walker.post( node );
1331 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1332 scene::Traversable* traversable = Node_getTraversable( root );
1333 if ( traversable != 0 ) {
1335 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1337 traversable->traverse( IncludeSelectedWalker( walker ) );
1342 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1343 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
1346 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1347 scene::Traversable* traversable = Node_getTraversable( root );
1348 if ( traversable != 0 ) {
1349 traversable->traverse( walker );
1353 class RegionExcluder : public Excluder
1356 bool excluded( scene::Node& node ) const {
1357 return node.excluded();
1361 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1362 scene::Traversable* traversable = Node_getTraversable( root );
1363 if ( traversable != 0 ) {
1364 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1368 bool Map_SaveRegion( const char *filename ){
1371 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1373 RemoveRegionBrushes();
1379 void Map_RenameAbsolute( const char* absolute ){
1380 Resource* resource = GlobalReferenceCache().capture( absolute );
1381 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1382 resource->setNode( clone.get_pointer() );
1385 //ScopeTimer timer("clone subgraph");
1386 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1389 g_map.m_resource->detach( g_map );
1390 GlobalReferenceCache().release( g_map.m_name.c_str() );
1392 g_map.m_resource = resource;
1394 g_map.m_name = absolute;
1395 Map_UpdateTitle( g_map );
1397 g_map.m_resource->attach( g_map );
1398 // refresh VFS to apply new pak filtering based on mapname
1399 // needed for daemon DPK VFS
1403 void Map_Rename( const char* filename ){
1404 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1405 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1407 Map_RenameAbsolute( filename );
1409 SceneChangeNotify();
1420 ScopeTimer timer( "map save" );
1422 return true; // assume success..
1432 //globalOutputStream() << "Map_New\n";
1434 g_map.m_name = "unnamed.map";
1435 Map_UpdateTitle( g_map );
1438 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1439 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1440 g_map.m_resource->attach( g_map );
1442 SceneChangeNotify();
1445 FocusViews( g_vector3_identity, 0 );
1447 g_currentMap = &g_map;
1449 // restart VFS to apply new pak filtering based on mapname
1450 // needed for daemon DPK VFS
1454 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1456 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1458 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1459 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1460 for now, let's just print an error
1463 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1465 for ( int i = 0 ; i < 3 ; i++ )
1467 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1468 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1473 // write the info_playerstart
1475 sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1476 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1477 sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1478 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1482 ===========================================================
1486 ===========================================================
1488 bool region_active = false;
1490 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_region_caller( region_active );
1492 ToggleItem g_region_item( g_region_caller );
1494 /*void Map_ToggleRegion(){
1495 region_active = !region_active;
1496 g_region_item.update();
1499 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1500 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1502 scene::Node* region_sides[6];
1503 scene::Node* region_startpoint = 0;
1508 a regioned map will have temp walls put up at the region boundary
1509 \todo TODO TTimo old implementation of region brushes
1510 we still add them straight in the worldspawn and take them out after the map is saved
1511 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1514 void AddRegionBrushes( void ){
1517 for ( i = 0; i < 6; i++ )
1519 region_sides[i] = &GlobalBrushCreator().createBrush();
1520 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1523 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1525 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1526 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1528 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1531 void RemoveRegionBrushes( void ){
1532 for ( std::size_t i = 0; i < 6; i++ )
1534 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1536 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1539 inline void exclude_node( scene::Node& node, bool exclude ){
1541 ? node.enable( scene::Node::eExcluded )
1542 : node.disable( scene::Node::eExcluded );
1545 class ExcludeAllWalker : public scene::Graph::Walker
1549 ExcludeAllWalker( bool exclude )
1550 : m_exclude( exclude ){
1553 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1554 exclude_node( path.top(), m_exclude );
1560 void Scene_Exclude_All( bool exclude ){
1561 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1564 bool Instance_isSelected( const scene::Instance& instance ){
1565 const Selectable* selectable = Instance_getSelectable( instance );
1566 return selectable != 0 && selectable->isSelected();
1569 class ExcludeSelectedWalker : public scene::Graph::Walker
1573 ExcludeSelectedWalker( bool exclude )
1574 : m_exclude( exclude ){
1577 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1578 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1583 void Scene_Exclude_Selected( bool exclude ){
1584 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1587 class ExcludeRegionedWalker : public scene::Graph::Walker
1591 ExcludeRegionedWalker( bool exclude )
1592 : m_exclude( exclude ){
1595 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1600 aabb_intersects_aabb(
1601 instance.worldAABB(),
1602 aabb_for_minmax( region_mins, region_maxs )
1612 void Scene_Exclude_Region( bool exclude ){
1613 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1620 Other filtering options may still be on
1623 void Map_RegionOff(){
1624 region_active = false;
1625 g_region_item.update();
1627 region_maxs[0] = g_MaxWorldCoord - 64;
1628 region_mins[0] = g_MinWorldCoord + 64;
1629 region_maxs[1] = g_MaxWorldCoord - 64;
1630 region_mins[1] = g_MinWorldCoord + 64;
1631 region_maxs[2] = g_MaxWorldCoord - 64;
1632 region_mins[2] = g_MinWorldCoord + 64;
1634 Scene_Exclude_All( false );
1637 void Map_ApplyRegion( void ){
1638 region_active = true;
1639 g_region_item.update();
1641 Scene_Exclude_Region( false );
1646 ========================
1647 Map_RegionSelectedBrushes
1648 ========================
1650 void Map_RegionSelectedBrushes( void ){
1653 if ( GlobalSelectionSystem().countSelected() != 0
1654 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1655 region_active = true;
1656 g_region_item.update();
1657 Select_GetBounds( region_mins, region_maxs );
1659 Scene_Exclude_Selected( false );
1661 GlobalSelectionSystem().setSelectedAll( false );
1671 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1674 region_mins[0] = x_min;
1675 region_maxs[0] = x_max;
1676 region_mins[1] = y_min;
1677 region_maxs[1] = y_max;
1678 region_mins[2] = g_MinWorldCoord + 64;
1679 region_maxs[2] = g_MaxWorldCoord - 64;
1684 void Map_RegionBounds( const AABB& bounds ){
1687 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1688 region_maxs = vector3_added( bounds.origin, bounds.extents );
1700 void Map_RegionBrush( void ){
1701 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1702 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1703 Map_RegionBounds( instance.worldAABB() );
1712 bool Map_ImportFile( const char* filename ){
1713 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1715 g_strLastMapFolder = g_path_get_dirname( filename );
1717 bool success = false;
1719 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1724 const MapFormat* format = NULL;
1725 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1726 if ( string_not_empty( moduleName ) ) {
1727 format = ReferenceAPI_getMapModules().findModule( moduleName );
1731 format->wrongFormat = false;
1733 Resource* resource = GlobalReferenceCache().capture( filename );
1734 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1735 if ( !resource->load() ) {
1736 GlobalReferenceCache().release( filename );
1740 if ( format->wrongFormat ) {
1741 GlobalReferenceCache().release( filename );
1745 NodeSmartReference clone( NewMapRoot( "" ) );
1746 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1747 Map_gatherNamespaced( clone );
1748 Map_mergeClonedNames();
1751 GlobalReferenceCache().release( filename );
1754 SceneChangeNotify();
1760 const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
1761 int n = string_length( path_get_extension( filename ) );
1762 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1764 output += AppPath_get();
1766 output += GDEF_OS_EXE_EXT;
1768 output += " -v -game ";
1769 output += ( type && *type ) ? type : "quake3";
1770 output += " -fs_basepath \"";
1771 output += EnginePath_get();
1772 output += "\" -fs_homepath \"";
1773 output += g_qeglobals.m_userEnginePath.c_str();
1777 for ( int i = 0; i < g_pakPathCount; i++ ) {
1778 if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
1779 output += " -fs_pakpath \"";
1780 output += g_strPakPath[i].c_str();
1786 if ( g_disableEnginePath ) {
1787 output += " -fs_nobasepath ";
1790 if ( g_disableHomePath ) {
1791 output += " -fs_nohomepath ";
1794 output += " -fs_game ";
1795 output += gamename_get();
1796 output += " -convert -format ";
1797 output += Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map";
1798 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1799 output += " -readmap ";
1806 Q_Exec( NULL, output.c_str(), NULL, false, true );
1808 // rebuild filename as "filenamewithoutext_converted.map"
1810 output.append( filename, string_length( filename ) - ( n + 1 ) );
1811 output += "_converted.map";
1812 filename = output.c_str();
1815 Resource* resource = GlobalReferenceCache().capture( filename );
1816 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1817 if ( !resource->load() ) {
1818 GlobalReferenceCache().release( filename );
1821 NodeSmartReference clone( NewMapRoot( "" ) );
1822 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1823 Map_gatherNamespaced( clone );
1824 Map_mergeClonedNames();
1827 GlobalReferenceCache().release( filename );
1830 SceneChangeNotify();
1839 bool Map_SaveFile( const char* filename ){
1840 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1841 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1843 // refresh VFS to apply new pak filtering based on mapname
1844 // needed for daemon DPK VFS
1855 // Saves selected world brushes and whole entities with partial/full selections
1857 bool Map_SaveSelected( const char* filename ){
1858 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1861 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1863 scene::Node& m_parent;
1864 mutable bool m_emptyOldParent;
1867 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
1870 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1871 if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
1872 Selectable* selectable = Instance_getSelectable( instance );
1873 if ( selectable && selectable->isSelected() && path.size() > 1 ) {
1880 void post( const scene::Path& path, scene::Instance& instance ) const {
1881 if ( path.top().get_pointer() == &m_parent )
1884 if ( Node_isPrimitive( path.top() ) ){
1885 m_emptyOldParent = false;
1886 Selectable* selectable = Instance_getSelectable( instance );
1888 if ( selectable && selectable->isSelected() && path.size() > 1 ){
1889 scene::Node& parent = path.parent();
1890 if ( &parent != &m_parent ){
1891 NodeSmartReference node( path.top().get() );
1892 scene::Traversable* traversable_parent = Node_getTraversable( parent );
1893 traversable_parent->erase( node );
1894 Node_getTraversable( m_parent )->insert( node );
1895 if ( traversable_parent->empty() )
1896 m_emptyOldParent = true;
1900 else if ( m_emptyOldParent ){
1901 m_emptyOldParent = false;
1902 // delete empty entities
1903 Entity* entity = Node_getEntity( path.top() );
1904 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map ) && Node_getTraversable( path.top() )->empty() ) {
1905 Path_deleteTop( path );
1911 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1912 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1915 class CountSelectedBrushes : public scene::Graph::Walker
1917 std::size_t& m_count;
1918 mutable std::size_t m_depth;
1920 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1924 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1925 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1928 Selectable* selectable = Instance_getSelectable( instance );
1929 if ( selectable != 0
1930 && selectable->isSelected()
1931 && Node_isPrimitive( path.top() ) ) {
1937 void post( const scene::Path& path, scene::Instance& instance ) const {
1942 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1944 graph.traverse( CountSelectedBrushes( count ) );
1956 const char* nodetype_get_name( ENodeType type ){
1957 if ( type == eNodeMap ) {
1960 if ( type == eNodeEntity ) {
1963 if ( type == eNodePrimitive ) {
1969 ENodeType node_get_nodetype( scene::Node& node ){
1970 if ( Node_isEntity( node ) ) {
1973 if ( Node_isPrimitive( node ) ) {
1974 return eNodePrimitive;
1976 return eNodeUnknown;
1979 bool contains_entity( scene::Node& node ){
1980 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1983 bool contains_primitive( scene::Node& node ){
1984 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1987 ENodeType node_get_contains( scene::Node& node ){
1988 if ( contains_entity( node ) ) {
1991 if ( contains_primitive( node ) ) {
1992 return eNodePrimitive;
1994 return eNodeUnknown;
1997 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1998 ENodeType contains = node_get_contains( parent.top() );
1999 ENodeType type = node_get_nodetype( child.top() );
2001 if ( contains != eNodeUnknown && contains == type ) {
2002 NodeSmartReference node( child.top().get() );
2003 Path_deleteTop( child );
2004 Node_getTraversable( parent.top() )->insert( node );
2005 SceneChangeNotify();
2009 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
2013 void Scene_parentSelected(){
2014 UndoableCommand undo( "parentSelected" );
2016 if ( GlobalSelectionSystem().countSelected() > 1 ) {
2017 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
2019 const scene::Path& m_parent;
2021 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
2024 void visit( scene::Instance& instance ) const {
2025 if ( &m_parent != &instance.path() ) {
2026 Path_parent( m_parent, instance.path() );
2031 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
2032 GlobalSelectionSystem().foreachSelected( visitor );
2036 globalOutputStream() << "failed - did not find two selected nodes.\n";
2042 if ( ConfirmModified( "New Map" ) ) {
2049 CopiedString g_mapsPath;
2051 const char* getMapsPath(){
2052 return g_mapsPath.c_str();
2055 const char* getLastMapFolderPath(){
2056 if (g_strLastMapFolder.empty()) {
2057 GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
2058 if (g_strLastMapFolder.empty()) {
2059 StringOutputStream buffer( 1024 );
2060 buffer << getMapsPath();
2061 if ( !file_readable( buffer.c_str() ) ) {
2063 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
2065 g_strLastMapFolder = buffer.c_str();
2068 return g_strLastMapFolder.c_str();
2071 const char* map_open( const char* title ){
2072 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
2075 const char* map_import( const char* title ){
2076 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
2079 const char* map_save( const char* title ){
2080 return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
2084 if ( !ConfirmModified( "Open Map" ) ) {
2088 const char* filename = map_open( "Open Map" );
2090 if ( filename != NULL ) {
2091 MRU_AddFile( filename );
2094 Map_LoadFile( filename );
2099 const char* filename = map_import( "Import Map" );
2101 if ( filename != NULL ) {
2102 UndoableCommand undo( "mapImport" );
2103 Map_ImportFile( filename );
2108 const char* filename = map_save( "Save Map" );
2110 if ( filename != NULL ) {
2111 g_strLastMapFolder = g_path_get_dirname( filename );
2112 MRU_AddFile( filename );
2113 Map_Rename( filename );
2124 if ( Map_Unnamed( g_map ) ) {
2127 else if ( Map_Modified( g_map ) ) {
2129 MRU_AddFile( g_map.m_name.c_str() ); //add on saving, but not opening via cmd line: spoils the list
2134 const char* filename = map_save( "Export Selection" );
2136 if ( filename != NULL ) {
2137 g_strLastMapFolder = g_path_get_dirname( filename );
2138 Map_SaveSelected( filename );
2143 const char* filename = map_save( "Export Region" );
2145 if ( filename != NULL ) {
2146 g_strLastMapFolder = g_path_get_dirname( filename );
2147 Map_SaveRegion( filename );
2154 SceneChangeNotify();
2159 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2160 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2161 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2162 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2164 SceneChangeNotify();
2169 SceneChangeNotify();
2172 void RegionSelected(){
2173 Map_RegionSelectedBrushes();
2174 SceneChangeNotify();
2181 class BrushFindByIndexWalker : public scene::Traversable::Walker
2183 mutable std::size_t m_index;
2184 scene::Path& m_path;
2186 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
2187 : m_index( index ), m_path( path ){
2190 bool pre( scene::Node& node ) const {
2191 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
2192 m_path.push( makeReference( node ) );
2198 class EntityFindByIndexWalker : public scene::Traversable::Walker
2200 mutable std::size_t m_index;
2201 scene::Path& m_path;
2203 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
2204 : m_index( index ), m_path( path ){
2207 bool pre( scene::Node& node ) const {
2208 if ( Node_isEntity( node ) && m_index-- == 0 ) {
2209 m_path.push( makeReference( node ) );
2215 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
2216 path.push( makeReference( GlobalSceneGraph().root() ) );
2218 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
2220 if ( path.size() == 2 ) {
2221 scene::Traversable* traversable = Node_getTraversable( path.top() );
2222 if ( traversable != 0 ) {
2223 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
2228 inline bool Node_hasChildren( scene::Node& node ){
2229 scene::Traversable* traversable = Node_getTraversable( node );
2230 return traversable != 0 && !traversable->empty();
2233 void SelectBrush( int entitynum, int brushnum ){
2235 Scene_FindEntityBrush( entitynum, brushnum, path );
2236 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
2237 scene::Instance* instance = GlobalSceneGraph().find( path );
2238 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
2239 Selectable* selectable = Instance_getSelectable( *instance );
2240 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
2241 selectable->setSelected( true );
2242 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
2247 class BrushFindIndexWalker : public scene::Traversable::Walker
2249 mutable const scene::Node* m_node;
2250 std::size_t& m_count;
2252 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
2253 : m_node( &node ), m_count( count ){
2256 bool pre( scene::Node& node ) const {
2257 if ( Node_isPrimitive( node ) ) {
2258 if ( m_node == &node ) {
2269 class EntityFindIndexWalker : public scene::Traversable::Walker
2271 mutable const scene::Node* m_node;
2272 std::size_t& m_count;
2274 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2275 : m_node( &node ), m_count( count ){
2278 bool pre( scene::Node& node ) const {
2279 if ( Node_isEntity( node ) ) {
2280 if ( m_node == &node ) {
2291 static void GetSelectionIndex( int *ent, int *brush ){
2292 std::size_t count_brush = 0;
2293 std::size_t count_entity = 0;
2294 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2295 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2298 scene::Traversable* traversable = Node_getTraversable( path.parent() );
2299 if ( traversable != 0 && path.size() == 3 ) {
2300 traversable->traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2305 scene::Traversable* traversable = Node_getTraversable( GlobalSceneGraph().root() );
2306 if ( traversable != 0 ) {
2307 if( path.size() == 3 ){
2308 traversable->traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2310 else if ( path.size() == 2 ){
2311 traversable->traverse( EntityFindIndexWalker( path.top(), count_entity ) );
2316 *brush = int(count_brush);
2317 *ent = int(count_entity);
2322 ui::Entry entity{ui::null};
2323 ui::Entry brush{ui::null};
2325 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2327 auto accel = ui::AccelGroup(ui::New);
2328 window.add_accel_group( accel );
2331 auto vbox = create_dialog_vbox( 4, 4 );
2334 auto table = create_dialog_table( 2, 2, 4, 4 );
2335 vbox.pack_start( table, TRUE, TRUE, 0 );
2337 ui::Widget label = ui::Label( "Entity number" );
2339 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2342 ui::Widget label = ui::Label( "Brush number" );
2344 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2347 auto entry = ui::Entry(ui::New);
2349 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2350 gtk_widget_grab_focus( entry );
2354 auto entry = ui::Entry(ui::New);
2356 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2362 auto hbox = create_dialog_hbox( 4 );
2363 vbox.pack_start( hbox, TRUE, TRUE, 0 );
2365 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2366 hbox.pack_start( button, FALSE, FALSE, 0 );
2367 widget_make_default( button );
2368 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2371 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2372 hbox.pack_start( button, FALSE, FALSE, 0 );
2373 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2378 // Initialize dialog
2382 GetSelectionIndex( &ent, &br );
2383 sprintf( buf, "%i", ent );
2385 sprintf( buf, "%i", br );
2388 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2389 const char *entstr = gtk_entry_get_text( entity );
2390 const char *brushstr = gtk_entry_get_text( brush );
2391 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2397 void Map_constructPreferences( PreferencesPage& page ){
2398 page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
2399 page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
2403 class MapEntityClasses : public ModuleObserver
2405 std::size_t m_unrealised;
2407 MapEntityClasses() : m_unrealised( 1 ){
2411 if ( --m_unrealised == 0 ) {
2412 if ( g_map.m_resource != 0 ) {
2413 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2414 g_map.m_resource->realise();
2420 if ( ++m_unrealised == 1 ) {
2421 if ( g_map.m_resource != 0 ) {
2422 g_map.m_resource->flush();
2423 g_map.m_resource->unrealise();
2429 MapEntityClasses g_MapEntityClasses;
2432 class MapModuleObserver : public ModuleObserver
2434 std::size_t m_unrealised;
2436 MapModuleObserver() : m_unrealised( 1 ){
2440 if ( --m_unrealised == 0 ) {
2441 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2442 StringOutputStream buffer( 256 );
2443 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2444 Q_mkdir( buffer.c_str() );
2445 g_mapsPath = buffer.c_str();
2450 if ( ++m_unrealised == 1 ) {
2456 MapModuleObserver g_MapModuleObserver;
2458 CopiedString g_strLastMap;
2459 bool g_bLoadLastMap = false;
2461 void Map_Construct(){
2462 GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2463 GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2464 GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2465 //GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2466 GlobalToggles_insert( "RegionSetSelection", makeCallbackF(RegionSelected), ToggleItem::AddCallbackCaller( g_region_item ), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2468 GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2469 GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2470 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2471 GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
2473 PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2475 GlobalEntityClassManager().attach( g_MapEntityClasses );
2476 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2480 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2481 GlobalEntityClassManager().detach( g_MapEntityClasses );