1#include <Dataflow/Core/DataflowGraph.hpp>
2#include <Dataflow/Core/Node.hpp>
3#include <Dataflow/Core/NodeFactory.hpp>
4#include <Dataflow/Core/PortIn.hpp>
5#include <Dataflow/Core/PortOut.hpp>
6#include <Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp>
7#include <QtNodes/DataFlowGraphicsScene>
8#include <QtNodes/GraphicsView>
15#include <unordered_map>
19#include "GraphEditor/GraphModel.hpp"
20#include "ui_NodeEditor.h"
25namespace GraphEditor {
26using namespace Ra::Dataflow::Core;
28GraphEditorWindow::~GraphEditorWindow() {}
33 auto central_widget =
new QWidget(
this );
34 auto central_layout =
new QVBoxLayout( central_widget );
37 m_scene =
new QtNodes::BasicGraphicsScene( *m_graph_model,
this );
38 m_view =
new QtNodes::GraphicsView( m_scene,
this );
40 central_layout->addWidget( m_view );
41 central_layout->setContentsMargins( 0, 0, 0, 0 );
42 central_layout->setSpacing( 0 );
43 setCentralWidget( central_widget );
44 central_widget->setFocusPolicy( Qt::StrongFocus );
50 setCurrentFile( QString() );
51 setUnifiedTitleAndToolBarOnMac(
true );
53 QDockWidget* dock =
new QDockWidget(
this );
54 dock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
56 auto node_tree_widget =
new QTreeWidget( dock );
57 node_tree_widget->setColumnCount( 1 );
58 node_tree_widget->setHeaderLabel(
"Nodes" );
60 for (
const auto& [factory_name, factory] : NodeFactoriesManager::factory_manager() ) {
62 for (
const auto& [model_name, creator] :
factory->factory_map() ) {
63 auto f = creator.first;
65 node_list[creator.second].push_back( model_name );
69 new QTreeWidgetItem( QStringList() << QString::fromStdString( factory_name ) );
71 for (
auto [category, model_names] : node_list ) {
72 auto n =
new QTreeWidgetItem( QStringList() << QString::fromStdString( category ) );
73 std::sort( model_names.begin(), model_names.end() );
74 for (
const auto& m : model_names ) {
75 n->addChild(
new QTreeWidgetItem( QStringList() << QString::fromStdString( m ) ) );
78 factory_item->addChild( n );
80 node_tree_widget->addTopLevelItem( factory_item );
84 node_tree_widget, &QTreeWidget::itemDoubleClicked,
this, &GraphEditorWindow::add_node );
85 connect( m_graph_model.get(), &GraphModel::node_edited,
this, &GraphEditorWindow::node_editor );
87 &QtNodes::BasicGraphicsScene::nodeDoubleClicked,
89 &GraphEditorWindow::node_dialog );
91 dock->setWidget( node_tree_widget );
92 addDockWidget( Qt::LeftDockWidgetArea, dock );
93 viewMenu->addAction( dock->toggleViewAction() );
96void GraphEditorWindow::add_node( QTreeWidgetItem* item,
int ) {
98 if ( item->childCount() == 0 ) { m_graph_model->addNode( item->text( 0 ) ); }
101class GraphEditorDialog :
public QDialog
105 auto p_dialogLayout =
new QGridLayout(
this );
106 auto p_MainWindow =
new GraphEditorWindow( graph );
107 auto model = p_MainWindow->graph_model();
108 model->addInputOutputNodesForGraph();
109 p_dialogLayout->addWidget( p_MainWindow );
110 p_MainWindow->setParent(
this );
117 auto n = g->instance_name();
118 auto dn = g->display_name();
119 auto w =
new GraphEditorDialog( g,
this );
121 w->setWindowModality( Qt::ApplicationModal );
123 connect( w, &GraphEditorDialog::finished, [
this, g, n, dn](
int ) {
124 g->set_instance_name( n );
125 g->set_display_name( dn );
127 m_graph_model->clear_node_widget( g.get() );
128 m_graph_model->sync_data();
132class NodeEditorDialog :
public QDialog
135 NodeEditorDialog(
Node* node, QWidget* parent =
nullptr, Qt::WindowFlags f = Qt::Dialog ) :
136 QDialog( parent, f ), m_node { node } {
139 ui.node_model->setText( QString::fromStdString( node->
model_name() ) );
140 ui.node_instance->setText( QString::fromStdString( node->
instance_name() ) );
141 ui.display_name_line_edit->setText( QString::fromStdString( node->
display_name() ) );
142 data_editors.emplace_back( ui.display_name_line_edit, [node](
const QString& text ) {
143 node->set_display_name( text.toStdString() );
147 auto layout = ui.port_grid_layout;
150 for (
auto p : node->
inputs() ) {
151 auto l = new QLineEdit( QString::fromStdString( p->name() ) );
152 data_editors.emplace_back(
153 l, [p]( const QString& text ) { p->set_name( text.toStdString() ); } );
155 layout->addWidget(
new QLabel( QString::fromStdString( p->name() ) ), row, 0 );
156 layout->addWidget( l, row, 1 );
161 for (
auto p : node->
outputs() ) {
162 auto l =
new QLineEdit( QString::fromStdString( p->name() ) );
163 data_editors.emplace_back(
164 l, [p](
const QString& text ) { p->set_name( text.toStdString() ); } );
166 layout->addWidget(
new QLabel( QString::fromStdString( p->name() ) ), row, 2 );
167 layout->addWidget( l, row, 3 );
171 auto graph_node =
dynamic_cast<GraphNode*
>( node );
173 clear_unused =
new QCheckBox(
"clear unused" );
174 layout->addWidget( clear_unused, row, 0 );
176 connect( ui.buttonBox, &QDialogButtonBox::accepted,
this, &NodeEditorDialog::run );
181 for (
auto p : data_editors ) {
182 p.second( p.first->displayText() );
184 if ( clear_unused && clear_unused->checkState() == Qt::Checked ) {
185 auto graph_node =
dynamic_cast<GraphNode*
>( m_node );
186 if ( graph_node ) { graph_node->remove_unlinked_ports(); }
192 Node* m_node {
nullptr };
193 QCheckBox* clear_unused {
nullptr };
197void GraphEditorWindow::node_dialog( QtNodes::NodeId node_id ) {
198 auto node = m_graph_model->node_ptr( node_id );
200 NodeEditorDialog dialog( node.get() );
203 m_graph_model->sync_data();
206void GraphEditorWindow::closeEvent( QCloseEvent* event ) {
211 else {
event->ignore(); }
214void GraphEditorWindow::newFile() {
217 m_graph_model->sync_data();
219 setCurrentFile(
"" );
223void GraphEditorWindow::open() {
226 QString path = settings.value(
"files/open", QDir::homePath() ).toString();
227 QString fname = QFileDialog::getOpenFileName(
228 this, tr(
"Open File" ), path, tr(
"Flow (*.flow);; All (*.*)" ) );
229 if ( !fname.isEmpty() ) loadFile( fname );
230 settings.setValue(
"files/open", fname );
234bool GraphEditorWindow::save() {
235 if ( m_curFile.isEmpty() ) {
return saveAs(); }
236 else {
return saveFile( m_curFile ); }
239bool GraphEditorWindow::saveAs() {
241 QString path = settings.value(
"files/save", QDir::homePath() ).toString();
242 QString fname = QFileDialog::getSaveFileName(
243 this, tr(
"Open File" ), path, tr(
"Flow (*.flow);; All (*.*)" ) );
244 if ( fname.isNull() || fname.isEmpty() )
return false;
245 settings.setValue(
"files/save", fname );
246 return saveFile( fname );
249void GraphEditorWindow::about() {
250 QMessageBox::about(
this,
251 tr(
"About Node Editor" ),
252 tr(
"This is NodeGraph Editor widget from Radium::Dataflow::QtGui." ) );
255void GraphEditorWindow::documentWasModified() {
256 setWindowModified( m_graph->shouldBeSaved() );
259void GraphEditorWindow::createActions() {
260 auto fileMenu = menuBar()->addMenu( tr(
"&File" ) );
261 auto fileToolBar = addToolBar( tr(
"File" ) );
262 const QIcon newIcon = QIcon::fromTheme(
"document-new", QIcon(
":/images/new.png" ) );
263 auto newAct =
new QAction( newIcon, tr(
"&New" ),
this );
264 newAct->setShortcuts( QKeySequence::New );
265 newAct->setStatusTip( tr(
"Create a new file" ) );
266 connect( newAct, &QAction::triggered,
this, &GraphEditorWindow::newFile );
267 fileMenu->addAction( newAct );
268 fileToolBar->addAction( newAct );
270 const QIcon openIcon = QIcon::fromTheme(
"document-open", QIcon(
":/images/open.png" ) );
271 auto openAct =
new QAction( openIcon, tr(
"&Open..." ),
this );
272 openAct->setShortcuts( QKeySequence::Open );
273 openAct->setStatusTip( tr(
"Open an existing file" ) );
274 connect( openAct, &QAction::triggered,
this, &GraphEditorWindow::open );
275 fileMenu->addAction( openAct );
276 fileToolBar->addAction( openAct );
278 const QIcon saveIcon = QIcon::fromTheme(
"document-save", QIcon(
":/images/save.png" ) );
279 auto saveAct =
new QAction( saveIcon, tr(
"&Save" ),
this );
280 saveAct->setShortcuts( QKeySequence::Save );
281 saveAct->setStatusTip( tr(
"Save the document to disk" ) );
282 connect( saveAct, &QAction::triggered,
this, &GraphEditorWindow::save );
283 fileMenu->addAction( saveAct );
284 fileToolBar->addAction( saveAct );
286 const auto saveAsIcon = QIcon::fromTheme(
"document-save-as" );
288 fileMenu->addAction( saveAsIcon, tr(
"Save &As..." ),
this, &GraphEditorWindow::saveAs );
289 saveAsAct->setShortcuts( QKeySequence::SaveAs );
290 saveAsAct->setStatusTip( tr(
"Save the document under a new name" ) );
292 fileMenu->addSeparator();
294 const QIcon exitIcon = QIcon::fromTheme(
"application-exit" );
295 auto exitAct = fileMenu->addAction( exitIcon, tr(
"E&xit" ),
this, &QWidget::close );
296 exitAct->setShortcuts( QKeySequence::Quit );
297 exitAct->setStatusTip( tr(
"Exit the application" ) );
299 auto helpMenu = menuBar()->addMenu( tr(
"&Help" ) );
300 auto aboutAct = helpMenu->addAction( tr(
"&About" ),
this, &GraphEditorWindow::about );
301 aboutAct->setStatusTip( tr(
"Show the application's About box" ) );
303 auto aboutQtAct = helpMenu->addAction( tr(
"About &Qt" ), qApp, &QApplication::aboutQt );
304 aboutQtAct->setStatusTip( tr(
"Show the Qt library's About box" ) );
307 viewMenu = menuBar()->addMenu( tr(
"&View" ) );
310void GraphEditorWindow::createStatusBar() {
311 statusBar()->showMessage( tr(
"Ready" ) );
314void GraphEditorWindow::readSettings() {
315 QSettings settings( QCoreApplication::organizationName(), QCoreApplication::applicationName() );
316 settings.beginGroup(
"nodegraph editor" );
317 const QByteArray geometry = settings.value(
"geometry", QByteArray() ).toByteArray();
318 if ( geometry.isEmpty() ) {
319 const QRect availableGeometry = screen()->availableGeometry();
320 resize( availableGeometry.width() / 3, availableGeometry.height() / 2 );
321 move( ( availableGeometry.width() - width() ) / 2,
322 ( availableGeometry.height() - height() ) / 2 );
324 else { restoreGeometry( geometry ); }
328void GraphEditorWindow::writeSettings() {
329 QSettings settings( QCoreApplication::organizationName(), QCoreApplication::applicationName() );
330 settings.beginGroup(
"nodegraph editor" );
331 settings.setValue(
"geometry", saveGeometry() );
335bool GraphEditorWindow::maybeSave() {
336 if ( m_graph ==
nullptr ) {
return true; }
337 if ( !m_graph->shouldBeSaved() ) {
return true; }
338 const QMessageBox::StandardButton ret =
339 QMessageBox::warning(
this,
341 tr(
"The document has been modified.\n"
342 "Do you want to save your changes?" ),
343 QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel );
345 case QMessageBox::Save:
347 case QMessageBox::Cancel:
356void GraphEditorWindow::loadFile(
const QString& fileName ) {
357 QGuiApplication::setOverrideCursor( Qt::WaitCursor );
359 QFile file( fileName );
360 if ( !file.open( QFile::ReadOnly | QFile::Text ) ) {
361 QMessageBox::warning(
364 tr(
"Cannot read file %1:\n%2." )
365 .arg( QDir::toNativeSeparators( fileName ), file.errorString() ) );
372 loaded = m_graph->loadFromJson( fileName.toStdString() );
375 QMessageBox::warning(
378 tr(
"Can't load graph from file %1.\n" ).arg( QDir::toNativeSeparators( fileName ) ) );
381 QGuiApplication::restoreOverrideCursor();
383 m_graph_model->sync_data();
384 m_view->centerScene();
385 setCurrentFile( fileName );
386 statusBar()->showMessage( tr(
"File loaded" ), 2000 );
392bool GraphEditorWindow::saveFile(
const QString& fileName ) {
393 QString errorMessage;
395 QGuiApplication::setOverrideCursor( Qt::WaitCursor );
399 m_graph->saveToJson( fileName.toStdString() );
401 QGuiApplication::restoreOverrideCursor();
403 if ( !errorMessage.isEmpty() ) {
404 QMessageBox::warning(
this, tr(
"Application" ), errorMessage );
408 setCurrentFile( fileName );
409 statusBar()->showMessage( tr(
"File saved" ), 2000 );
413void GraphEditorWindow::setCurrentFile(
const QString& fileName ) {
414 m_curFile = fileName;
416 setWindowModified(
false );
418 QString shownName = m_curFile;
419 if ( m_curFile.isEmpty() ) shownName =
"untitled.flow";
420 setWindowFilePath( shownName );
423QString GraphEditorWindow::strippedName(
const QString& fullFileName ) {
424 return QFileInfo( fullFileName ).fileName();
Base abstract class for all the nodes added and used by the node system.
const std::string & instance_name() const
Gets the instance name of the node.
const PortBaseInCollection & inputs() const
Gets the in ports of the node.
const std::string & display_name() const
Gets the display name of the node (e.g. for gui), no need to be unique in a graph.
const std::string & model_name() const
Gets the model (type/class) name of the node.
const PortBaseOutCollection & outputs() const
Gets the out ports of the node.
auto factory(const NodeFactorySet::key_type &name) -> NodeFactorySet::mapped_type
Gets the given factory from the manager.
hepler function to manage enum as underlying types in VariableSet
T dynamic_pointer_cast(T... args)