Handling units#
In this tutorial, we explore the role of units in SAMSON by developing an app that performs energy conversions. Please, refer to the Units section for an extensive description of the units mechanism in SAMSON.
Note
For the sake of the tutorial, we demonstrate here a simplified implementation of the units converter.
The source code for an Extension created in this tutorial can be found at SAMSON-Developer-Tutorials.
Generating an Extension#
We will start by creating a new SAMSON Extension called EnergyConverter thanks to the SAMSON Extension Generator (please, refer to the SAMSON Extension Generator tutorial for a reminder on how to use it):
- launch SAMSON and run the SAMSON Extension Generator;
- specify the path to a folder where you want to develop your SAMSON Extensions;
- name the SAMSON Extension as EnergyConverter and add some description;
- add an App class (called
SEEnergyConverterApp
); - generate the SAMSON Extension.
The SAMSON Extension Generator generates an app class that implements the functionality of the app and is derived from the SBDApp
class, and a GUI class that implements the user interface of the app and is derived from the SBGApp
class.
UnitsConverterDescription Setting the description of the Extension#
Open the header file of the app descriptor (file: SEEnergyConverterAppDescriptor.hpp) and modify the class description:
Open the source file of the app GUI (file: SEEnergyConverterAppGUI.cpp) and modify the getName
function to have a user-friendly description of the app inside SAMSON:
Now, try building the Extension and when you launch SAMSON you should be able to find it in Home > Apps. For now, it has no interface and does nothing, but we will now add functionality to it.
Setting up the interface#
Let's start by adding the interface for the energy conversion.
Modify the user interface SEEnergyConverterAppGUI.ui to include two line edits (QLineEdit
), two combo boxes (QComboBox
), and a label.
Note how two vertical layouts and a horizontal one are used to align widgets in the interface. One good strategy is to create the first vertical layout and its contents and then copy it and rename widgets. You might also need to set the minimal width for the label to e.g. 10.
Please, name the added widgets as on an image above, so that you can copy and paste code later in this tutorial.
Now, enter the following entries (eV, Eh, kcal/mol, zJ
) in each combo box (right clicl on a combo box and click on Edit items...):
These are the different units supported by our app to perform conversions.
Add two slots called onLeftChanged()
and onRightChanged()
to the interface:
We want to implement the following behavior for the interface:
- when the user modifies the value in the left line edit or changes the current index in any of the combo boxes, the value in the right line edit should be updated;
- when the user modifies the value in the right line edit, the value in the left line edit should be updated.
For that, we connect the textEdited
signal from the left line edit and the currentIndexChanged
signal from both combo boxes to the onLeftChanged()
slot, and the textEdited
signal from the right line editto the onRightChanged()
slot as shown on an image below:
Note
We use the textEdited
signal for the line edits because it is emitted when the text was edited by the user; the textChanged
signal, on the other hand, is also emitted when the text was changed programatically.
UnitsConverterImplementation Converting energies#
Let's add these slots to the GUI class (class: SEEnergyConverterAppGUI
, file: SEEnergyConverterAppGUI.hpp):
class SEEnergyConverterAppGUI : public SBGApp {
// ...
public slots:
void onLeftChanged();
void onRightChanged();
// ...
};
For a clean organization, we separate the functionality of the app from its interface. As a result, the SEEnergyConverterAppGUI
class is going to delegate the actual energy conversion work to the app (SEEnergyConverterApp
class).
Let's now implement a function for energy conversion in the app. Add in the app class (class: SEEnergyConverterApp
, file: SEEnergyConverterApp.hpp) the following function declaration:
class SEEnergyConverterApp : public SBDApp {
// ...
/// \name Conversion
//@{
double convert(double sourceEnergy,
const unsigned int sourceUnit,
const unsigned int destinationUnit);
//@}
};
In the source file of the app (SEEnergyConverterApp.cpp) add the header SBQuantity.hpp:
And implement the convert
function as follows:
double SEEnergyConverterApp::convert(
double sourceEnergy,
const unsigned int sourceUnit,
const unsigned int destinationUnit) {
SBQuantity::energy source;
if (sourceUnit == 0) source = SBQuantity::eV(sourceEnergy);
if (sourceUnit == 1) source = SBQuantity::Eh(sourceEnergy);
if (sourceUnit == 2) source = SBQuantity::kcalPerMol(sourceEnergy);
if (sourceUnit == 3) source = SBQuantity::zJ(sourceEnergy);
if (destinationUnit == 0) return (SBQuantity::eV(source)).getValue();
if (destinationUnit == 1) return (SBQuantity::Eh(source)).getValue();
if (destinationUnit == 2) return (SBQuantity::kcalPerMol(source)).getValue();
if (destinationUnit == 3) return (SBQuantity::zJ(source)).getValue();
return 0.0;
}
Here, we first declare a physical quantity with energy units (zeptoJoules by default in SAMSON). Then we convert from the source energy to zeptoJoules and then we convert from zeptoJoules to the destination unit, and extract the value as a double from the result. Note, that all the conversions are done internaly by SAMSON. You can check the units page for more information.
Now, we program the slots. Open the source file for the GUI of the app (SEEnergyConverterAppGUI.cpp) and implement the onLeftChanged()
slot as follows:
void SEEnergyConverterAppGUI::onLeftChanged() {
// convert the string from the line edit to a double
// with checking for valid numerical characters
// (including the scientific notation)
bool ok;
double source = ui.lineEditLeft->text().toDouble(&ok);
if (ok) {
// convert units
unsigned int sourceUnit = ui.comboBoxLeft->currentIndex();
unsigned int destinationUnit = ui.comboBoxRight->currentIndex();
double destination = getApp()->convert(source,
sourceUnit, destinationUnit);
// set the destination value
// QString::number will use float or scientific notation format,
// whichever is the most concise
ui.lineEditRight->setText(QString::number(destination));
}
else {
// in the case when the line edit contained invalid characters
// clear another line edit
ui.lineEditRight->setText("");
}
}
Here, we first try to convert the string from the line edit into a number: the toDouble
function from the QString
class takes care of checking the validity of the numeric string, including the support for scientific notation (i.e. numbers in the format 1.5e+5 are supported). Then, if the string has a valid number we do a conversion, else we empty the other line edit.
Implement the onRightChanged()
slot in the same way by exchanging the source and destination widgets:
void SEEnergyConverterAppGUI::onRightChanged() {
// convert the string from the line edit to a double
// with checking for valid numerical characters
// (including the scientific notation)
bool ok;
double source = ui.lineEditRight->text().toDouble(&ok);
if (ok) {
// convert units
unsigned int sourceUnit = ui.comboBoxRight->currentIndex();
unsigned int destinationUnit = ui.comboBoxLeft->currentIndex();
double destination = getApp()->convert(source,
sourceUnit, destinationUnit);
// set the destination value
// QString::number will use float or scientific notation format,
// whichever is the most concise
ui.lineEditLeft->setText(QString::number(destination));
}
else {
// in the case when the line edit contained invalid characters
// clear another line edit
ui.lineEditLeft->setText("");
}
}
Now, you have a simple energy converter implementation. Build the Extension, launch SAMSON, open the Extension, and try converting values.