Commit eeb2ce33 authored by cfouard's avatar cfouard
Browse files

NEW adding brand new Action State Machine !!

git-svn-id: svn+ssh://scm.forge.imag.fr/var/lib/gforge/chroot/scmrepos/svn/camitk/trunk/camitk@398 ec899d31-69d1-42ba-9299-647d76f65fb3
parent 1e46a31c
#include "ActionState.h"
// -- CamiTK Core stuff
#include <Application.h>
#include <Component.h>
#include <ActionWidget.h>
// -- Qt stuff
#include <QPushButton>
#include <QMainWindow>
#include <QMetaProperty>
ActionState::ActionState(QState * parent, QString name, QString description, QTextStream * logStream)
: QState(parent)
{
this->name = name;
this->description = description;
this->logStream = logStream;
this->myAction = NULL;
this->myStateWidget = new ActionStateWidget(this);
}
void ActionState::setAction(Action * action,
QMap<QString, QVariant> parameters,
QMap<QString, QString> inputComponentNames,
QMap<QString, QString> outputComponentNames) {
this->myAction = action;
action->setAutoUpdateProperties(true);
for (QMap<QString, QVariant>::const_iterator it = parameters.begin(); it != parameters.end(); it++) {
action->setProperty(it.key().toStdString().c_str(), it.value());
}
this->inputComponentNames = inputComponentNames;
int size = inputComponentNames.size();
this->outputComponentNames = outputComponentNames;
size = outputComponentNames.size();
ActionWidget * actionWidget = dynamic_cast<ActionWidget *> (this->myAction->getWidget());
if (actionWidget) {
actionWidget->setButtonVisibility(false);
this->myStateWidget->setActionWidget(actionWidget);
}
}
QString ActionState::getName() {
return name;
}
QString ActionState::getDescription() {
return description;
}
ActionStateWidget * ActionState::getWidget() {
return myStateWidget;
}
ActionTransition * ActionState::addActionTransition(QString buttonText, QAbstractState * nextState,
bool applyAction, QVector<Action::ApplyStatus> disableConditions) {
QPushButton * button = myStateWidget->addTransitionButton(buttonText);
ActionTransition * transition = new ActionTransition(button, SIGNAL(clicked()), this, buttonText, applyAction, logStream);
transition->setTargetState(nextState);
for (QVector<Action::ApplyStatus>::const_iterator it = disableConditions.begin();
it != disableConditions.end(); it++) {
Action::ApplyStatus status = (*it);
QMap<Action::ApplyStatus, QVector<QPushButton *>>::iterator buts = conditionalButtons.find(status);
if (buts != conditionalButtons.end()) {
buts.value().append(button);
}
else {
QVector<QPushButton *> vect;
vect.append(button);
conditionalButtons.insert(status, vect);
}
}
return transition;
}
Action::ApplyStatus ActionState::applyAction() {
Action::ApplyStatus status(Action::TRIGGERED);
if (myAction != NULL) {
if (logStream != NULL) {
(*logStream) << " <applyAction>" << endl;
(*logStream) << " <name>" << myAction->getName() << "</name>" << endl;
// Properties
int nbStaticProps = myAction->metaObject()->propertyCount();
QList<QByteArray> propertyNames = myAction->dynamicPropertyNames();
// static properties
if ((nbStaticProps > 0) || (! propertyNames.isEmpty())) {
(*logStream) << " <parameters>" << endl;
for (int index = 0 ; index < nbStaticProps; index++) {
QString staticPropName = myAction->metaObject()->property(index).name();
(*logStream) << " <parameter name='";
(*logStream) << staticPropName << "' value='";
(*logStream) << myAction->property(staticPropName.toStdString().c_str()).toString() ;
(*logStream) << "' static='true'/>" << endl;
}
// dynamic properties
for (QList<QByteArray>::const_iterator it = propertyNames.begin(); it != propertyNames.end(); it++) {
(*logStream) << " <parameter name='";
(*logStream) << (*it) << "' value='" << myAction->property(*it).toString() << "'/>" << endl;
}
(*logStream) << " </parameters>" << endl;
}
}
ComponentList allComps = Application::getAllComponents();
QMap<QString, QString>::const_iterator namesIt;
ComponentList::const_iterator compIt;
// set input components
if ((logStream != NULL) && (inputComponentNames.size() > 0)) {
(*logStream) << " <inputs>" << endl;
}
ComponentList inputComps;
for (namesIt = inputComponentNames.begin(); namesIt != inputComponentNames.end(); namesIt++) {
QString compName = namesIt.key();
QString compType = namesIt.value();
// Look for the corresponding component into the list of all components
compIt = allComps.begin();
while ( (compIt != allComps.end()) &&
( ((*compIt)->getName() != compName) && (!(*compIt)->isInstanceOf(compType)) )
)
compIt++;
if (compIt != allComps.end()) {
if (logStream != NULL) {
(*logStream) << " <component name='";
(*logStream) << (*compIt)->getName() << "' type='" << compType << "'/>" << endl;
}
inputComps.append((*compIt));
}
}
if ((logStream != NULL) && (inputComponentNames.size() > 0)) {
(*logStream) << " </inputs>" << endl;
}
myAction->setInputComponents(inputComps);
// apply the action
status = myAction->applyInPipeline();
// set the right names to outputComponents
ComponentList outputComps = myAction->getOutputComponents();
ComponentList::const_iterator outIt;
namesIt = outputComponentNames.begin();
if ((logStream != NULL) && (outputComps.size() > 0)) {
(*logStream) << " <outputs>" << endl;
}
for (outIt = outputComps.begin(); outIt != outputComps.end(); outIt++) {
if (namesIt != outputComponentNames.end()) {
QString compName = namesIt.key();
QString compType = namesIt.value();
if ((*outIt)->isInstanceOf(compType)) {
(*outIt)->setName(compName);
namesIt++;
if (logStream != NULL) {
(*logStream) << " <component name='" ;
(*logStream) << compName << "' type='" << compType << "'/>" << endl;
}
}
}
}
if ((logStream != NULL) && (outputComps.size() > 0)) {
(*logStream) << " </outputs>" << endl;
}
Application::refresh();
if (logStream != NULL) {
QString statusStr;
switch(status) {
case Action::ABORTED:
statusStr = "ABORTED";
break;
case Action::ERROR:
statusStr = "ERROR";
break;
case Action::SUCCESS:
statusStr = "SUCCESS";
break;
case Action::TRIGGERED:
statusStr = "TRIGGERED";
break;
default:
statusStr = "UNKNOWN";
break;
}
(*logStream) << " <status>" << statusStr << "</status>" << endl;
(*logStream) << " </applyAction>" << endl;
}
}
return status;
}
void ActionState::setAleternativeDesc(QString altDescText, QVector<Action::ApplyStatus> statusList) {
for (QVector<Action::ApplyStatus>::const_iterator it = statusList.begin();
it != statusList.end(); it++) {
Action::ApplyStatus status = (*it);
conditionalDescriptions.insert(status, altDescText);
}
}
void ActionState::setPreviousActionStatus(camitk::Action::ApplyStatus status) {
// Change the description
QMap<Action::ApplyStatus, QString>::const_iterator desc = conditionalDescriptions.find(status);
if (desc != conditionalDescriptions.end()) {
myStateWidget->setDescription(desc.value());
}
else {
myStateWidget->setDescription(description);
}
// Inhibate/Re-enable proper buttons
for (QMap<Action::ApplyStatus, QVector<QPushButton *>>::const_iterator it = conditionalButtons.begin();
it != conditionalButtons.end(); it++) {
Action::ApplyStatus itStatus = it.key();
QVector<QPushButton *> itButtons = it.value();
for (QVector<QPushButton *>::const_iterator but = itButtons.begin(); but != itButtons.end(); but++) {
if (itStatus == status)
(*but)->setEnabled(false);
else
(*but)->setEnabled(true);
}
}
}
void ActionState::onEntry ( QEvent * event ) {
if (logStream == NULL)
return;
startTime = new QTime();
startTime->start();
(*logStream) << " <state>" << endl;
(*logStream) << " <name>" << this->name << "</name>" << endl;
(*logStream) << " <startTime>" << startTime->toString("hh:mm:ss:zzz") << "</startTime>" << endl;
}
void ActionState::onExit(QEvent *event) {
if (logStream == NULL)
return;
QTime endTime = QTime::currentTime();
(*logStream) << " <endTime>" << endTime.toString("hh:mm:ss:zzz") << "</endTime>" << endl;
(*logStream) << " <timeEnlapsed unit='ms'>" << (startTime->elapsed()) << "</timeEnlapsed>" << endl;
(*logStream) << " </state>" << endl;
}
\ No newline at end of file
#ifndef ACTIONSTATE_H
#define ACTIONSTATE_H
// Qt stuff
#include <QTime>
#include <QState>
#include <QVector>
#include <QTextStream>
//CamiTK stuff
#include <Action.h>
// Local stuff
#include "ActionStateWidget.h"
#include "ActionTransition.h"
using namespace camitk;
class ActionState : public QState {
public:
/**
*/
ActionState(QState * parent, QString name, QString description, QTextStream * logStream = NULL);
void setAction(Action * action,
QMap<QString, QVariant> parameters,
QMap<QString, QString> inputComponentNames,
QMap<QString, QString> outputComponentNames);
/// Returns the name of the action state (may be different from the name of the actual action)
QString getName();
/// Returns the description of the action state (may be different from the description of the actual action)
QString getDescription();
/// May change its description according to the previous action result
void setAleternativeDesc(QString altDescText, QVector<Action::ApplyStatus> statusList);
virtual Action::ApplyStatus applyAction();
/// Adds a possible transition from this action
ActionTransition * addActionTransition(QString buttonText, QAbstractState * nextState,
bool applyAction = true, QVector<Action::ApplyStatus> disableConditions = QVector<Action::ApplyStatus>());
ActionStateWidget * getWidget();
void setPreviousActionStatus(Action::ApplyStatus status);
void setFinal();
protected:
/// Reimplemented from QState
/// @{
virtual void onEntry ( QEvent * event );
virtual void onExit ( QEvent * event );
///@}
/// Name of the state action (may not be the same as the action's name)
QString name;
/// Description of the state action
/// may not be the same as the action's description but complementary
QString description;
/// Actual CamiTK action
Action * myAction;
QMap<QString, QString> inputComponentNames;
QMap<QString, QString> outputComponentNames;
/// Buttons that should be disabled if the previous aciton state did not happen correctly
QMap<Action::ApplyStatus, QVector<QPushButton *>> conditionalButtons;
//// Descriptions that should be displaied if the previous action state did not happen correctly
QMap<Action::ApplyStatus, QString> conditionalDescriptions;
/**
* Widget containing:
* - the name of the state action
* - the description of the action
* - the action's widget
* - the buttons linking to the transitions
*/
ActionStateWidget * myStateWidget;
/// Log stream to write report on logFile
QTextStream * logStream;
/// Keep track of time...
QTime * startTime;
};
#endif // ACTIONSTATE_H
\ No newline at end of file
/*
$CAMITK_LICENCE_BEGIN$
CamiTK - Computer Assisted Medical Intervention ToolKit
Visit http://camitk.imag.fr for more information
Copyright (C) 2012 Celine Fouard, Emmanuel Promayon, Nicolas Saubat
This library 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 2.1 of the License, or (at your option) any later version.
This library 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 this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA
$CAMITK_LICENCE_END$
*/
// -- Qt dependencies
#include <QFileDialog>
#include <QMenuBar>
#include <QMessageBox>
#include <QTextStream>
#include <QString>
#include <QDate>
// -- Qt State Machine stuff
#include <QFinalState>
// -- CamiTK Core stuff
#include <MainWindow.h>
#include <Explorer.h>
#include <Application.h>
#include <MedicalImageViewer.h>
#include <Core.h>
#include <ExtensionManager.h>
#include <Explorer.h>
#include <Log.h>
// -- CamiTK Application Local stuff
#include "ActionStateMachine.h"
#include "ActionStateViewer.h"
#include "ActionTransition.h"
// constructor
ActionStateMachine::ActionStateMachine(int &argc, char ** argv) :
Application("Action State Machine Main Window", argc, argv)
{
statesMap = new QMap<QString, ActionState *>();
// Atributes initialization
mainWindow = NULL;
name = QString("Action State Machine");
// CamiTK Application main window
initMainWindow();
// Load File
QString filename;
if (argc < 2)
{
filename = QFileDialog::getOpenFileName( NULL, "Please select an xml file");
}
else {
filename = argv[1];
}
// check the given file
this->checkSCXMLFile(filename);
// parse XML file and store actions and transitions in states
name = this->parseSCXMLTree();
mainWindow->setWindowSubtitle(name);
// Load exra components
// Load extra actions
// Prepare the output directory
QFile::copy(":/resources/log2html.xsl", saveDirectory.absolutePath() + "/log2html.xsl");
// Starting the Machine !!
//create the main timer
QDate date = QDate::currentDate();
startTime = new QTime();
startTime->start();
(*logStream) << "<?xml version='1.0' encoding='UTF-8' ?>" << endl;
(*logStream) << "<?xml-stylesheet type='text/xsl' href='log2html.xsl'?>" << endl << endl;
(*logStream) << "<application xmlns='http://ujf-grenoble.fr/camitk/logApplication'>" << endl;
(*logStream) << " <name>" << name << "</name>" << endl;
(*logStream) << " <startDate>" << date.toString("yyyy-MM-dd") << "</startDate>" << endl;
(*logStream) << " <startTime>" << startTime->toString("hh:mm:ss:zzz") << "</startTime>" << endl;
machine.start();
}
// destructor
ActionStateMachine::~ActionStateMachine()
{
QTime endTime = QTime::currentTime();
(*logStream) << " <endTime>" << endTime.toString("hh:mm:ss:zzz") << "</endTime>" << endl;
(*logStream) << " <timeEnlapsed unit='ms'>" << (startTime->elapsed()) << "</timeEnlapsed>" << endl;
(*logStream) << "</application>" << endl;
logFile->close();
}
void ActionStateMachine::initMainWindow() {
mainWindow = new MainWindow(name);
mainWindow->setCentralViewer(MedicalImageViewer::getInstance());
mainWindow->addDockViewer(Qt::RightDockWidgetArea, ActionStateViewer::getActionStateViewer());
Explorer * explorer = new Explorer();
mainWindow->addDockViewer(Qt::LeftDockWidgetArea, explorer);
mainWindow->showStatusBar(true);
this->setMainWindow(mainWindow);
}
void ActionStateMachine::checkSCXMLFile(QString filename) throw (AbortException) {
QString msg;
QDomDocument doc;
//read the file
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
msg = "File not found: the file " + filename + " was not found";
CAMITK_ERROR("ActionStateMachine", "setFileName", msg.toStdString());
msg = "Error: cannot fine file " + filename + " sorry...\n";
throw AbortException (msg.toStdString());
}
if (!doc.setContent(&file)) {
msg = "File " + filename + " have no valid root (not a valid XML file format).\n";
file.close();
CAMITK_ERROR("ActionStateMachine", "setFileName", msg.toStdString());
throw AbortException(msg.toStdString());
}
QString rootName = doc.documentElement().nodeName();
if (rootName != QString("scxml")) {
file.close();
msg = "File " + filename + " is not in the right scxml file format.\n";
CAMITK_ERROR("ActionStateMachineWindow", "setFileName", msg.toStdString());
throw AbortException(msg.toStdString());
}
// Ok, after all this checking, the file seems to be good looking,
// set it as the right xml doc...
this->scxmlDoc = doc;
file.close();
}
QString ActionStateMachine::parseSCXMLTree() throw(AbortException) {
// Get the name of the application
QString applicationName = "";
QDomElement docElem = scxmlDoc.documentElement();
applicationName = docElem.attribute("camitk:applicationName");
// Get the name of the saving directory
QString saveDir = docElem.attribute("camitk:saveDirectory");
if (saveDir.isEmpty()) {
saveDirectory = QFileDialog::getExistingDirectory(this->mainWindow, "Please select a directory where to save log and component files");
}
else {
saveDirectory = QDir(saveDir);
}
QDate date = QDate::currentDate();
QTime time = QTime::currentTime();
QString newDir = QString("%1-%2-%3at%4h%5").arg(date.year())
.arg(date.month())
.arg(date.day())
.arg(time.hour())
.arg(time.minute());
if (! saveDirectory.mkdir(newDir)) {
saveDirectory = QFileDialog::getExistingDirectory(this->mainWindow, "Please select a directory where to save log and component files");
saveDirectory.mkdir(newDir);
}
saveDirectory.cd(newDir);
QString logFileName = this->saveDirectory.absolutePath() + "/log.xml";
logFile = new QFile(logFileName);
logFile->open(QIODevice::WriteOnly | QIODevice::Text);
logStream = new QTextStream(logFile);
// Get the states node
QDomNodeList theStates = scxmlDoc.elementsByTagName("state");
// Create ActionStates
createAllActionStates(theStates);
// Now that all the states are created and stored in the map,
// create the transitions in between.
createTransitions(theStates);
// Set the initial state (specified in the scxml element with initial attribute).
QString initialStateName = scxmlDoc.documentElement().attribute("initial");
ActionState * initialState = statesMap->find(initialStateName).value();
machine.setInitialState(initialState);
ActionStateViewer::getActionStateViewer()->setState(initialState);
// Connect the end of the machine with the end of the application
QObject::connect(&machine, SIGNAL(finished()), QApplication::instance(), SLOT(quit()));
// Ok, Actions and Wizard are created. We do not need the file any more.
// Clean up: