Commit 9b2ad449 authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Further work on workflow support:

* Workflow stages now store the visibility of pipeline canvases
* Extended PipelineFactory to also hold creator functions to create workflows
* CampVisApplication now creates and initializes workflows when launched with "-w WorkflowName" parameter

refs #13
parent 94f27ca9
......@@ -44,6 +44,7 @@
#include "core/tools/stringutils.h"
#include "core/tools/quadrenderer.h"
#include "core/pipeline/abstractpipeline.h"
#include "core/pipeline/abstractworkflow.h"
#include "core/datastructures/imagerepresentationconverter.h"
#include "core/pipeline/visualizationprocessor.h"
......@@ -174,10 +175,33 @@ namespace campvis {
// parse argument list and create pipelines
QStringList pipelinesToAdd = this->arguments();
for (int i = 1; i < pipelinesToAdd.size(); ++i) {
DataContainer* dc = createAndAddDataContainer("DataContainer #" + StringUtils::toString(_dataContainers.size() + 1));
AbstractPipeline* p = PipelineFactory::getRef().createPipeline(pipelinesToAdd[i].toStdString(), dc);
if (p != 0)
addPipeline(pipelinesToAdd[i].toStdString(), p);
if (pipelinesToAdd[i] == "-w" && i+1 < pipelinesToAdd.size()) {
// create workflow
AbstractWorkflow* w = PipelineFactory::getRef().createWorkflow(pipelinesToAdd[i+1].toStdString());
if (w != nullptr) {
// get DataContainers and Pipelines from workflow, take ownership and initialize them where necessary
_dataContainers.push_back(w->getDataContainer());
s_DataContainersChanged.emitSignal();
std::vector<AbstractPipeline*> pipelines = w->getPipelines();
for (auto it = pipelines.begin(); it != pipelines.end(); ++it) {
addPipeline((*it)->getName(), *it);
}
_workflows.push_back(w);
_mainWindow->setWorkflow(w);
w->init();
}
++i;
}
else {
DataContainer* dc = createAndAddDataContainer("DataContainer #" + StringUtils::toString(_dataContainers.size() + 1));
AbstractPipeline* p = PipelineFactory::getRef().createPipeline(pipelinesToAdd[i].toStdString(), dc);
if (p != nullptr)
addPipeline(pipelinesToAdd[i].toStdString(), p);
}
}
GLJobProc.setContext(_localContext);
......@@ -194,6 +218,9 @@ namespace campvis {
it->_pipeline->stop();
}
for (auto it = _workflows.begin(); it != _workflows.end(); ++it)
(*it)->deinit();
{
// Deinit everything OpenGL related using the local context.
cgt::GLContextScopedLock lock(_localContext);
......@@ -341,7 +368,13 @@ namespace campvis {
LuaVmState* CampVisApplication::getLuaVmState() {
return _luaVmState;
}
#endif
void CampVisApplication::setPipelineVisibility(AbstractPipeline* pipeline, bool visibility) {
auto it = _pipelineWindows.find(pipeline);
if (it != _pipelineWindows.end()) {
it->second->setVisible(visibility);
}
}
}
......@@ -48,6 +48,7 @@ namespace cgt {
namespace campvis {
class AbstractPipeline;
class AbstractWorkflow;
class MainWindow;
class CampVisPainter;
class MdiDockableWindow;
......@@ -138,6 +139,14 @@ namespace campvis {
void rebuildAllShadersFromFiles();
/**
* Sets the visibility of the given pipeline's canvas to \a visibility.
* \param pipeline Pipeline whose canvas' visibility should be changed.
* \param visibility New visibility of the canvas.
*/
void setPipelineVisibility(AbstractPipeline* pipeline, bool visibility);
#ifdef CAMPVIS_HAS_SCRIPTING
/**
* Returns the global LuaVmState of this application.
......@@ -148,18 +157,20 @@ namespace campvis {
/// Signal emitted when the collection of pipelines has changed.
sigslot::signal0 s_PipelinesChanged;
/// Signal emitted when the collection of DataContainers has changed.
sigslot::signal0 s_DataContainersChanged;
private:
void initGlContextAndPipeline(cgt::GLCanvas* canvas, AbstractPipeline* pipeline);
/// All workflows
std::vector<AbstractWorkflow*> _workflows;
/// All pipelines
std::vector<PipelineRecord> _pipelines;
std::map<AbstractPipeline *, MdiDockableWindow *> _pipelineWindows;
/// Map of all pipelines with their MDI windows
std::map<AbstractPipeline*, MdiDockableWindow*> _pipelineWindows;
/// All DataContainers
std::vector<DataContainer*> _dataContainers;
......
......@@ -142,8 +142,9 @@ namespace campvis {
ui.scriptingConsoleDock->setVisible(false);
#endif
_workflowWidget = new WorkflowControllerWidget(this);
_workflowWidget = new WorkflowControllerWidget(_application, this);
ui.workflowDock->setWidget(_workflowWidget);
ui.workflowDock->setVisible(! _application->_workflows.empty());
_dcInspectorWidget = new DataContainerInspectorWidget();
this->populateMainMenu();
......@@ -352,4 +353,9 @@ namespace campvis {
#endif
}
void MainWindow::setWorkflow(AbstractWorkflow* w) {
ui.workflowDock->setVisible(true);
_workflowWidget->setWorkflow(w);
}
}
......@@ -92,6 +92,13 @@ namespace campvis {
*/
MdiDockableWindow * addVisualizationPipelineWidget(const std::string& name, QWidget* canvas);
/**
* Sets the workflow of this Mainwindow to \a w.
* \param w The workflow to display for this window.
*/
void setWorkflow(AbstractWorkflow* w);
protected:
/**
* Listens to resize events on _pipelinePropertiesWidget and resizes _pipelinePropertiesScrollArea accordingly
......
......@@ -30,6 +30,7 @@
#include "core/properties/abstractproperty.h"
#include "core/properties/propertycollection.h"
#include <QMetaType>
#include <QPushButton>
namespace campvis {
......@@ -74,12 +75,13 @@ namespace campvis {
setLayout(_layout);
connect(this, SIGNAL(s_widgetVisibilityChanged(QWidget*, bool)), this, SLOT(onWidgetVisibilityChanged(QWidget*, bool)));
connect(this, SIGNAL(propertyAdded(AbstractProperty*)), this, SLOT(addProperty(AbstractProperty*)));
connect(this, SIGNAL(propertyRemoved(std::map<AbstractProperty*, QWidget*>::iterator)), this, SLOT(removeProperty(std::map<AbstractProperty*, QWidget*>::iterator)));
connect(this, SIGNAL(propertyRemoved(AbstractProperty*, QWidget*)), this, SLOT(removeProperty(AbstractProperty*, QWidget*)));
}
void PropertyCollectionWidget::clearWidgetMap() {
for (std::map<AbstractProperty*, QWidget*>::iterator it = _widgetMap.begin(); it != _widgetMap.end(); ++it) {
removeProperty(it);
it->first->s_visibilityChanged.disconnect(this);
removeProperty(it->first, it->second);
}
_widgetMap.clear();
......@@ -107,8 +109,10 @@ namespace campvis {
void PropertyCollectionWidget::onPropCollectionPropRemoved(AbstractProperty* prop) {
std::map<AbstractProperty*, QWidget*>::iterator it = _widgetMap.find(prop);
if (it != _widgetMap.end())
emit propertyRemoved(it);
if (it != _widgetMap.end()) {
emit propertyRemoved(it->first, it->second);
_widgetMap.erase(it);
}
}
void PropertyCollectionWidget::addProperty(AbstractProperty* prop) {
......@@ -123,10 +127,10 @@ namespace campvis {
prop->s_visibilityChanged.emitSignal(prop);
}
void PropertyCollectionWidget::removeProperty(std::map<AbstractProperty*, QWidget*>::iterator it) {
it->first->s_visibilityChanged.disconnect(this);
_layout->removeWidget(it->second);
delete it->second;
void PropertyCollectionWidget::removeProperty(AbstractProperty* prop, QWidget* widget) {
prop->s_visibilityChanged.disconnect(this);
_layout->removeWidget(widget);
delete widget;
}
}
\ No newline at end of file
......@@ -84,13 +84,13 @@ namespace campvis {
* Removes the property widget for \a prop, disconnects all neccesary signals, etc.
* \param prop Iterator to widget map for the property to remove.
*/
void removeProperty(std::map<AbstractProperty*, QWidget*>::iterator prop);
void removeProperty(AbstractProperty* prop, QWidget* widget);
signals:
void s_widgetVisibilityChanged(QWidget* widget, bool visibility);
void propertyAdded(AbstractProperty* prop);
void propertyRemoved(std::map<AbstractProperty*, QWidget*>::iterator prop);
void propertyRemoved(AbstractProperty* prop, QWidget* widget);
private:
/**
......
......@@ -25,6 +25,7 @@
#include "workflowcontrollerwidget.h"
#include "cgt/assert.h"
#include "application/campvisapplication.h"
#include "application/qtjobprocessor.h"
#include <QLabel>
......@@ -39,10 +40,11 @@ namespace campvis {
const std::string WorkflowControllerWidget::loggerCat_ = "CAMPVis.application.WorkflowControllerWidget";
WorkflowControllerWidget::WorkflowControllerWidget(QWidget* parent /*= nullptr*/)
WorkflowControllerWidget::WorkflowControllerWidget(CampVisApplication* application, QWidget* parent /*= nullptr*/)
: QWidget(parent)
, _application(application)
, _workflow(nullptr)
, _isBackWardsStep(false)
, _isBackWardsStep(true)
, _signalMapper(nullptr)
, _lblWorkflowStage(nullptr)
, _propertyCollectionWidget(nullptr)
......@@ -78,7 +80,7 @@ namespace campvis {
QtJobProc.enqueueJob([&]() {
if (_workflow != nullptr) {
// FIXME: Taking the first of the DataContainers here is not really beautiful!
_propertyCollectionWidget->updatePropCollection(_workflow, _workflow->getDataContainers().front());
_propertyCollectionWidget->updatePropCollection(_workflow, _workflow->getDataContainer());
_workflow->s_stageChanged.connect(this, &WorkflowControllerWidget::onStageChanged);
_workflow->s_stageAvailabilityChanged.connect(this, &WorkflowControllerWidget::onStageAvailabilityChanged);
......@@ -156,6 +158,15 @@ namespace campvis {
connect(theButton, SIGNAL(clicked()), _signalMapper, SLOT(map()));
}
if (_stageHistory.empty()) {
_btnPrevStage->setEnabled(false);
_btnPrevStage->setText(tr("<< n/a"));
}
else {
_btnPrevStage->setEnabled(true);
_btnPrevStage->setText(tr("<< ") + QString::fromStdString(_workflow->getStage(_stageHistory.back())._title));
}
onStageAvailabilityChanged();
}
}
......@@ -165,20 +176,30 @@ namespace campvis {
_stageHistory.push_back(previousStage);
_isBackWardsStep = false;
QtJobProc.enqueueJob([=]() {
QtJobProc.enqueueJob([&]() {
populateNextStagesLayout();
const AbstractWorkflow::Stage& s = _workflow->getCurrentStage();
_lblWorkflowStage->setText(QString::fromStdString(s._title));
for (auto it = s._pipelineCanvasVisibilities.begin(); it != s._pipelineCanvasVisibilities.end(); ++it) {
_application->setPipelineVisibility(it->first, it->second);
}
});
}
void WorkflowControllerWidget::onStageAvailabilityChanged() {
for (auto it = _nextButtons.begin(); it != _nextButtons.end(); ++it) {
int theStage = it->second;
it->first->setEnabled(_workflow->isStageAvailable(theStage));
}
QtJobProc.enqueueJob([&]() {
for (auto it = _nextButtons.begin(); it != _nextButtons.end(); ++it) {
int theStage = it->second;
it->first->setEnabled(_workflow->isStageAvailable(theStage));
}
});
}
QSize WorkflowControllerWidget::sizeHint() const {
return QSize(300, 400);
}
}
......@@ -41,6 +41,7 @@ class QSignalMapper;
class QVBoxLayout;
namespace campvis {
class CampVisApplication;
/**
* This widget is used to offer a user interface to an \a AbstractWorkflow instance.
......@@ -55,9 +56,10 @@ namespace campvis {
/**
* Creates a new WorkflowControllerWidget.
* \param application Pointer to the CampvisApplication owning this widget, must not be 0.
* \param parent Parent Qt widget, may be 0 (default)
*/
explicit WorkflowControllerWidget(QWidget* parent = nullptr);
explicit WorkflowControllerWidget(CampVisApplication* application, QWidget* parent = nullptr);
/**
* Destructor.
......@@ -70,6 +72,8 @@ namespace campvis {
/// dummy. may be useful at some point
void deinit();
QSize sizeHint() const;
/**
* Sets the AbstractWorkflow that should be handled by this widget.
* Sets up the signal/slot connections.
......@@ -93,6 +97,7 @@ namespace campvis {
/// connects to \a AbstractWorkflow::s_stageAvailabilityChanged signal
void onStageAvailabilityChanged();
CampVisApplication* _application; ///< Pointer to the CampvisApplication owning this widget, must not be 0.
AbstractWorkflow* _workflow; ///< the currently associated \a WorkflowController
std::list<int> _stageHistory; ///< History of the visited stages, used to provide "go back" functionality
bool _isBackWardsStep; ///< Flag whether current stage selection is a backwards step (then don't add previous stage to history)
......
......@@ -30,10 +30,11 @@
namespace campvis {
const std::string AbstractWorkflow::loggerCat_ = "CAMPVis.core.pipeline.AbstractWorkflow";
AbstractWorkflow::AbstractWorkflow()
: _currentStage(0)
AbstractWorkflow::AbstractWorkflow(const std::string& title)
: _dataContainer(nullptr)
, _currentStage(-1)
{
_dataContainer = new DataContainer(title);
}
AbstractWorkflow::~AbstractWorkflow() {
......@@ -47,7 +48,7 @@ namespace campvis {
// default initialize the workflow with the stage with the lowest ID
if (_stages.find(_currentStage) == _stages.end())
_currentStage = _stages.begin()->first;
setCurrentStage(_stages.begin()->first);
}
void AbstractWorkflow::deinit() {
......@@ -64,10 +65,14 @@ namespace campvis {
}
const AbstractWorkflow::Stage& AbstractWorkflow::getCurrentStage() const {
cgtAssert(_stages.find(_currentStage) != _stages.end(), "Could not find current stage in workflow stage map. This must not happen!");
return getStage(_currentStage);
}
const AbstractWorkflow::Stage& AbstractWorkflow::getStage(int id) const {
cgtAssert(_stages.find(id) != _stages.end(), "Could not find current stage in workflow stage map. This must not happen!");
// use at() since there is no const overload of operator[]...
const Stage* toReturn = _stages.at(_currentStage);
const Stage* toReturn = _stages.at(id);
return *toReturn;
}
......@@ -86,22 +91,28 @@ namespace campvis {
s_stageChanged.emitSignal(oldStage, _currentStage);
}
void AbstractWorkflow::addStage(int id, const std::string& title, const std::vector<AbstractProperty*>& visibleProperties /*= std::vector<AbstractProperty*>()*/) {
cgtAssert(_stages.find(id) == _stages.end(), "Tried two stages with the same ID.");
void AbstractWorkflow::addStage(int id, const std::string& title, const std::vector< std::pair<AbstractPipeline*, bool> >& pipelineCanvasVisibilities, const std::vector<AbstractProperty*>& visibleProperties /*= std::vector<AbstractProperty*>()*/) {
cgtAssert(_stages.find(id) == _stages.end(), "Tried to add two stages with the same ID.");
Stage* s = new Stage();
s->_id = id;
s->_title = title;
s->_pipelineCanvasVisibilities = pipelineCanvasVisibilities;
s->_visibleProperties = visibleProperties;
_stages[id] = s;
}
void AbstractWorkflow::addStageTransition(int from, int to) {
cgtAssert(_stages.find(from) == _stages.end(), "Tried to register a stage transition with an ivalid stage ID.");
cgtAssert(_stages.find(to) == _stages.end(), "Tried to register a stage transition with an ivalid stage ID.");
cgtAssert(_stages.find(from) != _stages.end(), "Tried to register a stage transition with an ivalid stage ID.");
cgtAssert(_stages.find(to) != _stages.end(), "Tried to register a stage transition with an ivalid stage ID.");
_stages[from]->_possibleTransitions.push_back(_stages[to]);
}
DataContainer* AbstractWorkflow::getDataContainer() {
return _dataContainer;
}
}
......@@ -47,9 +47,9 @@ namespace campvis {
* states.
*
* To implement your own workflow, subclass this class and populate it with your needs.
* Implement the two pure virtual methods getDataContainers() and getPipelines() accordingly
* in order to allow the outside world (i.e. campvis-application) access to the DataContainers
* and Pipelines, for instance to create and initialize canvases and stuff.
* Implement the pure virtual method getPipelines() accordingly in order to allow the outside
* world (i.e. campvis-application) access to the Pipelines, for instance to create and
* initialize canvases and stuff.
* \b Beware: This way you will transfer ownership of the returned pointers to the caller
* (usually the owner of the workflow object). Every owner of a workflow has to make sure to
* call these functions and take ownership of those pointers. Furthermore, it has to guarantee
......@@ -75,6 +75,9 @@ namespace campvis {
/// Possible workflow stages following this stage
std::vector<Stage*> _possibleTransitions;
/// Visibilities of the pipeline's canvases
std::vector< std::pair<AbstractPipeline*, bool> > _pipelineCanvasVisibilities;
/// Visible properties for this stage
std::vector<AbstractProperty*> _visibleProperties;
};
......@@ -83,24 +86,13 @@ namespace campvis {
/**
* Default constructor
*/
AbstractWorkflow();
AbstractWorkflow(const std::string& title);
/**
* Default virtual destructor.
*/
virtual ~AbstractWorkflow();
/**
* Returns a list of all DataContainers, which are used by this workflow and should appear
* in campvis-application. This method is to be called once by the owner of the workflow
* object. Implement this method to your needs when subclassing AbstractWorkflow.
*
* \note Be aware that the caller will take ownership of all pointers in the returned vector!
*
* \return A vector of all DataContainers used in this workflow, caller will take ownership of the pointers.
*/
virtual std::vector<DataContainer*> getDataContainers() = 0;
/**
* Returns a list of all Pipelines, which are used by this workflow and should appear
* in campvis-application (and thus need to be initialized). This method is to be called
......@@ -112,6 +104,12 @@ namespace campvis {
* \return A vector of all Pipelines used in this workflow, caller will take ownership of the pointers.
*/
virtual std::vector<AbstractPipeline*> getPipelines() = 0;
/**
* Gets the name of this very workflow. To be defined by every subclass.
* \return The name of this workflow.
*/
virtual const std::string getName() const = 0;
/**
* This method gets called by the owner of the workflow object after it has initialized
......@@ -130,6 +128,14 @@ namespace campvis {
*/
virtual void deinit();
/**
* Returns the DataContainer of this workflow.
*
* \note Be aware that the caller will take ownership of this pointer!
* \return _dataContainer
*/
DataContainer* getDataContainer();
/**
* Performs an additional check, whether the stage with ID \a stage is currently available.
* This check is applied to all possible transitions and allows to implement custom checks
......@@ -149,11 +155,18 @@ namespace campvis {
int getCurrentStageId() const;
/**
* Returns a const reference to the current workflow stage;
* Returns a const reference to the current workflow stage.
* \return _stages[_currentStage]
*/
const Stage& getCurrentStage() const;
/**
* Returns a const reference to the workflow stage with the given id.
* \param id Id of the workflow stage to return
* \return _stages[id]
*/
const Stage& getStage(int id) const;
/**
* Sets the current workflow stage to the one with the given ID.
* This method does \b not do any checks whether this transition is actually possible.
......@@ -169,18 +182,20 @@ namespace campvis {
/// Signal emitted when the current stage has changed, passes the IDs of the former and the new workflow stage.
sigslot::signal2<int, int> s_stageChanged;
/// signal emitted each time the availability of any stage has changed
/// Signal emitted each time the availability of any stage has changed.
/// \note You should emit this signal according to your overload of isStageAvailable().
sigslot::signal0 s_stageAvailabilityChanged;
protected:
/**
* Registers a new workflow stage with the given ID, title and optionally visible parameters.
* \note You should not call this method somewhere else than in the constructor.
* \param id Integer ID of the new stage. I \b strongly recommend to use enums for this.
* \param title Title of the stage to display in the GUI.
* \param visibleProperties Vector of visible properties, when this stage is active. Defaults to an empty list.
* \param id Integer ID of the new stage. I \b strongly recommend to use enums for this.
* \param title Title of the stage to display in the GUI.
* \param pipelineCanvasVisibilities Visibilities of the pipeline's canvases.
* \param visibleProperties Vector of visible properties, when this stage is active. Defaults to an empty list.
*/
void addStage(int id, const std::string& title, const std::vector<AbstractProperty*>& visibleProperties = std::vector<AbstractProperty*>());
void addStage(int id, const std::string& title, const std::vector< std::pair<AbstractPipeline*, bool> >& pipelineCanvasVisibilities, const std::vector<AbstractProperty*>& visibleProperties = std::vector<AbstractProperty*>());
/**
* Registers a new transition between the two given workflow stages.
......@@ -189,7 +204,7 @@ namespace campvis {
*/
void addStageTransition(int from, int to);
DataContainer* _dataContainer; ///< DataContainer of this workflow. BEWARE: You do not own this pointer!
int _currentStage; ///< currently active workflow stage
std::map<int, Stage*> _stages; ///< Map of all workflow stages by ID
......
......@@ -401,7 +401,7 @@ namespace campvis {
}
void SliceRenderProcessor::renderGeometry(DataContainer& dataContainer, const ImageRepresentationGL::ScopedRepresentation& img) {
ScopedTypedData<GeometryData> geometry(dataContainer, p_geometryID.getValue());
ScopedTypedData<GeometryData> geometry(dataContainer, p_geometryID.getValue(), true);
// render optional geometry
if (geometry) {
......
......@@ -55,7 +55,7 @@ namespace campvis {
std::vector<std::string> toReturn;
toReturn.reserve(_pipelineMap.size());
for (std::map< std::string, std::function<AbstractPipeline*(DataContainer*)> >::const_iterator it = _pipelineMap.begin(); it != _pipelineMap.end(); ++it)
for (auto it = _pipelineMap.begin(); it != _pipelineMap.end(); ++it)
toReturn.push_back(it->first);
return toReturn;
}
......@@ -63,11 +63,21 @@ namespace campvis {
AbstractPipeline* PipelineFactory::createPipeline(const std::string& id, DataContainer* dc) const {
tbb::spin_mutex::scoped_lock lock(_mutex);
std::map< std::string, std::function<AbstractPipeline*(DataContainer*)> >::const_iterator it = _pipelineMap.find(id);
auto it = _pipelineMap.find(id);
if (it == _pipelineMap.end())
return 0;
return nullptr;
else
return (it->second)(dc);
}
AbstractWorkflow* PipelineFactory::createWorkflow(const std::string& id) const {
tbb::spin_mutex::scoped_lock lock(_mutex);
auto it = _workflowMap.find(id);
if (it == _workflowMap.end())
return nullptr;
else
return (it->second)();
}
}
......@@ -38,6 +38,7 @@
namespace campvis {
class AbstractPipeline;
class AbstractWorkflow;
class DataContainer;
/**
......@@ -62,6 +63,8 @@ namespace campvis {
AbstractPipeline* createPipeline(const std::string& id, DataContainer* dc) const;
AbstractWorkflow* createWorkflow(const std::string& id) const;
/**
* Statically registers the pipeline of type T using \a callee as factory method.
* \note The template instantiation of PipelineRegistrar takes care of calling this method.
......@@ -72,7 +75,7 @@ namespace campvis {
size_t registerPipeline(std::function<AbstractPipeline*(DataContainer*)> callee) {
tbb::spin_mutex::scoped_lock lock(_mutex);
std::map< std::string, std::function<AbstractPipeline*(DataContainer*)> >::iterator it = _pipelineMap.lower_bound(T::getId());
auto it = _pipelineMap.lower_bound(T::getId());