Starting from 2021-07-01, all LRZ GitLab users will be required to explicitly accept the GitLab Terms of Service. Please see the detailed information at https://doku.lrz.de/display/PUBLIC/GitLab and make sure that your projects conform to the requirements.

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<