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 ) );
769 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
771 class EntityBreakdownWalker : public scene::Graph::Walker
773 EntityBreakdown& m_entitymap;
775 EntityBreakdownWalker( EntityBreakdown& entitymap )
776 : m_entitymap( entitymap ){
779 bool pre( const scene::Path& path, scene::Instance& instance ) const {
780 Entity* entity = Node_getEntity( path.top() );
782 const EntityClass& eclass = entity->getEntityClass();
783 if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
784 m_entitymap[eclass.name()] = 1;
787 ++m_entitymap[eclass.name()];
794 void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
795 GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
798 class CountStuffWalker : public scene::Graph::Walker
803 int& m_groupents_ingame;
805 CountStuffWalker( int& patches, int& ents_ingame, int& groupents, int& groupents_ingame )
806 : m_patches( patches ), m_ents_ingame( ents_ingame ), m_groupents( groupents ), m_groupents_ingame( groupents_ingame ){
808 bool pre( const scene::Path& path, scene::Instance& instance ) const {
809 Patch* patch = Node_getPatch( path.top() );
813 Entity* entity = Node_getEntity( path.top() );
815 if( entity->isContainer() ){
817 if( !string_equal_nocase( "func_group", entity->getKeyValue( "classname" ) ) &&
818 !string_equal_nocase( "_decal", entity->getKeyValue( "classname" ) ) ){
819 ++m_groupents_ingame;
824 if( !string_equal_nocase_n( "light", entity->getKeyValue( "classname" ), 5 ) &&
825 !string_equal_nocase( "misc_model", entity->getKeyValue( "classname" ) ) ){
833 void Scene_CountStuff( int& patches, int& ents_ingame, int& groupents, int& groupents_ingame ){
834 GlobalSceneGraph().traverse( CountStuffWalker( patches, ents_ingame, groupents, groupents_ingame ) );
837 WindowPosition g_posMapInfoWnd( c_default_window_pos );
841 ui::Widget w_brushes{ui::null};
842 ui::Widget w_patches{ui::null};
843 ui::Widget w_ents{ui::null};
844 ui::Widget w_ents_ingame{ui::null};
845 ui::Widget w_groupents{ui::null};
846 ui::Widget w_groupents_ingame{ui::null};
848 ui::ListStore EntityBreakdownWalker{ui::null};
850 ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
852 window_set_position( window, g_posMapInfoWnd );
855 auto vbox = create_dialog_vbox( 4, 4 );
859 auto hbox = create_dialog_hbox( 4 );
860 vbox.pack_start( hbox, FALSE, FALSE, 0 );
863 auto table = create_dialog_table( 3, 4, 4, 4 );
864 hbox.pack_start( table, TRUE, TRUE, 0 );
867 auto label = ui::Label( "Total Brushes:" );
869 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
870 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
873 auto label = ui::Label( "" );
875 table.attach(label, {1, 2, 0, 1}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
879 auto label = ui::Label( "Total Patches" );
881 table.attach(label, {2, 3, 0, 1}, {GTK_FILL, 0});
882 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
885 auto label = ui::Label( "" );
887 table.attach(label, {3, 4, 0, 1}, {GTK_FILL, 0}, {3, 0});
888 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
892 auto label = ui::Label( "Total Entities:" );
894 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
895 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
898 auto label = ui::Label( "" );
900 table.attach(label, {1, 2, 1, 2}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
901 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
905 auto label = ui::Label( "Ingame Entities:" );
907 table.attach(label, {2, 3, 1, 2}, {GTK_FILL, 0});
908 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
911 auto label = ui::Label( "" );
913 table.attach(label, {3, 4, 1, 2}, {GTK_FILL | GTK_EXPAND, 0 }, {3, 0});
914 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
915 w_ents_ingame = label;
918 auto label = ui::Label( "Group Entities:" );
920 table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
921 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
924 auto label = ui::Label( "" );
926 table.attach(label, {1, 2, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
927 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
931 auto label = ui::Label( "Ingame Group Entities:" );
933 table.attach(label, {2, 3, 2, 3}, {GTK_FILL, 0});
934 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
937 auto label = ui::Label( "" );
939 table.attach(label, {3, 4, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
940 gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
941 w_groupents_ingame = label;
946 auto vbox2 = create_dialog_vbox( 4 );
947 hbox.pack_start( vbox2, FALSE, FALSE, 0 );
950 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
951 vbox2.pack_start( button, FALSE, FALSE, 0 );
956 ui::Widget label = ui::Label( "*** Entity breakdown ***" );
958 vbox.pack_start( label, FALSE, TRUE, 0 );
959 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
962 auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
963 vbox.pack_start( scr, TRUE, TRUE, 0 );
966 auto store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_UINT ));
968 auto view = ui::TreeView(ui::TreeModel::from(store._handle));
969 gtk_tree_view_set_headers_clickable(view, TRUE );
972 auto renderer = ui::CellRendererText(ui::New);
973 auto column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
974 gtk_tree_view_append_column(view, column );
975 gtk_tree_view_column_set_sort_column_id( column, 0 );
979 auto renderer = ui::CellRendererText(ui::New);
980 auto column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
981 gtk_tree_view_append_column(view, column );
982 gtk_tree_view_column_set_sort_column_id( column, 1 );
989 EntityBreakdownWalker = store;
997 EntityBreakdown entitymap;
998 Scene_EntityBreakdown( entitymap );
1000 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
1003 sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
1004 EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
1008 EntityBreakdownWalker.unref();
1011 int n_ents_ingame = 0;
1012 int n_groupents = 0;
1013 int n_groupents_ingame = 0;
1014 Scene_CountStuff( n_patches, n_ents_ingame, n_groupents, n_groupents_ingame );
1015 //globalOutputStream() << n_patches << n_ents_ingame << n_groupents << n_groupents_ingame << "\n";
1019 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span> ", Unsigned( g_brushCount.get() ) );
1020 gtk_label_set_markup( GTK_LABEL( w_brushes ), markup );
1023 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_patches );
1024 gtk_label_set_markup( GTK_LABEL( w_patches ), markup );
1027 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span> ", Unsigned( g_entityCount.get() ) );
1028 gtk_label_set_markup( GTK_LABEL( w_ents ), markup );
1031 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_ents_ingame );
1032 gtk_label_set_markup( GTK_LABEL( w_ents_ingame ), markup );
1035 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_groupents );
1036 gtk_label_set_markup( GTK_LABEL( w_groupents ), markup );
1039 markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span> ", n_groupents_ingame );
1040 gtk_label_set_markup( GTK_LABEL( w_groupents_ingame ), markup );
1044 modal_dialog_show( window, dialog );
1047 window_get_position( window, g_posMapInfoWnd );
1057 const char* m_message;
1059 ScopeTimer( const char* message )
1060 : m_message( message ){
1065 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
1066 globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
1070 CopiedString g_strLastMapFolder = "";
1078 void Map_LoadFile( const char *filename ){
1079 g_map.m_name = filename;
1081 // refresh VFS to apply new pak filtering based on mapname
1082 // needed for daemon DPK VFS
1085 globalOutputStream() << "Loading map from " << filename << "\n";
1086 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1088 MRU_AddFile( filename );
1089 g_strLastMapFolder = g_path_get_dirname( filename );
1092 ScopeTimer timer( "map load" );
1094 const MapFormat* format = NULL;
1095 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1096 if ( string_not_empty( moduleName ) ) {
1097 format = ReferenceAPI_getMapModules().findModule( moduleName );
1100 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
1105 Brush_toggleFormat( i );
1106 Map_UpdateTitle( g_map );
1108 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1110 format->wrongFormat = false;
1112 g_map.m_resource->attach( g_map );
1114 if ( !format->wrongFormat ) {
1120 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
1123 globalOutputStream() << "--- LoadMapFile ---\n";
1124 globalOutputStream() << g_map.m_name.c_str() << "\n";
1126 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
1127 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
1129 //GlobalEntityCreator().printStatistics();
1132 // move the view to a start position
1134 Map_StartPosition();
1136 g_currentMap = &g_map;
1142 virtual bool excluded( scene::Node& node ) const = 0;
1145 class ExcludeWalker : public scene::Traversable::Walker
1147 const scene::Traversable::Walker& m_walker;
1148 const Excluder* m_exclude;
1149 mutable bool m_skip;
1151 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1152 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1155 bool pre( scene::Node& node ) const {
1156 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1162 m_walker.pre( node );
1167 void post( scene::Node& node ) const {
1173 m_walker.post( node );
1178 class AnyInstanceSelected : public scene::Instantiable::Visitor
1182 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1186 void visit( scene::Instance& instance ) const {
1187 Selectable* selectable = Instance_getSelectable( instance );
1188 if ( selectable != 0
1189 && selectable->isSelected() ) {
1195 bool Node_instanceSelected( scene::Node& node ){
1196 scene::Instantiable* instantiable = Node_getInstantiable( node );
1197 ASSERT_NOTNULL( instantiable );
1199 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1203 class SelectedDescendantWalker : public scene::Traversable::Walker
1207 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1211 bool pre( scene::Node& node ) const {
1212 if ( node.isRoot() ) {
1216 if ( Node_instanceSelected( node ) ) {
1224 bool Node_selectedDescendant( scene::Node& node ){
1226 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1230 class SelectionExcluder : public Excluder
1233 bool excluded( scene::Node& node ) const {
1234 return !Node_selectedDescendant( node );
1238 class IncludeSelectedWalker : public scene::Traversable::Walker
1240 const scene::Traversable::Walker& m_walker;
1241 mutable std::size_t m_selected;
1242 mutable bool m_skip;
1244 bool selectedParent() const {
1245 return m_selected != 0;
1249 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1250 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1253 bool pre( scene::Node& node ) const {
1255 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1256 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1257 if ( Node_instanceSelected( node ) ) {
1260 m_walker.pre( node );
1270 void post( scene::Node& node ) const {
1276 if ( Node_instanceSelected( node ) ) {
1279 m_walker.post( node );
1284 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1285 scene::Traversable* traversable = Node_getTraversable( root );
1286 if ( traversable != 0 ) {
1288 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1290 traversable->traverse( IncludeSelectedWalker( walker ) );
1295 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1296 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
1299 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1300 scene::Traversable* traversable = Node_getTraversable( root );
1301 if ( traversable != 0 ) {
1302 traversable->traverse( walker );
1306 class RegionExcluder : public Excluder
1309 bool excluded( scene::Node& node ) const {
1310 return node.excluded();
1314 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1315 scene::Traversable* traversable = Node_getTraversable( root );
1316 if ( traversable != 0 ) {
1317 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1321 bool Map_SaveRegion( const char *filename ){
1324 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1326 RemoveRegionBrushes();
1332 void Map_RenameAbsolute( const char* absolute ){
1333 Resource* resource = GlobalReferenceCache().capture( absolute );
1334 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1335 resource->setNode( clone.get_pointer() );
1338 //ScopeTimer timer("clone subgraph");
1339 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1342 g_map.m_resource->detach( g_map );
1343 GlobalReferenceCache().release( g_map.m_name.c_str() );
1345 g_map.m_resource = resource;
1347 g_map.m_name = absolute;
1348 Map_UpdateTitle( g_map );
1350 g_map.m_resource->attach( g_map );
1351 // refresh VFS to apply new pak filtering based on mapname
1352 // needed for daemon DPK VFS
1356 void Map_Rename( const char* filename ){
1357 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1358 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1360 Map_RenameAbsolute( filename );
1362 SceneChangeNotify();
1373 ScopeTimer timer( "map save" );
1375 return true; // assume success..
1385 //globalOutputStream() << "Map_New\n";
1387 g_map.m_name = "unnamed.map";
1388 Map_UpdateTitle( g_map );
1391 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1392 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1393 g_map.m_resource->attach( g_map );
1395 SceneChangeNotify();
1398 FocusViews( g_vector3_identity, 0 );
1400 g_currentMap = &g_map;
1402 // restart VFS to apply new pak filtering based on mapname
1403 // needed for daemon DPK VFS
1407 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1409 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1411 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1412 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1413 for now, let's just print an error
1416 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1418 for ( int i = 0 ; i < 3 ; i++ )
1420 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1421 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1426 // write the info_playerstart
1428 sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1429 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1430 sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1431 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1435 ===========================================================
1439 ===========================================================
1441 bool region_active = false;
1443 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_region_caller( region_active );
1445 ToggleItem g_region_item( g_region_caller );
1447 /*void Map_ToggleRegion(){
1448 region_active = !region_active;
1449 g_region_item.update();
1452 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1453 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1455 scene::Node* region_sides[6];
1456 scene::Node* region_startpoint = 0;
1461 a regioned map will have temp walls put up at the region boundary
1462 \todo TODO TTimo old implementation of region brushes
1463 we still add them straight in the worldspawn and take them out after the map is saved
1464 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1467 void AddRegionBrushes( void ){
1470 for ( i = 0; i < 6; i++ )
1472 region_sides[i] = &GlobalBrushCreator().createBrush();
1473 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1476 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1478 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1479 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1481 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1484 void RemoveRegionBrushes( void ){
1485 for ( std::size_t i = 0; i < 6; i++ )
1487 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1489 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1492 inline void exclude_node( scene::Node& node, bool exclude ){
1494 ? node.enable( scene::Node::eExcluded )
1495 : node.disable( scene::Node::eExcluded );
1498 class ExcludeAllWalker : public scene::Graph::Walker
1502 ExcludeAllWalker( bool exclude )
1503 : m_exclude( exclude ){
1506 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1507 exclude_node( path.top(), m_exclude );
1513 void Scene_Exclude_All( bool exclude ){
1514 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1517 bool Instance_isSelected( const scene::Instance& instance ){
1518 const Selectable* selectable = Instance_getSelectable( instance );
1519 return selectable != 0 && selectable->isSelected();
1522 class ExcludeSelectedWalker : public scene::Graph::Walker
1526 ExcludeSelectedWalker( bool exclude )
1527 : m_exclude( exclude ){
1530 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1531 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1536 void Scene_Exclude_Selected( bool exclude ){
1537 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1540 class ExcludeRegionedWalker : public scene::Graph::Walker
1544 ExcludeRegionedWalker( bool exclude )
1545 : m_exclude( exclude ){
1548 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1553 aabb_intersects_aabb(
1554 instance.worldAABB(),
1555 aabb_for_minmax( region_mins, region_maxs )
1565 void Scene_Exclude_Region( bool exclude ){
1566 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1573 Other filtering options may still be on
1576 void Map_RegionOff(){
1577 region_active = false;
1578 g_region_item.update();
1580 region_maxs[0] = g_MaxWorldCoord - 64;
1581 region_mins[0] = g_MinWorldCoord + 64;
1582 region_maxs[1] = g_MaxWorldCoord - 64;
1583 region_mins[1] = g_MinWorldCoord + 64;
1584 region_maxs[2] = g_MaxWorldCoord - 64;
1585 region_mins[2] = g_MinWorldCoord + 64;
1587 Scene_Exclude_All( false );
1590 void Map_ApplyRegion( void ){
1591 region_active = true;
1592 g_region_item.update();
1594 Scene_Exclude_Region( false );
1599 ========================
1600 Map_RegionSelectedBrushes
1601 ========================
1603 void Map_RegionSelectedBrushes( void ){
1606 if ( GlobalSelectionSystem().countSelected() != 0
1607 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1608 region_active = true;
1609 g_region_item.update();
1610 Select_GetBounds( region_mins, region_maxs );
1612 Scene_Exclude_Selected( false );
1614 GlobalSelectionSystem().setSelectedAll( false );
1624 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1627 region_mins[0] = x_min;
1628 region_maxs[0] = x_max;
1629 region_mins[1] = y_min;
1630 region_maxs[1] = y_max;
1631 region_mins[2] = g_MinWorldCoord + 64;
1632 region_maxs[2] = g_MaxWorldCoord - 64;
1637 void Map_RegionBounds( const AABB& bounds ){
1640 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1641 region_maxs = vector3_added( bounds.origin, bounds.extents );
1653 void Map_RegionBrush( void ){
1654 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1655 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1656 Map_RegionBounds( instance.worldAABB() );
1665 bool Map_ImportFile( const char* filename ){
1666 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1668 g_strLastMapFolder = g_path_get_dirname( filename );
1670 bool success = false;
1672 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1677 const MapFormat* format = NULL;
1678 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1679 if ( string_not_empty( moduleName ) ) {
1680 format = ReferenceAPI_getMapModules().findModule( moduleName );
1684 format->wrongFormat = false;
1686 Resource* resource = GlobalReferenceCache().capture( filename );
1687 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1688 if ( !resource->load() ) {
1689 GlobalReferenceCache().release( filename );
1693 if ( format->wrongFormat ) {
1694 GlobalReferenceCache().release( filename );
1698 NodeSmartReference clone( NewMapRoot( "" ) );
1699 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1700 Map_gatherNamespaced( clone );
1701 Map_mergeClonedNames();
1704 GlobalReferenceCache().release( filename );
1707 SceneChangeNotify();
1713 const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
1714 int n = string_length( path_get_extension( filename ) );
1715 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1716 StringBuffer output;
1717 output.push_string( AppPath_get() );
1718 output.push_string( "q3map2." );
1719 output.push_string( RADIANT_EXECUTABLE );
1720 output.push_string( " -v -game " );
1721 output.push_string( ( type && *type ) ? type : "quake3" );
1722 output.push_string( " -fs_basepath \"" );
1723 output.push_string( EnginePath_get() );
1724 output.push_string( "\" -fs_homepath \"" );
1725 output.push_string( g_qeglobals.m_userEnginePath.c_str() );
1726 output.push_string( "\"" );
1729 for ( int i = 0; i < g_pakPathCount; i++ ) {
1730 if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
1731 output.push_string( " -fs_pakpath \"" );
1732 output.push_string( g_strPakPath[i].c_str() );
1733 output.push_string( "\"" );
1738 if ( g_disableEnginePath ) {
1739 output.push_string( " -fs_nobasepath " );
1742 if ( g_disableHomePath ) {
1743 output.push_string( " -fs_nohomepath " );
1746 output.push_string( " -fs_game " );
1747 output.push_string( gamename_get() );
1748 output.push_string( " -convert -format " );
1749 output.push_string( Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map" );
1750 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1751 output.push_string( " -readmap " );
1753 output.push_string( " \"" );
1754 output.push_string( filename );
1755 output.push_string( "\"" );
1758 Q_Exec( NULL, output.c_str(), NULL, false, true );
1760 // rebuild filename as "filenamewithoutext_converted.map"
1762 output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
1763 output.push_string( "_converted.map" );
1764 filename = output.c_str();
1767 Resource* resource = GlobalReferenceCache().capture( filename );
1768 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1769 if ( !resource->load() ) {
1770 GlobalReferenceCache().release( filename );
1773 NodeSmartReference clone( NewMapRoot( "" ) );
1774 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1775 Map_gatherNamespaced( clone );
1776 Map_mergeClonedNames();
1779 GlobalReferenceCache().release( filename );
1782 SceneChangeNotify();
1791 bool Map_SaveFile( const char* filename ){
1792 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1793 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1795 // refresh VFS to apply new pak filtering based on mapname
1796 // needed for daemon DPK VFS
1807 // Saves selected world brushes and whole entities with partial/full selections
1809 bool Map_SaveSelected( const char* filename ){
1810 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1813 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1815 scene::Node& m_parent;
1816 mutable bool m_emptyOldParent;
1819 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
1822 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1823 if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
1824 Selectable* selectable = Instance_getSelectable( instance );
1825 if ( selectable && selectable->isSelected() && path.size() > 1 ) {
1832 void post( const scene::Path& path, scene::Instance& instance ) const {
1833 if ( path.top().get_pointer() == &m_parent )
1836 if ( Node_isPrimitive( path.top() ) ){
1837 m_emptyOldParent = false;
1838 Selectable* selectable = Instance_getSelectable( instance );
1840 if ( selectable && selectable->isSelected() && path.size() > 1 ){
1841 scene::Node& parent = path.parent();
1842 if ( &parent != &m_parent ){
1843 NodeSmartReference node( path.top().get() );
1844 scene::Traversable* traversable_parent = Node_getTraversable( parent );
1845 traversable_parent->erase( node );
1846 Node_getTraversable( m_parent )->insert( node );
1847 if ( traversable_parent->empty() )
1848 m_emptyOldParent = true;
1852 else if ( m_emptyOldParent ){
1853 m_emptyOldParent = false;
1854 // delete empty entities
1855 Entity* entity = Node_getEntity( path.top() );
1856 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map ) && Node_getTraversable( path.top() )->empty() ) {
1857 Path_deleteTop( path );
1863 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1864 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1867 class CountSelectedBrushes : public scene::Graph::Walker
1869 std::size_t& m_count;
1870 mutable std::size_t m_depth;
1872 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1876 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1877 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1880 Selectable* selectable = Instance_getSelectable( instance );
1881 if ( selectable != 0
1882 && selectable->isSelected()
1883 && Node_isPrimitive( path.top() ) ) {
1889 void post( const scene::Path& path, scene::Instance& instance ) const {
1894 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1896 graph.traverse( CountSelectedBrushes( count ) );
1908 const char* nodetype_get_name( ENodeType type ){
1909 if ( type == eNodeMap ) {
1912 if ( type == eNodeEntity ) {
1915 if ( type == eNodePrimitive ) {
1921 ENodeType node_get_nodetype( scene::Node& node ){
1922 if ( Node_isEntity( node ) ) {
1925 if ( Node_isPrimitive( node ) ) {
1926 return eNodePrimitive;
1928 return eNodeUnknown;
1931 bool contains_entity( scene::Node& node ){
1932 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1935 bool contains_primitive( scene::Node& node ){
1936 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1939 ENodeType node_get_contains( scene::Node& node ){
1940 if ( contains_entity( node ) ) {
1943 if ( contains_primitive( node ) ) {
1944 return eNodePrimitive;
1946 return eNodeUnknown;
1949 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1950 ENodeType contains = node_get_contains( parent.top() );
1951 ENodeType type = node_get_nodetype( child.top() );
1953 if ( contains != eNodeUnknown && contains == type ) {
1954 NodeSmartReference node( child.top().get() );
1955 Path_deleteTop( child );
1956 Node_getTraversable( parent.top() )->insert( node );
1957 SceneChangeNotify();
1961 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1965 void Scene_parentSelected(){
1966 UndoableCommand undo( "parentSelected" );
1968 if ( GlobalSelectionSystem().countSelected() > 1 ) {
1969 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1971 const scene::Path& m_parent;
1973 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1976 void visit( scene::Instance& instance ) const {
1977 if ( &m_parent != &instance.path() ) {
1978 Path_parent( m_parent, instance.path() );
1983 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1984 GlobalSelectionSystem().foreachSelected( visitor );
1988 globalOutputStream() << "failed - did not find two selected nodes.\n";
1994 if ( ConfirmModified( "New Map" ) ) {
2001 CopiedString g_mapsPath;
2003 const char* getMapsPath(){
2004 return g_mapsPath.c_str();
2007 const char* getLastMapFolderPath(){
2008 if (g_strLastMapFolder.empty()) {
2009 GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
2010 if (g_strLastMapFolder.empty()) {
2011 StringOutputStream buffer( 1024 );
2012 buffer << getMapsPath();
2013 if ( !file_readable( buffer.c_str() ) ) {
2015 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
2017 g_strLastMapFolder = buffer.c_str();
2020 return g_strLastMapFolder.c_str();
2023 const char* map_open( const char* title ){
2024 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
2027 const char* map_import( const char* title ){
2028 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
2031 const char* map_save( const char* title ){
2032 return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
2036 if ( !ConfirmModified( "Open Map" ) ) {
2040 const char* filename = map_open( "Open Map" );
2042 if ( filename != NULL ) {
2043 MRU_AddFile( filename );
2046 Map_LoadFile( filename );
2051 const char* filename = map_import( "Import Map" );
2053 if ( filename != NULL ) {
2054 UndoableCommand undo( "mapImport" );
2055 Map_ImportFile( filename );
2060 const char* filename = map_save( "Save Map" );
2062 if ( filename != NULL ) {
2063 g_strLastMapFolder = g_path_get_dirname( filename );
2064 MRU_AddFile( filename );
2065 Map_Rename( filename );
2076 if ( Map_Unnamed( g_map ) ) {
2079 else if ( Map_Modified( g_map ) ) {
2081 MRU_AddFile( g_map.m_name.c_str() ); //add on saving, but not opening via cmd line: spoils the list
2086 const char* filename = map_save( "Export Selection" );
2088 if ( filename != NULL ) {
2089 g_strLastMapFolder = g_path_get_dirname( filename );
2090 Map_SaveSelected( filename );
2095 const char* filename = map_save( "Export Region" );
2097 if ( filename != NULL ) {
2098 g_strLastMapFolder = g_path_get_dirname( filename );
2099 Map_SaveRegion( filename );
2106 SceneChangeNotify();
2111 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2112 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2113 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2114 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2116 SceneChangeNotify();
2121 SceneChangeNotify();
2124 void RegionSelected(){
2125 Map_RegionSelectedBrushes();
2126 SceneChangeNotify();
2133 class BrushFindByIndexWalker : public scene::Traversable::Walker
2135 mutable std::size_t m_index;
2136 scene::Path& m_path;
2138 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
2139 : m_index( index ), m_path( path ){
2142 bool pre( scene::Node& node ) const {
2143 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
2144 m_path.push( makeReference( node ) );
2150 class EntityFindByIndexWalker : public scene::Traversable::Walker
2152 mutable std::size_t m_index;
2153 scene::Path& m_path;
2155 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
2156 : m_index( index ), m_path( path ){
2159 bool pre( scene::Node& node ) const {
2160 if ( Node_isEntity( node ) && m_index-- == 0 ) {
2161 m_path.push( makeReference( node ) );
2167 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
2168 path.push( makeReference( GlobalSceneGraph().root() ) );
2170 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
2172 if ( path.size() == 2 ) {
2173 scene::Traversable* traversable = Node_getTraversable( path.top() );
2174 if ( traversable != 0 ) {
2175 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
2180 inline bool Node_hasChildren( scene::Node& node ){
2181 scene::Traversable* traversable = Node_getTraversable( node );
2182 return traversable != 0 && !traversable->empty();
2185 void SelectBrush( int entitynum, int brushnum ){
2187 Scene_FindEntityBrush( entitynum, brushnum, path );
2188 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
2189 scene::Instance* instance = GlobalSceneGraph().find( path );
2190 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
2191 Selectable* selectable = Instance_getSelectable( *instance );
2192 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
2193 selectable->setSelected( true );
2194 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
2199 class BrushFindIndexWalker : public scene::Graph::Walker
2201 mutable const scene::Node* m_node;
2202 std::size_t& m_count;
2204 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
2205 : m_node( &node ), m_count( count ){
2208 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2209 if ( Node_isPrimitive( path.top() ) ) {
2210 if ( m_node == path.top().get_pointer() ) {
2221 class EntityFindIndexWalker : public scene::Graph::Walker
2223 mutable const scene::Node* m_node;
2224 std::size_t& m_count;
2226 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2227 : m_node( &node ), m_count( count ){
2230 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2231 if ( Node_isEntity( path.top() ) ) {
2232 if ( m_node == path.top().get_pointer() ) {
2243 static void GetSelectionIndex( int *ent, int *brush ){
2244 std::size_t count_brush = 0;
2245 std::size_t count_entity = 0;
2246 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2247 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2249 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2250 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2252 *brush = int(count_brush);
2253 *ent = int(count_entity);
2258 ui::Entry entity{ui::null};
2259 ui::Entry brush{ui::null};
2261 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2263 auto accel = ui::AccelGroup(ui::New);
2264 window.add_accel_group( accel );
2267 auto vbox = create_dialog_vbox( 4, 4 );
2270 auto table = create_dialog_table( 2, 2, 4, 4 );
2271 vbox.pack_start( table, TRUE, TRUE, 0 );
2273 ui::Widget label = ui::Label( "Entity number" );
2275 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2278 ui::Widget label = ui::Label( "Brush number" );
2280 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2283 auto entry = ui::Entry(ui::New);
2285 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2286 gtk_widget_grab_focus( entry );
2290 auto entry = ui::Entry(ui::New);
2292 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2298 auto hbox = create_dialog_hbox( 4 );
2299 vbox.pack_start( hbox, TRUE, TRUE, 0 );
2301 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2302 hbox.pack_start( button, FALSE, FALSE, 0 );
2303 widget_make_default( button );
2304 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2307 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2308 hbox.pack_start( button, FALSE, FALSE, 0 );
2309 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2314 // Initialize dialog
2318 GetSelectionIndex( &ent, &br );
2319 sprintf( buf, "%i", ent );
2321 sprintf( buf, "%i", br );
2324 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2325 const char *entstr = gtk_entry_get_text( entity );
2326 const char *brushstr = gtk_entry_get_text( brush );
2327 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2333 void Map_constructPreferences( PreferencesPage& page ){
2334 page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
2335 page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
2339 class MapEntityClasses : public ModuleObserver
2341 std::size_t m_unrealised;
2343 MapEntityClasses() : m_unrealised( 1 ){
2347 if ( --m_unrealised == 0 ) {
2348 if ( g_map.m_resource != 0 ) {
2349 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2350 g_map.m_resource->realise();
2356 if ( ++m_unrealised == 1 ) {
2357 if ( g_map.m_resource != 0 ) {
2358 g_map.m_resource->flush();
2359 g_map.m_resource->unrealise();
2365 MapEntityClasses g_MapEntityClasses;
2368 class MapModuleObserver : public ModuleObserver
2370 std::size_t m_unrealised;
2372 MapModuleObserver() : m_unrealised( 1 ){
2376 if ( --m_unrealised == 0 ) {
2377 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2378 StringOutputStream buffer( 256 );
2379 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2380 Q_mkdir( buffer.c_str() );
2381 g_mapsPath = buffer.c_str();
2386 if ( ++m_unrealised == 1 ) {
2392 MapModuleObserver g_MapModuleObserver;
2394 CopiedString g_strLastMap;
2395 bool g_bLoadLastMap = false;
2397 void Map_Construct(){
2398 GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2399 GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2400 GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2401 //GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2402 GlobalToggles_insert( "RegionSetSelection", makeCallbackF(RegionSelected), ToggleItem::AddCallbackCaller( g_region_item ), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2404 GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2405 GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2406 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2407 GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
2409 PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2411 GlobalEntityClassManager().attach( g_MapEntityClasses );
2412 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2416 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2417 GlobalEntityClassManager().detach( g_MapEntityClasses );