Skip to content

Instantly share code, notes, and snippets.

@mbasaglia
Created September 14, 2015 12:13
Show Gist options
  • Save mbasaglia/0f9a9d1a9e90eb18c5c1 to your computer and use it in GitHub Desktop.
Save mbasaglia/0f9a9d1a9e90eb18c5c1 to your computer and use it in GitHub Desktop.
Property Tree for Qt
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2015 Mattia Basaglia
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PIXEL_CAYMAN_MISC_PROPERTY_TREE_HPP
#define PIXEL_CAYMAN_MISC_PROPERTY_TREE_HPP
#include <QVariant>
#include <stdexcept>
namespace misc {
/**
* \brief Tree structure to store properties in a tree structure
*/
class PropertyTree
{
public:
/**
* \brief Exception class
*/
class Error : std::runtime_error
{
public:
Error(const QString& msg)
: runtime_error(msg.toStdString())
{}
};
typedef QMap<QString, PropertyTree>::iterator iterator;
typedef QMap<QString, PropertyTree>::const_iterator const_iterator;
/**
* \brief Build a single node
*/
explicit PropertyTree(QVariant value = {})
: data_(value)
{}
/**
* \brief Construct a full tree
*/
PropertyTree(const std::initializer_list<QPair<QString,PropertyTree>>& il,
QVariant value = {})
: data_(value), children_(il)
{
for ( auto& child : children_ )
child.parent_ = this;
}
PropertyTree(const PropertyTree& rhs)
: data_(rhs.data_), children_(rhs.children_)
{
fixParent(rhs.parent_);
}
PropertyTree(PropertyTree&& rhs)
: data_(std::move(rhs.data_)), children_(std::move(rhs.children_))
{
fixParent(nullptr);
}
PropertyTree& operator=(const PropertyTree& rhs)
{
data_ = rhs.data_;
children_ = rhs.children_;
fixParent(parent_);
return *this;
}
PropertyTree& operator=(PropertyTree&& rhs)
{
data_ = std::move(rhs.data_);
children_ = std::move(rhs.children_);
fixParent(parent_);
return *this;
}
/**
* \brief Data for this node
*/
QVariant data() const
{
data_;
}
/**
* \brief Set the data for this node
*/
void setData(const QVariant& variant)
{
data_ = variant;
}
/**
* \brief Get data as a specific type
*/
template<class T>
T get() const
{
return data_.value<T>();
}
/**
* \brief Set data as a specific type
*/
template <class T>
void put(T&& value)
{
data_.setValue<typename std::remove_reference<T>::type>(std::forward<T>(value));
}
/**
* \brief Find a child node
* \param path The path to the child (keys separated by periods)
* \throws Error if the child doesn't exist
*/
PropertyTree& child(const QString& path)
{
if ( auto child = traverse(path) )
return const_cast<PropertyTree&>(*child);
throw Error("Path not found: "+path);
}
/**
* \brief Find a child node
* \param path The path to the child (keys separated by periods)
* \throws Error if the child doesn't exist
*/
const PropertyTree& child(const QString& path) const
{
if ( auto child = traverse(path) )
return *child;
throw Error("Path not found: "+path);
}
/**
* \brief Whether a child exists
* \param path The path to the child (keys separated by periods)
*/
bool contains(const QString& path) const
{
return traverse(path);
}
/**
* \brief Get a value
* \param path The path to the child (keys separated by periods)
* \throws Error if the child doesn't exist
*/
template<class T>
T get(const QString& path) const
{
return child(path).get<T>();
}
/**
* \brief Get a value
* \param path The path to the child (keys separated by periods)
* \param default_value Value returned in the event the child doesn't exist
*/
template<class T>
T get(const QString& path, const T& default_value) const
{
if ( auto child = traverse(path) )
return child->get<T>();
return default_value;
}
/**
* \brief Set a value
* \param path The path to the child (keys separated by periods)
* \param value Value to set
*
* It will create all missing intermediate nodes
*/
template <class T>
void put(const QString& path, T&& value)
{
traverseCreate(path)->put(std::forward<T>(value));
}
/**
* \brief Removes a node
* \param path The path to the child (keys separated by periods)
* \param remove_empty_ancestors Whether to recursively remove empty
* ancestors of the removed node
*
* \p path can be empty (pointing to the current node) as long as the node
* isn't the root of the tree.
* \throws Error if \p path points to the root of the tree
*/
void remove(const QString& path={}, bool remove_empty_ancestors = false)
{
auto child = traverse(path);
if ( !child )
return;
if ( !child->parent_ )
throw Error("Cannot remove the root of the tree");
child->parent_->removeChild(child);
}
/**
* \brief Path from the root to the current node
*/
QString path() const
{
if ( !parent_ )
return "";
return parent_->path()+"."+parent_->key(this);
}
iterator begin()
{
return children_.begin();
}
iterator end()
{
return children_.end();
}
iterator begin() const
{
return children_.begin();
}
iterator end() const
{
return children_.end();
}
iterator cbegin() const
{
return children_.begin();
}
iterator cend() const
{
return children_.end();
}
int size() const
{
return children_.size();
}
iterator find(const QString& key)
{
return children_.find(key);
}
iterator erase(const iterator& iter)
{
return children_.erase(iter);
}
private:
/**
* \brief Builds a child node
*/
explicit PropertyTree(PropertyTree* parent)
: parent_(parent)
{}
const PropertyTree* traverse(const QString& path) const
{
return traverse(path.split("."));
}
/**
* \brief Traverses the path recursively
* \return The node corresponding to the path or \b nullptr if not found
*/
const PropertyTree* traverse(const QStringList& path) const
{
if ( path.empty() )
return this;
auto it = children_.find(path.front());
if ( !it )
return nullptr;
path.pop_front();
return it->traverse(path);
}
PropertyTree* traverseCreate(const QString& path)
{
return traverseCreate(path.split("."));
}
/**
* \brief Traverses the path recursively creating missing nodes
*/
PropertyTree* traverseCreate(const QStringList& path)
{
if ( path.empty() )
return this;
auto it = children_.find(path.front());
if ( !it )
it = children_.insert(PropertyTree(this));
path.pop_front();
return it->traverse(path);
}
/**
* \brief Assigns the parent recursively
*/
void fixParent(PropertyTree* parent)
{
parent_ = parent;
for ( auto& child : children_ )
child.fixParent(this);
}
/**
* \brief Removes a direct child
*
* If \p remove_empty_ancestors is \b true, it will recurse up and remove
* unneeded nodes (possibly including this)
*/
void removeChild(PropertyTree* child, bool remove_empty_ancestors = false)
{
for ( int i = children_.begin(); i != children_.end(); ++i )
if ( &i->value == child )
{
children_.erase(i);
if ( remove_empty_ancestors && children_.empty() && parent_ )
parent_->removeChild(this);
return;
}
}
/**
* \brief Key for a direct child
*/
QString key(const PropertyTree* child) const
{
for ( int i = children_.begin(); i != children_.end(); ++i )
if ( &i->value == child )
return i->key;
return {};
}
QVariant data_;
QMap<QString, PropertyTree> children_;
PropertyTree* parent_ = nullptr;
};
} // namespace misc
#endif // PIXEL_CAYMAN_MISC_PROPERTY_TREE_HPP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment