]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
Merge commit '48410b113dd2036e69dbf723a39ec9af02fc9b12'
[xonotic/netradiant.git] / radiant / map.cpp
1 /*
2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 #include "map.h"
23
24 #include "debugging/debugging.h"
25
26 #include "imap.h"
27 MapModules& ReferenceAPI_getMapModules();
28 #include "iselection.h"
29 #include "iundo.h"
30 #include "ibrush.h"
31 #include "ifilter.h"
32 #include "ireference.h"
33 #include "ifiletypes.h"
34 #include "ieclass.h"
35 #include "irender.h"
36 #include "ientity.h"
37 #include "editable.h"
38 #include "ifilesystem.h"
39 #include "namespace.h"
40 #include "moduleobserver.h"
41
42 #include <set>
43
44 #include <gtk/gtkmain.h>
45 #include <gtk/gtkbox.h>
46 #include <gtk/gtkentry.h>
47 #include <gtk/gtklabel.h>
48 #include <gtk/gtktable.h>
49 #include <gtk/gtktreemodel.h>
50 #include <gtk/gtktreeview.h>
51 #include <gtk/gtkliststore.h>
52 #include <gtk/gtkcellrenderertext.h>
53
54 #include "scenelib.h"
55 #include "transformlib.h"
56 #include "selectionlib.h"
57 #include "instancelib.h"
58 #include "traverselib.h"
59 #include "maplib.h"
60 #include "eclasslib.h"
61 #include "cmdlib.h"
62 #include "stream/textfilestream.h"
63 #include "os/path.h"
64 #include "uniquenames.h"
65 #include "modulesystem/singletonmodule.h"
66 #include "modulesystem/moduleregistry.h"
67 #include "stream/stringstream.h"
68 #include "signal/signal.h"
69
70 #include "gtkutil/filechooser.h"
71 #include "timer.h"
72 #include "select.h"
73 #include "plugin.h"
74 #include "filetypes.h"
75 #include "gtkdlgs.h"
76 #include "entityinspector.h"
77 #include "points.h"
78 #include "qe3.h"
79 #include "camwindow.h"
80 #include "xywindow.h"
81 #include "mainframe.h"
82 #include "preferences.h"
83 #include "referencecache.h"
84 #include "mru.h"
85 #include "commands.h"
86 #include "autosave.h"
87 #include "brushmodule.h"
88 #include "brush.h"
89
90 class NameObserver
91 {
92   UniqueNames& m_names;
93   CopiedString m_name;
94
95   void construct()
96   {
97     if(!empty())
98     {
99       //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
100       m_names.insert(name_read(c_str()));
101     }
102   }
103   void destroy()
104   {
105     if(!empty())
106     {
107       //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
108       m_names.erase(name_read(c_str()));
109     }
110   }
111
112   NameObserver& operator=(const NameObserver& other);
113 public:
114   NameObserver(UniqueNames& names) : m_names(names)
115   {
116     construct();
117   }
118   NameObserver(const NameObserver& other) : m_names(other.m_names), m_name(other.m_name)
119   {
120     construct();
121   }
122   ~NameObserver()
123   {
124     destroy();
125   }
126   bool empty() const
127   {
128     return string_empty(c_str()); 
129   }
130   const char* c_str() const
131   {
132     return m_name.c_str();
133   }
134   void nameChanged(const char* name)
135   {
136     destroy();
137     m_name = name;
138     construct();
139   }
140   typedef MemberCaller1<NameObserver, const char*, &NameObserver::nameChanged> NameChangedCaller;
141 };
142
143 class BasicNamespace : public Namespace
144 {
145   typedef std::map<NameCallback, NameObserver> Names;
146   Names m_names;
147   UniqueNames m_uniqueNames;
148 public:
149   ~BasicNamespace()
150   {
151     ASSERT_MESSAGE(m_names.empty(), "namespace: names still registered at shutdown");
152   }
153   void attach(const NameCallback& setName, const NameCallbackCallback& attachObserver)
154   {
155     std::pair<Names::iterator, bool> result = m_names.insert(Names::value_type(setName, m_uniqueNames));
156     ASSERT_MESSAGE(result.second, "cannot attach name");
157     attachObserver(NameObserver::NameChangedCaller((*result.first).second));
158     //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
159   }
160   void detach(const NameCallback& setName, const NameCallbackCallback& detachObserver)
161   {
162     Names::iterator i = m_names.find(setName);
163     ASSERT_MESSAGE(i != m_names.end(), "cannot detach name");
164     //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
165     detachObserver(NameObserver::NameChangedCaller((*i).second));
166     m_names.erase(i);
167   }
168
169   void makeUnique(const char* name, const NameCallback& setName) const
170   {
171     char buffer[1024];
172     name_write(buffer, m_uniqueNames.make_unique(name_read(name)));
173     setName(buffer);
174   }
175
176   void mergeNames(const BasicNamespace& other) const
177   {
178     typedef std::list<NameCallback> SetNameCallbacks;
179     typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
180     NameGroups groups;
181
182     UniqueNames uniqueNames(other.m_uniqueNames);
183
184     for(Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i)
185     {
186       groups[(*i).second.c_str()].push_back((*i).first);
187     }
188
189     for(NameGroups::iterator i = groups.begin(); i != groups.end(); ++i)
190     {
191       name_t uniqueName(uniqueNames.make_unique(name_read((*i).first.c_str())));
192       uniqueNames.insert(uniqueName);
193
194       char buffer[1024];
195       name_write(buffer, uniqueName);
196
197       //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
198
199       SetNameCallbacks& setNameCallbacks = (*i).second;
200
201       for(SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j)
202       {
203         (*j)(buffer);
204       }
205     }
206   }
207 };
208
209 BasicNamespace g_defaultNamespace;
210 BasicNamespace g_cloneNamespace;
211
212 class NamespaceAPI
213 {
214   Namespace* m_namespace;
215 public:
216   typedef Namespace Type;
217   STRING_CONSTANT(Name, "*");
218
219   NamespaceAPI()
220   {
221     m_namespace = &g_defaultNamespace;
222   }
223   Namespace* getTable()
224   {
225     return m_namespace;
226   }
227 };
228
229 typedef SingletonModule<NamespaceAPI> NamespaceModule;
230 typedef Static<NamespaceModule> StaticNamespaceModule;
231 StaticRegisterModule staticRegisterDefaultNamespace(StaticNamespaceModule::instance());
232
233
234 std::list<Namespaced*> g_cloned;
235
236 inline Namespaced* Node_getNamespaced(scene::Node& node)
237 {
238   return NodeTypeCast<Namespaced>::cast(node);
239 }
240
241 void Node_gatherNamespaced(scene::Node& node)
242 {
243   Namespaced* namespaced = Node_getNamespaced(node);
244   if(namespaced != 0)
245   {
246     g_cloned.push_back(namespaced);
247   }
248 }
249
250 class GatherNamespaced : public scene::Traversable::Walker
251 {
252 public:
253   bool pre(scene::Node& node) const
254   {
255     Node_gatherNamespaced(node);
256     return true;
257   }
258 };
259
260 void Map_gatherNamespaced(scene::Node& root)
261 {
262   Node_traverseSubgraph(root, GatherNamespaced());
263 }
264
265 void Map_mergeClonedNames()
266 {
267   for(std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i)
268   {
269     (*i)->setNamespace(g_cloneNamespace);
270   }
271   g_cloneNamespace.mergeNames(g_defaultNamespace);
272   for(std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i)
273   {
274     (*i)->setNamespace(g_defaultNamespace);
275   }
276
277   g_cloned.clear();
278 }
279
280 class WorldNode
281 {
282   scene::Node* m_node;
283 public:
284   WorldNode()
285     : m_node(0)
286   {
287   }
288   void set(scene::Node* node)
289   {
290     if(m_node != 0)
291       m_node->DecRef();
292     m_node = node;
293     if(m_node != 0)
294       m_node->IncRef();
295   }
296   scene::Node* get() const
297   {
298     return m_node;
299   }
300 };
301
302 class Map;
303 void Map_SetValid(Map& map, bool valid);
304 void Map_UpdateTitle(const Map& map);
305 void Map_SetWorldspawn(Map& map, scene::Node* node);
306
307
308 class Map : public ModuleObserver
309 {
310 public:
311   CopiedString m_name;
312   Resource* m_resource;
313   bool m_valid;
314
315   bool m_modified;
316   void (*m_modified_changed)(const Map&);
317
318   Signal0 m_mapValidCallbacks;
319
320   WorldNode m_world_node; // "classname" "worldspawn" !
321
322   Map() : m_resource(0), m_valid(false), m_modified_changed(Map_UpdateTitle)
323   {
324   }
325
326   void realise()
327   {
328     if(m_resource != 0)
329     {
330       if(Map_Unnamed(*this))
331       {
332         g_map.m_resource->setNode(NewMapRoot("").get_pointer());
333         MapFile* map = Node_getMapFile(*g_map.m_resource->getNode());
334         if(map != 0)
335         {
336           map->save();
337         }
338       }
339       else
340       {
341         m_resource->load();
342       }
343
344       GlobalSceneGraph().insert_root(*m_resource->getNode());
345
346       AutoSave_clear();
347
348       Map_SetValid(g_map, true);
349     }
350   }
351   void unrealise()
352   {
353     if(m_resource != 0)
354     {
355       Map_SetValid(g_map, false);
356       Map_SetWorldspawn(g_map, 0);
357
358
359       GlobalUndoSystem().clear();
360
361       GlobalSceneGraph().erase_root();
362     }
363   }
364 };
365
366 Map g_map;
367 Map* g_currentMap = 0;
368
369 void Map_addValidCallback(Map& map, const SignalHandler& handler)
370 {
371   map.m_mapValidCallbacks.connectLast(handler);
372 }
373
374 bool Map_Valid(const Map& map)
375 {
376   return map.m_valid;
377 }
378
379 void Map_SetValid(Map& map, bool valid)
380 {
381   map.m_valid = valid;
382   map.m_mapValidCallbacks();
383 }
384
385
386 const char* Map_Name(const Map& map)
387 {
388   return map.m_name.c_str();
389 }
390
391 bool Map_Unnamed(const Map& map)
392 {
393   return string_equal(Map_Name(map), "unnamed.map");
394 }
395
396 inline const MapFormat& MapFormat_forFile(const char* filename)
397 {
398   const char* moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), path_get_extension(filename));
399   MapFormat* format = Radiant_getMapModules().findModule(moduleName);
400   ASSERT_MESSAGE(format != 0, "map format not found for file " << makeQuoted(filename));
401   return *format;
402 }
403
404 const MapFormat& Map_getFormat(const Map& map)
405 {
406   return MapFormat_forFile(Map_Name(map));
407 }
408
409
410 bool Map_Modified(const Map& map)
411 {
412   return map.m_modified;
413 }
414
415 void Map_SetModified(Map& map, bool modified)
416 {
417   if(map.m_modified ^ modified)
418   {
419     map.m_modified = modified;
420
421     map.m_modified_changed(map);
422   }
423 }
424
425 void Map_UpdateTitle(const Map& map)
426 {
427   Sys_SetTitle(map.m_name.c_str(), Map_Modified(map));
428 }
429
430
431
432 scene::Node* Map_GetWorldspawn(const Map& map)
433 {
434   return map.m_world_node.get();
435 }
436
437 void Map_SetWorldspawn(Map& map, scene::Node* node)
438 {
439   map.m_world_node.set(node);
440 }
441
442
443 // TTimo
444 // need that in a variable, will have to tweak depending on the game
445 float g_MaxWorldCoord = 64*1024;
446 float g_MinWorldCoord = -64*1024;
447
448 void AddRegionBrushes (void);
449 void RemoveRegionBrushes (void);
450
451
452 /*
453 ================
454 Map_Free
455 free all map elements, reinitialize the structures that depend on them
456 ================
457 */
458 void Map_Free()
459 {
460         Pointfile_Clear();
461
462   g_map.m_resource->detach(g_map);
463   GlobalReferenceCache().release(g_map.m_name.c_str());
464   g_map.m_resource = 0;
465
466   FlushReferences();
467
468   g_currentMap = 0;
469   Brush_unlatchPreferences();
470 }
471
472 class EntityFindByClassname : public scene::Graph::Walker
473 {
474   const char* m_name;
475   Entity*& m_entity;
476 public:
477   EntityFindByClassname(const char* name, Entity*& entity) : m_name(name), m_entity(entity)
478   {
479     m_entity = 0;
480   }
481   bool pre(const scene::Path& path, scene::Instance& instance) const
482   {
483     if(m_entity == 0)
484     {
485       Entity* entity = Node_getEntity(path.top());
486       if(entity != 0
487         && string_equal(m_name, entity->getKeyValue("classname")))
488       {
489         m_entity = entity;
490       }
491     }
492     return true;
493   }
494 };
495
496 Entity* Scene_FindEntityByClass(const char* name)
497 {
498   Entity* entity;
499   GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
500   return entity;
501 }
502
503 Entity *Scene_FindPlayerStart()
504 {
505   typedef const char* StaticString;
506   StaticString strings[] = {
507     "info_player_start",
508     "info_player_deathmatch",
509     "team_CTF_redplayer",
510     "team_CTF_blueplayer",
511     "team_CTF_redspawn",
512     "team_CTF_bluespawn",
513   };
514   typedef const StaticString* StaticStringIterator;
515   for(StaticStringIterator i = strings, end = strings+(sizeof(strings)/sizeof(StaticString)); i != end; ++i)
516   {
517     Entity* entity = Scene_FindEntityByClass(*i);
518     if(entity != 0)
519     {
520       return entity;
521     }
522   }
523   return 0;
524 }
525
526 //
527 // move the view to a start position
528 //
529
530
531 void FocusViews(const Vector3& point, float angle)
532 {
533   CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
534   Camera_setOrigin(camwnd, point);
535   Vector3 angles(Camera_getAngles(camwnd));
536   angles[CAMERA_PITCH] = 0;
537   angles[CAMERA_YAW] = angle;
538   Camera_setAngles(camwnd, angles);
539
540   XYWnd* xywnd = g_pParentWnd->GetXYWnd();
541   xywnd->SetOrigin(point);
542 }
543
544 #include "stringio.h"
545
546 void Map_StartPosition()
547 {
548   Entity* entity = Scene_FindPlayerStart();
549
550   if (entity)
551   {
552     Vector3 origin;
553     string_parse_vector3(entity->getKeyValue("origin"), origin);
554     FocusViews(origin, string_read_float(entity->getKeyValue("angle")));
555   }
556   else
557   {
558     FocusViews(g_vector3_identity, 0);
559   }
560 }
561
562
563 inline bool node_is_worldspawn(scene::Node& node)
564 {
565   Entity* entity = Node_getEntity(node);
566   return entity != 0 && string_equal(entity->getKeyValue("classname"), "worldspawn");
567 }
568
569
570 // use first worldspawn
571 class entity_updateworldspawn : public scene::Traversable::Walker
572 {
573 public:
574   bool pre(scene::Node& node) const
575   {
576     if(node_is_worldspawn(node))
577     {
578       if(Map_GetWorldspawn(g_map) == 0)
579       {
580         Map_SetWorldspawn(g_map, &node);
581       }
582     }
583     return false;
584   }
585 };
586
587 scene::Node* Map_FindWorldspawn(Map& map)
588 {
589   Map_SetWorldspawn(map, 0);
590
591   Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
592
593   return Map_GetWorldspawn(map);
594 }
595
596
597 class CollectAllWalker : public scene::Traversable::Walker
598 {
599   scene::Node& m_root;
600   UnsortedNodeSet& m_nodes;
601 public:
602   CollectAllWalker(scene::Node& root, UnsortedNodeSet& nodes) : m_root(root), m_nodes(nodes)
603   {
604   }
605   bool pre(scene::Node& node) const
606   {
607     m_nodes.insert(NodeSmartReference(node));
608     Node_getTraversable(m_root)->erase(node);
609     return false;
610   }
611 };
612
613 void Node_insertChildFirst(scene::Node& parent, scene::Node& child)
614 {
615   UnsortedNodeSet nodes;
616   Node_getTraversable(parent)->traverse(CollectAllWalker(parent, nodes));
617   Node_getTraversable(parent)->insert(child);
618
619   for(UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i)
620   {
621     Node_getTraversable(parent)->insert((*i));
622   }
623 }
624
625 scene::Node& createWorldspawn()
626 {
627   NodeSmartReference worldspawn(GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("worldspawn", true)));
628   Node_insertChildFirst(GlobalSceneGraph().root(), worldspawn);
629   return worldspawn;
630 }
631
632 void Map_UpdateWorldspawn(Map& map)
633 {
634   if(Map_FindWorldspawn(map) == 0)
635   {
636     Map_SetWorldspawn(map, &createWorldspawn());
637   }
638 }
639
640 scene::Node& Map_FindOrInsertWorldspawn(Map& map)
641 {
642   Map_UpdateWorldspawn(map);
643   return *Map_GetWorldspawn(map);
644 }
645
646
647 class MapMergeAll : public scene::Traversable::Walker
648 {
649   mutable scene::Path m_path;
650 public:
651   MapMergeAll(const scene::Path& root)
652     : m_path(root)
653   {
654   }
655   bool pre(scene::Node& node) const
656   {
657     Node_getTraversable(m_path.top())->insert(node);
658     m_path.push(makeReference(node));
659     selectPath(m_path, true);
660     return false;
661   }
662   void post(scene::Node& node) const
663   {
664     m_path.pop();
665   }
666 };
667
668 class MapMergeEntities : public scene::Traversable::Walker
669 {
670   mutable scene::Path m_path;
671 public:
672   MapMergeEntities(const scene::Path& root)
673     : m_path(root)
674   {
675   }
676   bool pre(scene::Node& node) const
677   {
678     if(node_is_worldspawn(node))
679     {
680       scene::Node* world_node = Map_FindWorldspawn(g_map);
681       if(world_node == 0)
682       {
683         Map_SetWorldspawn(g_map, &node);
684         Node_getTraversable(m_path.top().get())->insert(node);
685         m_path.push(makeReference(node));
686         Node_getTraversable(node)->traverse(SelectChildren(m_path));
687       }
688       else
689       {
690         m_path.push(makeReference(*world_node));
691         Node_getTraversable(node)->traverse(MapMergeAll(m_path));
692       }
693     }
694     else
695     {
696       Node_getTraversable(m_path.top())->insert(node);
697       m_path.push(makeReference(node));
698       if(node_is_group(node))
699       {
700         Node_getTraversable(node)->traverse(SelectChildren(m_path));
701       }
702       else
703       {
704         selectPath(m_path, true);
705       }
706     }
707     return false;
708   }
709   void post(scene::Node& node) const
710   {
711     m_path.pop();
712   }
713 };
714
715 class BasicContainer : public scene::Node::Symbiot
716 {
717   class TypeCasts
718   {
719     NodeTypeCastTable m_casts;
720   public:
721     TypeCasts()
722     {
723       NodeContainedCast<BasicContainer, scene::Traversable>::install(m_casts);
724     }
725     NodeTypeCastTable& get()
726     {
727       return m_casts;
728     }
729   };
730
731   scene::Node m_node;
732   TraversableNodeSet m_traverse;
733 public:
734
735   typedef LazyStatic<TypeCasts> StaticTypeCasts;
736
737   scene::Traversable& get(NullType<scene::Traversable>)
738   {
739     return m_traverse;
740   }
741
742   BasicContainer() : m_node(this, this, StaticTypeCasts::instance().get())
743   {
744   }
745   void release()
746   {
747     delete this;
748   }
749   scene::Node& node()
750   {
751     return m_node;
752   }
753 };
754
755 /// Merges the map graph rooted at \p node into the global scene-graph.
756 void MergeMap(scene::Node& node)
757 {
758   Node_getTraversable(node)->traverse(MapMergeEntities(scene::Path(makeReference(GlobalSceneGraph().root()))));
759 }
760 void Map_ImportSelected(TextInputStream& in, const MapFormat& format)
761 {
762   NodeSmartReference node((new BasicContainer)->node());
763   format.readGraph(node, in, GlobalEntityCreator());
764   Map_gatherNamespaced(node);
765   Map_mergeClonedNames();
766   MergeMap(node);
767 }
768
769 inline scene::Cloneable* Node_getCloneable(scene::Node& node)
770 {
771   return NodeTypeCast<scene::Cloneable>::cast(node);
772 }
773
774 inline scene::Node& node_clone(scene::Node& node)
775 {
776   scene::Cloneable* cloneable = Node_getCloneable(node);
777   if(cloneable != 0)
778   {
779     return cloneable->clone();
780   }
781   
782   return (new scene::NullNode)->node();
783 }
784
785 class CloneAll : public scene::Traversable::Walker
786 {
787   mutable scene::Path m_path;
788 public:
789   CloneAll(scene::Node& root)
790     : m_path(makeReference(root))
791   {
792   }
793   bool pre(scene::Node& node) const
794   {
795     if(node.isRoot())
796     {
797       return false;
798     }
799     
800     m_path.push(makeReference(node_clone(node)));
801     m_path.top().get().IncRef();
802
803     return true;
804   }
805   void post(scene::Node& node) const
806   {
807     if(node.isRoot())
808     {
809       return;
810     }
811
812     Node_getTraversable(m_path.parent())->insert(m_path.top());
813
814     m_path.top().get().DecRef();
815     m_path.pop();
816   }
817 };
818
819 scene::Node& Node_Clone(scene::Node& node)
820 {
821   scene::Node& clone = node_clone(node);
822   scene::Traversable* traversable = Node_getTraversable(node);
823   if(traversable != 0)
824   {
825     traversable->traverse(CloneAll(clone));
826   }
827   return clone;
828 }
829
830
831 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
832
833 class EntityBreakdownWalker : public scene::Graph::Walker
834 {
835   EntityBreakdown& m_entitymap;
836 public:
837   EntityBreakdownWalker(EntityBreakdown& entitymap)
838     : m_entitymap(entitymap)
839   {
840   }
841   bool pre(const scene::Path& path, scene::Instance& instance) const
842   {
843     Entity* entity = Node_getEntity(path.top());
844     if(entity != 0)
845     {
846       const EntityClass& eclass = entity->getEntityClass();
847       if(m_entitymap.find(eclass.name()) == m_entitymap.end())
848       {
849         m_entitymap[eclass.name()] = 1;
850       }
851       else ++m_entitymap[eclass.name()];
852     }
853     return true;
854   }
855 };
856
857 void Scene_EntityBreakdown(EntityBreakdown& entitymap)
858 {
859   GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap));
860 }
861
862
863 WindowPosition g_posMapInfoWnd(c_default_window_pos);
864
865 void DoMapInfo()
866 {
867   ModalDialog dialog;
868   GtkEntry* brushes_entry;
869   GtkEntry* entities_entry;
870   GtkListStore* EntityBreakdownWalker;
871
872   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Map Info", G_CALLBACK(dialog_delete_callback), &dialog);
873
874   window_set_position(window, g_posMapInfoWnd);
875
876   {
877     GtkVBox* vbox = create_dialog_vbox(4, 4);
878     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
879
880     {
881       GtkHBox* hbox = create_dialog_hbox(4);
882       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, TRUE, 0);
883
884       {
885         GtkTable* table = create_dialog_table(2, 2, 4, 4);
886         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0);
887
888         {
889           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
890           gtk_widget_show(GTK_WIDGET(entry));
891           gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1,
892                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
893                             (GtkAttachOptions) (0), 0, 0);
894           gtk_entry_set_editable(entry, FALSE);
895
896           brushes_entry = entry;
897         }
898         {
899           GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
900           gtk_widget_show(GTK_WIDGET(entry));
901           gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2,
902                             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
903                             (GtkAttachOptions) (0), 0, 0);
904           gtk_entry_set_editable(entry, FALSE);
905
906           entities_entry = entry;
907         }
908         {
909           GtkWidget* label = gtk_label_new ("Total Brushes");
910           gtk_widget_show (label);
911           gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
912                             (GtkAttachOptions) (GTK_FILL),
913                             (GtkAttachOptions) (0), 0, 0);
914           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
915         }
916         {
917           GtkWidget* label = gtk_label_new ("Total Entities");
918           gtk_widget_show (label);
919           gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
920                             (GtkAttachOptions) (GTK_FILL),
921                             (GtkAttachOptions) (0), 0, 0);
922           gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
923         }
924       }
925       {
926         GtkVBox* vbox2 = create_dialog_vbox(4);
927         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox2), FALSE, FALSE, 0);
928
929         {
930           GtkButton* button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog);
931           gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(button), FALSE, FALSE, 0);
932         }
933       }
934     }
935     {
936       GtkWidget* label = gtk_label_new ("Entity breakdown");
937       gtk_widget_show (label);
938       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, TRUE, 0);
939       gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
940     }
941     {
942       GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4);
943       gtk_box_pack_start(GTK_BOX (vbox), GTK_WIDGET(scr), TRUE, TRUE, 0);
944
945       {
946         GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
947
948         GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
949         gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(view), TRUE);
950
951         {
952           GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
953           GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Entity", renderer, "text", 0, 0);
954           gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
955           gtk_tree_view_column_set_sort_column_id(column, 0);
956         }
957
958         {
959           GtkCellRenderer* renderer = gtk_cell_renderer_text_new();
960           GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Count", renderer, "text", 1, 0);
961           gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
962           gtk_tree_view_column_set_sort_column_id(column, 1);
963         }
964
965         gtk_widget_show(view);
966
967         gtk_container_add(GTK_CONTAINER(scr), view);
968         
969         EntityBreakdownWalker = store;
970       }
971     }
972   }
973
974   // Initialize fields
975
976   {
977     EntityBreakdown entitymap;
978     Scene_EntityBreakdown(entitymap);
979
980     for(EntityBreakdown::iterator i=entitymap.begin(); i != entitymap.end(); ++i)
981     {
982       char tmp[16];
983       sprintf (tmp, "%u", Unsigned((*i).second));
984       GtkTreeIter iter;
985       gtk_list_store_append(GTK_LIST_STORE(EntityBreakdownWalker), &iter);
986       gtk_list_store_set(GTK_LIST_STORE(EntityBreakdownWalker), &iter, 0, (*i).first.c_str(), 1, tmp, -1);
987     }
988   }
989
990   g_object_unref(G_OBJECT(EntityBreakdownWalker));
991
992   char tmp[16];
993   sprintf (tmp, "%u", Unsigned(g_brushCount.get()));
994   gtk_entry_set_text (GTK_ENTRY (brushes_entry), tmp);
995   sprintf (tmp, "%u", Unsigned(g_entityCount.get()));
996   gtk_entry_set_text (GTK_ENTRY (entities_entry), tmp);
997
998   modal_dialog_show(window, dialog);
999
1000   // save before exit
1001   window_get_position(window, g_posMapInfoWnd);
1002
1003   gtk_widget_destroy(GTK_WIDGET(window));
1004 }
1005
1006
1007
1008 class ScopeTimer
1009 {
1010   Timer m_timer;
1011   const char* m_message;
1012 public:
1013   ScopeTimer(const char* message)
1014     : m_message(message)
1015   {
1016     m_timer.start();
1017   }
1018   ~ScopeTimer()
1019   {
1020     double elapsed_time = m_timer.elapsed_msec() / 1000.f;
1021     globalOutputStream() << m_message << " timer: " << FloatFormat(elapsed_time, 5, 2) << " second(s) elapsed\n";
1022   }
1023 };
1024
1025 /*
1026 ================
1027 Map_LoadFile
1028 ================
1029 */
1030
1031 void Map_LoadFile (const char *filename)
1032 {
1033   globalOutputStream() << "Loading map from " << filename << "\n";
1034   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1035
1036   {
1037     ScopeTimer timer("map load");
1038
1039     const MapFormat* format = NULL;
1040     const char* moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1041     if(string_not_empty(moduleName))
1042       format = ReferenceAPI_getMapModules().findModule(moduleName);
1043
1044     for(int i = 0; i < Brush_toggleFormatCount(); ++i)
1045     {
1046       if(i)
1047         Map_Free();
1048       Brush_toggleFormat(i);
1049       g_map.m_name = filename;
1050       Map_UpdateTitle(g_map);
1051       g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1052       if(format)
1053         format->wrongFormat = false;
1054       g_map.m_resource->attach(g_map);
1055       if(format)
1056         if(!format->wrongFormat)
1057           break;
1058     }
1059
1060     Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
1061   }
1062
1063   globalOutputStream() << "--- LoadMapFile ---\n";
1064   globalOutputStream() << g_map.m_name.c_str() << "\n";
1065   
1066   globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
1067   globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
1068
1069   //GlobalEntityCreator().printStatistics();
1070   
1071   //
1072   // move the view to a start position
1073   //
1074   Map_StartPosition();
1075
1076   g_currentMap = &g_map;
1077 }
1078
1079 class Excluder
1080 {
1081 public:
1082   virtual bool excluded(scene::Node& node) const = 0;
1083 };
1084
1085 class ExcludeWalker : public scene::Traversable::Walker
1086 {
1087   const scene::Traversable::Walker& m_walker;
1088   const Excluder* m_exclude;
1089   mutable bool m_skip;
1090 public:
1091   ExcludeWalker(const scene::Traversable::Walker& walker, const Excluder& exclude)
1092     : m_walker(walker), m_exclude(&exclude), m_skip(false)
1093   {
1094   }
1095   bool pre(scene::Node& node) const
1096   {
1097     if(m_exclude->excluded(node) || node.isRoot())
1098     {
1099       m_skip = true;
1100       return false;
1101     }
1102     else
1103     {
1104       m_walker.pre(node);
1105     }
1106     return true;
1107   }
1108   void post(scene::Node& node) const
1109   {
1110     if(m_skip)
1111     {
1112       m_skip = false;
1113     }
1114     else
1115     {
1116       m_walker.post(node);
1117     }
1118   }
1119 };
1120
1121 class AnyInstanceSelected : public scene::Instantiable::Visitor
1122 {
1123   bool& m_selected;
1124 public:
1125   AnyInstanceSelected(bool& selected) : m_selected(selected)
1126   {
1127     m_selected = false;
1128   }
1129   void visit(scene::Instance& instance) const
1130   {
1131     Selectable* selectable = Instance_getSelectable(instance);
1132     if(selectable != 0
1133       && selectable->isSelected())
1134     {
1135       m_selected = true;
1136     }
1137   }
1138 };
1139
1140 bool Node_instanceSelected(scene::Node& node)
1141 {
1142   scene::Instantiable* instantiable = Node_getInstantiable(node);
1143   ASSERT_NOTNULL(instantiable);
1144   bool selected;
1145   instantiable->forEachInstance(AnyInstanceSelected(selected));
1146   return selected;
1147 }
1148
1149 class SelectedDescendantWalker : public scene::Traversable::Walker
1150 {
1151   bool& m_selected;
1152 public:
1153   SelectedDescendantWalker(bool& selected) : m_selected(selected)
1154   {
1155     m_selected = false;
1156   }
1157
1158   bool pre(scene::Node& node) const
1159   {
1160     if(node.isRoot())
1161     {
1162       return false;
1163     }
1164
1165     if(Node_instanceSelected(node))
1166     {
1167       m_selected = true;
1168     }
1169
1170     return true;
1171   }
1172 };
1173
1174 bool Node_selectedDescendant(scene::Node& node)
1175 {
1176   bool selected;
1177   Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
1178   return selected;
1179 }
1180
1181 class SelectionExcluder : public Excluder
1182 {
1183 public:
1184   bool excluded(scene::Node& node) const
1185   {
1186     return !Node_selectedDescendant(node);
1187   }
1188 };
1189
1190 class IncludeSelectedWalker : public scene::Traversable::Walker
1191 {
1192   const scene::Traversable::Walker& m_walker;
1193   mutable std::size_t m_selected;
1194   mutable bool m_skip;
1195
1196   bool selectedParent() const
1197   {
1198     return m_selected != 0;
1199   }
1200 public:
1201   IncludeSelectedWalker(const scene::Traversable::Walker& walker)
1202     : m_walker(walker), m_selected(0), m_skip(false)
1203   {
1204   }
1205   bool pre(scene::Node& node) const
1206   {
1207     // include node if:
1208     // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1209     if(!node.isRoot() && (Node_selectedDescendant(node) || selectedParent()))
1210     {
1211       if(Node_instanceSelected(node))
1212       {
1213         ++m_selected;
1214       }
1215       m_walker.pre(node);
1216       return true;
1217     }
1218     else
1219     {
1220       m_skip = true;
1221       return false;
1222     }
1223   }
1224   void post(scene::Node& node) const
1225   {
1226     if(m_skip)
1227     {
1228       m_skip = false;
1229     }
1230     else
1231     {
1232       if(Node_instanceSelected(node))
1233       {
1234         --m_selected;
1235       }
1236       m_walker.post(node);
1237     }
1238   }
1239 };
1240
1241 void Map_Traverse_Selected(scene::Node& root, const scene::Traversable::Walker& walker)
1242 {
1243   scene::Traversable* traversable = Node_getTraversable(root);
1244   if(traversable != 0)
1245   {
1246 #if 0
1247     traversable->traverse(ExcludeWalker(walker, SelectionExcluder()));
1248 #else
1249     traversable->traverse(IncludeSelectedWalker(walker));
1250 #endif
1251   }
1252 }
1253
1254 void Map_ExportSelected(TextOutputStream& out, const MapFormat& format)
1255 {
1256   format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out);
1257 }
1258
1259 void Map_Traverse(scene::Node& root, const scene::Traversable::Walker& walker)
1260 {
1261   scene::Traversable* traversable = Node_getTraversable(root);
1262   if(traversable != 0)
1263   {
1264     traversable->traverse(walker);
1265   }
1266 }
1267
1268 class RegionExcluder : public Excluder
1269 {
1270 public:
1271   bool excluded(scene::Node& node) const
1272   {
1273     return node.excluded();
1274   }
1275 };
1276
1277 void Map_Traverse_Region(scene::Node& root, const scene::Traversable::Walker& walker)
1278 {
1279   scene::Traversable* traversable = Node_getTraversable(root);
1280   if(traversable != 0)
1281   {
1282     traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
1283   }
1284 }
1285
1286 bool Map_SaveRegion(const char *filename)
1287 {
1288   AddRegionBrushes();
1289
1290   bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region, filename); 
1291
1292   RemoveRegionBrushes();
1293
1294   return success;
1295 }
1296
1297
1298 void Map_RenameAbsolute(const char* absolute)
1299 {
1300   Resource* resource = GlobalReferenceCache().capture(absolute);
1301   NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
1302   resource->setNode(clone.get_pointer());
1303
1304   {
1305     //ScopeTimer timer("clone subgraph");
1306     Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
1307   }
1308
1309   g_map.m_resource->detach(g_map);
1310   GlobalReferenceCache().release(g_map.m_name.c_str());
1311
1312   g_map.m_resource = resource;
1313
1314   g_map.m_name = absolute;
1315   Map_UpdateTitle(g_map);
1316
1317   g_map.m_resource->attach(g_map);
1318 }
1319
1320 void Map_Rename(const char* filename)
1321 {
1322   if(!string_equal(g_map.m_name.c_str(), filename))
1323   {
1324     ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1325
1326     Map_RenameAbsolute(filename);
1327     
1328     SceneChangeNotify();
1329   }
1330   else
1331   {
1332     SaveReferences();
1333   }
1334 }
1335
1336 bool Map_Save()
1337 {
1338         Pointfile_Clear();
1339
1340   ScopeTimer timer("map save");
1341   SaveReferences();
1342   return true; // assume success..
1343 }
1344
1345 /*
1346 ===========
1347 Map_New
1348
1349 ===========
1350 */
1351 void Map_New()
1352 {
1353         //globalOutputStream() << "Map_New\n";
1354
1355         g_map.m_name = "unnamed.map";
1356   Map_UpdateTitle(g_map);
1357
1358   {
1359     g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1360 //    ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1361     g_map.m_resource->attach(g_map);
1362
1363     SceneChangeNotify();
1364   }
1365
1366   FocusViews(g_vector3_identity, 0);
1367
1368   g_currentMap = &g_map;
1369 }
1370
1371 extern void ConstructRegionBrushes(scene::Node* brushes[6], const Vector3& region_mins, const Vector3& region_maxs);
1372
1373 void ConstructRegionStartpoint(scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs)
1374 {
1375   /*! 
1376   \todo we need to make sure that the player start IS inside the region and bail out if it's not
1377   the compiler will refuse to compile a map with a player_start somewhere in empty space..
1378   for now, let's just print an error
1379   */
1380   
1381   Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
1382
1383   for (int i=0 ; i<3 ; i++)
1384   {
1385     if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i])
1386     {
1387       globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1388       break;
1389     }
1390   }
1391   
1392   // write the info_playerstart
1393   char sTmp[1024];
1394   sprintf(sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2]);
1395   Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
1396   sprintf(sTmp, "%d", (int)Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
1397   Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
1398 }
1399
1400 /*
1401 ===========================================================
1402
1403   REGION
1404
1405 ===========================================================
1406 */
1407 bool    region_active;
1408 Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
1409 Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
1410
1411 scene::Node* region_sides[6];
1412 scene::Node* region_startpoint = 0;
1413
1414 /*
1415 ===========
1416 AddRegionBrushes
1417 a regioned map will have temp walls put up at the region boundary
1418 \todo TODO TTimo old implementation of region brushes
1419   we still add them straight in the worldspawn and take them out after the map is saved
1420   with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1421 ===========
1422 */
1423 void AddRegionBrushes (void)
1424 {
1425         int             i;
1426
1427   for(i=0; i<6; i++)
1428   {
1429     region_sides[i] = &GlobalBrushCreator().createBrush();
1430     Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
1431   }
1432
1433   region_startpoint = &GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("info_player_start", false));
1434
1435   ConstructRegionBrushes(region_sides, region_mins, region_maxs);
1436   ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
1437
1438   Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
1439 }
1440
1441 void RemoveRegionBrushes (void)
1442 {
1443   for(std::size_t i=0; i<6; i++)
1444   {
1445     Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
1446   }
1447   Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
1448 }
1449
1450 inline void exclude_node(scene::Node& node, bool exclude)
1451 {
1452   exclude
1453     ? node.enable(scene::Node::eExcluded)
1454     : node.disable(scene::Node::eExcluded);
1455 }
1456
1457 class ExcludeAllWalker : public scene::Graph::Walker
1458 {
1459   bool m_exclude;
1460 public:
1461   ExcludeAllWalker(bool exclude)
1462     : m_exclude(exclude)
1463   {
1464   }
1465   bool pre(const scene::Path& path, scene::Instance& instance) const
1466   {
1467     exclude_node(path.top(), m_exclude);
1468
1469     return true;
1470   }
1471 };
1472
1473 void Scene_Exclude_All(bool exclude)
1474 {
1475   GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
1476 }
1477
1478 bool Instance_isSelected(const scene::Instance& instance)
1479 {
1480   const Selectable* selectable = Instance_getSelectable(instance);
1481   return selectable != 0 && selectable->isSelected();
1482 }
1483
1484 class ExcludeSelectedWalker : public scene::Graph::Walker
1485 {
1486   bool m_exclude;
1487 public:
1488   ExcludeSelectedWalker(bool exclude)
1489     : m_exclude(exclude)
1490   {
1491   }
1492   bool pre(const scene::Path& path, scene::Instance& instance) const
1493   {
1494     exclude_node(path.top(), (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
1495     return true;
1496   }
1497 };
1498
1499 void Scene_Exclude_Selected(bool exclude)
1500 {
1501   GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
1502 }
1503
1504 class ExcludeRegionedWalker : public scene::Graph::Walker
1505 {
1506   bool m_exclude;
1507 public:
1508   ExcludeRegionedWalker(bool exclude)
1509     : m_exclude(exclude)
1510   {
1511   }
1512   bool pre(const scene::Path& path, scene::Instance& instance) const
1513   {
1514     exclude_node(
1515       path.top(),
1516       !(
1517         (
1518           aabb_intersects_aabb(
1519             instance.worldAABB(),
1520             aabb_for_minmax(region_mins, region_maxs)
1521           ) != 0
1522         ) ^ m_exclude
1523       )
1524     );
1525
1526     return true;
1527   }
1528 };
1529
1530 void Scene_Exclude_Region(bool exclude)
1531 {
1532   GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
1533 }
1534
1535 /*
1536 ===========
1537 Map_RegionOff
1538
1539 Other filtering options may still be on
1540 ===========
1541 */
1542 void Map_RegionOff()
1543 {
1544         region_active = false;
1545
1546         region_maxs[0] = g_MaxWorldCoord - 64;
1547         region_mins[0] = g_MinWorldCoord + 64;
1548         region_maxs[1] = g_MaxWorldCoord - 64;
1549         region_mins[1] = g_MinWorldCoord + 64;
1550         region_maxs[2] = g_MaxWorldCoord - 64;
1551         region_mins[2] = g_MinWorldCoord + 64;
1552         
1553         Scene_Exclude_All(false);
1554 }
1555
1556 void Map_ApplyRegion (void)
1557 {
1558         region_active = true;
1559
1560         Scene_Exclude_Region(false);
1561 }
1562
1563
1564 /*
1565 ========================
1566 Map_RegionSelectedBrushes
1567 ========================
1568 */
1569 void Map_RegionSelectedBrushes (void)
1570 {
1571         Map_RegionOff();
1572
1573   if(GlobalSelectionSystem().countSelected() != 0
1574     && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive)
1575   {
1576           region_active = true;
1577           Select_GetBounds (region_mins, region_maxs);
1578
1579           Scene_Exclude_Selected(false);
1580     
1581     GlobalSelectionSystem().setSelectedAll(false);
1582   }
1583 }
1584
1585
1586 /*
1587 ===========
1588 Map_RegionXY
1589 ===========
1590 */
1591 void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
1592 {
1593         Map_RegionOff();
1594
1595         region_mins[0] = x_min;
1596   region_maxs[0] = x_max;
1597   region_mins[1] = y_min;
1598   region_maxs[1] = y_max;
1599   region_mins[2] = g_MinWorldCoord + 64;
1600         region_maxs[2] = g_MaxWorldCoord - 64;
1601
1602         Map_ApplyRegion();
1603 }
1604
1605 void Map_RegionBounds(const AABB& bounds)
1606 {
1607   Map_RegionOff();
1608
1609   region_mins = vector3_subtracted(bounds.origin, bounds.extents);
1610   region_maxs = vector3_added(bounds.origin, bounds.extents);
1611
1612   deleteSelection();
1613
1614   Map_ApplyRegion();
1615 }
1616
1617 /*
1618 ===========
1619 Map_RegionBrush
1620 ===========
1621 */
1622 void Map_RegionBrush (void)
1623 {
1624   if(GlobalSelectionSystem().countSelected() != 0)
1625   {
1626     scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1627     Map_RegionBounds(instance.worldAABB());
1628   }
1629 }
1630
1631 //
1632 //================
1633 //Map_ImportFile
1634 //================
1635 //
1636 bool Map_ImportFile(const char* filename)
1637 {
1638   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1639
1640   bool success = false;
1641
1642   if(extension_equal(path_get_extension(filename), "bsp"))
1643     goto tryDecompile;
1644
1645   {
1646     const MapFormat* format = NULL;
1647     const char* moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1648     if(string_not_empty(moduleName))
1649       format = ReferenceAPI_getMapModules().findModule(moduleName);
1650
1651     if(format)
1652       format->wrongFormat = false;
1653     Resource* resource = GlobalReferenceCache().capture(filename);
1654     resource->refresh(); // avoid loading old version if map has changed on disk since last import
1655     if(!resource->load())
1656     {
1657       GlobalReferenceCache().release(filename);
1658       goto tryDecompile;
1659     }
1660     if(format)
1661       if(format->wrongFormat)
1662       {
1663         GlobalReferenceCache().release(filename);
1664         goto tryDecompile;
1665       }
1666     NodeSmartReference clone(NewMapRoot(""));
1667     Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1668     Map_gatherNamespaced(clone);
1669     Map_mergeClonedNames();
1670     MergeMap(clone);
1671     success = true;
1672     GlobalReferenceCache().release(filename);
1673   }
1674
1675   SceneChangeNotify();
1676
1677   return success;
1678
1679 tryDecompile:
1680
1681   const char *type = GlobalRadiant().getRequiredGameDescriptionKeyValue("q3map2_type");
1682   int n = string_length(path_get_extension(filename));
1683   if(n && (extension_equal(path_get_extension(filename), "bsp") || extension_equal(path_get_extension(filename), "map")))
1684   {
1685     StringBuffer output;
1686     output.push_string(AppPath_get());
1687     output.push_string("q3map2.");
1688     output.push_string(RADIANT_EXECUTABLE);
1689     output.push_string(" -v -game ");
1690     output.push_string((type && *type) ? type : "quake3");
1691     output.push_string(" -fs_basepath \"");
1692     output.push_string(EnginePath_get());
1693     output.push_string("\" -fs_homepath \"");
1694     output.push_string(g_qeglobals.m_userEnginePath.c_str());
1695     output.push_string("\" -fs_game ");
1696     output.push_string(gamename_get());
1697     output.push_string(" -convert -format ");
1698     output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
1699     if(extension_equal(path_get_extension(filename), "map"))
1700       output.push_string(" -readmap ");
1701     output.push_string(" \"");
1702     output.push_string(filename);
1703     output.push_string("\"");
1704
1705     // run
1706     Q_Exec(NULL, output.c_str(), NULL, false, true);
1707
1708     // rebuild filename as "filenamewithoutext_converted.map"
1709     output.clear();
1710     output.push_range(filename, filename + string_length(filename) - (n + 1));
1711     output.push_string("_converted.map");
1712     filename = output.c_str();
1713
1714     // open
1715     Resource* resource = GlobalReferenceCache().capture(filename);
1716     resource->refresh(); // avoid loading old version if map has changed on disk since last import
1717     if(!resource->load())
1718     {
1719       GlobalReferenceCache().release(filename);
1720       goto tryDecompile;
1721     }
1722     NodeSmartReference clone(NewMapRoot(""));
1723     Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1724     Map_gatherNamespaced(clone);
1725     Map_mergeClonedNames();
1726     MergeMap(clone);
1727     success = true;
1728     GlobalReferenceCache().release(filename);
1729   }
1730   
1731   SceneChangeNotify();
1732   return success;
1733 }
1734
1735 /*
1736 ===========
1737 Map_SaveFile
1738 ===========
1739 */
1740 bool Map_SaveFile(const char* filename)
1741 {
1742   ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1743   return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename); 
1744 }
1745
1746 //
1747 //===========
1748 //Map_SaveSelected
1749 //===========
1750 //
1751 // Saves selected world brushes and whole entities with partial/full selections
1752 //
1753 bool Map_SaveSelected(const char* filename)
1754 {
1755   return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected, filename); 
1756 }
1757
1758
1759 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1760 {
1761   scene::Node& m_parent;
1762 public:
1763   ParentSelectedBrushesToEntityWalker(scene::Node& parent) : m_parent(parent)
1764   {
1765   }
1766   bool pre(const scene::Path& path, scene::Instance& instance) const
1767   {
1768     if(path.top().get_pointer() != &m_parent
1769       && Node_isPrimitive(path.top()))
1770     {
1771       Selectable* selectable = Instance_getSelectable(instance);
1772       if(selectable != 0
1773         && selectable->isSelected()
1774         && path.size() > 1)
1775       {
1776         return false;
1777       }
1778     }
1779     return true;
1780   }
1781   void post(const scene::Path& path, scene::Instance& instance) const
1782   {
1783     if(path.top().get_pointer() != &m_parent
1784       && Node_isPrimitive(path.top()))
1785     {
1786       Selectable* selectable = Instance_getSelectable(instance);
1787       if(selectable != 0
1788         && selectable->isSelected()
1789         && path.size() > 1)
1790       {
1791         scene::Node& parent = path.parent();
1792         if(&parent != &m_parent)
1793         {
1794           NodeSmartReference node(path.top().get());
1795           Node_getTraversable(parent)->erase(node);
1796           Node_getTraversable(m_parent)->insert(node);
1797         }
1798       }
1799     }
1800   }
1801 };
1802
1803 void Scene_parentSelectedBrushesToEntity(scene::Graph& graph, scene::Node& parent)
1804 {
1805   graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1806 }
1807
1808 class CountSelectedBrushes : public scene::Graph::Walker
1809 {
1810   std::size_t& m_count;
1811   mutable std::size_t m_depth;
1812 public:
1813   CountSelectedBrushes(std::size_t& count) : m_count(count), m_depth(0)
1814   {
1815     m_count = 0;
1816   }
1817   bool pre(const scene::Path& path, scene::Instance& instance) const
1818   {
1819     if(++m_depth != 1 && path.top().get().isRoot())
1820     {
1821       return false;
1822     }
1823     Selectable* selectable = Instance_getSelectable(instance);
1824     if(selectable != 0
1825       && selectable->isSelected()
1826       && Node_isPrimitive(path.top()))
1827     {
1828       ++m_count;
1829     }
1830     return true;
1831   }
1832   void post(const scene::Path& path, scene::Instance& instance) const
1833   {
1834     --m_depth;
1835   }
1836 };
1837
1838 std::size_t Scene_countSelectedBrushes(scene::Graph& graph)
1839 {
1840   std::size_t count;
1841   graph.traverse(CountSelectedBrushes(count));
1842   return count;
1843 }
1844
1845 enum ENodeType
1846 {
1847   eNodeUnknown,
1848   eNodeMap,
1849   eNodeEntity,
1850   eNodePrimitive,
1851 };
1852
1853 const char* nodetype_get_name(ENodeType type)
1854 {
1855   if(type == eNodeMap)
1856     return "map";
1857   if(type == eNodeEntity)
1858     return "entity";
1859   if(type == eNodePrimitive)
1860     return "primitive";
1861   return "unknown";
1862 }
1863
1864 ENodeType node_get_nodetype(scene::Node& node)
1865 {
1866   if(Node_isEntity(node))
1867   {
1868     return eNodeEntity;
1869   }
1870   if(Node_isPrimitive(node))
1871   {
1872     return eNodePrimitive;
1873   }
1874   return eNodeUnknown;
1875 }
1876
1877 bool contains_entity(scene::Node& node)
1878 {
1879   return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1880 }
1881
1882 bool contains_primitive(scene::Node& node)
1883 {
1884   return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1885 }
1886
1887 ENodeType node_get_contains(scene::Node& node)
1888 {
1889   if(contains_entity(node))
1890   {
1891     return eNodeEntity;
1892   }
1893   if(contains_primitive(node))
1894   {
1895     return eNodePrimitive;
1896   }
1897   return eNodeUnknown;
1898 }
1899
1900 void Path_parent(const scene::Path& parent, const scene::Path& child)
1901 {
1902   ENodeType contains = node_get_contains(parent.top());
1903   ENodeType type = node_get_nodetype(child.top());
1904
1905   if(contains != eNodeUnknown && contains == type)
1906   {
1907     NodeSmartReference node(child.top().get());
1908     Path_deleteTop(child);
1909     Node_getTraversable(parent.top())->insert(node);
1910     SceneChangeNotify();
1911   }
1912   else
1913   {
1914     globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to " << nodetype_get_name(contains) << " container.\n";
1915   }
1916 }
1917
1918 void Scene_parentSelected()
1919 {
1920   UndoableCommand undo("parentSelected");
1921
1922   if(GlobalSelectionSystem().countSelected() > 1)
1923   {
1924     class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1925     {
1926       const scene::Path& m_parent;
1927     public:
1928       ParentSelectedBrushesToEntityWalker(const scene::Path& parent) : m_parent(parent)
1929       {
1930       }
1931       void visit(scene::Instance& instance) const
1932       {
1933         if(&m_parent != &instance.path())
1934         {
1935           Path_parent(m_parent, instance.path());
1936         }
1937       }
1938     };
1939     
1940     ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1941     GlobalSelectionSystem().foreachSelected(visitor);
1942   }
1943   else
1944   {
1945     globalOutputStream() << "failed - did not find two selected nodes.\n";
1946   }
1947 }
1948
1949
1950
1951 void NewMap()
1952 {
1953   if (ConfirmModified("New Map"))
1954   {
1955     Map_RegionOff();
1956           Map_Free();
1957     Map_New();
1958   }
1959 }
1960
1961 CopiedString g_mapsPath;
1962
1963 const char* getMapsPath()
1964 {
1965   return g_mapsPath.c_str();
1966 }
1967
1968 const char* map_open(const char* title)
1969 {
1970   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), TRUE, title, getMapsPath(), MapFormat::Name(), true, false, false);
1971 }
1972
1973 const char* map_import(const char* title)
1974 {
1975   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), TRUE, title, getMapsPath(), MapFormat::Name(), false, true, false);
1976 }
1977
1978 const char* map_save(const char* title)
1979 {
1980   return file_dialog(GTK_WIDGET(MainFrame_getWindow()), FALSE, title, getMapsPath(), MapFormat::Name(), false, false, true);
1981 }
1982
1983 void OpenMap()
1984 {
1985   if (!ConfirmModified("Open Map"))
1986     return;
1987
1988   const char* filename = map_open("Open Map");
1989
1990   if (filename != 0)
1991   {
1992     MRU_AddFile(filename);
1993     Map_RegionOff();
1994     Map_Free();
1995     Map_LoadFile(filename);
1996   }
1997 }
1998
1999 void ImportMap()
2000 {
2001   const char* filename = map_import("Import Map");
2002
2003   if(filename != 0)
2004   {
2005     UndoableCommand undo("mapImport");
2006     Map_ImportFile(filename);
2007   }
2008 }
2009
2010 bool Map_SaveAs()
2011 {
2012   const char* filename = map_save("Save Map");
2013   
2014   if(filename != 0)
2015   {
2016     MRU_AddFile(filename);
2017     Map_Rename(filename);
2018     return Map_Save();
2019   }
2020   return false;
2021 }
2022
2023 void SaveMapAs()
2024 {
2025   Map_SaveAs();
2026 }
2027
2028 void SaveMap()
2029 {
2030   if(Map_Unnamed(g_map))
2031   {
2032     SaveMapAs();
2033   }
2034   else if(Map_Modified(g_map))
2035   {
2036     Map_Save();
2037   }
2038 }
2039
2040 void ExportMap()
2041 {
2042   const char* filename = map_save("Export Selection");
2043
2044   if(filename != 0)
2045   {
2046     Map_SaveSelected(filename);
2047   }
2048 }
2049
2050 void SaveRegion()
2051 {
2052   const char* filename = map_save("Export Region");
2053   
2054   if(filename != 0)
2055   {
2056     Map_SaveRegion(filename);
2057   }
2058 }
2059
2060
2061 void RegionOff()
2062 {
2063   Map_RegionOff();
2064   SceneChangeNotify();
2065 }
2066
2067 void RegionXY()
2068 {
2069   Map_RegionXY(
2070     g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2071     g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2072     g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2073     g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2074     );
2075   SceneChangeNotify();
2076 }
2077
2078 void RegionBrush()
2079 {
2080   Map_RegionBrush();
2081   SceneChangeNotify();
2082 }
2083
2084 void RegionSelected()
2085 {
2086   Map_RegionSelectedBrushes();
2087   SceneChangeNotify();
2088 }
2089
2090
2091
2092
2093
2094 class BrushFindByIndexWalker : public scene::Traversable::Walker
2095 {
2096   mutable std::size_t m_index;
2097   scene::Path& m_path;
2098 public:
2099   BrushFindByIndexWalker(std::size_t index, scene::Path& path)
2100     : m_index(index), m_path(path)
2101   {
2102   }
2103   bool pre(scene::Node& node) const
2104   {
2105     if(Node_isPrimitive(node) && m_index-- == 0)
2106     {
2107       m_path.push(makeReference(node));
2108     }
2109     return false;
2110   }
2111 };
2112
2113 class EntityFindByIndexWalker : public scene::Traversable::Walker
2114 {
2115   mutable std::size_t m_index;
2116   scene::Path& m_path;
2117 public:
2118   EntityFindByIndexWalker(std::size_t index, scene::Path& path)
2119     : m_index(index), m_path(path)
2120   {
2121   }
2122   bool pre(scene::Node& node) const
2123   {
2124     if(Node_isEntity(node) && m_index-- == 0)
2125     {
2126       m_path.push(makeReference(node));
2127     }
2128     return false;
2129   }
2130 };
2131
2132 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path& path)
2133 {
2134   path.push(makeReference(GlobalSceneGraph().root()));
2135   {
2136     Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2137   }
2138   if(path.size() == 2)
2139   {
2140     scene::Traversable* traversable = Node_getTraversable(path.top());
2141     if(traversable != 0)
2142     {
2143       traversable->traverse(BrushFindByIndexWalker(brush, path));
2144     }
2145   }
2146 }
2147
2148 inline bool Node_hasChildren(scene::Node& node)
2149 {
2150   scene::Traversable* traversable = Node_getTraversable(node);
2151   return traversable != 0 && !traversable->empty();
2152 }
2153
2154 void SelectBrush (int entitynum, int brushnum)
2155 {
2156   scene::Path path;
2157   Scene_FindEntityBrush(entitynum, brushnum, path);
2158   if(path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top())))
2159   {
2160     scene::Instance* instance = GlobalSceneGraph().find(path);
2161     ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2162     Selectable* selectable = Instance_getSelectable(*instance);
2163     ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2164     selectable->setSelected(true);
2165     g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2166   }
2167 }
2168
2169
2170 class BrushFindIndexWalker : public scene::Graph::Walker
2171 {
2172   mutable const scene::Node* m_node;
2173   std::size_t& m_count;
2174 public:
2175   BrushFindIndexWalker(const scene::Node& node, std::size_t& count)
2176     : m_node(&node), m_count(count)
2177   {
2178   }
2179   bool pre(const scene::Path& path, scene::Instance& instance) const
2180   {
2181     if(Node_isPrimitive(path.top()))
2182     {
2183       if(m_node == path.top().get_pointer())
2184       {
2185         m_node = 0;
2186       }
2187       if(m_node)
2188       {
2189         ++m_count;
2190       }
2191     }
2192     return true;
2193   }
2194 };
2195
2196 class EntityFindIndexWalker : public scene::Graph::Walker
2197 {
2198   mutable const scene::Node* m_node;
2199   std::size_t& m_count;
2200 public:
2201   EntityFindIndexWalker(const scene::Node& node, std::size_t& count)
2202     : m_node(&node), m_count(count)
2203   {
2204   }
2205   bool pre(const scene::Path& path, scene::Instance& instance) const
2206   {
2207     if(Node_isEntity(path.top()))
2208     {
2209       if(m_node == path.top().get_pointer())
2210       {
2211         m_node = 0;
2212       }
2213       if(m_node)
2214       {
2215         ++m_count;
2216       }
2217     }
2218     return true;
2219   }
2220 };
2221
2222 static void GetSelectionIndex (int *ent, int *brush)
2223 {
2224   std::size_t count_brush = 0;
2225   std::size_t count_entity = 0;
2226   if(GlobalSelectionSystem().countSelected() != 0)
2227   {
2228     const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2229
2230     GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2231     GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2232   }
2233   *brush = int(count_brush);
2234   *ent = int(count_entity);
2235 }
2236
2237 void DoFind()
2238 {
2239   ModalDialog dialog;
2240   GtkEntry* entity;
2241   GtkEntry* brush;
2242
2243   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Find Brush", G_CALLBACK(dialog_delete_callback), &dialog);
2244
2245   GtkAccelGroup* accel = gtk_accel_group_new();
2246   gtk_window_add_accel_group(window, accel);
2247
2248   {
2249     GtkVBox* vbox = create_dialog_vbox(4, 4);
2250     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
2251     {
2252       GtkTable* table = create_dialog_table(2, 2, 4, 4);
2253       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), TRUE, TRUE, 0);
2254       {
2255         GtkWidget* label = gtk_label_new ("Entity number");
2256         gtk_widget_show (label);
2257         gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
2258                           (GtkAttachOptions) (0),
2259                           (GtkAttachOptions) (0), 0, 0);
2260       }
2261       {
2262         GtkWidget* label = gtk_label_new ("Brush number");
2263         gtk_widget_show (label);
2264         gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
2265                           (GtkAttachOptions) (0),
2266                           (GtkAttachOptions) (0), 0, 0);
2267       }
2268       {
2269         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
2270         gtk_widget_show(GTK_WIDGET(entry));
2271         gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1,
2272                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
2273                           (GtkAttachOptions) (0), 0, 0);
2274         gtk_widget_grab_focus(GTK_WIDGET(entry));
2275         entity = entry;
2276       }
2277       {
2278         GtkEntry* entry = GTK_ENTRY(gtk_entry_new());
2279         gtk_widget_show(GTK_WIDGET(entry));
2280         gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2,
2281                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
2282                           (GtkAttachOptions) (0), 0, 0);
2283
2284         brush = entry;
2285       }
2286     }
2287     {
2288       GtkHBox* hbox = create_dialog_hbox(4);
2289       gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0);
2290       {
2291         GtkButton* button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2292         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
2293         widget_make_default(GTK_WIDGET(button));
2294         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
2295       }
2296       {
2297         GtkButton* button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2298         gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
2299         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
2300       }
2301     }
2302   }
2303
2304   // Initialize dialog
2305   char buf[16];
2306   int ent, br;
2307
2308   GetSelectionIndex (&ent, &br);
2309   sprintf (buf, "%i", ent);
2310   gtk_entry_set_text(entity, buf);
2311   sprintf (buf, "%i", br);
2312   gtk_entry_set_text(brush, buf);
2313
2314   if(modal_dialog_show(window, dialog) == eIDOK)
2315   {
2316     const char *entstr = gtk_entry_get_text(entity);
2317     const char *brushstr = gtk_entry_get_text(brush);
2318     SelectBrush (atoi(entstr), atoi(brushstr));
2319   }
2320
2321   gtk_widget_destroy(GTK_WIDGET(window));
2322 }
2323
2324 void Map_constructPreferences(PreferencesPage& page)
2325 {
2326   page.appendCheckBox("", "Load last map on open", g_bLoadLastMap);
2327 }
2328
2329
2330 class MapEntityClasses : public ModuleObserver
2331 {
2332   std::size_t m_unrealised;
2333 public:
2334   MapEntityClasses() : m_unrealised(1)
2335   {
2336   }
2337   void realise()
2338   {
2339     if(--m_unrealised == 0)
2340     {
2341       if(g_map.m_resource != 0)
2342       {
2343         ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2344               g_map.m_resource->realise();
2345       }
2346     }
2347   }
2348   void unrealise()
2349   {
2350     if(++m_unrealised == 1)
2351     {
2352       if(g_map.m_resource != 0)
2353       {
2354         g_map.m_resource->flush();
2355               g_map.m_resource->unrealise();
2356       }
2357     }
2358   }
2359 };
2360
2361 MapEntityClasses g_MapEntityClasses;
2362
2363
2364 class MapModuleObserver : public ModuleObserver
2365 {
2366   std::size_t m_unrealised;
2367 public:
2368   MapModuleObserver() : m_unrealised(1)
2369   {
2370   }
2371   void realise()
2372   {
2373     if(--m_unrealised == 0)
2374     {
2375       ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()), "maps_directory: user-game-path is empty");
2376       StringOutputStream buffer(256);
2377       buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2378       Q_mkdir(buffer.c_str());
2379       g_mapsPath = buffer.c_str();
2380     }
2381   }
2382   void unrealise()
2383   {
2384     if(++m_unrealised == 1)
2385     {
2386       g_mapsPath = "";
2387     }
2388   }
2389 };
2390
2391 MapModuleObserver g_MapModuleObserver;
2392
2393 #include "preferencesystem.h"
2394
2395 CopiedString g_strLastMap;
2396 bool g_bLoadLastMap = false;
2397
2398 void Map_Construct()
2399 {
2400   GlobalCommands_insert("RegionOff", FreeCaller<RegionOff>());
2401   GlobalCommands_insert("RegionSetXY", FreeCaller<RegionXY>());
2402   GlobalCommands_insert("RegionSetBrush", FreeCaller<RegionBrush>());
2403   GlobalCommands_insert("RegionSetSelection", FreeCaller<RegionSelected>(), Accelerator('R', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK)));
2404
2405   GlobalPreferenceSystem().registerPreference("LastMap", CopiedStringImportStringCaller(g_strLastMap), CopiedStringExportStringCaller(g_strLastMap));
2406   GlobalPreferenceSystem().registerPreference("LoadLastMap", BoolImportStringCaller(g_bLoadLastMap), BoolExportStringCaller(g_bLoadLastMap));
2407   GlobalPreferenceSystem().registerPreference("MapInfoDlg", WindowPositionImportStringCaller(g_posMapInfoWnd), WindowPositionExportStringCaller(g_posMapInfoWnd));
2408   
2409   PreferencesDialog_addSettingsPreferences(FreeCaller1<PreferencesPage&, Map_constructPreferences>());
2410
2411   GlobalEntityClassManager().attach(g_MapEntityClasses);
2412   Radiant_attachHomePathsObserver(g_MapModuleObserver);
2413 }
2414
2415 void Map_Destroy()
2416 {
2417   Radiant_detachHomePathsObserver(g_MapModuleObserver);
2418   GlobalEntityClassManager().detach(g_MapEntityClasses);
2419 }