]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - radiant/map.cpp
radiant: replace StringBuffer with std::string
[xonotic/netradiant.git] / radiant / map.cpp
index 056f3ecfb04ae1c6db67030f84b9d2c3c3d78e5d..36b77ae1b828478767148f91738b156287139b5f 100644 (file)
@@ -26,7 +26,9 @@
 #include "debugging/debugging.h"
 
 #include "imap.h"
+
 MapModules& ReferenceAPI_getMapModules();
+
 #include "iselection.h"
 #include "iundo.h"
 #include "ibrush.h"
@@ -57,6 +59,7 @@ MapModules& ReferenceAPI_getMapModules();
 #include "cmdlib.h"
 #include "stream/textfilestream.h"
 #include "os/path.h"
+#include "os/file.h"
 #include "uniquenames.h"
 #include "modulesystem/singletonmodule.h"
 #include "modulesystem/moduleregistry.h"
@@ -84,6 +87,8 @@ MapModules& ReferenceAPI_getMapModules();
 #include "brushmodule.h"
 #include "brush.h"
 
+bool g_writeMapComments = true;
+
 class NameObserver
 {
 UniqueNames& m_names;
@@ -95,6 +100,7 @@ void construct(){
                m_names.insert( name_read( c_str() ) );
        }
 }
+
 void destroy(){
        if ( !empty() ) {
                //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
@@ -103,6 +109,7 @@ void destroy(){
 }
 
 NameObserver& operator=( const NameObserver& other );
+
 public:
 NameObserver( UniqueNames& names ) : m_names( names ){
        construct();
@@ -110,21 +117,26 @@ NameObserver( UniqueNames& names ) : m_names( names ){
 NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
        construct();
 }
+
 ~NameObserver(){
        destroy();
 }
+
 bool empty() const {
        return string_empty( c_str() );
 }
+
 const char* c_str() const {
        return m_name.c_str();
 }
+
 void nameChanged( const char* name ){
        destroy();
        m_name = name;
        construct();
 }
-typedef MemberCaller1<NameObserver, const char*, &NameObserver::nameChanged> NameChangedCaller;
+
+typedef MemberCaller<NameObserver, void(const char*), &NameObserver::nameChanged> NameChangedCaller;
 };
 
 class BasicNamespace : public Namespace
@@ -136,12 +148,14 @@ public:
 ~BasicNamespace(){
        ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
 }
+
 void attach( const NameCallback& setName, const NameCallbackCallback& attachObserver ){
        std::pair<Names::iterator, bool> result = m_names.insert( Names::value_type( setName, m_uniqueNames ) );
        ASSERT_MESSAGE( result.second, "cannot attach name" );
        attachObserver( NameObserver::NameChangedCaller( ( *result.first ).second ) );
        //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
 }
+
 void detach( const NameCallback& setName, const NameCallbackCallback& detachObserver ){
        Names::iterator i = m_names.find( setName );
        ASSERT_MESSAGE( i != m_names.end(), "cannot detach name" );
@@ -196,11 +210,13 @@ class NamespaceAPI
 Namespace* m_namespace;
 public:
 typedef Namespace Type;
+
 STRING_CONSTANT( Name, "*" );
 
 NamespaceAPI(){
        m_namespace = &g_defaultNamespace;
 }
+
 Namespace* getTable(){
        return m_namespace;
 }
@@ -258,6 +274,7 @@ public:
 WorldNode()
        : m_node( 0 ){
 }
+
 void set( scene::Node* node ){
        if ( m_node != 0 ) {
                m_node->DecRef();
@@ -267,6 +284,7 @@ void set( scene::Node* node ){
                m_node->IncRef();
        }
 }
+
 scene::Node* get() const {
        return m_node;
 }
@@ -274,7 +292,9 @@ scene::Node* get() const {
 
 class Map;
 void Map_SetValid( Map& map, bool valid );
+
 void Map_UpdateTitle( const Map& map );
+
 void Map_SetWorldspawn( Map& map, scene::Node* node );
 
 
@@ -286,6 +306,7 @@ Resource* m_resource;
 bool m_valid;
 
 bool m_modified;
+
 void ( *m_modified_changed )( const Map& );
 
 Signal0 m_mapValidCallbacks;
@@ -316,6 +337,7 @@ void realise(){
                Map_SetValid( g_map, true );
        }
 }
+
 void unrealise(){
        if ( m_resource != 0 ) {
                Map_SetValid( g_map, false );
@@ -383,7 +405,6 @@ void Map_UpdateTitle( const Map& map ){
 }
 
 
-
 scene::Node* Map_GetWorldspawn( const Map& map ){
        return map.m_world_node.get();
 }
@@ -399,8 +420,8 @@ float g_MaxWorldCoord = 64 * 1024;
 float g_MinWorldCoord = -64 * 1024;
 
 void AddRegionBrushes( void );
-void RemoveRegionBrushes( void );
 
+void RemoveRegionBrushes( void );
 
 
 /*
@@ -430,6 +451,7 @@ public:
 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
        m_entity = 0;
 }
+
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        if ( m_entity == 0 ) {
                Entity* entity = Node_getEntity( path.top() );
@@ -539,6 +561,7 @@ UnsortedNodeSet& m_nodes;
 public:
 CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
 }
+
 bool pre( scene::Node& node ) const {
        m_nodes.insert( NodeSmartReference( node ) );
        Node_getTraversable( m_root )->erase( node );
@@ -582,12 +605,14 @@ public:
 MapMergeAll( const scene::Path& root )
        : m_path( root ){
 }
+
 bool pre( scene::Node& node ) const {
        Node_getTraversable( m_path.top() )->insert( node );
        m_path.push( makeReference( node ) );
        selectPath( m_path, true );
        return false;
 }
+
 void post( scene::Node& node ) const {
        m_path.pop();
 }
@@ -600,6 +625,7 @@ public:
 MapMergeEntities( const scene::Path& root )
        : m_path( root ){
 }
+
 bool pre( scene::Node& node ) const {
        if ( node_is_worldspawn( node ) ) {
                scene::Node* world_node = Map_FindWorldspawn( g_map );
@@ -629,6 +655,7 @@ bool pre( scene::Node& node ) const {
        }
        return false;
 }
+
 void post( scene::Node& node ) const {
        m_path.pop();
 }
@@ -643,6 +670,7 @@ public:
 TypeCasts(){
        NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
 }
+
 NodeTypeCastTable& get(){
        return m_casts;
 }
@@ -660,9 +688,11 @@ scene::Traversable& get( NullType<scene::Traversable>){
 
 BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
 }
+
 void release(){
        delete this;
 }
+
 scene::Node& node(){
        return m_node;
 }
@@ -672,6 +702,7 @@ scene::Node& node(){
 void MergeMap( scene::Node& node ){
        Node_getTraversable( node )->traverse( MapMergeEntities( scene::Path( makeReference( GlobalSceneGraph().root() ) ) ) );
 }
+
 void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
        NodeSmartReference node( ( new BasicContainer )->node() );
        format.readGraph( node, in, GlobalEntityCreator() );
@@ -700,6 +731,7 @@ public:
 CloneAll( scene::Node& root )
        : m_path( makeReference( root ) ){
 }
+
 bool pre( scene::Node& node ) const {
        if ( node.isRoot() ) {
                return false;
@@ -710,6 +742,7 @@ bool pre( scene::Node& node ) const {
 
        return true;
 }
+
 void post( scene::Node& node ) const {
        if ( node.isRoot() ) {
                return;
@@ -741,14 +774,17 @@ public:
 EntityBreakdownWalker( EntityBreakdown& entitymap )
        : m_entitymap( entitymap ){
 }
+
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        Entity* entity = Node_getEntity( path.top() );
        if ( entity != 0 ) {
                const EntityClass& eclass = entity->getEntityClass();
                if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
                        m_entitymap[eclass.name()] = 1;
+               } else
+               {
+                       ++m_entitymap[eclass.name()];
                }
-               else{ ++m_entitymap[eclass.name()]; }
        }
        return true;
 }
@@ -763,8 +799,8 @@ WindowPosition g_posMapInfoWnd( c_default_window_pos );
 
 void DoMapInfo(){
        ModalDialog dialog;
-       GtkEntry* brushes_entry;
-       GtkEntry* entities_entry;
+       ui::Entry brushes_entry{ui::null};
+       ui::Entry entities_entry{ui::null};
        ui::ListStore EntityBreakdownWalker{ui::null};
 
        ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
@@ -776,29 +812,25 @@ void DoMapInfo(){
                window.add(vbox);
 
                {
-                       GtkHBox* hbox = create_dialog_hbox( 4 );
-                       gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 );
+                       auto hbox = create_dialog_hbox( 4 );
+                       vbox.pack_start( hbox, FALSE, TRUE, 0 );
 
                        {
-                               GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
-                               gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
+                               auto table = create_dialog_table( 2, 2, 4, 4 );
+                               hbox.pack_start( table, TRUE, TRUE, 0 );
 
                                {
-                                       auto entry = ui::Entry();
+                                       auto entry = ui::Entry(ui::New);
                                        entry.show();
-                                       gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
-                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
-                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
+                    table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
                                        gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
 
                                        brushes_entry = entry;
                                }
                                {
-                                       auto entry = ui::Entry();
+                                       auto entry = ui::Entry(ui::New);
                                        entry.show();
-                                       gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
-                                                                         (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
-                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
+                    table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
                                        gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
 
                                        entities_entry = entry;
@@ -806,57 +838,53 @@ void DoMapInfo(){
                                {
                                        ui::Widget label = ui::Label( "Total Brushes" );
                                        label.show();
-                                       gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
-                                                                         (GtkAttachOptions) ( GTK_FILL ),
-                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
+                    table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
                                        gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
                                }
                                {
                                        ui::Widget label = ui::Label( "Total Entities" );
                                        label.show();
-                                       gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
-                                                                         (GtkAttachOptions) ( GTK_FILL ),
-                                                                         (GtkAttachOptions) ( 0 ), 0, 0 );
+                    table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
                                        gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
                                }
                        }
                        {
-                               GtkVBox* vbox2 = create_dialog_vbox( 4 );
-                               gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), FALSE, FALSE, 0 );
+                               auto vbox2 = create_dialog_vbox( 4 );
+                               hbox.pack_start( vbox2, FALSE, FALSE, 0 );
 
                                {
-                                       GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
-                                       gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
+                                       auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
+                                       vbox2.pack_start( button, FALSE, FALSE, 0 );
                                }
                        }
                }
                {
                        ui::Widget label = ui::Label( "Entity breakdown" );
                        label.show();
-                       gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, TRUE, 0 );
+                       vbox.pack_start( label, FALSE, TRUE, 0 );
                        gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
                }
                {
                        auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
-                       gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( scr ), TRUE, TRUE, 0 );
+                       vbox.pack_start( scr, TRUE, TRUE, 0 );
 
                        {
-                               ui::ListStore store = ui::ListStore(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ));
+                               auto store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ));
 
-                               ui::Widget view = ui::TreeView(ui::TreeModel( GTK_TREE_MODEL( store ) ));
-                               gtk_tree_view_set_headers_clickable( GTK_TREE_VIEW( view ), TRUE );
+                               auto view = ui::TreeView(ui::TreeModel::from(store._handle));
+                               gtk_tree_view_set_headers_clickable(view, TRUE );
 
                                {
-                                       auto renderer = ui::CellRendererText();
-                                       GtkTreeViewColumn* column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
-                                       gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
+                                       auto renderer = ui::CellRendererText(ui::New);
+                                       auto column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
+                                       gtk_tree_view_append_column(view, column );
                                        gtk_tree_view_column_set_sort_column_id( column, 0 );
                                }
 
                                {
-                                       auto renderer = ui::CellRendererText();
-                                       GtkTreeViewColumn* column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
-                                       gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column );
+                                       auto renderer = ui::CellRendererText(ui::New);
+                                       auto column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
+                                       gtk_tree_view_append_column(view, column );
                                        gtk_tree_view_column_set_sort_column_id( column, 1 );
                                }
 
@@ -879,9 +907,7 @@ void DoMapInfo(){
                {
                        char tmp[16];
                        sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
-                       GtkTreeIter iter;
-                       gtk_list_store_append( GTK_LIST_STORE( EntityBreakdownWalker ), &iter );
-                       gtk_list_store_set( GTK_LIST_STORE( EntityBreakdownWalker ), &iter, 0, ( *i ).first.c_str(), 1, tmp, -1 );
+                       EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
                }
        }
 
@@ -889,16 +915,16 @@ void DoMapInfo(){
 
        char tmp[16];
        sprintf( tmp, "%u", Unsigned( g_brushCount.get() ) );
-       gtk_entry_set_text( GTK_ENTRY( brushes_entry ), tmp );
+       brushes_entry.text(tmp);
        sprintf( tmp, "%u", Unsigned( g_entityCount.get() ) );
-       gtk_entry_set_text( GTK_ENTRY( entities_entry ), tmp );
+       entities_entry.text(tmp);
 
        modal_dialog_show( window, dialog );
 
        // save before exit
        window_get_position( window, g_posMapInfoWnd );
 
-       gtk_widget_destroy( GTK_WIDGET( window ) );
+    window.destroy();
 }
 
 
@@ -912,13 +938,14 @@ ScopeTimer( const char* message )
        : m_message( message ){
        m_timer.start();
 }
+
 ~ScopeTimer(){
        double elapsed_time = m_timer.elapsed_msec() / 1000.f;
        globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
 }
 };
 
-CopiedString g_strLastFolder = "";
+CopiedString g_strLastMapFolder = "";
 
 /*
    ================
@@ -927,11 +954,17 @@ CopiedString g_strLastFolder = "";
  */
 
 void Map_LoadFile( const char *filename ){
+       g_map.m_name = filename;
+
+       // refresh VFS to apply new pak filtering based on mapname
+       // needed for daemon DPK VFS
+       VFS_Refresh();
+
        globalOutputStream() << "Loading map from " << filename << "\n";
        ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
 
        MRU_AddFile( filename );
-       g_strLastFolder = g_path_get_dirname( filename );
+       g_strLastMapFolder = g_path_get_dirname( filename );
 
        {
                ScopeTimer timer( "map load" );
@@ -948,8 +981,8 @@ void Map_LoadFile( const char *filename ){
                                Map_Free();
                        }
                        Brush_toggleFormat( i );
-                       g_map.m_name = filename;
                        Map_UpdateTitle( g_map );
+
                        g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
                        if ( format ) {
                                format->wrongFormat = false;
@@ -979,10 +1012,6 @@ void Map_LoadFile( const char *filename ){
        Map_StartPosition();
 
        g_currentMap = &g_map;
-
-       // restart VFS to apply new pak filtering based on mapname
-       // needed for daemon DPK VFS
-       VFS_Restart();
 }
 
 class Excluder
@@ -1000,6 +1029,7 @@ public:
 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
        : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
 }
+
 bool pre( scene::Node& node ) const {
        if ( m_exclude->excluded( node ) || node.isRoot() ) {
                m_skip = true;
@@ -1011,6 +1041,7 @@ bool pre( scene::Node& node ) const {
        }
        return true;
 }
+
 void post( scene::Node& node ) const {
        if ( m_skip ) {
                m_skip = false;
@@ -1029,6 +1060,7 @@ public:
 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
        m_selected = false;
 }
+
 void visit( scene::Instance& instance ) const {
        Selectable* selectable = Instance_getSelectable( instance );
        if ( selectable != 0
@@ -1090,10 +1122,12 @@ mutable bool m_skip;
 bool selectedParent() const {
        return m_selected != 0;
 }
+
 public:
 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
        : m_walker( walker ), m_selected( 0 ), m_skip( false ){
 }
+
 bool pre( scene::Node& node ) const {
        // include node if:
        // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
@@ -1110,6 +1144,7 @@ bool pre( scene::Node& node ) const {
                return false;
        }
 }
+
 void post( scene::Node& node ) const {
        if ( m_skip ) {
                m_skip = false;
@@ -1136,7 +1171,7 @@ void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker&
 }
 
 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
-       format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out );
+       format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
 }
 
 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
@@ -1335,6 +1370,7 @@ public:
 ExcludeAllWalker( bool exclude )
        : m_exclude( exclude ){
 }
+
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        exclude_node( path.top(), m_exclude );
 
@@ -1358,6 +1394,7 @@ public:
 ExcludeSelectedWalker( bool exclude )
        : m_exclude( exclude ){
 }
+
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
        return true;
@@ -1375,6 +1412,7 @@ public:
 ExcludeRegionedWalker( bool exclude )
        : m_exclude( exclude ){
 }
+
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        exclude_node(
                path.top(),
@@ -1492,7 +1530,7 @@ void Map_RegionBrush( void ){
 bool Map_ImportFile( const char* filename ){
        ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
 
-       g_strLastFolder = g_path_get_dirname( filename );
+       g_strLastMapFolder = g_path_get_dirname( filename );
 
        bool success = false;
 
@@ -1537,37 +1575,58 @@ bool Map_ImportFile( const char* filename ){
 
 tryDecompile:
 
-       const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue( "q3map2_type" );
+       const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
        int n = string_length( path_get_extension( filename ) );
        if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
-               StringBuffer output;
-               output.push_string( AppPath_get() );
-               output.push_string( "q3map2." );
-               output.push_string( RADIANT_EXECUTABLE );
-               output.push_string( " -v -game " );
-               output.push_string( ( type && *type ) ? type : "quake3" );
-               output.push_string( " -fs_basepath \"" );
-               output.push_string( EnginePath_get() );
-               output.push_string( "\" -fs_homepath \"" );
-               output.push_string( g_qeglobals.m_userEnginePath.c_str() );
-               output.push_string( "\" -fs_game " );
-               output.push_string( gamename_get() );
-               output.push_string( " -convert -format " );
-               output.push_string( Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map" );
+               std::string output;
+               output += AppPath_get();
+               output += "q3map2";
+               output += GDEF_OS_EXE_EXT;
+
+               output += " -v -game ";
+               output += ( type && *type ) ? type : "quake3";
+               output += " -fs_basepath \"";
+               output += EnginePath_get();
+               output += "\" -fs_homepath \"";
+               output += g_qeglobals.m_userEnginePath.c_str();
+               output += "\"";
+
+               // extra pakpaths
+               for ( int i = 0; i < g_pakPathCount; i++ ) {
+                       if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
+                               output += " -fs_pakpath \"";
+                               output += g_strPakPath[i].c_str();
+                               output += "\"";
+                       }
+               }
+
+               // extra switches
+               if ( g_disableEnginePath ) {
+                       output += " -fs_nobasepath ";
+               }
+
+               if ( g_disableHomePath ) {
+                       output += " -fs_nohomepath ";
+               }
+
+               output += " -fs_game ";
+               output += gamename_get();
+               output += " -convert -format ";
+               output += Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map";
                if ( extension_equal( path_get_extension( filename ), "map" ) ) {
-                       output.push_string( " -readmap " );
+                       output += " -readmap ";
                }
-               output.push_string( " \"" );
-               output.push_string( filename );
-               output.push_string( "\"" );
+               output += " \"";
+               output += filename;
+               output += "\"";
 
                // run
                Q_Exec( NULL, output.c_str(), NULL, false, true );
 
                // rebuild filename as "filenamewithoutext_converted.map"
-               output.clear();
-               output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
-               output.push_string( "_converted.map" );
+               output = "";
+               output.append( filename, string_length( filename ) - ( n + 1 ) );
+               output += "_converted.map";
                filename = output.c_str();
 
                // open
@@ -1624,6 +1683,7 @@ scene::Node& m_parent;
 public:
 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ){
 }
+
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        if ( path.top().get_pointer() != &m_parent
                 && Node_isPrimitive( path.top() ) ) {
@@ -1636,6 +1696,7 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
        }
        return true;
 }
+
 void post( const scene::Path& path, scene::Instance& instance ) const {
        if ( path.top().get_pointer() != &m_parent
                 && Node_isPrimitive( path.top() ) ) {
@@ -1666,6 +1727,7 @@ public:
 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
        m_count = 0;
 }
+
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        if ( ++m_depth != 1 && path.top().get().isRoot() ) {
                return false;
@@ -1678,6 +1740,7 @@ bool pre( const scene::Path& path, scene::Instance& instance ) const {
        }
        return true;
 }
+
 void post( const scene::Path& path, scene::Instance& instance ) const {
        --m_depth;
 }
@@ -1764,6 +1827,7 @@ void Scene_parentSelected(){
 public:
                ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
                }
+
                void visit( scene::Instance& instance ) const {
                        if ( &m_parent != &instance.path() ) {
                                Path_parent( m_parent, instance.path() );
@@ -1781,7 +1845,6 @@ public:
 }
 
 
-
 void NewMap(){
        if ( ConfirmModified( "New Map" ) ) {
                Map_RegionOff();
@@ -1796,26 +1859,32 @@ const char* getMapsPath(){
        return g_mapsPath.c_str();
 }
 
-const char* getLastFolderPath(){
-       if (g_strLastFolder.empty()) {
-               GlobalPreferenceSystem().registerPreference( "LastFolder", CopiedStringImportStringCaller( g_strLastFolder ), CopiedStringExportStringCaller( g_strLastFolder ) );
-               if (g_strLastFolder.empty()) {
-                       g_strLastFolder = g_qeglobals.m_userGamePath;
+const char* getLastMapFolderPath(){
+       if (g_strLastMapFolder.empty()) {
+               GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
+               if (g_strLastMapFolder.empty()) {
+                       StringOutputStream buffer( 1024 );
+                       buffer << getMapsPath();
+                       if ( !file_readable( buffer.c_str() ) ) {
+                               buffer.clear();
+                               buffer << g_qeglobals.m_userGamePath.c_str() << "/";
+                       }
+                       g_strLastMapFolder = buffer.c_str();
                }
        }
-       return g_strLastFolder.c_str();
+       return g_strLastMapFolder.c_str();
 }
 
 const char* map_open( const char* title ){
-       return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false );
+       return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
 }
 
 const char* map_import( const char* title ){
-       return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false );
+       return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
 }
 
 const char* map_save( const char* title ){
-       return MainFrame_getWindow().file_dialog( FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true );
+       return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
 }
 
 void OpenMap(){
@@ -1846,7 +1915,7 @@ bool Map_SaveAs(){
        const char* filename = map_save( "Save Map" );
 
        if ( filename != NULL ) {
-               g_strLastFolder = g_path_get_dirname( filename );
+               g_strLastMapFolder = g_path_get_dirname( filename );
                MRU_AddFile( filename );
                Map_Rename( filename );
                return Map_Save();
@@ -1871,7 +1940,7 @@ void ExportMap(){
        const char* filename = map_save( "Export Selection" );
 
        if ( filename != NULL ) {
-               g_strLastFolder = g_path_get_dirname( filename );
+               g_strLastMapFolder = g_path_get_dirname( filename );
                Map_SaveSelected( filename );
        }
 }
@@ -1880,7 +1949,7 @@ void SaveRegion(){
        const char* filename = map_save( "Export Region" );
 
        if ( filename != NULL ) {
-               g_strLastFolder = g_path_get_dirname( filename );
+               g_strLastMapFolder = g_path_get_dirname( filename );
                Map_SaveRegion( filename );
        }
 }
@@ -1923,6 +1992,7 @@ public:
 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
        : m_index( index ), m_path( path ){
 }
+
 bool pre( scene::Node& node ) const {
        if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
                m_path.push( makeReference( node ) );
@@ -1939,6 +2009,7 @@ public:
 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
        : m_index( index ), m_path( path ){
 }
+
 bool pre( scene::Node& node ) const {
        if ( Node_isEntity( node ) && m_index-- == 0 ) {
                m_path.push( makeReference( node ) );
@@ -1987,6 +2058,7 @@ public:
 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
        : m_node( &node ), m_count( count ){
 }
+
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        if ( Node_isPrimitive( path.top() ) ) {
                if ( m_node == path.top().get_pointer() ) {
@@ -2008,6 +2080,7 @@ public:
 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
        : m_node( &node ), m_count( count ){
 }
+
 bool pre( const scene::Path& path, scene::Instance& instance ) const {
        if ( Node_isEntity( path.top() ) ) {
                if ( m_node == path.top().get_pointer() ) {
@@ -2036,66 +2109,58 @@ static void GetSelectionIndex( int *ent, int *brush ){
 
 void DoFind(){
        ModalDialog dialog;
-       GtkEntry* entity;
-       GtkEntry* brush;
+       ui::Entry entity{ui::null};
+       ui::Entry brush{ui::null};
 
        ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
 
-       auto accel = ui::AccelGroup();
+       auto accel = ui::AccelGroup(ui::New);
        window.add_accel_group( accel );
 
        {
                auto vbox = create_dialog_vbox( 4, 4 );
                window.add(vbox);
                {
-                       GtkTable* table = create_dialog_table( 2, 2, 4, 4 );
-                       gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 );
+                       auto table = create_dialog_table( 2, 2, 4, 4 );
+                       vbox.pack_start( table, TRUE, TRUE, 0 );
                        {
                                ui::Widget label = ui::Label( "Entity number" );
                                label.show();
-                               gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
-                                                                 (GtkAttachOptions) ( 0 ),
-                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                (table).attach(label, {0, 1, 0, 1}, {0, 0});
                        }
                        {
                                ui::Widget label = ui::Label( "Brush number" );
                                label.show();
-                               gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2,
-                                                                 (GtkAttachOptions) ( 0 ),
-                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                (table).attach(label, {0, 1, 1, 2}, {0, 0});
                        }
                        {
-                               auto entry = ui::Entry();
+                               auto entry = ui::Entry(ui::New);
                                entry.show();
-                               gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1,
-                                                                 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
-                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
-                               gtk_widget_grab_focus( GTK_WIDGET( entry ) );
+                table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
+                               gtk_widget_grab_focus( entry  );
                                entity = entry;
                        }
                        {
-                               auto entry = ui::Entry();
+                               auto entry = ui::Entry(ui::New);
                                entry.show();
-                               gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2,
-                                                                 (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ),
-                                                                 (GtkAttachOptions) ( 0 ), 0, 0 );
+                table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
 
                                brush = entry;
                        }
                }
                {
-                       GtkHBox* hbox = create_dialog_hbox( 4 );
-                       gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), TRUE, TRUE, 0 );
+                       auto hbox = create_dialog_hbox( 4 );
+                       vbox.pack_start( hbox, TRUE, TRUE, 0 );
                        {
                                auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
-                               gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
+                               hbox.pack_start( button, FALSE, FALSE, 0 );
                                widget_make_default( button );
-                               gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
+                               gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
                        }
                        {
-                               GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
-                               gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 );
-                               gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
+                               auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
+                               hbox.pack_start( button, FALSE, FALSE, 0 );
+                               gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
                        }
                }
        }
@@ -2106,9 +2171,9 @@ void DoFind(){
 
        GetSelectionIndex( &ent, &br );
        sprintf( buf, "%i", ent );
-       gtk_entry_set_text( entity, buf );
+       entity.text(buf);
        sprintf( buf, "%i", br );
-       gtk_entry_set_text( brush, buf );
+       brush.text(buf);
 
        if ( modal_dialog_show( window, dialog ) == eIDOK ) {
                const char *entstr = gtk_entry_get_text( entity );
@@ -2116,11 +2181,12 @@ void DoFind(){
                SelectBrush( atoi( entstr ), atoi( brushstr ) );
        }
 
-       gtk_widget_destroy( GTK_WIDGET( window ) );
+    window.destroy();
 }
 
 void Map_constructPreferences( PreferencesPage& page ){
-       page.appendCheckBox( "", "Load last map on open", g_bLoadLastMap );
+       page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
+       page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
 }
 
 
@@ -2130,6 +2196,7 @@ std::size_t m_unrealised;
 public:
 MapEntityClasses() : m_unrealised( 1 ){
 }
+
 void realise(){
        if ( --m_unrealised == 0 ) {
                if ( g_map.m_resource != 0 ) {
@@ -2138,6 +2205,7 @@ void realise(){
                }
        }
 }
+
 void unrealise(){
        if ( ++m_unrealised == 1 ) {
                if ( g_map.m_resource != 0 ) {
@@ -2157,6 +2225,7 @@ std::size_t m_unrealised;
 public:
 MapModuleObserver() : m_unrealised( 1 ){
 }
+
 void realise(){
        if ( --m_unrealised == 0 ) {
                ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
@@ -2166,6 +2235,7 @@ void realise(){
                g_mapsPath = buffer.c_str();
        }
 }
+
 void unrealise(){
        if ( ++m_unrealised == 1 ) {
                g_mapsPath = "";
@@ -2179,16 +2249,17 @@ CopiedString g_strLastMap;
 bool g_bLoadLastMap = false;
 
 void Map_Construct(){
-       GlobalCommands_insert( "RegionOff", FreeCaller<RegionOff>() );
-       GlobalCommands_insert( "RegionSetXY", FreeCaller<RegionXY>() );
-       GlobalCommands_insert( "RegionSetBrush", FreeCaller<RegionBrush>() );
-       GlobalCommands_insert( "RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
+       GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
+       GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
+       GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
+       GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
 
-       GlobalPreferenceSystem().registerPreference( "LastMap", CopiedStringImportStringCaller( g_strLastMap ), CopiedStringExportStringCaller( g_strLastMap ) );
-       GlobalPreferenceSystem().registerPreference( "LoadLastMap", BoolImportStringCaller( g_bLoadLastMap ), BoolExportStringCaller( g_bLoadLastMap ) );
-       GlobalPreferenceSystem().registerPreference( "MapInfoDlg", WindowPositionImportStringCaller( g_posMapInfoWnd ), WindowPositionExportStringCaller( g_posMapInfoWnd ) );
+       GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
+       GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
+       GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
+       GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
 
-       PreferencesDialog_addSettingsPreferences( FreeCaller1<PreferencesPage&, Map_constructPreferences>() );
+       PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
 
        GlobalEntityClassManager().attach( g_MapEntityClasses );
        Radiant_attachHomePathsObserver( g_MapModuleObserver );