Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1831 lines (1700 sloc) 80.3 KB
/********************************************************************************
**
** [W]ASABI [A]ffect [S]imulation [A]rchitecture for [B]elievable [I]nteractivity
**
** Copyright (C) 2011 Christian Becker-Asano.
** All rights reserved.
** Contact: Christian Becker-Asano (christian@becker-asano.de)
**
** This file is part of the WASABIQtGui program.
**
** The WASABIQtGui program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Lesser General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** The WASABIQtGui program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public License
** along with the WASABIQtGui program. If not, see <http://www.gnu.org/licenses/>
**
********************************************************************************/
#include "wasabiqtwindow.h"
#include "ui_wasabiqtwindow.h"
#include "padwindow.h"
#include "PrimaryEmotion.h"
#include "SecondaryEmotion.h"
#include <sstream>
#include <fstream>
#include <string>
#include <QDebug>
#include <QDir>
#include <QInputDialog>
#include <QtGui>
#include <QtNetwork>
#include <QMessageBox>
#include <QErrorMessage>
// some helpers from http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=2896
#define SPACES " \t\r\n"
using namespace std;
inline string trim_right(const string & s, const string & t = SPACES) {
string d(s);
string::size_type i(d.find_last_not_of(t));
if (i == string::npos)
return "";
else
return d.erase(d.find_last_not_of(t) + 1);
} // end of trim_right
inline string trim_left(const string & s, const string & t = SPACES) {
string d(s);
return d.erase(0, s.find_first_not_of(t));
} // end of trim_left
inline string trim(const string & s, const string & t = SPACES) {
string d(s);
return trim_left(trim_right(d, t), t);
} // end of trim
void myReplace(std::string& str, const std::string& oldStr, const std::string& newStr)
{
size_t pos = 0;
while((pos = str.find(oldStr, pos)) != std::string::npos)
{
str.replace(pos, oldStr.length(), newStr);
pos += newStr.length();
}
}
// Used internally to temporarily store data collected from the xml file
struct emodata {
std::string category; // fsre-, occ-, or everyday-category name, e.g. 'relaxed'
std::string emoclass; // primary or secondary
int xTens;
int yTens;
int slope;
int mass;
int xReg;
int boredom;
int prevalence;
float base_intensity;
float act_threshold;
float sat_threshold;
std::string decay;
float pleasure, arousal, dominance;
float lifetime;
std::vector<AffectPolygon*> affect_polygons;
} tmp_emodata;
WASABIQtWindow::WASABIQtWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::WASABIQtWindow)
{
ui->setupUi(this);
//setCentralWidget(ui->tabWidgetMain); // DON'T EVER DO THIS! Otherwise other parts are invisible!!!
globalInitFilename = "WASABI.ini";
wasabi = new WASABIEngine("secondary");
wasabi->setMaxSimulations(1000);
currentEA = 1;
guiUpdate = 0;
updateRate = 50;
updateRateSender = 1;
lastPackageTimeStamp = 0;
serverAutoStart = false;
startMinimized = false;
networkOutputFormat = new QString("default");
QDir dir;
qDebug() << "Hello World " << dir.currentPath();
sPort = 42424;
// Create the pad space OpenGL-window
padWindow = new PADWindow(this, this);
padWindow->resize(padWindow->sizeHint());
padWindow->show();
if (!loadInitFile(sPort)) {
qDebug() << "WASABIQtWindow::WASABIQtWindow: unable to load WASABI.ini or something went wrong!";
QErrorMessage *error = new QErrorMessage(this);
QString errorMessage;
errorMessage.append("Unable to load WASABI.ini, which should be in ").append(dir.currentPath()).append("!");
error->showMessage(errorMessage);
}
qDebug() << "WASABIQtWindow::WASABIQtWindow: serverPort is '" << sPort << "'";
// create the main gui window
/* If WASABI.ini for a given EA does not reference a *.emo_dyn AND a *.emo_pad file, but only a *.xml file,
* then initAllEAs() will fail for that EA and ea->initialized will remain false.
* To keep Qt out of the WASABIEngine library, we need to check here all uninitialized ea's for their respective
* ea->xmlFilename(s) and parse these using Qt functionality.
*/
wasabi->initAllEAs();
std::vector<cogaEmotionalAttendee*>::iterator iter_ea;
for (iter_ea = wasabi->emoAttendees.begin(); iter_ea != wasabi->emoAttendees.end(); ++iter_ea){
cogaEmotionalAttendee* ea = (*iter_ea);
if (!(ea->initialized)){
qDebug() << "WASABIQtWindow::Trying to initialize EA " << ea->getLocalID() << " with xml file " << ea->EmoConPerson->xmlFilename.c_str();
if (initEAbyXML(ea)) {
std::cout << "WASABIQtWindow: xml initialization successful!" << std::endl;
}
else {
std::cerr << "WASABIQtWindow: xml initialization FAILED!!!" << std::endl;
QErrorMessage *error = new QErrorMessage(this);
QString errorMessage;
errorMessage.append("Failure to load ").append(ea->EmoConPerson->xmlFilename.c_str()).append(", (which should be in ").append(dir.currentPath()).append("/xml/)!");
error->showMessage(errorMessage);
}
}
}
comboBoxAttendee_update();
initValues(wasabi->getEAfromID());
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000/updateRate); // 50Hz
timerSender = new QTimer(this);
connect(timerSender, SIGNAL(timeout()), this, SLOT(broadcastDatagram()));
timerSender->start(1000/updateRateSender); // 50Hz
////// Server stuff from network/broadcastreceiver/receiver.cpp
udpSocketReceiver = new QUdpSocket(this);
if (udpSocketReceiver->bind(sPort+1, QUdpSocket::ShareAddress)) {//
std::cout << "The udpSocket receiving on port: " << sPort+1 << std::endl;
connect(udpSocketReceiver, SIGNAL(readyRead()),
this, SLOT(processPendingDatagrams()));
udpSocketSender = new QUdpSocket(this);
}
else {
QMessageBox::critical(this, tr("Threaded WASABI Server"),
tr("Unable to start the server"));
}
QString str;
str.setNum(sPort);
ui->lineEditSenderPort->setText(str); // udpSocketSender must have been initialized first!
str.setNum(sPort+1);
ui->lineEditReceiverPort->setText(str);
connect(ui->actionAbout, SIGNAL(triggered()), SLOT(actionAbout()) );
connect(ui->actionPAD_space, SIGNAL(triggered()), SLOT(actionPAD_space()) );
}
WASABIQtWindow::~WASABIQtWindow()
{
delete ui;
}
void WASABIQtWindow::update() {
if (!wasabi) {
qDebug() << "WASABIQtWindow::update(): no wasabi engine set!";
return;
}
wasabi->update();
if (padWindow) {
padWindow->refresh();
}
else {
qDebug() << "WASABIQtWindow::update(): no padWindow, no refresh..";
}
updateGUI();
}
void WASABIQtWindow::updateGUI(bool force) {
cogaEmotionalAttendee* ea = wasabi->getEAfromID(currentEA);
if (!ea || !ui){
return;
}
QString tmp = QString("(%0, %1, %2)").arg(ea->getPValue()).arg(ea->getAValue()).arg(ea->getDValue());
ui->textEditPAD->setPlainText(tmp);
tmp = QString("(%0, %1, %2)").arg(ea->getXPos()).arg(ea->getYPos()).arg(ea->getZPos());
ui->textEditXYZ->setPlainText(tmp);
//temp.str("");
guiUpdate += updateRate;
if (guiUpdate > 499 || force) {
guiUpdate = 0;
// just a test with uid = 0
std::string padString;
if (wasabi->getPADString(padString, currentEA)) {
myReplace(padString, "&", " ");
} else {
std::cout << "WASABIQtWindow::updateGUI: No padString found!" << std::endl;
}
}
if (padWindow) {
padWindow->refresh();
}
}
bool WASABIQtWindow::loadInitFile(int& sPort) {
qDebug() << "WASABIQtWindow::loadInitFile(): Trying to find and parse '" << globalInitFilename.c_str() << "'!";
//Open file for reading
std::ifstream file(globalInitFilename.c_str());
std::string line;
if (file) {
std::string word1;
std::string word2;
int newLocalID;
int number;
cogaEmotionalAttendee* ea;
while (std::getline(file, line)) {
line = trim(line);
if (!line.empty() && (line.at(0)) != '#') { //comments begin with '#'
qDebug() << "> " << line.c_str();
std::istringstream isline(line);
isline >> word1;
//cout << line << endl;
if (!(word1.empty())) {
//cout << "(" << word1 << ")" << endl;
isline >> word2;
//cout << "-->(" << word2 << ")" << endl;
if (!(word2.empty())) {
switch (returnIndex(word1, "EA ServerAutoStart ServerPort StartMinimized MaxSimulations")) {
case 1: //EA: This starts a block of data for a specific EmotionalAttendee. Has to be terminated by \EA
// addEmotionalAttendee return 0 only in case of failure to create the new EmotionalAttendee
if (newLocalID = wasabi->addEmotionalAttendee(word2)) {
qDebug() << "newLocalID assigned to " << QString(word2.c_str()) << " is " << newLocalID;
// The EA was just initialized with "init.[dyn|pad]" files automatically
bool initByXML = false; // We set this to true, when an 'xmlFilename' key was found.
while (std::getline(file, line) && trim(line) != "EA_END") { // We stop if a line starts with EA_END, see above
line = trim(line);
if (!line.empty() && (line.at(0)) != '#') { //comments begin with '#'
qDebug() << "> " << line.c_str();
std::istringstream isline(line);
isline >> word1;
if (!(word1.empty())) {
isline >> word2;
ea = wasabi->getEAfromID(newLocalID);
if (!(word2.empty())) {
switch (returnIndex(word1, "dynFilename padFilename globalID simulationOn xmlFilename")) {
case 1: // dynFilename
if (ea && !initByXML ) {
ea->EmoConPerson->dynFilename = word2;
} else {
qDebug()
<< "MyApp::loadInitFile(): ERROR no ea with ID "
<< newLocalID
<< " found!"
;
}
break;
case 2: // padFilename
if (ea && !initByXML) {
ea->EmoConPerson->padFilename = word2;
} else {
qDebug()
<< "MyApp::loadInitFile(): ERROR no ea with ID "
<< newLocalID
<< " found!"
;
}
break;
case 3: //globalID
if (ea) {
ea->setGlobalID(word2);
} else {
qDebug()
<< "MyApp::loadInitFile(): ERROR no ea with ID "
<< newLocalID
<< " found!"
;
}
break;
case 4: // simulationOn
if (ea) {
if (word2 == "false" || word2 == "0") {
ea->simulationOn = false; // default value is true, see cogaEmotionalAttendee
}
}
break;
case 5: // xmlFilename
if (ea) {
ea->EmoConPerson->xmlFilename = word2;
initByXML = true;
} else {
qDebug()
<< "MyApp::loadInitFile(): ERROR no ea with ID "
<< newLocalID
<< " found!"
;
}
break;
default:
if (word1 != "EA_END"){
qDebug()
<< "MyApp::loadInitFile: keyword '"
<< word1.c_str()
<< "' not recognized in EA context!"
;
}
}
}
}
}
}
}
break;
case 2: //ServerAutoStart
serverAutoStart = false;
if (word2 == "true" || word2 == "1") {
serverAutoStart = true; // false is default value
}
break;
case 3: //ServerPort
number = atoi(word2.c_str());
if (number > 500 && number < 65536) {
sPort = number; // default is 42424
}
break;
case 4: //StartMinimized
startMinimized = false;
if (word2 == "true" || word2 == "1") {
startMinimized = true; // false is default value
}
break;
case 5: //MaxSimulations
// TODO: Fix this thing!!!
//MaxSimulations = 10;
number = atoi(word2.c_str());
if (number > 0 && number < 50) {
//MaxSimulations = number;
}
break;
default:
qDebug()
<< "MyApp::loadInitFile: Error: Unknown keyword \""
<< word1.c_str() << "\"!";
}
}
}
} // end if ((line.at(0)) != '#')
}//end while
file.close();
} //if (file)
else {
qDebug()
<< "MyApp::loadInitFile: ERROR Could not open file! --> creating dummy JohnDoe :)"
;
wasabi->addEmotionalAttendee("JohnDoe");
return false; //could not open file;
}
return true;
}
void WASABIQtWindow::resetValues()
{
if (!wasabi) {
std::cout << "myGuiFrame::resetValues(): ERROR no wasabi engine found.." << std::endl;
return;
}
cogaEmotionalAttendee* ea = wasabi->getEAfromID(currentEA);
if (!ea){
std::cerr << "myGuiFrame::resetValues(): ERROR no cogaEmotionalAttendee found.." << std::endl;
return;
}
ea->setXTens(ui->spinBoxForceX->value());
ea->setYTens(ui->spinBoxForceY->value());
ea->setSlope(ui->spinBoxSlope->value());
ea->setMass(ui->spinBoxMass->value());
ea->setAlpha(ui->spinBoxAlpha->value());
ea->setBeta(ui->spinBoxBeta->value());
ea->setFactor(ui->spinBoxFactor->value());
ea->setUpdateRate(ui->spinBoxUpdateRate->value());
}
void WASABIQtWindow::initValues(cogaEmotionalAttendee* ea)
{
if (ea) {
ui->spinBoxForceX->setValue(ea->getXTens());
ui->spinBoxForceY->setValue(ea->getYTens());
ui->spinBoxSlope->setValue(ea->getSlope());
ui->spinBoxMass->setValue(ea->getMass());
//ui->spinBoxUpdateRate->setValue(ea->getUpdateRate());
ui->spinBoxAlpha->setValue(ea->getAlpha());
ui->spinBoxBeta->setValue(ea->getBeta());
ui->spinBoxFactor->setValue(ea->getFactor());
ui->treeViewAffectiveStates->selectAll();
ui->treeViewAffectiveStates->clearSelection();
ea_model = new QStandardItemModel();
QStandardItem *parentItem = ea_model->invisibleRootItem();
QStandardItem *item = new QStandardItem(QString(ea->getName().c_str()));
parentItem->appendRow(item);
std::vector<AffectiveState*>::iterator iter_as;
bool peItemCreated = false;
bool seItemCreated = false;
QStandardItem *primaryItem;
QStandardItem *secondaryItem;
for (iter_as = ea->EmoConPerson->affectiveStates.begin(); iter_as!=ea->EmoConPerson->affectiveStates.end(); iter_as++) {
AffectiveState* as = *iter_as;
PrimaryEmotion* pe;
pe = dynamic_cast<PrimaryEmotion*>(as);
SecondaryEmotion* se;
se = dynamic_cast<SecondaryEmotion*>(as);
QStandardItem *new_item;
if (pe) {
if (!peItemCreated) {
primaryItem = new QStandardItem(QString("Primary"));
item->appendRow(primaryItem);
peItemCreated = true;
}
new_item = new QStandardItem(QString(pe->type.c_str()));
primaryItem->appendRow(new_item);
}
else if (se) {
if (!seItemCreated) {
secondaryItem = new QStandardItem(QString("Secondary"));
item->appendRow(secondaryItem);
seItemCreated = true;
}
new_item = new QStandardItem(QString(se->type.c_str()));
secondaryItem->appendRow(new_item);
}
if (new_item) {
std::vector<AffectPolygon*>::iterator iter_poly;
int polycount = 1;
for (iter_poly = as->polygons.begin(); iter_poly!=as->polygons.end(); iter_poly++) {
AffectPolygon* p = *iter_poly;
QStandardItem *poly = new QStandardItem(QString("polygon#%0").arg(polycount));
new_item->appendRow(poly);
int vertexcount = 1;
std::vector<AffectVertex*>::iterator iter_av;
for (iter_av = p->vertices.begin(); iter_av!=p->vertices.end(); iter_av++) {
AffectVertex* av = *iter_av;
QStandardItem *vertex = new QStandardItem(QString("vertex#%0 (%1,%2,%3)").arg(vertexcount).arg(av->coords[0]).arg(av->coords[1]).arg(av->coords[2]));
poly->appendRow(vertex);
vertexcount++;
}
polycount++;
}
}
else {
std::cout << "myGuiFrame::updateValues: affect pointer == NULL!" << std::endl;
}
}
ui->treeViewAffectiveStates->setModel(ea_model);
std::cout << "myGuiFrame::updateValues: finished!" << std::endl;
}
else {
std::cerr << "myGuiFrame::updateValues(): ERROR no EmotionalAttendee given.." << std::endl;
}
}
void WASABIQtWindow::comboBoxAttendee_update()
{
if (!wasabi){
return;
}
QString selection = ui->comboBoxEmoAttendee->currentText();
ui->comboBoxEmoAttendee->clear();
std::vector<cogaEmotionalAttendee*>::iterator iter_ea;
for (iter_ea = wasabi->emoAttendees.begin(); iter_ea != wasabi->emoAttendees.end(); ++iter_ea){
cogaEmotionalAttendee* ea = (*iter_ea);
QString item;
std::ostringstream temp;
temp << ea->getName().c_str() << "/" << ea->getLocalID();
item.append(temp.str().c_str());
ui->comboBoxEmoAttendee->addItem(item);
}
if (ui->comboBoxEmoAttendee->findText(selection) == -1) {
if (ui->comboBoxEmoAttendee->count() > 0) {
ui->comboBoxEmoAttendee->setCurrentIndex(0);
}
}
else {
ui->comboBoxEmoAttendee->setCurrentIndex(ui->comboBoxEmoAttendee->findText(selection));
}
}
bool WASABIQtWindow::isRunning() {
return ui->checkBoxRun->isChecked();
}
bool WASABIQtWindow::showXYZ() {
return ui->checkBoxShowIn3D->isChecked();
}
/** Checks if supplied globalID already in use.
* If not, uses the wasabi engine to create a new EmotionalAttendee
* for which a new localID is generated and returned.
*/
int WASABIQtWindow::addEmotionalAttendee(const QString& name, const QString& globalID) {
// we might wanna perform some checks here in the future, before adding the EmotionalAttendee
//long int globalID_int = -1; //i.e. undef
bool converted;
int globalID_int = (int)globalID.toLong(&converted);
if (converted && wasabi->getEAfromID(globalID_int)) {
std::cout << "myGuiFrame::addEmotionalAttendee: ERROR globalID " << globalID.toStdString() << " already in use!" << std::endl;
return 0; // means ERROR here
}
return wasabi->addEmotionalAttendee(name.toStdString(), globalID.toStdString());
/*int newLocalID = wasabi->addEmotionalAttendee(name.toStdString(), globalID.toStdString());
if (newLocalID != 0 && wasabi->initEA(wasabi->getEAfromID(newLocalID))){
comboBoxAttendee_update();
currentEA = newLocalID;
cogaEmotionalAttendee* ea = wasabi->getEAfromID(currentEA);
if (ea) {
initValues(ea);
ui->statusBar->showMessage(QString("Attendee %0 initialized").arg(ea->getName().c_str()), 1000);
}
return newLocalID;
}
std::cout << "myGuiFrame::addEmotionalAttendee: ERROR could not create new EA or it could not be initialized!" << std::endl;
return newLocalID;*/
}
void WASABIQtWindow::updateGuiAfterAddingAgent(cogaEmotionalAttendee* ea){
comboBoxAttendee_update();
initValues(ea);
ui->statusBar->showMessage(QString("Attendee %0 initialized").arg(ea->getName().c_str()), 1000);
}
void WASABIQtWindow::on_spinBoxForceX_valueChanged(int arg1)
{
if (arg1 >= 0 && wasabi) {
wasabi->setXForce(arg1, currentEA);
}
}
void WASABIQtWindow::on_spinBoxForceY_valueChanged(int arg1)
{
if (arg1 >= 0 && wasabi) {
wasabi->setYForce(arg1, currentEA);
}
}
void WASABIQtWindow::on_pushButtonAdd_clicked()
{
bool ok;
QString name = QInputDialog::getText(this, tr("WASABIQtGui: Please provide input.."),
tr("Name of new Attendee:"), QLineEdit::Normal,
"Geminoid F", &ok);
if (!ok || name.isEmpty()) {
return;
}
name = name.replace('/', '_');//because '/' is used to seperate name from id
QString globalID = QInputDialog::getText(this, tr("WASABIQtGui: Please provide input.."),
tr("GlobalID of new Attendee:"), QLineEdit::Normal,
"gf1", &ok);
if (!ok || globalID.isEmpty()) {
return;
}
int localId = addEmotionalAttendee(name, globalID);
cogaEmotionalAttendee* ea = wasabi->getEAfromID(localId);
if(!wasabi->initEA(ea)){
initEAbyXML(ea);
}
}
void WASABIQtWindow::on_comboBoxEmoAttendee_currentIndexChanged(const QString &arg1)
{
QStringList str_list = arg1.split("/");
if (str_list.size() != 2) {
std::cerr << "WASABIQtWindow::on_comboBoxEmoAttendee_currentIndexChanged: str_list.size() != 2, bailing out.." << std::endl;
return;
}
bool ok;
currentEA = str_list.at(1).toLong(&ok); //Note, we ignore the name at str_list.at(0)!!!
if (ok && wasabi) {
cogaEmotionalAttendee* ea = wasabi->getEAfromID(currentEA);
if (ea) {
initValues(ea);
ui->statusBar->showMessage(QString("Attendee %0 initialized").arg(ea->getName().c_str()), 1000);
}
}
}
void WASABIQtWindow::on_checkBoxRun_stateChanged(int arg1)
{
switch (arg1) {
case 0: //unchecked
timer->stop();
break;
case 2: // fully checked (instead of partially checked state 1)
timer->start();
}
}
void WASABIQtWindow::on_spinBoxSlope_valueChanged(int arg1)
{
if (arg1 >= 0 && wasabi) {
wasabi->setSlope(arg1, currentEA);
}
}
void WASABIQtWindow::on_spinBoxMass_valueChanged(int arg1)
{
if (arg1 >= 0 && wasabi) {
wasabi->setMass(arg1, currentEA);
}
}
void WASABIQtWindow::on_spinBoxUpdateRate_valueChanged(int arg1)
{
if (arg1 > 0) {
updateRate = arg1;
if (wasabi) {
wasabi->setUpdateRate(ui->spinBoxUpdateRate->value(), currentEA);
}
if (ui->checkBoxRun->isChecked()) {
timer->start();
}
}
else {
updateRate = 0;
timer->stop();
}
}
void WASABIQtWindow::on_spinBoxAlpha_valueChanged(int arg1)
{
if (arg1 >= 0 && wasabi) {
wasabi->setAlpha(arg1, currentEA);
}
}
void WASABIQtWindow::on_spinBoxBeta_valueChanged(int arg1)
{
if (arg1 >= 0 && wasabi) {
wasabi->setBeta(arg1, currentEA);
}
}
void WASABIQtWindow::on_spinBoxFactor_valueChanged(int arg1)
{
if (arg1 >= 0 && wasabi) {
wasabi->setFactor(arg1, currentEA);
}
}
void WASABIQtWindow::on_pushButtonMinus50_clicked()
{
wasabi->emotionalImpulse(-50, currentEA);
}
void WASABIQtWindow::on_pushButtonPlus50_clicked()
{
wasabi->emotionalImpulse(50, currentEA);
}
void WASABIQtWindow::on_pushButtonReset_clicked()
{
wasabi->resetToZero(currentEA);
}
void WASABIQtWindow::on_verticalSliderP_valueChanged(int value)
{
cogaEmotionalAttendee* ea = wasabi->getEAfromID(currentEA);
if (!ea){
return;
}
ui->checkBoxRun->setChecked(false);
timer->stop();
ea->resetForces();
ea->setPValue(value);
ea->EmoConPerson->updateAffectLikelihoods(true); // true forces recalculation of PAD-based likelihoods
ea->updatePADstring();
updateGUI(true);
}
void WASABIQtWindow::on_horizontalSliderA_valueChanged(int value)
{
cogaEmotionalAttendee* ea = wasabi->getEAfromID(currentEA);
if (!ea){
return;
}
ui->checkBoxRun->setChecked(false);
timer->stop();
ea->resetForces();
ea->setAValue(value);
ea->EmoConPerson->updateAffectLikelihoods(true); // true forces recalculation of PAD-based likelihoods
ea->updatePADstring();
updateGUI(true);
}
void WASABIQtWindow::on_verticalSliderD_valueChanged(int value)
{
cogaEmotionalAttendee* ea = wasabi->getEAfromID(currentEA);
if (!ea){
return;
}
ui->checkBoxRun->setChecked(false);
timer->stop();
ea->resetForces();
ea->setDValue(value);
ea->EmoConPerson->updateAffectLikelihoods(true); // true forces recalculation of PAD-based likelihoods
ea->updatePADstring();
updateGUI(true);
}
void WASABIQtWindow::on_treeViewAffectiveStates_activated(const QModelIndex &index)
{
ui->pushButtonTrigger->setEnabled(false);
ui->pushButtonShow->setEnabled(false);
highlighted_as = NULL;
QString evText = ea_model->itemFromIndex(index)->text();
if (ea_model->parent(index).isValid()) {
QString parentText = (ea_model->itemFromIndex(ea_model->parent(index)))->text();
if (parentText == "Secondary" || parentText == "Primary") {
std::cout << "WASABIGuiguiFrame::m_treeCtrlAffectiveStates_Activated: Emotion \"" << evText.toStdString() << "\" clicked." << std::endl;
ui->pushButtonTrigger->setEnabled(true);
ui->pushButtonShow->setEnabled(true);
highlighted_as = wasabi->getEAfromID(currentEA)->EmoConPerson->getAffectiveStateByType(evText.toStdString());
}
else {
std::cout << "WASABIQtWindow::on_treeViewAffectiveStates_activated: No emotion activated!" << std::endl;
}
}
}
void WASABIQtWindow::on_pushButtonTrigger_clicked()
{
if (highlighted_as) {
PrimaryEmotion* pe;
pe = dynamic_cast<PrimaryEmotion*> (highlighted_as);
SecondaryEmotion* se;
se = dynamic_cast<SecondaryEmotion*> (highlighted_as);
if (pe) {
highlighted_as->trigger(10.0);
}
else if (se) {
highlighted_as->trigger(); // -1 indicates SecondayEmotion's standard lifetime to be used
}
}
else {
std::cout << "myGuiFrame::m_buttonTriggerOnClick: No highlighted_as!" << std::endl;
}
}
/** We expect messages (i.e. strings with a maximum length of 100 characters) that conform the following BNF:
* <message> ::= <senderID> '&' <command>
* <senderID> ::= (any non-empty string)
* <command> ::= <add> | <trigger> | <impulse> | <dominance> | <remove> | <removeAll> | <getXmlFile>
* <add> ::= 'ADD' '&' <name> [ '&' <globalID> '&' <initfile> ]
* <trigger> ::= 'TRIGGER' '&' <targetID> '&' <affectiveStateName> [ '&' <lifetime> ]
* <impulse> ::= 'IMPULSE' '&' <targetID> '&' <impvalue>
* <dominance>::= 'DOMINANCE' '&' <targetID> '&' <domvalue>
* <name> ::= (any non-empty string)
* <initfile> ::= <padFile> | <xmlFile>
* <padFile> ::= (any non-empty string, defaults to 'init', if not found)
* <xmlFile> ::= (any non-empty string) '.xml'
* <globalID> ::= (any non-empty string)
* <targetID> ::= (any non-empty string)
* <affectiveStateName> ::= (any non-empty string, must match a name of an emotion, though)
* <lifetime> ::= (any double d with 0 <= d <= 100 or d == -1)
* <impvalue> ::= (any integer i with -100 <= i <= 100 and i != 0)
* <domvalue> ::= (any integer i with -100 <= i <= 100)
* <remove> ::= 'REMOVE' '&' '<targetID>
* <removeAll>::= 'REMOVEALL'
* <getXmlFile>::= 'GETXMLFILE' '&' <xmlFile>
* ------------------------------------------------------------------------------------------
* ADD: Adds a new EmotionalAttendee (EA) to the simulation.
* For each EA an independent reference point (XYZ) is created
* The affective states are loaded from file <initfile>, if provided, or else from the default file 'init'.
* A globalID can optionally be provided as well, but an internal uid (int) is created as well.
* Returns: 'REPLY&ADD&OK&' <localID> only in case of success.
* TRIGGER: Is used whenever an affective state's intensity is to be set to maximum for a certain amount of (life)time.
* For example, 'surprise' is initialized to have a baseIntensity of zero and, thus, needs to be triggered,
* before it can gain a positive awareness likelihood.
* Only triggering an emotion, however, might not be sufficient to 'activate' this emotion with a certain awareness likelihood.
* The PAD values of the reference point must be close enough to the emotion in question as well, while it has a non-zero intensity.
* This is what it means to have an 'emotion dynamics' instead of a purely rule-based and direct emotion elicitation.
* You need to provide the <targetID>, which is the uid returned by the ADD command or '1' for the default simulation of 'John Doe'.
* By specifying the <affectiveStateName> you tell the system, which "affective state" (i.e. primary or secondary emotion) to TRIGGER.
* Take care yourself that this affective state has been loaded from <initfile>.emo_pad before.
* Optionally, you might provide a lifetime (double) for this emotion. If no lifetime is given, the emotions standardLifetime will be used.
* IMPULSE: Is used to drive the emotion dynamics in XYZ space itself.
* As soon as something positive or negative is detected to happen, you might use this command to tell the WASABI engine.
* Of course, the event can have happend to the agent/robot itself or to another person, i.e. another EmotionalAttendee (EA).
* In the former case, <targetID> should be set to '1', in the latter case, to that uid, which was returned after ADDing this EA to the simulation.
* The impulse must be within the range [-100,100] and should be an integer.
* DOMINANCE: Is used to set the dominance value of the EA with ID <targetID> to any value
* between 100 (dominant) and -100 (submissive). As of June 2012 only the two extreme values make sense, though.
* Concerning the value of <targetID> the same applies as in case of IMPULSE explained above.
* REMOVE : Removes a attendee with the given id;
* REMOVEALL: Removes all attendees of the given owner.
* GETXMLFILE: Advises WASABI to send all specified emotions of the given file.
* For each emotion a separte udp package is sent.
*/
bool WASABIQtWindow::parseMessage(QString message) {
std::cout << "WASABIQtWindow::parseMessage: message = '" << message.toStdString() << "'" << std::endl;
QStringList str_list = message.split("&");
if (str_list.size() < 3) {
std::cerr << "WASABIQtWindow::parseMessage: too few tokens in message!" << std::endl;
return false;
}
QString senderID = str_list.at(0); // will be ignored so far, but must be supplied
QString command = str_list.at(1);
QString targetID = str_list.at(2); // or <name> for ADD or <typeOfInfo> for REQUEST
QString param1;
QString param2;
std::cout << "WASABIQtWindow::parseMessage: now checking '" << senderID.toStdString()
<< "' with command '" << command.toStdString()
<< "' with target '"
<< targetID.toStdString() << "'." << std::endl;
double lifetime = -1;
int eaID;
int newLocalID = 0;
int impulse; // P, A, D, X, Y, Z;
cogaEmotionalAttendee* ea = wasabi->getEAfromID(currentEA);
if (!ea) {
std::cout << "WASABIQtWindow::parseMessage: no emotionalAttendee with ID '" << currentEA << "' found!" << std::endl;
return false;
}
bool ok = false;
switch (returnIndex(command.toStdString(), "ADD TRIGGER IMPULSE DOMINANCE REMOVE REMOVEALL GETXMLFILE")) {
//<String senderID>|ADD|<String name==targetID>|<String globalID (optional)>
//e.g. 'Robovie|ADD|Robovie|Robovie23' or 'Robovie1|ADD|Dylan F. Glas|120345_1' or simply 'Robovie|ADD|Chris'
case 1://ADD <name> [ <globalID> <initfile> ]
if (str_list.size() == 3) {
newLocalID = addEmotionalAttendee(targetID, "undef"); // targetID is the 'real name' here, e.g. 'Dylan F. Glas'
}
else if (str_list.size() == 4){
param1 = str_list.at(3); // <String globalID>
newLocalID = addEmotionalAttendee(targetID, param1); // param1 is the globalID here, e.g. '120345_1'
}
else if (str_list.size() == 5){
param1 = str_list.at(3); // <String globalID>
param2 = str_list.at(4); // <String globalID>
newLocalID = addEmotionalAttendee(targetID, param1); // param1 is the globalID here, e.g. '120345_1'
ea = wasabi->getEAfromID(newLocalID);
if(param2.endsWith(".xml")){
if(ea != NULL){
ea->EmoConPerson->xmlFilename = param2.toStdString();
initEAbyXML(ea);
ea->setOwner(senderID.toStdString());
} else {
qDebug()
<< "MyApp::loadInitFile(): ERROR no ea with ID "
<< newLocalID
<< " found!"
;
}
}
else{
QString param3 = param2.append(".emo_dyn");
param2 = param2.append(".emo_pad");
std::string dyn = param3.toStdString();
std::string pad = param2.toStdString();
if (ea != NULL) {
ea->EmoConPerson->dynFilename = dyn;
ea->EmoConPerson->padFilename = pad;
wasabi->initEA(ea);
ea->setOwner(senderID.toStdString());
} else {
qDebug()
<< "MyApp::loadInitFile(): ERROR no ea with ID "
<< newLocalID
<< " found!"
;
}
updateGuiAfterAddingAgent(ea);
}
}
if (newLocalID == 0) {
return false;
}
break;
case 2: //TRIGGER <String targetID> <String affectiveStateName> <double lifetime>
if (str_list.size()==3) {
std::cout << "WASABIQtWindow::parseMessage: not enough tokens for '"
<< command.toStdString() << "'!" << std::endl;
return false;
}
param1 = str_list.at(3); // <String affectiveStateName>
if (str_list.size() == 4) {
std::cout << "WASABIQtWindow::parseMessage: no lifetime for '"
<< command.toStdString() << "', using standard lifetime." << std::endl;
param2 = "-1";
}
else {
param2 = str_list.at(4); // <double lifetime>
}
lifetime = param2.toDouble(&ok); //atof((const char*)param2.mb_str());
if (!ok || (lifetime < 0 && lifetime != -1) || lifetime > 100) {
std::cout
<< "WASABIQtWindow::parseMessage: second parameter (double lifetime) of '"
<< command.toStdString()
<< "' is not in range ]0, 100] and not -1 (or no double at all)!"
<< std::endl;
return false;
}
//TODO CHECK THE ABOVE!!!
ea = wasabi->getEAfromID(targetID.toStdString());
if(!ea)
return false;
if (!ea->EmoConPerson->triggerAS(param1.toStdString(), lifetime)) {
std::cout << "WASABIQtWindow::parseMessage: Couldn't TRIGGER '" << param1.toStdString() << "'!" << std::endl;
return false;
}
break;
case 3: //IMPULSE <String targetID> <int impulse>
if (str_list.size()==3) {
std::cout << "WASABIQtWindow::parseMessage: not enough tokens for '"
<< command.toStdString() << "'!" << std::endl;
return false;
}
param1 = str_list.at(3);
impulse = param1.toInt(&ok); //atoi((const char*)param1.mb_str());
if (!ok || impulse < -100 || impulse > 100 || impulse == 0) {
std::cout
<< "WASABIQtWindow::parseMessage: second parameter (int impulse) of '"
<< command.toStdString()
<< "' is not in range [-100, 100] or 0 (or no integer at all)!"
<< std::endl;
return false;
}
ea = wasabi->getEAfromID(targetID.toStdString());
if(!ea)
return false;
eaID = ea->getLocalID();
std::cout << "WASABIQtWindow::parseMessage: eaID = " << eaID << std::endl;
return wasabi->emotionalImpulse(impulse, eaID);
break;
case 4: //DOMINANCE <String targetID> <int impulse>
if (str_list.size()==3) {
std::cout << "WASABIQtWindow::parseMessage: not enough tokens for '"
<< command.toStdString() << "'!" << std::endl;
return false;
}
param1 = str_list.at(3);
impulse = param1.toInt(&ok); //atoi((const char*)param1.mb_str());
if (!ok || impulse < -100 || impulse > 100) {
std::cout
<< "WASABIQtWindow::parseMessage: second parameter (int impulse) of '"
<< command.toStdString()
<< "' is not in range [-100, 100] or 0 (or no integer at all)!"
<< std::endl;
return false;
}
ea = wasabi->getEAfromID(targetID.toStdString());
if(!ea)
return false;
ea->setDValue(impulse);
return true;
break;
case 5: //REMOVE <String targetID>
std::cout << "WASABIQtWindow::parseMessage: '" << "Removing attendee with global id "
<< targetID.toStdString() << "'" << " and local id " << ea->getLocalID() << std::endl;
//TODO this is just a workaround because otherwise WASABI crashes
ea = wasabi->getEAfromID("undef1");
currentEA = ea->getLocalID();
eaID = ea->getLocalID();
if(!wasabi->removeAttendee(targetID.toStdString())){
std::cerr << "WASABIQtWindow::parseMessage: Could not remove attendee with id '" << eaID
<< "'!" << std::endl;
return false;
}
return true;
break;
case 6: //REMOVEALL
std::cout << "WASABIQtWindow::parseMessage: '" << "Removing all attendees of " << senderID.toStdString() << std::endl;
//TODO this is just a workaround because otherwise WASABI crashes
ea = wasabi->getEAfromID("undef1");
currentEA = ea->getLocalID();
wasabi->removeAllAttendeesOf(senderID.toStdString());
//std::cout << "WASABIQtWindow::parseMessage: Removed " << attendeesRemoved << " attendees." << std::endl;
return true;
break;
case 7: //GETXMLFILE <xmlValue>
std::cout << "WASABIQtWindow::parseMessage: '" << "Sending xml file." << std::endl;
sendXmlFile(targetID);
return true;
break;
default:
std::cerr << "WASABIQtWindow::parseMessage: unknown command '" << command.toStdString()
<< "'!" << std::endl;
return false;
}
return true;
}
//Server stuff
void WASABIQtWindow::processPendingDatagrams()
{
while (udpSocketReceiver->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(udpSocketReceiver->pendingDatagramSize());
udpSocketReceiver->readDatagram(datagram.data(), datagram.size());
std::cout << "Received datagram: [" << datagram.data() << "]" << std::endl;
if (!ui->checkBoxReceiving->isChecked()) {
std::cout << "WASABIQtWindow::processPendingDatagrams: 'Receiving' not checked, ignoring message" << std::endl;
printNetworkMessage(datagram.data(), true, false, false);
return;
}
printNetworkMessage(datagram.data(), true, parseMessage(datagram.data()), true);
}
}
void WASABIQtWindow::broadcastDatagram() {
if (ui->checkBox_senderMode_AL->isChecked()) {
string timeStamp = getPackageTimeStamp();
int i=0;
while(i<=wasabi->emoAttendees.size()/50){
int firstIndex = i*50;
int lastIndex = firstIndex + 50;
if(lastIndex > wasabi->emoAttendees.size()){
lastIndex = wasabi->emoAttendees.size();
}
vector<cogaEmotionalAttendee*> subset(&(wasabi->emoAttendees[firstIndex]), &(wasabi->emoAttendees[lastIndex]));
string padStrings = buildAffectedLikelihoodStrings(subset, timeStamp);
sendAffectedLikelihood(padStrings);
i++;
}
}
/*******
if(ui->checkBox_senderMode_PAD){
//QByteArray datagram = "SenderID&IMPULSE&1&" + QByteArray::number(messageNo);
std::stringstream ostr;
wasabi->getEAfromID(currentEA)->EmoConPerson->writeTransferable(ostr);
QByteArray datagram = QByteArray(ostr.str().data(), ostr.str().size());
if (udpSocketSender->writeDatagram(datagram.data(), datagram.size(), QHostAddress::Broadcast, sPort) != -1) {
printNetworkMessage(datagram.data(), false, true);
}
else {
printNetworkMessage(datagram.data(), false, false);
}
}
********/
//EXTENSION1:
if (ui->checkBox_senderMode_PADtrace->isChecked()) {
sendEmoMlPadTrace();
}
//END OF EXTENSION1
}
string WASABIQtWindow::getPackageTimeStamp(){
stringstream time;
clock_t t = clock();
if(t <= lastPackageTimeStamp){
t = lastPackageTimeStamp+1;
}
lastPackageTimeStamp = t;
time << t;
return time.str();
}
std::string WASABIQtWindow::buildAffectedLikelihoodStrings(vector<cogaEmotionalAttendee*> attendees, string timeStamp){
std::string padString;
std::string padStrings = "agents: timeStamp=";
padStrings += timeStamp;
padStrings += " ";
padStrings += "total=";
padStrings += std::to_string(attendees.size());
padStrings += " ";
std::vector<cogaEmotionalAttendee*>::iterator iter_ea;
//const char* pEmoMlStr;
for (iter_ea = attendees.begin(); iter_ea != attendees.end(); ++iter_ea){
if (iter_ea != attendees.end() && iter_ea != attendees.begin()) {
padStrings += " ";
}
cogaEmotionalAttendee* ea = (*iter_ea);
if (wasabi->getPADString(padString, ea->getLocalID())) {
myReplace(padString, "&", " ");
} else {
std::cerr << "WASABIQtWindow::printNetworkMessage: No padString found!" << std::endl;
}
ui->textEditOut->append(QString("(%0) %1: %2").arg(QTime::currentTime().toString())
.arg(ea->getName().c_str())
.arg(padString.c_str()));
padStrings += "ID";
padStrings += std::to_string(ea->getLocalID());
stringstream padValues;
padValues << " P=" << ea->getPValue() << " A=" << ea->getAValue() << " D=" << ea->getDValue();
padStrings += "=" + ea->getGlobalID() + " ( " + padString + padValues.str() + " )";
}
return padStrings;
}
void WASABIQtWindow::sendAffectedLikelihood(string padStrings){
QByteArray datagram(padStrings.c_str());
if (udpSocketSender->writeDatagram(datagram.data(), datagram.size(), QHostAddress::Broadcast, sPort) != -1) {
printNetworkMessage(datagram.data(), false, true);
}
else {
printNetworkMessage(datagram.data(), false, false);
}
}
void WASABIQtWindow::sendEmoMlPadTrace(){
std::vector<cogaEmotionalAttendee*>::iterator iter_ea;
//const char* pEmoMlStr;
for (iter_ea = wasabi->emoAttendees.begin(); iter_ea != wasabi->emoAttendees.end(); ++iter_ea){
cogaEmotionalAttendee* ea = (*iter_ea);
EmoMLString = composeEmoML(ea);
//pEmoMlStr = EmoMLString.c_str();
QByteArray emoMlDatagram(EmoMLString.c_str());
//udpSocketSender->writeDatagram(emoMlDatagram.data(), emoMlDatagram.size(), QHostAddress::Broadcast, sPort);
if (udpSocketSender->writeDatagram(emoMlDatagram.data(), emoMlDatagram.size(), QHostAddress::Broadcast, sPort) != -1) {
printNetworkMessage(emoMlDatagram.data(), false, true);
ea->resetBuffer();
}
else {
printNetworkMessage(emoMlDatagram.data(), false, false);
}
}
}
void WASABIQtWindow::sendXmlFile(QString filename){
QString xmlString;
QFile inputFile("xml/" + filename);
if (inputFile.open(QIODevice::ReadOnly))
{
QTextStream in(&inputFile);
while ( !in.atEnd() )
{
xmlString.append(in.readLine());
}
inputFile.close();
}
QString startElement = "<emotionml ";
QXmlStreamReader xmlReader(xmlString);
xmlReader.readNextStartElement();
QXmlStreamNamespaceDeclarations namespaces = xmlReader.namespaceDeclarations();
for (int i=0; i<namespaces.size(); ++i){
if(namespaces.at(i).prefix().toString() == "wasabi"){
startElement += "xmlns:wasabi=\"" + namespaces.at(i).namespaceUri().toString()+ "\"";
}
}
startElement = startElement.trimmed();
startElement += ">";
QDomDocument domTree;
domTree.setContent(xmlString);
QDomNodeList elementNodes = domTree.elementsByTagName("emotion");
for (int i=0; i<elementNodes.count(); i++) {
QDomNode node = elementNodes.item(i);
QString nodeString;
QTextStream stream(&nodeString);
node.save(stream, 0);
nodeString = "emotion: " + startElement + nodeString + "</emotionml>";
QByteArray xmlDatagram(nodeString.toStdString().c_str());
if (udpSocketSender->writeDatagram(xmlDatagram.data(), xmlDatagram.size(), QHostAddress::Broadcast, sPort) != -1) {
printNetworkMessage(xmlDatagram.data(), false, true);
}
else {
printNetworkMessage(xmlDatagram.data(), false, false);
}
//TODO Find a better solution. If there are sent too many packages at once, some get missed.
usleep(1000);
}
}
void WASABIQtWindow::printNetworkMessage(QString message, bool receive, bool success, bool parsed) {
QString p;
if (receive) {
p = "Receive (";
parsed ? p = p.append("parsed, ") : p.append("skipped, ");
}
else {
p = "Transmit (";
}
success ? p = p.append("success)") : p.append("failure)");
ui->textEditNetworkTraffic->append(QString("(%0) %1 [%2]").arg(QTime::currentTime().toString()).arg(p).arg(message));
// HACK, please find a better place, perhaps we need to create an independent times (TODO)
}
void WASABIQtWindow::on_lineEditSenderPort_textEdited(const QString &arg1)
{
bool ok = false;
int tmp = arg1.toInt(&ok);
if (ok && tmp > 500 && tmp < 65536) {
sPort = tmp;
udpSocketSender->bind(sPort);
}
}
void WASABIQtWindow::on_spinBoxSendRate_valueChanged(int arg1)
{
if (arg1 > 0 && arg1 <= 50) {
updateRateSender = arg1;
timerSender->setInterval(1000/updateRateSender);
}
}
void WASABIQtWindow::on_checkBoxSending_stateChanged(int arg1)
{
if (arg1 == 0) { // unchecked -> stop sending
timerSender->stop();
}
else {
timerSender->start();
}
}
void WASABIQtWindow::actionAbout() {
QMessageBox* about;
about = new QMessageBox(QMessageBox::Information, "About WASABIQtGUI", QString("<p>Copyright (C) 2011 Christian Becker-Asano. <br>All rights reserved.<br>Contact: Christian Becker-Asano (christian@becker-asano.de)</p>This is version %0 of the Qt5-based Graphical User Interface (GUI) WASABIQtGUI, which depends on the shared librarie WASABIEngine to run the WASABI Affect Simulation Architecture as described in the doctoral thesis of Christian Becker-Asano. It is licensed under the LGPL and its source can be obtained freely via GitHub.<p>For further information, please visit:<br> <a href='https://www.becker-asano.de/index.php/component/search/?searchword=WASABI'>https://www.becker-asano.de/index.php/component/search/?searchword=WASABI</a></p>").arg(CURRENT_VERSION), QMessageBox::Ok);
about->setTextFormat(Qt::RichText);
about->show();
}
void WASABIQtWindow::actionPAD_space() {
if (ui->actionPAD_space->isChecked()){
padWindow->show();
}
else {
padWindow->hide();
}
}
//EXTENSION2
std::string
WASABIQtWindow::composeEmoML(cogaEmotionalAttendee* ea)
{
if (ea) {
std::string trace1, trace2, trace3;
//strUpdateRate = ea->intToString(updateRate);
trace1 = ea->getPBuffer();
trace2 = ea->getABuffer();
trace3= ea-> getDBuffer();
std::stringstream ssEmoML;
ssEmoML << "<emotionml version=\"1.0\" xmlns=\"http://www.w3.org/2009/10/emotionml\"> <emotion dimension-set=\"http://www.w3.org/TR/emotion-voc/xml#pad-dimensions\"> <dimension name=\"pleasure\"> <trace freq=\"" << updateRate << "Hz\" samples= \"" << trace1 << "\"/> </dimension> <dimension name=\"arousal\"> <trace freq=\"" << updateRate << "Hz\" samples= \"" << trace2 << "\"/> </dimension> <dimension name=\"dominance\"> <trace freq=\"" << updateRate << "Hz\" samples=\"" << trace3 << "\"/> </dimension> </emotion> </emotionml>";
return ssEmoML.str();
}
return "Error";
}
//END OF EXTENSION2
void WASABIQtWindow::on_pushButton_network_send_clicked()
{
if (ui->lineEdit_network_send->text().isEmpty())
return;
QByteArray datagram;
datagram.append(ui->lineEdit_network_send->text());
int port = (ui->lineEditReceiverPort->text()).toInt();
std::cout << "Sending to port " << port;
//udpSocketSender->setPeerPort((ui->lineEditReceiverPort->text()).toInt());
if (udpSocketSender->writeDatagram(datagram.data(), datagram.size(), QHostAddress::Broadcast, port) != -1) {
printNetworkMessage(datagram.data(), false, true);
}
else {
printNetworkMessage(datagram.data(), false, false);
}
std::cout << " done!" << std::endl;
}
void WASABIQtWindow::setPADspace(bool state)
{
ui->actionPAD_space->setChecked(state);
}
/*!
* Initializing ea's using the xml file.
*/
bool WASABIQtWindow::initEAbyXML(cogaEmotionalAttendee* ea)
{
QString filename = "xml/";
filename = filename.append(ea->EmoConPerson->xmlFilename.c_str());
QFile file;
if (!(file.exists(filename))) {
qDebug() << "WASABIQtWindow::initEAbyXML: file " << filename << " not found.";
return false;
}
QXmlStreamReader xml;
file.setFileName(filename);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "WASABIQtWindow::initEAbyXML: Could not open " << filename << " read-only in text mode.";
return false;
}
xml.setDevice(&file);
if (xml.readNextStartElement()) {
std::cout << "xml.name()='''" << xml.name().toString().toStdString() << "'''" << std::endl;
if (xml.name() == "emotionml" && xml.attributes().value("version") == "1.0") {
return readEmotionML(xml, ea);
}
else
xml.raiseError(QObject::tr("The file is not an EMOTIONML version 1.0 file."));
}
return false;
}
bool WASABIQtWindow::readEmotionML(QXmlStreamReader& xml, cogaEmotionalAttendee* ea) {
Q_ASSERT(xml.isStartElement() && xml.name() == "emotionml");
bool returnValue = false;
tmp_emodata.affect_polygons.clear();
tmp_emodata.emoclass = "undef";
while (xml.readNextStartElement()) {
std::cout << "^^^xml.name()='''" << xml.name().toString().toStdString() << "'''" << std::endl;
if (xml.name() == "info")
returnValue = readInfo(xml, ea);
else if (xml.name() == "emotion") {
std::cout << "***WASABIQtWindow::readEmotionML: xml.name()='''" << xml.name().toString().toStdString() << "'''" << std::endl;
returnValue = readEmotion(xml, ea);
if (returnValue && tmp_emodata.emoclass != "undef") {
//We got a primary or secondary emotion
if (tmp_emodata.emoclass == "primary") {
std::vector<float> p_a_d_ID_max_min_baseInt;
p_a_d_ID_max_min_baseInt.push_back(tmp_emodata.pleasure);
p_a_d_ID_max_min_baseInt.push_back(tmp_emodata.arousal);
p_a_d_ID_max_min_baseInt.push_back(tmp_emodata.dominance);
p_a_d_ID_max_min_baseInt.push_back(-1); //MOOD_ID is obsolete without agent MAX!
p_a_d_ID_max_min_baseInt.push_back(tmp_emodata.sat_threshold);
p_a_d_ID_max_min_baseInt.push_back(tmp_emodata.act_threshold);
p_a_d_ID_max_min_baseInt.push_back(tmp_emodata.base_intensity);
ea->EmoConPerson->buildPrimaryEmotion(p_a_d_ID_max_min_baseInt, tmp_emodata.category, tmp_emodata.decay);
}
else if (tmp_emodata.emoclass == "secondary" && tmp_emodata.affect_polygons.size() > 0) {
SecondaryEmotion* se = new SecondaryEmotion();
EmotionDynamics* me = dynamic_cast<EmotionDynamics*>(ea->EmoConPerson);
if (me) {
se->setEmotionContainer(me);
}
se->addPolygon(tmp_emodata.affect_polygons);
if (!se->setStandardLifetime((double)tmp_emodata.lifetime) || !se->setLifetime((double)tmp_emodata.lifetime)) {
cerr << "WASABIQtWindow::readEmotionML: WARNING invalid lifetime " << (double)tmp_emodata.lifetime << endl;
}
if (!se->setBaseIntensity((double)tmp_emodata.base_intensity)) {
cerr << "SecondaryEmotion::loadFromFile: WARNING invalid baseIntensity " << (double)tmp_emodata.base_intensity << endl;
}
se->type = tmp_emodata.category;
if (tmp_emodata.decay == "linear") {
se->setDecayFunction(SecondaryEmotion::LINEAR);
}
else if (tmp_emodata.decay == "none") {
se->setDecayFunction(SecondaryEmotion::NONE);
}
else if (tmp_emodata.decay == "exponential") {
se->setDecayFunction(SecondaryEmotion::EXPONENTIAL);
}
else if (tmp_emodata.decay == "cosine") {
se->setDecayFunction(SecondaryEmotion::COSINE);
}
ea->EmoConPerson->affectiveStates.push_back(se);
}
}
}
else
xml.skipCurrentElement();
}
return returnValue;
}
bool WASABIQtWindow::readInfo(QXmlStreamReader& xml, cogaEmotionalAttendee* ea) {
Q_ASSERT(xml.isStartElement() && xml.name() == "info");
xml.readNext();
std::cout << xml.tokenType() << " tokenType!" << std::endl;
while (!(xml.tokenType() == QXmlStreamReader::EndElement &&
xml.name() == "info")) {
if (xml.name() == "" || xml.tokenType() == QXmlStreamReader::EndElement) {
xml.readNext();
continue;
}
std::cout << "WASABIQtWindow::readInfo: xml.name()='''" << xml.name().toString().toStdString() << "'''" << std::endl;
//std::cout << "WASABIQtWindow::readInfo: xml.namespaceUri()='''" << xml.namespaceUri().toString().toStdString() << "'''" << std::endl;
//This results in the http address!
if (xml.name() == "parameter") {
if ( xml.namespaceUri().toString().toStdString() != "http://www.becker-asano.de/WASABI/Shema/WASABI") {
std::cout << "WASABIQtWindow::readInfo: wrong namespaceUri, only http://www.becker-asano.de/WASABI/Shema/WASABI allowed:" << std::endl;
xml.readNext();
continue;
}
if (xml.attributes().hasAttribute("type") && xml.attributes().value("type") == "dynamic") {
// These are the general dynamic infos
std::cout << "xml.attributes().value(\"name\") = " << xml.attributes().value("name").toString().toStdString() << std::endl;
QStringRef nameValue = xml.attributes().value("name");
bool ok;
int value;
switch (returnIndex(nameValue.toString().toStdString(), "xTens yTens slope mass xReg yReg boredom prevalence")) {
case 1: //xTens
value = xml.attributes().value("value").toInt(&ok);
if (ok) {
std::cout << "setting xTens for EA " << ea->getLocalID() << std::endl;
ea->EmoConPerson->xTens = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to integer.";
}
break;
case 2: //yTens
value = xml.attributes().value("value").toInt(&ok);
if (ok) {
std::cout << "setting yTens for EA " << ea->getLocalID() << std::endl;
ea->EmoConPerson->yTens = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to integer.";
}
break;
case 3: //slope
value = xml.attributes().value("value").toInt(&ok);
if (ok) {
std::cout << "setting slope for EA " << ea->getLocalID() << std::endl;
ea->EmoConPerson->slope = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to integer.";
}
break;
case 4: //mass
value = xml.attributes().value("value").toInt(&ok);
if (ok) {
std::cout << "setting mass for EA " << ea->getLocalID() << std::endl;
ea->EmoConPerson->mass = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to integer.";
}
break;
case 5: //xReg
value = xml.attributes().value("value").toInt(&ok);
if (ok) {
std::cout << "setting xReg for EA " << ea->getLocalID() << std::endl;
ea->EmoConPerson->xReg = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to integer.";
}
break;
case 6: //yReg
value = xml.attributes().value("value").toInt(&ok);
if (ok) {
std::cout << "setting yReg for EA " << ea->getLocalID() << std::endl;
ea->EmoConPerson->yReg = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to integer.";
}
break;
case 7: //boredom
value = xml.attributes().value("value").toInt(&ok);
if (ok) {
std::cout << "setting boredom for EA " << ea->getLocalID() << std::endl;
ea->EmoConPerson->boredom = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to integer.";
}
break;
case 8: //prevalence
value = xml.attributes().value("value").toInt(&ok);
if (ok) {
std::cout << "setting prevalence for EA " << ea->getLocalID() << std::endl;
ea->EmoConPerson->prevalence = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to integer.";
}
break;
default:
qDebug() << "WASABIQtWindow::readInfo: error reading unknown value " << xml.attributes().value("value") << "!";
}
//xml.skipCurrentElement();
}
else //i.e. if !(xml.attributes().hasAttribute("type") && xml.attributes().value("type") == "dynamic")
{
std::cout << "WASABIQtWindow::readInfo: error: only 'parameter' tags with attribute with 'type=dynamic' allowed on top level." << std::endl;
}
}
else if (xml.name() == "primary") {
tmp_emodata.emoclass = "primary";
// We have to parse a primary emotion
xml.readNext();
while (!(xml.tokenType() == QXmlStreamReader::EndElement &&
xml.name() == "primary")) {
if (xml.name() == "" || xml.tokenType() == QXmlStreamReader::EndElement) {
xml.readNext();
continue;
}
std::cout << "xml.name()='''" << xml.name().toString().toStdString() << "''', tokenType = " << xml.tokenType() << std::endl;
if (xml.name() == "parameter") {
std::cout << "xml.attributes().value(\"name\") = " << xml.attributes().value("name").toString().toStdString() << std::endl;
QStringRef nameValue = xml.attributes().value("name");
//std::cout << "xml.namespaceUri()='''" << xml.namespaceUri().toString().toStdString() << "'''" << std::endl;
if ( xml.namespaceUri().toString().toStdString() != "http://www.becker-asano.de/WASABI/Shema/WASABI") {
std::cout << "WASABIQtWindow::readInfo: wrong namespaceUri, only http://www.becker-asano.de/WASABI/Shema/WASABI allowed:" << std::endl;
xml.readNext();
continue;
}
bool ok;
float value;
switch (returnIndex(nameValue.toString().toStdString(), "base_intensity act_threshold sat_threshold decay")) {
case 1: //base_intensity
value = xml.attributes().value("value").toFloat(&ok);
if (ok) {
std::cout << "setting base_intensity for EA " << ea->getLocalID() << std::endl;
tmp_emodata.base_intensity = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to float.";
}
break;
case 2: //act_threshold
value = xml.attributes().value("value").toFloat(&ok);
if (ok) {
std::cout << "setting activation threshold for EA " << ea->getLocalID() << std::endl;
tmp_emodata.act_threshold = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to float.";
}
break;
case 3: //sat_threshold
value = xml.attributes().value("value").toFloat(&ok);
if (ok) {
std::cout << "setting saturation threshold for EA " << ea->getLocalID() << std::endl;
tmp_emodata.sat_threshold = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to float.";
}
break;
case 4: //decay
std::cout << "setting decay function for EA " << ea->getLocalID() << std::endl;
tmp_emodata.decay = xml.attributes().value("value").toString().toStdString();
break;
default:
qDebug() << "WASABIQtWindow::readInfo: (primary) error reading incorrect parameter value " << xml.attributes().value("value") << "!";
}
}
xml.readNext();
}
}
else if (xml.name() == "secondary") {
// We have to parse a primary emotion
tmp_emodata.emoclass = "secondary";
tmp_emodata.affect_polygons.clear();
xml.readNext();
while (!(xml.tokenType() == QXmlStreamReader::EndElement &&
xml.name() == "secondary") && xml.tokenType() != QXmlStreamReader::Invalid) {
if (xml.name() == "" || xml.tokenType() == QXmlStreamReader::EndElement) {
xml.readNext();
continue;
}
std::cout << "xml.name()='''" << xml.name().toString().toStdString() << "''', tokenType = " << xml.tokenType() << std::endl;
//std::cout << "xml.namespaceUri()='''" << xml.namespaceUri().toString().toStdString() << "'''" << std::endl;
if ( xml.namespaceUri().toString().toStdString() != "http://www.becker-asano.de/WASABI/Shema/WASABI") {
std::cout << "WASABIQtWindow::readInfo: wrong namespaceUri, only http://www.becker-asano.de/WASABI/Shema/WASABI allowed:" << std::endl;
xml.readNext();
continue;
}
if (xml.name() == "parameter") {
std::cout << "xml.attributes().value(\"name\") = " << xml.attributes().value("name").toString().toStdString() << std::endl;
QStringRef nameValue = xml.attributes().value("name");
bool ok;
float value;
switch (returnIndex(nameValue.toString().toStdString(), "base_intensity lifetime decay")) {
case 1: //base_intensity
value = xml.attributes().value("value").toFloat(&ok);
if (ok) {
std::cout << "setting base_intensity for EA " << ea->getLocalID() << std::endl;
tmp_emodata.base_intensity = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to float.";
}
break;
case 2: //lifetime
value = xml.attributes().value("value").toFloat(&ok);
if (ok) {
std::cout << "setting lifetime threshold for EA " << ea->getLocalID() << std::endl;
tmp_emodata.lifetime = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to float.";
}
break;
case 3: //decay
std::cout << "setting decay function for EA " << ea->getLocalID() << std::endl;
tmp_emodata.decay = xml.attributes().value("value").toString().toStdString();
break;
default:
qDebug() << "WASABIQtWindow::readInfo: (secondary) error reading incorrect parameter value " << xml.attributes().value("value") << "!";
}
}
else if(xml.name() == "polygon"){
// We have to parse a primary emotion
QStringRef type = xml.attributes().value("type");
if (type != "QUAD") {
std::cout << "WASABIQtWindow::readInfo: wrong type '''" << type.toString().toStdString() << "''' for polygon" << std::endl;
break;
}
std::vector<AffectVertex*> affect_vertices;
xml.readNext();
while (!(xml.tokenType() == QXmlStreamReader::EndElement &&
xml.name() == "polygon")) {
if (xml.name() == "" || xml.tokenType() == QXmlStreamReader::EndElement) {
xml.readNext();
continue;
}
std::cout << "xml.name()='''" << xml.name().toString().toStdString() << "''', tokenType = " << xml.tokenType() << std::endl;
//std::cout << "xml.namespaceUri()='''" << xml.namespaceUri().toString().toStdString() << "'''" << std::endl;
if ( xml.namespaceUri().toString().toStdString() != "http://www.becker-asano.de/WASABI/Shema/WASABI") {
qDebug() << "WASABIQtWindow::readInfo: wrong namespaceUri, only http://www.becker-asano.de/WASABI/Shema/WASABI allowed:";
xml.readNext();
continue;
}
if (xml.name() == "vertex") {
if (xml.attributes().hasAttribute("pleasure") && xml.attributes().hasAttribute("arousal") && xml.attributes().hasAttribute("dominance") && xml.attributes().hasAttribute("intensity")) {
float pad_f[3]; //pleasure, arousal, dominance;
int pad[3];
float intensity;
bool ok;
pad_f[0] = xml.attributes().value("pleasure").toFloat(&ok);
if (ok) {
pad[0] = (int)((pad_f[0] * 2 - 1.0) * 100); // old range [0.0, 1.0], new range [-100, 100]
pad_f[1] = xml.attributes().value("arousal").toFloat(&ok);
if (ok) {
pad[1] = (int)((pad_f[1] * 2 - 1.0) * 100);
pad_f[2] = xml.attributes().value("dominance").toFloat(&ok);
if (ok) {
pad[2] = (int)((pad_f[2] * 2 - 1.0) * 100);
intensity = xml.attributes().value("intensity").toFloat(&ok);
if (ok) {
affect_vertices.push_back(new AffectVertex(pad, (double)intensity));
}
}
}
}
if (!ok) {
std::cout << "WASABIQtWindow::readInfo: at least one dimension or intensity was not a float value!" << std::endl;
}
}
else {
std::cout << "WASABIQtWindow::readInfo: at least one dimension or intensity is missing in vertex defnition" << std::endl;
}
}
xml.readNext();
}
if (affect_vertices.size() > 0) {
AffectPolygon* ap = new AffectPolygon(affect_vertices, "QUAD");
tmp_emodata.affect_polygons.push_back(ap);
}
}
xml.readNext();
}
}
xml.readNext();
}
return true;
}
bool WASABIQtWindow::readEmotion(QXmlStreamReader& xml, cogaEmotionalAttendee* ea) {
Q_ASSERT(xml.isStartElement() && xml.name() == "emotion");
bool returnValue = false;;
xml.readNext();
while (!(xml.tokenType() == QXmlStreamReader::EndElement &&
xml.name() == "emotion")) {
if (xml.name() == "" || xml.tokenType() == QXmlStreamReader::EndElement) {
xml.readNext();
continue;
}
std::cout << "WASABIQtWindow::readEmotion xml.name()='''" << xml.name().toString().toStdString() << "'''" << std::endl;
if (xml.name() == "info") {
std::cout << "------------------inner readInfo start--------------------" << std::endl;
returnValue = readInfo(xml, ea);
std::cout << "------------------inner readInfo end --------------------" << std::endl;
}
else if (xml.name() == "category") {
std::string attrname = xml.attributes().value("name").toString().toStdString();
std::cout << "WASABIQtWindow::readEmotion xml.attributes().value(\"name\") = " << attrname << std::endl;
if (attrname.length() == 0) {
std::cerr << "Attribute " << attrname << ".length() == 0!, bailing out." << std::endl;
returnValue = false;
break;
}
tmp_emodata.category = xml.attributes().value("name").toString().toStdString();
returnValue = true;
}
else if (xml.name() == "dimension") {
std::string attrname = xml.attributes().value("name").toString().toStdString();
std::cout << "WASABIQtWindow::readEmotion xml.attributes().value(\"name\") = " << attrname << std::endl;
if (attrname.length() == 0) {
std::cerr << "Attribute " << attrname << ".length() == 0!, bailing out." << std::endl;
returnValue = false;
break;
}
bool ok;
float value;
// We have to rescale these values from [0, 1] to [-1, 1] !!!
switch (returnIndex(attrname, "pleasure arousal dominance")) {
case 1: //pleasure
value = xml.attributes().value("value").toFloat(&ok);
if (ok) {
value = value * 2 -1;
std::cout << "setting pleasure for EA " << ea->getLocalID() << " with emotion " << tmp_emodata.category << "." << std::endl;
tmp_emodata.pleasure = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to float.";
}
break;
case 2: //arousal
value = xml.attributes().value("value").toFloat(&ok);
if (ok) {
value = value * 2 -1;
std::cout << "setting arousal for EA " << ea->getLocalID() << " with emotion " << tmp_emodata.category << "." << std::endl;
tmp_emodata.arousal = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to float.";
}
break;
case 3: //dominance
value = xml.attributes().value("value").toFloat(&ok);
if (ok) {
value = value * 2 -1;
std::cout << "setting dominance for EA " << ea->getLocalID() << " with emotion " << tmp_emodata.category << "." << std::endl;
tmp_emodata.dominance = value;
}
else {
qDebug() << "WASABIQtWindow::readInfo: error converting " << xml.attributes().value("value") << " to float.";
}
break;
default:
qDebug() << "WASABIQtWindow::readInfo: error reading incorrect parameter value " << xml.attributes().value("value") << "!";
}
returnValue = true;
}
//else {
//xml.skipCurrentElement();
//}
//xml.skipCurrentElement();
xml.readNext();
if (xml.tokenType() == QXmlStreamReader::StartElement) {
std::cout << "WASABIQtWindow::readEmotion StartElement with name " << xml.name().toString().toStdString() << std::endl;
}
if (xml.tokenType() == QXmlStreamReader::Invalid) {
std::cerr << "WASABIQtWindow::readEmotion Invalid element, bailing out! " << std::endl;
returnValue = false;
break;
}
}
return returnValue;
}