diff --git a/.gitmodules b/.gitmodules
index aeaaeb50ec5b3705b1bdf9fb250f6ce5dac6664a..318e50e507845db1f06a7e49415d0680acafafce 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
-[submodule "radix"]
-	path = submodules/radix
-	url = https://code.ornl.gov/jap/radix.git
 [submodule "TriBITS"]
 	path = submodules/TriBITS
 	url = https://github.com/lefebvre/TriBITS.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6253317af1b2ac21cd4b188d362ef5bde87d2786..6277458bf414657481c1aba59338a368a059c3c6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,8 +29,8 @@ IF (NOT TRIBITS_PROCESSING_PACKAGE)
   IF(DEBUG_OUTPUT)
      ADD_DEFINITIONS("-DDEBUG_OUTPUT=${DEBUG_OUTPUT}")
   ENDIF()
-  IF(RADIX_DBC)
-     ADD_DEFINITIONS("-DRADIX_DBC=${RADIX_DBC}")
+  IF(QCP_DBC)
+     ADD_DEFINITIONS("-QCP_DBC=${QCP_DBC}")
   ENDIF()
   #
   # For windows with BUILD_SHARED_LIBS we must use CMAKE_RUNTIME_OUTPUT_DIRECTORY
@@ -40,7 +40,7 @@ IF (NOT TRIBITS_PROCESSING_PACKAGE)
       SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/cmake_runtime_output" CACHE STRING "")
     ENDIF()
   ENDIF()
-
+  SET(USE_QT5 ON CACHE BOOL "")
   TRIBITS_PROJECT()
 ELSE()
   # This CMakeLists.txt file is being processed as the TriBITS package file.
diff --git a/PackagesList.cmake b/PackagesList.cmake
index 2a30980d01cdf0ca7f5444fdca89ffec49ad928e..8a9dd464d4d2da59cb9a0b3d2d190d662faefa93 100644
--- a/PackagesList.cmake
+++ b/PackagesList.cmake
@@ -13,14 +13,12 @@
 TRIBITS_REPOSITORY_DEFINE_PACKAGES(
   googletest submodules/googletest/googletest PT
   testframework submodules/testframework PT
-  radix submodules/radix PT
   qcp . PT
 )
 
 TRIBITS_ALLOW_MISSING_EXTERNAL_PACKAGES(
   googletest
   testframework
-  radix
 )
 
 ##---------------------------------------------------------------------------##
diff --git a/README.md b/README.md
index d3511aeb5be2a516770d77b9ceb1bc4aefae6693..bc9758ee9e034af7cad975d3e1e69bab64e9f59a 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 # Getting Started
 * You will need to save your ssh-key in [code.ornl.gov](https://code.ornl.gov/profile/keys).
 * Clone qcp
-`git clone git@code.ornl.gov:jap/qcp.git ~/qcp`
+`git clone git@code.ornl.gov:nsm/qcp.git ~/qcp`
 * Initialize and update git submodules
 `git submodule init && git submodule update`
 * Change directory into qcp `cd ~/qcp`
diff --git a/qcpcontext/CMakeLists.txt b/qcpcontext/CMakeLists.txt
index be1bc49dd216a38ebc643315822a63436226fa94..2850eb7545a63d7546ac0479a805cd2fde981c20 100644
--- a/qcpcontext/CMakeLists.txt
+++ b/qcpcontext/CMakeLists.txt
@@ -18,12 +18,16 @@ TRIBITS_SUBPACKAGE(context)
 ##---------------------------------------------------------------------------##
 # C/C++ headers
 SET(MOC_HEADERS
+   navigationmodel.hh
+   navigationwidget.hh
+   navigationactionmanager.hh
    qcpcontext.hh
    qcpcustom.hh
    qcpoptionsdialog.hh
 )
 SET(HEADERS
    ${MOC_HEADERS}
+   navigationitem.hh
    qcpgraphdata.hh
 )
 #
@@ -39,6 +43,11 @@ QT5_ADD_RESOURCES(RESOURCE_RESULT
 )
 # C/C++ source
 SET(SOURCES
+   ../qcpcore/value.cc # hack until TriBITS is removed
+   navigationitem.cc
+   navigationmodel.cc
+   navigationwidget.cc
+   navigationactionmanager.cc
    qcpcontext.cc
    qcpcustom.cc
    qcpoptionsdialog.cc
diff --git a/qcpcontext/cmake/Dependencies.cmake b/qcpcontext/cmake/Dependencies.cmake
index ef65c07f91b641c00981df882f162f9e68c47592..b0b7256ac0300b58f15f4a8d224009c4da1d5c6f 100644
--- a/qcpcontext/cmake/Dependencies.cmake
+++ b/qcpcontext/cmake/Dependencies.cmake
@@ -3,7 +3,7 @@
 ## Jordan P. Lefebvre
 ##---------------------------------------------------------------------------##
 TRIBITS_PACKAGE_DEFINE_DEPENDENCIES(
-   LIB_REQUIRED_PACKAGES qcpQtPropertyBrowser qcpQCustomPlot radixcore
+   LIB_REQUIRED_PACKAGES qcpQtPropertyBrowser qcpQCustomPlot
    LIB_REQUIRED_TPLS Qt5PrintSupport Qt5Widgets Qt5Gui Qt5Core
 )
 ##---------------------------------------------------------------------------##
diff --git a/qcpcontext/navigationactionmanager.cc b/qcpcontext/navigationactionmanager.cc
new file mode 100644
index 0000000000000000000000000000000000000000..db2ad7aca0da7ce6eab7eb05fea54fa7cec942bb
--- /dev/null
+++ b/qcpcontext/navigationactionmanager.cc
@@ -0,0 +1,66 @@
+#include "qcpcontext/navigationactionmanager.hh"
+
+#include <QHash>
+#include <QList>
+#include <QMenu>
+
+#include "qcpcore/bug.hh"
+
+namespace qcp
+{
+class NavigationActionManager::PImpl
+{
+ public:
+  QHash<NavigationItem *, QList<std::pair<QString, std::function<void()>>>>
+      item_functions;
+  ~PImpl();
+};
+
+NavigationActionManager::NavigationActionManager(QObject *parent)
+    : QObject(parent)
+    , p(new PImpl)
+{
+}
+
+NavigationActionManager::PImpl::~PImpl() {}
+
+NavigationActionManager::~NavigationActionManager() { delete p; }
+
+void NavigationActionManager::clearActions(NavigationItem *item)
+{
+  p->item_functions.remove(item);
+}
+
+void NavigationActionManager::registerAction(NavigationItem *item, QString text,
+                                             std::function<void()> functor)
+{
+  auto item_it = p->item_functions.find(item);
+  if (item_it == p->item_functions.end())
+  {
+    // create a new reference
+    QList<std::pair<QString, std::function<void()>>> list;
+    list.append(std::make_pair(text, functor));
+    p->item_functions.insert(item, list);
+  }
+  else
+  {
+    item_it.value().append(std::make_pair(text, functor));
+  }
+}
+
+void NavigationActionManager::itemActions(NavigationItem *item,
+                                          QMenu *menu) const
+{
+  //
+  // Get the list of functors
+  auto functor_it = p->item_functions.find(item);
+  if (functor_it == p->item_functions.end()) return;
+
+  auto &list = functor_it.value();
+  for (int i = 0; i < list.length(); ++i)
+  {
+    menu->addAction(list[i].first, list[i].second);
+  }
+}
+
+}  // namespace qcp
diff --git a/qcpcontext/navigationactionmanager.hh b/qcpcontext/navigationactionmanager.hh
new file mode 100644
index 0000000000000000000000000000000000000000..ff1a31039604b0cd89c4cf0f4cb8c8018c350fba
--- /dev/null
+++ b/qcpcontext/navigationactionmanager.hh
@@ -0,0 +1,33 @@
+#ifndef QCP_QCPCONTEXT_NAVIGATIONACTIONMANAGER_HH_
+#define QCP_QCPCONTEXT_NAVIGATIONACTIONMANAGER_HH_
+#include <QAction>
+#include <QList>
+#include <QObject>
+
+#include <functional>
+#include <memory>
+
+#include "qcpcore/declspec.hh"
+#include "qcpcontext/navigationitem.hh"
+
+namespace qcp
+{
+class QCP_PUBLIC NavigationActionManager : public QObject
+{
+  Q_OBJECT
+ public:
+  NavigationActionManager(QObject *parent = nullptr);
+  ~NavigationActionManager();
+
+  void clearActions(NavigationItem *item);
+  void registerAction(NavigationItem *item, QString text,
+                      std::function<void()> functor);
+  void itemActions(NavigationItem *item, QMenu *menu) const;
+
+ private:
+  class PImpl;
+  PImpl *p;
+};
+}  // namespace qcp
+
+#endif /** QCP_QCPCONTEXT_NAVIGATIONACTIONMANAGER_HH_ */
diff --git a/qcpcontext/navigationitem.cc b/qcpcontext/navigationitem.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9394aab2e1729b7ce2f709d6802d4e8fa64832ae
--- /dev/null
+++ b/qcpcontext/navigationitem.cc
@@ -0,0 +1,126 @@
+#include "qcpcontext/navigationitem.hh"
+#include "qcpcore/bug.hh"
+
+#include <QList>
+
+namespace qcp
+{
+class NavigationItem::PImpl
+{
+ public:
+  NavigationItem *parent = nullptr;
+  QList<NavigationItem *> children;
+  QList<QVariant> data;
+  QList<QString> tooltips;
+  bool checked = false;
+  int type     = 0;
+  ~PImpl();
+};
+
+NavigationItem::PImpl::~PImpl() { qDeleteAll(children); }
+
+NavigationItem::NavigationItem(QVariant data, NavigationItem *parentItem)
+    : p(new PImpl())
+{
+  p->parent = parentItem;
+  p->data.append(data);
+}
+
+NavigationItem::NavigationItem(QList<QVariant> data, NavigationItem *parentItem)
+    : p(new PImpl())
+{
+  p->parent = parentItem;
+  p->data   = data;
+}
+
+NavigationItem::NavigationItem(NavigationItem *parentItem)
+    : p(new PImpl())
+{
+  p->parent = parentItem;
+}
+
+NavigationItem::~NavigationItem() { delete p; }
+
+int NavigationItem::type() const { return p->type; }
+
+void NavigationItem::setType(int type) { p->type = type; }
+
+void NavigationItem::addChild(NavigationItem *child)
+{
+  p->children.append(child);
+}
+
+int NavigationItem::childCount() const { return p->children.size(); }
+
+int NavigationItem::columnCount() const
+{
+  // recursively find the largest number of columns from children
+  int count = p->data.size();
+  for (int ci = 0; ci < p->children.size(); ++ci)
+  {
+    count = std::max(count, p->children[ci]->columnCount());
+  }
+  return count;
+}
+
+QVariant NavigationItem::data(int column, int role) const
+{
+  if (Qt::DisplayRole == role)
+  {
+    if (column >= p->data.size()) return QVariant();
+    return p->data.value(column);
+  }
+  else if (Qt::ToolTipRole == role)
+  {
+    if (column < p->tooltips.size()) return p->tooltips.value(column);
+  }
+  else if (Qt::CheckStateRole == role)
+  {
+    return static_cast<int>(isChecked() ? Qt::Checked : Qt::Unchecked);
+  }
+  return QVariant();
+}
+
+void NavigationItem::setData(int column, QVariant value, int role)
+{
+  if (Qt::DisplayRole == role)
+  {
+    int size = p->data.size();
+    for (int i = size; i <= column; ++i)
+    {
+      p->data.append(QVariant());
+    }
+    p->data[column] = value;
+  }
+  else if (Qt::ToolTipRole == role)
+  {
+    p->tooltips.insert(column, value.toString());
+  }
+}
+
+int NavigationItem::row() const
+{
+  if (p->parent)
+  {
+    return p->parent->p->children.indexOf(const_cast<NavigationItem *>(this));
+  }
+  return 0;
+}
+
+NavigationItem *NavigationItem::parentItem() { return p->parent; }
+
+bool NavigationItem::isChecked() const { return p->checked; }
+
+void NavigationItem::setChecked(bool checked) { p->checked = checked; }
+
+NavigationItem *NavigationItem::child(int row)
+{
+  return p->children.value(row);
+}
+
+bool NavigationItem::disown(NavigationItem *child)
+{
+  return p->children.removeOne(child);
+}
+
+}  // namespace qcp
diff --git a/qcpcontext/navigationitem.hh b/qcpcontext/navigationitem.hh
new file mode 100644
index 0000000000000000000000000000000000000000..1fce735afbc88618280650276417984a43e7e735
--- /dev/null
+++ b/qcpcontext/navigationitem.hh
@@ -0,0 +1,38 @@
+#ifndef QCP_QCPCONTEXT_NAVIGATIONITEM_HH_
+#define QCP_QCPCONTEXT_NAVIGATIONITEM_HH_
+#include <QVariant>
+
+#include <memory>
+
+#include "qcpcore/declspec.hh"
+namespace qcp
+{
+class QCP_PUBLIC NavigationItem
+{
+ public:
+  NavigationItem(QVariant data, NavigationItem *parentItem = nullptr);
+  NavigationItem(QList<QVariant> data, NavigationItem *parentItem = nullptr);
+  NavigationItem(NavigationItem *parentItem = nullptr);
+  ~NavigationItem();
+
+  int type() const;
+  void setType(int type);
+  void addChild(NavigationItem *child);
+  NavigationItem *child(int row);
+  bool disown(NavigationItem *child);
+  int childCount() const;
+  int columnCount() const;
+  QVariant data(int column, int role = Qt::DisplayRole) const;
+  void setData(int column, QVariant value, int role = Qt::DisplayRole);
+  int row() const;
+  NavigationItem *parentItem();
+  bool isChecked() const;
+  void setChecked(bool checked);
+
+ private:
+  class PImpl;
+  PImpl *p;
+};
+}  // namespace qcp
+
+#endif
diff --git a/qcpcontext/navigationmodel.cc b/qcpcontext/navigationmodel.cc
new file mode 100644
index 0000000000000000000000000000000000000000..533ff5a4afab9be26cdc5df9410d86021dabe39a
--- /dev/null
+++ b/qcpcontext/navigationmodel.cc
@@ -0,0 +1,205 @@
+#include "qcpcontext/navigationmodel.hh"
+#include "qcpcontext/navigationitem.hh"
+
+#include "qcpcore/bug.hh"
+
+#include <QList>
+
+namespace qcp
+{
+class NavigationModel::PImpl
+{
+ public:
+  NavigationItem* root = nullptr;
+
+  ~PImpl();
+};
+
+NavigationModel::PImpl::~PImpl()
+{
+  if (root) delete root;
+}
+NavigationModel::NavigationModel(QObject* parent)
+    : QAbstractItemModel(parent)
+    , p(new PImpl())
+{
+  p->root = new NavigationItem();
+}
+
+NavigationModel::~NavigationModel() { delete p; }
+
+QVariant NavigationModel::data(const QModelIndex& index, int role) const
+{
+  if (!index.isValid()) return QVariant();
+
+  NavigationItem* item = toItem(index);
+  return item->data(index.column(), role);
+}
+
+bool NavigationModel::setData(const QModelIndex& index, const QVariant& value,
+                              int role)
+{
+  NavigationItem* item = toItem(index);
+  if (Qt::CheckStateRole == role)
+  {
+    item->setChecked(value.toBool());
+    // signal selection/deselection
+    if (item->isChecked())
+      emit itemChecked(index);
+    else
+      emit itemUnChecked(index);
+    emit dataChanged(index, index, {Qt::CheckStateRole});
+    return true;
+  }
+  else if (Qt::ToolTipRole == role)
+  {
+    item->setData(index.column(), value, role);
+  }
+  return false;
+}
+
+Qt::ItemFlags NavigationModel::flags(const QModelIndex& index) const
+{
+  if (!index.isValid()) return QAbstractItemModel::flags(index);
+
+  Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+  if (index.column() == 0) flags |= Qt::ItemIsUserCheckable;
+
+  return flags;
+}
+
+QVariant NavigationModel::headerData(int section, Qt::Orientation orientation,
+                                     int role) const
+{
+  if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
+    return p->root->data(section);
+
+  return QVariant();
+}
+
+QModelIndex NavigationModel::index(int row, int column,
+                                   const QModelIndex& parent) const
+{
+  if (parent.isValid() && parent.column() != 0) return QModelIndex();
+
+  NavigationItem* parentItem = toItem(parent);
+
+  if (!parentItem) return QModelIndex();
+
+  NavigationItem* childItem = parentItem->child(row);
+  if (childItem) return createIndex(row, column, childItem);
+  return QModelIndex();
+}
+
+QModelIndex NavigationModel::parent(const QModelIndex& index) const
+{
+  if (!index.isValid()) return QModelIndex();
+
+  NavigationItem* childItem  = toItem(index);
+  NavigationItem* parentItem = childItem ? childItem->parentItem() : nullptr;
+
+  if (parentItem == p->root || !parentItem) return QModelIndex();
+
+  return createIndex(parentItem->row(), 0, parentItem);
+}
+
+int NavigationModel::rowCount(const QModelIndex& parent) const
+{
+  NavigationItem* parentItem = toItem(parent);
+
+  return parentItem ? parentItem->childCount() : 0;
+}
+
+int NavigationModel::columnCount(const QModelIndex& parent) const
+{
+  if (parent.isValid())
+    return static_cast<NavigationItem*>(parent.internalPointer())
+        ->columnCount();
+  else
+    return p->root->columnCount();
+}
+
+bool NavigationModel::removeChild(NavigationItem* parent, NavigationItem* item)
+{
+  if (parent == nullptr) return false;
+  for (int ri = 0; ri < parent->childCount(); ++ri)
+  {
+    NavigationItem* child = parent->child(ri);
+
+    if (item == child)
+    {
+      QModelIndex qIndex = createIndex(ri, 0, child);
+      return removeRow(ri, qIndex);
+    }
+    else
+    {
+      qcp_tagged_line("Recursing down parent(" << parent << ") child("
+                                                 << child << ")");
+      if (removeChild(child, item)) return true;
+    }
+  }
+  return false;
+}
+
+bool NavigationModel::removeItem(NavigationItem* item)
+{
+  return removeChild(p->root, item);
+}
+
+bool NavigationModel::removeRows(int row, int count, const QModelIndex& index)
+{
+  QVector<NavigationItem*> deleteList;
+  beginRemoveRows(index.parent(), row, row + count - 1);
+
+  NavigationItem* parentItem = toItem(index.parent());
+  for (int ci = 0; ci < count; ++ci)
+  {
+    NavigationItem* child = parentItem->child(row);
+    qcp_tagged_line("Remove child(" << child << ") from parent ("
+                                      << parentItem << ") at row(" << row
+                                      << ")");
+    parentItem->disown(child);
+    deleteList.push_back(child);
+  }
+
+  endRemoveRows();
+  qDeleteAll(deleteList);
+  return true;
+}
+
+NavigationItem* NavigationModel::rootItem() { return p->root; }
+
+NavigationItem* NavigationModel::addItem(QVariant value, NavigationItem* parent)
+{
+  beginResetModel();
+  NavigationItem* newItem = new NavigationItem(value, parent);
+  parent->addChild(newItem);
+  endResetModel();
+  return newItem;
+}
+
+NavigationItem* NavigationModel::toItem(const QModelIndex& index) const
+{
+  if (index.isValid())
+  {
+    NavigationItem* item = nullptr;
+    item = static_cast<NavigationItem*>(index.internalPointer());
+    if (item) return item;
+  }
+
+  return p->root;
+}
+
+void NavigationModel::clear()
+{
+  int numKids = p->root->childCount();
+  for (int ki = 0; ki < numKids; ++ki)
+  {
+    NavigationItem* kid = p->root->child(0);
+    if (p->root->disown(kid))
+    {
+      delete kid;
+    }
+  }
+}
+}  // namespace qcp
diff --git a/qcpcontext/navigationmodel.hh b/qcpcontext/navigationmodel.hh
new file mode 100644
index 0000000000000000000000000000000000000000..644eda71d7695ec87a2b8373355601125b6e532d
--- /dev/null
+++ b/qcpcontext/navigationmodel.hh
@@ -0,0 +1,51 @@
+#ifndef QCP_QCPCONTEXT_NAVIGATIONMODEL_HH_
+#define QCP_QCPCONTEXT_NAVIGATIONMODEL_HH_
+#include <QAbstractItemModel>
+#include <QObject>
+
+#include <memory>
+
+#include "qcpcore/declspec.hh"
+namespace qcp
+{
+// Forward declaration
+class NavigationItem;
+
+class QCP_PUBLIC NavigationModel : public QAbstractItemModel
+{
+  Q_OBJECT
+ public:
+  NavigationModel(QObject *parent = nullptr);
+  ~NavigationModel();
+
+  QVariant data(const QModelIndex &index, int role) const override;
+  bool setData(const QModelIndex &index, const QVariant &value,
+               int role = Qt::EditRole) override;
+  Qt::ItemFlags flags(const QModelIndex &index) const override;
+  QVariant headerData(int section, Qt::Orientation orientation,
+                      int role = Qt::DisplayRole) const override;
+  QModelIndex index(int row, int column,
+                    const QModelIndex &parent = QModelIndex()) const override;
+  QModelIndex parent(const QModelIndex &index) const override;
+  int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+  int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+  bool removeItem(NavigationItem *item);
+  bool removeRows(int row, int count,
+                  const QModelIndex &index = QModelIndex()) override;
+  NavigationItem *rootItem();
+  NavigationItem *addItem(QVariant value, NavigationItem *parent);
+  NavigationItem *toItem(const QModelIndex &index) const;
+  void clear();
+
+ signals:
+  void itemChecked(const QModelIndex &index);
+  void itemUnChecked(const QModelIndex &index);
+
+ private:
+  bool removeChild(NavigationItem *parent, NavigationItem *item);
+  class PImpl;
+  PImpl *p;
+};
+}  // namespace qcp
+
+#endif
diff --git a/qcpcontext/navigationwidget.cc b/qcpcontext/navigationwidget.cc
new file mode 100644
index 0000000000000000000000000000000000000000..01cbe0c0996a071a011d22001f294dd27463486b
--- /dev/null
+++ b/qcpcontext/navigationwidget.cc
@@ -0,0 +1,192 @@
+#include "qcpcontext/navigationwidget.hh"
+#include "qcpcontext/navigationactionmanager.hh"
+#include "qcpcontext/navigationitem.hh"
+#include "qcpcontext/navigationmodel.hh"
+
+#include "qcpcore/bug.hh"
+
+#include <QClipboard>
+#include <QEvent>
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QHeaderView>
+#include <QKeyEvent>
+#include <QLabel>
+#include <QLineEdit>
+#include <QMenu>
+#include <QMessageBox>
+#include <QStandardItemModel>
+#include <QTextDocument>
+#include <QToolTip>
+#include <QTreeView>
+#include <QtDebug>
+
+namespace qcp
+{
+class QCP_PUBLIC NavigationItemSortFilterProxyModel::PImpl
+{
+ public:
+  QHash<QModelIndex, bool> seen;
+};
+
+NavigationItemSortFilterProxyModel::NavigationItemSortFilterProxyModel(
+    QObject* parent)
+    : QSortFilterProxyModel(parent)
+    , p(new PImpl())
+{
+}
+
+NavigationItemSortFilterProxyModel::~NavigationItemSortFilterProxyModel()
+{
+  delete p;
+}
+
+bool NavigationItemSortFilterProxyModel::accepts(const QModelIndex& index) const
+{
+  const QString& text  = index.data().toString();
+  const QRegExp& regex = filterRegExp();
+
+  bool accept = text.contains(regex);
+
+  for (int i = 0, ie = sourceModel()->rowCount(index); i < ie; i++)
+  {
+    if (accepts(sourceModel()->index(i, 0, index)))
+    {
+      accept = true;
+      break;
+    }
+  }
+
+  p->seen[index] = accept;
+  return accept;
+}
+
+void NavigationItemSortFilterProxyModel::clearCache() { p->seen.clear(); }
+
+bool NavigationItemSortFilterProxyModel::filterAcceptsRow(
+    int source_row, const QModelIndex& source_parent) const
+{
+  const QModelIndex& index = sourceModel()->index(source_row, 0, source_parent);
+
+  if (p->seen.contains(index))
+  {
+    return p->seen.value(index);
+  }
+
+  return accepts(index);
+}
+
+class QCP_PUBLIC NavigationWidget::PImpl
+{
+ public:
+  QLineEdit* filter;
+  NavigationModel* model;
+  NavigationActionManager* action_manager;
+  NavigationItemSortFilterProxyModel* proxy;
+  QTreeView* view;
+};
+
+NavigationWidget::NavigationWidget(QWidget* parent)
+    : QWidget(parent)
+    , p(new PImpl())
+{
+  initMembers();
+  initLayout();
+}
+
+NavigationWidget::~NavigationWidget() { delete p; }
+
+NavigationModel* NavigationWidget::navigationModel() { return p->model; }
+
+NavigationActionManager* NavigationWidget::navigationActionManager()
+{
+  return p->action_manager;
+}
+
+void NavigationWidget::contextMenuRequested(QPoint point)
+{
+  qcp_tagged_line("contextMenuRequested()");
+  QModelIndex pindex   = p->view->indexAt(point);
+  QModelIndex index    = p->proxy->mapToSource(pindex);
+  NavigationItem* item = static_cast<NavigationItem*>(index.internalPointer());
+
+  QMenu m;
+  p->action_manager->itemActions(item, &m);
+  m.exec(p->view->viewport()->mapToGlobal(point));
+}
+
+void NavigationWidget::initLayout()
+{
+  mVertLayout = new QVBoxLayout(this);
+  mVertLayout->addWidget(p->filter);
+  mVertLayout->addWidget(p->view);
+}
+
+void NavigationWidget::initMembers()
+{
+  // allocate
+  p->filter         = new QLineEdit(this);
+  p->model          = new NavigationModel(this);
+  p->proxy          = new NavigationItemSortFilterProxyModel(this);
+  p->view           = new QTreeView(this);
+  p->action_manager = new NavigationActionManager(this);
+  // init properties
+  p->filter->setPlaceholderText("Filter");
+
+  p->view->installEventFilter(this);
+  p->view->setContextMenuPolicy(Qt::CustomContextMenu);
+  p->view->setEditTriggers(QAbstractItemView::NoEditTriggers);
+  p->view->setModel(p->proxy);
+  p->view->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+  p->view->header()->setStretchLastSection(false);
+  p->view->header()->setVisible(false);
+
+  p->proxy->setSourceModel(p->model);
+
+  // shortcuts
+  mActivateEnter  = new QShortcut(p->view);
+  mActivateReturn = new QShortcut(p->view);
+  mCollapseBranch = new QShortcut(p->view);
+
+  mActivateEnter->setContext(Qt::WidgetShortcut);
+  mActivateEnter->setKey(Qt::Key_Enter);
+
+  mActivateReturn->setContext(Qt::WidgetShortcut);
+  mActivateReturn->setKey(Qt::Key_Return);
+
+  mCollapseBranch->setContext(Qt::WidgetShortcut);
+  mCollapseBranch->setKey(Qt::Key_Left);
+
+  //
+  // connect signals/slots
+  connect(p->filter, SIGNAL(textChanged(QString)), this,
+          SLOT(filterModel(QString)));
+  connect(p->view, SIGNAL(customContextMenuRequested(QPoint)), this,
+          SLOT(contextMenuRequested(QPoint)));
+}
+void NavigationWidget::filterModel(const QString& pattern)
+{
+  // regex used to filter the model
+  QRegExp regex(pattern, Qt::CaseInsensitive, QRegExp::RegExp2);
+
+  // update the model
+  p->proxy->clearCache();
+  p->proxy->setFilterRegExp(regex);
+
+  // update the tree
+  if (pattern.isEmpty())
+  {
+    p->view->collapseAll();
+
+    for (int i = 0, ie = p->model->rowCount(); i < ie; i++)
+    {
+      p->view->expand(p->proxy->mapFromSource(p->model->index(i, 0)));
+    }
+  }
+  else
+  {
+    p->view->expandAll();
+  }
+}
+
+}  // namespace qcp
diff --git a/qcpcontext/navigationwidget.hh b/qcpcontext/navigationwidget.hh
new file mode 100644
index 0000000000000000000000000000000000000000..9f6e2fb0a94b858d360bec684ad1d0ade768a83d
--- /dev/null
+++ b/qcpcontext/navigationwidget.hh
@@ -0,0 +1,87 @@
+#ifndef QCP_QCPCONTEXT_NAVIGATIONWIDGET_HH_
+#define QCP_QCPCONTEXT_NAVIGATIONWIDGET_HH_
+
+#include <QShortcut>
+#include <QSortFilterProxyModel>
+#include <QStandardItemModel>
+#include <QStyledItemDelegate>
+#include <QVBoxLayout>
+#include <QWidget>
+
+#include "qcpcore/declspec.hh"
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+class QStandardItem;
+class QTreeView;
+QT_END_NAMESPACE
+
+namespace qcp
+{
+// Forward declaration
+class NavigationItem;
+class NavigationModel;
+class NavigationActionManager;
+
+class QCP_PUBLIC NavigationItemSortFilterProxyModel
+    : public QSortFilterProxyModel
+{
+  Q_OBJECT
+
+ public:
+  NavigationItemSortFilterProxyModel(QObject* parent = nullptr);
+  ~NavigationItemSortFilterProxyModel();
+
+ public slots:
+  void clearCache();
+
+ protected:
+  bool filterAcceptsRow(int row, const QModelIndex& parent) const;
+
+ private:
+  bool accepts(const QModelIndex& index) const;
+  class PImpl;
+  PImpl* p;
+};
+
+class QCP_PUBLIC NavigationWidget : public QWidget
+{
+  Q_OBJECT
+
+ public:
+  NavigationWidget(QWidget* parent = nullptr);
+  ~NavigationWidget();
+
+  void collapse(QStandardItem* item);
+
+  void expand(QStandardItem* item);
+  NavigationModel* navigationModel();
+  NavigationActionManager* navigationActionManager();
+
+  QTreeView* navigationTree() const;
+ private slots:
+  void contextMenuRequested(QPoint);
+ public slots:
+  void filterModel(const QString& pattern);
+
+ signals:
+  void itemDoubleClicked(const QModelIndex& index);
+  void itemClicked(const QModelIndex& index);
+  void itemChecked(const QModelIndex& index);
+  void itemUnchecked(const QModelIndex& index);
+
+ private:
+  QVBoxLayout* mVertLayout;
+  QShortcut *mActivateEnter, *mActivateReturn, *mCollapseBranch;
+
+  void initLayout();
+
+  void initMembers();
+
+  class PImpl;
+  PImpl* p;
+};
+}  // namespace qcp
+
+#endif /** QCP_QCPCONTEXT_NAVIGATIONWIDGET_HH_ */
diff --git a/qcpcontext/qcpcontext.cc b/qcpcontext/qcpcontext.cc
index 2ac1b9c46a5d1d76f5e30aca000cffe88875c7b5..7795a081b319d8211fe4d2a5311354c304d49050 100644
--- a/qcpcontext/qcpcontext.cc
+++ b/qcpcontext/qcpcontext.cc
@@ -60,8 +60,8 @@ QCPContext::~QCPContext()
   if (mOptionsDialog) delete mOptionsDialog;
 }  // QCPContext::~Plot
 
-CustomPlot *QCPContext::qcp() { return mQCPPlot; }  // QCPContext::qcp
-const CustomPlot *QCPContext::qcp() const
+CustomPlot *QCPContext::qcplot() { return mQCPPlot; }  // QCPContext::qcp
+const CustomPlot *QCPContext::qcplot() const
 {
   return mQCPPlot;
 }  // QCPContext::qcp
@@ -604,9 +604,9 @@ void QCPContext::legendDoubleClick(QCPLegend *legend,
 
 void QCPContext::mouseMove(QMouseEvent *event)
 {
-  auto x  = qcp()->xAxis->pixelToCoord(event->x());
-  auto y  = qcp()->yAxis->pixelToCoord(event->y());
-  auto yl = qcp()->yAxis->pixelToCoord(event->y() + 16);
+  auto x  = qcplot()->xAxis->pixelToCoord(event->x());
+  auto y  = qcplot()->yAxis->pixelToCoord(event->y());
+  auto yl = qcplot()->yAxis->pixelToCoord(event->y() + 16);
 
   // mouse move is outside axis range
   // we don't show the label
@@ -666,7 +666,7 @@ void QCPContext::mouseMove(QMouseEvent *event)
       vAlign = Qt::AlignBottom;
     }
 
-    qcp()->axisRect()->insetLayout()->setInsetAlignment(0, vAlign | hAlign);
+    qcplot()->axisRect()->insetLayout()->setInsetAlignment(0, vAlign | hAlign);
   }
 
   // need to redraw
@@ -681,7 +681,7 @@ void QCPContext::mousePress(QMouseEvent *event)
     setSelectionRectMode(QCP::srmNone);
   }
 
-  mLegendDrag = qcp()->legend->selectTest(event->pos(), false) >= 0;
+  mLegendDrag = qcplot()->legend->selectTest(event->pos(), false) >= 0;
 }
 
 void QCPContext::mouseRelease(QMouseEvent *event)
@@ -694,9 +694,9 @@ void QCPContext::mouseRelease(QMouseEvent *event)
 void QCPContext::plottableClicked(QCPAbstractPlottable *plottable,
                                   QMouseEvent *event)
 {
-  auto x  = qcp()->xAxis->pixelToCoord(event->x());
-  auto y  = qcp()->yAxis->pixelToCoord(event->y());
-  auto yl = qcp()->yAxis->pixelToCoord(event->y() + 16);  // label adjustment
+  auto x  = qcplot()->xAxis->pixelToCoord(event->x());
+  auto y  = qcplot()->yAxis->pixelToCoord(event->y());
+  auto yl = qcplot()->yAxis->pixelToCoord(event->y() + 16);  // label adjustment
 
   mLabel->position->setCoords(x, yl);
   mLabel->setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
@@ -711,7 +711,7 @@ void QCPContext::plottableClicked(QCPAbstractPlottable *plottable,
 void QCPContext::selectionChanged()
 {
   // plot object
-  auto plot = qcp();
+  auto plot = qcplot();
 
   // synchronize selection of graphs with selection of corresponding legend
   // items multiple selection can occur
diff --git a/qcpcontext/qcpcontext.hh b/qcpcontext/qcpcontext.hh
index 1b84c8d8452920b0e3aa1130a12b63a63c615b24..ca2834e73a853562a0611ab9e14630c498aaba5a 100644
--- a/qcpcontext/qcpcontext.hh
+++ b/qcpcontext/qcpcontext.hh
@@ -66,8 +66,8 @@ class QCPContext : public QWidget
    * @brief qcp Retrieve the raw QCustomPlot
    * @return QCustomPlot*
    */
-  CustomPlot *qcp();
-  const CustomPlot *qcp() const;
+  CustomPlot *qcplot();
+  const CustomPlot *qcplot() const;
 
   /**
    * @brief createQCPGraph creates a slot in this Plot for a QCPGraph
diff --git a/qcpcontext/qcpgraphdata.hh b/qcpcontext/qcpgraphdata.hh
index 1a53de4e6248db238b2d2efcc845058d225d2c2b..443cfaa64fc66fb0948a648eb352bc0ccc5b2976 100644
--- a/qcpcontext/qcpgraphdata.hh
+++ b/qcpcontext/qcpgraphdata.hh
@@ -7,7 +7,7 @@
 #ifndef QCPGRAPHDATA_HH_
 #define QCPGRAPHDATA_HH_
 
-#include "radixcore/visibility.hh"
+#include "qcpcore/declspec.hh"
 
 #include <memory>
 
@@ -16,7 +16,7 @@
 
 namespace qcp
 {
-class RADIX_PUBLIC QCPGraphData
+class QCP_PUBLIC QCPGraphData
 {
  public:
   typedef std::shared_ptr<QCPGraphData> SP;
diff --git a/qcpcontext/qcpoptionsdialog.cc b/qcpcontext/qcpoptionsdialog.cc
index c1be8d8233a48a1d68e5f42b8de60f7c36297e3e..56ddb27b1acb0a928a6cb7f7c17a4f0df051e663 100644
--- a/qcpcontext/qcpoptionsdialog.cc
+++ b/qcpcontext/qcpoptionsdialog.cc
@@ -92,7 +92,7 @@ void QCPOptionsDialog::initAxisProperties() {
 
 void QCPOptionsDialog::initBarsProperties() {
   // plot
-  auto qcp = mPlot->qcp();
+  auto qcp = mPlot->qcplot();
 
   // list of color maps
   QList<QCPBars *> bs;
@@ -136,7 +136,7 @@ void QCPOptionsDialog::initBarsProperties() {
 
 void QCPOptionsDialog::initColorMapProperties() {
   // plot
-  auto qcp = mPlot->qcp();
+  auto qcp = mPlot->qcplot();
 
   // list of color maps
   QList<QCPColorMap *> cms;
@@ -179,7 +179,7 @@ void QCPOptionsDialog::initColorMapProperties() {
 
 void QCPOptionsDialog::initGraphProperties() {
   // plot
-  auto qcp = mPlot->qcp();
+  auto qcp = mPlot->qcplot();
 
   // no graphs
   if (qcp->graphCount() == 0) {
@@ -215,7 +215,7 @@ void QCPOptionsDialog::initGraphProperties() {
 
 void QCPOptionsDialog::initCurveProperties() {
   // plot
-  auto qcp = mPlot->qcp();
+  auto qcp = mPlot->qcplot();
 
   // list of curves
   QList<QCPCurve *> cms;
diff --git a/qcpcore/bug.hh b/qcpcore/bug.hh
new file mode 100644
index 0000000000000000000000000000000000000000..271d1c84c058a13fe7f39e9aeb438044e6ad509e
--- /dev/null
+++ b/qcpcore/bug.hh
@@ -0,0 +1,258 @@
+#ifndef QCP_QCPCORE_BUG_HH_
+#define QCP_QCPCORE_BUG_HH_
+
+#ifndef DEBUG_OUTPUT
+#define DEBUG_OUTPUT 0
+#define DEBUG_CALL(c)
+#else
+#define DEBUG_CALL(c) c
+#endif
+
+// default to c++ compiler
+/*
+ * @file:   bug.hh
+ * @author: Jordan P. Lefebvre, lefebvrejp@ornl.gov
+ * @brief Debug functionality for including debug output \
+ * in your source without a performance penalty.
+ * This is an include ONLY package. There is no library to link against
+ * Each function is preprocessed to be included or not.
+ * If DEBUG_OUTPUT is defined the MACROs are included.
+ *
+ * Available MACROs:
+ * qcp(arg) - std::cerr << arg - Pushes content to stderr
+ * qcp_line(arg) - std::cerr << arg << std::endl - Push newline terminated
+ * content to stderr
+ * qcp_eol() - std::cerr << std::endl - Push newline to stderr
+ * qcp(arg) - std::cerr << arg - Pushes content to stderr
+ * qcp_warning(arg) - std::cerr << arg << std::endl - Push newline terminated
+ * content to stderr
+ * qcp_tagged_line(arg) - Same as qcp_line, prefixed with
+ * FILE and LINE
+ * qcp_tagged(arg) - Same as qcp, prefixed with FILE and LINE
+ * qcp_tagged_warning(arg) - Same as qcp_warning, prefixed with FILE and Line
+ * qcp_block(block) - block - Simply places code block in preprocessor
+ */
+#include <cstdio>
+#include <iostream>
+#if DEBUG_OUTPUT & 1
+#ifndef qcp_stream
+#define qcp_stream std::cerr
+#endif
+#define qcp_define_stream
+#define qcp_line(arg) qcp_stream << arg << std::endl
+#define qcp_eol() qcp_stream << std::endl
+#define qcp_flush_line(arg)       \
+  qcp_stream << arg << std::endl; \
+  fflush(stderr)
+#define qcp(arg) qcp_stream << arg
+#define qcp_warning(arg) qcp_stream << arg << std::endl
+#define qcp_tagged_warning(arg) \
+  qcp_stream << __FILE__ << ":" << __LINE__ << ": " << arg << std::endl
+#define qcp_flush_warning(arg)    \
+  qcp_stream << arg << std::endl; \
+  fflush(stderr)
+#define qcp_tagged_line(arg) \
+  qcp_stream << __FILE__ << ":" << __LINE__ << ": " << arg << std::endl
+#define qcp_flush_tagged_line(arg)                                       \
+  qcp_stream << __FILE__ << ":" << __LINE__ << ": " << arg << std::endl; \
+  fflush(stderr)
+#define qcp_tagged(arg) \
+  qcp_stream << __FILE__ << ":" << __LINE__ << ": " << arg
+#define qcp_flush_tagged_warning(arg)                                    \
+  qcp_stream << __FILE__ << ":" << __LINE__ << ": " << arg << std::endl; \
+  fflush(stderr)
+#define qcp_tagged_block(block)                                  \
+  qcp_stream << __FILE__ << ":" << __LINE__ << ":" << std::endl; \
+  block
+#define qcp_block(block) block
+#else
+#define qcp(arg)
+#define qcp_line(arg)
+#define qcp_eol()
+#define qcp_flush_line(arg)
+#define qcp_warning(arg)
+#define qcp_flush_warning(arg)
+#define qcp_tagged_line(arg)
+#define qcp_flush_tagged_line(arg)
+#define qcp_tagged(arg)
+#define qcp_tagged_warning(arg)
+#define qcp_flush_tagged_warning(arg)
+#define qcp_tagged_block(block)
+#define qcp_block(block)
+#endif /* DEBUG_OUTPUT */
+
+#include <sstream>
+#include <stdexcept>
+#ifndef QCP_DBC
+#define QCP_DBC 0
+#endif
+
+//
+// Defined all levels of QCP_DBC
+// QCP_DBC = 1 enables Require
+// QCP_DBC = 2 enables Check
+// QCP_DBC = 4 enables Remember & Ensure
+// QCP_DBC = 7 enables all
+// Insist is always enabled
+#if QCP_DBC & 1
+#define qcp_require(c)                                             \
+  if (!(c))                                                          \
+  {                                                                  \
+    std::ostringstream stream;                                       \
+    stream << __FILE__ << ":" << __LINE__ << " qcp_require(" << #c \
+           << ") failed." << std::endl;                              \
+    throw std::runtime_error(stream.str());                          \
+  }
+#else
+#define qcp_require(c)
+#endif
+#if QCP_DBC & 2
+#define qcp_check(c)                                             \
+  if (!(c))                                                        \
+  {                                                                \
+    std::ostringstream stream;                                     \
+    stream << __FILE__ << ":" << __LINE__ << " qcp_check(" << #c \
+           << ") failed." << std::endl;                            \
+    throw std::runtime_error(stream.str());                        \
+  }
+#else
+#define qcp_check(c)
+#endif
+#if QCP_DBC & 4
+#define qcp_ensure(c)                                             \
+  if (!(c))                                                         \
+  {                                                                 \
+    std::ostringstream stream;                                      \
+    stream << __FILE__ << ":" << __LINE__ << " qcp_ensure(" << #c \
+           << ") failed." << std::endl;                             \
+    throw std::runtime_error(stream.str());                         \
+  }
+#define qcp_remember(c) c
+#else
+#define qcp_ensure(c)
+#define qcp_remember(c)
+#endif
+
+#define qcp_insist(c, msg)                                       \
+  if (!(c))                                                        \
+  {                                                                \
+    std::ostringstream stream;                                     \
+    stream << __FILE__ << ":" << __LINE__ << "qcp_insist(" << #c \
+           << ") failed with this message:" << std::endl           \
+           << msg << std::endl;                                    \
+    throw std::runtime_error(stream.str());                        \
+  }
+
+#define qcp_not_implemented(msg)                        \
+  {                                                       \
+    std::ostringstream stream;                            \
+    stream << __FILE__ << ":" << __LINE__ << " : " << msg \
+           << " is not implemented. " << std::endl;       \
+    throw std::runtime_error(stream.str());               \
+  }
+
+/// set default timing to off
+#ifndef QCP_TIMING
+#define QCP_TIMING 0
+#endif
+
+#include <chrono>
+#include <ctime>
+namespace qcp
+{
+class Timer
+{
+ private:
+  bool mRunning;
+  std::chrono::steady_clock::time_point mStart;
+  std::chrono::steady_clock::time_point mEnd;
+  std::chrono::nanoseconds mDuration;
+  size_t mIntervals;
+
+ public:
+  Timer()
+      : mRunning(false)
+      , mDuration(0)
+      , mIntervals(0)
+  {
+  }
+  void start()
+  {
+    qcp_check(!mRunning);
+    mRunning = true;
+    mIntervals++;
+    mStart = std::chrono::steady_clock::now();
+  }
+  void stop()
+  {
+    qcp_check(mRunning);
+    mEnd     = std::chrono::steady_clock::now();
+    mRunning = false;
+    mDuration +=
+        std::chrono::duration_cast<std::chrono::nanoseconds>(mEnd - mStart);
+  }
+
+  std::chrono::nanoseconds::rep duration() const
+  {
+    qcp_check(!mRunning);
+    return mDuration.count();
+  }
+  size_t intervals() const { return mIntervals; }
+
+  double wall_clock()
+  {
+    qcp_require(!mRunning);
+    // 1e9 nanoseconds in a second
+    using seconds = std::chrono::duration<double, std::ratio<1000, 1>>;
+    return seconds(mEnd - mStart).count();
+  }
+
+  double sum_wall_clock()
+  {
+    // 1e9 nanoseconds in a second
+    using seconds = std::chrono::duration<double, std::ratio<1000, 1>>;
+    return seconds(mDuration).count();
+  }
+
+  bool running() const { return mRunning; };
+};
+
+// class Timer
+}  // namespace qcp
+
+#if QCP_TIMING > 0
+#define qcp_timer(name) qcp::Timer name
+#define qcp_timer_start(name) name.start()
+#define qcp_timer_stop(name) name.stop()
+#define qcp_timer_block(content) content
+#else
+#define qcp_timer(name)
+#define qcp_timer_start(name)
+#define qcp_timer_stop(name)
+#define qcp_timer_block(content)
+#endif
+#if QCP_TIMING > 1
+#define qcp_timer_2(name) qcp::Timer name
+#define qcp_timer_start_2(name) name.start()
+#define qcp_timer_stop_2(name) name.stop()
+#define qcp_timer_block_2(content) content
+#else
+#define qcp_timer_2(name)
+#define qcp_timer_start_2(name)
+#define qcp_timer_stop_2(name)
+#define qcp_timer_block_2(content)
+#endif
+
+#if QCP_TIMING > 2
+#define qcp_timer_3(name) qcp::Timer name
+#define qcp_timer_start_3(name) name.start()
+#define qcp_timer_stop_3(name) name.stop()
+#define qcp_timer_block_3(content) content
+#else
+#define qcp_timer_3(name)
+#define qcp_timer_start_3(name)
+#define qcp_timer_stop_3(name)
+#define qcp_timer_block_3(content)
+#endif
+
+#endif /* QCP_QCPCORE_BUG_HH_*/
diff --git a/qcpcore/declspec.hh b/qcpcore/declspec.hh
new file mode 100644
index 0000000000000000000000000000000000000000..d8d09f779c572be7153d345f283545554ac8e18e
--- /dev/null
+++ b/qcpcore/declspec.hh
@@ -0,0 +1,10 @@
+#ifndef QCP_QCPCORE_VISIBILITY_HH_
+#define QCP_QCPCORE_VISIBILITY_HH_
+#if defined _WIN32 || defined __CYGWIN__
+  #define QCP_PUBLIC __declspec(dllexport)
+  #define QCP_LOCAL
+#else
+  #define QCP_PUBLIC
+  #define QCP_LOCAL __declspec(dllimport)
+#endif
+#endif /* QCP_QCPCORE_VISIBILITY_HH_*/
diff --git a/qcpcore/json.hh b/qcpcore/json.hh
new file mode 100644
index 0000000000000000000000000000000000000000..2bc66ea1abf88ff671fa59555b52ba4b9f9fe252
--- /dev/null
+++ b/qcpcore/json.hh
@@ -0,0 +1,625 @@
+#ifndef QCP_QCPCORE_JSON_HH_
+#define QCP_QCPCORE_JSON_HH_
+
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <vector>
+
+#include "qcpcore/value.hh"
+#include "qcpcore/declspec.hh"
+
+//-----------------------------------------------------------------------------
+// see www.json.org for parsing grammar
+namespace qcp
+{
+template <class value_type, class array_type, class object_type>
+class QCP_PUBLIC JSONParserImpl
+{
+ public:
+  //-------------------------------------------------------------------------
+  JSONParserImpl()
+  {
+    literals[0] = "false";
+    literals[1] = "null";
+    literals[2] = "true";
+    for (size_t i = 0; i < N_LITERALS; i++)
+      literal_lens[i] = strlen(literals[i]);
+  }
+
+  //-------------------------------------------------------------------------
+  value_type& root() { return m_root; }
+
+  //-------------------------------------------------------------------------
+  std::string last_error()
+  {
+    return m_last_error + " at line " + std::to_string(m_line) + " column " +
+           std::to_string(m_col);
+  }
+
+  //-------------------------------------------------------------------------
+  // there can be only one value at root node
+  bool parse()
+  {
+    m_po         = 0;
+    m_line       = 1;
+    m_col        = 1;
+    m_last_error = "";
+
+    m_root      = value_type();
+    bool result = parse_value(m_root);
+    if (!result) return false;
+    skip_whitespace();
+    if (m_po != m_text.size())
+    {
+      set_error("unexpected trailing character(s)");
+      // clear text copy of json file
+      m_text.clear();
+      return false;
+    }
+    // clear text copy of json file
+    m_text.clear();
+    return true;
+  }
+
+  //-------------------------------------------------------------------------
+  bool parse_from_stream(std::istream& in_stream)
+  {
+    in_stream.seekg(0, std::ios::end);
+    std::streampos file_size = in_stream.tellg();
+    in_stream.seekg(0);
+    m_text.clear();
+    m_text.resize(static_cast<unsigned>(file_size), '\0');
+    in_stream.read(&m_text[0], file_size);
+    if (!in_stream)
+    {
+      set_error("could only read " + std::to_string(file_size) + " of " +
+                std::to_string(in_stream.gcount()) + " bytes");
+      // clear text copy of json file
+      m_text.clear();
+      return false;
+    }
+    return parse();
+  }
+
+  //-------------------------------------------------------------------------
+  bool parse_from_file(const std::string& fn)
+  {
+    std::ifstream file;
+    file.open(fn, std::ios::in | std::ios::binary | std::ios::ate);
+    if (!file.is_open())
+    {
+      set_error("could not open file: " + fn);
+      return false;
+    }
+    bool result = parse_from_stream(file);
+    file.close();
+    return result;
+  }
+
+ private:
+  void set_error(const std::string& error)
+  {
+    // save the error meesage
+    m_last_error = error;
+  }
+  //-------------------------------------------------------------------------
+  bool is_whitespace()
+  {
+    char ch = m_text[m_po];
+    return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
+  }
+
+  //-------------------------------------------------------------------------
+  bool is_structural_character()
+  {
+    char ch = m_text[m_po];
+    return (ch == ',' || ch == ':' || ch == '[' || ch == ']' || ch == '{' ||
+            ch == '}');
+  }
+
+  //-------------------------------------------------------------------------
+  void skip_whitespace()
+  {
+    for (; m_po < m_text.size(); m_po++)
+    {
+      if (m_text[m_po] == ' ' || m_text[m_po] == '\t')
+      {
+        m_col++;
+        continue;
+      }
+      if (m_text[m_po] == '\r')
+      {
+        m_line++;
+        m_col = 1;
+        continue;
+      }
+      if (m_text[m_po] == '\n')
+      {
+        // treat \r\n as one new-line
+        if (m_po > 0 && m_text[m_po - 1] == '\r') continue;
+        m_line++;
+        m_col = 1;
+        continue;
+      }
+      break;
+    }
+  }
+
+  //-------------------------------------------------------------------------
+  bool parse_array(value_type& value)
+  {
+    value = value_type();
+    if (m_po >= m_text.size()) return false;
+    if (m_text[m_po] != '[') return false;
+    m_po++;
+    m_col++;
+
+    value_type parent = array_type();
+    bool found_child  = false;
+    for (; m_po < m_text.size(); m_po++, m_col++)
+    {
+      skip_whitespace();
+      if (m_text[m_po] == ']') break;
+      value_type child;
+      if (!parse_value(child))
+      {
+        found_child = false;
+        if (m_po >= m_text.size())
+        {
+          set_error("no closing bracket ']' for array");
+        }
+        else if (m_text[m_po] == ']')
+          break;
+      }
+      else
+      {
+        found_child = true;
+        parent.as_array().push_back(child);
+      }
+      skip_whitespace();
+      char ch = m_text[m_po];
+      if (ch == ',')
+      {
+        if (!found_child)
+        {
+          set_error("missing value in array");
+          return false;
+        }
+        continue;
+      }
+      else if (ch == ']')
+        break;
+      else
+      {
+        std::string err_msg = "invalid character '";
+        err_msg += ch;
+        err_msg += "' in array";
+        set_error(err_msg);
+        return false;
+      }
+    }
+    if (m_po >= m_text.size() || m_text[m_po] != ']' || m_last_error != "")
+    {
+      set_error("no closing bracket ']' for array");
+      return false;
+    }
+    m_po++;
+    m_col++;
+
+    value = parent;
+    return true;
+  }
+
+  //-------------------------------------------------------------------------
+  // parsed to match the following regular expression:
+  // (-)?
+  // (0|([1-9][0-9]*))
+  // (\.[0-9]+)?
+  // ([Ee][+-]?[0-9]+)?
+  bool parse_number(value_type& value)
+  {
+    bool is_float = false;
+    value         = value_type();
+    if (m_po >= m_text.size()) return false;
+
+    size_t len = 0;
+
+    // (-)?
+    if (m_text[m_po] == '-')
+    {
+      m_po++;
+      m_col++;
+      len++;
+      if (m_po >= m_text.size())
+      {
+        set_error("invalid number (no digits after -)");
+        return false;
+      }
+    }
+
+    // (0|([1-9][0-9]*))
+    char ch = m_text[m_po];
+    if (m_po >= m_text.size() || !(ch >= '0' && m_text[m_po] <= '9'))
+    {
+      set_error("invalid number (no digits)");
+      return false;
+    }
+    m_po++;
+    m_col++;
+    len++;
+    // [1-9][0-9]*
+    if (ch >= '1' && ch <= '9')
+    {
+      // [0-9]*
+      for (; m_po < m_text.size(); m_po++, m_col++, len++)
+      {
+        ch = m_text[m_po];
+        if (!(ch >= '0' && ch <= '9')) break;
+      }
+    }
+    if (m_po >= m_text.size())
+    {
+      try
+      {
+        value = value_type(std::stoi(std::string(&m_text[m_po - len], len)));
+        return true;
+      }
+      catch (...)
+      {
+        return false;
+      }
+    }
+    ch = m_text[m_po];
+
+    // (\.[0-9]+)?
+    if (ch == '.')
+    {
+      is_float = true;
+      m_po++;
+      m_col++;
+      len++;
+      if (m_po >= m_text.size())
+      {
+        set_error("invalid number (no digits after decimal)");
+        return false;
+      }
+      ch              = m_text[m_po];
+      size_t n_digits = 0;
+      // [0-9]+
+      for (; m_po < m_text.size(); m_po++, m_col++, len++, n_digits++)
+      {
+        ch = m_text[m_po];
+        if (!(ch >= '0' && ch <= '9')) break;
+      }
+      if (n_digits == 0)
+      {
+        set_error("invalid number (no digits after decimal)");
+        return false;
+      }
+    }
+
+    // ([Ee][+-]?[0-9]+)?
+    if (ch == 'E' || ch == 'e')
+    {
+      is_float = true;
+      m_po++;
+      m_col++;
+      len++;
+      if (m_po >= m_text.size())
+      {
+        set_error("invalid number (no digits for exponent)");
+        return false;
+      }
+      ch = m_text[m_po];
+      // [+-]?
+      if (ch == '+' || ch == '-')
+      {
+        m_po++;
+        m_col++;
+        len++;
+      }
+      if (m_po >= m_text.size())
+      {
+        set_error("invalid number (no digits for exponent)");
+        return false;
+      }
+
+      size_t n_digits = 0;
+      // [0-9]+
+      for (; m_po < m_text.size(); m_po++, m_col++, len++, n_digits++)
+      {
+        ch = m_text[m_po];
+        if (!(ch >= '0' && ch <= '9')) break;
+      }
+      if (n_digits == 0)
+      {
+        set_error("invalid number (no digits for exponent)");
+        return false;
+      }
+    }
+
+    try
+    {
+      double v = std::stod(std::string(&m_text[m_po - len], len));
+      if (is_float)
+        value = value_type(v);
+      else
+        value = value_type(int(v >= 0 ? v + .5 : v - .5));
+
+      return true;
+    }
+    catch (...)
+    {
+      return false;
+    }
+  }
+
+  //-------------------------------------------------------------------------
+  // str_known must be a null-terminated string
+  inline bool sub_str_eq(char* sub_str_unknown, const char* str_known)
+  {
+    size_t i = 0;
+    while (str_known[i] != '\0')
+    {
+      if (sub_str_unknown[i] != str_known[i]) return false;
+      i++;
+    }
+    return true;
+  }
+
+  //-------------------------------------------------------------------------
+  // only 3 valid literals all in lower case: false, null, true
+  // TODO: need to refactor code to support null return (currently it is
+  // treated as an error)
+  bool parse_literal(value_type& value)
+  {
+    size_t len = 0;
+    value      = value_type();
+    for (; m_po + len < m_text.size(); len++)
+    {
+      char ch = m_text[m_po + len];
+      if (!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))) break;
+    }
+    for (size_t i = 0; i < N_LITERALS; i++)
+    {
+      if (literal_lens[i] == len && sub_str_eq(&m_text[m_po], literals[i]))
+      {
+        m_po += len;
+        m_col += len;
+        if (std::string("true") == literals[i])
+        {
+          value = value_type(true);
+          return true;
+        }
+        else if (std::string("false") == literals[i])
+        {
+          value = value_type(false);
+          return true;
+        }
+        // "null" default is already handled
+        return true;
+      }
+    }
+    set_error("invalid literal");
+    return false;
+  }
+
+  //-------------------------------------------------------------------------
+  bool parse_object(value_type& value)
+  {
+    value = value_type();
+    if (m_po >= m_text.size()) return false;
+    if (m_text[m_po] != '{') return false;
+    m_po++;
+    m_col++;
+
+    value_type parent = object_type();
+    for (; m_po < m_text.size(); m_po++, m_col++)
+    {
+      skip_whitespace();
+
+      if (m_po >= m_text.size() || m_text[m_po] == '}') break;
+
+      // parse key
+      std::string key = parse_string_contents();
+      if (m_last_error != "") return false;
+      skip_whitespace();
+
+      // parse ':'
+      if (m_po >= m_text.size() || m_text[m_po] != ':')
+      {
+        set_error("no ':' following key in object");
+        return false;
+      }
+      m_po++;
+      m_col++;
+
+      // parse value
+      bool result      = false;
+      value_type child = value_type();
+      result           = parse_value(child);
+      if (!result)
+      {
+        set_error("missing value in object");
+        return false;
+      }
+      else
+      {
+        parent.as_object()[key] = child;
+      }
+      skip_whitespace();
+
+      char ch = m_text[m_po];
+      if (ch == ',')
+        continue;
+      else if (ch == '}')
+        break;
+      else
+      {
+        set_error("invalid character in object");
+        return false;
+      }
+    }
+    if (m_po >= m_text.size() || m_text[m_po] != '}')
+    {
+      set_error("no closing curly bracket '}' for object");
+      return false;
+    }
+    m_po++;
+    m_col++;
+
+    value = parent;
+    return true;
+  }
+
+  //-------------------------------------------------------------------------
+  inline bool parse_escape_seq(size_t* len)
+  {
+    if (m_po >= m_text.size()) return false;
+    if (m_text[m_po] != '\\') return false;
+    m_po++;
+    m_col++;
+    (*len)++;
+    if (m_po >= m_text.size())
+    {
+      set_error("incomplete unicode character escape sequence in string");
+      return false;
+    }
+    char ch = m_text[m_po];
+    if (ch == '"' || ch == '\\' || ch == '/' || ch == 'b' || ch == 'f' ||
+        ch == 'n' || ch == 'r' || ch == 't')
+    {
+      m_po++;
+      m_col++;
+      (*len)++;
+      return true;
+    }
+    else if (ch == 'u')
+    {
+      m_po++;
+      m_col++;
+      (*len)++;
+      size_t code_len = 0;
+      // parse 4-digit unicode character escape sequence
+      for (; m_po < m_text.size() && code_len < 4;
+           m_po++, m_col++, (*len)++, code_len++)
+      {
+        char ch = m_text[m_po];
+        if (!((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') ||
+              (ch >= 'a' && ch <= 'f')))
+        {
+          set_error("invalid unicode character escape sequence in string");
+          return false;
+        }
+      }
+      if (code_len < 4)
+      {
+        set_error("incomplete unicode character escape sequence in string");
+        return false;
+      }
+      return true;
+    }
+    set_error("invalid escape sequence in string");
+    return false;
+  }
+
+  //-------------------------------------------------------------------------
+  // parse a quoted string from m_text
+  // used for string values and object keys
+  // processes and discards leading and trailing quotes
+  // on success, returns string contents without quotes
+  // on error, returns empty string and sets m_last_error
+  std::string parse_string_contents()
+  {
+    if (m_po >= m_text.size() || m_text[m_po] != '"')
+    {
+      set_error("string missing opening quote");
+      return "";
+    }
+    m_po++;
+    m_col++;
+    for (size_t len = 0; m_po < m_text.size();)
+    {
+      char ch = m_text[m_po];
+      // disallow control characters <= 0x1f
+      if (static_cast<unsigned>(ch) <= 0x1f)
+      {
+        set_error("invalid character in string");
+        return "";
+      }
+      if (ch == '\\')
+      {
+        bool ok = parse_escape_seq(&len);
+        if (!ok) return "";
+        continue;
+      }
+      if (ch == '"')
+      {
+        m_po++;
+        m_col++;
+        return std::string(&m_text[m_po - 1 - len], len);
+      }
+      m_po++;
+      m_col++;
+      len++;
+    }
+    set_error("string missing closing quote");
+    return "";
+  }
+
+  //-------------------------------------------------------------------------
+  bool parse_string(value_type& value)
+  {
+    value           = value_type();
+    std::string str = parse_string_contents();
+    if (!m_last_error.empty()) return false;
+    value = value_type(str);
+    return true;
+  }
+
+  //-------------------------------------------------------------------------
+  bool parse_value(value_type& value)
+  {
+    skip_whitespace();
+    char ch     = m_text[m_po];
+    value       = value_type();
+    bool result = false;
+    if (ch == '"')
+      result = parse_string(value);
+    else if (ch == '[')
+      result = parse_array(value);
+    else if (ch == '{')
+      result = parse_object(value);
+    else if (ch == '-' || (ch >= '0' && ch <= '9'))
+      result = parse_number(value);
+    else if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
+      result = parse_literal(value);
+    // TODO: need to handle ']', '}' and ',' differently from actual
+    // invalid characters
+    //~ else {}
+    return result;
+  }
+
+  static const size_t N_LITERALS = 3;
+  const char* literals[N_LITERALS];
+  size_t literal_lens[N_LITERALS];
+
+  value_type m_root;
+
+  std::string m_text = "";
+
+  // current parsing offset, line and column
+  size_t m_po   = 0;
+  size_t m_line = 1;
+  size_t m_col  = 1;
+
+  std::string m_last_error = "";
+};
+
+typedef JSONParserImpl<Value, DataArray, DataObject> JSONParser;
+
+}  // namespace qcp
+#endif /** QCP_QCPCORE_JSON_HH_ */
diff --git a/qcpcore/value.cc b/qcpcore/value.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d2d63d07e6bb21ae6a48e7617ec1d86190bc1af5
--- /dev/null
+++ b/qcpcore/value.cc
@@ -0,0 +1,832 @@
+#include "qcpcore/value.hh"
+
+#include <cassert>
+#include <cstdlib>  // atoi/atof, etc
+#include <cstring>  // strdup, free
+#include <sstream>
+#include "qcpcore/bug.hh"
+
+namespace qcp
+{
+Value::Value()
+    : m_allocated(false)
+    , m_type(TYPE_NULL)
+{
+}
+Value::Value(const Value& orig) { this->copy_from(orig); }
+Value::Value(Value&& orig)
+    : m_allocated(orig.m_allocated)
+    , m_type(orig.m_type)
+    , m_data(orig.m_data)
+{
+  orig.m_allocated = false;
+  orig.m_type      = TYPE_NULL;
+}
+Value::Value(bool v)
+{
+  m_data.m_bool = v;
+  m_type        = TYPE_BOOLEAN;
+}
+Value::Value(int v)
+{
+  m_data.m_int = v;
+  m_type       = TYPE_INTEGER;
+}
+Value::Value(double v)
+{
+  m_data.m_double = v;
+  m_type          = TYPE_DOUBLE;
+}
+Value::Value(const char* v)
+{
+  qcp_require(v);
+  m_data.m_string = strdup(v);
+  m_type          = TYPE_STRING;
+  m_allocated     = true;
+}
+Value::Value(const std::string& v)
+{
+  m_data.m_string = strdup(v.c_str());
+  m_type          = TYPE_STRING;
+  m_allocated     = true;
+}
+Value::Value(const DataArray& v)
+{
+  m_data.m_array = new DataArray(v);
+  m_type         = TYPE_ARRAY;
+  m_allocated    = true;
+}
+Value::Value(const DataObject& v)
+{
+  m_data.m_object = new DataObject(v);
+  m_type          = TYPE_OBJECT;
+  m_allocated     = true;
+}
+void Value::copy_from(const Value& orig)
+{
+  m_allocated = orig.m_allocated;
+  m_type      = orig.m_type;
+  switch (m_type)
+  {
+    case TYPE_NULL:
+      break;
+    case TYPE_BOOLEAN:
+      m_data.m_bool = orig.m_data.m_bool;
+      break;
+    case TYPE_INTEGER:
+      m_data.m_int = orig.m_data.m_int;
+      break;
+    case TYPE_DOUBLE:
+      m_data.m_double = orig.m_data.m_double;
+      break;
+    case TYPE_STRING:
+      qcp_check(m_allocated);
+      m_data.m_string = strdup(orig.m_data.m_string);
+      break;
+    case TYPE_ARRAY:
+      qcp_check(m_allocated);
+      m_data.m_array = new DataArray(*orig.m_data.m_array);
+      qcp_ensure(m_data.m_array);
+      break;
+    case TYPE_OBJECT:
+      qcp_check(m_allocated);
+      m_data.m_object = new DataObject(*orig.m_data.m_object);
+      qcp_ensure(m_data.m_object);
+      break;
+  }
+}
+
+Value::Type Value::type() const { return m_type; }
+
+std::string Value::categoryString() const
+{
+  switch (m_type)
+  {
+    case Type::TYPE_INTEGER:
+    case Type::TYPE_DOUBLE:
+      return "number";
+    case Type::TYPE_STRING:
+      return "string";
+    case Type::TYPE_OBJECT:
+      return "object";
+    case Type::TYPE_ARRAY:
+      return "array";
+    case Type::TYPE_BOOLEAN:
+      return "boolean";
+    default:
+      return "null";
+  }
+}
+
+Value& Value::operator=(const Value& orig)
+{
+  // release any allocated memory
+  nullify();
+  // copy from the originator
+  copy_from(orig);
+  return *this;
+}
+Value& Value::operator=(Value&& orig)
+{
+  // release any allocated memory
+  nullify();
+  m_allocated = orig.m_allocated;
+  m_data      = orig.m_data;
+  m_type      = orig.m_type;
+
+  orig.m_allocated = false;
+  orig.m_type      = TYPE_NULL;
+
+  return *this;
+}
+Value& Value::operator=(bool v)
+{
+  nullify();
+  m_type        = TYPE_BOOLEAN;
+  m_data.m_bool = v;
+  return *this;
+}
+Value& Value::operator=(int v)
+{
+  nullify();
+  m_type       = TYPE_INTEGER;
+  m_data.m_int = v;
+  return *this;
+}
+Value& Value::operator=(double v)
+{
+  nullify();
+  m_type          = TYPE_DOUBLE;
+  m_data.m_double = v;
+  return *this;
+}
+Value& Value::operator=(const char* v)
+{
+  nullify();
+  qcp_require(v);
+  m_data.m_string = strdup(v);
+  m_type          = TYPE_STRING;
+  m_allocated     = true;
+  return *this;
+}
+Value& Value::operator=(const std::string& v)
+{
+  nullify();
+  m_data.m_string = strdup(v.c_str());
+  m_type          = TYPE_STRING;
+  m_allocated     = true;
+  return *this;
+}
+Value& Value::operator=(const DataArray& v)
+{
+  nullify();
+  m_data.m_array = new DataArray(v);
+  m_type         = TYPE_ARRAY;
+  m_allocated    = true;
+  return *this;
+}
+Value& Value::operator=(const DataObject& v)
+{
+  nullify();
+  m_data.m_object = new DataObject(v);
+  m_type          = TYPE_OBJECT;
+  m_allocated     = true;
+  return *this;
+}
+
+void Value::assign(DataObject* obj)
+{
+  if (obj == nullptr)
+  {
+    m_type      = TYPE_NULL;
+    m_allocated = false;
+    return;
+  }
+  nullify();
+  m_type          = TYPE_OBJECT;
+  m_allocated     = true;
+  m_data.m_object = obj;
+}
+void Value::assign(DataArray* array)
+{
+  if (array == nullptr)
+  {
+    m_type      = TYPE_NULL;
+    m_allocated = false;
+    return;
+  }
+  nullify();
+  m_type         = TYPE_ARRAY;
+  m_allocated    = true;
+  m_data.m_array = array;
+}
+void Value::nullify()
+{
+  switch (m_type)
+  {
+    case TYPE_NULL:
+      break;
+    case TYPE_BOOLEAN:
+      break;
+    case TYPE_INTEGER:
+      break;
+    case TYPE_DOUBLE:
+      break;
+    case TYPE_STRING:
+      qcp_check(m_allocated);
+      free(m_data.m_string);
+      break;
+    case TYPE_ARRAY:
+      qcp_check(m_allocated);
+      delete m_data.m_array;
+      break;
+    case TYPE_OBJECT:
+      qcp_check(m_allocated);
+      delete m_data.m_object;
+      break;
+  }
+  m_type      = TYPE_NULL;
+  m_allocated = false;
+}
+
+Value::~Value() { this->nullify(); }
+
+int Value::to_int() const
+{
+  switch (m_type)
+  {
+    case TYPE_NULL:
+      return 0;
+
+    case TYPE_BOOLEAN:
+      return int(m_data.m_bool);
+
+    case TYPE_INTEGER:
+      return m_data.m_int;
+
+    case TYPE_DOUBLE:
+      return int(m_data.m_double);
+
+    case TYPE_STRING:
+      qcp_ensure(m_allocated);
+      qcp_ensure(m_data.m_string);
+      return atoi(m_data.m_string);
+
+    case TYPE_ARRAY:
+      qcp_not_implemented("conversion of array to integer");
+
+    case TYPE_OBJECT:
+      qcp_not_implemented("conversion of object to integer")
+  }
+  qcp_not_implemented("unknown type conversion to integer")
+}
+double Value::to_double() const
+{
+  switch (m_type)
+  {
+    case TYPE_NULL:
+      return 0.0;
+
+    case TYPE_BOOLEAN:
+      return double(m_data.m_bool);
+
+    case TYPE_INTEGER:
+      return double(m_data.m_int);
+
+    case TYPE_DOUBLE:
+      return m_data.m_double;
+
+    case TYPE_STRING:
+      qcp_ensure(m_allocated);
+      qcp_ensure(m_data.m_string);
+      return atof(m_data.m_string);
+
+    case TYPE_ARRAY:
+      qcp_not_implemented("conversion of array to double");
+
+    case TYPE_OBJECT:
+      qcp_not_implemented("conversion of object to double")
+  }
+  qcp_not_implemented("unknown type conversion to double")
+}
+bool Value::to_bool() const
+{
+  switch (m_type)
+  {
+    case TYPE_NULL:
+      return false;
+
+    case TYPE_BOOLEAN:
+      return m_data.m_bool;
+
+    case TYPE_INTEGER:
+      return m_data.m_int != 0;
+
+    case TYPE_DOUBLE:
+      return m_data.m_double ? true : false;
+
+    case TYPE_STRING:
+      qcp_not_implemented("conversion of string to boolean");
+
+    case TYPE_ARRAY:
+      qcp_not_implemented("conversion of array to double");
+
+    case TYPE_OBJECT:
+      qcp_not_implemented("conversion of object to double")
+  }
+  qcp_not_implemented("unknown type conversion to bool")
+}
+const char* Value::to_cstring() const
+{
+  switch (m_type)
+  {
+    case TYPE_NULL:
+    case TYPE_BOOLEAN:
+    case TYPE_INTEGER:
+    case TYPE_DOUBLE:
+      return nullptr;
+    case TYPE_STRING:
+      qcp_check(m_allocated);
+      return m_data.m_string;
+
+    case TYPE_ARRAY:
+      qcp_not_implemented("conversion of array to cstring");
+
+    case TYPE_OBJECT:
+      qcp_not_implemented("conversion of object to cstring")
+  }
+  qcp_not_implemented("unknown type conversion to cstring")
+}
+std::string Value::to_string() const
+{
+  switch (m_type)
+  {
+    case TYPE_NULL:
+      return "null";
+    case TYPE_BOOLEAN:
+      return m_data.m_bool ? "true" : "false";
+    case TYPE_INTEGER:
+      return std::to_string(m_data.m_int);
+    case TYPE_DOUBLE:
+      return std::to_string(m_data.m_double);
+    case TYPE_STRING:
+      qcp_check(m_allocated);
+      return m_data.m_string;
+
+    case TYPE_ARRAY:
+      qcp_not_implemented("conversion of array to string");
+
+    case TYPE_OBJECT:
+      std::stringstream str;
+      to_object()->pack_json(str);
+      return str.str();
+  }
+  qcp_not_implemented("unknown type conversion to string")
+}
+
+DataArray* Value::to_array() const
+{
+  qcp_insist(
+      convertable(TYPE_ARRAY),
+      "Value object must be convertable to an array") return m_data.m_array;
+}
+DataObject* Value::to_object() const
+{
+  qcp_insist(
+      convertable(TYPE_OBJECT),
+      "Value object must be convertable to an object") return m_data.m_object;
+}
+
+const DataArray& Value::as_array() const
+{
+  qcp_insist(
+      convertable(TYPE_ARRAY),
+      "Value object must be convertable to an array") return *(m_data.m_array);
+}
+
+DataArray& Value::as_array()
+{
+  qcp_insist(
+      convertable(TYPE_ARRAY),
+      "Value object must be convertable to an array") return *(m_data.m_array);
+}
+
+const DataObject& Value::as_object() const
+{
+  qcp_insist(
+      convertable(TYPE_OBJECT),
+      "Value object must be convertable to an object") return *(m_data
+                                                                    .m_object);
+}
+
+DataObject& Value::as_object()
+{
+  qcp_insist(
+      convertable(TYPE_OBJECT),
+      "Value object must be convertable to an object") return *(m_data
+                                                                    .m_object);
+}
+bool Value::convertable(Value::Type to) const
+{
+  switch (to)
+  {
+    case TYPE_NULL:
+      return (is_number() && to_double() == 0.0);
+    case TYPE_BOOLEAN:
+    case TYPE_INTEGER:
+    case TYPE_DOUBLE:
+      return is_bool() || is_null() || is_number();
+    case TYPE_STRING:
+      return is_string();
+    case TYPE_ARRAY:
+      return is_array();
+    case TYPE_OBJECT:
+      return is_object();
+  }
+  qcp_not_implemented("unknown type conversion")
+}
+
+Value& Value::operator[](const std::string& name)
+{
+  qcp_check(is_object());
+  DataObject* o = to_object();
+  return o->operator[](name);
+}
+const Value& Value::operator[](const std::string& name) const
+{
+  qcp_check(is_object());
+  const DataObject* o = to_object();
+  return o->operator[](name);
+}
+Value& Value::operator[](size_t i)
+{
+  qcp_check(is_array());
+  DataArray* a = to_array();
+  return a->operator[](i);
+}
+const Value& Value::operator[](size_t i) const
+{
+  qcp_check(is_array());
+  DataArray* a = to_array();
+  return a->operator[](i);
+}
+bool Value::empty() const
+{
+  if (is_array())
+  {
+    qcp_check(to_array());
+    return to_array()->empty();
+  }
+  if (is_object())
+  {
+    qcp_check(to_object());
+    return to_object()->empty();
+  }
+  return is_null();
+}
+size_t Value::size() const
+{
+  if (is_array())
+  {
+    qcp_check(to_array());
+    return to_array()->size();
+  }
+  if (is_object())
+  {
+    qcp_check(to_object());
+    return to_object()->size();
+  }
+  return 0;
+}
+
+bool Value::format_json(std::ostream& out, int indent_level, int level,
+                        bool order_by_insert) const
+{
+  if (is_object())
+  {
+    to_object()->format_json(out, indent_level, level, order_by_insert);
+  }
+  else if (is_array())
+  {
+    to_array()->format_json(out, indent_level, level, order_by_insert);
+  }
+  else
+  {
+    switch (type())
+    {
+      case TYPE_STRING:
+        out << "\"" << to_string() << "\"";
+        break;
+      case TYPE_BOOLEAN:
+        out << std::boolalpha << to_bool();
+        break;
+      case TYPE_INTEGER:
+        out << to_int();
+        break;
+      case TYPE_DOUBLE:
+        // the precision can be set by the caller
+        out << to_double();
+        break;
+      case TYPE_NULL:
+        out << "null";
+        break;
+      default:
+        qcp_not_implemented("unknown Object value type json emission")
+    }
+  }
+  return out.good();
+}
+bool Value::pack_json(std::ostream& out) const
+{
+  if (is_object())
+  {
+    to_object()->pack_json(out);
+  }
+  else if (is_array())
+  {
+    to_array()->pack_json(out);
+  }
+  else
+  {
+    switch (type())
+    {
+      case TYPE_STRING:
+        out << "\"" << to_string() << "\"";
+        break;
+      case TYPE_BOOLEAN:
+        out << std::boolalpha << to_bool();
+        break;
+      case TYPE_INTEGER:
+        out << to_int();
+        break;
+      case TYPE_DOUBLE:
+        // the precision can be set by the caller
+        out << to_double();
+        break;
+      case TYPE_NULL:
+        out << "null";
+        break;
+      default:
+        qcp_not_implemented("unknown Object value type json emission")
+    }
+  }
+  return out.good();
+}
+DataArray::DataArray() {}
+DataArray::~DataArray() {}
+DataArray::DataArray(const DataArray& orig)
+    : m_data(orig.m_data)
+{
+}
+size_t DataArray::size() const { return m_data.size(); }
+bool DataArray::empty() const { return m_data.empty(); }
+bool DataArray::format_json(std::ostream& out, int indent_level, int level,
+                            bool order_by_insert) const
+{
+  qcp_require(indent_level >= 0);
+  qcp_require(level >= 0);
+  if (empty())
+  {
+    out << "[]";
+    return true;
+  }
+  out << "[" << std::endl;
+  std::string indent = std::string(indent_level * (level + 1), ' ');
+  out << indent;
+  at(0).format_json(out, indent_level, level + 1, order_by_insert);
+
+  for (size_t i = 1, count = size(); i < count; ++i)
+  {
+    out << "," << std::endl << indent;
+    if (!at(i).format_json(out, indent_level, level + 1, order_by_insert))
+      return false;
+  }
+  out << std::endl;
+  out << std::string(indent_level * level, ' ') << "]";
+  return out.good();
+}
+bool DataArray::pack_json(std::ostream& out) const
+{
+  if (empty())
+  {
+    out << "[]";
+    return true;
+  }
+  out << "[";
+  at(0).pack_json(out);
+
+  for (size_t i = 1, count = size(); i < count; ++i)
+  {
+    out << ",";
+    if (!at(i).pack_json(out)) return false;
+  }
+  out << "]";
+  return out.good();
+}
+
+void DataArray::merge(const DataArray& rhs)
+{
+  // loop over every item in rhs array
+  size_t index = 0;
+  for (auto item : rhs)
+  {
+    qcp_line("Checking inbound array index(" << index << ")");
+    if (size() <= index)
+    {
+      qcp_line("lhs doesn't have index. adding rhs index to lhs");
+      this->push_back(item);
+    }
+    else
+    {
+      // must check the type of lhs[index] against that of rhs[index]
+      Value& child = m_data.at(index);
+      // if Value is an object do a depth-first merge
+      if (child.is_object())
+      {
+        // check that my object is indeed an object
+        if (!item.is_object())
+        {
+          std::stringstream ss;
+          ss << "Attempting to merge data arrary where member index(" << index
+             << ") differs in type." << std::endl
+             << "Left-hand side type(object) differs from right-hand "
+                "side type("
+             << item.categoryString() << ")";
+          throw std::logic_error(ss.str());
+        }
+        // merge objects
+        child.to_object()->merge(item.as_object());
+      }
+      else if (child.is_array())
+      {  // if Value is an array do a depth-first merge
+        if (!item.is_array())
+        {
+          std::stringstream ss;
+          ss << "Attempting to merge data array where member index(" << index
+             << " differs in type."
+             << "Left-hand side type(array) differs from right-hand "
+                "side type("
+             << item.categoryString() << ")";
+          throw std::logic_error(ss.str());
+        }
+        // merge array
+        child.to_array()->merge(item.as_array());
+      }
+    }
+    ++index;
+  }
+}
+DataObject::DataObject() {}
+DataObject::DataObject(const DataObject& orig)
+    : m_data(orig.m_data)
+    , m_insert_order(orig.m_insert_order)
+{
+}
+
+DataObject::~DataObject() {}
+size_t DataObject::size() const { return m_data.size(); }
+bool DataObject::empty() const { return m_data.empty(); }
+
+Value& DataObject::operator[](const std::string& name)
+{
+  // since c++11 std::map<>[] does insertion if key doesn't exist
+  m_insert_order.push_back(name);
+  return m_data[name];
+}
+
+const Value& DataObject::operator[](const std::string& name) const
+{
+  auto itr = m_data.find(name);
+
+  if (itr == m_data.end())
+  {
+    throw std::out_of_range(name + " not found in object");
+  }
+  return itr->second;
+}
+
+std::pair<DataObject::storage_type::iterator, bool> DataObject::insert(
+    const std::pair<std::string, Value>& v)
+{
+  m_insert_order.push_back(v.first);
+  return m_data.insert(v);
+}
+
+bool DataObject::format_json(std::ostream& out, int indent_level, int level,
+                             bool order_by_insert) const
+{
+  qcp_require(indent_level >= 0);
+  qcp_check(level >= 0);
+  if (empty())
+  {
+    out << "{}";
+    return true;
+  }
+
+  out << "{" << std::endl;
+  std::string indent = std::string(indent_level * (level + 1), ' ');
+  out << indent;
+
+  // determine order to print (map default of sorted, or order of insertion)
+  std::vector<std::string> order;
+  if (order_by_insert)
+    order = m_insert_order;
+  else
+    for (auto itr = m_data.begin(); itr != m_data.end(); ++itr)
+      order.push_back(itr->first);
+
+  auto itr = order.begin();
+  out << "\"" << *itr << "\" : ";
+  m_data.at(*itr).format_json(out, indent_level, level + 1, order_by_insert);
+  ++itr;
+  for (; itr != order.end(); ++itr)
+  {
+    out << "," << std::endl;
+    out << indent << "\"" << *itr << "\" : ";
+    if (!m_data.at(*itr).format_json(out, indent_level, level + 1,
+                                     order_by_insert))
+      return false;
+  }
+
+  out << std::endl;
+
+  out << std::string(indent_level * (level), ' ') << "}";
+  return out.good();
+}
+bool DataObject::pack_json(std::ostream& out) const
+{
+  if (empty())
+  {
+    out << "{}";
+    return true;
+  }
+
+  out << "{";
+  auto itr = begin();
+  out << "\"" << itr->first << "\":";
+  itr->second.pack_json(out);
+  ++itr;
+  for (; itr != end(); ++itr)
+  {
+    out << ",";
+    out << "\"" << itr->first << "\":";
+    if (!itr->second.pack_json(out)) return false;
+  }
+
+  out << "}";
+  return out.good();
+}
+
+void DataObject::merge(const DataObject& rhs)
+{
+  // merge every Value, Object and Array from 'obj'
+  // that does not exist in 'this'
+  for (auto& item : rhs)
+  {
+    qcp_line("Checking inbound " << item.first);
+    if (contains(item.first))
+    {
+      qcp_line("Destination contains " << item.first);
+      Value& child = m_data.at(item.first);
+      // if Value is an object do a depth-first merge
+      if (child.is_object())
+      {
+        // check that my object is indeed an object
+        if (!item.second.is_object())
+        {
+          std::stringstream ss;
+          ss << "Attempting to merge data objects where member(" << item.first
+             << ") differs in type." << std::endl
+             << "Left-hand side type(object) differs from right-hand "
+                "side type("
+             << item.second.categoryString() << ")";
+          throw std::logic_error(ss.str());
+        }
+        // merge objects
+        child.to_object()->merge(item.second.as_object());
+      }
+      else if (child.is_array())
+      {  // if Value is an array do a depth-first merge
+        if (!item.second.is_array())
+        {
+          std::stringstream ss;
+          ss << "Attempting to merge data objects where member(" << item.first
+             << " differs in type."
+             << "Left-hand side type(array) differs from right-hand "
+                "side type("
+             << item.second.categoryString() << ")";
+          throw std::logic_error(ss.str());
+        }
+        // merge array
+        child.to_array()->merge(item.second.as_array());
+      }
+    }
+    else
+    {  // no destination to merge so we can simply copy
+      qcp_line("Destination does not contain " << item.first
+                                                 << ". Copying...");
+      this->insert(item);
+    }
+  }
+}
+}  // namespace qcp
diff --git a/qcpcore/value.hh b/qcpcore/value.hh
new file mode 100644
index 0000000000000000000000000000000000000000..3e48424efd2054462919350d5aba0f67b529dac1
--- /dev/null
+++ b/qcpcore/value.hh
@@ -0,0 +1,256 @@
+#ifndef QCP_QCPCORE_VALUE_HH_
+#define QCP_QCPCORE_VALUE_HH_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "qcpcore/declspec.hh"
+
+namespace qcp
+{
+/**
+ * @brief The Value class represents values of objects (null, integer, double,
+ * string, array, or object)
+ */
+class QCP_PUBLIC Value
+{
+ public:
+  typedef std::shared_ptr<Value> SP;
+  enum Type : unsigned char
+  {
+    TYPE_NULL,
+    TYPE_BOOLEAN,
+    TYPE_INTEGER,
+    TYPE_DOUBLE,
+    TYPE_STRING,
+    TYPE_ARRAY,
+    TYPE_OBJECT
+  };
+
+ private:
+  bool m_allocated;
+  Type m_type;
+  union DataUnion {
+    bool m_bool;
+    double m_double;
+    int m_int;
+    char* m_string;
+    class DataArray* m_array;
+    class DataObject* m_object;
+  } m_data;
+
+ public:
+  /// null constructor
+  Value();
+  /// copy constructor
+  Value(const Value& orig);
+  // move constructor
+  Value(Value&& orig);
+
+  // boolean
+  Value(bool v);
+  // integer
+  Value(int v);
+  // double
+  Value(double v);
+  // const char *
+  Value(const char* v);
+  // string
+  Value(const std::string& v);
+  // data array
+  Value(const DataArray& v);
+  // data object
+  Value(const DataObject& v);
+  ~Value();
+
+  // assignment operators
+  Value& operator=(const Value& orig);
+  Value& operator=(Value&& orig);
+  Value& operator=(bool v);
+  Value& operator=(int v);
+  Value& operator=(double v);
+  Value& operator=(const char* v);
+  Value& operator=(const std::string& v);
+  Value& operator=(const DataArray& v);
+  Value& operator=(const DataObject& v);
+
+  Value::Type type() const;
+  /**
+   * @brief categoryString
+   * @return Returns a string for type display (object, array, number,
+   * boolean, null)
+   */
+  std::string categoryString() const;
+
+  bool is_null() const { return m_type == TYPE_NULL; }
+  bool is_int() const { return m_type == TYPE_INTEGER; }
+  bool is_double() const { return m_type == TYPE_DOUBLE; }
+  bool is_number() const { return is_int() || is_double(); }
+  bool is_bool() const { return m_type == TYPE_BOOLEAN; }
+  bool is_string() const { return m_type == TYPE_STRING; }
+  bool is_array() const { return m_type == TYPE_ARRAY; }
+  bool is_object() const { return m_type == TYPE_OBJECT; }
+  bool is_primitive() const { return !(is_array() || is_object()); }
+
+  bool convertable(Value::Type to) const;
+
+  int to_int() const;
+  double to_double() const;
+  bool to_bool() const;
+  const char* to_cstring() const;
+  std::string to_string() const;
+  DataArray* to_array() const;
+  DataObject* to_object() const;
+
+  const DataArray& as_array() const;
+  DataArray& as_array();
+  const DataObject& as_object() const;
+  DataObject& as_object();
+
+  Value& operator[](const std::string& name);
+  const Value& operator[](const std::string& name) const;
+  Value& operator[](size_t i);
+  const Value& operator[](size_t i) const;
+
+  /**
+   * @brief empty whether the value is empty
+   * @return false when value is null or empty object/array
+   */
+  bool empty() const;
+
+  /**
+   * @brief size the number of elements (object keys or array indices)
+   * @return size_t array element count, object member count, or 0
+   */
+  size_t size() const;
+
+  bool format_json(std::ostream& out, int indent_level = 2, int level = 0,
+                   bool order_by_insert = false) const;
+  bool pack_json(std::ostream& out) const;
+
+ private:
+  friend class JSONObjectParser;
+  void assign(DataObject* obj);
+  void assign(DataArray* array);
+  /**
+   * @brief nullify deletes and nullifies this object
+   */
+  void nullify();
+
+  /**
+   * @brief copy_from copies the given value to this value
+   * @param orig the value from which data will be copied
+   */
+  void copy_from(const Value& orig);
+};
+
+class QCP_PUBLIC DataArray
+{
+ public:
+  typedef std::shared_ptr<DataArray> SP;
+  typedef std::vector<Value> storage_type;
+
+ private:
+  storage_type m_data;
+
+ public:
+  DataArray();
+  DataArray(const DataArray& orig);
+  ~DataArray();
+
+  size_t size() const;
+  bool empty() const;
+
+  storage_type::const_iterator begin() const { return m_data.begin(); }
+  storage_type::const_iterator end() const { return m_data.end(); }
+  storage_type::iterator begin() { return m_data.begin(); }
+  storage_type::iterator end() { return m_data.end(); }
+
+  const Value& operator[](size_t i) const
+  {
+    return m_data.at(i);
+  }  // at(i) for exception
+  Value& operator[](size_t i)
+  {
+    if (size() <= i) resize(i + 1);
+    return m_data[i];
+  }
+
+  const Value& front() const { return m_data.front(); }
+  Value& front() { return m_data.front(); }
+  const Value& back() const { return m_data.back(); }
+  Value& back() { return m_data.back(); }
+  Value& at(size_t i) { return m_data.at(i); }
+  const Value& at(size_t i) const { return m_data.at(i); }
+  void push_back(const Value& n) { m_data.push_back(n); }
+  void resize(size_t nsize) { m_data.resize(nsize); }
+
+  bool format_json(std::ostream& out, int indent_level = 2, int level = 0,
+                   bool order_by_insert = false) const;
+  bool pack_json(std::ostream& out) const;
+  void merge(const DataArray& rhs);
+};
+
+class QCP_PUBLIC DataObject
+{
+ public:
+  typedef std::shared_ptr<DataObject> SP;
+  typedef std::map<std::string, Value> storage_type;
+
+ private:
+  std::vector<std::string> m_insert_order;
+  storage_type m_data;
+
+ public:
+  DataObject();
+  DataObject(const DataObject& orig);
+  ~DataObject();
+
+  size_t size() const;
+  bool empty() const;
+
+  storage_type::const_iterator find(const std::string& name) const
+  {
+    return m_data.find(name);
+  }
+  storage_type::iterator find(const std::string& name)
+  {
+    return m_data.find(name);
+  }
+
+  storage_type::const_iterator begin() const { return m_data.begin(); }
+  storage_type::const_iterator end() const { return m_data.end(); }
+
+  storage_type::iterator begin() { return m_data.begin(); }
+  storage_type::iterator end() { return m_data.end(); }
+
+  Value& operator[](const std::string& name);
+  const Value& operator[](const std::string& name) const;
+  std::pair<storage_type::iterator, bool> insert(
+      const std::pair<std::string, Value>& v);
+
+  bool contains(const std::string& name) const
+  {
+    return m_data.find(name) != end();
+  }
+
+  bool format_json(std::ostream& out, int indent_level = 2, int level = 0,
+                   bool order_by_insert = false) const;
+  bool pack_json(std::ostream& out) const;
+
+  void merge(const DataObject& rhs);
+};
+
+template <class Interp>
+QCP_PUBLIC bool generate_object(DataObject::SP& obj, std::istream& input,
+                                  std::ostream& errors)
+{
+  Interp interpreter(obj, input, errors, nullptr);
+  bool parsed = interpreter.parse() == 0;
+  return parsed;
+}
+
+}  // namespace qcp
+#endif /** QCP_QCPCORE_VALUE_HH_ */
diff --git a/qcpexchange/cmake/Dependencies.cmake b/qcpexchange/cmake/Dependencies.cmake
index 9e4d55fe863831c5d4609bcb2f50144b7dbee5d9..de696c74a63ffaaf81dbd4a6aa80d82c07ea1459 100644
--- a/qcpexchange/cmake/Dependencies.cmake
+++ b/qcpexchange/cmake/Dependencies.cmake
@@ -3,7 +3,7 @@
 ## Tom Norby
 ##---------------------------------------------------------------------------##
 TRIBITS_PACKAGE_DEFINE_DEPENDENCIES(
-   LIB_REQUIRED_PACKAGES qcpcontext radixwidgets radixcore radixbug
+   LIB_REQUIRED_PACKAGES qcpcontext
    LIB_REQUIRED_TPLS Qt5Widgets Qt5Core Qt5Network
 )
 ##---------------------------------------------------------------------------##
diff --git a/qcpexchange/qcpexchange.cc b/qcpexchange/qcpexchange.cc
index a597d914d53afdadf4f803e6b17748cd8eea98d6..f092135d0ce0856078c50b4414455e7f437ef090 100644
--- a/qcpexchange/qcpexchange.cc
+++ b/qcpexchange/qcpexchange.cc
@@ -1,13 +1,13 @@
 #include "qcpexchange/qcpexchange.hh"
 #include "qcpexchange/singleinstanceapp.hh"
 
-#include "radixbug/bug.hh"
-#include "radixcore/json.hh"
-#include "radixcore/system.hh"
+#include "qcpcore/bug.hh"
+#include "qcpcore/json.hh"
+//#include "qcpcore/system.hh"
 
-#include "radixwidgets/navigationactionmanager.hh"
-#include "radixwidgets/navigationitem.hh"
-#include "radixwidgets/navigationmodel.hh"
+#include "qcpcontext/navigationactionmanager.hh"
+#include "qcpcontext/navigationitem.hh"
+#include "qcpcontext/navigationmodel.hh"
 
 #include <QApplication>
 #include <QLocalSocket>
@@ -24,8 +24,8 @@ QCPExchange::QCPExchange(QWidget *parent)
 
   mSplit = new QSplitter(mCentralWidget);
 
-  mNavigationView                  = new radix::NavigationWidget(this);
-  radix::NavigationModel *navModel = mNavigationView->navigationModel();
+  mNavigationView                  = new NavigationWidget(this);
+  NavigationModel *navModel = mNavigationView->navigationModel();
   // mPlotTreeView   = new QTreeView(this);
   // mPlotTreeModel  = new QStandardItemModel(mPlotTreeView);
   // mPlotTreeView->setModel(mPlotTreeModel);
@@ -52,7 +52,7 @@ QCPExchange::QCPExchange(QWidget *parent)
     {
       if (args.length() < 2)
       {
-        radix_line("No json file passed to application.");
+        qcp_line("No json file passed to application.");
         return;
       }
 
@@ -72,7 +72,7 @@ QCPExchange::QCPExchange(QWidget *parent)
     }
   };
 
-  connect(mPlot->qcp(), SIGNAL(selectionChangedByUser()), this,
+  connect(mPlot->qcplot(), SIGNAL(selectionChangedByUser()), this,
           SLOT(plotSelectionChangedByUser()));
   //
   // connect navigation model signals
@@ -118,34 +118,35 @@ QCPExchange::~QCPExchange() {}
 
 void QCPExchange::processPlotFile(const char *absFilepath)
 {
-  radix::NavigationModel *navModel = mNavigationView->navigationModel();
-  radix::NavigationActionManager *navActions =
+  NavigationModel *navModel = mNavigationView->navigationModel();
+  NavigationActionManager *navActions =
       mNavigationView->navigationActionManager();
   // get plot data from .json
-  radix::DataObject rootObject;
+  DataObject rootObject;
   {
-    radix::JSONParser jp;
+    JSONParser jp;
     bool parsed = jp.parse_from_file(absFilepath);
     if (!parsed || jp.root().is_null())
     {
-      radix_line("JSONParser failed to parse'" << absFilepath << "'");
-      radix_line("JSONParser ERROR: " << jp.last_error());
+      qcp_line("JSONParser failed to parse'" << absFilepath << "'");
+      qcp_line("JSONParser ERROR: " << jp.last_error());
       QMessageBox::information(this, "Error", jp.last_error().c_str());
       return;
     }
     if (!jp.root().is_object())
     {
-      radix_line("Root is not an object");
+      qcp_line("Root is not an object");
       return;
     }
     rootObject = jp.root().as_object();
   }
-  radix_line("Successfully parsed JSON from: " << absFilepath);
+  qcp_line("Successfully parsed JSON from: " << absFilepath);
 
+  QFileInfo fileInfo(absFilepath);
   //
   // Add root file item to navigation model
-  radix::NavigationItem *navFileItem = navModel->addItem(
-      radix::basename(absFilepath).c_str(), navModel->rootItem());
+  NavigationItem *navFileItem = navModel->addItem(
+      fileInfo.baseName(), navModel->rootItem());
   // Display absolute file path on mouse over
   navFileItem->setData(0, absFilepath, Qt::ToolTipRole);
   //
@@ -163,10 +164,10 @@ void QCPExchange::processPlotFile(const char *absFilepath)
   // interactions
   if (rootObject.contains("interactions"))
   {
-    const radix::DataArray &interactionArray =
+    const DataArray &interactionArray =
         rootObject["interactions"].as_array();
     QCP::Interactions interactions = nullptr;
-    for (const radix::Value &val : interactionArray)
+    for (const Value &val : interactionArray)
     {
       QCP::Interaction interaction = QCP::Interaction(val.to_int());
       if (!interactions)
@@ -174,14 +175,14 @@ void QCPExchange::processPlotFile(const char *absFilepath)
       else
         interactions |= interaction;
     }
-    mPlot->qcp()->setInteractions(interactions);
+    mPlot->qcplot()->setInteractions(interactions);
   }
 
   // selection rect mode
   if (rootObject.contains("selectionRectMode"))
   {
     int srm = rootObject["selectionRectMode"].to_int();
-    mPlot->qcp()->setSelectionRectMode(QCP::SelectionRectMode(srm));
+    mPlot->qcplot()->setSelectionRectMode(QCP::SelectionRectMode(srm));
   }
 
   struct AxisStruct
@@ -193,7 +194,7 @@ void QCPExchange::processPlotFile(const char *absFilepath)
     int numberPrecision;
   };
   auto axisOptionsLambda = [](AxisStruct &axis,
-                              const radix::DataObject &axisVal) {
+                              const DataObject &axisVal) {
     if (axisVal.contains("label")) axis.label = axisVal["label"].to_cstring();
 
     if (axisVal.contains("subGridVisible"))
@@ -235,13 +236,13 @@ void QCPExchange::processPlotFile(const char *absFilepath)
 
   // graphs
   {
-    const radix::DataArray &groups = rootObject["groups"].as_array();
+    const DataArray &groups = rootObject["groups"].as_array();
     for (size_t gi = 0; gi < groups.size(); ++gi)
     {
-      const radix::DataObject &group = groups[gi].as_object();
+      const DataObject &group = groups[gi].as_object();
       //
       // Add group to the file navigation
-      radix::NavigationItem *plotGroupNav =
+      NavigationItem *plotGroupNav =
           navModel->addItem(group["groupName"].to_cstring(), navFileItem);
       //
       // Register actions to group navigation
@@ -251,12 +252,12 @@ void QCPExchange::processPlotFile(const char *absFilepath)
                                    navActions->clearActions(plotGroupNav);
                                  });
 
-      const radix::DataArray &graphsArray = group["graphs"].as_array();
+      const DataArray &graphsArray = group["graphs"].as_array();
       for (size_t i = 0; i < graphsArray.size(); ++i)
       {
         QCPGraphData::SP graphData = std::make_shared<QCPGraphData>();
         // cast as an object
-        const radix::DataObject &graph = graphsArray[i].as_object();
+        const DataObject &graph = graphsArray[i].as_object();
         if (graph.contains("keyAxis") && graph.contains("valueAxis"))
         {
           std::string value = graph["keyAxis"].to_string();
@@ -286,7 +287,7 @@ void QCPExchange::processPlotFile(const char *absFilepath)
           graphData->setName(graph["name"].to_cstring());
         }
 
-        radix::NavigationItem *plotItemNav =
+        NavigationItem *plotItemNav =
             navModel->addItem(graphData->name(), plotGroupNav);
 
         navActions->registerAction(plotItemNav, "remove",
@@ -302,7 +303,7 @@ void QCPExchange::processPlotFile(const char *absFilepath)
         // pen color & style
         if (graph.contains("pen"))
         {
-          const radix::DataObject &pen = graph["pen"].as_object();
+          const DataObject &pen = graph["pen"].as_object();
           if (pen.contains("color"))
           {
             graphData->setPenColor(pen["color"].to_cstring());
@@ -340,13 +341,13 @@ void QCPExchange::processPlotFile(const char *absFilepath)
         if (graph.contains("xData") && graph.contains("yData"))
         {
           QVector<double> xData, yData;
-          const radix::DataArray &xArray = graph["xData"].as_array();
-          for (const radix::Value &val : xArray)
+          const DataArray &xArray = graph["xData"].as_array();
+          for (const Value &val : xArray)
           {
             xData.push_back(val.to_double());
           }
-          const radix::DataArray &yArray = graph["yData"].as_array();
-          for (const radix::Value &val : yArray)
+          const DataArray &yArray = graph["yData"].as_array();
+          for (const Value &val : yArray)
           {
             yData.push_back(val.to_double());
           }
@@ -361,8 +362,8 @@ void QCPExchange::processPlotFile(const char *absFilepath)
 
 bool QCPExchange::enableItem(const QModelIndex &index)
 {
-  radix::NavigationModel *navModel = mNavigationView->navigationModel();
-  radix::NavigationItem *item      = navModel->toItem(index);
+  NavigationModel *navModel = mNavigationView->navigationModel();
+  NavigationItem *item      = navModel->toItem(index);
   auto it                          = mData.find(item);
   // not all nav items have graph data associated
   if (it != mData.end())
@@ -371,7 +372,7 @@ bool QCPExchange::enableItem(const QModelIndex &index)
     QCPGraph *graph       = it->second.second;
     // if the graph is already allocated, return
     if (graph != nullptr) return true;
-    radix_tagged_line("Item checked (" << item << ")->name("
+    qcp_tagged_line("Item checked (" << item << ")->name("
                                        << data->name().toStdString() << ")");
     QCPAxis *keyAxis = nullptr, *valueAxis = nullptr;
     QString value = data->keyAxisName();
@@ -412,14 +413,14 @@ bool QCPExchange::enableItem(const QModelIndex &index)
 
     QString xAxisLabel = data->xAxisLabel();
     QString yAxisLabel = data->yAxisLabel();
-    radix_tagged_line("Comparing xAxis("
+    qcp_tagged_line("Comparing xAxis("
                       << keyAxis->label().toStdString() << ") to data("
                       << xAxisLabel.toStdString() << ") and yAxis("
                       << valueAxis->label().toStdString() << ") to data("
                       << yAxisLabel.toStdString() << ")");
     if ((xAxisLabel.compare(keyAxis->label()) != 0 ||
          yAxisLabel.compare(valueAxis->label()) != 0) &&
-        (mPlot->qcp()->plottableCount() != 0))  // There are plots visible
+        (mPlot->qcplot()->plottableCount() != 0))  // There are plots visible
     {
       //
       // Ask the user if they want to continue
@@ -493,14 +494,14 @@ bool QCPExchange::enableItem(const QModelIndex &index)
 
 void QCPExchange::plotSelectionChangedByUser()
 {
-  int graphCount = mPlot->qcp()->graphCount();
+  int graphCount = mPlot->qcplot()->graphCount();
   //
   // Select the item in the legend if the graph was selected within context
   for (int i = 0; i < graphCount; ++i)
   {
-    QCPGraph *graph = mPlot->qcp()->graph(i);
+    QCPGraph *graph = mPlot->qcplot()->graph(i);
     QCPPlottableLegendItem *item =
-        mPlot->qcp()->legend->itemWithPlottable(graph);
+        mPlot->qcplot()->legend->itemWithPlottable(graph);
     if (item->selected() || graph->selected())
     {
       item->setSelected(true);
@@ -541,9 +542,9 @@ void QCPExchange::saveToCSV(QCPContext *plot)
   // Create file
   std::ofstream ofs(filePath.toStdString(), std::ofstream::out);
 
-  for (int i = 0; i < plot->qcp()->graphCount(); ++i)
+  for (int i = 0; i < plot->qcplot()->graphCount(); ++i)
   {
-    QCPGraph *graph = plot->qcp()->graph(i);
+    QCPGraph *graph = plot->qcplot()->graph(i);
     // print header
     ofs << graph->name().toStdString() << ", "
         << graph->keyAxis()->label().toStdString() << ", "
@@ -557,7 +558,7 @@ void QCPExchange::saveToCSV(QCPContext *plot)
 
 void QCPExchange::itemChecked(const QModelIndex &index)
 {
-  radix::NavigationModel *navModel = mNavigationView->navigationModel();
+  NavigationModel *navModel = mNavigationView->navigationModel();
 
   if (index.isValid())
   {
@@ -591,10 +592,10 @@ void QCPExchange::itemChecked(const QModelIndex &index)
 
 void QCPExchange::itemUnChecked(const QModelIndex &index)
 {
-  radix_tagged_line("item unchecked");
+  qcp_tagged_line("item unchecked");
   if (index.isValid())
   {
-    radix::NavigationModel *navModel = mNavigationView->navigationModel();
+    NavigationModel *navModel = mNavigationView->navigationModel();
 
     int numKids = navModel->rowCount(index);
     for (int ki = 0; ki < numKids; ++ki)
@@ -604,8 +605,8 @@ void QCPExchange::itemUnChecked(const QModelIndex &index)
       // set them as checked
       navModel->setData(child, false, Qt::CheckStateRole);
     }
-    radix::NavigationItem *item = navModel->toItem(index);
-    radix_tagged_line("Item unchecked (" << item << ")");
+    NavigationItem *item = navModel->toItem(index);
+    qcp_tagged_line("Item unchecked (" << item << ")");
     auto it = mData.find(item);
     // not all nav items have graph data associated
     if (it != mData.end())
@@ -613,7 +614,7 @@ void QCPExchange::itemUnChecked(const QModelIndex &index)
       QCPGraphData::SP data = it->second.first;
       QCPGraph *graph       = it->second.second;
       if (graph == nullptr) return;
-      mPlot->qcp()->removeGraph(graph);
+      mPlot->qcplot()->removeGraph(graph);
       it->second.second = nullptr;
       updatePlot(true);
     }
@@ -625,8 +626,8 @@ static bool updateFile()
 {
   QLocalSocket *socket = new QLocalSocket;
   socket->connectToServer("localhost");
-  radix_line(socket->error());
-  radix_line(socket->open());
+  qcp_line(socket->error());
+  qcp_line(socket->open());
   QDir appdir      = SingleInstanceApp::applicationDirPath();
   QStringList args = SingleInstanceApp::arguments();
   QByteArray block;
diff --git a/qcpexchange/qcpexchange.hh b/qcpexchange/qcpexchange.hh
index 592957efd1da4b0365db3429a1082e6ee762009b..1c0469ba248754095c9fc30458e30d4f8d354b58 100644
--- a/qcpexchange/qcpexchange.hh
+++ b/qcpexchange/qcpexchange.hh
@@ -11,8 +11,8 @@
 #include "qcpcontext/qcpgraphdata.hh"
 #include "qcpexchange/plottreeitem.hh"
 
-#include "radixwidgets/navigationitem.hh"
-#include "radixwidgets/navigationwidget.hh"
+#include "qcpcontext/navigationitem.hh"
+#include "qcpcontext/navigationwidget.hh"
 
 namespace qcp
 {
@@ -44,8 +44,8 @@ class QCPExchange : public QMainWindow
   QSplitter *mSplit;
   QVBoxLayout *mPlotLayout;
 
-  radix::NavigationWidget *mNavigationView;
-  std::unordered_map<radix::NavigationItem *,
+  NavigationWidget *mNavigationView;
+  std::unordered_map<NavigationItem *,
                      std::pair<QCPGraphData::SP, QCPGraph *>>
       mData;
 
diff --git a/submodules/radix b/submodules/radix
deleted file mode 160000
index 582e91f5a30cc25b4cd58fc889dc1aa32f5e5c2a..0000000000000000000000000000000000000000
--- a/submodules/radix
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 582e91f5a30cc25b4cd58fc889dc1aa32f5e5c2a