Index: ps/trunk/source/graphics/ColladaManager.h
===================================================================
--- ps/trunk/source/graphics/ColladaManager.h (revision 13418)
+++ ps/trunk/source/graphics/ColladaManager.h (revision 13419)
@@ -1,76 +1,78 @@
-/* Copyright (C) 2012 Wildfire Games.
+/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
#ifndef INCLUDED_COLLADAMANAGER
#define INCLUDED_COLLADAMANAGER
#include "lib/file/vfs/vfs.h"
class CStr8;
class CColladaManagerImpl;
class MD5;
class CColladaManager
{
+ NONCOPYABLE(CColladaManager);
+
public:
enum FileType { PMD, PSA };
CColladaManager(const PIVFS& vfs);
~CColladaManager();
/**
* Returns the VFS path to a PMD/PSA file for the given source file.
* Performs a (cached) conversion from COLLADA if necessary.
*
* @param pathnameNoExtension path and name, minus extension, of file to load.
* One of either "sourceName.pmd" or "sourceName.dae" should exist.
* @param type FileType, .pmd or .psa
*
* @return full VFS path (including extension) of file to load; or empty
* string if there was a problem and it could not be loaded. Doesn't knowingly
* return an invalid path.
*/
VfsPath GetLoadablePath(const VfsPath& pathnameNoExtension, FileType type);
/**
* Converts DAE to archive cached .pmd/psa and outputs the resulting path
* (used by archive builder)
*
* @param[in] sourcePath path of the .dae to load
* @param[in] type FileType, .pmd or .psa
* @param[out] archiveCachePath output path of the cached file
*
* @return true if COLLADA converter completed successfully; or false if it failed
*/
bool GenerateCachedFile(const VfsPath& sourcePath, FileType type, VfsPath& archiveCachePath);
private:
/**
* Creates MD5 hash key from skeletons.xml info and COLLADA converter version,
* used to invalidate cached .pmd/psas
*
* @param[out] hash resulting MD5 hash
* @param[out] version version passed to CCacheLoader, used if code change should force
* cache invalidation
*/
void PrepareCacheKey(MD5& hash, u32& version);
CColladaManagerImpl* m;
PIVFS m_VFS;
};
#endif // INCLUDED_COLLADAMANAGER
Index: ps/trunk/source/graphics/MapGenerator.h
===================================================================
--- ps/trunk/source/graphics/MapGenerator.h (revision 13418)
+++ ps/trunk/source/graphics/MapGenerator.h (revision 13419)
@@ -1,145 +1,146 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
#ifndef INCLUDED_MAPGENERATOR
#define INCLUDED_MAPGENERATOR
#include "ps/FileIo.h"
#include "ps/ThreadUtil.h"
#include "scriptinterface/ScriptInterface.h"
#include
#include
class CMapGeneratorWorker;
/**
* Random map generator interface. Initialized by CMapReader and then checked
* periodically during loading, until it's finished (progress value is 0).
*
* The actual work is performed by CMapGeneratorWorker in a separate thread.
*/
class CMapGenerator
{
+ NONCOPYABLE(CMapGenerator);
public:
CMapGenerator();
~CMapGenerator();
/**
* Start the map generator thread
*
* @param scriptFile The VFS path for the script, e.g. "maps/random/latium.js"
* @param settings JSON string containing settings for the map generator
*/
void GenerateMap(const VfsPath& scriptFile, const std::string& settings);
/**
* Get status of the map generator thread
*
* @return Progress percentage 1-100 if active, 0 when finished, or -1 on error
*/
int GetProgress();
/**
* Get random map data, according to this format:
* http://trac.wildfiregames.com/wiki/Random_Map_Generator_Internals#Dataformat
*
* @return StructuredClone containing map data
*/
shared_ptr GetResults();
private:
CMapGeneratorWorker* m_Worker;
};
/**
* Random map generator worker thread.
* (This is run in a thread so that the GUI remains responsive while loading)
*
* Thread-safety:
* - Initialize and constructor/destructor must be called from the main thread.
* - ScriptInterface created and destroyed by thread
* - StructuredClone used to return JS map data - jsvals can't be used across threads/runtimes
*/
class CMapGeneratorWorker
{
public:
CMapGeneratorWorker();
~CMapGeneratorWorker();
/**
* Start the map generator thread
*
* @param scriptFile The VFS path for the script, e.g. "maps/random/latium.js"
* @param settings JSON string containing settings for the map generator
*/
void Initialize(const VfsPath& scriptFile, const std::string& settings);
/**
* Get status of the map generator thread
*
* @return Progress percentage 1-100 if active, 0 when finished, or -1 on error
*/
int GetProgress();
/**
* Get random map data, according to this format:
* http://trac.wildfiregames.com/wiki/Random_Map_Generator_Internals#Dataformat
*
* @return StructuredClone containing map data
*/
shared_ptr GetResults();
private:
// Mapgen
/**
* Load all scripts of the given library
*
* @param libraryName String specifying name of the library (subfolder of ../maps/random/)
* @return true if all scripts ran successfully, false if there's an error
*/
bool LoadScripts(const std::wstring& libraryName);
// callbacks for script functions
static bool LoadLibrary(void* cbdata, std::wstring name);
static void ExportMap(void* cbdata, CScriptValRooted data);
static void SetProgress(void* cbdata, int progress);
static void MaybeGC(void* cbdata);
static std::vector GetCivData(void* cbdata);
std::set m_LoadedLibraries;
shared_ptr m_MapData;
boost::rand48 m_MapGenRNG;
int m_Progress;
ScriptInterface* m_ScriptInterface;
VfsPath m_ScriptPath;
std::string m_Settings;
// Thread
static void* RunThread(void* data);
bool Run();
pthread_t m_WorkerThread;
CMutex m_WorkerMutex;
};
#endif //INCLUDED_MAPGENERATOR
Index: ps/trunk/source/graphics/TextureManager.h
===================================================================
--- ps/trunk/source/graphics/TextureManager.h (revision 13418)
+++ ps/trunk/source/graphics/TextureManager.h (revision 13419)
@@ -1,289 +1,291 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
#ifndef INCLUDED_TEXTUREMANAGER
#define INCLUDED_TEXTUREMANAGER
#include "Texture.h"
#include "lib/ogl.h"
#include "lib/file/vfs/vfs.h"
#include "lib/res/handle.h"
#include
class CTextureProperties;
class CTextureManagerImpl;
/**
* Texture manager with asynchronous loading and automatic DDS conversion/compression.
*
* Input textures can be any format. They will be converted to DDS using settings defined
* in files named "texture.xml", in the same directory as the texture and in its parent
* directories. See CTextureConverter for the XML syntax. The DDS file will be cached
* for faster loading in the future.
*
* Typically the graphics code will initialise many textures at the start of the game,
* mostly for off-screen objects, by calling CreateTexture().
* Loading texture data may be very slow (especially if it needs to be converted
* to DDS), and we don't want the game to become unresponsive.
* CreateTexture therefore returns an object immediately, without loading the
* texture. If the object is never used then the data will never be loaded.
*
* Typically, the renderer will call CTexture::Bind() when it wants to use the
* texture. This will trigger the loading of the texture data. If it can be loaded
* quickly (i.e. there is already a cached DDS version), then it will be loaded before
* the function returns, and the texture can be rendered as normal.
*
* If loading will take a long time, then Bind() binds a default placeholder texture
* and starts loading the texture in the background. It will use the correct texture
* when the renderer next calls Bind() after the load has finished.
*
* It is also possible to prefetch textures which are not being rendered yet, but
* are expected to be rendered soon (e.g. for off-screen terrain tiles).
* These will be loaded in the background, when there are no higher-priority textures
* to load.
*
* The same texture file can be safely loaded multiple times with different GL parameters
* (but this should be avoided whenever possible, as it wastes VRAM).
*
* For release packages, DDS files can be precached by appending ".dds" to their name,
* which will be used instead of doing runtime conversion. This means most players should
* never experience the slow asynchronous conversion behaviour.
* These cache files will typically be packed into an archive for faster loading;
* if no archive cache is available then the source file will be converted and stored
* as a loose cache file on the user's disk.
*/
class CTextureManager
{
+ NONCOPYABLE(CTextureManager);
+
public:
/**
* Construct texture manager. vfs must be the VFS instance used for all textures
* loaded from this object.
* highQuality is slower and intended for batch-conversion modes.
* disableGL is intended for tests, and will disable all GL uploads.
*/
CTextureManager(PIVFS vfs, bool highQuality, bool disableGL);
~CTextureManager();
/**
* Create a texture with the given GL properties.
* The texture data will not be loaded immediately.
*/
CTexturePtr CreateTexture(const CTextureProperties& props);
/**
* Returns a magenta texture. Use this for highlighting errors
* (e.g. missing terrain textures).
*/
CTexturePtr GetErrorTexture();
/**
* Work on asynchronous texture loading operations, if any.
* Returns true if it did any work.
* The caller should typically loop this per frame until it returns
* false or exceeds the allocated time for this frame.
*/
bool MakeProgress();
/**
* Synchronously converts and compresses and saves the texture,
* and returns the output path (minus a "cache/" prefix). This
* is intended for pre-caching textures in release archives.
* @return true on success
*/
bool GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath);
private:
CTextureManagerImpl* m;
};
/**
* Represents the filename and GL parameters of a texture,
* for passing to CTextureManager::CreateTexture.
*/
class CTextureProperties
{
friend class CTextureManagerImpl;
friend struct TextureCacheCmp;
friend struct TPequal_to;
friend struct TPhash;
public:
/**
* Use the given texture name, and default GL parameters.
*/
explicit CTextureProperties(const VfsPath& path) :
m_Path(path), m_Filter(GL_LINEAR_MIPMAP_LINEAR),
m_WrapS(GL_REPEAT), m_WrapT(GL_REPEAT), m_Aniso(1.0f)
{
}
/**
* Set min/mag filter mode (typically GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST, etc).
*/
void SetFilter(GLint filter) { m_Filter = filter; }
/**
* Set wrapping mode (typically GL_REPEAT, GL_CLAMP_TO_EDGE, etc).
*/
void SetWrap(GLint wrap) { m_WrapS = wrap; m_WrapT = wrap; }
/**
* Set wrapping mode (typically GL_REPEAT, GL_CLAMP_TO_EDGE, etc),
* separately for S and T.
*/
void SetWrap(GLint wrap_s, GLint wrap_t) { m_WrapS = wrap_s; m_WrapT = wrap_t; }
/**
* Set maximum anisotropy value. Must be >= 1.0. Should be a power of 2.
*/
void SetMaxAnisotropy(float aniso) { m_Aniso = aniso; }
// TODO: rather than this static definition of texture properties
// (especially anisotropy), maybe we want something that can be more
// easily tweaked in an Options menu? e.g. the caller just specifies
// "terrain texture mode" and we combine it with the user's options.
// That'd let us dynamically change texture properties easily.
//
// enum EQualityMode
// {
// NONE,
// TERRAIN,
// MODEL,
// GUI
// }
// void SetQuality(EQualityMode mode, float anisotropy, float lodbias, int reducemipmaps, ...);
//
// or something a bit like that.
private:
VfsPath m_Path;
GLint m_Filter;
GLint m_WrapS;
GLint m_WrapT;
float m_Aniso;
};
/**
* Represents a texture object.
* The texture data may or may not have been loaded yet.
* Before it has been loaded, all operations will act on a default
* 1x1-pixel grey texture instead.
*/
class CTexture
{
friend class CTextureManagerImpl;
friend struct TextureCacheCmp;
friend struct TPequal_to;
friend struct TPhash;
// Only the texture manager can create these
explicit CTexture(Handle handle, const CTextureProperties& props, CTextureManagerImpl* textureManager);
NONCOPYABLE(CTexture);
public:
~CTexture();
/**
* Returns the width (in pixels) of the current texture.
*/
size_t GetWidth() const;
/**
* Returns the height (in pixels) of the current texture.
*/
size_t GetHeight() const;
/**
* Returns whether the current texture has an alpha channel.
*/
bool HasAlpha() const;
/**
* Returns the ARGB value of the lowest mipmap level (i.e. the
* average of the whole texture).
* Returns 0 if the texture has no mipmaps.
*/
u32 GetBaseColour() const;
/**
* Bind the texture to the given GL texture unit.
* If the texture data hasn't been loaded yet, this may wait a short while to
* load it. If loading takes too long then it will return sooner and the data will
* be loaded in a background thread, so this does not guarantee the texture really
* will be loaded.
*/
void Bind(size_t unit = 0);
/**
* Returns a ogl_tex handle, for later binding. See comments from Bind().
*/
Handle GetHandle();
/**
* Attempt to load the texture data quickly, as with Bind().
* Returns whether the texture data is currently loaded.
*/
bool TryLoad();
/**
* Returns whether the texture data is currently loaded.
*/
bool IsLoaded();
/**
* Activate the prefetching optimisation for this texture.
* Use this if it is likely the texture will be needed in the near future.
* It will be loaded in the background so that it is likely to be ready when
* it is used by Bind().
*/
void Prefetch();
private:
/**
* Replace the Handle stored by this object.
* If takeOwnership is true, it will not increment the Handle's reference count.
*/
void SetHandle(Handle handle, bool takeOwnership = false);
const CTextureProperties m_Properties;
Handle m_Handle;
u32 m_BaseColour;
enum {
UNLOADED, // loading has not started
PREFETCH_NEEDS_LOADING, // was prefetched; currently waiting to try loading from cache
PREFETCH_NEEDS_CONVERTING, // was prefetched; currently waiting to be sent to the texture converter
PREFETCH_IS_CONVERTING, // was prefetched; currently being processed by the texture converter
HIGH_NEEDS_CONVERTING, // high-priority; currently waiting to be sent to the texture converter
HIGH_IS_CONVERTING, // high-priority; currently being processed by the texture converter
LOADED // loading has completed (successfully or not)
} m_State;
CTextureManagerImpl* m_TextureManager;
// Self-reference to let us recover the CTexturePtr for this object.
// (weak pointer to avoid cycles)
boost::weak_ptr m_Self;
};
#endif // INCLUDED_TEXTUREMANAGER
Index: ps/trunk/source/gui/CGUI.h
===================================================================
--- ps/trunk/source/gui/CGUI.h (revision 13418)
+++ ps/trunk/source/gui/CGUI.h (revision 13419)
@@ -1,683 +1,685 @@
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
/*
CGUI
--Overview--
This is the top class of the whole GUI, all objects
and settings are stored within this class.
--More info--
Check GUI.h
*/
#ifndef INCLUDED_CGUI
#define INCLUDED_CGUI
//--------------------------------------------------------
// Includes / Compiler directives
//--------------------------------------------------------
// NOTE: GUI.h included at the bottom of this file (has to be after CGUI class
// definition)
#include "GUITooltip.h"
#include "GUIbase.h"
#include "ps/Overlay.h" // CPos and CRect
#include "lib/input.h"
#include "ps/XML/Xeromyces.h"
#include
//--------------------------------------------------------
// Macros
//--------------------------------------------------------
//--------------------------------------------------------
// Types
//--------------------------------------------------------
//--------------------------------------------------------
// Error declarations
//--------------------------------------------------------
ERROR_TYPE(GUI, JSOpenFailed);
//--------------------------------------------------------
// Declarations
//--------------------------------------------------------
/**
* Contains a list of values for new defaults to objects.
*/
struct SGUIStyle
{
// A list of defaults for
std::map m_SettingsDefaults;
};
struct JSObject; // The GUI stores a JSObject*, so needs to know that JSObject exists
class IGUIObject;
class CGUISpriteInstance;
struct SGUIText;
struct CColor;
struct SGUIText;
struct SGUIIcon;
class CGUIString;
class CGUISprite;
struct SGUIImageEffects;
struct SGUIScrollBarStyle;
class GUITooltip;
/**
* The main object that represents a whole GUI page.
*
* No interfacial functions throws.
*/
class CGUI
{
+ NONCOPYABLE(CGUI);
+
friend class IGUIObject;
friend class IGUIScrollBarOwner;
friend class CInternalCGUIAccessorBase;
private:
// Private typedefs
typedef IGUIObject *(*ConstructObjectFunction)();
public:
CGUI();
~CGUI();
/**
* Initializes GUI script classes
*/
static void ScriptingInit();
/**
* Initializes the GUI, needs to be called before the GUI is used
*/
void Initialize();
/**
* Performs processing that should happen every frame
* (including sending the "Tick" event to scripts)
*/
void TickObjects();
/**
* Sends a specified script event to every object
*
* @param EventName String representation of event name
*/
void SendEventToAll(const CStr& EventName);
/**
* Displays the whole GUI
*/
void Draw();
/**
* Draw GUI Sprite
*
* @param Sprite Object referring to the sprite (which also caches
* calculations for faster rendering)
* @param CellID Number of the icon cell to use. (Ignored if this sprite doesn't
* have any images with "cell-size")
* @param Z Drawing order, depth value
* @param Rect Position and Size
* @param Clipping The sprite shouldn't be drawn outside this rectangle
*/
void DrawSprite(const CGUISpriteInstance& Sprite, int CellID, const float &Z,
const CRect &Rect, const CRect &Clipping=CRect());
/**
* Draw a SGUIText object
*
* @param Text Text object.
* @param DefaultColor Color used if no tag applied.
* @param pos position
* @param z z value.
* @param clipping
*/
void DrawText(SGUIText &Text, const CColor &DefaultColor,
const CPos &pos, const float &z, const CRect &clipping);
/**
* Clean up, call this to clean up all memory allocated
* within the GUI.
*/
void Destroy();
/**
* The replacement of Process(), handles an SDL_Event_
*
* @param ev SDL Event, like mouse/keyboard input
*/
InReaction HandleEvent(const SDL_Event_* ev);
/**
* Load a GUI XML file into the GUI.
*
* VERY IMPORTANT! All \-files must be read before
* everything else!
*
* @param Filename Name of file
* @param Paths Set of paths; all XML and JS files loaded will be added to this
*/
void LoadXmlFile(const VfsPath& Filename, boost::unordered_set& Paths);
/**
* Checks if object exists and return true or false accordingly
*
* @param Name String name of object
* @return true if object exists
*/
bool ObjectExists(const CStr& Name) const;
/**
* Returns the GUI object with the desired name, or NULL
* if no match is found,
*
* @param Name String name of object
* @return Matching object, or NULL
*/
IGUIObject* FindObjectByName(const CStr& Name) const;
/**
* Returns the GUI object under the mouse, or NULL if none.
*/
IGUIObject* FindObjectUnderMouse() const;
/**
* The GUI needs to have all object types inputted and
* their constructors. Also it needs to associate a type
* by a string name of the type.
*
* To add a type:
* @code
* AddObjectType("button", &CButton::ConstructObject);
* @endcode
*
* @param str Reference name of object type
* @param pFunc Pointer of function ConstuctObject() in the object
*
* @see CGUI#ConstructObject()
*/
void AddObjectType(const CStr& str, ConstructObjectFunction pFunc) { m_ObjectTypes[str] = pFunc; }
/**
* Update Resolution, should be called every time the resolution
* of the OpenGL screen has been changed, this is because it needs
* to re-cache all its actual sizes
*
* Needs no input since screen resolution is global.
*
* @see IGUIObject#UpdateCachedSize()
*/
void UpdateResolution();
/**
* Generate a SGUIText object from the inputted string.
* The function will break down the string and its
* tags to calculate exactly which rendering queries
* will be sent to the Renderer. Also, horizontal alignment
* is taken into acount in this method but NOT vertical alignment.
*
* Done through the CGUI since it can communicate with
*
* @param Text Text to generate SGUIText object from
* @param Font Default font, notice both Default color and default font
* can be changed by tags.
* @param Width Width, 0 if no word-wrapping.
* @param BufferZone space between text and edge, and space between text and images.
* @param pObject Optional parameter for error output. Used *only* if error parsing fails,
* and we need to be able to output which object the error occured in to aid the user.
*/
SGUIText GenerateText(const CGUIString &Text, const CStrW& Font,
const float &Width, const float &BufferZone,
const IGUIObject *pObject=NULL);
/**
* Returns the JSObject* associated with the GUI
*
* @return The relevant JS object
*/
JSObject* GetScriptObject() { return m_ScriptObject; }
/**
* Check if an icon exists
*/
bool IconExists(const CStr& str) const { return (m_Icons.count(str) != 0); }
/**
* Get Icon (a copy, can never be changed)
*/
SGUIIcon GetIcon(const CStr& str) const { return m_Icons.find(str)->second; }
/**
* Get pre-defined color (if it exists)
* Returns false if it fails.
*/
bool GetPreDefinedColor(const CStr& name, CColor &Output);
private:
/**
* Updates the object pointers, needs to be called each
* time an object has been added or removed.
*
* This function is atomic, meaning if it throws anything, it will
* have seen it through that nothing was ultimately changed.
*
* @throws PSERROR_GUI that is thrown from IGUIObject::AddToPointersMap().
*/
void UpdateObjects();
/**
* Adds an object to the GUI's object database
* Private, since you can only add objects through
* XML files. Why? Because it enables the GUI to
* be much more encapsulated and safe.
*
* @throws Rethrows PSERROR_GUI from IGUIObject::AddChild().
*/
void AddObject(IGUIObject* pObject);
/**
* You input the name of the object type, and let's
* say you input "button", then it will construct a
* CGUIObjet* as a CButton.
*
* @param str Name of object type
* @return Newly constructed IGUIObject (but constructed as a subclass)
*/
IGUIObject *ConstructObject(const CStr& str);
/**
* Get Focused Object.
*/
IGUIObject *GetFocusedObject() { return m_FocusedObject; }
public:
/**
* Change focus to new object.
* Will send LOST_FOCUS/GOT_FOCUS messages as appropriate.
* pObject can be NULL to remove all focus.
*/
void SetFocusedObject(IGUIObject* pObject);
private:
//--------------------------------------------------------
/** @name XML Reading Xeromyces specific subroutines
*
* These does not throw!
* Because when reading in XML files, it won't be fatal
* if an error occurs, perhaps one particular object
* fails, but it'll still continue reading in the next.
* All Error are reported with ReportParseError
*/
//--------------------------------------------------------
/*
Xeromyces_* functions tree
(ReadRootObjects)
|
+-