Index: src/sas/qtgui/DensityPanel.py
===================================================================
--- src/sas/qtgui/DensityPanel.py	(revision 6083d6eac7288c42811c392e38943a4b00078d5e)
+++ src/sas/qtgui/DensityPanel.py	(revision 6083d6eac7288c42811c392e38943a4b00078d5e)
@@ -0,0 +1,171 @@
+# global
+import logging
+from periodictable import formula as Formula
+
+from PyQt4 import QtGui, QtCore
+
+# Local UI
+from UI.DensityPanel import Ui_DensityPanel
+
+def enum(*sequential, **named):
+    enums = dict(zip(sequential, range(len(sequential))), **named)
+    return type('Enum', (), enums)
+
+MODEL = enum(
+    'MOLECULAR_FORMULA',
+    'MOLAR_MASS',
+    'MOLAR_VOLUME',
+    'MASS_DENSITY',
+)
+
+MODES = enum(
+    'VOLUME_TO_DENSITY',
+    'DENSITY_TO_VOLUME',
+)
+
+def toMolarMass(formula):
+    AVOGADRO = 6.02214129e23
+
+    try:
+        f = Formula(str(formula))
+        return "%g" % (f.molecular_mass * AVOGADRO)
+    except:
+        return ""
+
+
+class FormulaValidator(QtGui.QValidator):
+    def __init__(self, parent=None):
+        super(FormulaValidator, self).__init__(parent)
+  
+    def validate(self, input, pos):
+        try:
+            Formula(str(input))
+            self._setStyleSheet("")
+            return QtGui.QValidator.Acceptable, pos
+
+        except Exception as e:
+            self._setStyleSheet("background-color:pink;")
+            return QtGui.QValidator.Intermediate, pos
+
+    def _setStyleSheet(self, value):
+        try:
+            if self.parent():
+                self.parent().setStyleSheet(value)
+        except:
+            pass
+
+
+class DensityPanel(QtGui.QDialog):
+
+    def __init__(self, parent=None):
+        super(DensityPanel, self).__init__(parent)
+
+        self.mode = None
+
+        self.setupUi()
+        self.setupModel()
+        self.setupMapper()
+
+    def setupUi(self):
+        self.ui = Ui_DensityPanel()
+        self.ui.setupUi(self)
+
+        # set validators
+        self.ui.editMolecularFormula.setValidator(FormulaValidator(self.ui.editMolecularFormula))
+
+        rx = QtCore.QRegExp("[+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?")
+        self.ui.editMolarVolume.setValidator(QtGui.QRegExpValidator(rx, self.ui.editMolarVolume))
+        self.ui.editMassDensity.setValidator(QtGui.QRegExpValidator(rx, self.ui.editMassDensity))
+
+        # signals
+        QtCore.QObject.connect(
+            self.ui.editMolarVolume,
+            QtCore.SIGNAL("textEdited(QString)"),
+            lambda text: self.setMode(MODES.VOLUME_TO_DENSITY))
+        QtCore.QObject.connect(
+            self.ui.editMassDensity,
+            QtCore.SIGNAL("textEdited(QString)"),
+            lambda text: self.setMode(MODES.DENSITY_TO_VOLUME))
+        QtCore.QObject.connect(
+            self.ui.buttonBox.button(QtGui.QDialogButtonBox.Reset),
+            QtCore.SIGNAL("clicked(bool)"),
+            lambda checked: self.modelReset())
+
+    def setupModel(self):
+        self.model = QtGui.QStandardItemModel(self)
+        self.model.setItem(MODEL.MOLECULAR_FORMULA, QtGui.QStandardItem())
+        self.model.setItem(MODEL.MOLAR_MASS       , QtGui.QStandardItem())
+        self.model.setItem(MODEL.MOLAR_VOLUME     , QtGui.QStandardItem())
+        self.model.setItem(MODEL.MASS_DENSITY     , QtGui.QStandardItem())
+
+        QtCore.QObject.connect(
+            self.model,
+            QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
+            self.dataChanged)
+
+        self.modelReset()
+
+    def setupMapper(self):
+        self.mapper = QtGui.QDataWidgetMapper(self)
+        self.mapper.setModel(self.model)
+        self.mapper.setOrientation(QtCore.Qt.Vertical)
+
+        self.mapper.addMapping(self.ui.editMolecularFormula, MODEL.MOLECULAR_FORMULA)
+        self.mapper.addMapping(self.ui.editMolarMass       , MODEL.MOLAR_MASS)
+        self.mapper.addMapping(self.ui.editMolarVolume     , MODEL.MOLAR_VOLUME)
+        self.mapper.addMapping(self.ui.editMassDensity     , MODEL.MASS_DENSITY)
+
+        self.mapper.toFirst()
+
+    def dataChanged(self, top, bottom):
+        for index in xrange(top.row(), bottom.row() + 1):
+            if index == MODEL.MOLECULAR_FORMULA:
+                molarMass = toMolarMass(self.model.item(MODEL.MOLECULAR_FORMULA).text())
+                self.model.item(MODEL.MOLAR_MASS).setText(molarMass)
+
+                if self.mode == MODES.VOLUME_TO_DENSITY:
+                    self._updateDensity()
+                elif self.mode == MODES.DENSITY_TO_VOLUME:
+                    self._updateVolume()
+
+            elif index == MODEL.MOLAR_VOLUME and self.mode == MODES.VOLUME_TO_DENSITY:
+                self._updateDensity()
+
+            elif index == MODEL.MASS_DENSITY and self.mode == MODES.DENSITY_TO_VOLUME:
+                self._updateVolume()
+
+    def setMode(self, mode):
+        self.mode = mode
+
+    def _updateDensity(self):
+        try:
+            molarMass = float(toMolarMass(self.model.item(MODEL.MOLECULAR_FORMULA).text()))
+            molarVolume = float(self.model.item(MODEL.MOLAR_VOLUME).text())
+
+            molarDensity = molarMass / molarVolume
+            self.model.item(MODEL.MASS_DENSITY).setText(str(molarDensity))
+
+        except:
+            self.model.item(MODEL.MASS_DENSITY).setText("")
+
+    def _updateVolume(self):
+        try:
+            molarMass = float(toMolarMass(self.model.item(MODEL.MOLECULAR_FORMULA).text()))
+            molarDensity = float(self.model.item(MODEL.MASS_DENSITY).text())
+
+            molarVolume = molarMass / molarDensity
+            self.model.item(MODEL.MOLAR_VOLUME).setText(str(molarVolume))
+
+        except:
+            self.model.item(MODEL.MOLAR_VOLUME).setText("")
+
+    def modelReset(self):
+        #self.model.beginResetModel()
+        try:
+            self.setMode(None)
+            self.model.item(MODEL.MOLECULAR_FORMULA).setText("H2O")
+            self.model.item(MODEL.MOLAR_VOLUME     ).setText("")
+            self.model.item(MODEL.MASS_DENSITY     ).setText("")
+        finally:
+            pass
+            #self.model.endResetModel()
Index: src/sas/qtgui/UI/DensityPanel.ui
===================================================================
--- src/sas/qtgui/UI/DensityPanel.ui	(revision 6083d6eac7288c42811c392e38943a4b00078d5e)
+++ src/sas/qtgui/UI/DensityPanel.ui	(revision 6083d6eac7288c42811c392e38943a4b00078d5e)
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DensityPanel</class>
+ <widget class="QDialog" name="DensityPanel">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>633</width>
+    <height>293</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Density/Volume Calculator</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_2">
+   <item row="0" column="0">
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="0" column="0">
+      <widget class="QLabel" name="label_1">
+       <property name="text">
+        <string>Molecular Formula</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QLineEdit" name="editMolecularFormula"/>
+     </item>
+     <item row="0" column="2">
+      <widget class="QLabel" name="label_3">
+       <property name="text">
+        <string>e.g. H2O</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string>Molar Mass</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QLineEdit" name="editMolarMass">
+       <property name="styleSheet">
+        <string notr="true"/>
+       </property>
+       <property name="readOnly">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="2">
+      <widget class="QLabel" name="label_4">
+       <property name="text">
+        <string>g/mol</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0" colspan="3">
+      <widget class="QFrame" name="frame">
+       <property name="minimumSize">
+        <size>
+         <width>0</width>
+         <height>5</height>
+        </size>
+       </property>
+       <property name="frameShape">
+        <enum>QFrame::HLine</enum>
+       </property>
+       <property name="frameShadow">
+        <enum>QFrame::Raised</enum>
+       </property>
+       <property name="lineWidth">
+        <number>1</number>
+       </property>
+       <property name="midLineWidth">
+        <number>0</number>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="0">
+      <widget class="QLabel" name="label_5">
+       <property name="text">
+        <string>Molar Volume</string>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="1">
+      <widget class="QLineEdit" name="editMolarVolume"/>
+     </item>
+     <item row="3" column="2">
+      <widget class="QLabel" name="label_6">
+       <property name="text">
+        <string>cmÂ³/mol</string>
+       </property>
+      </widget>
+     </item>
+     <item row="4" column="0">
+      <widget class="QLabel" name="label_7">
+       <property name="text">
+        <string>Mass Density</string>
+       </property>
+      </widget>
+     </item>
+     <item row="4" column="1">
+      <widget class="QLineEdit" name="editMassDensity"/>
+     </item>
+     <item row="4" column="2">
+      <widget class="QLabel" name="label_8">
+       <property name="text">
+        <string>g/cmÂ³</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="1" column="0">
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Expanding</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="2" column="0">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close|QDialogButtonBox::Reset</set>
+     </property>
+     <property name="centerButtons">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>editMolecularFormula</tabstop>
+  <tabstop>editMolarMass</tabstop>
+  <tabstop>editMolarVolume</tabstop>
+  <tabstop>editMassDensity</tabstop>
+  <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>DensityPanel</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>DensityPanel</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
