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 );
1091 bool switch_format = false;
1094 ScopeTimer timer( "map load" );
1096 const MapFormat* format = NULL;
1097 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1098 if ( string_not_empty( moduleName ) ) {
1099 format = ReferenceAPI_getMapModules().findModule( moduleName );
1102 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
1107 Brush_toggleFormat( i );
1108 Map_UpdateTitle( g_map );
1110 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1112 format->wrongFormat = false;
1114 g_map.m_resource->attach( g_map );
1116 if ( !format->wrongFormat ) {
1119 switch_format = !switch_format;
1123 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
1126 globalOutputStream() << "--- LoadMapFile ---\n";
1127 globalOutputStream() << g_map.m_name.c_str() << "\n";
1129 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
1130 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
1132 //GlobalEntityCreator().printStatistics();
1135 // move the view to a start position
1137 Map_StartPosition();
1139 g_currentMap = &g_map;
1141 Brush_switchFormat( switch_format );
1147 virtual bool excluded( scene::Node& node ) const = 0;
1150 class ExcludeWalker : public scene::Traversable::Walker
1152 const scene::Traversable::Walker& m_walker;
1153 const Excluder* m_exclude;
1154 mutable bool m_skip;
1156 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1157 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1160 bool pre( scene::Node& node ) const {
1161 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1167 m_walker.pre( node );
1172 void post( scene::Node& node ) const {
1178 m_walker.post( node );
1183 class AnyInstanceSelected : public scene::Instantiable::Visitor
1187 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1191 void visit( scene::Instance& instance ) const {
1192 Selectable* selectable = Instance_getSelectable( instance );
1193 if ( selectable != 0
1194 && selectable->isSelected() ) {
1200 bool Node_instanceSelected( scene::Node& node ){
1201 scene::Instantiable* instantiable = Node_getInstantiable( node );
1202 ASSERT_NOTNULL( instantiable );
1204 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1208 class SelectedDescendantWalker : public scene::Traversable::Walker
1212 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1216 bool pre( scene::Node& node ) const {
1217 if ( node.isRoot() ) {
1221 if ( Node_instanceSelected( node ) ) {
1229 bool Node_selectedDescendant( scene::Node& node ){
1231 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1235 class SelectionExcluder : public Excluder
1238 bool excluded( scene::Node& node ) const {
1239 return !Node_selectedDescendant( node );
1243 class IncludeSelectedWalker : public scene::Traversable::Walker
1245 const scene::Traversable::Walker& m_walker;
1246 mutable std::size_t m_selected;
1247 mutable bool m_skip;
1249 bool selectedParent() const {
1250 return m_selected != 0;
1254 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1255 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1258 bool pre( scene::Node& node ) const {
1260 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1261 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1262 if ( Node_instanceSelected( node ) ) {
1265 m_walker.pre( node );
1275 void post( scene::Node& node ) const {
1281 if ( Node_instanceSelected( node ) ) {
1284 m_walker.post( node );
1289 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1290 scene::Traversable* traversable = Node_getTraversable( root );
1291 if ( traversable != 0 ) {
1293 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1295 traversable->traverse( IncludeSelectedWalker( walker ) );
1300 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1301 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
1304 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1305 scene::Traversable* traversable = Node_getTraversable( root );
1306 if ( traversable != 0 ) {
1307 traversable->traverse( walker );
1311 class RegionExcluder : public Excluder
1314 bool excluded( scene::Node& node ) const {
1315 return node.excluded();
1319 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1320 scene::Traversable* traversable = Node_getTraversable( root );
1321 if ( traversable != 0 ) {
1322 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1326 bool Map_SaveRegion( const char *filename ){
1329 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1331 RemoveRegionBrushes();
1337 void Map_RenameAbsolute( const char* absolute ){
1338 Resource* resource = GlobalReferenceCache().capture( absolute );
1339 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1340 resource->setNode( clone.get_pointer() );
1343 //ScopeTimer timer("clone subgraph");
1344 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1347 g_map.m_resource->detach( g_map );
1348 GlobalReferenceCache().release( g_map.m_name.c_str() );
1350 g_map.m_resource = resource;
1352 g_map.m_name = absolute;
1353 Map_UpdateTitle( g_map );
1355 g_map.m_resource->attach( g_map );
1356 // refresh VFS to apply new pak filtering based on mapname
1357 // needed for daemon DPK VFS
1361 void Map_Rename( const char* filename ){
1362 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1363 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1365 Map_RenameAbsolute( filename );
1367 SceneChangeNotify();
1378 ScopeTimer timer( "map save" );
1380 return true; // assume success..
1390 //globalOutputStream() << "Map_New\n";
1392 g_map.m_name = "unnamed.map";
1393 Map_UpdateTitle( g_map );
1396 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1397 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1398 g_map.m_resource->attach( g_map );
1400 SceneChangeNotify();
1403 FocusViews( g_vector3_identity, 0 );
1405 g_currentMap = &g_map;
1407 // restart VFS to apply new pak filtering based on mapname
1408 // needed for daemon DPK VFS
1412 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1414 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1416 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1417 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1418 for now, let's just print an error
1421 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1423 for ( int i = 0 ; i < 3 ; i++ )
1425 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1426 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1431 // write the info_playerstart
1433 sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1434 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1435 sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1436 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1440 ===========================================================
1444 ===========================================================
1446 bool region_active = false;
1448 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_region_caller( region_active );
1450 ToggleItem g_region_item( g_region_caller );
1452 /*void Map_ToggleRegion(){
1453 region_active = !region_active;
1454 g_region_item.update();
1457 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1458 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1460 scene::Node* region_sides[6];
1461 scene::Node* region_startpoint = 0;
1466 a regioned map will have temp walls put up at the region boundary
1467 \todo TODO TTimo old implementation of region brushes
1468 we still add them straight in the worldspawn and take them out after the map is saved
1469 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1472 void AddRegionBrushes( void ){
1475 for ( i = 0; i < 6; i++ )
1477 region_sides[i] = &GlobalBrushCreator().createBrush();
1478 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1481 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1483 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1484 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1486 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1489 void RemoveRegionBrushes( void ){
1490 for ( std::size_t i = 0; i < 6; i++ )
1492 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1494 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1497 inline void exclude_node( scene::Node& node, bool exclude ){
1499 ? node.enable( scene::Node::eExcluded )
1500 : node.disable( scene::Node::eExcluded );
1503 class ExcludeAllWalker : public scene::Graph::Walker
1507 ExcludeAllWalker( bool exclude )
1508 : m_exclude( exclude ){
1511 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1512 exclude_node( path.top(), m_exclude );
1518 void Scene_Exclude_All( bool exclude ){
1519 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1522 bool Instance_isSelected( const scene::Instance& instance ){
1523 const Selectable* selectable = Instance_getSelectable( instance );
1524 return selectable != 0 && selectable->isSelected();
1527 class ExcludeSelectedWalker : public scene::Graph::Walker
1531 ExcludeSelectedWalker( bool exclude )
1532 : m_exclude( exclude ){
1535 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1536 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1541 void Scene_Exclude_Selected( bool exclude ){
1542 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1545 class ExcludeRegionedWalker : public scene::Graph::Walker
1549 ExcludeRegionedWalker( bool exclude )
1550 : m_exclude( exclude ){
1553 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1558 aabb_intersects_aabb(
1559 instance.worldAABB(),
1560 aabb_for_minmax( region_mins, region_maxs )
1570 void Scene_Exclude_Region( bool exclude ){
1571 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1578 Other filtering options may still be on
1581 void Map_RegionOff(){
1582 region_active = false;
1583 g_region_item.update();
1585 region_maxs[0] = g_MaxWorldCoord - 64;
1586 region_mins[0] = g_MinWorldCoord + 64;
1587 region_maxs[1] = g_MaxWorldCoord - 64;
1588 region_mins[1] = g_MinWorldCoord + 64;
1589 region_maxs[2] = g_MaxWorldCoord - 64;
1590 region_mins[2] = g_MinWorldCoord + 64;
1592 Scene_Exclude_All( false );
1595 void Map_ApplyRegion( void ){
1596 region_active = true;
1597 g_region_item.update();
1599 Scene_Exclude_Region( false );
1604 ========================
1605 Map_RegionSelectedBrushes
1606 ========================
1608 void Map_RegionSelectedBrushes( void ){
1611 if ( GlobalSelectionSystem().countSelected() != 0
1612 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1613 region_active = true;
1614 g_region_item.update();
1615 Select_GetBounds( region_mins, region_maxs );
1617 Scene_Exclude_Selected( false );
1619 GlobalSelectionSystem().setSelectedAll( false );
1629 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1632 region_mins[0] = x_min;
1633 region_maxs[0] = x_max;
1634 region_mins[1] = y_min;
1635 region_maxs[1] = y_max;
1636 region_mins[2] = g_MinWorldCoord + 64;
1637 region_maxs[2] = g_MaxWorldCoord - 64;
1642 void Map_RegionBounds( const AABB& bounds ){
1645 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1646 region_maxs = vector3_added( bounds.origin, bounds.extents );
1658 void Map_RegionBrush( void ){
1659 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1660 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1661 Map_RegionBounds( instance.worldAABB() );
1670 bool Map_ImportFile( const char* filename ){
1671 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1673 g_strLastMapFolder = g_path_get_dirname( filename );
1675 bool success = false;
1677 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1682 const MapFormat* format = NULL;
1683 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1684 if ( string_not_empty( moduleName ) ) {
1685 format = ReferenceAPI_getMapModules().findModule( moduleName );
1689 format->wrongFormat = false;
1691 Resource* resource = GlobalReferenceCache().capture( filename );
1692 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1693 if ( !resource->load() ) {
1694 GlobalReferenceCache().release( filename );
1698 if ( format->wrongFormat ) {
1699 GlobalReferenceCache().release( filename );
1703 NodeSmartReference clone( NewMapRoot( "" ) );
1704 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1705 Map_gatherNamespaced( clone );
1706 Map_mergeClonedNames();
1709 GlobalReferenceCache().release( filename );
1712 SceneChangeNotify();
1718 const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
1719 int n = string_length( path_get_extension( filename ) );
1720 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1722 output += AppPath_get();
1724 output += GDEF_OS_EXE_EXT;
1726 output += " -v -game ";
1727 output += ( type && *type ) ? type : "quake3";
1728 output += " -fs_basepath \"";
1729 output += EnginePath_get();
1730 output += "\" -fs_homepath \"";
1731 output += g_qeglobals.m_userEnginePath.c_str();
1735 for ( int i = 0; i < g_pakPathCount; i++ ) {
1736 if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
1737 output += " -fs_pakpath \"";
1738 output += g_strPakPath[i].c_str();
1744 if ( g_disableEnginePath ) {
1745 output += " -fs_nobasepath ";
1748 if ( g_disableHomePath ) {
1749 output += " -fs_nohomepath ";
1752 output += " -fs_game ";
1753 output += gamename_get();
1754 output += " -convert -format ";
1755 output += Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map";
1756 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1757 output += " -readmap ";
1764 Q_Exec( NULL, output.c_str(), NULL, false, true );
1766 // rebuild filename as "filenamewithoutext_converted.map"
1768 output.append( filename, string_length( filename ) - ( n + 1 ) );
1769 output += "_converted.map";
1770 filename = output.c_str();
1773 Resource* resource = GlobalReferenceCache().capture( filename );
1774 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1775 if ( !resource->load() ) {
1776 GlobalReferenceCache().release( filename );
1779 NodeSmartReference clone( NewMapRoot( "" ) );
1780 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1781 Map_gatherNamespaced( clone );
1782 Map_mergeClonedNames();
1785 GlobalReferenceCache().release( filename );
1788 SceneChangeNotify();
1797 bool Map_SaveFile( const char* filename ){
1798 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1799 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1801 // refresh VFS to apply new pak filtering based on mapname
1802 // needed for daemon DPK VFS
1813 // Saves selected world brushes and whole entities with partial/full selections
1815 bool Map_SaveSelected( const char* filename ){
1816 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1819 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1821 scene::Node& m_parent;
1822 mutable bool m_emptyOldParent;
1825 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
1828 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1829 if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
1830 Selectable* selectable = Instance_getSelectable( instance );
1831 if ( selectable && selectable->isSelected() && path.size() > 1 ) {
1838 void post( const scene::Path& path, scene::Instance& instance ) const {
1839 if ( path.top().get_pointer() == &m_parent )
1842 if ( Node_isPrimitive( path.top() ) ){
1843 m_emptyOldParent = false;
1844 Selectable* selectable = Instance_getSelectable( instance );
1846 if ( selectable && selectable->isSelected() && path.size() > 1 ){
1847 scene::Node& parent = path.parent();
1848 if ( &parent != &m_parent ){
1849 NodeSmartReference node( path.top().get() );
1850 scene::Traversable* traversable_parent = Node_getTraversable( parent );
1851 traversable_parent->erase( node );
1852 Node_getTraversable( m_parent )->insert( node );
1853 if ( traversable_parent->empty() )
1854 m_emptyOldParent = true;
1858 else if ( m_emptyOldParent ){
1859 m_emptyOldParent = false;
1860 // delete empty entities
1861 Entity* entity = Node_getEntity( path.top() );
1862 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map ) && Node_getTraversable( path.top() )->empty() ) {
1863 Path_deleteTop( path );
1869 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1870 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1873 class CountSelectedBrushes : public scene::Graph::Walker
1875 std::size_t& m_count;
1876 mutable std::size_t m_depth;
1878 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1882 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1883 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1886 Selectable* selectable = Instance_getSelectable( instance );
1887 if ( selectable != 0
1888 && selectable->isSelected()
1889 && Node_isPrimitive( path.top() ) ) {
1895 void post( const scene::Path& path, scene::Instance& instance ) const {
1900 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1902 graph.traverse( CountSelectedBrushes( count ) );
1914 const char* nodetype_get_name( ENodeType type ){
1915 if ( type == eNodeMap ) {
1918 if ( type == eNodeEntity ) {
1921 if ( type == eNodePrimitive ) {
1927 ENodeType node_get_nodetype( scene::Node& node ){
1928 if ( Node_isEntity( node ) ) {
1931 if ( Node_isPrimitive( node ) ) {
1932 return eNodePrimitive;
1934 return eNodeUnknown;
1937 bool contains_entity( scene::Node& node ){
1938 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1941 bool contains_primitive( scene::Node& node ){
1942 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1945 ENodeType node_get_contains( scene::Node& node ){
1946 if ( contains_entity( node ) ) {
1949 if ( contains_primitive( node ) ) {
1950 return eNodePrimitive;
1952 return eNodeUnknown;
1955 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1956 ENodeType contains = node_get_contains( parent.top() );
1957 ENodeType type = node_get_nodetype( child.top() );
1959 if ( contains != eNodeUnknown && contains == type ) {
1960 NodeSmartReference node( child.top().get() );
1961 Path_deleteTop( child );
1962 Node_getTraversable( parent.top() )->insert( node );
1963 SceneChangeNotify();
1967 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1971 void Scene_parentSelected(){
1972 UndoableCommand undo( "parentSelected" );
1974 if ( GlobalSelectionSystem().countSelected() > 1 ) {
1975 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1977 const scene::Path& m_parent;
1979 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1982 void visit( scene::Instance& instance ) const {
1983 if ( &m_parent != &instance.path() ) {
1984 Path_parent( m_parent, instance.path() );
1989 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1990 GlobalSelectionSystem().foreachSelected( visitor );
1994 globalOutputStream() << "failed - did not find two selected nodes.\n";
2000 if ( ConfirmModified( "New Map" ) ) {
2007 CopiedString g_mapsPath;
2009 const char* getMapsPath(){
2010 return g_mapsPath.c_str();
2013 const char* getLastMapFolderPath(){
2014 if (g_strLastMapFolder.empty()) {
2015 GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
2016 if (g_strLastMapFolder.empty()) {
2017 StringOutputStream buffer( 1024 );
2018 buffer << getMapsPath();
2019 if ( !file_readable( buffer.c_str() ) ) {
2021 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
2023 g_strLastMapFolder = buffer.c_str();
2026 return g_strLastMapFolder.c_str();
2029 const char* map_open( const char* title ){
2030 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
2033 const char* map_import( const char* title ){
2034 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
2037 const char* map_save( const char* title ){
2038 return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
2042 if ( !ConfirmModified( "Open Map" ) ) {
2046 const char* filename = map_open( "Open Map" );
2048 if ( filename != NULL ) {
2049 MRU_AddFile( filename );
2052 Map_LoadFile( filename );
2057 const char* filename = map_import( "Import Map" );
2059 if ( filename != NULL ) {
2060 UndoableCommand undo( "mapImport" );
2061 Map_ImportFile( filename );
2066 const char* filename = map_save( "Save Map" );
2068 if ( filename != NULL ) {
2069 g_strLastMapFolder = g_path_get_dirname( filename );
2070 MRU_AddFile( filename );
2071 Map_Rename( filename );
2082 if ( Map_Unnamed( g_map ) ) {
2085 else if ( Map_Modified( g_map ) ) {
2087 MRU_AddFile( g_map.m_name.c_str() ); //add on saving, but not opening via cmd line: spoils the list
2092 const char* filename = map_save( "Export Selection" );
2094 if ( filename != NULL ) {
2095 g_strLastMapFolder = g_path_get_dirname( filename );
2096 Map_SaveSelected( filename );
2101 const char* filename = map_save( "Export Region" );
2103 if ( filename != NULL ) {
2104 g_strLastMapFolder = g_path_get_dirname( filename );
2105 Map_SaveRegion( filename );
2112 SceneChangeNotify();
2117 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2118 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2119 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2120 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2122 SceneChangeNotify();
2127 SceneChangeNotify();
2130 void RegionSelected(){
2131 Map_RegionSelectedBrushes();
2132 SceneChangeNotify();
2139 class BrushFindByIndexWalker : public scene::Traversable::Walker
2141 mutable std::size_t m_index;
2142 scene::Path& m_path;
2144 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
2145 : m_index( index ), m_path( path ){
2148 bool pre( scene::Node& node ) const {
2149 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
2150 m_path.push( makeReference( node ) );
2156 class EntityFindByIndexWalker : public scene::Traversable::Walker
2158 mutable std::size_t m_index;
2159 scene::Path& m_path;
2161 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
2162 : m_index( index ), m_path( path ){
2165 bool pre( scene::Node& node ) const {
2166 if ( Node_isEntity( node ) && m_index-- == 0 ) {
2167 m_path.push( makeReference( node ) );
2173 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
2174 path.push( makeReference( GlobalSceneGraph().root() ) );
2176 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
2178 if ( path.size() == 2 ) {
2179 scene::Traversable* traversable = Node_getTraversable( path.top() );
2180 if ( traversable != 0 ) {
2181 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
2186 inline bool Node_hasChildren( scene::Node& node ){
2187 scene::Traversable* traversable = Node_getTraversable( node );
2188 return traversable != 0 && !traversable->empty();
2191 void SelectBrush( int entitynum, int brushnum ){
2193 Scene_FindEntityBrush( entitynum, brushnum, path );
2194 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
2195 scene::Instance* instance = GlobalSceneGraph().find( path );
2196 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
2197 Selectable* selectable = Instance_getSelectable( *instance );
2198 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
2199 selectable->setSelected( true );
2200 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
2205 class BrushFindIndexWalker : public scene::Traversable::Walker
2207 mutable const scene::Node* m_node;
2208 std::size_t& m_count;
2210 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
2211 : m_node( &node ), m_count( count ){
2214 bool pre( scene::Node& node ) const {
2215 if ( Node_isPrimitive( node ) ) {
2216 if ( m_node == &node ) {
2227 class EntityFindIndexWalker : public scene::Traversable::Walker
2229 mutable const scene::Node* m_node;
2230 std::size_t& m_count;
2232 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2233 : m_node( &node ), m_count( count ){
2236 bool pre( scene::Node& node ) const {
2237 if ( Node_isEntity( node ) ) {
2238 if ( m_node == &node ) {
2249 static void GetSelectionIndex( int *ent, int *brush ){
2250 std::size_t count_brush = 0;
2251 std::size_t count_entity = 0;
2252 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2253 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2256 scene::Traversable* traversable = Node_getTraversable( path.parent() );
2257 if ( traversable != 0 && path.size() == 3 ) {
2258 traversable->traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2263 scene::Traversable* traversable = Node_getTraversable( GlobalSceneGraph().root() );
2264 if ( traversable != 0 ) {
2265 if( path.size() == 3 ){
2266 traversable->traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2268 else if ( path.size() == 2 ){
2269 traversable->traverse( EntityFindIndexWalker( path.top(), count_entity ) );
2274 *brush = int(count_brush);
2275 *ent = int(count_entity);
2280 ui::Entry entity{ui::null};
2281 ui::Entry brush{ui::null};
2283 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2285 auto accel = ui::AccelGroup(ui::New);
2286 window.add_accel_group( accel );
2289 auto vbox = create_dialog_vbox( 4, 4 );
2292 auto table = create_dialog_table( 2, 2, 4, 4 );
2293 vbox.pack_start( table, TRUE, TRUE, 0 );
2295 ui::Widget label = ui::Label( "Entity number" );
2297 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2300 ui::Widget label = ui::Label( "Brush number" );
2302 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2305 auto entry = ui::Entry(ui::New);
2307 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2308 gtk_widget_grab_focus( entry );
2312 auto entry = ui::Entry(ui::New);
2314 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2320 auto hbox = create_dialog_hbox( 4 );
2321 vbox.pack_start( hbox, TRUE, TRUE, 0 );
2323 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2324 hbox.pack_start( button, FALSE, FALSE, 0 );
2325 widget_make_default( button );
2326 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2329 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2330 hbox.pack_start( button, FALSE, FALSE, 0 );
2331 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2336 // Initialize dialog
2340 GetSelectionIndex( &ent, &br );
2341 sprintf( buf, "%i", ent );
2343 sprintf( buf, "%i", br );
2346 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2347 const char *entstr = gtk_entry_get_text( entity );
2348 const char *brushstr = gtk_entry_get_text( brush );
2349 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2355 void Map_constructPreferences( PreferencesPage& page ){
2356 page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
2357 page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
2361 class MapEntityClasses : public ModuleObserver
2363 std::size_t m_unrealised;
2365 MapEntityClasses() : m_unrealised( 1 ){
2369 if ( --m_unrealised == 0 ) {
2370 if ( g_map.m_resource != 0 ) {
2371 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2372 g_map.m_resource->realise();
2378 if ( ++m_unrealised == 1 ) {
2379 if ( g_map.m_resource != 0 ) {
2380 g_map.m_resource->flush();
2381 g_map.m_resource->unrealise();
2387 MapEntityClasses g_MapEntityClasses;
2390 class MapModuleObserver : public ModuleObserver
2392 std::size_t m_unrealised;
2394 MapModuleObserver() : m_unrealised( 1 ){
2398 if ( --m_unrealised == 0 ) {
2399 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2400 StringOutputStream buffer( 256 );
2401 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2402 Q_mkdir( buffer.c_str() );
2403 g_mapsPath = buffer.c_str();
2408 if ( ++m_unrealised == 1 ) {
2414 MapModuleObserver g_MapModuleObserver;
2416 CopiedString g_strLastMap;
2417 bool g_bLoadLastMap = false;
2419 void Map_Construct(){
2420 GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2421 GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2422 GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2423 //GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2424 GlobalToggles_insert( "RegionSetSelection", makeCallbackF(RegionSelected), ToggleItem::AddCallbackCaller( g_region_item ), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2426 GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2427 GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2428 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2429 GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
2431 PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2433 GlobalEntityClassManager().attach( g_MapEntityClasses );
2434 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2438 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2439 GlobalEntityClassManager().detach( g_MapEntityClasses );