/*
 * Copyright (C) 2014-2016 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>

#include "gazebo/common/Console.hh"
#include "gazebo/gui/ConfigWidget.hh"
#include "gazebo/gui/ConfigWidgetPrivate.hh"

using namespace gazebo;
using namespace gui;

const std::vector<QString> ConfigWidget::bgColors(
      {"#999999", "#777777", "#555555", "#333333"});

const std::vector<QString> ConfigWidget::widgetColors(
      {"#eeeeee", "#cccccc", "#aaaaaa", "#888888"});

const QString ConfigWidget::redColor = "#d42b2b";
const QString ConfigWidget::greenColor = "#3bc43b";
const QString ConfigWidget::blueColor = "#0d0df2";

/////////////////////////////////////////////////
ConfigWidget::ConfigWidget()
  : dataPtr(new ConfigWidgetPrivate())
{
  this->dataPtr->configMsg = nullptr;
  this->setObjectName("configWidget");
}

/////////////////////////////////////////////////
ConfigWidget::~ConfigWidget()
{
  delete this->dataPtr->configMsg;
}

/////////////////////////////////////////////////
void ConfigWidget::Load(const google::protobuf::Message *_msg)
{
  this->dataPtr->configMsg = _msg->New();
  this->dataPtr->configMsg->CopyFrom(*_msg);

  QWidget *widget = this->Parse(this->dataPtr->configMsg, 0);
  QVBoxLayout *mainLayout = new QVBoxLayout;
  mainLayout->setAlignment(Qt::AlignTop);
  mainLayout->addWidget(widget);

  this->setLayout(mainLayout);

  // set up event filter for scrollable widgets to make sure they don't steal
  // focus when embedded in a QScrollArea.
  QList<QAbstractSpinBox *> spinBoxes =
      this->findChildren<QAbstractSpinBox *>();
  for (int i = 0; i < spinBoxes.size(); ++i)
  {
    spinBoxes[i]->installEventFilter(this);
    spinBoxes[i]->setFocusPolicy(Qt::StrongFocus);
  }
  QList<QComboBox *> comboBoxes =
      this->findChildren<QComboBox *>();
  for (int i = 0; i < comboBoxes.size(); ++i)
  {
    comboBoxes[i]->installEventFilter(this);
    comboBoxes[i]->setFocusPolicy(Qt::StrongFocus);
  }
}

/////////////////////////////////////////////////
void ConfigWidget::UpdateFromMsg(const google::protobuf::Message *_msg)
{
  this->dataPtr->configMsg->CopyFrom(*_msg);
  this->Parse(this->dataPtr->configMsg, true);
}

/////////////////////////////////////////////////
google::protobuf::Message *ConfigWidget::GetMsg()
{
  return this->Msg();
}

/////////////////////////////////////////////////
google::protobuf::Message *ConfigWidget::Msg()
{
  this->UpdateMsg(this->dataPtr->configMsg);
  return this->dataPtr->configMsg;
}

/////////////////////////////////////////////////
std::string ConfigWidget::GetHumanReadableKey(const std::string &_key)
{
  return this->HumanReadableKey(_key);
}

/////////////////////////////////////////////////
std::string ConfigWidget::HumanReadableKey(const std::string &_key) const
{
  std::string humanKey = _key;
  humanKey[0] = std::toupper(humanKey[0]);
  std::replace(humanKey.begin(), humanKey.end(), '_', ' ');
  return humanKey;
}

/////////////////////////////////////////////////
std::string ConfigWidget::GetUnitFromKey(const std::string &_key,
    const std::string &_jointType)
{
  return this->UnitFromKey(_key, _jointType);
}

/////////////////////////////////////////////////
std::string ConfigWidget::UnitFromKey(const std::string &_key,
    const std::string &_jointType) const
{
  if (_key == "pos" || _key == "length" || _key == "min_depth")
  {
    return "m";
  }

  if (_key == "rot")
    return "rad";

  if (_key == "kp" || _key == "kd")
    return "N/m";

  if (_key == "max_vel")
    return "m/s";

  if (_key == "mass")
    return "kg";

  if (_key == "ixx" || _key == "ixy" || _key == "ixz" ||
      _key == "iyy" || _key == "iyz" || _key == "izz")
  {
    return "kg&middot;m<sup>2</sup>";
  }

  if (_key == "limit_lower" || _key == "limit_upper")
  {
    if (_jointType == "PRISMATIC")
      return "m";
    else if (_jointType != "")
      return "rad";
  }

  if (_key == "limit_effort")
  {
    if (_jointType == "PRISMATIC")
      return "N";
    else if (_jointType != "")
      return "Nm";
  }

  if (_key == "limit_velocity" || _key == "velocity")
  {
    if (_jointType == "PRISMATIC")
      return "m/s";
    else if (_jointType != "")
      return "rad/s";
  }

  if (_key == "damping")
  {
    if (_jointType == "PRISMATIC")
      return "Ns/m";
    else if (_jointType != "")
      return "Ns";
  }

  if (_key == "friction")
  {
    if (_jointType == "PRISMATIC")
      return "N";
    else if (_jointType != "")
      return "Nm";
  }

  return "";
}

/////////////////////////////////////////////////
void ConfigWidget::GetRangeFromKey(const std::string &_key, double &_min,
    double &_max)
{
  this->RangeFromKey(_key, _min, _max);
}

/////////////////////////////////////////////////
void ConfigWidget::RangeFromKey(const std::string &_key, double &_min,
    double &_max) const
{
  // Maximum range by default
  _min = -GZ_DBL_MAX;
  _max = GZ_DBL_MAX;

  if (_key == "mass" || _key == "ixx" || _key == "ixy" || _key == "ixz" ||
      _key == "iyy" || _key == "iyz" || _key == "izz" || _key == "length" ||
      _key == "min_depth")
  {
    _min = 0;
  }
  else if (_key == "bounce" || _key == "transparency" ||
      _key == "laser_retro" || _key == "ambient" || _key == "diffuse" ||
      _key == "specular" || _key == "emissive" ||
      _key == "restitution_coefficient")
  {
    _min = 0;
    _max = 1;
  }
  else if (_key == "fdir1" || _key == "xyz")
  {
    _min = -1;
    _max = +1;
  }
}

/////////////////////////////////////////////////
bool ConfigWidget::GetWidgetVisible(const std::string &_name) const
{
  return this->WidgetVisible(_name);
}

/////////////////////////////////////////////////
bool ConfigWidget::WidgetVisible(const std::string &_name) const
{
  auto iter = this->dataPtr->configWidgets.find(_name);
  if (iter != this->dataPtr->configWidgets.end())
  {
    if (iter->second->groupWidget)
    {
      GroupWidget *groupWidget =
          qobject_cast<GroupWidget *>(iter->second->groupWidget);
      if (groupWidget)
      {
        return groupWidget->isVisible();
      }
    }
    return iter->second->isVisible();
  }
  return false;
}

/////////////////////////////////////////////////
void ConfigWidget::SetWidgetVisible(const std::string &_name, bool _visible)
{
  auto iter = this->dataPtr->configWidgets.find(_name);
  if (iter != this->dataPtr->configWidgets.end())
  {
    if (iter->second->groupWidget)
    {
      GroupWidget *groupWidget =
          qobject_cast<GroupWidget *>(iter->second->groupWidget);
      if (groupWidget)
      {
        groupWidget->setVisible(_visible);
        return;
      }
    }
    iter->second->setVisible(_visible);
  }
}

/////////////////////////////////////////////////
bool ConfigWidget::GetWidgetReadOnly(const std::string &_name) const
{
  return this->WidgetReadOnly(_name);
}

/////////////////////////////////////////////////
bool ConfigWidget::WidgetReadOnly(const std::string &_name) const
{
  auto iter = this->dataPtr->configWidgets.find(_name);
  if (iter != this->dataPtr->configWidgets.end())
  {
    if (iter->second->groupWidget)
    {
      GroupWidget *groupWidget =
          qobject_cast<GroupWidget *>(iter->second->groupWidget);
      if (groupWidget)
      {
        return !groupWidget->isEnabled();
      }
    }
    return !iter->second->isEnabled();
  }
  return false;
}

/////////////////////////////////////////////////
void ConfigWidget::SetWidgetReadOnly(const std::string &_name, bool _readOnly)
{
  auto iter = this->dataPtr->configWidgets.find(_name);
  if (iter != this->dataPtr->configWidgets.end())
  {
    if (iter->second->groupWidget)
    {
      GroupWidget *groupWidget =
          qobject_cast<GroupWidget *>(iter->second->groupWidget);
      if (groupWidget)
      {
        groupWidget->setEnabled(!_readOnly);

        // Qt docs: "Disabling a widget implicitly disables all its children.
        // Enabling respectively enables all child widgets unless they have
        // been explicitly disabled."
        auto childWidgets = groupWidget->findChildren<QWidget *>();
        for (auto widget : childWidgets)
          widget->setEnabled(!_readOnly);

        return;
      }
    }
    iter->second->setEnabled(!_readOnly);
  }
}

/////////////////////////////////////////////////
bool ConfigWidget::SetIntWidgetValue(const std::string &_name, int _value)
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return this->UpdateIntWidget(iter->second, _value);

  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::SetUIntWidgetValue(const std::string &_name,
    unsigned int _value)
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return this->UpdateUIntWidget(iter->second, _value);

  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::SetDoubleWidgetValue(const std::string &_name,
    double _value)
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return this->UpdateDoubleWidget(iter->second, _value);

  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::SetBoolWidgetValue(const std::string &_name,
    bool _value)
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return this->UpdateBoolWidget(iter->second, _value);

  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::SetStringWidgetValue(const std::string &_name,
    const std::string &_value)
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return this->UpdateStringWidget(iter->second, _value);

  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::SetVector3WidgetValue(const std::string &_name,
    const math::Vector3 &_value)
{
  return this->SetVector3dWidgetValue(_name, _value.Ign());
}

/////////////////////////////////////////////////
bool ConfigWidget::SetVector3dWidgetValue(const std::string &_name,
    const ignition::math::Vector3d &_value)
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return this->UpdateVector3dWidget(iter->second, _value);

  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::SetColorWidgetValue(const std::string &_name,
    const common::Color &_value)
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return this->UpdateColorWidget(iter->second, _value);

  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::SetPoseWidgetValue(const std::string &_name,
    const math::Pose &_value)
{
  return this->SetPoseWidgetValue(_name, _value.Ign());
}

/////////////////////////////////////////////////
bool ConfigWidget::SetPoseWidgetValue(const std::string &_name,
    const ignition::math::Pose3d &_value)
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return this->UpdatePoseWidget(iter->second, _value);

  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::SetGeometryWidgetValue(const std::string &_name,
    const std::string &_value, const math::Vector3 &_dimensions,
    const std::string &_uri)
{
  return this->SetGeometryWidgetValue(_name, _value, _dimensions.Ign(), _uri);
}

/////////////////////////////////////////////////
bool ConfigWidget::SetGeometryWidgetValue(const std::string &_name,
    const std::string &_value, const ignition::math::Vector3d &_dimensions,
    const std::string &_uri)
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return this->UpdateGeometryWidget(iter->second, _value, _dimensions, _uri);

  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::SetEnumWidgetValue(const std::string &_name,
    const std::string &_value)
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return this->UpdateEnumWidget(iter->second, _value);

  return false;
}

/////////////////////////////////////////////////
int ConfigWidget::GetIntWidgetValue(const std::string &_name) const
{
  return this->IntWidgetValue(_name);
}

/////////////////////////////////////////////////
int ConfigWidget::IntWidgetValue(const std::string &_name) const
{
  int value = 0;
  std::map <std::string, ConfigChildWidget *>::const_iterator iter =
      this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    value = this->IntWidgetValue(iter->second);
  return value;
}

/////////////////////////////////////////////////
unsigned int ConfigWidget::GetUIntWidgetValue(const std::string &_name) const
{
  return this->UIntWidgetValue(_name);
}

/////////////////////////////////////////////////
unsigned int ConfigWidget::UIntWidgetValue(const std::string &_name) const
{
  unsigned int value = 0;
  std::map <std::string, ConfigChildWidget *>::const_iterator iter =
      this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    value = this->UIntWidgetValue(iter->second);
  return value;
}

/////////////////////////////////////////////////
double ConfigWidget::GetDoubleWidgetValue(const std::string &_name) const
{
  return this->DoubleWidgetValue(_name);
}

/////////////////////////////////////////////////
double ConfigWidget::DoubleWidgetValue(const std::string &_name) const
{
  double value = 0.0;
  std::map <std::string, ConfigChildWidget *>::const_iterator iter =
      this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    value = this->DoubleWidgetValue(iter->second);
  return value;
}

/////////////////////////////////////////////////
bool ConfigWidget::GetBoolWidgetValue(const std::string &_name) const
{
  return this->BoolWidgetValue(_name);
}

/////////////////////////////////////////////////
bool ConfigWidget::BoolWidgetValue(const std::string &_name) const
{
  bool value = false;
  std::map <std::string, ConfigChildWidget *>::const_iterator iter =
      this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    value = this->BoolWidgetValue(iter->second);
  return value;
}

/////////////////////////////////////////////////
std::string ConfigWidget::GetStringWidgetValue(const std::string &_name) const
{
  return this->StringWidgetValue(_name);
}

/////////////////////////////////////////////////
std::string ConfigWidget::StringWidgetValue(const std::string &_name) const
{
  std::string value;
  std::map <std::string, ConfigChildWidget *>::const_iterator iter =
      this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    value = this->StringWidgetValue(iter->second);
  return value;
}

/////////////////////////////////////////////////
math::Vector3 ConfigWidget::GetVector3WidgetValue(const std::string &_name)
    const
{
  return this->Vector3dWidgetValue(_name);
}

/////////////////////////////////////////////////
ignition::math::Vector3d ConfigWidget::Vector3dWidgetValue(
    const std::string &_name) const
{
  ignition::math::Vector3d value;
  std::map <std::string, ConfigChildWidget *>::const_iterator iter =
      this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    value = this->Vector3dWidgetValue(iter->second);
  return value;
}

/////////////////////////////////////////////////
common::Color ConfigWidget::GetColorWidgetValue(const std::string &_name) const
{
  return this->ColorWidgetValue(_name);
}

/////////////////////////////////////////////////
common::Color ConfigWidget::ColorWidgetValue(const std::string &_name) const
{
  common::Color value;
  std::map <std::string, ConfigChildWidget *>::const_iterator iter =
      this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    value = this->ColorWidgetValue(iter->second);
  return value;
}

/////////////////////////////////////////////////
math::Pose ConfigWidget::GetPoseWidgetValue(const std::string &_name) const
{
  return this->PoseWidgetValue(_name);
}

/////////////////////////////////////////////////
ignition::math::Pose3d ConfigWidget::PoseWidgetValue(const std::string &_name)
    const
{
  ignition::math::Pose3d value;
  std::map <std::string, ConfigChildWidget *>::const_iterator iter =
      this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    value = this->PoseWidgetValue(iter->second);
  return value;
}

/////////////////////////////////////////////////
std::string ConfigWidget::GetGeometryWidgetValue(const std::string &_name,
    math::Vector3 &_dimensions, std::string &_uri) const
{
  ignition::math::Vector3d dimensions;
  std::string type = this->GeometryWidgetValue(_name, dimensions, _uri);
  _dimensions = dimensions;

  return type;
}

/////////////////////////////////////////////////
std::string ConfigWidget::GeometryWidgetValue(const std::string &_name,
    ignition::math::Vector3d &_dimensions, std::string &_uri) const
{
  std::string type;
  std::map <std::string, ConfigChildWidget *>::const_iterator iter =
      this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    type = this->GeometryWidgetValue(iter->second, _dimensions, _uri);
  return type;
}

/////////////////////////////////////////////////
std::string ConfigWidget::GetEnumWidgetValue(const std::string &_name) const
{
  return this->EnumWidgetValue(_name);
}

/////////////////////////////////////////////////
std::string ConfigWidget::EnumWidgetValue(const std::string &_name) const
{
  std::string value;
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    value = this->EnumWidgetValue(iter->second);
  return value;
}

/////////////////////////////////////////////////
QWidget *ConfigWidget::Parse(google::protobuf::Message *_msg,
  bool _update, const std::string &_name, const int _level)
{
  std::vector<QWidget *> newWidgets;

  const google::protobuf::Descriptor *d = _msg->GetDescriptor();
  if (!d)
    return NULL;
  unsigned int count = d->field_count();

  for (unsigned int i = 0; i < count ; ++i)
  {
    const google::protobuf::FieldDescriptor *field = d->field(i);

    if (!field)
      return NULL;

    const google::protobuf::Reflection *ref = _msg->GetReflection();

    if (!ref)
      return NULL;

    std::string name = field->name();

    // Parse each field in the message
    // TODO parse repeated fields
    if (!field->is_repeated())
    {
      if (_update && !ref->HasField(*_msg, field))
        continue;

      QWidget *newFieldWidget = NULL;
      ConfigChildWidget *configChildWidget = NULL;

      bool newWidget = true;
      std::string scopedName = _name.empty() ? name : _name + "::" + name;
      if (this->dataPtr->configWidgets.find(scopedName) !=
          this->dataPtr->configWidgets.end())
      {
        newWidget = false;
        configChildWidget = this->dataPtr->configWidgets[scopedName];
      }

      switch (field->type())
      {
        case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
        {
          double value = ref->GetDouble(*_msg, field);
          if (!math::equal(value, value))
            value = 0;
          if (newWidget)
          {
            configChildWidget = this->CreateDoubleWidget(name, _level);
            newFieldWidget = configChildWidget;
          }
          this->UpdateDoubleWidget(configChildWidget, value);
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_FLOAT:
        {
          float value = ref->GetFloat(*_msg, field);
          if (!math::equal(value, value))
            value = 0;
          if (newWidget)
          {
            configChildWidget = this->CreateDoubleWidget(name, _level);
            newFieldWidget = configChildWidget;
          }
          this->UpdateDoubleWidget(configChildWidget, value);
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_INT64:
        {
          int64_t value = ref->GetInt64(*_msg, field);
          if (newWidget)
          {
            configChildWidget = this->CreateIntWidget(name, _level);
            newFieldWidget = configChildWidget;
          }
          this->UpdateIntWidget(configChildWidget, value);
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_UINT64:
        {
          uint64_t value = ref->GetUInt64(*_msg, field);
          if (newWidget)
          {
            configChildWidget = this->CreateUIntWidget(name, _level);
            newFieldWidget = configChildWidget;
          }
          this->UpdateUIntWidget(configChildWidget, value);
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_INT32:
        {
          int32_t value = ref->GetInt32(*_msg, field);
          if (newWidget)
          {
            configChildWidget = this->CreateIntWidget(name, _level);
            newFieldWidget = configChildWidget;
          }
          this->UpdateIntWidget(configChildWidget, value);
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_UINT32:
        {
          uint32_t value = ref->GetUInt32(*_msg, field);
          if (newWidget)
          {
            configChildWidget = this->CreateUIntWidget(name, _level);
            newFieldWidget = configChildWidget;
          }
          this->UpdateUIntWidget(configChildWidget, value);
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_BOOL:
        {
          bool value = ref->GetBool(*_msg, field);
          if (newWidget)
          {
            configChildWidget = this->CreateBoolWidget(name, _level);
            newFieldWidget = configChildWidget;
          }
          this->UpdateBoolWidget(configChildWidget, value);
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_STRING:
        {
          std::string value = ref->GetString(*_msg, field);
          if (newWidget)
          {
            // Choose either a one-line or a multi-line widget according to name
            std::string type = "line";
            if (name == "innerxml")
              type = "plain";

            configChildWidget = this->CreateStringWidget(name, _level, type);
            newFieldWidget = configChildWidget;
          }
          this->UpdateStringWidget(configChildWidget, value);
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
        {
          google::protobuf::Message *valueMsg =
              ref->MutableMessage(_msg, field);

          // parse and create custom geometry widgets
          if (field->message_type()->name() == "Geometry")
          {
            if (newWidget)
            {
              configChildWidget = this->CreateGeometryWidget(name, _level);
              newFieldWidget = configChildWidget;
            }

            // type
            const google::protobuf::Descriptor *valueDescriptor =
                valueMsg->GetDescriptor();
            const google::protobuf::FieldDescriptor *typeField =
                valueDescriptor->FindFieldByName("type");

            if (valueMsg->GetReflection()->HasField(*valueMsg, typeField))
            {
              const google::protobuf::EnumValueDescriptor *typeValueDescriptor =
                  valueMsg->GetReflection()->GetEnum(*valueMsg, typeField);

              std::string geometryTypeStr;
              if (typeValueDescriptor)
              {
                geometryTypeStr =
                    QString(typeValueDescriptor->name().c_str()).toLower().
                    toStdString();
              }

              ignition::math::Vector3d dimensions;
              // dimensions
              for (int k = 0; k < valueDescriptor->field_count() ; ++k)
              {
                const google::protobuf::FieldDescriptor *geomField =
                    valueDescriptor->field(k);

                if (geomField->is_repeated())
                    continue;

                if (geomField->type() !=
                    google::protobuf::FieldDescriptor::TYPE_MESSAGE ||
                    !valueMsg->GetReflection()->HasField(*valueMsg, geomField))
                  continue;

                google::protobuf::Message *geomValueMsg =
                    valueMsg->GetReflection()->MutableMessage(
                    valueMsg, geomField);
                const google::protobuf::Descriptor *geomValueDescriptor =
                    geomValueMsg->GetDescriptor();

                std::string geomMsgName = geomField->message_type()->name();
                if (geomMsgName == "BoxGeom" || geomMsgName == "MeshGeom")
                {
                  int fieldIdx = (geomMsgName == "BoxGeom") ? 0 : 1;
                  google::protobuf::Message *geomDimMsg =
                      geomValueMsg->GetReflection()->MutableMessage(
                      geomValueMsg, geomValueDescriptor->field(fieldIdx));
                  dimensions = this->ParseVector3d(geomDimMsg);
                  break;
                }
                else if (geomMsgName == "CylinderGeom")
                {
                  const google::protobuf::FieldDescriptor *geomRadiusField =
                      geomValueDescriptor->FindFieldByName("radius");
                  double radius = geomValueMsg->GetReflection()->GetDouble(
                      *geomValueMsg, geomRadiusField);
                  const google::protobuf::FieldDescriptor *geomLengthField =
                      geomValueDescriptor->FindFieldByName("length");
                  double length = geomValueMsg->GetReflection()->GetDouble(
                      *geomValueMsg, geomLengthField);
                  dimensions.X(radius * 2.0);
                  dimensions.Y(dimensions.X());
                  dimensions.Z(length);
                  break;
                }
                else if (geomMsgName == "SphereGeom")
                {
                  const google::protobuf::FieldDescriptor *geomRadiusField =
                      geomValueDescriptor->FindFieldByName("radius");
                  double radius = geomValueMsg->GetReflection()->GetDouble(
                      *geomValueMsg, geomRadiusField);
                  dimensions.X(radius * 2.0);
                  dimensions.Y(dimensions.X());
                  dimensions.Z(dimensions.X());
                  break;
                }
                else if (geomMsgName == "PolylineGeom")
                {
                  continue;
                }
              }
              this->UpdateGeometryWidget(configChildWidget,
                  geometryTypeStr, dimensions);
            }
          }
          // parse and create custom pose widgets
          else if (field->message_type()->name() == "Pose")
          {
            if (newWidget)
            {
              configChildWidget = this->CreatePoseWidget(name, _level);
              newFieldWidget = configChildWidget;
            }

            ignition::math::Pose3d value;
            const google::protobuf::Descriptor *valueDescriptor =
                valueMsg->GetDescriptor();
            int valueMsgFieldCount = valueDescriptor->field_count();
            for (int j = 0; j < valueMsgFieldCount ; ++j)
            {
              const google::protobuf::FieldDescriptor *valueField =
                  valueDescriptor->field(j);

              if (valueField->type() !=
                  google::protobuf::FieldDescriptor::TYPE_MESSAGE)
                continue;

              if (valueField->message_type()->name() == "Vector3d")
              {
                // pos
                google::protobuf::Message *posValueMsg =
                    valueMsg->GetReflection()->MutableMessage(
                    valueMsg, valueField);
                auto vec3 = this->ParseVector3d(posValueMsg);
                value.Pos() = vec3;
              }
              else if (valueField->message_type()->name() == "Quaternion")
              {
                // rot
                google::protobuf::Message *quatValueMsg =
                    valueMsg->GetReflection()->MutableMessage(
                    valueMsg, valueField);
                const google::protobuf::Descriptor *quatValueDescriptor =
                    quatValueMsg->GetDescriptor();
                std::vector<double> quatValues;
                for (unsigned int k = 0; k < 4; ++k)
                {
                  const google::protobuf::FieldDescriptor *quatValueField =
                      quatValueDescriptor->field(k);
                  quatValues.push_back(quatValueMsg->GetReflection()->GetDouble(
                      *quatValueMsg, quatValueField));
                }
                ignition::math::Quaterniond quat(quatValues[3], quatValues[0],
                    quatValues[1], quatValues[2]);
                value.Rot() = quat;
              }
            }
            this->UpdatePoseWidget(configChildWidget, value);
          }
          // parse and create custom vector3 widgets
          else if (field->message_type()->name() == "Vector3d")
          {
            if (newWidget)
            {
              configChildWidget = this->CreateVector3dWidget(name, _level);
              newFieldWidget = configChildWidget;
            }

            ignition::math::Vector3d vec3 = this->ParseVector3d(valueMsg);
            this->UpdateVector3dWidget(configChildWidget, vec3);
          }
          // parse and create custom color widgets
          else if (field->message_type()->name() == "Color")
          {
            if (newWidget)
            {
              configChildWidget = this->CreateColorWidget(name, _level);
              newFieldWidget = configChildWidget;
            }

            common::Color color;
            const google::protobuf::Descriptor *valueDescriptor =
                valueMsg->GetDescriptor();
            std::vector<double> values;
            for (unsigned int j = 0; j < configChildWidget->widgets.size(); ++j)
            {
              const google::protobuf::FieldDescriptor *valueField =
                  valueDescriptor->field(j);
              if (valueMsg->GetReflection()->HasField(*valueMsg, valueField))
              {
                values.push_back(valueMsg->GetReflection()->GetFloat(
                    *valueMsg, valueField));
              }
              else
                values.push_back(0);
            }
            color.r = values[0];
            color.g = values[1];
            color.b = values[2];
            color.a = values[3];
            this->UpdateColorWidget(configChildWidget, color);
          }
          else
          {
            // parse the message fields recursively
            QWidget *groupBoxWidget =
                this->Parse(valueMsg, _update, scopedName, _level+1);
            if (groupBoxWidget)
            {
              newFieldWidget = new ConfigChildWidget();
              QVBoxLayout *groupBoxLayout = new QVBoxLayout;
              groupBoxLayout->setContentsMargins(0, 0, 0, 0);
              groupBoxLayout->addWidget(groupBoxWidget);
              newFieldWidget->setLayout(groupBoxLayout);
              qobject_cast<ConfigChildWidget *>(newFieldWidget)->
                  widgets.push_back(groupBoxWidget);
            }
          }

          if (newWidget)
          {
            // Make it into a group widget
            ConfigChildWidget *childWidget =
                qobject_cast<ConfigChildWidget *>(newFieldWidget);
            if (childWidget)
            {
              newFieldWidget = this->CreateGroupWidget(name, childWidget,
                  _level);
            }
          }

          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_ENUM:
        {
          const google::protobuf::EnumValueDescriptor *value =
              ref->GetEnum(*_msg, field);

          if (!value)
          {
            gzerr << "Error retrieving enum value for '" << name << "'"
                << std::endl;
            break;
          }

          if (newWidget)
          {
            std::vector<std::string> enumValues;
            const google::protobuf::EnumDescriptor *descriptor = value->type();
            if (!descriptor)
              break;

            for (int j = 0; j < descriptor->value_count(); ++j)
            {
              const google::protobuf::EnumValueDescriptor *valueDescriptor =
                  descriptor->value(j);
              if (valueDescriptor)
                enumValues.push_back(valueDescriptor->name());
            }
            configChildWidget =
                this->CreateEnumWidget(name, enumValues, _level);

            if (!configChildWidget)
            {
              gzerr << "Error creating an enum widget for '" << name << "'"
                  << std::endl;
              break;
            }

            newFieldWidget = configChildWidget;
          }
          this->UpdateEnumWidget(configChildWidget, value->name());
          break;
        }
        default:
          break;
      }

      // Style widgets without parent (level 0)
      if (newFieldWidget && _level == 0 &&
          !qobject_cast<GroupWidget *>(newFieldWidget))
      {
        newFieldWidget->setStyleSheet(
            "QWidget\
            {\
              background-color: " + this->bgColors[0] +
            "}\
            QDoubleSpinBox, QSpinBox, QLineEdit, QComboBox, QPlainTextEdit\
            {\
              background-color: " + this->widgetColors[0] +
            "}");
      }

      if (newWidget && newFieldWidget)
      {
        newWidgets.push_back(newFieldWidget);

        // store the newly created widget in a map with a unique scoped name.
        if (qobject_cast<GroupWidget *>(newFieldWidget))
        {
          GroupWidget *groupWidget =
              qobject_cast<GroupWidget *>(newFieldWidget);
          ConfigChildWidget *childWidget = qobject_cast<ConfigChildWidget *>(
              groupWidget->childWidget);
          this->AddConfigChildWidget(scopedName, childWidget);
        }
        else if (qobject_cast<ConfigChildWidget *>(newFieldWidget))
        {
          this->AddConfigChildWidget(scopedName,
              qobject_cast<ConfigChildWidget *>(newFieldWidget));
        }
      }
    }
  }

  if (!newWidgets.empty())
  {
    // create a group box to hold child widgets.
    QGroupBox *widget = new QGroupBox();
    QVBoxLayout *widgetLayout = new QVBoxLayout;

    for (unsigned int i = 0; i < newWidgets.size(); ++i)
    {
      widgetLayout->addWidget(newWidgets[i]);
    }

    widgetLayout->setContentsMargins(0, 0, 0, 0);
    widgetLayout->setSpacing(0);
    widgetLayout->setAlignment(Qt::AlignTop);
    widget->setLayout(widgetLayout);
    return widget;
  }

  return NULL;
}

/////////////////////////////////////////////////
GroupWidget *ConfigWidget::CreateGroupWidget(const std::string &_name,
    ConfigChildWidget *_childWidget, const int _level)
{
  // Button label
  QLabel *buttonLabel = new QLabel(
      tr(this->HumanReadableKey(_name).c_str()));
  buttonLabel->setToolTip(tr(_name.c_str()));

  // Button icon
  QCheckBox *buttonIcon = new QCheckBox();
  buttonIcon->setChecked(true);
  buttonIcon->setStyleSheet(
      "QCheckBox::indicator::unchecked {\
        image: url(:/images/right_arrow.png);\
      }\
      QCheckBox::indicator::checked {\
        image: url(:/images/down_arrow.png);\
      }");

  // Button layout
  QHBoxLayout *buttonLayout = new QHBoxLayout();
  buttonLayout->addItem(new QSpacerItem(20*_level, 1,
      QSizePolicy::Fixed, QSizePolicy::Fixed));
  buttonLayout->addWidget(buttonLabel);
  buttonLayout->addWidget(buttonIcon);
  buttonLayout->setAlignment(buttonIcon, Qt::AlignRight);

  // Button frame
  QFrame *buttonFrame = new QFrame();
  buttonFrame->setFrameStyle(QFrame::Box);
  buttonFrame->setLayout(buttonLayout);

  // Set color for top level button
  if (_level == 0)
  {
    buttonFrame->setStyleSheet(
        "QWidget\
        {\
          background-color: " + this->bgColors[0] +
        "}");
  }

  // Child widgets are contained in a group box which can be collapsed
  GroupWidget *groupWidget = new GroupWidget;
  groupWidget->setStyleSheet(
      "QGroupBox {\
        border : 0;\
        margin : 0;\
        padding : 0;\
      }");

  connect(buttonIcon, SIGNAL(toggled(bool)), groupWidget, SLOT(Toggle(bool)));

  // Set the child widget
  groupWidget->childWidget = _childWidget;
  _childWidget->groupWidget = groupWidget;
  _childWidget->setContentsMargins(0, 0, 0, 0);

  // Set color for children
  if (_level == 0)
  {
    _childWidget->setStyleSheet(
        "QWidget\
        {\
          background-color: " + this->bgColors[1] +
        "}\
        QDoubleSpinBox, QSpinBox, QLineEdit, QComboBox, QPlainTextEdit\
        {\
          background-color: " + this->widgetColors[1] +
        "}");
  }
  else if (_level == 1)
  {
    _childWidget->setStyleSheet(
        "QWidget\
        {\
          background-color: " + this->bgColors[2] +
        "}\
        QDoubleSpinBox, QSpinBox, QLineEdit, QComboBox, QPlainTextEdit\
        {\
          background-color: " + this->widgetColors[2] +
        "}");
  }
  else if (_level == 2)
  {
    _childWidget->setStyleSheet(
        "QWidget\
        {\
          background-color: " + this->bgColors[3] +
        "}\
        QDoubleSpinBox, QSpinBox, QLineEdit, QComboBox, QPlainTextEdit\
        {\
          background-color: " + this->widgetColors[3] +
        "}");
  }

  // Group Layout
  QGridLayout *configGroupLayout = new QGridLayout;
  configGroupLayout->setContentsMargins(0, 0, 0, 0);
  configGroupLayout->setSpacing(0);
  configGroupLayout->addWidget(buttonFrame, 0, 0);
  configGroupLayout->addWidget(_childWidget, 1, 0);
  groupWidget->setLayout(configGroupLayout);

  return groupWidget;
}

/////////////////////////////////////////////////
ignition::math::Vector3d ConfigWidget::ParseVector3d(
    const google::protobuf::Message *_msg) const
{
  ignition::math::Vector3d vec3;
  const google::protobuf::Descriptor *valueDescriptor = _msg->GetDescriptor();
  std::vector<double> values;
  for (unsigned int i = 0; i < 3; ++i)
  {
    const google::protobuf::FieldDescriptor *valueField =
        valueDescriptor->field(i);
    values.push_back(_msg->GetReflection()->GetDouble(*_msg, valueField));
  }
  vec3.X(values[0]);
  vec3.Y(values[1]);
  vec3.Z(values[2]);
  return vec3;
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::CreateUIntWidget(const std::string &_key,
    const int _level)
{
  // ChildWidget
  ConfigChildWidget *widget = new ConfigChildWidget();

  // Label
  QLabel *keyLabel = new QLabel(tr(this->HumanReadableKey(_key).c_str()));
  keyLabel->setToolTip(tr(_key.c_str()));

  // SpinBox
  QSpinBox *valueSpinBox = new QSpinBox(widget);
  valueSpinBox->setRange(0, 1e8);
  valueSpinBox->setAlignment(Qt::AlignRight);
  connect(valueSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnUIntValueChanged()));

  // Layout
  QHBoxLayout *widgetLayout = new QHBoxLayout;
  if (_level != 0)
  {
    widgetLayout->addItem(new QSpacerItem(20*_level, 1,
        QSizePolicy::Fixed, QSizePolicy::Fixed));
  }
  widgetLayout->addWidget(keyLabel);
  widgetLayout->addWidget(valueSpinBox);

  // ChildWidget
  widget->setLayout(widgetLayout);
  widget->setFrameStyle(QFrame::Box);

  widget->widgets.push_back(valueSpinBox);

  return widget;
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::CreateIntWidget(const std::string &_key,
    const int _level)
{
  // ChildWidget
  ConfigChildWidget *widget = new ConfigChildWidget();

  // Label
  QLabel *keyLabel = new QLabel(tr(this->HumanReadableKey(_key).c_str()));
  keyLabel->setToolTip(tr(_key.c_str()));

  // SpinBox
  QSpinBox *valueSpinBox = new QSpinBox(widget);
  valueSpinBox->setRange(-1e8, 1e8);
  valueSpinBox->setAlignment(Qt::AlignRight);
  connect(valueSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnIntValueChanged()));

  // Layout
  QHBoxLayout *widgetLayout = new QHBoxLayout;
  if (_level != 0)
  {
    widgetLayout->addItem(new QSpacerItem(20*_level, 1,
        QSizePolicy::Fixed, QSizePolicy::Fixed));
  }
  widgetLayout->addWidget(keyLabel);
  widgetLayout->addWidget(valueSpinBox);

  // ChildWidget
  widget->setLayout(widgetLayout);
  widget->setFrameStyle(QFrame::Box);

  widget->widgets.push_back(valueSpinBox);

  return widget;
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::CreateDoubleWidget(const std::string &_key,
    const int _level)
{
  // ChildWidget
  ConfigChildWidget *widget = new ConfigChildWidget();

  // Label
  QLabel *keyLabel = new QLabel(tr(this->HumanReadableKey(_key).c_str()));
  keyLabel->setToolTip(tr(_key.c_str()));

  // SpinBox
  double min = 0;
  double max = 0;
  this->RangeFromKey(_key, min, max);

  QDoubleSpinBox *valueSpinBox = new QDoubleSpinBox(widget);
  valueSpinBox->setRange(min, max);
  valueSpinBox->setSingleStep(0.01);
  valueSpinBox->setDecimals(8);
  valueSpinBox->setAlignment(Qt::AlignRight);
  connect(valueSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnDoubleValueChanged()));

  // Unit
  std::string jointType = this->EnumWidgetValue("type");
  std::string unit = this->UnitFromKey(_key, jointType);

  QLabel *unitLabel = new QLabel();
  unitLabel->setMaximumWidth(40);
  unitLabel->setText(QString::fromStdString(unit));

  // Layout
  QHBoxLayout *widgetLayout = new QHBoxLayout;
  if (_level != 0)
  {
    widgetLayout->addItem(new QSpacerItem(20*_level, 1,
        QSizePolicy::Fixed, QSizePolicy::Fixed));
  }
  widgetLayout->addWidget(keyLabel);
  widgetLayout->addWidget(valueSpinBox);
  if (unitLabel->text() != "")
    widgetLayout->addWidget(unitLabel);

  // ChildWidget
  widget->key = _key;
  widget->setLayout(widgetLayout);
  widget->setFrameStyle(QFrame::Box);

  widget->widgets.push_back(valueSpinBox);
  widget->mapWidgetToUnit[valueSpinBox] = unitLabel;

  return widget;
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::CreateStringWidget(const std::string &_key,
    const int _level, const std::string &_type)
{
  // ChildWidget
  ConfigChildWidget *widget = new ConfigChildWidget();

  // Label
  QLabel *keyLabel = new QLabel(tr(this->HumanReadableKey(_key).c_str()));
  keyLabel->setToolTip(tr(_key.c_str()));

  // Line or Text Edit based on key
  QWidget *valueEdit;
  if (_type == "plain")
  {
    valueEdit = new QPlainTextEdit(widget);
    valueEdit->setMinimumHeight(50);
    // QPlainTextEdit's don't have editingFinished signals
  }
  else if (_type == "line")
  {
    valueEdit = new QLineEdit(widget);
    connect(valueEdit, SIGNAL(editingFinished()), this,
        SLOT(OnStringValueChanged()));
  }
  else
  {
    gzerr << "Unknown type [" << _type << "]. Not creating string widget" <<
        std::endl;
    return NULL;
  }

  // Layout
  QHBoxLayout *widgetLayout = new QHBoxLayout;
  if (_level != 0)
  {
    widgetLayout->addItem(new QSpacerItem(20*_level, 1,
        QSizePolicy::Fixed, QSizePolicy::Fixed));
  }
  widgetLayout->addWidget(keyLabel);
  widgetLayout->addWidget(valueEdit);

  // ChildWidget
  widget->setLayout(widgetLayout);
  widget->setFrameStyle(QFrame::Box);

  widget->widgets.push_back(valueEdit);

  return widget;
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::CreateBoolWidget(const std::string &_key,
    const int _level)
{
  // ChildWidget
  ConfigChildWidget *widget = new ConfigChildWidget();

  // Label
  QLabel *keyLabel = new QLabel(tr(this->HumanReadableKey(_key).c_str()));
  keyLabel->setToolTip(tr(_key.c_str()));

  // Buttons
  QRadioButton *valueTrueRadioButton = new QRadioButton(widget);
  valueTrueRadioButton->setText(tr("True"));
  connect(valueTrueRadioButton, SIGNAL(toggled(bool)), this,
      SLOT(OnBoolValueChanged()));

  QRadioButton *valueFalseRadioButton = new QRadioButton(widget);
  valueFalseRadioButton->setText(tr("False"));
  connect(valueFalseRadioButton, SIGNAL(toggled(bool)), this,
      SLOT(OnBoolValueChanged()));

  QButtonGroup *boolButtonGroup = new QButtonGroup;
  boolButtonGroup->addButton(valueTrueRadioButton);
  boolButtonGroup->addButton(valueFalseRadioButton);
  boolButtonGroup->setExclusive(true);

  QHBoxLayout *buttonLayout = new QHBoxLayout;
  buttonLayout->addWidget(valueTrueRadioButton);
  buttonLayout->addWidget(valueFalseRadioButton);

  // Layout
  QHBoxLayout *widgetLayout = new QHBoxLayout;
  if (_level != 0)
  {
    widgetLayout->addItem(new QSpacerItem(20*_level, 1,
        QSizePolicy::Fixed, QSizePolicy::Fixed));
  }
  widgetLayout->addWidget(keyLabel);
  widgetLayout->addLayout(buttonLayout);

  // ChildWidget
  widget->setLayout(widgetLayout);
  widget->setFrameStyle(QFrame::Box);

  widget->widgets.push_back(valueTrueRadioButton);
  widget->widgets.push_back(valueFalseRadioButton);

  return widget;
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::CreateVector3dWidget(
    const std::string &_key, const int _level)
{
  // ChildWidget
  ConfigChildWidget *widget = new ConfigChildWidget();

  // Presets
  auto presetsCombo = new QComboBox(widget);
  presetsCombo->addItem("Custom", 0);
  presetsCombo->addItem(" X", 1);
  presetsCombo->addItem("-X", 2);
  presetsCombo->addItem(" Y", 3);
  presetsCombo->addItem("-Y", 4);
  presetsCombo->addItem(" Z", 5);
  presetsCombo->addItem("-Z", 6);
  presetsCombo->setMinimumWidth(80);
  connect(presetsCombo, SIGNAL(currentIndexChanged(const int)), this,
      SLOT(OnVector3dPresetChanged(const int)));

  // Labels
  QLabel *vecXLabel = new QLabel(tr("X"));
  QLabel *vecYLabel = new QLabel(tr("Y"));
  QLabel *vecZLabel = new QLabel(tr("Z"));
  vecXLabel->setToolTip(tr("x"));
  vecYLabel->setToolTip(tr("y"));
  vecZLabel->setToolTip(tr("z"));

  // SpinBoxes
  double min = 0;
  double max = 0;
  this->RangeFromKey(_key, min, max);

  QDoubleSpinBox *vecXSpinBox = new QDoubleSpinBox(widget);
  vecXSpinBox->setRange(min, max);
  vecXSpinBox->setSingleStep(0.01);
  vecXSpinBox->setDecimals(6);
  vecXSpinBox->setAlignment(Qt::AlignRight);
  vecXSpinBox->setMaximumWidth(100);
  connect(vecXSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnVector3dValueChanged()));

  QDoubleSpinBox *vecYSpinBox = new QDoubleSpinBox(widget);
  vecYSpinBox->setRange(min, max);
  vecYSpinBox->setSingleStep(0.01);
  vecYSpinBox->setDecimals(6);
  vecYSpinBox->setAlignment(Qt::AlignRight);
  vecYSpinBox->setMaximumWidth(100);
  connect(vecYSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnVector3dValueChanged()));

  QDoubleSpinBox *vecZSpinBox = new QDoubleSpinBox(widget);
  vecZSpinBox->setRange(min, max);
  vecZSpinBox->setSingleStep(0.01);
  vecZSpinBox->setDecimals(6);
  vecZSpinBox->setAlignment(Qt::AlignRight);
  vecZSpinBox->setMaximumWidth(100);
  connect(vecZSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnVector3dValueChanged()));

  // This is inside a group
  int level = _level + 1;

  // Layout
  QHBoxLayout *widgetLayout = new QHBoxLayout;
  widgetLayout->addItem(new QSpacerItem(20*level, 1,
      QSizePolicy::Fixed, QSizePolicy::Fixed));
  widgetLayout->addWidget(presetsCombo);
  widgetLayout->addWidget(vecXLabel);
  widgetLayout->addWidget(vecXSpinBox);
  widgetLayout->addWidget(vecYLabel);
  widgetLayout->addWidget(vecYSpinBox);
  widgetLayout->addWidget(vecZLabel);
  widgetLayout->addWidget(vecZSpinBox);

  widgetLayout->setAlignment(vecXLabel, Qt::AlignRight);
  widgetLayout->setAlignment(vecYLabel, Qt::AlignRight);
  widgetLayout->setAlignment(vecZLabel, Qt::AlignRight);

  // ChildWidget
  widget->setLayout(widgetLayout);
  widget->setFrameStyle(QFrame::Box);

  widget->widgets.push_back(vecXSpinBox);
  widget->widgets.push_back(vecYSpinBox);
  widget->widgets.push_back(vecZSpinBox);
  widget->widgets.push_back(presetsCombo);

  return widget;
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::CreateColorWidget(const std::string &_key,
    const int _level)
{
  // ChildWidget
  ConfigChildWidget *widget = new ConfigChildWidget();

  // Labels
  QLabel *colorRLabel = new QLabel(tr("R"));
  QLabel *colorGLabel = new QLabel(tr("G"));
  QLabel *colorBLabel = new QLabel(tr("B"));
  QLabel *colorALabel = new QLabel(tr("A"));
  colorRLabel->setToolTip(tr("r"));
  colorGLabel->setToolTip(tr("g"));
  colorBLabel->setToolTip(tr("b"));
  colorALabel->setToolTip(tr("a"));

  // SpinBoxes
  double min = 0;
  double max = 0;
  this->RangeFromKey(_key, min, max);

  QDoubleSpinBox *colorRSpinBox = new QDoubleSpinBox(widget);
  colorRSpinBox->setRange(0, 1.0);
  colorRSpinBox->setSingleStep(0.1);
  colorRSpinBox->setDecimals(3);
  colorRSpinBox->setAlignment(Qt::AlignRight);
  colorRSpinBox->setMaximumWidth(10);
  connect(colorRSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnColorValueChanged()));

  QDoubleSpinBox *colorGSpinBox = new QDoubleSpinBox(widget);
  colorGSpinBox->setRange(0, 1.0);
  colorGSpinBox->setSingleStep(0.1);
  colorGSpinBox->setDecimals(3);
  colorGSpinBox->setAlignment(Qt::AlignRight);
  colorGSpinBox->setMaximumWidth(10);
  connect(colorGSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnColorValueChanged()));

  QDoubleSpinBox *colorBSpinBox = new QDoubleSpinBox(widget);
  colorBSpinBox->setRange(0, 1.0);
  colorBSpinBox->setSingleStep(0.1);
  colorBSpinBox->setDecimals(3);
  colorBSpinBox->setAlignment(Qt::AlignRight);
  colorBSpinBox->setMaximumWidth(10);
  connect(colorBSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnColorValueChanged()));

  QDoubleSpinBox *colorASpinBox = new QDoubleSpinBox(widget);
  colorASpinBox->setRange(0, 1.0);
  colorASpinBox->setSingleStep(0.1);
  colorASpinBox->setDecimals(3);
  colorASpinBox->setAlignment(Qt::AlignRight);
  colorASpinBox->setMaximumWidth(10);
  connect(colorASpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnColorValueChanged()));

  // This is inside a group
  int level = _level + 1;

  // Layout
  QHBoxLayout *widgetLayout = new QHBoxLayout;
  widgetLayout->addItem(new QSpacerItem(20*level, 1,
      QSizePolicy::Fixed, QSizePolicy::Fixed));
  widgetLayout->addWidget(colorRLabel);
  widgetLayout->addWidget(colorRSpinBox);
  widgetLayout->addWidget(colorGLabel);
  widgetLayout->addWidget(colorGSpinBox);
  widgetLayout->addWidget(colorBLabel);
  widgetLayout->addWidget(colorBSpinBox);
  widgetLayout->addWidget(colorALabel);
  widgetLayout->addWidget(colorASpinBox);

  widgetLayout->setAlignment(colorRLabel, Qt::AlignRight);
  widgetLayout->setAlignment(colorGLabel, Qt::AlignRight);
  widgetLayout->setAlignment(colorBLabel, Qt::AlignRight);
  widgetLayout->setAlignment(colorALabel, Qt::AlignRight);

  // ChildWidget
  widget->setLayout(widgetLayout);
  widget->setFrameStyle(QFrame::Box);

  widget->widgets.push_back(colorRSpinBox);
  widget->widgets.push_back(colorGSpinBox);
  widget->widgets.push_back(colorBSpinBox);
  widget->widgets.push_back(colorASpinBox);

  return widget;
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::CreatePoseWidget(const std::string &/*_key*/,
    const int _level)
{
  // Labels
  std::vector<std::string> elements;
  elements.push_back("x");
  elements.push_back("y");
  elements.push_back("z");
  elements.push_back("roll");
  elements.push_back("pitch");
  elements.push_back("yaw");

  // This is inside a group
  int level = _level+1;

  // Layout
  QGridLayout *widgetLayout = new QGridLayout;
  widgetLayout->setColumnStretch(3, 1);
  widgetLayout->addItem(new QSpacerItem(20*level, 1, QSizePolicy::Fixed,
      QSizePolicy::Fixed), 0, 0);

  // ChildWidget
  double min = 0;
  double max = 0;
  this->RangeFromKey("", min, max);

  ConfigChildWidget *widget = new ConfigChildWidget();
  widget->setLayout(widgetLayout);
  widget->setFrameStyle(QFrame::Box);

  for (unsigned int i = 0; i < elements.size(); ++i)
  {
    QDoubleSpinBox *spin = new QDoubleSpinBox(widget);
    connect(spin, SIGNAL(editingFinished()), this, SLOT(OnPoseValueChanged()));
    widget->widgets.push_back(spin);

    spin->setRange(min, max);
    spin->setSingleStep(0.01);
    spin->setDecimals(6);
    spin->setAlignment(Qt::AlignRight);
    spin->setMaximumWidth(100);

    QLabel *label = new QLabel(this->HumanReadableKey(elements[i]).c_str());
    label->setToolTip(tr(elements[i].c_str()));
    if (i == 0)
      label->setStyleSheet("QLabel{color: " + this->redColor + ";}");
    else if (i == 1)
      label->setStyleSheet("QLabel{color: " + this->greenColor + ";}");
    else if (i == 2)
      label->setStyleSheet("QLabel{color:" + this->blueColor + ";}");

    QLabel *unitLabel = new QLabel();
    unitLabel->setMaximumWidth(40);
    unitLabel->setMinimumWidth(40);
    if (i < 3)
      unitLabel->setText(QString::fromStdString(this->UnitFromKey("pos")));
    else
      unitLabel->setText(QString::fromStdString(this->UnitFromKey("rot")));

    widgetLayout->addWidget(label, i%3, std::floor(i/3)*3+1);
    widgetLayout->addWidget(spin, i%3, std::floor(i/3)*3+2);
    widgetLayout->addWidget(unitLabel, i%3, std::floor(i/3)*3+3);

    widgetLayout->setAlignment(label, Qt::AlignLeft);
    widgetLayout->setAlignment(spin, Qt::AlignLeft);
    widgetLayout->setAlignment(unitLabel, Qt::AlignLeft);
  }

  return widget;
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::CreateGeometryWidget(
    const std::string &/*_key*/, const int _level)
{
  // ChildWidget
  GeometryConfigWidget *widget = new GeometryConfigWidget;

  // Geometry ComboBox
  QLabel *geometryLabel = new QLabel(tr("Geometry"));
  geometryLabel->setToolTip(tr("geometry"));
  QComboBox *geometryComboBox = new QComboBox(widget);
  geometryComboBox->addItem(tr("box"));
  geometryComboBox->addItem(tr("cylinder"));
  geometryComboBox->addItem(tr("sphere"));
  geometryComboBox->addItem(tr("mesh"));
  geometryComboBox->addItem(tr("polyline"));
  connect(geometryComboBox, SIGNAL(currentIndexChanged(const int)), this,
      SLOT(OnGeometryValueChanged(const int)));

  // Size XYZ
  double min = 0;
  double max = 0;
  this->RangeFromKey("length", min, max);

  QDoubleSpinBox *geomSizeXSpinBox = new QDoubleSpinBox(widget);
  geomSizeXSpinBox->setRange(min, max);
  geomSizeXSpinBox->setSingleStep(0.01);
  geomSizeXSpinBox->setDecimals(6);
  geomSizeXSpinBox->setValue(1.000);
  geomSizeXSpinBox->setAlignment(Qt::AlignRight);
  geomSizeXSpinBox->setMaximumWidth(100);
  connect(geomSizeXSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnGeometryValueChanged()));

  QDoubleSpinBox *geomSizeYSpinBox = new QDoubleSpinBox(widget);
  geomSizeYSpinBox->setRange(min, max);
  geomSizeYSpinBox->setSingleStep(0.01);
  geomSizeYSpinBox->setDecimals(6);
  geomSizeYSpinBox->setValue(1.000);
  geomSizeYSpinBox->setAlignment(Qt::AlignRight);
  geomSizeYSpinBox->setMaximumWidth(100);
  connect(geomSizeYSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnGeometryValueChanged()));

  QDoubleSpinBox *geomSizeZSpinBox = new QDoubleSpinBox(widget);
  geomSizeZSpinBox->setRange(min, max);
  geomSizeZSpinBox->setSingleStep(0.01);
  geomSizeZSpinBox->setDecimals(6);
  geomSizeZSpinBox->setValue(1.000);
  geomSizeZSpinBox->setAlignment(Qt::AlignRight);
  geomSizeZSpinBox->setMaximumWidth(100);
  connect(geomSizeZSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnGeometryValueChanged()));

  QLabel *geomSizeXLabel = new QLabel(tr("X"));
  QLabel *geomSizeYLabel = new QLabel(tr("Y"));
  QLabel *geomSizeZLabel = new QLabel(tr("Z"));
  geomSizeXLabel->setStyleSheet("QLabel{color: " + this->redColor + ";}");
  geomSizeYLabel->setStyleSheet("QLabel{color: " + this->greenColor + ";}");
  geomSizeZLabel->setStyleSheet("QLabel{color: " + this->blueColor + ";}");
  geomSizeXLabel->setToolTip(tr("x"));
  geomSizeYLabel->setToolTip(tr("y"));
  geomSizeZLabel->setToolTip(tr("z"));

  std::string unit = this->UnitFromKey("length");
  QLabel *geomSizeXUnitLabel = new QLabel(QString::fromStdString(unit));
  QLabel *geomSizeYUnitLabel = new QLabel(QString::fromStdString(unit));
  QLabel *geomSizeZUnitLabel = new QLabel(QString::fromStdString(unit));

  QHBoxLayout *geomSizeLayout = new QHBoxLayout;
  geomSizeLayout->addWidget(geomSizeXLabel);
  geomSizeLayout->addWidget(geomSizeXSpinBox);
  geomSizeLayout->addWidget(geomSizeXUnitLabel);
  geomSizeLayout->addWidget(geomSizeYLabel);
  geomSizeLayout->addWidget(geomSizeYSpinBox);
  geomSizeLayout->addWidget(geomSizeYUnitLabel);
  geomSizeLayout->addWidget(geomSizeZLabel);
  geomSizeLayout->addWidget(geomSizeZSpinBox);
  geomSizeLayout->addWidget(geomSizeZUnitLabel);

  geomSizeLayout->setAlignment(geomSizeXLabel, Qt::AlignRight);
  geomSizeLayout->setAlignment(geomSizeYLabel, Qt::AlignRight);
  geomSizeLayout->setAlignment(geomSizeZLabel, Qt::AlignRight);

  // Uri
  QLabel *geomFilenameLabel = new QLabel(tr("Uri"));
  geomFilenameLabel->setToolTip(tr("uri"));
  QLineEdit *geomFilenameLineEdit = new QLineEdit(widget);
  connect(geomFilenameLineEdit, SIGNAL(editingFinished()), this,
      SLOT(OnGeometryValueChanged()));
  QPushButton *geomFilenameButton = new QPushButton(tr("..."));
  geomFilenameButton->setMaximumWidth(30);

  QHBoxLayout *geomFilenameLayout = new QHBoxLayout;
  geomFilenameLayout->addWidget(geomFilenameLabel);
  geomFilenameLayout->addWidget(geomFilenameLineEdit);
  geomFilenameLayout->addWidget(geomFilenameButton);

  QVBoxLayout *geomSizeFilenameLayout = new QVBoxLayout;
  geomSizeFilenameLayout->addLayout(geomSizeLayout);
  geomSizeFilenameLayout->addLayout(geomFilenameLayout);

  QWidget *geomSizeWidget = new QWidget(widget);
  geomSizeWidget->setLayout(geomSizeFilenameLayout);

  // Radius / Length
  QLabel *geomRadiusLabel = new QLabel(tr("Radius"));
  QLabel *geomLengthLabel = new QLabel(tr("Length"));
  QLabel *geomRadiusUnitLabel = new QLabel(QString::fromStdString(unit));
  QLabel *geomLengthUnitLabel = new QLabel(QString::fromStdString(unit));
  geomRadiusLabel->setToolTip(tr("radius"));
  geomLengthLabel->setToolTip(tr("length"));

  QDoubleSpinBox *geomRadiusSpinBox = new QDoubleSpinBox(widget);
  geomRadiusSpinBox->setRange(min, max);
  geomRadiusSpinBox->setSingleStep(0.01);
  geomRadiusSpinBox->setDecimals(6);
  geomRadiusSpinBox->setValue(0.500);
  geomRadiusSpinBox->setAlignment(Qt::AlignRight);
  geomRadiusSpinBox->setMaximumWidth(100);
  connect(geomRadiusSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnGeometryValueChanged()));

  QDoubleSpinBox *geomLengthSpinBox = new QDoubleSpinBox(widget);
  geomLengthSpinBox->setRange(min, max);
  geomLengthSpinBox->setSingleStep(0.01);
  geomLengthSpinBox->setDecimals(6);
  geomLengthSpinBox->setValue(1.000);
  geomLengthSpinBox->setAlignment(Qt::AlignRight);
  geomLengthSpinBox->setMaximumWidth(100);
  connect(geomLengthSpinBox, SIGNAL(editingFinished()), this,
      SLOT(OnGeometryValueChanged()));

  QHBoxLayout *geomRLLayout = new QHBoxLayout;
  geomRLLayout->addWidget(geomRadiusLabel);
  geomRLLayout->addWidget(geomRadiusSpinBox);
  geomRLLayout->addWidget(geomRadiusUnitLabel);
  geomRLLayout->addWidget(geomLengthLabel);
  geomRLLayout->addWidget(geomLengthSpinBox);
  geomRLLayout->addWidget(geomLengthUnitLabel);

  geomRLLayout->setAlignment(geomRadiusLabel, Qt::AlignRight);
  geomRLLayout->setAlignment(geomLengthLabel, Qt::AlignRight);

  QWidget *geomRLWidget = new QWidget;
  geomRLWidget->setLayout(geomRLLayout);

  // Dimensions
  QStackedWidget *geomDimensionWidget = new QStackedWidget(widget);
  geomDimensionWidget->insertWidget(0, geomSizeWidget);

  geomDimensionWidget->insertWidget(1, geomRLWidget);
  geomDimensionWidget->setCurrentIndex(0);
  geomDimensionWidget->setSizePolicy(
      QSizePolicy::Minimum, QSizePolicy::Minimum);

  // This is inside a group
  int level = _level + 1;

  // Layout
  QGridLayout *widgetLayout = new QGridLayout;
  widgetLayout->addItem(new QSpacerItem(20*level, 1,
      QSizePolicy::Fixed, QSizePolicy::Fixed), 0, 0);
  widgetLayout->addWidget(geometryLabel, 0, 1);
  widgetLayout->addWidget(geometryComboBox, 0, 2, 1, 2);
  widgetLayout->addWidget(geomDimensionWidget, 2, 1, 1, 3);

  // ChildWidget
  widget->setFrameStyle(QFrame::Box);
  widget->geomDimensionWidget = geomDimensionWidget;
  widget->geomLengthSpinBox = geomLengthSpinBox;
  widget->geomLengthLabel = geomLengthLabel;
  widget->geomLengthUnitLabel = geomLengthUnitLabel;
  widget->geomFilenameLabel = geomFilenameLabel;
  widget->geomFilenameLineEdit = geomFilenameLineEdit;
  widget->geomFilenameButton = geomFilenameButton;

  geomFilenameLabel->setVisible(false);
  geomFilenameLineEdit->setVisible(false);
  geomFilenameButton->setVisible(false);

  connect(geometryComboBox, SIGNAL(currentIndexChanged(const QString)),
      widget, SLOT(GeometryChanged(const QString)));
  connect(geomFilenameButton, SIGNAL(clicked()), widget, SLOT(OnSelectFile()));

  widget->setLayout(widgetLayout);
  widget->widgets.push_back(geometryComboBox);
  widget->widgets.push_back(geomSizeXSpinBox);
  widget->widgets.push_back(geomSizeYSpinBox);
  widget->widgets.push_back(geomSizeZSpinBox);
  widget->widgets.push_back(geomRadiusSpinBox);
  widget->widgets.push_back(geomLengthSpinBox);
  widget->widgets.push_back(geomFilenameLineEdit);
  widget->widgets.push_back(geomFilenameButton);

  return widget;
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::CreateEnumWidget(
    const std::string &_key, const std::vector<std::string> &_values,
    const int _level)
{
  // Label
  QLabel *enumLabel = new QLabel(this->HumanReadableKey(_key).c_str());
  enumLabel->setToolTip(tr(_key.c_str()));

  // ComboBox
  QComboBox *enumComboBox = new QComboBox;

  for (unsigned int i = 0; i < _values.size(); ++i)
    enumComboBox->addItem(tr(_values[i].c_str()));

  // Layout
  QHBoxLayout *widgetLayout = new QHBoxLayout;
  if (_level != 0)
  {
    widgetLayout->addItem(new QSpacerItem(20*_level, 1,
        QSizePolicy::Fixed, QSizePolicy::Fixed));
  }
  widgetLayout->addWidget(enumLabel);
  widgetLayout->addWidget(enumComboBox);

  // ChildWidget
  EnumConfigWidget *widget = new EnumConfigWidget();
  widget->setLayout(widgetLayout);
  widget->setFrameStyle(QFrame::Box);
  connect(enumComboBox, SIGNAL(currentIndexChanged(const QString &)),
      widget, SLOT(EnumChanged(const QString &)));

  widget->widgets.push_back(enumComboBox);

  // connect enum config widget event so that we can fire another
  // event from ConfigWidget that has the name of this field
  connect(widget,
      SIGNAL(EnumValueChanged(const QString &)), this,
      SLOT(OnEnumValueChanged(const QString &)));

  return widget;
}

/////////////////////////////////////////////////
void ConfigWidget::UpdateMsg(google::protobuf::Message *_msg,
    const std::string &_name)
{
  const google::protobuf::Descriptor *d = _msg->GetDescriptor();
  if (!d)
    return;
  unsigned int count = d->field_count();

  for (unsigned int i = 0; i < count ; ++i)
  {
    const google::protobuf::FieldDescriptor *field = d->field(i);

    if (!field)
      return;

    const google::protobuf::Reflection *ref = _msg->GetReflection();

    if (!ref)
      return;

    std::string name = field->name();

    // Update each field in the message
    // TODO update repeated fields
    if (!field->is_repeated() /*&& ref->HasField(*_msg, field)*/)
    {
      std::string scopedName = _name.empty() ? name : _name + "::" + name;
      if (this->dataPtr->configWidgets.find(scopedName) ==
          this->dataPtr->configWidgets.end())
        continue;

      // don't update msgs field that are associated with read-only widgets
      if (this->WidgetReadOnly(scopedName))
        continue;

      ConfigChildWidget *childWidget = this->dataPtr->configWidgets[scopedName];

      switch (field->type())
      {
        case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
        {
          QDoubleSpinBox *valueSpinBox =
              qobject_cast<QDoubleSpinBox *>(childWidget->widgets[0]);
          ref->SetDouble(_msg, field, valueSpinBox->value());
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_FLOAT:
        {
          QDoubleSpinBox *valueSpinBox =
              qobject_cast<QDoubleSpinBox *>(childWidget->widgets[0]);
          ref->SetFloat(_msg, field, valueSpinBox->value());
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_INT64:
        {
          QSpinBox *valueSpinBox =
              qobject_cast<QSpinBox *>(childWidget->widgets[0]);
          ref->SetInt64(_msg, field, valueSpinBox->value());
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_UINT64:
        {
          QSpinBox *valueSpinBox =
              qobject_cast<QSpinBox *>(childWidget->widgets[0]);
          ref->SetUInt64(_msg, field, valueSpinBox->value());
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_INT32:
        {
          QSpinBox *valueSpinBox =
              qobject_cast<QSpinBox *>(childWidget->widgets[0]);
          ref->SetInt32(_msg, field, valueSpinBox->value());
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_UINT32:
        {
          QSpinBox *valueSpinBox =
              qobject_cast<QSpinBox *>(childWidget->widgets[0]);
          ref->SetUInt32(_msg, field, valueSpinBox->value());
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_BOOL:
        {
          QRadioButton *valueRadioButton =
              qobject_cast<QRadioButton *>(childWidget->widgets[0]);
          ref->SetBool(_msg, field, valueRadioButton->isChecked());
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_STRING:
        {
          if (qobject_cast<QLineEdit *>(childWidget->widgets[0]))
          {
            QLineEdit *valueLineEdit =
              qobject_cast<QLineEdit *>(childWidget->widgets[0]);
            ref->SetString(_msg, field, valueLineEdit->text().toStdString());
          }
          else if (qobject_cast<QPlainTextEdit *>(childWidget->widgets[0]))
          {
            QPlainTextEdit *valueTextEdit =
                qobject_cast<QPlainTextEdit *>(childWidget->widgets[0]);
            ref->SetString(_msg, field,
                valueTextEdit->toPlainText().toStdString());
          }
          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
        {
          google::protobuf::Message *valueMsg =
              (ref->MutableMessage(_msg, field));

          // update geometry msg field
          if (field->message_type()->name() == "Geometry")
          {
            // manually retrieve values from widgets in order to update
            // the message fields.
            QComboBox *valueComboBox =
                qobject_cast<QComboBox *>(childWidget->widgets[0]);
            std::string geomType = valueComboBox->currentText().toStdString();

            const google::protobuf::Descriptor *valueDescriptor =
                valueMsg->GetDescriptor();
            const google::protobuf::Reflection *geomReflection =
                valueMsg->GetReflection();
            const google::protobuf::FieldDescriptor *typeField =
                valueDescriptor->FindFieldByName("type");
            const google::protobuf::EnumDescriptor *typeEnumDescriptor =
                typeField->enum_type();

            if (geomType == "box" || geomType == "mesh")
            {
              double sizeX = qobject_cast<QDoubleSpinBox *>(
                  childWidget->widgets[1])->value();
              double sizeY = qobject_cast<QDoubleSpinBox *>(
                  childWidget->widgets[2])->value();
              double sizeZ = qobject_cast<QDoubleSpinBox *>(
                  childWidget->widgets[3])->value();
              ignition::math::Vector3d geomSize(sizeX, sizeY, sizeZ);

              // set type
              std::string typeStr =
                  QString(tr(geomType.c_str())).toUpper().toStdString();
              const google::protobuf::EnumValueDescriptor *geometryType =
                  typeEnumDescriptor->FindValueByName(typeStr);
              geomReflection->SetEnum(valueMsg, typeField, geometryType);

              // set dimensions
              const google::protobuf::FieldDescriptor *geomFieldDescriptor =
                valueDescriptor->FindFieldByName(geomType);
              google::protobuf::Message *geomValueMsg =
                  geomReflection->MutableMessage(valueMsg, geomFieldDescriptor);

              int fieldIdx = (geomType == "box") ? 0 : 1;
              google::protobuf::Message *geomDimensionMsg =
                  geomValueMsg->GetReflection()->MutableMessage(geomValueMsg,
                  geomValueMsg->GetDescriptor()->field(fieldIdx));
              this->UpdateVector3dMsg(geomDimensionMsg, geomSize);

              if (geomType == "mesh")
              {
                std::string uri = qobject_cast<QLineEdit *>(
                     childWidget->widgets[6])->text().toStdString();
                const google::protobuf::FieldDescriptor *uriFieldDescriptor =
                    geomValueMsg->GetDescriptor()->field(0);
                geomValueMsg->GetReflection()->SetString(geomValueMsg,
                    uriFieldDescriptor, uri);
              }
            }
            else if (geomType == "cylinder")
            {
              double radius = qobject_cast<QDoubleSpinBox *>(
                  childWidget->widgets[4])->value();
              double length = qobject_cast<QDoubleSpinBox *>(
                  childWidget->widgets[5])->value();

              // set type
              const google::protobuf::EnumValueDescriptor *geometryType =
                  typeEnumDescriptor->FindValueByName("CYLINDER");
              geomReflection->SetEnum(valueMsg, typeField, geometryType);

              // set radius and length
              const google::protobuf::FieldDescriptor *geomFieldDescriptor =
                valueDescriptor->FindFieldByName(geomType);
              google::protobuf::Message *geomValueMsg =
                  geomReflection->MutableMessage(valueMsg, geomFieldDescriptor);

              const google::protobuf::FieldDescriptor *geomRadiusField =
                  geomValueMsg->GetDescriptor()->field(0);
              geomValueMsg->GetReflection()->SetDouble(geomValueMsg,
                  geomRadiusField, radius);
              const google::protobuf::FieldDescriptor *geomLengthField =
                  geomValueMsg->GetDescriptor()->field(1);
              geomValueMsg->GetReflection()->SetDouble(geomValueMsg,
                  geomLengthField, length);
            }
            else if (geomType == "sphere")
            {
              double radius = qobject_cast<QDoubleSpinBox *>(
                  childWidget->widgets[4])->value();

              // set type
              const google::protobuf::EnumValueDescriptor *geometryType =
                  typeEnumDescriptor->FindValueByName("SPHERE");
              geomReflection->SetEnum(valueMsg, typeField, geometryType);

              // set radius
              const google::protobuf::FieldDescriptor *geomFieldDescriptor =
                valueDescriptor->FindFieldByName(geomType);
              google::protobuf::Message *geomValueMsg =
                  geomReflection->MutableMessage(valueMsg, geomFieldDescriptor);

              const google::protobuf::FieldDescriptor *geomRadiusField =
                  geomValueMsg->GetDescriptor()->field(0);
              geomValueMsg->GetReflection()->SetDouble(geomValueMsg,
                  geomRadiusField, radius);
            }
            else if (geomType == "polyline")
            {
              const google::protobuf::EnumValueDescriptor *geometryType =
                  typeEnumDescriptor->FindValueByName("POLYLINE");
              geomReflection->SetEnum(valueMsg, typeField, geometryType);
            }
          }
          // update pose msg field
          else if (field->message_type()->name() == "Pose")
          {
            const google::protobuf::Descriptor *valueDescriptor =
                valueMsg->GetDescriptor();
            int valueMsgFieldCount = valueDescriptor->field_count();

            // loop through the message fields to update:
            // a vector3d field (position)
            // and quaternion field (orientation)
            for (int j = 0; j < valueMsgFieldCount ; ++j)
            {
              const google::protobuf::FieldDescriptor *valueField =
                  valueDescriptor->field(j);

              if (valueField->type() !=
                  google::protobuf::FieldDescriptor::TYPE_MESSAGE)
                continue;

              if (valueField->message_type()->name() == "Vector3d")
              {
                // pos
                google::protobuf::Message *posValueMsg =
                    valueMsg->GetReflection()->MutableMessage(
                    valueMsg, valueField);
                std::vector<double> values;
                for (unsigned int k = 0; k < 3; ++k)
                {
                  QDoubleSpinBox *valueSpinBox =
                      qobject_cast<QDoubleSpinBox *>(childWidget->widgets[k]);
                  values.push_back(valueSpinBox->value());
                }
                ignition::math::Vector3d vec3(values[0], values[1], values[2]);
                this->UpdateVector3dMsg(posValueMsg, vec3);
              }
              else if (valueField->message_type()->name() == "Quaternion")
              {
                // rot
                google::protobuf::Message *quatValueMsg =
                    valueMsg->GetReflection()->MutableMessage(
                    valueMsg, valueField);
                std::vector<double> rotValues;
                for (unsigned int k = 3; k < 6; ++k)
                {
                  QDoubleSpinBox *valueSpinBox =
                      qobject_cast<QDoubleSpinBox *>(childWidget->widgets[k]);
                  rotValues.push_back(valueSpinBox->value());
                }
                math::Quaternion quat(rotValues[0], rotValues[1], rotValues[2]);

                std::vector<double> quatValues;
                quatValues.push_back(quat.x);
                quatValues.push_back(quat.y);
                quatValues.push_back(quat.z);
                quatValues.push_back(quat.w);
                const google::protobuf::Descriptor *quatValueDescriptor =
                    quatValueMsg->GetDescriptor();
                for (unsigned int k = 0; k < quatValues.size(); ++k)
                {
                  const google::protobuf::FieldDescriptor *quatValueField =
                      quatValueDescriptor->field(k);
                  quatValueMsg->GetReflection()->SetDouble(quatValueMsg,
                      quatValueField, quatValues[k]);
                }
              }
            }
          }
          else if (field->message_type()->name() == "Vector3d")
          {
            std::vector<double> values;
            for (unsigned int j = 0; j < 3; ++j)
            {
              QDoubleSpinBox *valueSpinBox =
                  qobject_cast<QDoubleSpinBox *>(childWidget->widgets[j]);
              values.push_back(valueSpinBox->value());
            }
            ignition::math::Vector3d vec3(values[0], values[1], values[2]);
            this->UpdateVector3dMsg(valueMsg, vec3);
          }
          else if (field->message_type()->name() == "Color")
          {
            const google::protobuf::Descriptor *valueDescriptor =
                valueMsg->GetDescriptor();
            for (unsigned int j = 0; j < childWidget->widgets.size(); ++j)
            {
              QDoubleSpinBox *valueSpinBox =
                  qobject_cast<QDoubleSpinBox *>(childWidget->widgets[j]);
              const google::protobuf::FieldDescriptor *valueField =
                  valueDescriptor->field(j);
              valueMsg->GetReflection()->SetFloat(valueMsg, valueField,
                  valueSpinBox->value());
            }
          }
          else
          {
            // update the message fields recursively
            this->UpdateMsg(valueMsg, scopedName);
          }

          break;
        }
        case google::protobuf::FieldDescriptor::TYPE_ENUM:
        {
          QComboBox *valueComboBox =
              qobject_cast<QComboBox *>(childWidget->widgets[0]);
          if (valueComboBox)
          {
            std::string valueStr = valueComboBox->currentText().toStdString();
            const google::protobuf::EnumDescriptor *enumDescriptor =
                field->enum_type();
            if (enumDescriptor)
            {
              const google::protobuf::EnumValueDescriptor *enumValue =
                  enumDescriptor->FindValueByName(valueStr);
              if (enumValue)
                ref->SetEnum(_msg, field, enumValue);
              else
                gzerr << "Unable to find enum value: '" << valueStr << "'"
                    << std::endl;
            }
          }
          break;
        }
        default:
          break;
      }
    }
  }
}

/////////////////////////////////////////////////
void ConfigWidget::UpdateVector3dMsg(google::protobuf::Message *_msg,
    const ignition::math::Vector3d &_value)
{
  const google::protobuf::Descriptor *valueDescriptor = _msg->GetDescriptor();

  std::vector<double> values;
  values.push_back(_value.X());
  values.push_back(_value.Y());
  values.push_back(_value.Z());

  for (unsigned int i = 0; i < 3; ++i)
  {
    const google::protobuf::FieldDescriptor *valueField =
        valueDescriptor->field(i);
    _msg->GetReflection()->SetDouble(_msg, valueField, values[i]);
  }
}

/////////////////////////////////////////////////
bool ConfigWidget::UpdateIntWidget(ConfigChildWidget *_widget,  int _value)
{
  if (_widget->widgets.size() == 1u)
  {
    qobject_cast<QSpinBox *>(_widget->widgets[0])->setValue(_value);
    return true;
  }
  else
  {
    gzerr << "Error updating Int Config widget" << std::endl;
  }
  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::UpdateUIntWidget(ConfigChildWidget *_widget,
    unsigned int _value)
{
  if (_widget->widgets.size() == 1u)
  {
    qobject_cast<QSpinBox *>(_widget->widgets[0])->setValue(_value);
    return true;
  }
  else
  {
    gzerr << "Error updating UInt Config widget" << std::endl;
  }
  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::UpdateDoubleWidget(ConfigChildWidget *_widget, double _value)
{
  if (_widget->widgets.size() == 1u)
  {
    // Spin value
    QDoubleSpinBox *spin =
        qobject_cast<QDoubleSpinBox *>(_widget->widgets[0]);
    spin->setValue(_value);

    // Unit label
    std::string jointType = this->EnumWidgetValue("type");
    std::string unit = this->UnitFromKey(_widget->key, jointType);
    qobject_cast<QLabel *>(
        _widget->mapWidgetToUnit[spin])->setText(QString::fromStdString(unit));

    return true;
  }
  else
  {
    gzerr << "Error updating Double Config widget" << std::endl;
  }
  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::UpdateStringWidget(ConfigChildWidget *_widget,
    const std::string &_value)
{
  if (_widget->widgets.size() == 1u)
  {
    if (qobject_cast<QLineEdit *>(_widget->widgets[0]))
    {
      qobject_cast<QLineEdit *>(_widget->widgets[0])
          ->setText(tr(_value.c_str()));
      return true;
    }
    else if (qobject_cast<QPlainTextEdit *>(_widget->widgets[0]))
    {
      qobject_cast<QPlainTextEdit *>(_widget->widgets[0])
          ->setPlainText(tr(_value.c_str()));
      return true;
    }
  }
  else
  {
    gzerr << "Error updating String Config Widget" << std::endl;
  }
  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::UpdateBoolWidget(ConfigChildWidget *_widget, bool _value)
{
  if (_widget->widgets.size() == 2u)
  {
    qobject_cast<QRadioButton *>(_widget->widgets[0])->setChecked(_value);
    qobject_cast<QRadioButton *>(_widget->widgets[1])->setChecked(!_value);
    return true;
  }
  else
  {
    gzerr << "Error updating Bool Config widget" << std::endl;
  }
  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::UpdateVector3dWidget(ConfigChildWidget *_widget,
    const ignition::math::Vector3d &_vec)
{
  if (_widget->widgets.size() == 4u)
  {
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[0])->setValue(_vec.X());
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[1])->setValue(_vec.Y());
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[2])->setValue(_vec.Z());

    // Update preset
    int preset = 0;
    if (_vec == ignition::math::Vector3d::UnitX)
      preset = 1;
    else if (_vec == -ignition::math::Vector3d::UnitX)
      preset = 2;
    else if (_vec == ignition::math::Vector3d::UnitY)
      preset = 3;
    else if (_vec == -ignition::math::Vector3d::UnitY)
      preset = 4;
    else if (_vec == ignition::math::Vector3d::UnitZ)
      preset = 5;
    else if (_vec == -ignition::math::Vector3d::UnitZ)
      preset = 6;

    qobject_cast<QComboBox *>(_widget->widgets[3])->setCurrentIndex(preset);

    return true;
  }
  else
  {
    gzerr << "Error updating Vector3 Config widget" << std::endl;
  }
  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::UpdateColorWidget(ConfigChildWidget *_widget,
    const common::Color &_color)
{
  if (_widget->widgets.size() == 4u)
  {
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[0])->setValue(_color.r);
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[1])->setValue(_color.g);
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[2])->setValue(_color.b);
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[3])->setValue(_color.a);
    return true;
  }
  else
  {
    gzerr << "Error updating Color Config widget" << std::endl;
  }
  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::UpdatePoseWidget(ConfigChildWidget *_widget,
    const ignition::math::Pose3d &_pose)
{
  if (_widget->widgets.size() == 6u)
  {
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[0])->setValue(
        _pose.Pos().X());
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[1])->setValue(
        _pose.Pos().Y());
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[2])->setValue(
        _pose.Pos().Z());

    ignition::math::Vector3d rot = _pose.Rot().Euler();
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[3])->setValue(rot.X());
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[4])->setValue(rot.Y());
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[5])->setValue(rot.Z());
    return true;
  }
  else
  {
    gzerr << "Error updating Pose Config widget" << std::endl;
  }
  return false;
}

/////////////////////////////////////////////////
bool ConfigWidget::UpdateGeometryWidget(ConfigChildWidget *_widget,
    const std::string &_value, const ignition::math::Vector3d &_dimensions,
    const std::string &_uri)
{
  if (_widget->widgets.size() != 8u)
  {
    gzerr << "Error updating Geometry Config widget " << std::endl;
    return false;
  }

  QComboBox *valueComboBox = qobject_cast<QComboBox *>(_widget->widgets[0]);
  int index = valueComboBox->findText(tr(_value.c_str()));

  if (index < 0)
  {
    gzerr << "Error updating Geometry Config widget: '" << _value <<
      "' not found" << std::endl;
    return false;
  }

  qobject_cast<QComboBox *>(_widget->widgets[0])->setCurrentIndex(index);

  bool isMesh =  _value == "mesh";
  if (_value == "box" || isMesh)
  {
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[1])->setValue(
        _dimensions.X());
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[2])->setValue(
        _dimensions.Y());
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[3])->setValue(
        _dimensions.Z());
  }
  else if (_value == "cylinder")
  {
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[4])->setValue(
        _dimensions.X()*0.5);
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[5])->setValue(
        _dimensions.Z());
  }
  else if (_value == "sphere")
  {
    qobject_cast<QDoubleSpinBox *>(_widget->widgets[4])->setValue(
        _dimensions.X()*0.5);
  }
  else if (_value == "polyline")
  {
    // do nothing
  }

  if (isMesh)
    qobject_cast<QLineEdit *>(_widget->widgets[6])->setText(tr(_uri.c_str()));

  return true;
}

/////////////////////////////////////////////////
bool ConfigWidget::UpdateEnumWidget(ConfigChildWidget *_widget,
    const std::string &_value)
{
  if (_widget->widgets.size() != 1u)
  {
    gzerr << "Error updating Enum Config widget" << std::endl;
    return false;
  }

  QComboBox *valueComboBox = qobject_cast<QComboBox *>(_widget->widgets[0]);
  if (!valueComboBox)
  {
    gzerr << "Error updating Enum Config widget" << std::endl;
    return false;
  }

  int index = valueComboBox->findText(tr(_value.c_str()));

  if (index < 0)
  {
    gzerr << "Error updating Enum Config widget: '" << _value <<
      "' not found" << std::endl;
    return false;
  }

  qobject_cast<QComboBox *>(_widget->widgets[0])->setCurrentIndex(index);

  return true;
}

/////////////////////////////////////////////////
int ConfigWidget::IntWidgetValue(ConfigChildWidget *_widget) const
{
  int value = 0;
  if (_widget->widgets.size() == 1u)
  {
    value = qobject_cast<QSpinBox *>(_widget->widgets[0])->value();
  }
  else
  {
    gzerr << "Error getting value from Int Config widget" << std::endl;
  }
  return value;
}

/////////////////////////////////////////////////
unsigned int ConfigWidget::UIntWidgetValue(ConfigChildWidget *_widget) const
{
  unsigned int value = 0;
  if (_widget->widgets.size() == 1u)
  {
    value = qobject_cast<QSpinBox *>(_widget->widgets[0])->value();
  }
  else
  {
    gzerr << "Error getting value from UInt Config widget" << std::endl;
  }
  return value;
}

/////////////////////////////////////////////////
double ConfigWidget::DoubleWidgetValue(ConfigChildWidget *_widget) const
{
  double value = 0.0;
  if (_widget->widgets.size() == 1u)
  {
    value = qobject_cast<QDoubleSpinBox *>(_widget->widgets[0])->value();
  }
  else
  {
    gzerr << "Error getting value from Double Config widget" << std::endl;
  }
  return value;
}

/////////////////////////////////////////////////
std::string ConfigWidget::StringWidgetValue(ConfigChildWidget *_widget) const
{
  std::string value;
  if (_widget->widgets.size() == 1u)
  {
    if (qobject_cast<QLineEdit *>(_widget->widgets[0]))
    {
      value =
          qobject_cast<QLineEdit *>(_widget->widgets[0])->text().toStdString();
    }
    else if (qobject_cast<QPlainTextEdit *>(_widget->widgets[0]))
    {
      value = qobject_cast<QPlainTextEdit *>(_widget->widgets[0])
          ->toPlainText().toStdString();
    }
  }
  else
  {
    gzerr << "Error getting value from String Config Widget" << std::endl;
  }
  return value;
}

/////////////////////////////////////////////////
bool ConfigWidget::BoolWidgetValue(ConfigChildWidget *_widget) const
{
  bool value = false;
  if (_widget->widgets.size() == 2u)
  {
    value = qobject_cast<QRadioButton *>(_widget->widgets[0])->isChecked();
  }
  else
  {
    gzerr << "Error getting value from Bool Config widget" << std::endl;
  }
  return value;
}

/////////////////////////////////////////////////
ignition::math::Vector3d ConfigWidget::Vector3dWidgetValue(
    ConfigChildWidget *_widget) const
{
  ignition::math::Vector3d value;
  if (_widget->widgets.size() == 4u)
  {
    value.X(qobject_cast<QDoubleSpinBox *>(_widget->widgets[0])->value());
    value.Y(qobject_cast<QDoubleSpinBox *>(_widget->widgets[1])->value());
    value.Z(qobject_cast<QDoubleSpinBox *>(_widget->widgets[2])->value());
  }
  else
  {
    gzerr << "Error getting value from Vector3 Config widget" << std::endl;
  }
  return value;
}

/////////////////////////////////////////////////
common::Color ConfigWidget::ColorWidgetValue(ConfigChildWidget *_widget)
    const
{
  common::Color value;
  if (_widget->widgets.size() == 4u)
  {
    value.r = qobject_cast<QDoubleSpinBox *>(_widget->widgets[0])->value();
    value.g = qobject_cast<QDoubleSpinBox *>(_widget->widgets[1])->value();
    value.b = qobject_cast<QDoubleSpinBox *>(_widget->widgets[2])->value();
    value.a = qobject_cast<QDoubleSpinBox *>(_widget->widgets[3])->value();
  }
  else
  {
    gzerr << "Error getting value from Color Config widget" << std::endl;
  }
  return value;
}

/////////////////////////////////////////////////
ignition::math::Pose3d ConfigWidget::PoseWidgetValue(ConfigChildWidget *_widget)
    const
{
  ignition::math::Pose3d value;
  if (_widget->widgets.size() == 6u)
  {
    value.Pos().X(qobject_cast<QDoubleSpinBox *>(_widget->widgets[0])->value());
    value.Pos().Y(qobject_cast<QDoubleSpinBox *>(_widget->widgets[1])->value());
    value.Pos().Z(qobject_cast<QDoubleSpinBox *>(_widget->widgets[2])->value());

    ignition::math::Vector3d rot;
    rot.X(qobject_cast<QDoubleSpinBox *>(_widget->widgets[3])->value());
    rot.Y(qobject_cast<QDoubleSpinBox *>(_widget->widgets[4])->value());
    rot.Z(qobject_cast<QDoubleSpinBox *>(_widget->widgets[5])->value());
    value.Rot().Euler(rot);
  }
  else
  {
    gzerr << "Error getting value from Pose Config widget" << std::endl;
  }
  return value;
}

/////////////////////////////////////////////////
std::string ConfigWidget::GeometryWidgetValue(ConfigChildWidget *_widget,
    ignition::math::Vector3d &_dimensions, std::string &_uri) const
{
  std::string value;
  if (_widget->widgets.size() != 8u)
  {
    gzerr << "Error getting value from Geometry Config widget " << std::endl;
    return value;
  }

  QComboBox *valueComboBox = qobject_cast<QComboBox *>(_widget->widgets[0]);
  value = valueComboBox->currentText().toStdString();

  bool isMesh = value == "mesh";
  if (value == "box" || isMesh)
  {
    _dimensions.X(qobject_cast<QDoubleSpinBox *>(_widget->widgets[1])->value());
    _dimensions.Y(qobject_cast<QDoubleSpinBox *>(_widget->widgets[2])->value());
    _dimensions.Z(qobject_cast<QDoubleSpinBox *>(_widget->widgets[3])->value());
  }
  else if (value == "cylinder")
  {
    _dimensions.X(
        qobject_cast<QDoubleSpinBox *>(_widget->widgets[4])->value()*2.0);
    _dimensions.Y(_dimensions.X());
    _dimensions.Z(qobject_cast<QDoubleSpinBox *>(_widget->widgets[5])->value());
  }
  else if (value == "sphere")
  {
    _dimensions.X(
        qobject_cast<QDoubleSpinBox *>(_widget->widgets[4])->value()*2.0);
    _dimensions.Y(_dimensions.X());
    _dimensions.Z(_dimensions.X());
  }
  else if (value == "polyline")
  {
    // do nothing
  }
  else
  {
    gzerr << "Error getting geometry dimensions for type: '" << value << "'"
        << std::endl;
  }

  if (isMesh)
    _uri = qobject_cast<QLineEdit *>(_widget->widgets[6])->text().toStdString();

  return value;
}

/////////////////////////////////////////////////
std::string ConfigWidget::EnumWidgetValue(ConfigChildWidget *_widget) const
{
  std::string value;
  if (_widget->widgets.size() != 1u)
  {
    gzerr << "Error getting value from Enum Config widget " << std::endl;
    return value;
  }

  QComboBox *valueComboBox = qobject_cast<QComboBox *>(_widget->widgets[0]);
  value = valueComboBox->currentText().toStdString();

  return value;
}

/////////////////////////////////////////////////
void ConfigWidget::OnItemSelection(QTreeWidgetItem *_item,
                                   const int /*_column*/)
{
  if (_item && _item->childCount() > 0)
    _item->setExpanded(!_item->isExpanded());
}

/////////////////////////////////////////////////
void ConfigWidget::OnUIntValueChanged()
{
  QSpinBox *spin =
      qobject_cast<QSpinBox *>(QObject::sender());

  if (!spin)
    return;

  ConfigChildWidget *widget =
      qobject_cast<ConfigChildWidget *>(spin->parent());

  if (!widget)
    return;

  emit UIntValueChanged(widget->scopedName.c_str(),
      this->UIntWidgetValue(widget));
}

/////////////////////////////////////////////////
void ConfigWidget::OnIntValueChanged()
{
  QSpinBox *spin =
      qobject_cast<QSpinBox *>(QObject::sender());

  if (!spin)
    return;

  ConfigChildWidget *widget =
      qobject_cast<ConfigChildWidget *>(spin->parent());

  if (!widget)
    return;

  emit IntValueChanged(widget->scopedName.c_str(),
      this->IntWidgetValue(widget));
}

/////////////////////////////////////////////////
void ConfigWidget::OnDoubleValueChanged()
{
  QDoubleSpinBox *spin =
      qobject_cast<QDoubleSpinBox *>(QObject::sender());

  if (!spin)
    return;

  ConfigChildWidget *widget =
      qobject_cast<ConfigChildWidget *>(spin->parent());

  if (!widget)
    return;

  emit DoubleValueChanged(widget->scopedName.c_str(),
      this->DoubleWidgetValue(widget));
}

/////////////////////////////////////////////////
void ConfigWidget::OnBoolValueChanged()
{
  QRadioButton *radio =
      qobject_cast<QRadioButton *>(QObject::sender());

  if (!radio)
    return;

  ConfigChildWidget *widget =
      qobject_cast<ConfigChildWidget *>(radio->parent());

  if (!widget)
    return;

  emit BoolValueChanged(widget->scopedName.c_str(),
      this->BoolWidgetValue(widget));
}

/////////////////////////////////////////////////
void ConfigWidget::OnStringValueChanged()
{
  QLineEdit *lineEdit = qobject_cast<QLineEdit *>(QObject::sender());
  QPlainTextEdit *plainTextEdit =
      qobject_cast<QPlainTextEdit *>(QObject::sender());

  QWidget *valueEdit;
  if (!lineEdit && !plainTextEdit)
    return;
  else if (lineEdit)
    valueEdit = lineEdit;
  else
    valueEdit = plainTextEdit;

  ConfigChildWidget *widget =
      qobject_cast<ConfigChildWidget *>(valueEdit->parent());

  if (!widget)
    return;

  emit StringValueChanged(widget->scopedName.c_str(),
      this->StringWidgetValue(widget));
}

/////////////////////////////////////////////////
void ConfigWidget::OnVector3dValueChanged()
{
  QDoubleSpinBox *spin =
      qobject_cast<QDoubleSpinBox *>(QObject::sender());

  if (!spin)
    return;

  ConfigChildWidget *widget =
      qobject_cast<ConfigChildWidget *>(spin->parent());

  if (!widget)
    return;

  auto value = this->Vector3dWidgetValue(widget);

  // Update preset
  this->UpdateVector3dWidget(widget, value);

  // Signal
  emit Vector3dValueChanged(widget->scopedName.c_str(), value);
}

/////////////////////////////////////////////////
void ConfigWidget::OnVector3dPresetChanged(const int _index)
{
  auto combo = qobject_cast<QComboBox *>(QObject::sender());

  if (!combo)
    return;

  auto widget = qobject_cast<ConfigChildWidget *>(combo->parent());

  if (!widget)
    return;

  // Update spins
  ignition::math::Vector3d vec;
  if (_index == 1)
    vec = ignition::math::Vector3d::UnitX;
  else if (_index == 2)
    vec = -ignition::math::Vector3d::UnitX;
  else if (_index == 3)
    vec = ignition::math::Vector3d::UnitY;
  else if (_index == 4)
    vec = -ignition::math::Vector3d::UnitY;
  else if (_index == 5)
    vec = ignition::math::Vector3d::UnitZ;
  else if (_index == 6)
    vec = -ignition::math::Vector3d::UnitZ;
  else
    return;

  this->UpdateVector3dWidget(widget, vec);

  // Signal
  emit Vector3dValueChanged(widget->scopedName.c_str(), vec);
}

/////////////////////////////////////////////////
void ConfigWidget::OnColorValueChanged()
{
  QDoubleSpinBox *spin =
      qobject_cast<QDoubleSpinBox *>(QObject::sender());

  if (!spin)
    return;

  ConfigChildWidget *widget =
      qobject_cast<ConfigChildWidget *>(spin->parent());

  if (!widget)
    return;

  emit ColorValueChanged(widget->scopedName.c_str(),
      this->ColorWidgetValue(widget));
}

/////////////////////////////////////////////////
void ConfigWidget::OnPoseValueChanged()
{
  QDoubleSpinBox *spin =
      qobject_cast<QDoubleSpinBox *>(QObject::sender());

  if (!spin)
    return;

  ConfigChildWidget *widget =
      qobject_cast<ConfigChildWidget *>(spin->parent());

  if (!widget)
    return;

  emit PoseValueChanged(widget->scopedName.c_str(),
      this->PoseWidgetValue(widget));
}

/////////////////////////////////////////////////
void ConfigWidget::OnGeometryValueChanged()
{
  QWidget *senderWidget = qobject_cast<QWidget *>(QObject::sender());

  if (!senderWidget)
    return;

  ConfigChildWidget *widget;
  while (senderWidget->parent() != NULL)
  {
    senderWidget = qobject_cast<QWidget *>(senderWidget->parent());
    widget = qobject_cast<ConfigChildWidget *>(senderWidget);
    if (widget)
      break;
  }

  if (!widget)
    return;

  ignition::math::Vector3d dimensions;
  std::string uri;
  std::string value = this->GeometryWidgetValue(widget, dimensions, uri);

  emit GeometryValueChanged(widget->scopedName.c_str(), value, dimensions,
      uri);
}

/////////////////////////////////////////////////
void ConfigWidget::OnGeometryValueChanged(const int /*_value*/)
{
  QComboBox *combo =
      qobject_cast<QComboBox *>(QObject::sender());

  if (!combo)
    return;

  GeometryConfigWidget *widget =
      qobject_cast<GeometryConfigWidget *>(combo->parent());

  if (!widget)
    return;

  ignition::math::Vector3d dimensions;
  std::string uri;
  std::string value = this->GeometryWidgetValue(widget, dimensions, uri);

  emit GeometryValueChanged(widget->scopedName.c_str(), value, dimensions, uri);
}

/////////////////////////////////////////////////
void ConfigWidget::OnEnumValueChanged(const QString &_value)
{
  ConfigChildWidget *widget =
      qobject_cast<ConfigChildWidget *>(QObject::sender());

  if (!widget)
    return;

  emit EnumValueChanged(widget->scopedName.c_str(), _value);
}

/////////////////////////////////////////////////
bool ConfigWidget::AddConfigChildWidget(const std::string &_name,
    ConfigChildWidget *_child)
{
  if (_name.empty() || _child == NULL)
  {
    gzerr << "Given name or child is invalid. Not adding child widget."
          << std::endl;
    return false;
  }
  if (this->dataPtr->configWidgets.find(_name) !=
      this->dataPtr->configWidgets.end())
  {
    gzerr << "This config widget already has a child with that name. " <<
       "Names must be unique. Not adding child." << std::endl;
    return false;
  }

  _child->scopedName = _name;
  this->dataPtr->configWidgets[_name] = _child;
  return true;
}

/////////////////////////////////////////////////
unsigned int ConfigWidget::ConfigChildWidgetCount() const
{
  return this->dataPtr->configWidgets.size();
}

/////////////////////////////////////////////////
bool ConfigWidget::eventFilter(QObject *_obj, QEvent *_event)
{
  QAbstractSpinBox *spinBox = qobject_cast<QAbstractSpinBox *>(_obj);
  QComboBox *comboBox = qobject_cast<QComboBox *>(_obj);
  if (spinBox || comboBox)
  {
    QWidget *widget = qobject_cast<QWidget *>(_obj);
    if (_event->type() == QEvent::Wheel)
    {
      if (widget->focusPolicy() == Qt::WheelFocus)
      {
        _event->accept();
        return false;
      }
      else
      {
        _event->ignore();
        return true;
      }
    }
    else if (_event->type() == QEvent::FocusIn)
    {
      widget->setFocusPolicy(Qt::WheelFocus);
    }
    else if (_event->type() == QEvent::FocusOut)
    {
      widget->setFocusPolicy(Qt::StrongFocus);
    }
  }
  return QObject::eventFilter(_obj, _event);
}

/////////////////////////////////////////////////
void GroupWidget::Toggle(bool _checked)
{
  if (!this->childWidget)
    return;

  this->childWidget->setVisible(_checked);
}

/////////////////////////////////////////////////
void GeometryConfigWidget::GeometryChanged(const QString _text)
{
  QWidget *widget= qobject_cast<QWidget *>(QObject::sender());

  if (widget)
  {
    std::string textStr = _text.toStdString();
    bool isMesh = (textStr == "mesh");
    if (textStr == "box" || isMesh)
    {
      this->geomDimensionWidget->show();
      this->geomDimensionWidget->setCurrentIndex(0);
    }
    else if (textStr == "cylinder")
    {
      this->geomDimensionWidget->show();
      this->geomDimensionWidget->setCurrentIndex(1);
      this->geomLengthSpinBox->show();
      this->geomLengthLabel->show();
      this->geomLengthUnitLabel->show();
    }
    else if (textStr == "sphere")
    {
      this->geomDimensionWidget->show();
      this->geomDimensionWidget->setCurrentIndex(1);
      this->geomLengthSpinBox->hide();
      this->geomLengthLabel->hide();
      this->geomLengthUnitLabel->hide();
    }
    else if (textStr == "polyline")
    {
      this->geomDimensionWidget->hide();
    }

    this->geomFilenameLabel->setVisible(isMesh);
    this->geomFilenameLineEdit->setVisible(isMesh);
    this->geomFilenameButton->setVisible(isMesh);
  }
}

/////////////////////////////////////////////////
void GeometryConfigWidget::OnSelectFile()
{
  QWidget *widget= qobject_cast<QWidget *>(QObject::sender());

  if (widget)
  {
    QFileDialog fd(this, tr("Select mesh file"), QDir::homePath(),
      tr("Mesh files (*.dae *.stl)"));
    fd.setFilter(QDir::AllDirs | QDir::Hidden);
    fd.setFileMode(QFileDialog::ExistingFile);
    fd.setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint |
        Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint);
    if (fd.exec())
    {
      if (!fd.selectedFiles().isEmpty())
      {
        QString file = fd.selectedFiles().at(0);
        if (!file.isEmpty())
        {
          dynamic_cast<QLineEdit *>(this->geomFilenameLineEdit)->setText(file);
        }
      }
    }
  }
}

/////////////////////////////////////////////////
void EnumConfigWidget::EnumChanged(const QString &_value)
{
  emit EnumValueChanged(_value);
}

/////////////////////////////////////////////////
bool ConfigWidget::ClearEnumWidget(const std::string &_name)
{
  // Find widget
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter == this->dataPtr->configWidgets.end())
    return false;

  EnumConfigWidget *enumWidget = dynamic_cast<EnumConfigWidget *>(iter->second);

  if (enumWidget->widgets.size() != 1u)
  {
    gzerr << "Enum config widget has wrong number of widgets." << std::endl;
    return false;
  }

  QComboBox *valueComboBox = qobject_cast<QComboBox *>(enumWidget->widgets[0]);
  if (!valueComboBox)
  {
    gzerr << "Enum config widget doesn't have a QComboBox." << std::endl;
    return false;
  }

  // Clear
  valueComboBox->blockSignals(true);
  valueComboBox->clear();
  valueComboBox->blockSignals(false);
  return true;
}

/////////////////////////////////////////////////
bool ConfigWidget::AddItemEnumWidget(const std::string &_name,
    const std::string &_itemText)
{
  // Find widget
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter == this->dataPtr->configWidgets.end())
    return false;

  EnumConfigWidget *enumWidget = dynamic_cast<EnumConfigWidget *>(iter->second);

  if (enumWidget->widgets.size() != 1u)
  {
    gzerr << "Enum config widget has wrong number of widgets." << std::endl;
    return false;
  }

  QComboBox *valueComboBox = qobject_cast<QComboBox *>(enumWidget->widgets[0]);
  if (!valueComboBox)
  {
    gzerr << "Enum config widget doesn't have a QComboBox." << std::endl;
    return false;
  }

  // Add item
  valueComboBox->blockSignals(true);
  valueComboBox->addItem(QString::fromStdString(_itemText));
  valueComboBox->blockSignals(false);

  return true;
}

/////////////////////////////////////////////////
bool ConfigWidget::RemoveItemEnumWidget(const std::string &_name,
    const std::string &_itemText)
{
  // Find widget
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter == this->dataPtr->configWidgets.end())
    return false;

  EnumConfigWidget *enumWidget = dynamic_cast<EnumConfigWidget *>(iter->second);

  if (enumWidget->widgets.size() != 1u)
  {
    gzerr << "Enum config widget has wrong number of widgets." << std::endl;
    return false;
  }

  QComboBox *valueComboBox = qobject_cast<QComboBox *>(enumWidget->widgets[0]);
  if (!valueComboBox)
  {
    gzerr << "Enum config widget doesn't have a QComboBox." << std::endl;
    return false;
  }

  // Remove item if exists, otherwise return false
  int index = valueComboBox->findText(QString::fromStdString(
      _itemText));
  if (index < 0)
    return false;

  valueComboBox->blockSignals(true);
  valueComboBox->removeItem(index);
  valueComboBox->blockSignals(false);

  return true;
}

/////////////////////////////////////////////////
void ConfigWidget::InsertLayout(QLayout *_layout, int _pos)
{
  QGroupBox *box = qobject_cast<QGroupBox *>(
      this->layout()->itemAt(0)->widget());
  if (!box)
    return;

  QVBoxLayout *boxLayout = qobject_cast<QVBoxLayout *>(box->layout());
  if (!boxLayout)
    return;

  boxLayout->insertLayout(_pos, _layout);
}

/////////////////////////////////////////////////
ConfigChildWidget *ConfigWidget::ConfigChildWidgetByName(
    const std::string &_name) const
{
  auto iter = this->dataPtr->configWidgets.find(_name);

  if (iter != this->dataPtr->configWidgets.end())
    return iter->second;
  else
    return NULL;
}

/////////////////////////////////////////////////
QString ConfigWidget::StyleSheet(const std::string &_type, const int _level)
{
  if (_type == "normal")
  {
    return "QWidget\
        {\
          background-color: " + ConfigWidget::bgColors[_level] + ";\
          color: #4c4c4c;\
        }\
        QLabel\
        {\
          color: #d0d0d0;\
        }\
        QDoubleSpinBox, QSpinBox, QLineEdit, QComboBox\
        {\
          background-color: " + ConfigWidget::widgetColors[_level] +
        "}";
  }
  else if (_type == "warning")
  {
    return "QWidget\
      {\
        background-color: " + ConfigWidget::bgColors[_level] + ";\
        color: " + ConfigWidget::redColor + ";\
      }\
      QDoubleSpinBox, QSpinBox, QLineEdit, QComboBox\
      {\
        background-color: " + ConfigWidget::widgetColors[_level] +
      "}";
  }
  else if (_type == "active")
  {
    return "QWidget\
      {\
        background-color: " + ConfigWidget::bgColors[_level] + ";\
        color: " + ConfigWidget::greenColor + ";\
      }\
      QDoubleSpinBox, QSpinBox, QLineEdit, QComboBox\
      {\
        background-color: " + ConfigWidget::widgetColors[_level] +
      "}";
  }
  gzwarn << "Requested unknown style sheet type [" << _type << "]" << std::endl;
  return "";
}
