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
24 #include "debugging/debugging.h"
27 MapModules& ReferenceAPI_getMapModules();
28 #include "iselection.h"
32 #include "ireference.h"
33 #include "ifiletypes.h"
39 #include "ifilesystem.h"
40 #include "namespace.h"
41 #include "moduleobserver.h"
45 #include <gtk/gtkmain.h>
46 #include <gtk/gtkbox.h>
47 #include <gtk/gtkentry.h>
48 #include <gtk/gtklabel.h>
49 #include <gtk/gtktable.h>
50 #include <gtk/gtktreemodel.h>
51 #include <gtk/gtktreeview.h>
52 #include <gtk/gtkliststore.h>
53 #include <gtk/gtkcellrenderertext.h>
56 #include "transformlib.h"
57 #include "selectionlib.h"
58 #include "instancelib.h"
59 #include "traverselib.h"
61 #include "eclasslib.h"
63 #include "stream/textfilestream.h"
65 #include "uniquenames.h"
66 #include "modulesystem/singletonmodule.h"
67 #include "modulesystem/moduleregistry.h"
68 #include "stream/stringstream.h"
69 #include "signal/signal.h"
71 #include "gtkutil/filechooser.h"
75 #include "filetypes.h"
77 #include "entityinspector.h"
80 #include "camwindow.h"
82 #include "mainframe.h"
83 #include "preferences.h"
84 #include "preferencesystem.h"
85 #include "referencecache.h"
89 #include "brushmodule.h"
99 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
100 m_names.insert( name_read( c_str() ) );
105 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
106 m_names.erase( name_read( c_str() ) );
110 NameObserver& operator=( const NameObserver& other );
112 NameObserver( UniqueNames& names ) : m_names( names ){
115 NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
122 return string_empty( c_str() );
124 const char* c_str() const {
125 return m_name.c_str();
127 void nameChanged( const char* name ){
132 typedef MemberCaller1<NameObserver, const char*, &NameObserver::nameChanged> NameChangedCaller;
135 class BasicNamespace : public Namespace
137 typedef std::map<NameCallback, NameObserver> Names;
139 UniqueNames m_uniqueNames;
142 ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
144 void attach( const NameCallback& setName, const NameCallbackCallback& attachObserver ){
145 std::pair<Names::iterator, bool> result = m_names.insert( Names::value_type( setName, m_uniqueNames ) );
146 ASSERT_MESSAGE( result.second, "cannot attach name" );
147 attachObserver( NameObserver::NameChangedCaller( ( *result.first ).second ) );
148 //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
150 void detach( const NameCallback& setName, const NameCallbackCallback& detachObserver ){
151 Names::iterator i = m_names.find( setName );
152 ASSERT_MESSAGE( i != m_names.end(), "cannot detach name" );
153 //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
154 detachObserver( NameObserver::NameChangedCaller( ( *i ).second ) );
158 void makeUnique( const char* name, const NameCallback& setName ) const {
160 name_write( buffer, m_uniqueNames.make_unique( name_read( name ) ) );
164 void mergeNames( const BasicNamespace& other ) const {
165 typedef std::list<NameCallback> SetNameCallbacks;
166 typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
169 UniqueNames uniqueNames( other.m_uniqueNames );
171 for ( Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
173 groups[( *i ).second.c_str()].push_back( ( *i ).first );
176 for ( NameGroups::iterator i = groups.begin(); i != groups.end(); ++i )
178 name_t uniqueName( uniqueNames.make_unique( name_read( ( *i ).first.c_str() ) ) );
179 uniqueNames.insert( uniqueName );
182 name_write( buffer, uniqueName );
184 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
186 SetNameCallbacks& setNameCallbacks = ( *i ).second;
188 for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j )
196 BasicNamespace g_defaultNamespace;
197 BasicNamespace g_cloneNamespace;
201 Namespace* m_namespace;
203 typedef Namespace Type;
204 STRING_CONSTANT( Name, "*" );
207 m_namespace = &g_defaultNamespace;
209 Namespace* getTable(){
214 typedef SingletonModule<NamespaceAPI> NamespaceModule;
215 typedef Static<NamespaceModule> StaticNamespaceModule;
216 StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() );
219 std::list<Namespaced*> g_cloned;
221 inline Namespaced* Node_getNamespaced( scene::Node& node ){
222 return NodeTypeCast<Namespaced>::cast( node );
225 void Node_gatherNamespaced( scene::Node& node ){
226 Namespaced* namespaced = Node_getNamespaced( node );
227 if ( namespaced != 0 ) {
228 g_cloned.push_back( namespaced );
232 class GatherNamespaced : public scene::Traversable::Walker
235 bool pre( scene::Node& node ) const {
236 Node_gatherNamespaced( node );
241 void Map_gatherNamespaced( scene::Node& root ){
242 Node_traverseSubgraph( root, GatherNamespaced() );
245 void Map_mergeClonedNames(){
246 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
248 ( *i )->setNamespace( g_cloneNamespace );
250 g_cloneNamespace.mergeNames( g_defaultNamespace );
251 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
253 ( *i )->setNamespace( g_defaultNamespace );
266 void set( scene::Node* node ){
275 scene::Node* get() const {
281 void Map_SetValid( Map& map, bool valid );
282 void Map_UpdateTitle( const Map& map );
283 void Map_SetWorldspawn( Map& map, scene::Node* node );
286 class Map : public ModuleObserver
290 Resource* m_resource;
294 void ( *m_modified_changed )( const Map& );
296 Signal0 m_mapValidCallbacks;
298 WorldNode m_world_node; // "classname" "worldspawn" !
300 Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){
304 if ( m_resource != 0 ) {
305 if ( Map_Unnamed( *this ) ) {
306 g_map.m_resource->setNode( NewMapRoot( "" ).get_pointer() );
307 MapFile* map = Node_getMapFile( *g_map.m_resource->getNode() );
317 GlobalSceneGraph().insert_root( *m_resource->getNode() );
321 Map_SetValid( g_map, true );
325 if ( m_resource != 0 ) {
326 Map_SetValid( g_map, false );
327 Map_SetWorldspawn( g_map, 0 );
330 GlobalUndoSystem().clear();
332 GlobalSceneGraph().erase_root();
338 Map* g_currentMap = 0;
340 void Map_addValidCallback( Map& map, const SignalHandler& handler ){
341 map.m_mapValidCallbacks.connectLast( handler );
344 bool Map_Valid( const Map& map ){
348 void Map_SetValid( Map& map, bool valid ){
350 map.m_mapValidCallbacks();
354 const char* Map_Name( const Map& map ){
355 return map.m_name.c_str();
358 bool Map_Unnamed( const Map& map ){
359 return string_equal( Map_Name( map ), "unnamed.map" );
362 inline const MapFormat& MapFormat_forFile( const char* filename ){
363 const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), path_get_extension( filename ) );
364 MapFormat* format = Radiant_getMapModules().findModule( moduleName );
365 ASSERT_MESSAGE( format != 0, "map format not found for file " << makeQuoted( filename ) );
369 const MapFormat& Map_getFormat( const Map& map ){
370 return MapFormat_forFile( Map_Name( map ) );
374 bool Map_Modified( const Map& map ){
375 return map.m_modified;
378 void Map_SetModified( Map& map, bool modified ){
379 if ( map.m_modified ^ modified ) {
380 map.m_modified = modified;
382 map.m_modified_changed( map );
386 void Map_UpdateTitle( const Map& map ){
387 Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) );
392 scene::Node* Map_GetWorldspawn( const Map& map ){
393 return map.m_world_node.get();
396 void Map_SetWorldspawn( Map& map, scene::Node* node ){
397 map.m_world_node.set( node );
402 // need that in a variable, will have to tweak depending on the game
403 float g_MaxWorldCoord = 64 * 1024;
404 float g_MinWorldCoord = -64 * 1024;
406 void AddRegionBrushes( void );
407 void RemoveRegionBrushes( void );
414 free all map elements, reinitialize the structures that depend on them
420 g_map.m_resource->detach( g_map );
421 GlobalReferenceCache().release( g_map.m_name.c_str() );
422 g_map.m_resource = 0;
427 Brush_unlatchPreferences();
430 class EntityFindByClassname : public scene::Graph::Walker
435 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
438 bool pre( const scene::Path& path, scene::Instance& instance ) const {
439 if ( m_entity == 0 ) {
440 Entity* entity = Node_getEntity( path.top() );
442 && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
450 Entity* Scene_FindEntityByClass( const char* name ){
452 GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
456 Entity *Scene_FindPlayerStart(){
457 typedef const char* StaticString;
458 StaticString strings[] = {
460 "info_player_deathmatch",
461 "team_CTF_redplayer",
462 "team_CTF_blueplayer",
464 "team_CTF_bluespawn",
466 typedef const StaticString* StaticStringIterator;
467 for ( StaticStringIterator i = strings, end = strings + ( sizeof( strings ) / sizeof( StaticString ) ); i != end; ++i )
469 Entity* entity = Scene_FindEntityByClass( *i );
478 // move the view to a start position
482 void FocusViews( const Vector3& point, float angle ){
483 CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
484 Camera_setOrigin( camwnd, point );
485 Vector3 angles( Camera_getAngles( camwnd ) );
486 angles[CAMERA_PITCH] = 0;
487 angles[CAMERA_YAW] = angle;
488 Camera_setAngles( camwnd, angles );
490 XYWnd* xywnd = g_pParentWnd->GetXYWnd();
491 xywnd->SetOrigin( point );
494 #include "stringio.h"
496 void Map_StartPosition(){
497 Entity* entity = Scene_FindPlayerStart();
501 string_parse_vector3( entity->getKeyValue( "origin" ), origin );
502 FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) );
506 FocusViews( g_vector3_identity, 0 );
511 inline bool node_is_worldspawn( scene::Node& node ){
512 Entity* entity = Node_getEntity( node );
513 return entity != 0 && string_equal( entity->getKeyValue( "classname" ), "worldspawn" );
517 // use first worldspawn
518 class entity_updateworldspawn : public scene::Traversable::Walker
521 bool pre( scene::Node& node ) const {
522 if ( node_is_worldspawn( node ) ) {
523 if ( Map_GetWorldspawn( g_map ) == 0 ) {
524 Map_SetWorldspawn( g_map, &node );
531 scene::Node* Map_FindWorldspawn( Map& map ){
532 Map_SetWorldspawn( map, 0 );
534 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
536 return Map_GetWorldspawn( map );
540 class CollectAllWalker : public scene::Traversable::Walker
543 UnsortedNodeSet& m_nodes;
545 CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
547 bool pre( scene::Node& node ) const {
548 m_nodes.insert( NodeSmartReference( node ) );
549 Node_getTraversable( m_root )->erase( node );
554 void Node_insertChildFirst( scene::Node& parent, scene::Node& child ){
555 UnsortedNodeSet nodes;
556 Node_getTraversable( parent )->traverse( CollectAllWalker( parent, nodes ) );
557 Node_getTraversable( parent )->insert( child );
559 for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i )
561 Node_getTraversable( parent )->insert( ( *i ) );
565 scene::Node& createWorldspawn(){
566 NodeSmartReference worldspawn( GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "worldspawn", true ) ) );
567 Node_insertChildFirst( GlobalSceneGraph().root(), worldspawn );
571 void Map_UpdateWorldspawn( Map& map ){
572 if ( Map_FindWorldspawn( map ) == 0 ) {
573 Map_SetWorldspawn( map, &createWorldspawn() );
577 scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
578 Map_UpdateWorldspawn( map );
579 return *Map_GetWorldspawn( map );
583 class MapMergeAll : public scene::Traversable::Walker
585 mutable scene::Path m_path;
587 MapMergeAll( const scene::Path& root )
590 bool pre( scene::Node& node ) const {
591 Node_getTraversable( m_path.top() )->insert( node );
592 m_path.push( makeReference( node ) );
593 selectPath( m_path, true );
596 void post( scene::Node& node ) const {
601 class MapMergeEntities : public scene::Traversable::Walker
603 mutable scene::Path m_path;
605 MapMergeEntities( const scene::Path& root )
608 bool pre( scene::Node& node ) const {
609 if ( node_is_worldspawn( node ) ) {
610 scene::Node* world_node = Map_FindWorldspawn( g_map );
611 if ( world_node == 0 ) {
612 Map_SetWorldspawn( g_map, &node );
613 Node_getTraversable( m_path.top().get() )->insert( node );
614 m_path.push( makeReference( node ) );
615 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
619 m_path.push( makeReference( *world_node ) );
620 Node_getTraversable( node )->traverse( MapMergeAll( m_path ) );
625 Node_getTraversable( m_path.top() )->insert( node );
626 m_path.push( makeReference( node ) );
627 if ( node_is_group( node ) ) {
628 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
632 selectPath( m_path, true );
637 void post( scene::Node& node ) const {
642 class BasicContainer : public scene::Node::Symbiot
646 NodeTypeCastTable m_casts;
649 NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
651 NodeTypeCastTable& get(){
657 TraversableNodeSet m_traverse;
660 typedef LazyStatic<TypeCasts> StaticTypeCasts;
662 scene::Traversable& get( NullType<scene::Traversable>){
666 BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
676 /// Merges the map graph rooted at \p node into the global scene-graph.
677 void MergeMap( scene::Node& node ){
678 Node_getTraversable( node )->traverse( MapMergeEntities( scene::Path( makeReference( GlobalSceneGraph().root() ) ) ) );
680 void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
681 NodeSmartReference node( ( new BasicContainer )->node() );
682 format.readGraph( node, in, GlobalEntityCreator() );
683 Map_gatherNamespaced( node );
684 Map_mergeClonedNames();
688 inline scene::Cloneable* Node_getCloneable( scene::Node& node ){
689 return NodeTypeCast<scene::Cloneable>::cast( node );
692 inline scene::Node& node_clone( scene::Node& node ){
693 scene::Cloneable* cloneable = Node_getCloneable( node );
694 if ( cloneable != 0 ) {
695 return cloneable->clone();
698 return ( new scene::NullNode )->node();
701 class CloneAll : public scene::Traversable::Walker
703 mutable scene::Path m_path;
705 CloneAll( scene::Node& root )
706 : m_path( makeReference( root ) ){
708 bool pre( scene::Node& node ) const {
709 if ( node.isRoot() ) {
713 m_path.push( makeReference( node_clone( node ) ) );
714 m_path.top().get().IncRef();
718 void post( scene::Node& node ) const {
719 if ( node.isRoot() ) {
723 Node_getTraversable( m_path.parent() )->insert( m_path.top() );
725 m_path.top().get().DecRef();
730 scene::Node& Node_Clone( scene::Node& node ){
731 scene::Node& clone = node_clone( node );
732 scene::Traversable* traversable = Node_getTraversable( node );
733 if ( traversable != 0 ) {
734 traversable->traverse( CloneAll( clone ) );
740 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
742 class EntityBreakdownWalker : public scene::Graph::Walker
744 EntityBreakdown& m_entitymap;
746 EntityBreakdownWalker( EntityBreakdown& entitymap )
747 : m_entitymap( entitymap ){
749 bool pre( const scene::Path& path, scene::Instance& instance ) const {
750 Entity* entity = Node_getEntity( path.top() );
752 const EntityClass& eclass = entity->getEntityClass();
753 if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
754 m_entitymap[eclass.name()] = 1;
756 else{ ++m_entitymap[eclass.name()]; }
762 void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
763 GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
767 WindowPosition g_posMapInfoWnd( c_default_window_pos );
771 GtkEntry* brushes_entry;
772 GtkEntry* entities_entry;
773 GtkListStore* EntityBreakdownWalker;
775 GtkWindow* window = create_dialog_window( MainFrame_getWindow(), "Map Info", G_CALLBACK( dialog_delete_callback ), &dialog );
777 window_set_position( window, g_posMapInfoWnd );
780 GtkVBox* vbox = create_dialog_vbox( 4, 4 );
781 gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( vbox ) );
784 GtkHBox* hbox = create_dialog_hbox( 4 );
785 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
788 GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
789 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
792 GtkEntry* entry = GTK_ENTRY( gtk_entry_new() );
793 gtk_widget_show( GTK_WIDGET( entry ) );
794 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
795 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
796 (GtkAttachOptions) ( 0 ), 0, 0 );
797 gtk_entry_set_editable( entry, FALSE );
799 brushes_entry = entry;
802 GtkEntry* entry = GTK_ENTRY( gtk_entry_new() );
803 gtk_widget_show( GTK_WIDGET( entry ) );
804 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
805 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
806 (GtkAttachOptions) ( 0 ), 0, 0 );
807 gtk_entry_set_editable( entry, FALSE );
809 entities_entry = entry;
812 GtkWidget* label = gtk_label_new( "Total Brushes" );
813 gtk_widget_show( label );
814 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
815 (GtkAttachOptions) ( GTK_FILL ),
816 (GtkAttachOptions) ( 0 ), 0, 0 );
817 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
820 GtkWidget* label = gtk_label_new( "Total Entities" );
821 gtk_widget_show( label );
822 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
823 (GtkAttachOptions) ( GTK_FILL ),
824 (GtkAttachOptions) ( 0 ), 0, 0 );
825 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
829 GtkVBox* vbox2 = create_dialog_vbox( 4 );
830 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), FALSE, FALSE, 0 );
833 GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
834 gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
839 GtkWidget* label = gtk_label_new( "Entity breakdown" );
840 gtk_widget_show( label );
841 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, TRUE, 0 );
842 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
845 GtkScrolledWindow* scr = create_scrolled_window( GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4 );
846 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( scr ), TRUE, TRUE, 0 );
849 GtkListStore* store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING );
851 GtkWidget* view = gtk_tree_view_new_with_model( GTK_TREE_MODEL( store ) );
852 gtk_tree_view_set_headers_clickable( GTK_TREE_VIEW( view ), TRUE );
855 GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
856 GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes( "Entity", renderer, "text", 0, 0 );
857 gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
858 gtk_tree_view_column_set_sort_column_id( column, 0 );
862 GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
863 GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes( "Count", renderer, "text", 1, 0 );
864 gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
865 gtk_tree_view_column_set_sort_column_id( column, 1 );
868 gtk_widget_show( view );
870 gtk_container_add( GTK_CONTAINER( scr ), view );
872 EntityBreakdownWalker = store;
880 EntityBreakdown entitymap;
881 Scene_EntityBreakdown( entitymap );
883 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
886 sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
888 gtk_list_store_append( GTK_LIST_STORE( EntityBreakdownWalker ), &iter );
889 gtk_list_store_set( GTK_LIST_STORE( EntityBreakdownWalker ), &iter, 0, ( *i ).first.c_str(), 1, tmp, -1 );
893 g_object_unref( G_OBJECT( EntityBreakdownWalker ) );
896 sprintf( tmp, "%u", Unsigned( g_brushCount.get() ) );
897 gtk_entry_set_text( GTK_ENTRY( brushes_entry ), tmp );
898 sprintf( tmp, "%u", Unsigned( g_entityCount.get() ) );
899 gtk_entry_set_text( GTK_ENTRY( entities_entry ), tmp );
901 modal_dialog_show( window, dialog );
904 window_get_position( window, g_posMapInfoWnd );
906 gtk_widget_destroy( GTK_WIDGET( window ) );
914 const char* m_message;
916 ScopeTimer( const char* message )
917 : m_message( message ){
921 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
922 globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
926 CopiedString g_strLastFolder = "";
934 void Map_LoadFile( const char *filename ){
935 globalOutputStream() << "Loading map from " << filename << "\n";
936 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
938 MRU_AddFile( filename );
939 g_strLastFolder = g_path_get_dirname( filename );
942 ScopeTimer timer( "map load" );
944 const MapFormat* format = NULL;
945 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
946 if ( string_not_empty( moduleName ) ) {
947 format = ReferenceAPI_getMapModules().findModule( moduleName );
950 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
955 Brush_toggleFormat( i );
956 g_map.m_name = filename;
957 Map_UpdateTitle( g_map );
958 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
960 format->wrongFormat = false;
962 g_map.m_resource->attach( g_map );
964 if ( !format->wrongFormat ) {
970 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
973 globalOutputStream() << "--- LoadMapFile ---\n";
974 globalOutputStream() << g_map.m_name.c_str() << "\n";
976 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
977 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
979 //GlobalEntityCreator().printStatistics();
982 // move the view to a start position
986 g_currentMap = &g_map;
988 // restart VFS to apply new pak filtering based on mapname
989 // needed for daemon DPK VFS
996 virtual bool excluded( scene::Node& node ) const = 0;
999 class ExcludeWalker : public scene::Traversable::Walker
1001 const scene::Traversable::Walker& m_walker;
1002 const Excluder* m_exclude;
1003 mutable bool m_skip;
1005 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1006 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1008 bool pre( scene::Node& node ) const {
1009 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1015 m_walker.pre( node );
1019 void post( scene::Node& node ) const {
1025 m_walker.post( node );
1030 class AnyInstanceSelected : public scene::Instantiable::Visitor
1034 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1037 void visit( scene::Instance& instance ) const {
1038 Selectable* selectable = Instance_getSelectable( instance );
1039 if ( selectable != 0
1040 && selectable->isSelected() ) {
1046 bool Node_instanceSelected( scene::Node& node ){
1047 scene::Instantiable* instantiable = Node_getInstantiable( node );
1048 ASSERT_NOTNULL( instantiable );
1050 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1054 class SelectedDescendantWalker : public scene::Traversable::Walker
1058 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1062 bool pre( scene::Node& node ) const {
1063 if ( node.isRoot() ) {
1067 if ( Node_instanceSelected( node ) ) {
1075 bool Node_selectedDescendant( scene::Node& node ){
1077 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1081 class SelectionExcluder : public Excluder
1084 bool excluded( scene::Node& node ) const {
1085 return !Node_selectedDescendant( node );
1089 class IncludeSelectedWalker : public scene::Traversable::Walker
1091 const scene::Traversable::Walker& m_walker;
1092 mutable std::size_t m_selected;
1093 mutable bool m_skip;
1095 bool selectedParent() const {
1096 return m_selected != 0;
1099 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1100 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1102 bool pre( scene::Node& node ) const {
1104 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1105 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1106 if ( Node_instanceSelected( node ) ) {
1109 m_walker.pre( node );
1118 void post( scene::Node& node ) const {
1124 if ( Node_instanceSelected( node ) ) {
1127 m_walker.post( node );
1132 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1133 scene::Traversable* traversable = Node_getTraversable( root );
1134 if ( traversable != 0 ) {
1136 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1138 traversable->traverse( IncludeSelectedWalker( walker ) );
1143 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1144 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out );
1147 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1148 scene::Traversable* traversable = Node_getTraversable( root );
1149 if ( traversable != 0 ) {
1150 traversable->traverse( walker );
1154 class RegionExcluder : public Excluder
1157 bool excluded( scene::Node& node ) const {
1158 return node.excluded();
1162 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1163 scene::Traversable* traversable = Node_getTraversable( root );
1164 if ( traversable != 0 ) {
1165 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1169 bool Map_SaveRegion( const char *filename ){
1172 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1174 RemoveRegionBrushes();
1180 void Map_RenameAbsolute( const char* absolute ){
1181 Resource* resource = GlobalReferenceCache().capture( absolute );
1182 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1183 resource->setNode( clone.get_pointer() );
1186 //ScopeTimer timer("clone subgraph");
1187 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1190 g_map.m_resource->detach( g_map );
1191 GlobalReferenceCache().release( g_map.m_name.c_str() );
1193 g_map.m_resource = resource;
1195 g_map.m_name = absolute;
1196 Map_UpdateTitle( g_map );
1198 g_map.m_resource->attach( g_map );
1199 // refresh VFS to apply new pak filtering based on mapname
1200 // needed for daemon DPK VFS
1204 void Map_Rename( const char* filename ){
1205 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1206 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1208 Map_RenameAbsolute( filename );
1210 SceneChangeNotify();
1221 ScopeTimer timer( "map save" );
1223 return true; // assume success..
1233 //globalOutputStream() << "Map_New\n";
1235 g_map.m_name = "unnamed.map";
1236 Map_UpdateTitle( g_map );
1239 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1240 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1241 g_map.m_resource->attach( g_map );
1243 SceneChangeNotify();
1246 FocusViews( g_vector3_identity, 0 );
1248 g_currentMap = &g_map;
1250 // restart VFS to apply new pak filtering based on mapname
1251 // needed for daemon DPK VFS
1255 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1257 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1259 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1260 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1261 for now, let's just print an error
1264 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1266 for ( int i = 0 ; i < 3 ; i++ )
1268 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1269 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1274 // write the info_playerstart
1276 sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1277 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1278 sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1279 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1283 ===========================================================
1287 ===========================================================
1290 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1291 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1293 scene::Node* region_sides[6];
1294 scene::Node* region_startpoint = 0;
1299 a regioned map will have temp walls put up at the region boundary
1300 \todo TODO TTimo old implementation of region brushes
1301 we still add them straight in the worldspawn and take them out after the map is saved
1302 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1305 void AddRegionBrushes( void ){
1308 for ( i = 0; i < 6; i++ )
1310 region_sides[i] = &GlobalBrushCreator().createBrush();
1311 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1314 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1316 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1317 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1319 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1322 void RemoveRegionBrushes( void ){
1323 for ( std::size_t i = 0; i < 6; i++ )
1325 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1327 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1330 inline void exclude_node( scene::Node& node, bool exclude ){
1332 ? node.enable( scene::Node::eExcluded )
1333 : node.disable( scene::Node::eExcluded );
1336 class ExcludeAllWalker : public scene::Graph::Walker
1340 ExcludeAllWalker( bool exclude )
1341 : m_exclude( exclude ){
1343 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1344 exclude_node( path.top(), m_exclude );
1350 void Scene_Exclude_All( bool exclude ){
1351 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1354 bool Instance_isSelected( const scene::Instance& instance ){
1355 const Selectable* selectable = Instance_getSelectable( instance );
1356 return selectable != 0 && selectable->isSelected();
1359 class ExcludeSelectedWalker : public scene::Graph::Walker
1363 ExcludeSelectedWalker( bool exclude )
1364 : m_exclude( exclude ){
1366 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1367 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1372 void Scene_Exclude_Selected( bool exclude ){
1373 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1376 class ExcludeRegionedWalker : public scene::Graph::Walker
1380 ExcludeRegionedWalker( bool exclude )
1381 : m_exclude( exclude ){
1383 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1388 aabb_intersects_aabb(
1389 instance.worldAABB(),
1390 aabb_for_minmax( region_mins, region_maxs )
1400 void Scene_Exclude_Region( bool exclude ){
1401 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1408 Other filtering options may still be on
1411 void Map_RegionOff(){
1412 region_active = false;
1414 region_maxs[0] = g_MaxWorldCoord - 64;
1415 region_mins[0] = g_MinWorldCoord + 64;
1416 region_maxs[1] = g_MaxWorldCoord - 64;
1417 region_mins[1] = g_MinWorldCoord + 64;
1418 region_maxs[2] = g_MaxWorldCoord - 64;
1419 region_mins[2] = g_MinWorldCoord + 64;
1421 Scene_Exclude_All( false );
1424 void Map_ApplyRegion( void ){
1425 region_active = true;
1427 Scene_Exclude_Region( false );
1432 ========================
1433 Map_RegionSelectedBrushes
1434 ========================
1436 void Map_RegionSelectedBrushes( void ){
1439 if ( GlobalSelectionSystem().countSelected() != 0
1440 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1441 region_active = true;
1442 Select_GetBounds( region_mins, region_maxs );
1444 Scene_Exclude_Selected( false );
1446 GlobalSelectionSystem().setSelectedAll( false );
1456 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1459 region_mins[0] = x_min;
1460 region_maxs[0] = x_max;
1461 region_mins[1] = y_min;
1462 region_maxs[1] = y_max;
1463 region_mins[2] = g_MinWorldCoord + 64;
1464 region_maxs[2] = g_MaxWorldCoord - 64;
1469 void Map_RegionBounds( const AABB& bounds ){
1472 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1473 region_maxs = vector3_added( bounds.origin, bounds.extents );
1485 void Map_RegionBrush( void ){
1486 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1487 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1488 Map_RegionBounds( instance.worldAABB() );
1497 bool Map_ImportFile( const char* filename ){
1498 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1500 g_strLastFolder = g_path_get_dirname( filename );
1502 bool success = false;
1504 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1509 const MapFormat* format = NULL;
1510 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1511 if ( string_not_empty( moduleName ) ) {
1512 format = ReferenceAPI_getMapModules().findModule( moduleName );
1516 format->wrongFormat = false;
1518 Resource* resource = GlobalReferenceCache().capture( filename );
1519 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1520 if ( !resource->load() ) {
1521 GlobalReferenceCache().release( filename );
1525 if ( format->wrongFormat ) {
1526 GlobalReferenceCache().release( filename );
1530 NodeSmartReference clone( NewMapRoot( "" ) );
1531 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1532 Map_gatherNamespaced( clone );
1533 Map_mergeClonedNames();
1536 GlobalReferenceCache().release( filename );
1539 SceneChangeNotify();
1545 const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue( "q3map2_type" );
1546 int n = string_length( path_get_extension( filename ) );
1547 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1548 StringBuffer output;
1549 output.push_string( AppPath_get() );
1550 output.push_string( "q3map2." );
1551 output.push_string( RADIANT_EXECUTABLE );
1552 output.push_string( " -v -game " );
1553 output.push_string( ( type && *type ) ? type : "quake3" );
1554 output.push_string( " -fs_basepath \"" );
1555 output.push_string( EnginePath_get() );
1556 output.push_string( "\" -fs_homepath \"" );
1557 output.push_string( g_qeglobals.m_userEnginePath.c_str() );
1558 output.push_string( "\" -fs_game " );
1559 output.push_string( gamename_get() );
1560 output.push_string( " -convert -format " );
1561 output.push_string( Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map" );
1562 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1563 output.push_string( " -readmap " );
1565 output.push_string( " \"" );
1566 output.push_string( filename );
1567 output.push_string( "\"" );
1570 Q_Exec( NULL, output.c_str(), NULL, false, true );
1572 // rebuild filename as "filenamewithoutext_converted.map"
1574 output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
1575 output.push_string( "_converted.map" );
1576 filename = output.c_str();
1579 Resource* resource = GlobalReferenceCache().capture( filename );
1580 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1581 if ( !resource->load() ) {
1582 GlobalReferenceCache().release( filename );
1585 NodeSmartReference clone( NewMapRoot( "" ) );
1586 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1587 Map_gatherNamespaced( clone );
1588 Map_mergeClonedNames();
1591 GlobalReferenceCache().release( filename );
1594 SceneChangeNotify();
1603 bool Map_SaveFile( const char* filename ){
1604 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1605 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1607 // refresh VFS to apply new pak filtering based on mapname
1608 // needed for daemon DPK VFS
1619 // Saves selected world brushes and whole entities with partial/full selections
1621 bool Map_SaveSelected( const char* filename ){
1622 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1626 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1628 scene::Node& m_parent;
1630 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ){
1632 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1633 if ( path.top().get_pointer() != &m_parent
1634 && Node_isPrimitive( path.top() ) ) {
1635 Selectable* selectable = Instance_getSelectable( instance );
1636 if ( selectable != 0
1637 && selectable->isSelected()
1638 && path.size() > 1 ) {
1644 void post( const scene::Path& path, scene::Instance& instance ) const {
1645 if ( path.top().get_pointer() != &m_parent
1646 && Node_isPrimitive( path.top() ) ) {
1647 Selectable* selectable = Instance_getSelectable( instance );
1648 if ( selectable != 0
1649 && selectable->isSelected()
1650 && path.size() > 1 ) {
1651 scene::Node& parent = path.parent();
1652 if ( &parent != &m_parent ) {
1653 NodeSmartReference node( path.top().get() );
1654 Node_getTraversable( parent )->erase( node );
1655 Node_getTraversable( m_parent )->insert( node );
1662 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1663 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1666 class CountSelectedBrushes : public scene::Graph::Walker
1668 std::size_t& m_count;
1669 mutable std::size_t m_depth;
1671 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1674 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1675 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1678 Selectable* selectable = Instance_getSelectable( instance );
1679 if ( selectable != 0
1680 && selectable->isSelected()
1681 && Node_isPrimitive( path.top() ) ) {
1686 void post( const scene::Path& path, scene::Instance& instance ) const {
1691 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1693 graph.traverse( CountSelectedBrushes( count ) );
1705 const char* nodetype_get_name( ENodeType type ){
1706 if ( type == eNodeMap ) {
1709 if ( type == eNodeEntity ) {
1712 if ( type == eNodePrimitive ) {
1718 ENodeType node_get_nodetype( scene::Node& node ){
1719 if ( Node_isEntity( node ) ) {
1722 if ( Node_isPrimitive( node ) ) {
1723 return eNodePrimitive;
1725 return eNodeUnknown;
1728 bool contains_entity( scene::Node& node ){
1729 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1732 bool contains_primitive( scene::Node& node ){
1733 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1736 ENodeType node_get_contains( scene::Node& node ){
1737 if ( contains_entity( node ) ) {
1740 if ( contains_primitive( node ) ) {
1741 return eNodePrimitive;
1743 return eNodeUnknown;
1746 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1747 ENodeType contains = node_get_contains( parent.top() );
1748 ENodeType type = node_get_nodetype( child.top() );
1750 if ( contains != eNodeUnknown && contains == type ) {
1751 NodeSmartReference node( child.top().get() );
1752 Path_deleteTop( child );
1753 Node_getTraversable( parent.top() )->insert( node );
1754 SceneChangeNotify();
1758 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1762 void Scene_parentSelected(){
1763 UndoableCommand undo( "parentSelected" );
1765 if ( GlobalSelectionSystem().countSelected() > 1 ) {
1766 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1768 const scene::Path& m_parent;
1770 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1772 void visit( scene::Instance& instance ) const {
1773 if ( &m_parent != &instance.path() ) {
1774 Path_parent( m_parent, instance.path() );
1779 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1780 GlobalSelectionSystem().foreachSelected( visitor );
1784 globalOutputStream() << "failed - did not find two selected nodes.\n";
1791 if ( ConfirmModified( "New Map" ) ) {
1798 CopiedString g_mapsPath;
1800 const char* getMapsPath(){
1801 return g_mapsPath.c_str();
1804 const char* getLastFolderPath(){
1805 if (g_strLastFolder.empty()) {
1806 GlobalPreferenceSystem().registerPreference( "LastFolder", CopiedStringImportStringCaller( g_strLastFolder ), CopiedStringExportStringCaller( g_strLastFolder ) );
1807 if (g_strLastFolder.empty()) {
1808 g_strLastFolder = g_qeglobals.m_userGamePath;
1811 return g_strLastFolder.c_str();
1814 const char* map_open( const char* title ){
1815 return file_dialog( GTK_WIDGET( MainFrame_getWindow() ), TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false );
1818 const char* map_import( const char* title ){
1819 return file_dialog( GTK_WIDGET( MainFrame_getWindow() ), TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false );
1822 const char* map_save( const char* title ){
1823 return file_dialog( GTK_WIDGET( MainFrame_getWindow() ), FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true );
1827 if ( !ConfirmModified( "Open Map" ) ) {
1831 const char* filename = map_open( "Open Map" );
1833 if ( filename != NULL ) {
1834 MRU_AddFile( filename );
1837 Map_LoadFile( filename );
1842 const char* filename = map_import( "Import Map" );
1844 if ( filename != NULL ) {
1845 UndoableCommand undo( "mapImport" );
1846 Map_ImportFile( filename );
1851 const char* filename = map_save( "Save Map" );
1853 if ( filename != NULL ) {
1854 g_strLastFolder = g_path_get_dirname( filename );
1855 MRU_AddFile( filename );
1856 Map_Rename( filename );
1867 if ( Map_Unnamed( g_map ) ) {
1870 else if ( Map_Modified( g_map ) ) {
1876 const char* filename = map_save( "Export Selection" );
1878 if ( filename != NULL ) {
1879 g_strLastFolder = g_path_get_dirname( filename );
1880 Map_SaveSelected( filename );
1885 const char* filename = map_save( "Export Region" );
1887 if ( filename != NULL ) {
1888 g_strLastFolder = g_path_get_dirname( filename );
1889 Map_SaveRegion( filename );
1896 SceneChangeNotify();
1901 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1902 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1903 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1904 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1906 SceneChangeNotify();
1911 SceneChangeNotify();
1914 void RegionSelected(){
1915 Map_RegionSelectedBrushes();
1916 SceneChangeNotify();
1923 class BrushFindByIndexWalker : public scene::Traversable::Walker
1925 mutable std::size_t m_index;
1926 scene::Path& m_path;
1928 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
1929 : m_index( index ), m_path( path ){
1931 bool pre( scene::Node& node ) const {
1932 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
1933 m_path.push( makeReference( node ) );
1939 class EntityFindByIndexWalker : public scene::Traversable::Walker
1941 mutable std::size_t m_index;
1942 scene::Path& m_path;
1944 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
1945 : m_index( index ), m_path( path ){
1947 bool pre( scene::Node& node ) const {
1948 if ( Node_isEntity( node ) && m_index-- == 0 ) {
1949 m_path.push( makeReference( node ) );
1955 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
1956 path.push( makeReference( GlobalSceneGraph().root() ) );
1958 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
1960 if ( path.size() == 2 ) {
1961 scene::Traversable* traversable = Node_getTraversable( path.top() );
1962 if ( traversable != 0 ) {
1963 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
1968 inline bool Node_hasChildren( scene::Node& node ){
1969 scene::Traversable* traversable = Node_getTraversable( node );
1970 return traversable != 0 && !traversable->empty();
1973 void SelectBrush( int entitynum, int brushnum ){
1975 Scene_FindEntityBrush( entitynum, brushnum, path );
1976 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
1977 scene::Instance* instance = GlobalSceneGraph().find( path );
1978 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
1979 Selectable* selectable = Instance_getSelectable( *instance );
1980 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
1981 selectable->setSelected( true );
1982 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
1987 class BrushFindIndexWalker : public scene::Graph::Walker
1989 mutable const scene::Node* m_node;
1990 std::size_t& m_count;
1992 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
1993 : m_node( &node ), m_count( count ){
1995 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1996 if ( Node_isPrimitive( path.top() ) ) {
1997 if ( m_node == path.top().get_pointer() ) {
2008 class EntityFindIndexWalker : public scene::Graph::Walker
2010 mutable const scene::Node* m_node;
2011 std::size_t& m_count;
2013 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2014 : m_node( &node ), m_count( count ){
2016 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2017 if ( Node_isEntity( path.top() ) ) {
2018 if ( m_node == path.top().get_pointer() ) {
2029 static void GetSelectionIndex( int *ent, int *brush ){
2030 std::size_t count_brush = 0;
2031 std::size_t count_entity = 0;
2032 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2033 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2035 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2036 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2038 *brush = int(count_brush);
2039 *ent = int(count_entity);
2047 GtkWindow* window = create_dialog_window( MainFrame_getWindow(), "Find Brush", G_CALLBACK( dialog_delete_callback ), &dialog );
2049 GtkAccelGroup* accel = gtk_accel_group_new();
2050 gtk_window_add_accel_group( window, accel );
2053 GtkVBox* vbox = create_dialog_vbox( 4, 4 );
2054 gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( vbox ) );
2056 GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
2057 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
2059 GtkWidget* label = gtk_label_new( "Entity number" );
2060 gtk_widget_show( label );
2061 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
2062 (GtkAttachOptions) ( 0 ),
2063 (GtkAttachOptions) ( 0 ), 0, 0 );
2066 GtkWidget* label = gtk_label_new( "Brush number" );
2067 gtk_widget_show( label );
2068 gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
2069 (GtkAttachOptions) ( 0 ),
2070 (GtkAttachOptions) ( 0 ), 0, 0 );
2073 GtkEntry* entry = GTK_ENTRY( gtk_entry_new() );
2074 gtk_widget_show( GTK_WIDGET( entry ) );
2075 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
2076 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2077 (GtkAttachOptions) ( 0 ), 0, 0 );
2078 gtk_widget_grab_focus( GTK_WIDGET( entry ) );
2082 GtkEntry* entry = GTK_ENTRY( gtk_entry_new() );
2083 gtk_widget_show( GTK_WIDGET( entry ) );
2084 gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
2085 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
2086 (GtkAttachOptions) ( 0 ), 0, 0 );
2092 GtkHBox* hbox = create_dialog_hbox( 4 );
2093 gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), TRUE, TRUE, 0 );
2095 GtkButton* button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2096 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
2097 widget_make_default( GTK_WIDGET( button ) );
2098 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2101 GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2102 gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
2103 gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2108 // Initialize dialog
2112 GetSelectionIndex( &ent, &br );
2113 sprintf( buf, "%i", ent );
2114 gtk_entry_set_text( entity, buf );
2115 sprintf( buf, "%i", br );
2116 gtk_entry_set_text( brush, buf );
2118 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2119 const char *entstr = gtk_entry_get_text( entity );
2120 const char *brushstr = gtk_entry_get_text( brush );
2121 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2124 gtk_widget_destroy( GTK_WIDGET( window ) );
2127 void Map_constructPreferences( PreferencesPage& page ){
2128 page.appendCheckBox( "", "Load last map on open", g_bLoadLastMap );
2132 class MapEntityClasses : public ModuleObserver
2134 std::size_t m_unrealised;
2136 MapEntityClasses() : m_unrealised( 1 ){
2139 if ( --m_unrealised == 0 ) {
2140 if ( g_map.m_resource != 0 ) {
2141 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2142 g_map.m_resource->realise();
2147 if ( ++m_unrealised == 1 ) {
2148 if ( g_map.m_resource != 0 ) {
2149 g_map.m_resource->flush();
2150 g_map.m_resource->unrealise();
2156 MapEntityClasses g_MapEntityClasses;
2159 class MapModuleObserver : public ModuleObserver
2161 std::size_t m_unrealised;
2163 MapModuleObserver() : m_unrealised( 1 ){
2166 if ( --m_unrealised == 0 ) {
2167 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2168 StringOutputStream buffer( 256 );
2169 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2170 Q_mkdir( buffer.c_str() );
2171 g_mapsPath = buffer.c_str();
2175 if ( ++m_unrealised == 1 ) {
2181 MapModuleObserver g_MapModuleObserver;
2183 CopiedString g_strLastMap;
2184 bool g_bLoadLastMap = false;
2186 void Map_Construct(){
2187 GlobalCommands_insert( "RegionOff", FreeCaller<RegionOff>() );
2188 GlobalCommands_insert( "RegionSetXY", FreeCaller<RegionXY>() );
2189 GlobalCommands_insert( "RegionSetBrush", FreeCaller<RegionBrush>() );
2190 GlobalCommands_insert( "RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2192 GlobalPreferenceSystem().registerPreference( "LastMap", CopiedStringImportStringCaller( g_strLastMap ), CopiedStringExportStringCaller( g_strLastMap ) );
2193 GlobalPreferenceSystem().registerPreference( "LoadLastMap", BoolImportStringCaller( g_bLoadLastMap ), BoolExportStringCaller( g_bLoadLastMap ) );
2194 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", WindowPositionImportStringCaller( g_posMapInfoWnd ), WindowPositionExportStringCaller( g_posMapInfoWnd ) );
2196 PreferencesDialog_addSettingsPreferences( FreeCaller1<PreferencesPage&, Map_constructPreferences>() );
2198 GlobalEntityClassManager().attach( g_MapEntityClasses );
2199 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2203 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2204 GlobalEntityClassManager().detach( g_MapEntityClasses );