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 )
1047 sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
1048 EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
1052 EntityBreakdownWalker.unref();
1055 int n_ents_ingame = 0;
1056 int n_groupents = 0;
1057 int n_groupents_ingame = 0;
1058 Scene_CountStuff( n_patches, n_ents_ingame, n_groupents, n_groupents_ingame );
1059 //globalOutputStream() << n_patches << n_ents_ingame << n_groupents << n_groupents_ingame << "\n";
1063 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span> ", Unsigned( g_brushCount.get() ) );
1064 gtk_label_set_markup( GTK_LABEL( w_brushes ), markup );
1067 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_patches );
1068 gtk_label_set_markup( GTK_LABEL( w_patches ), markup );
1071 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span> ", Unsigned( g_entityCount.get() ) );
1072 gtk_label_set_markup( GTK_LABEL( w_ents ), markup );
1075 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_ents_ingame );
1076 gtk_label_set_markup( GTK_LABEL( w_ents_ingame ), markup );
1079 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_groupents );
1080 gtk_label_set_markup( GTK_LABEL( w_groupents ), markup );
1083 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_groupents_ingame );
1084 gtk_label_set_markup( GTK_LABEL( w_groupents_ingame ), markup );
1088 modal_dialog_show( window, dialog );
1091 window_get_position( window, g_posMapInfoWnd );
1101 const char* m_message;
1103 ScopeTimer( const char* message )
1104 : m_message( message ){
1109 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
1110 globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
1114 CopiedString g_strLastMapFolder = "";
1122 void Map_LoadFile( const char *filename ){
1123 g_map.m_name = filename;
1125 // refresh VFS to apply new pak filtering based on mapname
1126 // needed for daemon DPK VFS
1129 globalOutputStream() << "Loading map from " << filename << "\n";
1130 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1132 MRU_AddFile( filename );
1133 g_strLastMapFolder = g_path_get_dirname( filename );
1135 bool switch_format = false;
1138 ScopeTimer timer( "map load" );
1140 const MapFormat* format = NULL;
1141 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1142 if ( string_not_empty( moduleName ) ) {
1143 format = ReferenceAPI_getMapModules().findModule( moduleName );
1146 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
1151 Brush_toggleFormat( i );
1152 Map_UpdateTitle( g_map );
1154 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1156 format->wrongFormat = false;
1158 g_map.m_resource->attach( g_map );
1160 if ( !format->wrongFormat ) {
1163 switch_format = !switch_format;
1167 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
1170 globalOutputStream() << "--- LoadMapFile ---\n";
1171 globalOutputStream() << g_map.m_name.c_str() << "\n";
1173 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
1174 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
1176 //GlobalEntityCreator().printStatistics();
1179 // move the view to a start position
1181 Map_StartPosition();
1183 g_currentMap = &g_map;
1185 Brush_switchFormat( switch_format );
1191 virtual bool excluded( scene::Node& node ) const = 0;
1194 class ExcludeWalker : public scene::Traversable::Walker
1196 const scene::Traversable::Walker& m_walker;
1197 const Excluder* m_exclude;
1198 mutable bool m_skip;
1200 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1201 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1204 bool pre( scene::Node& node ) const {
1205 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1211 m_walker.pre( node );
1216 void post( scene::Node& node ) const {
1222 m_walker.post( node );
1227 class AnyInstanceSelected : public scene::Instantiable::Visitor
1231 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1235 void visit( scene::Instance& instance ) const {
1236 Selectable* selectable = Instance_getSelectable( instance );
1237 if ( selectable != 0
1238 && selectable->isSelected() ) {
1244 bool Node_instanceSelected( scene::Node& node ){
1245 scene::Instantiable* instantiable = Node_getInstantiable( node );
1246 ASSERT_NOTNULL( instantiable );
1248 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1252 class SelectedDescendantWalker : public scene::Traversable::Walker
1256 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1260 bool pre( scene::Node& node ) const {
1261 if ( node.isRoot() ) {
1265 if ( Node_instanceSelected( node ) ) {
1273 bool Node_selectedDescendant( scene::Node& node ){
1275 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1279 class SelectionExcluder : public Excluder
1282 bool excluded( scene::Node& node ) const {
1283 return !Node_selectedDescendant( node );
1287 class IncludeSelectedWalker : public scene::Traversable::Walker
1289 const scene::Traversable::Walker& m_walker;
1290 mutable std::size_t m_selected;
1291 mutable bool m_skip;
1293 bool selectedParent() const {
1294 return m_selected != 0;
1298 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1299 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1302 bool pre( scene::Node& node ) const {
1304 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1305 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1306 if ( Node_instanceSelected( node ) ) {
1309 m_walker.pre( node );
1319 void post( scene::Node& node ) const {
1325 if ( Node_instanceSelected( node ) ) {
1328 m_walker.post( node );
1333 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1334 scene::Traversable* traversable = Node_getTraversable( root );
1335 if ( traversable != 0 ) {
1337 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1339 traversable->traverse( IncludeSelectedWalker( walker ) );
1344 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1345 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
1348 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1349 scene::Traversable* traversable = Node_getTraversable( root );
1350 if ( traversable != 0 ) {
1351 traversable->traverse( walker );
1355 class RegionExcluder : public Excluder
1358 bool excluded( scene::Node& node ) const {
1359 return node.excluded();
1363 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1364 scene::Traversable* traversable = Node_getTraversable( root );
1365 if ( traversable != 0 ) {
1366 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1370 bool Map_SaveRegion( const char *filename ){
1373 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1375 RemoveRegionBrushes();
1381 void Map_RenameAbsolute( const char* absolute ){
1382 Resource* resource = GlobalReferenceCache().capture( absolute );
1383 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1384 resource->setNode( clone.get_pointer() );
1387 //ScopeTimer timer("clone subgraph");
1388 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1391 g_map.m_resource->detach( g_map );
1392 GlobalReferenceCache().release( g_map.m_name.c_str() );
1394 g_map.m_resource = resource;
1396 g_map.m_name = absolute;
1397 Map_UpdateTitle( g_map );
1399 g_map.m_resource->attach( g_map );
1400 // refresh VFS to apply new pak filtering based on mapname
1401 // needed for daemon DPK VFS
1405 void Map_Rename( const char* filename ){
1406 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1407 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1409 Map_RenameAbsolute( filename );
1411 SceneChangeNotify();
1422 ScopeTimer timer( "map save" );
1424 return true; // assume success..
1434 //globalOutputStream() << "Map_New\n";
1436 g_map.m_name = "unnamed.map";
1437 Map_UpdateTitle( g_map );
1440 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1441 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1442 g_map.m_resource->attach( g_map );
1444 SceneChangeNotify();
1447 FocusViews( g_vector3_identity, 0 );
1449 g_currentMap = &g_map;
1451 // restart VFS to apply new pak filtering based on mapname
1452 // needed for daemon DPK VFS
1456 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1458 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1460 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1461 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1462 for now, let's just print an error
1465 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1467 for ( int i = 0 ; i < 3 ; i++ )
1469 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1470 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1475 // write the info_playerstart
1477 sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1478 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1479 sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1480 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1484 ===========================================================
1488 ===========================================================
1490 bool region_active = false;
1492 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_region_caller( region_active );
1494 ToggleItem g_region_item( g_region_caller );
1496 /*void Map_ToggleRegion(){
1497 region_active = !region_active;
1498 g_region_item.update();
1501 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1502 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1504 scene::Node* region_sides[6];
1505 scene::Node* region_startpoint = 0;
1510 a regioned map will have temp walls put up at the region boundary
1511 \todo TODO TTimo old implementation of region brushes
1512 we still add them straight in the worldspawn and take them out after the map is saved
1513 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1516 void AddRegionBrushes( void ){
1519 for ( i = 0; i < 6; i++ )
1521 region_sides[i] = &GlobalBrushCreator().createBrush();
1522 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1525 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1527 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1528 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1530 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1533 void RemoveRegionBrushes( void ){
1534 for ( std::size_t i = 0; i < 6; i++ )
1536 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1538 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1541 inline void exclude_node( scene::Node& node, bool exclude ){
1543 ? node.enable( scene::Node::eExcluded )
1544 : node.disable( scene::Node::eExcluded );
1547 class ExcludeAllWalker : public scene::Graph::Walker
1551 ExcludeAllWalker( bool exclude )
1552 : m_exclude( exclude ){
1555 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1556 exclude_node( path.top(), m_exclude );
1562 void Scene_Exclude_All( bool exclude ){
1563 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1566 bool Instance_isSelected( const scene::Instance& instance ){
1567 const Selectable* selectable = Instance_getSelectable( instance );
1568 return selectable != 0 && selectable->isSelected();
1571 class ExcludeSelectedWalker : public scene::Graph::Walker
1575 ExcludeSelectedWalker( bool exclude )
1576 : m_exclude( exclude ){
1579 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1580 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1585 void Scene_Exclude_Selected( bool exclude ){
1586 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1589 class ExcludeRegionedWalker : public scene::Graph::Walker
1593 ExcludeRegionedWalker( bool exclude )
1594 : m_exclude( exclude ){
1597 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1602 aabb_intersects_aabb(
1603 instance.worldAABB(),
1604 aabb_for_minmax( region_mins, region_maxs )
1614 void Scene_Exclude_Region( bool exclude ){
1615 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1622 Other filtering options may still be on
1625 void Map_RegionOff(){
1626 region_active = false;
1627 g_region_item.update();
1629 region_maxs[0] = g_MaxWorldCoord - 64;
1630 region_mins[0] = g_MinWorldCoord + 64;
1631 region_maxs[1] = g_MaxWorldCoord - 64;
1632 region_mins[1] = g_MinWorldCoord + 64;
1633 region_maxs[2] = g_MaxWorldCoord - 64;
1634 region_mins[2] = g_MinWorldCoord + 64;
1636 Scene_Exclude_All( false );
1639 void Map_ApplyRegion( void ){
1640 region_active = true;
1641 g_region_item.update();
1643 Scene_Exclude_Region( false );
1648 ========================
1649 Map_RegionSelectedBrushes
1650 ========================
1652 void Map_RegionSelectedBrushes( void ){
1655 if ( GlobalSelectionSystem().countSelected() != 0
1656 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1657 region_active = true;
1658 g_region_item.update();
1659 Select_GetBounds( region_mins, region_maxs );
1661 Scene_Exclude_Selected( false );
1663 GlobalSelectionSystem().setSelectedAll( false );
1673 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1676 region_mins[0] = x_min;
1677 region_maxs[0] = x_max;
1678 region_mins[1] = y_min;
1679 region_maxs[1] = y_max;
1680 region_mins[2] = g_MinWorldCoord + 64;
1681 region_maxs[2] = g_MaxWorldCoord - 64;
1686 void Map_RegionBounds( const AABB& bounds ){
1689 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1690 region_maxs = vector3_added( bounds.origin, bounds.extents );
1702 void Map_RegionBrush( void ){
1703 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1704 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1705 Map_RegionBounds( instance.worldAABB() );
1714 bool Map_ImportFile( const char* filename ){
1715 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1717 g_strLastMapFolder = g_path_get_dirname( filename );
1719 bool success = false;
1721 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1726 const MapFormat* format = NULL;
1727 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1728 if ( string_not_empty( moduleName ) ) {
1729 format = ReferenceAPI_getMapModules().findModule( moduleName );
1733 format->wrongFormat = false;
1735 Resource* resource = GlobalReferenceCache().capture( filename );
1736 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1737 if ( !resource->load() ) {
1738 GlobalReferenceCache().release( filename );
1742 if ( format->wrongFormat ) {
1743 GlobalReferenceCache().release( filename );
1747 NodeSmartReference clone( NewMapRoot( "" ) );
1748 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1749 Map_gatherNamespaced( clone );
1750 Map_mergeClonedNames();
1753 GlobalReferenceCache().release( filename );
1756 SceneChangeNotify();
1762 const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
1763 int n = string_length( path_get_extension( filename ) );
1764 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1766 output += AppPath_get();
1768 output += GDEF_OS_EXE_EXT;
1770 output += " -v -game ";
1771 output += ( type && *type ) ? type : "quake3";
1772 output += " -fs_basepath \"";
1773 output += EnginePath_get();
1774 output += "\" -fs_homepath \"";
1775 output += g_qeglobals.m_userEnginePath.c_str();
1779 for ( int i = 0; i < g_pakPathCount; i++ ) {
1780 if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
1781 output += " -fs_pakpath \"";
1782 output += g_strPakPath[i].c_str();
1788 if ( g_disableEnginePath ) {
1789 output += " -fs_nobasepath ";
1792 if ( g_disableHomePath ) {
1793 output += " -fs_nohomepath ";
1796 output += " -fs_game ";
1797 output += gamename_get();
1798 output += " -convert -format ";
1799 output += Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map";
1800 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1801 output += " -readmap ";
1808 Q_Exec( NULL, output.c_str(), NULL, false, true );
1810 // rebuild filename as "filenamewithoutext_converted.map"
1812 output.append( filename, string_length( filename ) - ( n + 1 ) );
1813 output += "_converted.map";
1814 filename = output.c_str();
1817 Resource* resource = GlobalReferenceCache().capture( filename );
1818 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1819 if ( !resource->load() ) {
1820 GlobalReferenceCache().release( filename );
1823 NodeSmartReference clone( NewMapRoot( "" ) );
1824 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1825 Map_gatherNamespaced( clone );
1826 Map_mergeClonedNames();
1829 GlobalReferenceCache().release( filename );
1832 SceneChangeNotify();
1841 bool Map_SaveFile( const char* filename ){
1842 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1843 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1845 // refresh VFS to apply new pak filtering based on mapname
1846 // needed for daemon DPK VFS
1857 // Saves selected world brushes and whole entities with partial/full selections
1859 bool Map_SaveSelected( const char* filename ){
1860 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1863 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1865 scene::Node& m_parent;
1866 mutable bool m_emptyOldParent;
1869 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
1872 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1873 if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
1874 Selectable* selectable = Instance_getSelectable( instance );
1875 if ( selectable && selectable->isSelected() && path.size() > 1 ) {
1882 void post( const scene::Path& path, scene::Instance& instance ) const {
1883 if ( path.top().get_pointer() == &m_parent )
1886 if ( Node_isPrimitive( path.top() ) ){
1887 m_emptyOldParent = false;
1888 Selectable* selectable = Instance_getSelectable( instance );
1890 if ( selectable && selectable->isSelected() && path.size() > 1 ){
1891 scene::Node& parent = path.parent();
1892 if ( &parent != &m_parent ){
1893 NodeSmartReference node( path.top().get() );
1894 scene::Traversable* traversable_parent = Node_getTraversable( parent );
1895 traversable_parent->erase( node );
1896 Node_getTraversable( m_parent )->insert( node );
1897 if ( traversable_parent->empty() )
1898 m_emptyOldParent = true;
1902 else if ( m_emptyOldParent ){
1903 m_emptyOldParent = false;
1904 // delete empty entities
1905 Entity* entity = Node_getEntity( path.top() );
1906 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map ) && Node_getTraversable( path.top() )->empty() ) {
1907 Path_deleteTop( path );
1913 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1914 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1917 class CountSelectedBrushes : public scene::Graph::Walker
1919 std::size_t& m_count;
1920 mutable std::size_t m_depth;
1922 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1926 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1927 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1930 Selectable* selectable = Instance_getSelectable( instance );
1931 if ( selectable != 0
1932 && selectable->isSelected()
1933 && Node_isPrimitive( path.top() ) ) {
1939 void post( const scene::Path& path, scene::Instance& instance ) const {
1944 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1946 graph.traverse( CountSelectedBrushes( count ) );
1958 const char* nodetype_get_name( ENodeType type ){
1959 if ( type == eNodeMap ) {
1962 if ( type == eNodeEntity ) {
1965 if ( type == eNodePrimitive ) {
1971 ENodeType node_get_nodetype( scene::Node& node ){
1972 if ( Node_isEntity( node ) ) {
1975 if ( Node_isPrimitive( node ) ) {
1976 return eNodePrimitive;
1978 return eNodeUnknown;
1981 bool contains_entity( scene::Node& node ){
1982 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1985 bool contains_primitive( scene::Node& node ){
1986 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1989 ENodeType node_get_contains( scene::Node& node ){
1990 if ( contains_entity( node ) ) {
1993 if ( contains_primitive( node ) ) {
1994 return eNodePrimitive;
1996 return eNodeUnknown;
1999 void Path_parent( const scene::Path& parent, const scene::Path& child ){
2000 ENodeType contains = node_get_contains( parent.top() );
2001 ENodeType type = node_get_nodetype( child.top() );
2003 if ( contains != eNodeUnknown && contains == type ) {
2004 NodeSmartReference node( child.top().get() );
2005 Path_deleteTop( child );
2006 Node_getTraversable( parent.top() )->insert( node );
2007 SceneChangeNotify();
2011 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
2015 void Scene_parentSelected(){
2016 UndoableCommand undo( "parentSelected" );
2018 if ( GlobalSelectionSystem().countSelected() > 1 ) {
2019 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
2021 const scene::Path& m_parent;
2023 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
2026 void visit( scene::Instance& instance ) const {
2027 if ( &m_parent != &instance.path() ) {
2028 Path_parent( m_parent, instance.path() );
2033 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
2034 GlobalSelectionSystem().foreachSelected( visitor );
2038 globalOutputStream() << "failed - did not find two selected nodes.\n";
2044 if ( ConfirmModified( "New Map" ) ) {
2051 CopiedString g_mapsPath;
2053 const char* getMapsPath(){
2054 return g_mapsPath.c_str();
2057 const char* getLastMapFolderPath(){
2058 if (g_strLastMapFolder.empty()) {
2059 GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
2060 if (g_strLastMapFolder.empty()) {
2061 StringOutputStream buffer( 1024 );
2062 buffer << getMapsPath();
2063 if ( !file_readable( buffer.c_str() ) ) {
2065 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
2067 g_strLastMapFolder = buffer.c_str();
2070 return g_strLastMapFolder.c_str();
2073 const char* map_open( const char* title ){
2074 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
2077 const char* map_import( const char* title ){
2078 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
2081 const char* map_save( const char* title ){
2082 return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
2086 if ( !ConfirmModified( "Open Map" ) ) {
2090 const char* filename = map_open( "Open Map" );
2092 if ( filename != NULL ) {
2093 MRU_AddFile( filename );
2096 Map_LoadFile( filename );
2101 const char* filename = map_import( "Import Map" );
2103 if ( filename != NULL ) {
2104 UndoableCommand undo( "mapImport" );
2105 Map_ImportFile( filename );
2110 const char* filename = map_save( "Save Map" );
2112 if ( filename != NULL ) {
2113 g_strLastMapFolder = g_path_get_dirname( filename );
2114 MRU_AddFile( filename );
2115 Map_Rename( filename );
2126 if ( Map_Unnamed( g_map ) ) {
2129 else if ( Map_Modified( g_map ) ) {
2131 MRU_AddFile( g_map.m_name.c_str() ); //add on saving, but not opening via cmd line: spoils the list
2136 const char* filename = map_save( "Export Selection" );
2138 if ( filename != NULL ) {
2139 g_strLastMapFolder = g_path_get_dirname( filename );
2140 Map_SaveSelected( filename );
2145 const char* filename = map_save( "Export Region" );
2147 if ( filename != NULL ) {
2148 g_strLastMapFolder = g_path_get_dirname( filename );
2149 Map_SaveRegion( filename );
2156 SceneChangeNotify();
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(),
2163 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2164 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2166 SceneChangeNotify();
2171 SceneChangeNotify();
2174 void RegionSelected(){
2175 Map_RegionSelectedBrushes();
2176 SceneChangeNotify();
2183 class BrushFindByIndexWalker : public scene::Traversable::Walker
2185 mutable std::size_t m_index;
2186 scene::Path& m_path;
2188 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
2189 : m_index( index ), m_path( path ){
2192 bool pre( scene::Node& node ) const {
2193 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
2194 m_path.push( makeReference( node ) );
2200 class EntityFindByIndexWalker : public scene::Traversable::Walker
2202 mutable std::size_t m_index;
2203 scene::Path& m_path;
2205 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
2206 : m_index( index ), m_path( path ){
2209 bool pre( scene::Node& node ) const {
2210 if ( Node_isEntity( node ) && m_index-- == 0 ) {
2211 m_path.push( makeReference( node ) );
2217 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
2218 path.push( makeReference( GlobalSceneGraph().root() ) );
2220 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
2222 if ( path.size() == 2 ) {
2223 scene::Traversable* traversable = Node_getTraversable( path.top() );
2224 if ( traversable != 0 ) {
2225 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
2230 inline bool Node_hasChildren( scene::Node& node ){
2231 scene::Traversable* traversable = Node_getTraversable( node );
2232 return traversable != 0 && !traversable->empty();
2235 void SelectBrush( int entitynum, int brushnum ){
2237 Scene_FindEntityBrush( entitynum, brushnum, path );
2238 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
2239 scene::Instance* instance = GlobalSceneGraph().find( path );
2240 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
2241 Selectable* selectable = Instance_getSelectable( *instance );
2242 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
2243 selectable->setSelected( true );
2244 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
2249 class BrushFindIndexWalker : public scene::Traversable::Walker
2251 mutable const scene::Node* m_node;
2252 std::size_t& m_count;
2254 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
2255 : m_node( &node ), m_count( count ){
2258 bool pre( scene::Node& node ) const {
2259 if ( Node_isPrimitive( node ) ) {
2260 if ( m_node == &node ) {
2271 class EntityFindIndexWalker : public scene::Traversable::Walker
2273 mutable const scene::Node* m_node;
2274 std::size_t& m_count;
2276 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2277 : m_node( &node ), m_count( count ){
2280 bool pre( scene::Node& node ) const {
2281 if ( Node_isEntity( node ) ) {
2282 if ( m_node == &node ) {
2293 static void GetSelectionIndex( int *ent, int *brush ){
2294 std::size_t count_brush = 0;
2295 std::size_t count_entity = 0;
2296 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2297 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2300 scene::Traversable* traversable = Node_getTraversable( path.parent() );
2301 if ( traversable != 0 && path.size() == 3 ) {
2302 traversable->traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2307 scene::Traversable* traversable = Node_getTraversable( GlobalSceneGraph().root() );
2308 if ( traversable != 0 ) {
2309 if( path.size() == 3 ){
2310 traversable->traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2312 else if ( path.size() == 2 ){
2313 traversable->traverse( EntityFindIndexWalker( path.top(), count_entity ) );
2318 *brush = int(count_brush);
2319 *ent = int(count_entity);
2324 ui::Entry entity{ui::null};
2325 ui::Entry brush{ui::null};
2327 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2329 auto accel = ui::AccelGroup(ui::New);
2330 window.add_accel_group( accel );
2333 auto vbox = create_dialog_vbox( 4, 4 );
2336 auto table = create_dialog_table( 2, 2, 4, 4 );
2337 vbox.pack_start( table, TRUE, TRUE, 0 );
2339 ui::Widget label = ui::Label( "Entity number" );
2341 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2344 ui::Widget label = ui::Label( "Brush number" );
2346 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2349 auto entry = ui::Entry(ui::New);
2351 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2352 gtk_widget_grab_focus( entry );
2356 auto entry = ui::Entry(ui::New);
2358 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2364 auto hbox = create_dialog_hbox( 4 );
2365 vbox.pack_start( hbox, TRUE, TRUE, 0 );
2367 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2368 hbox.pack_start( button, FALSE, FALSE, 0 );
2369 widget_make_default( button );
2370 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2373 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2374 hbox.pack_start( button, FALSE, FALSE, 0 );
2375 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2380 // Initialize dialog
2384 GetSelectionIndex( &ent, &br );
2385 sprintf( buf, "%i", ent );
2387 sprintf( buf, "%i", br );
2390 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2391 const char *entstr = gtk_entry_get_text( entity );
2392 const char *brushstr = gtk_entry_get_text( brush );
2393 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2399 void Map_constructPreferences( PreferencesPage& page ){
2400 page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
2401 page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
2405 class MapEntityClasses : public ModuleObserver
2407 std::size_t m_unrealised;
2409 MapEntityClasses() : m_unrealised( 1 ){
2413 if ( --m_unrealised == 0 ) {
2414 if ( g_map.m_resource != 0 ) {
2415 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2416 g_map.m_resource->realise();
2422 if ( ++m_unrealised == 1 ) {
2423 if ( g_map.m_resource != 0 ) {
2424 g_map.m_resource->flush();
2425 g_map.m_resource->unrealise();
2431 MapEntityClasses g_MapEntityClasses;
2434 class MapModuleObserver : public ModuleObserver
2436 std::size_t m_unrealised;
2438 MapModuleObserver() : m_unrealised( 1 ){
2442 if ( --m_unrealised == 0 ) {
2443 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2444 StringOutputStream buffer( 256 );
2445 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2446 Q_mkdir( buffer.c_str() );
2447 g_mapsPath = buffer.c_str();
2452 if ( ++m_unrealised == 1 ) {
2458 MapModuleObserver g_MapModuleObserver;
2460 CopiedString g_strLastMap;
2461 bool g_bLoadLastMap = false;
2463 void Map_Construct(){
2464 GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2465 GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2466 GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2467 //GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2468 GlobalToggles_insert( "RegionSetSelection", makeCallbackF(RegionSelected), ToggleItem::AddCallbackCaller( g_region_item ), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2470 GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2471 GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2472 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2473 GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
2475 PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2477 GlobalEntityClassManager().attach( g_MapEntityClasses );
2478 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2482 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2483 GlobalEntityClassManager().detach( g_MapEntityClasses );