Commit 8b665c26 authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Merge branch 'workflow-support' into 'development'

Workflow support

See merge request !104
parents 96ede32e 451917ca
......@@ -40,6 +40,7 @@ SET(CampvisApplicationToBeMocced
gui/logviewerwidget.h
gui/loghighlighter.h
gui/scriptingwidget.h
gui/workflowcontrollerwidget.h
gui/mdi/mdidockarea.h
gui/mdi/mdidockablewindow.h
gui/mdi/mdidockedwindow.h
......
......@@ -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);
......@@ -344,7 +371,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;
......
......@@ -67,6 +67,7 @@ namespace campvis {
, _selectedDataContainer(0)
, _logViewer(0)
, _scriptingConsoleWidget(nullptr)
, _workflowWidget(nullptr)
{
cgtAssert(_application != 0, "Application must not be 0.");
ui.setupUi(this);
......@@ -161,6 +162,10 @@ namespace campvis {
ui.scriptingConsoleDock->setVisible(false);
#endif
_workflowWidget = new WorkflowControllerWidget(_application, this);
ui.workflowDock->setWidget(_workflowWidget);
ui.workflowDock->setVisible(! _application->_workflows.empty());
_dcInspectorWidget = new DataContainerInspectorWidget();
this->populateMainMenu();
......@@ -208,6 +213,7 @@ namespace campvis {
toolsMenu->addAction(ui.pipelineTreeDock->toggleViewAction());
toolsMenu->addAction(ui.pipelinePropertiesDock->toggleViewAction());
toolsMenu->addAction(ui.logViewerDock->toggleViewAction());
toolsMenu->addAction(ui.workflowDock->toggleViewAction());
}
bool MainWindow::eventFilter(QObject* watched, QEvent* event) {
......@@ -417,4 +423,9 @@ namespace campvis {
#endif
}
void MainWindow::setWorkflow(AbstractWorkflow* w) {
ui.workflowDock->setVisible(true);
_workflowWidget->setWorkflow(w);
}
}
......@@ -32,6 +32,7 @@
#include "application/gui/properties/propertycollectionwidget.h"
#include "application/gui/logviewerwidget.h"
#include "application/gui/scriptingwidget.h"
#include "application/gui/workflowcontrollerwidget.h"
#include "application/tools/qtexteditlog.h"
#include "application/ui_mainwindow.h"
......@@ -91,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
......@@ -200,6 +208,7 @@ namespace campvis {
LogViewerWidget* _logViewer; ///< Widget displaying log messages
ScriptingWidget* _scriptingConsoleWidget; ///< Widget showing the scripting console (if available)
WorkflowControllerWidget* _workflowWidget; ///< Widget showing the workflow controller
QPushButton* _btnLuaLoad;
QPushButton* _btnLuaSave;
......
......@@ -30,6 +30,7 @@
#include "core/properties/abstractproperty.h"
#include "core/properties/propertycollection.h"
#include <QMetaType>
#include <QPushButton>
namespace campvis {
......@@ -72,12 +73,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();
......@@ -105,8 +107,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) {
......@@ -121,10 +125,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:
/**
......
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2014, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universitaet Muenchen
// Boltzmannstr. 3, 85748 Garching b. Muenchen, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
#include "workflowcontrollerwidget.h"
#include "cgt/assert.h"
#include "application/campvisapplication.h"
#include "application/qtjobprocessor.h"
#include <QLabel>
#include <QGridLayout>
#include <QPushButton>
#include <QScrollArea>
#include <QSignalMapper>
#include <QVBoxLayout>
namespace campvis {
const std::string WorkflowControllerWidget::loggerCat_ = "CAMPVis.application.WorkflowControllerWidget";
WorkflowControllerWidget::WorkflowControllerWidget(CampVisApplication* application, QWidget* parent /*= nullptr*/)
: QWidget(parent)
, _application(application)
, _workflow(nullptr)
, _isBackWardsStep(true)
, _signalMapper(nullptr)
, _lblWorkflowStage(nullptr)
, _propertyCollectionWidget(nullptr)
, _btnPrevStage(nullptr)
, _layoutNextStages(nullptr)
{
setupGUI();
}
WorkflowControllerWidget::~WorkflowControllerWidget() {
clearNextStagesLayout();
}
void WorkflowControllerWidget::init() {
if (_workflow != nullptr)
_workflow->init();
}
void WorkflowControllerWidget::deinit() {
if (_workflow != nullptr)
_workflow->deinit();
}
void WorkflowControllerWidget::setWorkflow(AbstractWorkflow* workflow) {
if (_workflow != nullptr) {
_workflow->s_stageChanged.disconnect(this);
_workflow->s_stageAvailabilityChanged.disconnect(this);
}
_workflow = workflow;
_stageHistory.clear();
QtJobProc.enqueueJob([&]() {
if (_workflow != nullptr) {
// FIXME: Taking the first of the DataContainers here is not really beautiful!
_propertyCollectionWidget->updatePropCollection(_workflow, _workflow->getDataContainer());
_workflow->s_stageChanged.connect(this, &WorkflowControllerWidget::onStageChanged);
_workflow->s_stageAvailabilityChanged.connect(this, &WorkflowControllerWidget::onStageAvailabilityChanged);
}
else {
_propertyCollectionWidget->updatePropCollection(nullptr, nullptr);
_lblWorkflowStage->setText(tr("No Workflow Loaded."));
}
});
}
void WorkflowControllerWidget::onPrevButtonClicked() {
if (!_stageHistory.empty()) {
int previousStage = _stageHistory.back();
_stageHistory.pop_back();
_isBackWardsStep = true;
_workflow->setCurrentStage(previousStage);
}
}
void WorkflowControllerWidget::onStageSelectionButtonClicked(int id) {
_workflow->setCurrentStage(id);
}
void WorkflowControllerWidget::setupGUI() {
QGridLayout* layout = new QGridLayout(this);
_lblWorkflowStage = new QLabel(tr("Current Stage: n/a"), this);
layout->addWidget(_lblWorkflowStage, 0, 0, 1, 2);
QScrollArea* sa = new QScrollArea(this);
sa->setWidgetResizable(true);
sa->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
sa->setFrameStyle(QScrollArea::NoFrame);
_propertyCollectionWidget = new PropertyCollectionWidget(this);
sa->setWidget(_propertyCollectionWidget);
layout->addWidget(sa, 1, 0, 1, 2);
_btnPrevStage = new QPushButton("<< n/a");
_btnPrevStage->setEnabled(false);
layout->addWidget(_btnPrevStage, 2, 0, 1, 1);
_layoutNextStages = new QVBoxLayout();
layout->addLayout(_layoutNextStages, 2, 1, 1, 1);
_signalMapper = new QSignalMapper(this);
populateNextStagesLayout();
connect(_btnPrevStage, SIGNAL(clicked()), this, SLOT(onPrevButtonClicked()));
connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(onStageSelectionButtonClicked(int)));
}
void WorkflowControllerWidget::clearNextStagesLayout() {
for (auto it = _nextButtons.begin(); it != _nextButtons.end(); ++it) {
_layoutNextStages->removeWidget(it->first);
delete it->first;
}
_nextButtons.clear();
}
void WorkflowControllerWidget::populateNextStagesLayout() {
// first, remove all buttons from the layout
clearNextStagesLayout();
// now, populate with buttons for the current stage
if (_workflow != nullptr) {
const AbstractWorkflow::Stage& s = _workflow->getCurrentStage();
for (auto it = s._possibleTransitions.begin(); it != s._possibleTransitions.end(); ++it) {
QPushButton* theButton = new QPushButton(QString::fromStdString((*it)->_title + " >>"));
_layoutNextStages->addWidget(theButton);
_nextButtons.push_back(std::make_pair(theButton, (*it)->_id));
_signalMapper->setMapping(theButton, (*it)->_id);
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();
}
}
void WorkflowControllerWidget::onStageChanged(int previousStage, int currentStage) {
if (!_isBackWardsStep)
_stageHistory.push_back(previousStage);
_isBackWardsStep = false;
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() {
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);
}
}
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012-2014, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universitaet Muenchen
// Boltzmannstr. 3, 85748 Garching b. Muenchen, Germany
//
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
//
// ================================================================================================
#ifndef WORKFLOWCONTROLLERWIDGET_H__
#define WORKFLOWCONTROLLERWIDGET_H__
#include "core/pipeline/abstractworkflow.h"
#include "application/gui/properties/propertycollectionwidget.h"
#include <QLabel>
#include <QWidget>
#include <list>
#include <utility>
#include <vector>
class QPushButton;
class QSignalMapper;
class QVBoxLayout;
namespace campvis {
class CampVisApplication;
/**
* This widget is used to offer a user interface to an \a AbstractWorkflow instance.
*
* It has two buttons to navigate through the workflow and displays the properties
* of the \a AbstractWorkflow using a \a PropertyCollectionWidget.
*/
class WorkflowControllerWidget : public QWidget, public sigslot::has_slots {
Q_OBJECT;
public:
/**
* 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(CampVisApplication* application, QWidget* parent = nullptr);
/**
* Destructor.
*/
~WorkflowControllerWidget();
/// dummy. may be useful at some point
void init();
/// 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.
*/
void setWorkflow(AbstractWorkflow* workflow);
public slots:
void onPrevButtonClicked();
void onStageSelectionButtonClicked(int id);
private:
/// Setup the the workflow controller's GUI.
void setupGUI();
void clearNextStagesLayout();
void populateNextStagesLayout();
/// connects to \a AbstractWorkflow::s_stageChanged signal
void onStageChanged(int previousStage, int currentStage);
/// 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)
/// All existing buttons to move to next stage paired with the stage they advance to.
std::vector< std::pair<QPushButton*, int> > _nextButtons;
QSignalMapper* _signalMapper;
QLabel* _lblWorkflowStage;
PropertyCollectionWidget* _propertyCollectionWidget;
QPushButton* _btnPrevStage;
QVBoxLayout* _layoutNextStages;
static const std::string loggerCat_;
};
}
#endif // WORKFLOWCONTROLLERWIDGET_H__
......@@ -13,6 +13,9 @@
<property name="windowTitle">
<string>MainWindow</string>
</property>
<property name="dockNestingEnabled">
<bool>true</bool>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
......@@ -58,15 +61,24 @@
</attribute>
<widget class="QWidget" name="logViewerDockContents"/>
</widget>
<widget class="QDockWidget" name="scriptingConsoleDock">
<property name="windowTitle">
<string>Scripting Console</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QWidget" name="scriptingConsoleDockContents"/>
</widget>
<widget