Loading [MathJax]/jax/output/HTML-CSS/config.js
Radium Engine  1.5.28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
GraphEditorWindow.cpp
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>
9#include <algorithm>
10#include <functional>
11#include <map>
12#include <memory>
13#include <string>
14#include <type_traits>
15#include <unordered_map>
16#include <utility>
17#include <vector>
18
19#include "GraphEditor/GraphModel.hpp"
20#include "ui_NodeEditor.h"
21
22namespace Ra {
23namespace Dataflow {
24namespace QtGui {
25namespace GraphEditor {
26using namespace Ra::Dataflow::Core;
27
28GraphEditorWindow::~GraphEditorWindow() {}
29
30GraphEditorWindow::GraphEditorWindow( std::shared_ptr<DataflowGraph> graph ) : m_graph { graph } {
31 if ( !m_graph ) { m_graph = std::make_shared<DataflowGraph>( "" ); }
32
33 auto central_widget = new QWidget( this );
34 auto central_layout = new QVBoxLayout( central_widget );
35
36 m_graph_model = std::make_shared<GraphModel>( m_graph );
37 m_scene = new QtNodes::BasicGraphicsScene( *m_graph_model, this );
38 m_view = new QtNodes::GraphicsView( m_scene, this );
39
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 );
45
46 createActions();
47 createStatusBar();
48 readSettings();
49
50 setCurrentFile( QString() );
51 setUnifiedTitleAndToolBarOnMac( true );
52
53 QDockWidget* dock = new QDockWidget( this );
54 dock->setAllowedAreas( Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea );
55
56 auto node_tree_widget = new QTreeWidget( dock );
57 node_tree_widget->setColumnCount( 1 );
58 node_tree_widget->setHeaderLabel( "Nodes" );
59
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;
64 auto creatorFactory = factory;
65 node_list[creator.second].push_back( model_name );
66 }
67
68 auto factory_item =
69 new QTreeWidgetItem( QStringList() << QString::fromStdString( factory_name ) );
70
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 ) ) );
76 }
77
78 factory_item->addChild( n );
79 }
80 node_tree_widget->addTopLevelItem( factory_item );
81 }
82
83 connect(
84 node_tree_widget, &QTreeWidget::itemDoubleClicked, this, &GraphEditorWindow::add_node );
85 connect( m_graph_model.get(), &GraphModel::node_edited, this, &GraphEditorWindow::node_editor );
86 connect( m_scene,
87 &QtNodes::BasicGraphicsScene::nodeDoubleClicked,
88 this,
89 &GraphEditorWindow::node_dialog );
90
91 dock->setWidget( node_tree_widget );
92 addDockWidget( Qt::LeftDockWidgetArea, dock );
93 viewMenu->addAction( dock->toggleViewAction() );
94}
95
96void GraphEditorWindow::add_node( QTreeWidgetItem* item, int ) {
97 // if no child, it's a model name
98 if ( item->childCount() == 0 ) { m_graph_model->addNode( item->text( 0 ) ); }
99}
100
101class GraphEditorDialog : public QDialog
102{
103 public:
104 GraphEditorDialog( std::shared_ptr<DataflowGraph> graph, QWidget* parent ) : QDialog( parent ) {
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 );
111 }
112};
113
114void GraphEditorWindow::node_editor( std::shared_ptr<Node> node ) {
116 // save names given at node creation to be restored after graph edition
117 auto n = g->instance_name();
118 auto dn = g->display_name();
119 auto w = new GraphEditorDialog( g, this );
120
121 w->setWindowModality( Qt::ApplicationModal );
122 w->show();
123 connect( w, &GraphEditorDialog::finished, [this, g, n, dn]( int ) {
124 g->set_instance_name( n );
125 g->set_display_name( dn );
126 g->generate_ports();
127 m_graph_model->clear_node_widget( g.get() );
128 m_graph_model->sync_data();
129 } );
130}
131
132class NodeEditorDialog : public QDialog
133{
134 public:
135 NodeEditorDialog( Node* node, QWidget* parent = nullptr, Qt::WindowFlags f = Qt::Dialog ) :
136 QDialog( parent, f ), m_node { node } {
137 ui.setupUi( this );
138 {
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() );
144 } );
145 }
146
147 auto layout = ui.port_grid_layout;
148 int row = 2;
149
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() ); } );
154
155 layout->addWidget( new QLabel( QString::fromStdString( p->name() ) ), row, 0 );
156 layout->addWidget( l, row, 1 );
157
158 ++row;
159 }
160 row = 2;
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() ); } );
165
166 layout->addWidget( new QLabel( QString::fromStdString( p->name() ) ), row, 2 );
167 layout->addWidget( l, row, 3 );
168
169 ++row;
170 }
171 auto graph_node = dynamic_cast<GraphNode*>( node );
172 if ( graph_node ) {
173 clear_unused = new QCheckBox( "clear unused" );
174 layout->addWidget( clear_unused, row, 0 );
175 }
176 connect( ui.buttonBox, &QDialogButtonBox::accepted, this, &NodeEditorDialog::run );
177 }
178
179 public slots:
180 void run() {
181 for ( auto p : data_editors ) {
182 p.second( p.first->displayText() );
183 }
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(); }
187 }
188 }
189
190 private:
191 std::vector<std::pair<QLineEdit*, std::function<void( const QString& )>>> data_editors;
192 Node* m_node { nullptr };
193 QCheckBox* clear_unused { nullptr };
194 Ui::NodeEditor ui;
195};
196
197void GraphEditorWindow::node_dialog( QtNodes::NodeId node_id ) {
198 auto node = m_graph_model->node_ptr( node_id );
199
200 NodeEditorDialog dialog( node.get() );
201 // int ret =
202 dialog.exec(); // exec is blocking main window, as expected for a modal
203 m_graph_model->sync_data();
204}
205
206void GraphEditorWindow::closeEvent( QCloseEvent* event ) {
207 if ( maybeSave() ) {
208 writeSettings();
209 event->accept();
210 }
211 else { event->ignore(); }
212}
213
214void GraphEditorWindow::newFile() {
215 if ( maybeSave() ) {
216 m_graph->destroy();
217 m_graph_model->sync_data();
218
219 setCurrentFile( "" );
220 }
221}
222
223void GraphEditorWindow::open() {
224 if ( maybeSave() ) {
225 QSettings settings;
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 );
231 }
232}
233
234bool GraphEditorWindow::save() {
235 if ( m_curFile.isEmpty() ) { return saveAs(); }
236 else { return saveFile( m_curFile ); }
237}
238
239bool GraphEditorWindow::saveAs() {
240 QSettings settings;
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 );
247}
248
249void GraphEditorWindow::about() {
250 QMessageBox::about( this,
251 tr( "About Node Editor" ),
252 tr( "This is NodeGraph Editor widget from Radium::Dataflow::QtGui." ) );
253}
254
255void GraphEditorWindow::documentWasModified() {
256 setWindowModified( m_graph->shouldBeSaved() );
257}
258
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 );
269
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 );
277
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 );
285
286 const auto saveAsIcon = QIcon::fromTheme( "document-save-as" );
287 auto saveAsAct =
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" ) );
291
292 fileMenu->addSeparator();
293
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" ) );
298
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" ) );
302
303 auto aboutQtAct = helpMenu->addAction( tr( "About &Qt" ), qApp, &QApplication::aboutQt );
304 aboutQtAct->setStatusTip( tr( "Show the Qt library's About box" ) );
305 // if ( !m_ownGraph ) { menuBar()->hide(); }
306
307 viewMenu = menuBar()->addMenu( tr( "&View" ) );
308}
309
310void GraphEditorWindow::createStatusBar() {
311 statusBar()->showMessage( tr( "Ready" ) );
312}
313
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 );
323 }
324 else { restoreGeometry( geometry ); }
325 settings.endGroup();
326}
327
328void GraphEditorWindow::writeSettings() {
329 QSettings settings( QCoreApplication::organizationName(), QCoreApplication::applicationName() );
330 settings.beginGroup( "nodegraph editor" );
331 settings.setValue( "geometry", saveGeometry() );
332 settings.endGroup();
333}
334
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,
340 tr( "Application" ),
341 tr( "The document has been modified.\n"
342 "Do you want to save your changes?" ),
343 QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel );
344 switch ( ret ) {
345 case QMessageBox::Save:
346 return save();
347 case QMessageBox::Cancel:
348 return false;
349 default:
350 break;
351 }
352
353 return true;
354}
355
356void GraphEditorWindow::loadFile( const QString& fileName ) {
357 QGuiApplication::setOverrideCursor( Qt::WaitCursor );
358 {
359 QFile file( fileName );
360 if ( !file.open( QFile::ReadOnly | QFile::Text ) ) {
361 QMessageBox::warning(
362 this,
363 tr( "Application" ),
364 tr( "Cannot read file %1:\n%2." )
365 .arg( QDir::toNativeSeparators( fileName ), file.errorString() ) );
366 return;
367 }
368 }
369
370 bool loaded( true );
371 m_graph->destroy();
372 loaded = m_graph->loadFromJson( fileName.toStdString() );
373
374 if ( !loaded ) {
375 QMessageBox::warning(
376 this,
377 tr( "Application" ),
378 tr( "Can't load graph from file %1.\n" ).arg( QDir::toNativeSeparators( fileName ) ) );
379 }
380
381 QGuiApplication::restoreOverrideCursor();
382 if ( loaded ) {
383 m_graph_model->sync_data();
384 m_view->centerScene();
385 setCurrentFile( fileName );
386 statusBar()->showMessage( tr( "File loaded" ), 2000 );
387
388 emit needUpdate();
389 }
390}
391
392bool GraphEditorWindow::saveFile( const QString& fileName ) {
393 QString errorMessage;
394
395 QGuiApplication::setOverrideCursor( Qt::WaitCursor );
397 // m_graph->compile();
398
399 m_graph->saveToJson( fileName.toStdString() );
400
401 QGuiApplication::restoreOverrideCursor();
402
403 if ( !errorMessage.isEmpty() ) {
404 QMessageBox::warning( this, tr( "Application" ), errorMessage );
405 return false;
406 }
407
408 setCurrentFile( fileName );
409 statusBar()->showMessage( tr( "File saved" ), 2000 );
410 return true;
411}
412
413void GraphEditorWindow::setCurrentFile( const QString& fileName ) {
414 m_curFile = fileName;
415
416 setWindowModified( false );
417
418 QString shownName = m_curFile;
419 if ( m_curFile.isEmpty() ) shownName = "untitled.flow";
420 setWindowFilePath( shownName );
421}
422
423QString GraphEditorWindow::strippedName( const QString& fullFileName ) {
424 return QFileInfo( fullFileName ).fileName();
425}
426} // namespace GraphEditor
427} // namespace QtGui
428} // namespace Dataflow
429} // namespace Ra
Base abstract class for all the nodes added and used by the node system.
Definition Node.hpp:40
const std::string & instance_name() const
Gets the instance name of the node.
Definition Node.hpp:447
const PortBaseInCollection & inputs() const
Gets the in ports of the node.
Definition Node.hpp:455
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.
Definition Node.hpp:195
const std::string & model_name() const
Gets the model (type/class) name of the node.
Definition Node.hpp:443
const PortBaseOutCollection & outputs() const
Gets the out ports of the node.
Definition Node.hpp:459
T make_shared(T... args)
T move(T... args)
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
Definition Cage.cpp:4
T dynamic_pointer_cast(T... args)
T sort(T... args)