]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - radiant/map.cpp
rename “LastFolder” to “LastMapFolder”
[xonotic/netradiant.git] / radiant / map.cpp
index 051c0a51ac6f90d4f47d64897b7221557900d0a9..06c5f48cf3a47c16a1ecd01035211bbc216f712e 100644 (file)
-/*\r
-Copyright (C) 1999-2007 id Software, Inc. and contributors.\r
-For a list of contributors, see the accompanying CONTRIBUTORS file.\r
-\r
-This file is part of GtkRadiant.\r
-\r
-GtkRadiant is free software; you can redistribute it and/or modify\r
-it under the terms of the GNU General Public License as published by\r
-the Free Software Foundation; either version 2 of the License, or\r
-(at your option) any later version.\r
-\r
-GtkRadiant is distributed in the hope that it will be useful,\r
-but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-GNU General Public License for more details.\r
-\r
-You should have received a copy of the GNU General Public License\r
-along with GtkRadiant; if not, write to the Free Software\r
-Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\r
-*/\r
-\r
-#include "stdafx.h"\r
-#include <string.h>\r
-#if defined (__linux__) || defined (__APPLE__)\r
-#include <unistd.h>\r
-#endif\r
-#include "preferences.h"\r
-#include "mainframe.h"\r
-#include "gtkmisc.h"\r
-#include "filters.h"\r
-\r
-extern MainFrame* g_pParentWnd;\r
-\r
-int    modified;       // for quit confirmation (0 = clean, 1 = unsaved,\r
-                               // 2 = autosaved, but not regular saved) \r
-\r
-char           currentmap[1024];\r
-\r
-brush_t        active_brushes;         // brushes currently being displayed\r
-brush_t        selected_brushes;       // highlighted\r
-\r
-face_t *selected_face;\r
-brush_t        *selected_face_brush;\r
-\r
-brush_t        filtered_brushes;       // brushes that have been filtered or regioned\r
-\r
-entity_t       entities;               // head/tail of doubly linked list\r
-\r
-entity_t       *world_entity = NULL; // "classname" "worldspawn" !\r
-\r
-void Map_Init()\r
-{\r
-  Map_Free();\r
-}\r
-\r
-\r
-bool  g_bCancel_Map_LoadFile; // Hydra: moved this here\r
-\r
-// TTimo\r
-// need that in a variable, will have to tweak depending on the game\r
-int g_MaxWorldCoord = 64*1024;\r
-int g_MinWorldCoord = -64*1024;\r
-\r
-// the max size we allow on brushes, this is dependant on world coords too\r
-// makes more sense to say smaller I think?\r
-int g_MaxBrushSize = (g_MaxWorldCoord-1)*2;\r
-\r
-void AddRegionBrushes (void);\r
-void RemoveRegionBrushes (void);\r
-\r
-/*\r
-=============================================================\r
-\r
-  Cross map selection saving\r
-\r
-  this could fuck up if you have only part of a complex entity selected...\r
-=============================================================\r
-*/\r
-\r
-brush_t                between_brushes;\r
-entity_t       between_entities;\r
-\r
-bool g_bRestoreBetween = false;\r
-\r
-void Map_SaveBetween (void)\r
-{\r
-  if (g_pParentWnd->ActiveXY())\r
-  {\r
-    g_bRestoreBetween = true;\r
-    g_pParentWnd->ActiveXY()->Copy();\r
-  }\r
-  return;\r
-}\r
-\r
-void Map_RestoreBetween (void)\r
-{\r
-  if (g_pParentWnd->ActiveXY() && g_bRestoreBetween)\r
-    g_pParentWnd->ActiveXY()->Paste();\r
-}\r
-\r
-//============================================================================\r
-\r
-bool CheckForTinyBrush(brush_t* b, int n, float fSize)\r
-{\r
-  bool bTiny = false;\r
-       for (int i=0 ; i<3 ; i++)\r
-       {\r
-    if (b->maxs[i] - b->mins[i] < fSize)\r
-      bTiny = true;\r
-  }\r
-  if (bTiny)\r
-    Sys_Printf("Possible problem brush (too small) #%i ", n);\r
-  return bTiny;\r
-}\r
-\r
-void Map_BuildBrushData(void)\r
-{\r
-  brush_t *b, *next;\r
-\r
-  if (active_brushes.next == NULL)\r
-    return;\r
-\r
-  Sys_BeginWait ();    // this could take a while\r
-\r
-  int n = 0;\r
-  for (b=active_brushes.next ; b != NULL && b != &active_brushes ; b=next)\r
-  {\r
-    next = b->next;\r
-    Brush_Build( b, true, false, false );\r
-    if (!b->brush_faces || (g_PrefsDlg.m_bCleanTiny && CheckForTinyBrush(b, n++, g_PrefsDlg.m_fTinySize)))\r
-    {\r
-      Brush_Free (b);\r
-      Sys_Printf ("Removed degenerate brush\n");\r
-    }\r
-  }\r
-  Sys_EndWait();\r
-}\r
-\r
-entity_t *Map_FindClass (char *cname)\r
-{\r
-       entity_t        *ent;\r
-\r
-       for (ent = entities.next ; ent != &entities ; ent=ent->next)\r
-       {\r
-               if (!strcmp(cname, ValueForKey (ent, "classname")))\r
-                       return ent;\r
-       }\r
-       return NULL;\r
-}\r
-\r
-/*\r
-================\r
-Map_Free\r
-free all map elements, reinitialize the structures that depend on them\r
-================\r
-*/\r
-void Map_Free (void)\r
-{\r
-  g_bRestoreBetween = false;\r
-  if (selected_brushes.next &&\r
-      (selected_brushes.next != &selected_brushes))\r
-    {\r
-      if (gtk_MessageBox (g_pParentWnd->m_pWidget, "Copy selection?", " ", MB_YESNO) == IDYES)\r
-       Map_SaveBetween ();\r
-    }\r
-\r
-       QERApp_ActiveShaders_SetInUse( false );\r
-       Pointfile_Clear ();\r
-       g_qeglobals.d_num_entities = 0;\r
-\r
-       if (!active_brushes.next)\r
-       {\r
-         // first map\r
-         active_brushes.prev = active_brushes.next = &active_brushes;\r
-         selected_brushes.prev = selected_brushes.next = &selected_brushes;\r
-         filtered_brushes.prev = filtered_brushes.next = &filtered_brushes;\r
-         entities.prev = entities.next = &entities;\r
-       }\r
-       else\r
-       {\r
-         // free selected faces array\r
-         g_ptrSelectedFaces.RemoveAll();\r
-         g_ptrSelectedFaceBrushes.RemoveAll();\r
-               while (active_brushes.next != &active_brushes)\r
-                       Brush_Free (active_brushes.next);\r
-               while (selected_brushes.next != &selected_brushes)\r
-                       Brush_Free (selected_brushes.next);\r
-               while (filtered_brushes.next != &filtered_brushes)\r
-                       Brush_Free (filtered_brushes.next);\r
-               while (entities.next != &entities)\r
-                       Entity_Free (entities.next);\r
-       }\r
-\r
-  if (world_entity)\r
-    Entity_Free(world_entity);\r
-       world_entity = NULL;\r
-}\r
-\r
-entity_t *AngledEntity()\r
-{\r
-  entity_t *ent = Map_FindClass ("info_player_start");\r
-       if (!ent)\r
-  {\r
-               ent = Map_FindClass ("info_player_deathmatch");\r
-  }\r
-  if (!ent)\r
-  {\r
-               ent = Map_FindClass ("info_player_deathmatch");\r
-  }\r
-  if (!ent)\r
-  {\r
-    ent = Map_FindClass ("team_CTF_redplayer");\r
-  }\r
-  if (!ent)\r
-  {\r
-    ent = Map_FindClass ("team_CTF_blueplayer");\r
-  }\r
-  if (!ent)\r
-  {\r
-    ent = Map_FindClass ("team_CTF_redspawn");\r
-  }\r
-  if (!ent)\r
-  {\r
-    ent = Map_FindClass ("team_CTF_bluespawn");\r
-  }\r
-  return ent;\r
-}\r
-\r
-//\r
-// move the view to a start position\r
-//\r
-void Map_StartPosition()\r
-{\r
-  entity_t *ent = AngledEntity();\r
-\r
-  g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;\r
-  if (ent)\r
-  {\r
-    GetVectorForKey (ent, "origin", g_pParentWnd->GetCamWnd()->Camera()->origin);\r
-    GetVectorForKey (ent, "origin", g_pParentWnd->GetXYWnd()->GetOrigin());\r
-    g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = FloatForKey (ent, "angle");\r
-  }\r
-  else\r
-  {\r
-    g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;\r
-    VectorCopy (vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin);\r
-    VectorCopy (vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin());\r
-  }\r
-}\r
-\r
-void Map_FreeEntities(CPtrArray *ents)\r
-{\r
-  int i, j, num_ents, num_brushes;\r
-  entity_t* e;\r
-  CPtrArray* brushes;\r
-\r
-  num_ents = ents->GetSize();\r
-  for(i=0; i<num_ents; i++)\r
-  {\r
-    e = (entity_t*)ents->GetAt(i);\r
-    brushes = (CPtrArray*)e->pData;\r
-    num_brushes = brushes->GetSize();\r
-    for(j=0; j<num_brushes; j++)\r
-      Brush_Free((brush_t*)brushes->GetAt(j));\r
-    brushes->RemoveAll();\r
-    delete (CPtrArray*)e->pData;\r
-    e->pData = NULL;\r
-    Entity_Free(e);\r
-  }\r
-  ents->RemoveAll();\r
-}\r
-\r
-/*!\todo Possibly make the import Undo-friendly by calling Undo_End for new brushes and ents */\r
-void Map_ImportEntities(CPtrArray *ents, bool bAddSelected = false)\r
-{\r
-  int num_ents, num_brushes;\r
-  CPtrArray *brushes;\r
-  vec3_t mins, maxs;\r
-  entity_t *e;\r
-  brush_t *b;\r
-  face_t *f;\r
-  int i,j;\r
-\r
-  GPtrArray *new_ents = g_ptr_array_new();\r
-\r
-  g_qeglobals.bPrimitBrushes = false;\r
-\r
-  brush_t *pBrushList = (bAddSelected) ? &selected_brushes : &active_brushes;\r
-\r
-  bool bDoneBPCheck = false;\r
-  g_qeglobals.bNeedConvert = false;\r
-  // HACK: find out if this map file was a BP one\r
-  // check the first brush in the file that is NOT a patch\r
-  // this will not be necessary when we allow both formats in the same file\r
-  num_ents = ents->GetSize();\r
-  for(i=0; !bDoneBPCheck && i<num_ents; i++)\r
-  {\r
-    e = (entity_t*)ents->GetAt(i);\r
-    brushes = (CPtrArray*)e->pData;\r
-    num_brushes = brushes->GetSize();\r
-    for(j=0; !bDoneBPCheck && j<num_brushes; j++)\r
-    {\r
-      /*!todo Allow mixing texdef formats per-face. */\r
-      b = (brush_t *)brushes->GetAt(j);\r
-      if(b->patchBrush) continue;\r
-      bDoneBPCheck = true;\r
-      int BP_param = -1;\r
-      if(b->bBrushDef && !g_qeglobals.m_bBrushPrimitMode)\r
-        BP_param = 0;\r
-      else if(!b->bBrushDef && g_qeglobals.m_bBrushPrimitMode)\r
-        BP_param = 1;\r
-\r
-      if(BP_param != -1)\r
-      {\r
-        switch(BP_MessageBox(BP_param))\r
-        {\r
-        case 0:\r
-          Map_FreeEntities(ents);\r
-          return;\r
-        case 1:\r
-          g_qeglobals.bNeedConvert = true;\r
-          break;\r
-        case 2:\r
-          g_qeglobals.bNeedConvert = false;\r
-          break;\r
-        }\r
-      }\r
-    }\r
-  }\r
-\r
-  // process the entities into the world geometry\r
-  num_ents = ents->GetSize();\r
-  for(i=0; i<num_ents; i++)\r
-  {\r
-    num_brushes = 0;\r
-    e = (entity_t*)ents->GetAt(i);\r
-    brushes = (CPtrArray*)e->pData;\r
\r
-    num_brushes = brushes->GetSize();\r
-    // link brushes into entity\r
-    for(j=0; j<num_brushes; j++)\r
-    {\r
-      Entity_LinkBrush(e, (brush_t *)brushes->GetAt(j));\r
-      g_qeglobals.d_parsed_brushes++;\r
-    }\r
-    brushes->RemoveAll();\r
-    delete brushes;\r
-    e->pData = NULL;\r
-\r
-    // set entity origin\r
-    GetVectorForKey (e, "origin", e->origin);\r
-    // set entity eclass\r
-    /*!\todo Make SetKeyValue check for "classname" change and assign appropriate eclass */\r
-    e->eclass = Eclass_ForName (ValueForKey (e, "classname"),\r
-      (e->brushes.onext != &e->brushes));\r
-\r
-    // go through all parsed brushes and build stuff\r
-    for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)\r
-    {\r
-      for(f = b->brush_faces; f != NULL; f = f->next)\r
-      {\r
-        f->pShader = QERApp_Shader_ForName(f->texdef.GetName());\r
-        f->d_texture = f->pShader->getTexture();\r
-      }\r
-\r
-      // when brushes are in final state, build the planes and windings\r
-      // NOTE: also converts BP brushes if g_qeglobals.bNeedConvert is true\r
-      Brush_Build(b);\r
-    }\r
-\r
-//#define TERRAIN_HACK\r
-#undef TERRAIN_HACK\r
-\r
-#ifdef TERRAIN_HACK\r
-    if ((strcmp(ValueForKey(e, "terrain"),"1") == 0 && strcmp(e->eclass->name,"func_group") == 0))\r
-    {\r
-      \r
-      // two aux pointers to the shaders used in the terrain entity\r
-      // we don't keep refcount on them since they are only temporary\r
-      // this avoids doing expensive lookups by name for all faces\r
-      IShader *pTerrainShader, *pCaulk;\r
-      \r
-      pTerrainShader = NULL;\r
-      pCaulk = QERApp_Shader_ForName(SHADER_CAULK);\r
-      \r
-      for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)\r
-      {\r
-        if (pTerrainShader == NULL)\r
-          for(f = b->brush_faces; f != NULL; f = f->next)\r
-            if (strcmp(f->texdef.GetName(), SHADER_CAULK)!=0)\r
-              pTerrainShader = f->pShader;\r
-            \r
-            if (pTerrainShader)\r
-            {\r
-              for(f = b->brush_faces; f != NULL; f = f->next)\r
-              {\r
-                if (strcmp(f->texdef.GetName(), SHADER_CAULK)!=0) // not caulk\r
-                  Face_SetShader(f, pTerrainShader->getName());\r
-                else\r
-                  Face_SetShader(f, pCaulk->getName());\r
-              }\r
-            }\r
-            else\r
-              Sys_Printf("WARNING: no terrain shader found for brush\n");\r
-      }\r
-    }\r
-#endif\r
-\r
-#define PATCH_HACK\r
-#ifdef PATCH_HACK\r
-    for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)\r
-    {\r
-      // patch hack, to be removed when dependency on brush_faces is removed\r
-      if (b->patchBrush)\r
-      {\r
-        Patch_CalcBounds(b->pPatch, mins, maxs);\r
-        for (int i=0; i<3; i++)\r
-        {\r
-          if ((int)mins[i] == (int)maxs[i])\r
-          {\r
-            mins[i] -= 4;\r
-            maxs[i] += 4;\r
-          }\r
-        }\r
-        Brush_Resize(b, mins, maxs);\r
-        Brush_Build(b);\r
-      }\r
-    }\r
-#endif\r
-    // add brush for fixedsize entity\r
-    if (e->eclass->fixedsize)\r
-    {\r
-      vec3_t mins, maxs;\r
-      VectorAdd (e->eclass->mins, e->origin, mins);\r
-      VectorAdd (e->eclass->maxs, e->origin, maxs);\r
-      b = Brush_Create (mins, maxs, &e->eclass->texdef);\r
-      Entity_LinkBrush(e, b);\r
-      Brush_Build(b);\r
-    }\r
-\r
-    for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)\r
-      Brush_AddToList(b, pBrushList);\r
-\r
-    if (strcmp(e->eclass->name, "worldspawn") == 0)\r
-    {\r
-      if (world_entity)\r
-      {\r
-        while(e->brushes.onext != &e->brushes)\r
-        {\r
-          b = e->brushes.onext;\r
-          Entity_UnlinkBrush(b);\r
-          Entity_LinkBrush(world_entity, b);\r
-        }\r
-        Entity_Free(e);\r
-      }\r
-      else\r
-      {\r
-        world_entity = e;\r
-      }\r
-    }\r
-    else if (strcmp(e->eclass->name, "group_info") == 0)\r
-    {\r
-      // it's a group thing!\r
-      Group_Add(e);\r
-      Entity_Free(e);\r
-    }\r
-    else\r
-    {\r
-      // fix target/targetname collisions\r
-      if ((g_PrefsDlg.m_bDoTargetFix) && (strcmp(ValueForKey(e, "target"), "") != 0))\r
-      {\r
-        GPtrArray *t_ents = g_ptr_array_new();\r
-        entity_t *e_target;\r
-        const char *target = ValueForKey(e, "target");\r
-        qboolean bCollision=FALSE;\r
-        \r
-        // check the current map entities for an actual collision\r
-        for (e_target = entities.next; e_target != &entities; e_target = e_target->next)\r
-        {\r
-          if(!strcmp(target, ValueForKey(e_target, "target")))\r
-          {\r
-            bCollision = TRUE;\r
-            // make sure the collision is not between two imported entities\r
-            for(j=0; j<(int)new_ents->len; j++)\r
-            {\r
-              if(e_target == g_ptr_array_index(new_ents, j))\r
-                bCollision = FALSE;\r
-            }\r
-          }\r
-        }\r
-        \r
-        // find the matching targeted entity(s)\r
-        if(bCollision)\r
-        {\r
-          for(j=num_ents-1; j>0; j--)\r
-          {\r
-            e_target = (entity_t*)ents->GetAt(j);\r
-            if(e_target != NULL && e_target != e)\r
-            {\r
-              const char *targetname = ValueForKey(e_target, "targetname");\r
-              if( (targetname != NULL) && (strcmp(target, targetname) == 0) )\r
-                g_ptr_array_add(t_ents, (gpointer)e_target);\r
-            }\r
-          }\r
-          if(t_ents->len > 0)\r
-          {\r
-            // link the first to get a unique target/targetname\r
-            Entity_Connect(e, (entity_t*)g_ptr_array_index(t_ents,0));\r
-            // set the targetname of the rest of them manually\r
-            for(j = 1; j < (int)t_ents->len; j++)\r
-              SetKeyValue( (entity_t*)g_ptr_array_index(t_ents, j), "targetname", ValueForKey(e, "target") );\r
-          }\r
-          g_ptr_array_free(t_ents, FALSE);\r
-        }\r
-      }\r
-      \r
-      // add the entity to the end of the entity list\r
-      Entity_AddToList(e, &entities);\r
-      g_qeglobals.d_num_entities++;\r
-      \r
-      // keep a list of ents added to avoid testing collisions against them\r
-      g_ptr_array_add(new_ents, (gpointer)e);\r
-    }\r
-  }\r
-  g_ptr_array_free(new_ents, FALSE);\r
-  \r
-  ents->RemoveAll();\r
-\r
-  g_qeglobals.bNeedConvert = false;\r
-}\r
-\r
-void Map_Import(IDataStream *in, const char *type, bool bAddSelected)\r
-{\r
-  CPtrArray ents;\r
-\r
-  g_pParentWnd->GetSynapseClient().ImportMap(in, &ents, type);\r
-  Map_ImportEntities(&ents, bAddSelected);\r
-}\r
-\r
-/*\r
-================\r
-Map_LoadFile\r
-================\r
-*/\r
-void Map_LoadFile (const char *filename)\r
-{\r
-  clock_t start, finish;\r
-  double elapsed_time;\r
-  start = clock();\r
-\r
-  Sys_BeginWait ();\r
-  Select_Deselect();\r
-  /*!\r
-  \todo FIXME TTimo why is this commented out? \r
-  stability issues maybe? or duplicate feature?\r
-  forcing to show the console during map load was a good thing IMO\r
-  */\r
-  //SetInspectorMode(W_CONSOLE);\r
-  Sys_Printf ("Loading map from %s\n", filename );\r
-\r
-  Map_Free ();\r
-  //++timo FIXME: maybe even easier to have Group_Init called from Map_Free?\r
-  Group_Init();\r
-  g_qeglobals.d_num_entities = 0;\r
-  g_qeglobals.d_parsed_brushes = 0;\r
-\r
-\r
-  // cancel the map loading process\r
-  // used when conversion between standard map format and BP format is required and the user cancels the process\r
-  g_bCancel_Map_LoadFile = false;\r
-\r
-  strcpy (currentmap, filename);\r
-\r
-  g_bScreenUpdates = false; // leo: avoid redraws while loading the map (see fenris:1952)\r
-\r
-  // prepare to let the map module do the parsing\r
-  FileStream file;\r
-  const char* type = strrchr(filename,'.');\r
-  if(type!=NULL) type++;\r
-    // NOTE TTimo opening has binary doesn't make a lot of sense\r
-  // but opening as text confuses the scriptlib parser\r
-  // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=261\r
-  // this may be a problem if we "rb" and use the XML parser, might have an incompatibility\r
-  if (file.Open(filename, "rb"))\r
-    Map_Import(&file, type);\r
-  else\r
-    Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for read\n", filename);\r
-  file.Close();\r
-\r
-  g_bScreenUpdates = true;\r
-\r
-  if (g_bCancel_Map_LoadFile)\r
-  {\r
-    Sys_Printf("Map_LoadFile canceled\n");\r
-    Map_New();\r
-    Sys_EndWait();\r
-    return;\r
-  }\r
-\r
-  if (!world_entity)\r
-  {\r
-    Sys_Printf ("No worldspawn in map.\n");\r
-    Map_New ();\r
-    Sys_EndWait();\r
-    return;\r
-  }\r
-  finish = clock();\r
-  elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;\r
-\r
-  Sys_Printf ("--- LoadMapFile ---\n");\r
-  Sys_Printf ("%s\n", filename );\r
-  \r
-  Sys_Printf ("%5i brushes\n",  g_qeglobals.d_parsed_brushes );\r
-  Sys_Printf ("%5i entities\n", g_qeglobals.d_num_entities);\r
-  Sys_Printf ("%5.2f second(s) load time\n", elapsed_time );\r
-  \r
-  Sys_EndWait();\r
-  \r
-  Map_RestoreBetween ();\r
-  \r
-  //\r
-  // move the view to a start position\r
-  //\r
-  Map_StartPosition();\r
-\r
-  Map_RegionOff ();\r
-\r
-  modified = false;\r
-  Sys_SetTitle (filename);\r
-\r
-  Texture_ShowInuse ();\r
-  QERApp_SortActiveShaders();\r
-\r
-  Sys_UpdateWindows (W_ALL);\r
-}\r
-\r
-/*!\r
-===========\r
-Supporting functions for Map_SaveFile, builds a CPtrArray with the filtered / non filtered brushes\r
-===========\r
-*/\r
-void CleanFilter(entity_t *ent)\r
-{\r
-  if (ent->pData)\r
-  {\r
-    delete static_cast<CPtrArray*>(ent->pData);\r
-    ent->pData = NULL;\r
-  }\r
-}\r
-\r
-/*!\r
-filters out the region brushes if necessary\r
-returns true if this entity as a whole is out of the region\r
-(if all brushes are filtered out, then the entity will be completely dropped .. except if it's worldspawn of course)\r
-*/\r
-bool FilterChildren(entity_t *ent, bool bRegionOnly = false, bool bSelectedOnly = false)\r
-{\r
-  if(ent->brushes.onext == &ent->brushes)\r
-    return false;\r
-  // entity without a brush, ignore it... this can be caused by Undo\r
-\r
-  // filter fixedsize ents by their eclass bounding box\r
-  // don't add their brushes\r
-  if (ent->eclass->fixedsize)\r
-  {\r
-    if(bSelectedOnly && !IsBrushSelected(ent->brushes.onext))\r
-      return false;\r
-\r
-    if(bRegionOnly && region_active)\r
-    {\r
-      for (int i=0 ; i<3 ; i++)\r
-           {\r
-                   if ((ent->origin[i] + ent->eclass->mins[i]) > region_maxs[i])\r
-                           return false;\r
-                   if ((ent->origin[i] + ent->eclass->maxs[i]) < region_mins[i])\r
-                           return false;\r
-           }\r
-    }\r
-  }\r
-  else\r
-  {\r
-    for (brush_t *b = ent->brushes.onext ; b != &ent->brushes ; b=b->onext)\r
-    {\r
-      // set flag to use brushprimit_texdef\r
-      if(g_qeglobals.m_bBrushPrimitMode)\r
-        b->bBrushDef = true;\r
-      else\r
-        b->bBrushDef = false;\r
-\r
-      // add brush, unless it's excluded by region\r
-      if ( !(bRegionOnly && Map_IsBrushFiltered(b)) &&\r
-           !(bSelectedOnly && !IsBrushSelected(b)) )\r
-        ((CPtrArray*)ent->pData)->Add(b);\r
-    }\r
-\r
-    if (((CPtrArray*)ent->pData)->GetSize() <= 0)\r
-      return false;\r
-  }\r
-  return true;\r
-}\r
-\r
-entity_t *region_startpoint = NULL;\r
-void Map_ExportEntities(CPtrArray* ents, bool bRegionOnly = false, bool bSelectedOnly = false)\r
-{\r
-  int i;\r
-  entity_t *e;\r
-\r
-  /*!\r
-  \todo the entity_t needs to be reworked and asbtracted some more\r
-  \r
-  keeping the entity_t as the struct providing access to a list of map objects, a list of epairs and various other info?\r
-  but separating some more the data that belongs to the entity_t and the 'sons' data\r
-  on a side note, I don't think that doing that with linked list would be a good thing\r
-  \r
-  for now, we use the blind void* in entity_t casted to a CPtrArray of brush_t* to hand out a list of the brushes for map write\r
-  the next step is very likely to be a change of the brush_t* to a more abstract object?\r
-  */\r
-\r
-  FilterChildren(world_entity, bRegionOnly, bSelectedOnly);\r
-  ents->Add(world_entity);\r
-\r
-  for (e=entities.next ; e!=&entities ; e=e->next)\r
-  {\r
-    // not sure this still happens, probably safe to leave it in\r
-    if ((!strcmp(ValueForKey (e, "classname"), "worldspawn")) && (e!=world_entity))\r
-    {\r
-      Sys_FPrintf(SYS_ERR, "Dropping parasite worldspawn entity\n");\r
-      continue;\r
-    }\r
-\r
-    // entities which brushes are completely filtered out by regioning are not printed to the map\r
-    if (FilterChildren(e, bRegionOnly, bSelectedOnly))\r
-      ents->Add(e);\r
-  }\r
-\r
-  if (bRegionOnly && region_active)\r
-  {\r
-    for(i=0; i<6; i++)\r
-      ((CPtrArray*)world_entity->pData)->Add(region_sides[i]);\r
-\r
-    ents->Add(region_startpoint);\r
-  }\r
-}\r
-\r
-void Map_Export(IDataStream *out, const char *type, bool bRegionOnly, bool bSelectedOnly)\r
-{\r
-  entity_t     *e;\r
-\r
-  CPtrArray ents;\r
-  \r
-  if (bRegionOnly && region_active)\r
-    AddRegionBrushes();\r
-\r
-  // create the filters\r
-  world_entity->pData = new CPtrArray();\r
-  for(e = entities.next; e != &entities; e = e->next)\r
-    e->pData = new CPtrArray();\r
-\r
-  Map_ExportEntities(&ents, bRegionOnly, bSelectedOnly);\r
-\r
-  g_pParentWnd->GetSynapseClient().ExportMap(&ents, out, type);\r
-\r
-  // cleanup the filters\r
-  CleanFilter(world_entity);\r
-  for (e=entities.next ; e!=&entities ; e=e->next)\r
-    CleanFilter(e);\r
-\r
-  if (bRegionOnly && region_active)\r
-    RemoveRegionBrushes();\r
-}\r
-\r
-const char* filename_get_extension(const char* filename)\r
-{\r
-  const char* type = strrchr(filename,'.');\r
-  if(type != NULL)\r
-    return ++type;\r
-  return "";\r
-}\r
-\r
-/*\r
-===========\r
-Map_SaveFile\r
-\todo FIXME remove the use_region, this is broken .. work with a global flag to set region mode or not\r
-===========\r
-*/\r
-void Map_SaveFile (const char *filename, qboolean use_region )\r
-{\r
-  clock_t start, finish;\r
-  double elapsed_time;\r
-  start = clock();\r
-  Sys_Printf("Saving map to %s\n",filename);\r
-\r
-       Pointfile_Clear ();\r
-\r
-       if (!use_region)\r
-       {\r
-               char    backup[1024];\r
-\r
-               // rename current to .bak\r
-               strcpy (backup, filename);\r
-               StripExtension (backup);\r
-               strcat (backup, ".bak");\r
-               unlink (backup);\r
-               rename (filename, backup);\r
-       }\r
-\r
-       Sys_Printf ("Map_SaveFile: %s\n", filename);\r
-\r
-  // build the out data stream\r
-  FileStream file;\r
-  if (!file.Open(filename,"w"))\r
-  {\r
-    Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for write\n", filename);\r
-    return;\r
-  }\r
-\r
-  // extract filetype\r
-  Map_Export(&file, filename_get_extension(filename), use_region);\r
-\r
-  file.Close();\r
-\r
-  finish = clock();\r
-  elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;\r
-\r
-  Sys_Printf ("Saved in %-.2f second(s).\n",elapsed_time);\r
-       modified = false;\r
-\r
-       if ( !strstr( filename, "autosave" ) )\r
-               Sys_SetTitle (filename);\r
-\r
-       if (!use_region)\r
-       {\r
-               time_t  timer;\r
-\r
-               time (&timer);\r
-\r
-    Sys_Beep ();\r
-\r
-               Sys_Status ("Saved.", 0);\r
-       }\r
-}\r
-\r
-/*\r
-===========\r
-Map_New\r
-\r
-===========\r
-*/\r
-void Map_New (void)\r
-{\r
-       Sys_Printf ("Map_New\n");\r
-       Map_Free ();\r
-\r
-       strcpy (currentmap, "unnamed.map");\r
-       Sys_SetTitle (currentmap);\r
-\r
-  world_entity = (entity_s*)qmalloc(sizeof(*world_entity));\r
-       world_entity->brushes.onext = \r
-               world_entity->brushes.oprev = &world_entity->brushes;\r
-       SetKeyValue (world_entity, "classname", "worldspawn");\r
-       world_entity->eclass = Eclass_ForName ("worldspawn", true);\r
-\r
-       g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;\r
-       g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;\r
-       VectorCopy (vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin);\r
-       g_pParentWnd->GetCamWnd()->Camera()->origin[2] = 48;\r
-       VectorCopy (vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin());\r
-\r
-       Map_RestoreBetween ();\r
-\r
-  Group_Init();\r
-\r
-       Sys_UpdateWindows (W_ALL);\r
-       modified = false;\r
-}\r
-\r
-/*\r
-===========================================================\r
-\r
-  REGION\r
-\r
-===========================================================\r
-*/\r
-qboolean       region_active;\r
-vec3_t region_mins = {g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord};\r
-vec3_t region_maxs = {g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord};\r
-\r
-brush_t        *region_sides[6];\r
-\r
-/*\r
-===========\r
-AddRegionBrushes\r
-a regioned map will have temp walls put up at the region boundary\r
-\todo TODO TTimo old implementation of region brushes\r
-  we still add them straight in the worldspawn and take them out after the map is saved\r
-  with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module\r
-===========\r
-*/\r
-void AddRegionBrushes (void)\r
-{\r
-       vec3_t  mins, maxs;\r
-       int             i;\r
-       texdef_t        td;\r
-\r
-       if (!region_active)\r
-  {\r
-#ifdef _DEBUG\r
-    Sys_FPrintf( SYS_WRN, "Unexpected AddRegionBrushes call.\n");\r
-#endif\r
-               return;\r
-  }\r
-\r
-       memset (&td, 0, sizeof(td));\r
-       td.SetName(SHADER_NOT_FOUND);\r
-\r
-  // set mins\r
-  VectorSet(mins, region_mins[0]-32, region_mins[1]-32, region_mins[2]-32);\r
-\r
-  // vary maxs\r
-  for(i=0; i<3; i++)\r
-  {\r
-    VectorSet(maxs, region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32);\r
-    maxs[i] = region_mins[i];\r
-    region_sides[i] = Brush_Create (mins, maxs, &td);\r
-  }\r
-\r
-  // set maxs\r
-  VectorSet(maxs, region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32);\r
-\r
-  // vary mins\r
-  for(i=0; i<3; i++)\r
-  {\r
-    VectorSet(mins, region_mins[0]-32, region_mins[1]-32, region_mins[2]-32);\r
-    mins[i] = region_maxs[i];\r
-    region_sides[i+3] = Brush_Create (mins, maxs, &td);\r
-  }\r
-\r
-  \r
-  // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=503\r
-  // this is a safe check, but it should not really happen anymore  \r
-  vec3_t vOrig;\r
-  VectorSet(vOrig,\r
-    (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],\r
-    (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1], \r
-    (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2]);\r
-\r
-  for (i=0 ; i<3 ; i++)\r
-  {\r
-    if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i])\r
-    {\r
-      Sys_FPrintf(SYS_ERR, "Camera is NOT in the region, it's likely that the region won't compile correctly\n");\r
-    }\r
-  }\r
-  \r
-  // write the info_playerstart\r
-  region_startpoint = Entity_Alloc();\r
-  SetKeyValue(region_startpoint, "classname", "info_player_start");\r
-  region_startpoint->eclass = Eclass_ForName ("info_player_start", false);\r
-  char sTmp[1024];\r
-  sprintf(sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2]);\r
-  SetKeyValue(region_startpoint, "origin", sTmp);\r
-  sprintf(sTmp, "%d", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW]);\r
-  SetKeyValue(region_startpoint, "angle", sTmp);\r
-  // empty array of children\r
-  region_startpoint->pData = new CPtrArray;\r
-}\r
-\r
-void RemoveRegionBrushes (void)\r
-{\r
-       int             i;\r
-\r
-       if (!region_active)\r
-               return;\r
-       for (i=0 ; i<6 ; i++)\r
-               Brush_Free (region_sides[i]);\r
-\r
-  CleanFilter(region_startpoint);\r
-  Entity_Free(region_startpoint);\r
-}\r
-\r
-qboolean Map_IsBrushFiltered (brush_t *b)\r
-{\r
-       int             i;\r
-\r
-       for (i=0 ; i<3 ; i++)\r
-       {\r
-               if (b->mins[i] > region_maxs[i])\r
-                       return true;\r
-               if (b->maxs[i] < region_mins[i])\r
-                       return true;\r
-       }\r
-       return false;\r
-}\r
-\r
-/*\r
-===========\r
-Map_RegionOff\r
-\r
-Other filtering options may still be on\r
-===========\r
-*/\r
-void Map_RegionOff (void)\r
-{\r
-       brush_t *b, *next;\r
-       int                     i;\r
-\r
-       region_active = false;\r
-       for (i=0 ; i<3 ; i++)\r
-       {\r
-               region_maxs[i] = g_MaxWorldCoord-64;\r
-               region_mins[i] = g_MinWorldCoord+64;\r
-       }\r
-       \r
-       for (b=filtered_brushes.next ; b != &filtered_brushes ; b=next)\r
-       {\r
-               next = b->next;\r
-               if (Map_IsBrushFiltered (b))\r
-                       continue;               // still filtered\r
-               Brush_RemoveFromList (b);\r
-               if (active_brushes.next == NULL || active_brushes.prev == NULL)\r
-               {\r
-                       active_brushes.next = &active_brushes;\r
-                       active_brushes.prev = &active_brushes;\r
-               }\r
-               Brush_AddToList (b, &active_brushes);\r
-               b->bFiltered = FilterBrush(b);\r
-       }\r
-       Sys_UpdateWindows (W_ALL);\r
-}\r
-\r
-void Map_ApplyRegion (void)\r
-{\r
-       brush_t *b, *next;\r
-\r
-       region_active = true;\r
-       for (b=active_brushes.next ; b != &active_brushes ; b=next)\r
-       {\r
-               next = b->next;\r
-               if (!Map_IsBrushFiltered (b))\r
-                       continue;               // still filtered\r
-               Brush_RemoveFromList (b);\r
-               Brush_AddToList (b, &filtered_brushes);\r
-       }\r
-\r
-       Sys_UpdateWindows (W_ALL);\r
-}\r
-\r
-\r
-/*\r
-========================\r
-Map_RegionSelectedBrushes\r
-========================\r
-*/\r
-void Map_RegionSelectedBrushes (void)\r
-{\r
-       Map_RegionOff ();\r
-\r
-       if (selected_brushes.next == &selected_brushes)  // nothing selected\r
-  {\r
-    Sys_Printf("Tried to region with no selection...\n");\r
-    return;\r
-  }\r
-       region_active = true;\r
-       Select_GetBounds (region_mins, region_maxs);\r
-\r
-#ifdef _DEBUG\r
-       if (filtered_brushes.next != &filtered_brushes)\r
-               Sys_Printf("WARNING: filtered_brushes list may not be empty in Map_RegionSelectedBrushes\n");\r
-#endif\r
-\r
-       if (active_brushes.next == &active_brushes)\r
-       {\r
-               // just have an empty filtered_brushes list\r
-               // this happens if you set region after selecting all the brushes in your map (some weird people do that, ask MrE!)\r
-               filtered_brushes.next = filtered_brushes.prev = &filtered_brushes;\r
-       }\r
-       else\r
-       {\r
-               // move the entire active_brushes list to filtered_brushes\r
-               filtered_brushes.next = active_brushes.next;\r
-               filtered_brushes.prev = active_brushes.prev;\r
-               filtered_brushes.next->prev = &filtered_brushes;\r
-               filtered_brushes.prev->next = &filtered_brushes;\r
-       }\r
-\r
-       // move the entire selected_brushes list to active_brushes\r
-       active_brushes.next = selected_brushes.next;\r
-       active_brushes.prev = selected_brushes.prev;\r
-       active_brushes.next->prev = &active_brushes;\r
-       active_brushes.prev->next = &active_brushes;\r
-\r
-       // deselect patches\r
-       for (brush_t *b = active_brushes.next; b != &active_brushes; b = b->next)\r
-          if (b->patchBrush)\r
-            b->pPatch->bSelected = false;\r
-\r
-       // clear selected_brushes\r
-       selected_brushes.next = selected_brushes.prev = &selected_brushes;\r
-\r
-       Sys_UpdateWindows (W_ALL);\r
-}\r
-\r
-\r
-/*\r
-===========\r
-Map_RegionXY\r
-===========\r
-*/\r
-void Map_RegionXY (void)\r
-{\r
-       Map_RegionOff ();\r
-\r
-       region_mins[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();\r
-       region_maxs[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();\r
-       region_mins[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();\r
-       region_maxs[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();\r
-       region_mins[2] = g_MinWorldCoord+64;\r
-       region_maxs[2] = g_MaxWorldCoord-64;\r
-       Map_ApplyRegion ();\r
-}\r
-\r
-/*\r
-===========\r
-Map_RegionTallBrush\r
-===========\r
-*/\r
-void Map_RegionTallBrush (void)\r
-{\r
-  brush_t *b;\r
-\r
-  if (!QE_SingleBrush ())\r
-    return;\r
-\r
-  b = selected_brushes.next;\r
-\r
-  Map_RegionOff ();\r
-\r
-  VectorCopy (b->mins, region_mins);\r
-  VectorCopy (b->maxs, region_maxs);\r
-  region_mins[2] = g_MinWorldCoord+64;\r
-  region_maxs[2] = g_MaxWorldCoord-64;\r
-\r
-  Undo_Start("delete");\r
-  Undo_AddBrushList(&selected_brushes);\r
-  Undo_AddEntity(b->owner);\r
-  Select_Delete ();\r
-  Undo_EndBrushList(&selected_brushes);\r
-  Undo_End();\r
-\r
-  Map_ApplyRegion ();\r
-}\r
-\r
-/*\r
-===========\r
-Map_RegionBrush\r
-===========\r
-*/\r
-void Map_RegionBrush (void)\r
-{\r
-  brush_t *b;\r
-\r
-  if (!QE_SingleBrush ())\r
-    return;\r
-\r
-  b = selected_brushes.next;\r
-\r
-  Map_RegionOff ();\r
-\r
-  VectorCopy (b->mins, region_mins);\r
-  VectorCopy (b->maxs, region_maxs);\r
-\r
-  Undo_Start("delete");\r
-  Undo_AddBrushList(&selected_brushes);\r
-  Undo_AddEntity(b->owner);\r
-  Select_Delete ();\r
-  Undo_EndBrushList(&selected_brushes);\r
-  Undo_End();\r
-\r
-  Map_ApplyRegion ();\r
-}\r
-\r
-GList *find_string(GList *glist, const char *buf)\r
-{\r
-  while (glist)\r
-  {\r
-    if (strcmp((char *)glist->data, buf) == 0)\r
-      break; // this name is in our list already\r
-    glist = glist->next;\r
-  }\r
-  return glist;\r
-}\r
-\r
-void Map_ImportBuffer(char *buf)\r
-{\r
-  Select_Deselect();\r
-  \r
-  Undo_Start("import buffer");\r
-\r
-  MemStream stream;\r
-\r
-  stream.Write(buf, strlen(buf));\r
-  Map_Import(&stream, "xmap");\r
-  stream.Close();\r
-  \r
-  Sys_UpdateWindows (W_ALL);\r
-  Sys_MarkMapModified();\r
-  \r
-  Undo_End();\r
-}\r
-\r
-\r
-//\r
-//================\r
-//Map_ImportFile\r
-//================\r
-//\r
-void Map_ImportFile (const char *filename)\r
-{\r
-  FileStream file;\r
-       Sys_BeginWait ();\r
-\r
-  Sys_Printf("Importing map from %s\n",filename);\r
-\r
-  const char* type = strrchr(filename,'.');\r
-  if(type!=NULL) type++;\r
-  /*!\todo Resolve "r" problem in scriptlib" */\r
-  if(file.Open(filename, "rb"))\r
-    Map_Import(&file, type, true);\r
-  else\r
-    Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for read\n", filename);\r
-\r
-  file.Close();\r
-\r
-       Sys_UpdateWindows (W_ALL);\r
-       modified = true;\r
-       Sys_EndWait();\r
-}\r
-\r
-//\r
-//===========\r
-//Map_SaveSelected\r
-//===========\r
-//\r
-// Saves selected world brushes and whole entities with partial/full selections\r
-//\r
-void Map_SaveSelected(const char* filename)\r
-{\r
-  FileStream file;\r
-\r
-  Sys_Printf("Saving selection to %s\n",filename);\r
-\r
-  const char* type = strrchr(filename,'.');\r
-  if(type!=NULL) type++;\r
-  if(file.Open(filename, "w"))\r
-    Map_Export (&file, type, false, true);\r
-  else\r
-    Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for write\n", filename);\r
-\r
-  file.Close();\r
-\r
-}\r
-\r
-//\r
-//===========\r
-//Map_SaveSelected\r
-//===========\r
-//\r
-// Saves selected world brushes and whole entities with partial/full selections\r
-//\r
-void Map_SaveSelected (MemStream* pMemFile, MemStream* pPatchFile)\r
-{\r
-  Map_Export (pMemFile, "xmap", false, true);\r
-\r
- /*\r
-       // write world entity first\r
-       Entity_WriteSelected(world_entity, pMemFile);\r
-\r
-       // then write all other ents\r
-       count = 1;\r
-       for (e=entities.next ; e != &entities ; e=next)\r
-       {\r
-               MemFile_fprintf(pMemFile, "// entity %i\n", count);\r
-               count++;\r
-               Entity_WriteSelected(e, pMemFile);\r
-               next = e->next;\r
-       }\r
-\r
-  //if (pPatchFile)\r
-  //  Patch_WriteFile(pPatchFile);\r
-  */\r
-}\r
-\r
-\r
-void MemFile_fprintf(MemStream* pMemFile, const char* pText, ...)\r
-{\r
-  char Buffer[4096];\r
-  va_list args;\r
-       va_start (args,pText);\r
-  vsprintf(Buffer, pText, args);\r
-  pMemFile->Write(Buffer, strlen(Buffer));\r
-}\r
-\r
-/*!\r
-==============\r
-Region_SpawnPoint\r
-push the region spawn point\r
-\todo FIXME TTimo this was in the #1 MAP module implementation (in the core)\r
-not sure it has any use anymore, should prolly drop it\r
-==============\r
-*/\r
-void Region_SpawnPoint(FILE *f)\r
-{\r
-  // write the info_player_start, we use the camera position\r
-  fprintf (f, "{\n");\r
-  fprintf (f, "\"classname\" \"info_player_start\"\n");\r
-  fprintf (f, "\"origin\" \"%i %i %i\"\n", \r
-    (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],\r
-    (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1], \r
-    (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2]);\r
-  fprintf (f, "\"angle\" \"%i\"\n", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW]);\r
-  fprintf (f, "}\n");\r
-}\r
+/*
+   Copyright (C) 1999-2006 Id Software, Inc. and contributors.
+   For a list of contributors, see the accompanying CONTRIBUTORS file.
+
+   This file is part of GtkRadiant.
+
+   GtkRadiant is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   GtkRadiant is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with GtkRadiant; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "map.h"
+
+#include <gtk/gtk.h>
+
+#include "debugging/debugging.h"
+
+#include "imap.h"
+
+MapModules &ReferenceAPI_getMapModules();
+
+#include "iselection.h"
+#include "iundo.h"
+#include "ibrush.h"
+#include "ifilter.h"
+#include "ireference.h"
+#include "ifiletypes.h"
+#include "ieclass.h"
+#include "irender.h"
+#include "ientity.h"
+#include "editable.h"
+#include "iarchive.h"
+#include "ifilesystem.h"
+#include "namespace.h"
+#include "moduleobserver.h"
+
+#include <set>
+
+#include <gdk/gdkkeysyms.h>
+#include "uilib/uilib.h"
+
+#include "scenelib.h"
+#include "transformlib.h"
+#include "selectionlib.h"
+#include "instancelib.h"
+#include "traverselib.h"
+#include "maplib.h"
+#include "eclasslib.h"
+#include "cmdlib.h"
+#include "stream/textfilestream.h"
+#include "os/path.h"
+#include "uniquenames.h"
+#include "modulesystem/singletonmodule.h"
+#include "modulesystem/moduleregistry.h"
+#include "stream/stringstream.h"
+#include "signal/signal.h"
+
+#include "gtkutil/filechooser.h"
+#include "timer.h"
+#include "select.h"
+#include "plugin.h"
+#include "filetypes.h"
+#include "gtkdlgs.h"
+#include "entityinspector.h"
+#include "points.h"
+#include "qe3.h"
+#include "camwindow.h"
+#include "xywindow.h"
+#include "mainframe.h"
+#include "preferences.h"
+#include "preferencesystem.h"
+#include "referencecache.h"
+#include "mru.h"
+#include "commands.h"
+#include "autosave.h"
+#include "brushmodule.h"
+#include "brush.h"
+
+class NameObserver {
+    UniqueNames &m_names;
+    CopiedString m_name;
+
+    void construct()
+    {
+        if (!empty()) {
+            //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
+            m_names.insert(name_read(c_str()));
+        }
+    }
+
+    void destroy()
+    {
+        if (!empty()) {
+            //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
+            m_names.erase(name_read(c_str()));
+        }
+    }
+
+    NameObserver &operator=(const NameObserver &other);
+
+public:
+    NameObserver(UniqueNames &names) : m_names(names)
+    {
+        construct();
+    }
+
+    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 MemberCaller<NameObserver, void(const char *), &NameObserver::nameChanged> NameChangedCaller;
+};
+
+class BasicNamespace : public Namespace {
+    typedef std::map<NameCallback, NameObserver> Names;
+    Names m_names;
+    UniqueNames m_uniqueNames;
+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");
+        //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
+        detachObserver(NameObserver::NameChangedCaller((*i).second));
+        m_names.erase(i);
+    }
+
+    void makeUnique(const char *name, const NameCallback &setName) const
+    {
+        char buffer[1024];
+        name_write(buffer, m_uniqueNames.make_unique(name_read(name)));
+        setName(buffer);
+    }
+
+    void mergeNames(const BasicNamespace &other) const
+    {
+        typedef std::list<NameCallback> SetNameCallbacks;
+        typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
+        NameGroups groups;
+
+        UniqueNames uniqueNames(other.m_uniqueNames);
+
+        for (Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i) {
+            groups[(*i).second.c_str()].push_back((*i).first);
+        }
+
+        for (NameGroups::iterator i = groups.begin(); i != groups.end(); ++i) {
+            name_t uniqueName(uniqueNames.make_unique(name_read((*i).first.c_str())));
+            uniqueNames.insert(uniqueName);
+
+            char buffer[1024];
+            name_write(buffer, uniqueName);
+
+            //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
+
+            SetNameCallbacks &setNameCallbacks = (*i).second;
+
+            for (SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j) {
+                (*j)(buffer);
+            }
+        }
+    }
+};
+
+BasicNamespace g_defaultNamespace;
+BasicNamespace g_cloneNamespace;
+
+class NamespaceAPI {
+    Namespace *m_namespace;
+public:
+    typedef Namespace Type;
+
+    STRING_CONSTANT(Name, "*");
+
+    NamespaceAPI()
+    {
+        m_namespace = &g_defaultNamespace;
+    }
+
+    Namespace *getTable()
+    {
+        return m_namespace;
+    }
+};
+
+typedef SingletonModule<NamespaceAPI> NamespaceModule;
+typedef Static<NamespaceModule> StaticNamespaceModule;
+StaticRegisterModule staticRegisterDefaultNamespace(StaticNamespaceModule::instance());
+
+
+std::list<Namespaced *> g_cloned;
+
+inline Namespaced *Node_getNamespaced(scene::Node &node)
+{
+    return NodeTypeCast<Namespaced>::cast(node);
+}
+
+void Node_gatherNamespaced(scene::Node &node)
+{
+    Namespaced *namespaced = Node_getNamespaced(node);
+    if (namespaced != 0) {
+        g_cloned.push_back(namespaced);
+    }
+}
+
+class GatherNamespaced : public scene::Traversable::Walker {
+public:
+    bool pre(scene::Node &node) const
+    {
+        Node_gatherNamespaced(node);
+        return true;
+    }
+};
+
+void Map_gatherNamespaced(scene::Node &root)
+{
+    Node_traverseSubgraph(root, GatherNamespaced());
+}
+
+void Map_mergeClonedNames()
+{
+    for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
+        (*i)->setNamespace(g_cloneNamespace);
+    }
+    g_cloneNamespace.mergeNames(g_defaultNamespace);
+    for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
+        (*i)->setNamespace(g_defaultNamespace);
+    }
+
+    g_cloned.clear();
+}
+
+class WorldNode {
+    scene::Node *m_node;
+public:
+    WorldNode()
+            : m_node(0)
+    {
+    }
+
+    void set(scene::Node *node)
+    {
+        if (m_node != 0) {
+            m_node->DecRef();
+        }
+        m_node = node;
+        if (m_node != 0) {
+            m_node->IncRef();
+        }
+    }
+
+    scene::Node *get() const
+    {
+        return m_node;
+    }
+};
+
+class Map;
+
+void Map_SetValid(Map &map, bool valid);
+
+void Map_UpdateTitle(const Map &map);
+
+void Map_SetWorldspawn(Map &map, scene::Node *node);
+
+
+class Map : public ModuleObserver {
+public:
+    CopiedString m_name;
+    Resource *m_resource;
+    bool m_valid;
+
+    bool m_modified;
+
+    void ( *m_modified_changed )(const Map &);
+
+    Signal0 m_mapValidCallbacks;
+
+    WorldNode m_world_node;   // "classname" "worldspawn" !
+
+    Map() : m_resource(0), m_valid(false), m_modified_changed(Map_UpdateTitle)
+    {
+    }
+
+    void realise()
+    {
+        if (m_resource != 0) {
+            if (Map_Unnamed(*this)) {
+                g_map.m_resource->setNode(NewMapRoot("").get_pointer());
+                MapFile *map = Node_getMapFile(*g_map.m_resource->getNode());
+                if (map != 0) {
+                    map->save();
+                }
+            } else {
+                m_resource->load();
+            }
+
+            GlobalSceneGraph().insert_root(*m_resource->getNode());
+
+            AutoSave_clear();
+
+            Map_SetValid(g_map, true);
+        }
+    }
+
+    void unrealise()
+    {
+        if (m_resource != 0) {
+            Map_SetValid(g_map, false);
+            Map_SetWorldspawn(g_map, 0);
+
+
+            GlobalUndoSystem().clear();
+
+            GlobalSceneGraph().erase_root();
+        }
+    }
+};
+
+Map g_map;
+Map *g_currentMap = 0;
+
+void Map_addValidCallback(Map &map, const SignalHandler &handler)
+{
+    map.m_mapValidCallbacks.connectLast(handler);
+}
+
+bool Map_Valid(const Map &map)
+{
+    return map.m_valid;
+}
+
+void Map_SetValid(Map &map, bool valid)
+{
+    map.m_valid = valid;
+    map.m_mapValidCallbacks();
+}
+
+
+const char *Map_Name(const Map &map)
+{
+    return map.m_name.c_str();
+}
+
+bool Map_Unnamed(const Map &map)
+{
+    return string_equal(Map_Name(map), "unnamed.map");
+}
+
+inline const MapFormat &MapFormat_forFile(const char *filename)
+{
+    const char *moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), path_get_extension(filename));
+    MapFormat *format = Radiant_getMapModules().findModule(moduleName);
+    ASSERT_MESSAGE(format != 0, "map format not found for file " << makeQuoted(filename));
+    return *format;
+}
+
+const MapFormat &Map_getFormat(const Map &map)
+{
+    return MapFormat_forFile(Map_Name(map));
+}
+
+
+bool Map_Modified(const Map &map)
+{
+    return map.m_modified;
+}
+
+void Map_SetModified(Map &map, bool modified)
+{
+    if (map.m_modified ^ modified) {
+        map.m_modified = modified;
+
+        map.m_modified_changed(map);
+    }
+}
+
+void Map_UpdateTitle(const Map &map)
+{
+    Sys_SetTitle(map.m_name.c_str(), Map_Modified(map));
+}
+
+
+scene::Node *Map_GetWorldspawn(const Map &map)
+{
+    return map.m_world_node.get();
+}
+
+void Map_SetWorldspawn(Map &map, scene::Node *node)
+{
+    map.m_world_node.set(node);
+}
+
+
+// TTimo
+// need that in a variable, will have to tweak depending on the game
+float g_MaxWorldCoord = 64 * 1024;
+float g_MinWorldCoord = -64 * 1024;
+
+void AddRegionBrushes(void);
+
+void RemoveRegionBrushes(void);
+
+
+/*
+   ================
+   Map_Free
+   free all map elements, reinitialize the structures that depend on them
+   ================
+ */
+void Map_Free()
+{
+    Pointfile_Clear();
+
+    g_map.m_resource->detach(g_map);
+    GlobalReferenceCache().release(g_map.m_name.c_str());
+    g_map.m_resource = 0;
+
+    FlushReferences();
+
+    g_currentMap = 0;
+    Brush_unlatchPreferences();
+}
+
+class EntityFindByClassname : public scene::Graph::Walker {
+    const char *m_name;
+    Entity *&m_entity;
+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());
+            if (entity != 0
+                && string_equal(m_name, entity->getKeyValue("classname"))) {
+                m_entity = entity;
+            }
+        }
+        return true;
+    }
+};
+
+Entity *Scene_FindEntityByClass(const char *name)
+{
+    Entity *entity;
+    GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
+    return entity;
+}
+
+Entity *Scene_FindPlayerStart()
+{
+    typedef const char *StaticString;
+    StaticString strings[] = {
+            "info_player_start",
+            "info_player_deathmatch",
+            "team_CTF_redplayer",
+            "team_CTF_blueplayer",
+            "team_CTF_redspawn",
+            "team_CTF_bluespawn",
+    };
+    typedef const StaticString *StaticStringIterator;
+    for (StaticStringIterator i = strings, end = strings + (sizeof(strings) / sizeof(StaticString)); i != end; ++i) {
+        Entity *entity = Scene_FindEntityByClass(*i);
+        if (entity != 0) {
+            return entity;
+        }
+    }
+    return 0;
+}
+
+//
+// move the view to a start position
+//
+
+
+void FocusViews(const Vector3 &point, float angle)
+{
+    CamWnd &camwnd = *g_pParentWnd->GetCamWnd();
+    Camera_setOrigin(camwnd, point);
+    Vector3 angles(Camera_getAngles(camwnd));
+    angles[CAMERA_PITCH] = 0;
+    angles[CAMERA_YAW] = angle;
+    Camera_setAngles(camwnd, angles);
+
+    XYWnd *xywnd = g_pParentWnd->GetXYWnd();
+    xywnd->SetOrigin(point);
+}
+
+#include "stringio.h"
+
+void Map_StartPosition()
+{
+    Entity *entity = Scene_FindPlayerStart();
+
+    if (entity) {
+        Vector3 origin;
+        string_parse_vector3(entity->getKeyValue("origin"), origin);
+        FocusViews(origin, string_read_float(entity->getKeyValue("angle")));
+    } else {
+        FocusViews(g_vector3_identity, 0);
+    }
+}
+
+
+inline bool node_is_worldspawn(scene::Node &node)
+{
+    Entity *entity = Node_getEntity(node);
+    return entity != 0 && string_equal(entity->getKeyValue("classname"), "worldspawn");
+}
+
+
+// use first worldspawn
+class entity_updateworldspawn : public scene::Traversable::Walker {
+public:
+    bool pre(scene::Node &node) const
+    {
+        if (node_is_worldspawn(node)) {
+            if (Map_GetWorldspawn(g_map) == 0) {
+                Map_SetWorldspawn(g_map, &node);
+            }
+        }
+        return false;
+    }
+};
+
+scene::Node *Map_FindWorldspawn(Map &map)
+{
+    Map_SetWorldspawn(map, 0);
+
+    Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
+
+    return Map_GetWorldspawn(map);
+}
+
+
+class CollectAllWalker : public scene::Traversable::Walker {
+    scene::Node &m_root;
+    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);
+        return false;
+    }
+};
+
+void Node_insertChildFirst(scene::Node &parent, scene::Node &child)
+{
+    UnsortedNodeSet nodes;
+    Node_getTraversable(parent)->traverse(CollectAllWalker(parent, nodes));
+    Node_getTraversable(parent)->insert(child);
+
+    for (UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i) {
+        Node_getTraversable(parent)->insert((*i));
+    }
+}
+
+scene::Node &createWorldspawn()
+{
+    NodeSmartReference worldspawn(
+            GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("worldspawn", true)));
+    Node_insertChildFirst(GlobalSceneGraph().root(), worldspawn);
+    return worldspawn;
+}
+
+void Map_UpdateWorldspawn(Map &map)
+{
+    if (Map_FindWorldspawn(map) == 0) {
+        Map_SetWorldspawn(map, &createWorldspawn());
+    }
+}
+
+scene::Node &Map_FindOrInsertWorldspawn(Map &map)
+{
+    Map_UpdateWorldspawn(map);
+    return *Map_GetWorldspawn(map);
+}
+
+
+class MapMergeAll : public scene::Traversable::Walker {
+    mutable scene::Path m_path;
+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();
+    }
+};
+
+class MapMergeEntities : public scene::Traversable::Walker {
+    mutable scene::Path m_path;
+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);
+            if (world_node == 0) {
+                Map_SetWorldspawn(g_map, &node);
+                Node_getTraversable(m_path.top().get())->insert(node);
+                m_path.push(makeReference(node));
+                Node_getTraversable(node)->traverse(SelectChildren(m_path));
+            } else {
+                m_path.push(makeReference(*world_node));
+                Node_getTraversable(node)->traverse(MapMergeAll(m_path));
+            }
+        } else {
+            Node_getTraversable(m_path.top())->insert(node);
+            m_path.push(makeReference(node));
+            if (node_is_group(node)) {
+                Node_getTraversable(node)->traverse(SelectChildren(m_path));
+            } else {
+                selectPath(m_path, true);
+            }
+        }
+        return false;
+    }
+
+    void post(scene::Node &node) const
+    {
+        m_path.pop();
+    }
+};
+
+class BasicContainer : public scene::Node::Symbiot {
+    class TypeCasts {
+        NodeTypeCastTable m_casts;
+    public:
+        TypeCasts()
+        {
+            NodeContainedCast<BasicContainer, scene::Traversable>::install(m_casts);
+        }
+
+        NodeTypeCastTable &get()
+        {
+            return m_casts;
+        }
+    };
+
+    scene::Node m_node;
+    TraversableNodeSet m_traverse;
+public:
+
+    typedef LazyStatic<TypeCasts> StaticTypeCasts;
+
+    scene::Traversable &get(NullType<scene::Traversable>)
+    {
+        return m_traverse;
+    }
+
+    BasicContainer() : m_node(this, this, StaticTypeCasts::instance().get())
+    {
+    }
+
+    void release()
+    {
+        delete this;
+    }
+
+    scene::Node &node()
+    {
+        return m_node;
+    }
+};
+
+/// Merges the map graph rooted at \p node into the global scene-graph.
+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());
+    Map_gatherNamespaced(node);
+    Map_mergeClonedNames();
+    MergeMap(node);
+}
+
+inline scene::Cloneable *Node_getCloneable(scene::Node &node)
+{
+    return NodeTypeCast<scene::Cloneable>::cast(node);
+}
+
+inline scene::Node &node_clone(scene::Node &node)
+{
+    scene::Cloneable *cloneable = Node_getCloneable(node);
+    if (cloneable != 0) {
+        return cloneable->clone();
+    }
+
+    return (new scene::NullNode)->node();
+}
+
+class CloneAll : public scene::Traversable::Walker {
+    mutable scene::Path m_path;
+public:
+    CloneAll(scene::Node &root)
+            : m_path(makeReference(root))
+    {
+    }
+
+    bool pre(scene::Node &node) const
+    {
+        if (node.isRoot()) {
+            return false;
+        }
+
+        m_path.push(makeReference(node_clone(node)));
+        m_path.top().get().IncRef();
+
+        return true;
+    }
+
+    void post(scene::Node &node) const
+    {
+        if (node.isRoot()) {
+            return;
+        }
+
+        Node_getTraversable(m_path.parent())->insert(m_path.top());
+
+        m_path.top().get().DecRef();
+        m_path.pop();
+    }
+};
+
+scene::Node &Node_Clone(scene::Node &node)
+{
+    scene::Node &clone = node_clone(node);
+    scene::Traversable *traversable = Node_getTraversable(node);
+    if (traversable != 0) {
+        traversable->traverse(CloneAll(clone));
+    }
+    return clone;
+}
+
+
+typedef std::map<CopiedString, std::size_t> EntityBreakdown;
+
+class EntityBreakdownWalker : public scene::Graph::Walker {
+    EntityBreakdown &m_entitymap;
+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()]; }
+        }
+        return true;
+    }
+};
+
+void Scene_EntityBreakdown(EntityBreakdown &entitymap)
+{
+    GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap));
+}
+
+
+WindowPosition g_posMapInfoWnd(c_default_window_pos);
+
+void DoMapInfo()
+{
+    ModalDialog dialog;
+    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);
+
+    window_set_position(window, g_posMapInfoWnd);
+
+    {
+        auto vbox = create_dialog_vbox(4, 4);
+        window.add(vbox);
+
+        {
+            auto hbox = create_dialog_hbox(4);
+            vbox.pack_start(hbox, FALSE, TRUE, 0);
+
+            {
+                auto table = create_dialog_table(2, 2, 4, 4);
+                hbox.pack_start(table, TRUE, TRUE, 0);
+
+                {
+                    auto entry = ui::Entry(ui::New);
+                    entry.show();
+                    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(ui::New);
+                    entry.show();
+                    table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
+                    gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
+
+                    entities_entry = entry;
+                }
+                {
+                    ui::Widget label = ui::Label("Total Brushes");
+                    label.show();
+                    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();
+                    table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
+                    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+                }
+            }
+            {
+                auto vbox2 = create_dialog_vbox(4);
+                hbox.pack_start(vbox2, 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();
+            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);
+            vbox.pack_start(scr, TRUE, TRUE, 0);
+
+            {
+                auto store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING));
+
+                auto view = ui::TreeView(ui::TreeModel::from(store._handle));
+                gtk_tree_view_set_headers_clickable(view, TRUE);
+
+                {
+                    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(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);
+                }
+
+                view.show();
+
+                scr.add(view);
+
+                EntityBreakdownWalker = store;
+            }
+        }
+    }
+
+    // Initialize fields
+
+    {
+        EntityBreakdown entitymap;
+        Scene_EntityBreakdown(entitymap);
+
+        for (EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i) {
+            char tmp[16];
+            sprintf(tmp, "%u", Unsigned((*i).second));
+            EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
+        }
+    }
+
+    EntityBreakdownWalker.unref();
+
+    char tmp[16];
+    sprintf(tmp, "%u", Unsigned(g_brushCount.get()));
+    brushes_entry.text(tmp);
+    sprintf(tmp, "%u", Unsigned(g_entityCount.get()));
+    entities_entry.text(tmp);
+
+    modal_dialog_show(window, dialog);
+
+    // save before exit
+    window_get_position(window, g_posMapInfoWnd);
+
+    window.destroy();
+}
+
+
+class ScopeTimer {
+    Timer m_timer;
+    const char *m_message;
+public:
+    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_strLastMapFolder = "";
+
+/*
+   ================
+   Map_LoadFile
+   ================
+ */
+
+void Map_LoadFile(const char *filename)
+{
+    globalOutputStream() << "Loading map from " << filename << "\n";
+    ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
+
+    MRU_AddFile(filename);
+    g_strLastMapFolder = g_path_get_dirname(filename);
+
+    {
+        ScopeTimer timer("map load");
+
+        const MapFormat *format = NULL;
+        const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
+        if (string_not_empty(moduleName)) {
+            format = ReferenceAPI_getMapModules().findModule(moduleName);
+        }
+
+        for (int i = 0; i < Brush_toggleFormatCount(); ++i) {
+            if (i) {
+                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;
+            }
+            g_map.m_resource->attach(g_map);
+            if (format) {
+                if (!format->wrongFormat) {
+                    break;
+                }
+            }
+        }
+
+        Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
+    }
+
+    globalOutputStream() << "--- LoadMapFile ---\n";
+    globalOutputStream() << g_map.m_name.c_str() << "\n";
+
+    globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
+    globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
+
+    //GlobalEntityCreator().printStatistics();
+
+    //
+    // move the view to a start position
+    //
+    Map_StartPosition();
+
+    g_currentMap = &g_map;
+
+    // refresh VFS to apply new pak filtering based on mapname
+    // needed for daemon DPK VFS
+    VFS_Refresh();
+}
+
+class Excluder {
+public:
+    virtual bool excluded(scene::Node &node) const = 0;
+};
+
+class ExcludeWalker : public scene::Traversable::Walker {
+    const scene::Traversable::Walker &m_walker;
+    const Excluder *m_exclude;
+    mutable bool m_skip;
+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;
+            return false;
+        } else {
+            m_walker.pre(node);
+        }
+        return true;
+    }
+
+    void post(scene::Node &node) const
+    {
+        if (m_skip) {
+            m_skip = false;
+        } else {
+            m_walker.post(node);
+        }
+    }
+};
+
+class AnyInstanceSelected : public scene::Instantiable::Visitor {
+    bool &m_selected;
+public:
+    AnyInstanceSelected(bool &selected) : m_selected(selected)
+    {
+        m_selected = false;
+    }
+
+    void visit(scene::Instance &instance) const
+    {
+        Selectable *selectable = Instance_getSelectable(instance);
+        if (selectable != 0
+            && selectable->isSelected()) {
+            m_selected = true;
+        }
+    }
+};
+
+bool Node_instanceSelected(scene::Node &node)
+{
+    scene::Instantiable *instantiable = Node_getInstantiable(node);
+    ASSERT_NOTNULL(instantiable);
+    bool selected;
+    instantiable->forEachInstance(AnyInstanceSelected(selected));
+    return selected;
+}
+
+class SelectedDescendantWalker : public scene::Traversable::Walker {
+    bool &m_selected;
+public:
+    SelectedDescendantWalker(bool &selected) : m_selected(selected)
+    {
+        m_selected = false;
+    }
+
+    bool pre(scene::Node &node) const
+    {
+        if (node.isRoot()) {
+            return false;
+        }
+
+        if (Node_instanceSelected(node)) {
+            m_selected = true;
+        }
+
+        return true;
+    }
+};
+
+bool Node_selectedDescendant(scene::Node &node)
+{
+    bool selected;
+    Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
+    return selected;
+}
+
+class SelectionExcluder : public Excluder {
+public:
+    bool excluded(scene::Node &node) const
+    {
+        return !Node_selectedDescendant(node);
+    }
+};
+
+class IncludeSelectedWalker : public scene::Traversable::Walker {
+    const scene::Traversable::Walker &m_walker;
+    mutable std::size_t m_selected;
+    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 )
+        if (!node.isRoot() && (Node_selectedDescendant(node) || selectedParent())) {
+            if (Node_instanceSelected(node)) {
+                ++m_selected;
+            }
+            m_walker.pre(node);
+            return true;
+        } else {
+            m_skip = true;
+            return false;
+        }
+    }
+
+    void post(scene::Node &node) const
+    {
+        if (m_skip) {
+            m_skip = false;
+        } else {
+            if (Node_instanceSelected(node)) {
+                --m_selected;
+            }
+            m_walker.post(node);
+        }
+    }
+};
+
+void Map_Traverse_Selected(scene::Node &root, const scene::Traversable::Walker &walker)
+{
+    scene::Traversable *traversable = Node_getTraversable(root);
+    if (traversable != 0) {
+#if 0
+        traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
+#else
+        traversable->traverse(IncludeSelectedWalker(walker));
+#endif
+    }
+}
+
+void Map_ExportSelected(TextOutputStream &out, const MapFormat &format)
+{
+    format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out);
+}
+
+void Map_Traverse(scene::Node &root, const scene::Traversable::Walker &walker)
+{
+    scene::Traversable *traversable = Node_getTraversable(root);
+    if (traversable != 0) {
+        traversable->traverse(walker);
+    }
+}
+
+class RegionExcluder : public Excluder {
+public:
+    bool excluded(scene::Node &node) const
+    {
+        return node.excluded();
+    }
+};
+
+void Map_Traverse_Region(scene::Node &root, const scene::Traversable::Walker &walker)
+{
+    scene::Traversable *traversable = Node_getTraversable(root);
+    if (traversable != 0) {
+        traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
+    }
+}
+
+bool Map_SaveRegion(const char *filename)
+{
+    AddRegionBrushes();
+
+    bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region,
+                                        filename);
+
+    RemoveRegionBrushes();
+
+    return success;
+}
+
+
+void Map_RenameAbsolute(const char *absolute)
+{
+    Resource *resource = GlobalReferenceCache().capture(absolute);
+    NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
+    resource->setNode(clone.get_pointer());
+
+    {
+        //ScopeTimer timer("clone subgraph");
+        Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
+    }
+
+    g_map.m_resource->detach(g_map);
+    GlobalReferenceCache().release(g_map.m_name.c_str());
+
+    g_map.m_resource = resource;
+
+    g_map.m_name = absolute;
+    Map_UpdateTitle(g_map);
+
+    g_map.m_resource->attach(g_map);
+    // refresh VFS to apply new pak filtering based on mapname
+    // needed for daemon DPK VFS
+    VFS_Refresh();
+}
+
+void Map_Rename(const char *filename)
+{
+    if (!string_equal(g_map.m_name.c_str(), filename)) {
+        ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
+
+        Map_RenameAbsolute(filename);
+
+        SceneChangeNotify();
+    } else {
+        SaveReferences();
+    }
+}
+
+bool Map_Save()
+{
+    Pointfile_Clear();
+
+    ScopeTimer timer("map save");
+    SaveReferences();
+    return true; // assume success..
+}
+
+/*
+   ===========
+   Map_New
+
+   ===========
+ */
+void Map_New()
+{
+    //globalOutputStream() << "Map_New\n";
+
+    g_map.m_name = "unnamed.map";
+    Map_UpdateTitle(g_map);
+
+    {
+        g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
+//    ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
+        g_map.m_resource->attach(g_map);
+
+        SceneChangeNotify();
+    }
+
+    FocusViews(g_vector3_identity, 0);
+
+    g_currentMap = &g_map;
+
+    // restart VFS to apply new pak filtering based on mapname
+    // needed for daemon DPK VFS
+    VFS_Restart();
+}
+
+extern void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 &region_mins, const Vector3 &region_maxs);
+
+void ConstructRegionStartpoint(scene::Node *startpoint, const Vector3 &region_mins, const Vector3 &region_maxs)
+{
+    /*!
+          \todo we need to make sure that the player start IS inside the region and bail out if it's not
+          the compiler will refuse to compile a map with a player_start somewhere in empty space..
+          for now, let's just print an error
+        */
+
+    Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
+
+    for (int i = 0; i < 3; i++) {
+        if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) {
+            globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
+            break;
+        }
+    }
+
+    // write the info_playerstart
+    char sTmp[1024];
+    sprintf(sTmp, "%d %d %d", (int) vOrig[0], (int) vOrig[1], (int) vOrig[2]);
+    Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
+    sprintf(sTmp, "%d", (int) Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
+    Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
+}
+
+/*
+   ===========================================================
+
+   REGION
+
+   ===========================================================
+ */
+bool region_active;
+Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
+Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
+
+scene::Node *region_sides[6];
+scene::Node *region_startpoint = 0;
+
+/*
+   ===========
+   AddRegionBrushes
+   a regioned map will have temp walls put up at the region boundary
+   \todo TODO TTimo old implementation of region brushes
+   we still add them straight in the worldspawn and take them out after the map is saved
+   with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
+   ===========
+ */
+void AddRegionBrushes(void)
+{
+    int i;
+
+    for (i = 0; i < 6; i++) {
+        region_sides[i] = &GlobalBrushCreator().createBrush();
+        Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
+    }
+
+    region_startpoint = &GlobalEntityCreator().createEntity(
+            GlobalEntityClassManager().findOrInsert("info_player_start", false));
+
+    ConstructRegionBrushes(region_sides, region_mins, region_maxs);
+    ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
+
+    Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
+}
+
+void RemoveRegionBrushes(void)
+{
+    for (std::size_t i = 0; i < 6; i++) {
+        Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
+    }
+    Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
+}
+
+inline void exclude_node(scene::Node &node, bool exclude)
+{
+    exclude
+    ? node.enable(scene::Node::eExcluded)
+    : node.disable(scene::Node::eExcluded);
+}
+
+class ExcludeAllWalker : public scene::Graph::Walker {
+    bool m_exclude;
+public:
+    ExcludeAllWalker(bool exclude)
+            : m_exclude(exclude)
+    {
+    }
+
+    bool pre(const scene::Path &path, scene::Instance &instance) const
+    {
+        exclude_node(path.top(), m_exclude);
+
+        return true;
+    }
+};
+
+void Scene_Exclude_All(bool exclude)
+{
+    GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
+}
+
+bool Instance_isSelected(const scene::Instance &instance)
+{
+    const Selectable *selectable = Instance_getSelectable(instance);
+    return selectable != 0 && selectable->isSelected();
+}
+
+class ExcludeSelectedWalker : public scene::Graph::Walker {
+    bool m_exclude;
+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;
+    }
+};
+
+void Scene_Exclude_Selected(bool exclude)
+{
+    GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
+}
+
+class ExcludeRegionedWalker : public scene::Graph::Walker {
+    bool m_exclude;
+public:
+    ExcludeRegionedWalker(bool exclude)
+            : m_exclude(exclude)
+    {
+    }
+
+    bool pre(const scene::Path &path, scene::Instance &instance) const
+    {
+        exclude_node(
+                path.top(),
+                !(
+                        (
+                                aabb_intersects_aabb(
+                                        instance.worldAABB(),
+                                        aabb_for_minmax(region_mins, region_maxs)
+                                ) != 0
+                        ) ^ m_exclude
+                )
+        );
+
+        return true;
+    }
+};
+
+void Scene_Exclude_Region(bool exclude)
+{
+    GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
+}
+
+/*
+   ===========
+   Map_RegionOff
+
+   Other filtering options may still be on
+   ===========
+ */
+void Map_RegionOff()
+{
+    region_active = false;
+
+    region_maxs[0] = g_MaxWorldCoord - 64;
+    region_mins[0] = g_MinWorldCoord + 64;
+    region_maxs[1] = g_MaxWorldCoord - 64;
+    region_mins[1] = g_MinWorldCoord + 64;
+    region_maxs[2] = g_MaxWorldCoord - 64;
+    region_mins[2] = g_MinWorldCoord + 64;
+
+    Scene_Exclude_All(false);
+}
+
+void Map_ApplyRegion(void)
+{
+    region_active = true;
+
+    Scene_Exclude_Region(false);
+}
+
+
+/*
+   ========================
+   Map_RegionSelectedBrushes
+   ========================
+ */
+void Map_RegionSelectedBrushes(void)
+{
+    Map_RegionOff();
+
+    if (GlobalSelectionSystem().countSelected() != 0
+        && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) {
+        region_active = true;
+        Select_GetBounds(region_mins, region_maxs);
+
+        Scene_Exclude_Selected(false);
+
+        GlobalSelectionSystem().setSelectedAll(false);
+    }
+}
+
+
+/*
+   ===========
+   Map_RegionXY
+   ===========
+ */
+void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
+{
+    Map_RegionOff();
+
+    region_mins[0] = x_min;
+    region_maxs[0] = x_max;
+    region_mins[1] = y_min;
+    region_maxs[1] = y_max;
+    region_mins[2] = g_MinWorldCoord + 64;
+    region_maxs[2] = g_MaxWorldCoord - 64;
+
+    Map_ApplyRegion();
+}
+
+void Map_RegionBounds(const AABB &bounds)
+{
+    Map_RegionOff();
+
+    region_mins = vector3_subtracted(bounds.origin, bounds.extents);
+    region_maxs = vector3_added(bounds.origin, bounds.extents);
+
+    deleteSelection();
+
+    Map_ApplyRegion();
+}
+
+/*
+   ===========
+   Map_RegionBrush
+   ===========
+ */
+void Map_RegionBrush(void)
+{
+    if (GlobalSelectionSystem().countSelected() != 0) {
+        scene::Instance &instance = GlobalSelectionSystem().ultimateSelected();
+        Map_RegionBounds(instance.worldAABB());
+    }
+}
+
+//
+//================
+//Map_ImportFile
+//================
+//
+bool Map_ImportFile(const char *filename)
+{
+    ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
+
+    g_strLastMapFolder = g_path_get_dirname(filename);
+
+    bool success = false;
+
+    if (extension_equal(path_get_extension(filename), "bsp")) {
+        goto tryDecompile;
+    }
+
+    {
+        const MapFormat *format = NULL;
+        const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
+        if (string_not_empty(moduleName)) {
+            format = ReferenceAPI_getMapModules().findModule(moduleName);
+        }
+
+        if (format) {
+            format->wrongFormat = false;
+        }
+        Resource *resource = GlobalReferenceCache().capture(filename);
+        resource->refresh(); // avoid loading old version if map has changed on disk since last import
+        if (!resource->load()) {
+            GlobalReferenceCache().release(filename);
+            goto tryDecompile;
+        }
+        if (format) {
+            if (format->wrongFormat) {
+                GlobalReferenceCache().release(filename);
+                goto tryDecompile;
+            }
+        }
+        NodeSmartReference clone(NewMapRoot(""));
+        Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
+        Map_gatherNamespaced(clone);
+        Map_mergeClonedNames();
+        MergeMap(clone);
+        success = true;
+        GlobalReferenceCache().release(filename);
+    }
+
+    SceneChangeNotify();
+
+    return success;
+
+    tryDecompile:
+
+    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("\"");
+
+        // extra pakpaths
+        for (int i = 0; i < g_pakPathCount; i++) {
+            if (g_strcmp0(g_strPakPath[i].c_str(), "")) {
+                output.push_string(" -fs_pakpath \"");
+                output.push_string(g_strPakPath[i].c_str());
+                output.push_string("\"");
+            }
+        }
+
+        // extra switches
+        if (g_disableEnginePath) {
+            output.push_string(" -fs_nobasepath ");
+        }
+
+        if (g_disableHomePath) {
+            output.push_string(" -fs_nohomepath ");
+        }
+
+        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");
+        if (extension_equal(path_get_extension(filename), "map")) {
+            output.push_string(" -readmap ");
+        }
+        output.push_string(" \"");
+        output.push_string(filename);
+        output.push_string("\"");
+
+        // 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");
+        filename = output.c_str();
+
+        // open
+        Resource *resource = GlobalReferenceCache().capture(filename);
+        resource->refresh(); // avoid loading old version if map has changed on disk since last import
+        if (!resource->load()) {
+            GlobalReferenceCache().release(filename);
+            goto tryDecompile;
+        }
+        NodeSmartReference clone(NewMapRoot(""));
+        Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
+        Map_gatherNamespaced(clone);
+        Map_mergeClonedNames();
+        MergeMap(clone);
+        success = true;
+        GlobalReferenceCache().release(filename);
+    }
+
+    SceneChangeNotify();
+    return success;
+}
+
+/*
+   ===========
+   Map_SaveFile
+   ===========
+ */
+bool Map_SaveFile(const char *filename)
+{
+    ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
+    bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename);
+    if (success) {
+        // refresh VFS to apply new pak filtering based on mapname
+        // needed for daemon DPK VFS
+        VFS_Refresh();
+    }
+    return success;
+}
+
+//
+//===========
+//Map_SaveSelected
+//===========
+//
+// Saves selected world brushes and whole entities with partial/full selections
+//
+bool Map_SaveSelected(const char *filename)
+{
+    return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected,
+                                filename);
+}
+
+
+class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker {
+    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())) {
+            Selectable *selectable = Instance_getSelectable(instance);
+            if (selectable != 0
+                && selectable->isSelected()
+                && path.size() > 1) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    void post(const scene::Path &path, scene::Instance &instance) const
+    {
+        if (path.top().get_pointer() != &m_parent
+            && Node_isPrimitive(path.top())) {
+            Selectable *selectable = Instance_getSelectable(instance);
+            if (selectable != 0
+                && selectable->isSelected()
+                && path.size() > 1) {
+                scene::Node &parent = path.parent();
+                if (&parent != &m_parent) {
+                    NodeSmartReference node(path.top().get());
+                    Node_getTraversable(parent)->erase(node);
+                    Node_getTraversable(m_parent)->insert(node);
+                }
+            }
+        }
+    }
+};
+
+void Scene_parentSelectedBrushesToEntity(scene::Graph &graph, scene::Node &parent)
+{
+    graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
+}
+
+class CountSelectedBrushes : public scene::Graph::Walker {
+    std::size_t &m_count;
+    mutable std::size_t m_depth;
+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;
+        }
+        Selectable *selectable = Instance_getSelectable(instance);
+        if (selectable != 0
+            && selectable->isSelected()
+            && Node_isPrimitive(path.top())) {
+            ++m_count;
+        }
+        return true;
+    }
+
+    void post(const scene::Path &path, scene::Instance &instance) const
+    {
+        --m_depth;
+    }
+};
+
+std::size_t Scene_countSelectedBrushes(scene::Graph &graph)
+{
+    std::size_t count;
+    graph.traverse(CountSelectedBrushes(count));
+    return count;
+}
+
+enum ENodeType {
+    eNodeUnknown,
+    eNodeMap,
+    eNodeEntity,
+    eNodePrimitive,
+};
+
+const char *nodetype_get_name(ENodeType type)
+{
+    if (type == eNodeMap) {
+        return "map";
+    }
+    if (type == eNodeEntity) {
+        return "entity";
+    }
+    if (type == eNodePrimitive) {
+        return "primitive";
+    }
+    return "unknown";
+}
+
+ENodeType node_get_nodetype(scene::Node &node)
+{
+    if (Node_isEntity(node)) {
+        return eNodeEntity;
+    }
+    if (Node_isPrimitive(node)) {
+        return eNodePrimitive;
+    }
+    return eNodeUnknown;
+}
+
+bool contains_entity(scene::Node &node)
+{
+    return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
+}
+
+bool contains_primitive(scene::Node &node)
+{
+    return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
+}
+
+ENodeType node_get_contains(scene::Node &node)
+{
+    if (contains_entity(node)) {
+        return eNodeEntity;
+    }
+    if (contains_primitive(node)) {
+        return eNodePrimitive;
+    }
+    return eNodeUnknown;
+}
+
+void Path_parent(const scene::Path &parent, const scene::Path &child)
+{
+    ENodeType contains = node_get_contains(parent.top());
+    ENodeType type = node_get_nodetype(child.top());
+
+    if (contains != eNodeUnknown && contains == type) {
+        NodeSmartReference node(child.top().get());
+        Path_deleteTop(child);
+        Node_getTraversable(parent.top())->insert(node);
+        SceneChangeNotify();
+    } else {
+        globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to "
+                            << nodetype_get_name(contains) << " container.\n";
+    }
+}
+
+void Scene_parentSelected()
+{
+    UndoableCommand undo("parentSelected");
+
+    if (GlobalSelectionSystem().countSelected() > 1) {
+        class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor {
+            const scene::Path &m_parent;
+        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());
+                }
+            }
+        };
+
+        ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
+        GlobalSelectionSystem().foreachSelected(visitor);
+    } else {
+        globalOutputStream() << "failed - did not find two selected nodes.\n";
+    }
+}
+
+
+void NewMap()
+{
+    if (ConfirmModified("New Map")) {
+        Map_RegionOff();
+        Map_Free();
+        Map_New();
+    }
+}
+
+CopiedString g_mapsPath;
+
+const char *getMapsPath()
+{
+    return g_mapsPath.c_str();
+}
+
+const char *getLastMapFolderPath()
+{
+    if (g_strLastMapFolder.empty()) {
+        GlobalPreferenceSystem().registerPreference("LastMapFolder", make_property_string(g_strLastMapFolder));
+        if (g_strLastMapFolder.empty()) {
+            g_strLastMapFolder = g_qeglobals.m_userGamePath;
+        }
+    }
+    return g_strLastMapFolder.c_str();
+}
+
+const char *map_open(const char *title)
+{
+    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, getLastMapFolderPath(), MapFormat::Name(), false, true, false);
+}
+
+const char *map_save(const char *title)
+{
+    return MainFrame_getWindow().file_dialog(FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true);
+}
+
+void OpenMap()
+{
+    if (!ConfirmModified("Open Map")) {
+        return;
+    }
+
+    const char *filename = map_open("Open Map");
+
+    if (filename != NULL) {
+        MRU_AddFile(filename);
+        Map_RegionOff();
+        Map_Free();
+        Map_LoadFile(filename);
+    }
+}
+
+void ImportMap()
+{
+    const char *filename = map_import("Import Map");
+
+    if (filename != NULL) {
+        UndoableCommand undo("mapImport");
+        Map_ImportFile(filename);
+    }
+}
+
+bool Map_SaveAs()
+{
+    const char *filename = map_save("Save Map");
+
+    if (filename != NULL) {
+        g_strLastMapFolder = g_path_get_dirname(filename);
+        MRU_AddFile(filename);
+        Map_Rename(filename);
+        return Map_Save();
+    }
+    return false;
+}
+
+void SaveMapAs()
+{
+    Map_SaveAs();
+}
+
+void SaveMap()
+{
+    if (Map_Unnamed(g_map)) {
+        SaveMapAs();
+    } else if (Map_Modified(g_map)) {
+        Map_Save();
+    }
+}
+
+void ExportMap()
+{
+    const char *filename = map_save("Export Selection");
+
+    if (filename != NULL) {
+        g_strLastMapFolder = g_path_get_dirname(filename);
+        Map_SaveSelected(filename);
+    }
+}
+
+void SaveRegion()
+{
+    const char *filename = map_save("Export Region");
+
+    if (filename != NULL) {
+        g_strLastMapFolder = g_path_get_dirname(filename);
+        Map_SaveRegion(filename);
+    }
+}
+
+
+void RegionOff()
+{
+    Map_RegionOff();
+    SceneChangeNotify();
+}
+
+void RegionXY()
+{
+    Map_RegionXY(
+            g_pParentWnd->GetXYWnd()->GetOrigin()[0] -
+            0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
+            g_pParentWnd->GetXYWnd()->GetOrigin()[1] -
+            0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
+            g_pParentWnd->GetXYWnd()->GetOrigin()[0] +
+            0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
+            g_pParentWnd->GetXYWnd()->GetOrigin()[1] +
+            0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
+    );
+    SceneChangeNotify();
+}
+
+void RegionBrush()
+{
+    Map_RegionBrush();
+    SceneChangeNotify();
+}
+
+void RegionSelected()
+{
+    Map_RegionSelectedBrushes();
+    SceneChangeNotify();
+}
+
+
+class BrushFindByIndexWalker : public scene::Traversable::Walker {
+    mutable std::size_t m_index;
+    scene::Path &m_path;
+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));
+        }
+        return false;
+    }
+};
+
+class EntityFindByIndexWalker : public scene::Traversable::Walker {
+    mutable std::size_t m_index;
+    scene::Path &m_path;
+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));
+        }
+        return false;
+    }
+};
+
+void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path &path)
+{
+    path.push(makeReference(GlobalSceneGraph().root()));
+    {
+        Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
+    }
+    if (path.size() == 2) {
+        scene::Traversable *traversable = Node_getTraversable(path.top());
+        if (traversable != 0) {
+            traversable->traverse(BrushFindByIndexWalker(brush, path));
+        }
+    }
+}
+
+inline bool Node_hasChildren(scene::Node &node)
+{
+    scene::Traversable *traversable = Node_getTraversable(node);
+    return traversable != 0 && !traversable->empty();
+}
+
+void SelectBrush(int entitynum, int brushnum)
+{
+    scene::Path path;
+    Scene_FindEntityBrush(entitynum, brushnum, path);
+    if (path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top()))) {
+        scene::Instance *instance = GlobalSceneGraph().find(path);
+        ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
+        Selectable *selectable = Instance_getSelectable(*instance);
+        ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
+        selectable->setSelected(true);
+        g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
+    }
+}
+
+
+class BrushFindIndexWalker : public scene::Graph::Walker {
+    mutable const scene::Node *m_node;
+    std::size_t &m_count;
+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()) {
+                m_node = 0;
+            }
+            if (m_node) {
+                ++m_count;
+            }
+        }
+        return true;
+    }
+};
+
+class EntityFindIndexWalker : public scene::Graph::Walker {
+    mutable const scene::Node *m_node;
+    std::size_t &m_count;
+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()) {
+                m_node = 0;
+            }
+            if (m_node) {
+                ++m_count;
+            }
+        }
+        return true;
+    }
+};
+
+static void GetSelectionIndex(int *ent, int *brush)
+{
+    std::size_t count_brush = 0;
+    std::size_t count_entity = 0;
+    if (GlobalSelectionSystem().countSelected() != 0) {
+        const scene::Path &path = GlobalSelectionSystem().ultimateSelected().path();
+
+        GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
+        GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
+    }
+    *brush = int(count_brush);
+    *ent = int(count_entity);
+}
+
+void DoFind()
+{
+    ModalDialog dialog;
+    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(ui::New);
+    window.add_accel_group(accel);
+
+    {
+        auto vbox = create_dialog_vbox(4, 4);
+        window.add(vbox);
+        {
+            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();
+                (table).attach(label, {0, 1, 0, 1}, {0, 0});
+            }
+            {
+                ui::Widget label = ui::Label("Brush number");
+                label.show();
+                (table).attach(label, {0, 1, 1, 2}, {0, 0});
+            }
+            {
+                auto entry = ui::Entry(ui::New);
+                entry.show();
+                table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
+                gtk_widget_grab_focus(entry);
+                entity = entry;
+            }
+            {
+                auto entry = ui::Entry(ui::New);
+                entry.show();
+                table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
+
+                brush = entry;
+            }
+        }
+        {
+            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);
+                hbox.pack_start(button, FALSE, FALSE, 0);
+                widget_make_default(button);
+                gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (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);
+            }
+        }
+    }
+
+    // Initialize dialog
+    char buf[16];
+    int ent, br;
+
+    GetSelectionIndex(&ent, &br);
+    sprintf(buf, "%i", ent);
+    entity.text(buf);
+    sprintf(buf, "%i", br);
+    brush.text(buf);
+
+    if (modal_dialog_show(window, dialog) == eIDOK) {
+        const char *entstr = gtk_entry_get_text(entity);
+        const char *brushstr = gtk_entry_get_text(brush);
+        SelectBrush(atoi(entstr), atoi(brushstr));
+    }
+
+    window.destroy();
+}
+
+void Map_constructPreferences(PreferencesPage &page)
+{
+    page.appendCheckBox("", "Load last map on open", g_bLoadLastMap);
+}
+
+
+class MapEntityClasses : public ModuleObserver {
+    std::size_t m_unrealised;
+public:
+    MapEntityClasses() : m_unrealised(1)
+    {
+    }
+
+    void realise()
+    {
+        if (--m_unrealised == 0) {
+            if (g_map.m_resource != 0) {
+                ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
+                g_map.m_resource->realise();
+            }
+        }
+    }
+
+    void unrealise()
+    {
+        if (++m_unrealised == 1) {
+            if (g_map.m_resource != 0) {
+                g_map.m_resource->flush();
+                g_map.m_resource->unrealise();
+            }
+        }
+    }
+};
+
+MapEntityClasses g_MapEntityClasses;
+
+
+class MapModuleObserver : public ModuleObserver {
+    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");
+            StringOutputStream buffer(256);
+            buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
+            Q_mkdir(buffer.c_str());
+            g_mapsPath = buffer.c_str();
+        }
+    }
+
+    void unrealise()
+    {
+        if (++m_unrealised == 1) {
+            g_mapsPath = "";
+        }
+    }
+};
+
+MapModuleObserver g_MapModuleObserver;
+
+CopiedString g_strLastMap;
+bool g_bLoadLastMap = false;
+
+void Map_Construct()
+{
+    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", make_property_string(g_strLastMap));
+    GlobalPreferenceSystem().registerPreference("LoadLastMap", make_property_string(g_bLoadLastMap));
+    GlobalPreferenceSystem().registerPreference("MapInfoDlg", make_property<WindowPosition_String>(g_posMapInfoWnd));
+
+    PreferencesDialog_addSettingsPreferences(makeCallbackF(Map_constructPreferences));
+
+    GlobalEntityClassManager().attach(g_MapEntityClasses);
+    Radiant_attachHomePathsObserver(g_MapModuleObserver);
+}
+
+void Map_Destroy()
+{
+    Radiant_detachHomePathsObserver(g_MapModuleObserver);
+    GlobalEntityClassManager().detach(g_MapEntityClasses);
+}