├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── ZodiacGraph_Showcase.pro ├── collapsible.cpp ├── collapsible.h ├── doc └── res │ └── zodiac_logo.png ├── doxyfile ├── main.cpp ├── mainctrl.cpp ├── mainctrl.h ├── mainwindow.cpp ├── mainwindow.h ├── nodectrl.cpp ├── nodectrl.h ├── nodeproperties.cpp ├── nodeproperties.h ├── propertyeditor.cpp ├── propertyeditor.h ├── res ├── bucket.svg ├── icons.qrc ├── incoming.svg ├── minus.svg ├── outgoing.svg ├── play.svg ├── plus.svg ├── questionmark.svg └── zodiac_logo.png └── zodiacgraph ├── baseedge.cpp ├── baseedge.h ├── bezieredge.cpp ├── bezieredge.h ├── drawedge.cpp ├── drawedge.h ├── edgearrow.cpp ├── edgearrow.h ├── edgegroup.cpp ├── edgegroup.h ├── edgegroupinterface.cpp ├── edgegroupinterface.h ├── edgegrouppair.cpp ├── edgegrouppair.h ├── edgelabel.cpp ├── edgelabel.h ├── labeltextfactory.cpp ├── labeltextfactory.h ├── node.cpp ├── node.h ├── nodehandle.cpp ├── nodehandle.h ├── nodelabel.cpp ├── nodelabel.h ├── perimeter.cpp ├── perimeter.h ├── plug.cpp ├── plug.h ├── plugarranger.cpp ├── plugarranger.h ├── plugedge.cpp ├── plugedge.h ├── plughandle.cpp ├── plughandle.h ├── pluglabel.cpp ├── pluglabel.h ├── scene.cpp ├── scene.h ├── scenehandle.cpp ├── scenehandle.h ├── straightdoubleedge.cpp ├── straightdoubleedge.h ├── straightedge.cpp ├── straightedge.h ├── utils.h ├── view.cpp └── view.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | 19 | # Images 20 | *.png binary 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ========================= 2 | # Project-specific Files 3 | # ========================= 4 | 5 | # Documentation 6 | /doc/html 7 | 8 | # QtCreator user files 9 | *.pro.user 10 | 11 | # ========================= 12 | # Generated Files 13 | # ========================= 14 | 15 | # Compiled Object files 16 | *.slo 17 | *.lo 18 | *.o 19 | *.obj 20 | 21 | # Precompiled Headers 22 | *.gch 23 | *.pch 24 | 25 | # Compiled Dynamic libraries 26 | *.so 27 | *.dylib 28 | *.dll 29 | 30 | # Fortran module files 31 | *.mod 32 | 33 | # Compiled Static libraries 34 | *.lai 35 | *.la 36 | *.a 37 | *.lib 38 | 39 | # Executables 40 | *.exe 41 | *.out 42 | *.app 43 | 44 | # ========================= 45 | # Operating System Files 46 | # ========================= 47 | 48 | # OSX 49 | # ========================= 50 | 51 | .DS_Store 52 | .AppleDouble 53 | .LSOverride 54 | 55 | # Thumbnails 56 | ._* 57 | 58 | # Files that might appear on external disk 59 | .Spotlight-V100 60 | .Trashes 61 | 62 | # Directories potentially created on remote AFP share 63 | .AppleDB 64 | .AppleDesktop 65 | Network Trash Folder 66 | Temporary Items 67 | .apdisk 68 | 69 | # Windows 70 | # ========================= 71 | 72 | # Windows image file caches 73 | Thumbs.db 74 | ehthumbs.db 75 | 76 | # Folder config file 77 | Desktop.ini 78 | 79 | # Recycle Bin used on file shares 80 | $RECYCLE.BIN/ 81 | 82 | # Windows Installer files 83 | *.cab 84 | *.msi 85 | *.msm 86 | *.msp 87 | 88 | # Windows shortcuts 89 | *.lnk 90 | 91 | # Linux 92 | # ========================= 93 | 94 | # backup file 95 | *.~ 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Clemens Sielaff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | ZodiacGraph Teaser Video 4 | 5 | A general-purpose, circular node graph GUI using Qt 6 |

7 | 8 | # About 9 | The ZodiacGraph is a node graph user interface module with a unique visual and conceptual design. 10 | It is written in modern C++, using C++11 features, on top of the Qt framework. 11 | Sources are released under the MIT license. 12 | 13 | This repository contains the ZodiacGraph embedded in a showcase application to demonstrate the UI from a user's 14 | perspective as well as to serve as an example for using the code in your own project.
15 | The complete ZodiacGraph module is contained within the "zodiacgraph" subfolder and nested in the "zodiac" namespace. 16 | Code outside this folder is part of the showcase application and only serves demonstration purposes. 17 | 18 | The ZodiacGraph is a pure user user interface that does not contain any business logic in itself. 19 | Therefore, it should fit most use-cases that require a node graph-like GUI. 20 | However, to fully utilize its potential, the showcase application does contain two controller classes that connect the 21 | node graph UI elements to the rest of the application 22 | 23 | # Installation 24 | Building the code in this repository was tested on Windows and Linux (Ubuntu) with Qt 5.4. 25 | Other configurations should work -- please let me know if you had any errors on your system. 26 | 27 | ## Using Qt-Creator 28 | This should be easy.
29 | Pull the repo, open QtCreator and open the project file "ZodiacGraph_Showcase.pro" in QtCreator. 30 | After configuring the build directories, click the play button and after a few seconds the showcase app should open. 31 | 32 | ## Using qmake 33 | Since building the ZodiacGraph requires Qt anyway, why not make use of the excellent qmake?
34 | On windows, open the Qt command line to make sure that all Qt-libraries are available on the path, call 35 | vcvarsall.bat and define the target machine type (32 or 64 bit) and finally head over the local ZodiacGraph repo. 36 | Then simply run: 37 | ~~~~ 38 | qmake 39 | nmake release 40 | release\ZodiacGraph_Showcase.exe 41 | ~~~~ 42 | 43 | # Documentation 44 | All code in the "zodiacgraph" subfolder is fully documented with doxygen comments. 45 | To generate the ZodiacGraph html documentation, use the provided doxyfile or visit: 46 | http://www.clemens-sielaff.com/zodiacgraph-doc/ 47 | 48 | # Integration into your own project. 49 | Find below a short introduction on how to use the public interface. 50 | For a full example implementation, take a look at the showcase application. 51 | It demonstrates how to integrate the ZodiacGraph into a full-fledged 3rd party Qt application. 52 | 53 | Somewhere, for example in the constructor of your MainWidget, create the zodiac::Scene and zodiac::View: 54 | ~~~~ 55 | zodiac::Scene* zodiacScene = new zodiac::Scene(this); 56 | zodiac::View* zodiacView = new zodiac::View(this); 57 | zodiacView->setScene(zodiacScene); 58 | ~~~~ 59 | 60 | Use the zodiacScene object, to create a zodiac::SceneHandle, that will act as the main interface between your 61 | business logic and the ZodiacGraph UI: 62 | ~~~~ 63 | zodiac::SceneHandle sceneHandle = zodiac::SceneHandle(zodiacScene); 64 | ~~~~ 65 | 66 | SceneHandes, NodeHandles and PlugHandles are thin wrapper around a pointer to a zodiac::Scene, zodiac::Node and zodiac::Plug respectively. 67 | They expose only high-level functionality, while keeping the user from leaving the graph in an inconsistent state. 68 | They also do no imply ownership of the object pointed to, we leave that to Qt and the ZodiacGraph internal 69 | logic. 70 | This means, you are free to create, copy, store and delete handles without affecting the data.
71 | All handles are connected to the QObject::destroyed()-signal of the object they point to and will become 72 | invalid, once the reference object is destroyed. 73 | Calling functions on invalid handles will raise Q_ASSERT errors in debug builds and do nothing in release builds.
74 | See Note on the use of Handles at the end of this section, on the usage of handles in a multi-threaded UI 75 | environment. 76 | 77 | Next, use the sceneHandle to create two Nodes in the Scene: 78 | ~~~~ 79 | zodiac::NodeHandle fooNode = sceneHandle.createNode("Foo"); 80 | zodiac::NodeHandle barNode = sceneHandle.createNode("Bar"); 81 | ~~~~ 82 | 83 | Then, through the NodeHandles, create one Plug for each Node -- one incoming, the other outgoing: 84 | ~~~~ 85 | zodiac::PlugHandle outPlug = fooNode.createOutgoingPlug("fooOut"); 86 | zodiac::PlugHandle inPlug = barNode.createIncomingPlug("barIn"); 87 | ~~~~ 88 | 89 | Lastly, connect the two Plugs: 90 | ~~~~ 91 | outPlug.connectPlug(inPlug); 92 | ~~~~ 93 | 94 | And that's it! 95 | You've just created a simple node graph consisting of two Nodes with one Plug each and an Edge between them.
96 | You can find all other functionality of the zodiac::SceneHandle, zodiac::NodeHandle and zodiac::PlugHandle classes in 97 | the documentation. 98 | 99 | Note on the use of Handles:
100 | While the current implementation of handles works in the general case, it is far from being fool-proof. 101 | You should strife to organize your own code in such a way that you are never required to call isValid(), 102 | because you know which handles are valid at what point and when they become invalid. 103 | I must assume (even though I never encountered the problem), that using handles in a multi-threaded environment would 104 | be subject to race-conditions, where one handle causes the internal object to be destroyed and another handle to 105 | access it before the destroyed()-signal had a chance of being emitted.
106 | The alternative of using weak references and smart pointers would clash with the currently used model of ownership: 107 | Qt's parent-child mechanism. 108 | Changing this model requires a ground-up rewrite of the architecture and as this has been a non-issue for me so far, 109 | I feel save to postpone this issue for a future release 2.0. 110 | 111 | # Contribution 112 | The ZodiacGraph is a standalone UI module of a larger application under active development. 113 | As such, I expect the code to remain alive and updated regularly for a fair amount of time to come. 114 | 115 | Contributions through pull requests are welcome and encouraged! 116 | There are no official guidelines, but please try to be consistent with the existing code and make sure to update 117 | and create documentation for your changes. 118 | 119 | If you happen to encounter a bug or any missing / outdated / wrong documentation, please report it in the issues 120 | section. 121 | 122 | # Future Features (except probably not, see below) 123 | - Node Colors
124 | You can already alter the global appearance of the ZodiacGraph. 125 | One of the first next steps is to add the ability to assign colors to individual nodes or node-groups. 126 | - Node Groups
127 | Combines a sub-network of nodes into an new node that exposes loose ends as input and outputs. 128 | - Touch Navigation
129 | Version 1.0.0 already contains rudimentary touch handling but I haven't got the necessary hardware to deploy (and test) a 130 | serious touch-interface application. 131 | - Node Core Symbols
132 | In addition to Node Colors and instead (?) of a title, one should be able to assign an icon to a node to ease the 133 | understanding of its meaning in the graph. 134 | - Progress Animation
135 | An optional feature to use when the process described by a Node has a measurable progress and a defined end. 136 | In that case, the arrows of all outgoing edges could wander from their outgoing start plug towards their incoming 137 | end-plug to visualize the progress within the graph. 138 | 139 | # Disclaimer 140 | It's now 2017 and I haven't done any substantial work on the project for over two years, so I think it's fair to say that I probably won't implement any major changes any time soon. I relicensed the project under the MIT license to make it truly open-source and just gonna leave it out there for now. 141 | If you find the ZodiacGraph helpful or just nice to look at, I am always happy to hear feedback - especially if you end up using it (or some derivate of it) in one of your own projects! 142 | 143 | So long, 144 | -Clemens 145 | -------------------------------------------------------------------------------- /ZodiacGraph_Showcase.pro: -------------------------------------------------------------------------------- 1 | # 2 | # ZodiacGraph - A general-purpose, circular node graph UI module. 3 | # Copyright (C) 2015 Clemens Sielaff 4 | # 5 | # The MIT License 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | # this software and associated documentation files (the "Software"), to deal in 9 | # the Software without restriction, including without limitation the rights to 10 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | # of the Software, and to permit persons to whom the Software is furnished to do so, 12 | # subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | 26 | QT += core gui widgets 27 | CONFIG += c++11 static 28 | DEFINES *= QT_USE_QSTRINGBUILDER 29 | 30 | static { 31 | QT += svg 32 | } 33 | 34 | unix { 35 | QMAKE_CXX = ccache g++ 36 | } 37 | 38 | TARGET = ZodiacGraph_Showcase 39 | TEMPLATE = app 40 | 41 | 42 | SOURCES += main.cpp\ 43 | mainwindow.cpp \ 44 | collapsible.cpp \ 45 | mainctrl.cpp \ 46 | nodectrl.cpp \ 47 | nodeproperties.cpp \ 48 | propertyeditor.cpp \ 49 | zodiacgraph/baseedge.cpp \ 50 | zodiacgraph/bezieredge.cpp \ 51 | zodiacgraph/drawedge.cpp \ 52 | zodiacgraph/edgearrow.cpp \ 53 | zodiacgraph/edgegroup.cpp \ 54 | zodiacgraph/edgegroupinterface.cpp \ 55 | zodiacgraph/edgegrouppair.cpp \ 56 | zodiacgraph/edgelabel.cpp \ 57 | zodiacgraph/labeltextfactory.cpp \ 58 | zodiacgraph/node.cpp \ 59 | zodiacgraph/nodehandle.cpp \ 60 | zodiacgraph/nodelabel.cpp \ 61 | zodiacgraph/perimeter.cpp \ 62 | zodiacgraph/plug.cpp \ 63 | zodiacgraph/plugarranger.cpp \ 64 | zodiacgraph/plugedge.cpp \ 65 | zodiacgraph/plughandle.cpp \ 66 | zodiacgraph/pluglabel.cpp \ 67 | zodiacgraph/scene.cpp \ 68 | zodiacgraph/scenehandle.cpp \ 69 | zodiacgraph/straightdoubleedge.cpp \ 70 | zodiacgraph/straightedge.cpp \ 71 | zodiacgraph/view.cpp 72 | 73 | HEADERS += mainwindow.h \ 74 | collapsible.h \ 75 | mainctrl.h \ 76 | nodectrl.h \ 77 | nodeproperties.h \ 78 | propertyeditor.h \ 79 | zodiacgraph/baseedge.h \ 80 | zodiacgraph/bezieredge.h \ 81 | zodiacgraph/drawedge.h \ 82 | zodiacgraph/edgearrow.h \ 83 | zodiacgraph/edgegroup.h \ 84 | zodiacgraph/edgegroupinterface.h \ 85 | zodiacgraph/edgegrouppair.h \ 86 | zodiacgraph/edgelabel.h \ 87 | zodiacgraph/labeltextfactory.h \ 88 | zodiacgraph/node.h \ 89 | zodiacgraph/nodehandle.h \ 90 | zodiacgraph/nodelabel.h \ 91 | zodiacgraph/perimeter.h \ 92 | zodiacgraph/plug.h \ 93 | zodiacgraph/plugarranger.h \ 94 | zodiacgraph/plugedge.h \ 95 | zodiacgraph/plughandle.h \ 96 | zodiacgraph/pluglabel.h \ 97 | zodiacgraph/scene.h \ 98 | zodiacgraph/scenehandle.h \ 99 | zodiacgraph/straightdoubleedge.h \ 100 | zodiacgraph/straightedge.h \ 101 | zodiacgraph/utils.h \ 102 | zodiacgraph/view.h 103 | 104 | RESOURCES += \ 105 | res/icons.qrc 106 | -------------------------------------------------------------------------------- /collapsible.cpp: -------------------------------------------------------------------------------- 1 | #include "collapsible.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | const QString Collapsible::s_downArrow = QString::fromUtf8(" \xE2\x96\xBC "); 9 | const QString Collapsible::s_upArrow = QString::fromUtf8(" \xE2\x96\xB2 "); 10 | const int Collapsible::s_maxWidth = 400; 11 | 12 | Collapsible::Collapsible(QWidget *parent) 13 | : QFrame(parent) 14 | , m_mainLayout(new QVBoxLayout(this)) 15 | , m_displayWidget(nullptr) 16 | , m_titleButton(new QPushButton(this)) 17 | , m_title(QString()) 18 | { 19 | setFrameShape(QFrame::NoFrame); 20 | setMaximumWidth(s_maxWidth); 21 | setStyleSheet("Collapsible { border: 1px solid #808080; border-radius: 2px; }"); 22 | 23 | m_titleButton->setFlat(true); 24 | m_titleButton->setStatusTip("Click to collapse / expand the section."); 25 | connect(m_titleButton, SIGNAL(clicked()), this, SLOT(toggleCollapse())); 26 | 27 | m_mainLayout->setMargin(0); 28 | m_mainLayout->setSpacing(0); 29 | m_mainLayout->addWidget(m_titleButton); 30 | setLayout(m_mainLayout); 31 | } 32 | 33 | void Collapsible::setWidget(QWidget* displayWidget) 34 | { 35 | // remove any existing widget 36 | if(m_displayWidget){ 37 | m_mainLayout->removeWidget(m_displayWidget); 38 | m_displayWidget->deleteLater(); 39 | m_displayWidget = nullptr; 40 | } 41 | 42 | // take possession of the widget 43 | if(displayWidget->parent() != this){ 44 | displayWidget->setParent(this); 45 | } 46 | m_displayWidget = displayWidget; 47 | m_mainLayout->addWidget(m_displayWidget); 48 | } 49 | 50 | void Collapsible::toggleCollapse() 51 | { 52 | m_displayWidget->setHidden(!m_displayWidget->isHidden()); 53 | updateTitle(); 54 | } 55 | 56 | void Collapsible::updateTitle(const QString& title) 57 | { 58 | QFontMetrics fontMetrics = QFontMetrics(m_titleButton->font()); 59 | qreal arrowWidth = fontMetrics.boundingRect(s_downArrow).width(); 60 | 61 | if(!title.isEmpty()){ 62 | m_title = fontMetrics.elidedText(title, Qt::ElideMiddle, s_maxWidth - arrowWidth); 63 | } 64 | 65 | // display the correct arrow 66 | // ... or if there is no display widget yet, always the up arrow, because display widgets are opened by default 67 | if(m_displayWidget && m_displayWidget->isHidden()){ 68 | m_titleButton->setText(s_downArrow + m_title); 69 | } else { 70 | m_titleButton->setText(s_upArrow + m_title); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /collapsible.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | #ifndef COLLAPSIBLE_H 26 | #define COLLAPSIBLE_H 27 | 28 | #include 29 | 30 | class QPushButton; 31 | class QVBoxLayout; 32 | 33 | /// 34 | /// \brief Container class for a collapsible display widget and a title button. 35 | /// 36 | class Collapsible : public QFrame 37 | { 38 | Q_OBJECT 39 | 40 | public: // methods 41 | 42 | /// 43 | /// \brief Constructor. 44 | /// 45 | /// \param [in] parent Qt parent widget. 46 | /// 47 | Collapsible(QWidget *parent); 48 | 49 | /// 50 | /// \brief Defines a new widget to contain in this Collapsible. 51 | /// 52 | /// Takes possession of the widget and resets its parent to this. 53 | /// If the Collapsible already contains a widget, 54 | /// 55 | /// \param [in] displayWidget The widget to display / hide in this Collapsible. 56 | /// 57 | void setWidget(QWidget* displayWidget); 58 | 59 | public slots: 60 | 61 | /// 62 | /// \brief Updates the direction of the arrow next to the title as well as the title string. 63 | /// 64 | /// \param [in] title New title string. If undefinded, the current title is used. 65 | /// 66 | void updateTitle(const QString& title = 0); 67 | 68 | public: // static methods 69 | 70 | /// 71 | /// \brief Returns the maximum width of a Collapsible widget in pixels. 72 | /// 73 | /// \return The maximum width of a Collapsible widget in pixels. 74 | /// 75 | static int getMaximumWidth() {return s_maxWidth;} 76 | 77 | private slots: 78 | 79 | /// 80 | /// \brief Toggles the display of the collapsible widget. 81 | /// 82 | void toggleCollapse(); 83 | 84 | private: // members 85 | 86 | /// 87 | /// \brief The layout stacking the collapsible title and the widget. 88 | /// 89 | QVBoxLayout* m_mainLayout; 90 | 91 | /// 92 | /// \brief The widget contained and displayed / hidden in this Collapsible. 93 | /// 94 | QWidget* m_displayWidget; 95 | 96 | /// 97 | /// \brief Title button of the Collapsible. 98 | /// 99 | QPushButton* m_titleButton; 100 | 101 | /// 102 | /// \brief Title of this Collapsible widget. 103 | /// 104 | QString m_title; 105 | 106 | private : // static members 107 | 108 | /// 109 | /// \brief Character sequence of the downward, "expansion" arrow in front of the tile. 110 | /// 111 | static const QString s_downArrow; 112 | 113 | /// 114 | /// \brief Character sequence of the upward, "collapse" arrow in front of the tile. 115 | /// 116 | static const QString s_upArrow; 117 | 118 | /// 119 | /// \brief Maximum width of a Collapsible in pixels. 120 | /// 121 | static const int s_maxWidth; 122 | }; 123 | 124 | #endif // COLLAPSIBLE_H 125 | -------------------------------------------------------------------------------- /doc/res/zodiac_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clemenssielaff/ZodiacGraph/c6a5e30d8ddb17463c52e987f4489ba55c728dc3/doc/res/zodiac_logo.png -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | /// 6 | /// \brief Main function of this application. 7 | /// 8 | /// \param [in] argc The number of strings pointed to by argv. 9 | /// \param [in] argv Name of the programm + misc. 10 | /// 11 | /// \return 0 if the application ran successfully, otherwise an error code. 12 | /// 13 | int main(int argc, char *argv[]) 14 | { 15 | // create application 16 | QApplication app(argc, argv); 17 | app.setOrganizationName("clemens-sielaff"); 18 | app.setOrganizationDomain("www.clemens-sielaff.com"); 19 | app.setApplicationName("ZodiacGraph_ExampleApp"); 20 | 21 | // create the main window and enter the main execution loop 22 | MainWindow window; 23 | window.show(); 24 | int result = app.exec(); 25 | 26 | return result; 27 | } 28 | -------------------------------------------------------------------------------- /mainctrl.cpp: -------------------------------------------------------------------------------- 1 | #include "mainctrl.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "nodectrl.h" 7 | #include "propertyeditor.h" 8 | #include "zodiacgraph/nodehandle.h" 9 | 10 | QString MainCtrl::s_defaultName = "Node "; 11 | 12 | MainCtrl::MainCtrl(QObject *parent, zodiac::Scene* scene, PropertyEditor* propertyEditor) 13 | : QObject(parent) 14 | , m_scene(zodiac::SceneHandle(scene)) 15 | , m_propertyEditor(propertyEditor) 16 | , m_nodes(QHash()) 17 | , m_nodeIndex(1) // name suffixes start at 1 18 | { 19 | m_propertyEditor->setMainCtrl(this); 20 | 21 | connect(&m_scene, SIGNAL(selectionChanged(QList)), 22 | this, SLOT(selectionChanged(QList))); 23 | } 24 | 25 | NodeCtrl* MainCtrl::createNode(const QString& name) 26 | { 27 | // the newly created Node is the only selected one to avoid confusion 28 | m_scene.deselectAll(); 29 | 30 | // use the given name or construct a default one 31 | QString nodeName = name; 32 | if(nodeName.isEmpty()){ 33 | nodeName = s_defaultName + QString::number(m_nodeIndex++); 34 | } 35 | 36 | // create the node 37 | NodeCtrl* nodeCtrl = new NodeCtrl(this, m_scene.createNode(nodeName)); 38 | m_nodes.insert(nodeCtrl->getNodeHandle(), nodeCtrl); 39 | 40 | return nodeCtrl; 41 | } 42 | 43 | bool MainCtrl::deleteNode(NodeCtrl* node) 44 | { 45 | #ifdef QT_DEBUG 46 | Q_ASSERT(m_nodes.contains(node->getNodeHandle())); 47 | #else 48 | if(!m_nodes.contains(node->getNodeHandle())){ 49 | return false; 50 | } 51 | #endif 52 | 53 | if(!node->isRemovable()){ 54 | // nodes with connections cannot be deleted 55 | return false; 56 | } 57 | 58 | // disconnect and delete the node 59 | node->disconnect(); 60 | zodiac::NodeHandle handle = node->getNodeHandle(); 61 | m_nodes.remove(handle); 62 | bool result = handle.remove(); 63 | Q_ASSERT(result); 64 | return result; 65 | } 66 | 67 | void MainCtrl::printZodiacScene() 68 | { 69 | QList allNodes = m_nodes.keys(); 70 | for(zodiac::NodeHandle node : allNodes){ 71 | int number = node.getName().right(2).trimmed().toInt(); 72 | QString nodeCtrl = "nodeCtrl" + QString::number(number); 73 | QPointF pos = node.getPos(); 74 | 75 | qDebug() << "NodeCtrl* nodeCtrl" + QString::number(number) + " = mainCtrl->createNode(\"" + node.getName() + "\");"; 76 | qDebug() << nodeCtrl + "->getNodeHandle().setPos(" + QString::number(pos.x()) + ", " + QString::number(pos.y()) + ");"; 77 | 78 | for(zodiac::PlugHandle plug : node.getPlugs()){ 79 | if(plug.isIncoming()){ 80 | qDebug() << nodeCtrl + "->addIncomingPlug(\"" + plug.getName() + "\");"; 81 | } else { 82 | qDebug() << nodeCtrl + "->addOutgoingPlug(\"" + plug.getName() + "\");"; 83 | } 84 | } 85 | 86 | qDebug() << ""; // newline 87 | } 88 | 89 | for(zodiac::NodeHandle node : allNodes){ 90 | int number = node.getName().right(2).trimmed().toInt(); 91 | QString nodeCtrl = "nodeCtrl" + QString::number(number); 92 | for(zodiac::PlugHandle plug : node.getPlugs()){ 93 | if(plug.isIncoming()) continue; 94 | for(zodiac::PlugHandle otherPlug : plug.getConnectedPlugs()){ 95 | int otherNumber = otherPlug.getNode().getName().right(2).trimmed().toInt(); 96 | QString otherNodeCtrl = "nodeCtrl" + QString::number(otherNumber); 97 | qDebug() << nodeCtrl + "->getNodeHandle().getPlug(\"" + plug.getName() + "\").connectPlug(" + otherNodeCtrl + "->getNodeHandle().getPlug(\"" + otherPlug.getName() + "\"));"; 98 | } 99 | } 100 | } 101 | } 102 | 103 | bool MainCtrl::shutdown() 104 | { 105 | // do not receive any more signals from the scene handle 106 | m_scene.disconnect(); 107 | 108 | return true; 109 | } 110 | 111 | 112 | void MainCtrl::createDefaultNode() 113 | { 114 | NodeCtrl* newNode = createNode(); 115 | 116 | // int plugCount = (qreal(qrand())/qreal(RAND_MAX))*12; 117 | // for(int i = 0; i < plugCount + 4; ++i){ 118 | // if((qreal(qrand())/qreal(RAND_MAX))<0.5){ 119 | // newNode->addIncomingPlug("plug"); 120 | // } else { 121 | // newNode->addOutgoingPlug("plug"); 122 | // } 123 | // } 124 | 125 | newNode->setSelected(true); 126 | } 127 | 128 | void MainCtrl::selectionChanged(QList selection) 129 | { 130 | m_propertyEditor->showNodes(selection); 131 | } 132 | -------------------------------------------------------------------------------- /mainctrl.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef NODEMANAGER_H 27 | #define NODEMANAGER_H 28 | 29 | #include 30 | #include 31 | 32 | #include "zodiacgraph/nodehandle.h" 33 | #include "zodiacgraph/scenehandle.h" 34 | 35 | class NodeCtrl; 36 | class PropertyEditor; 37 | namespace zodiac { 38 | class Scene; 39 | } 40 | 41 | /// 42 | /// \brief Controller Manager managing all NodeCtrls. 43 | /// 44 | /// This is the main class for controlling the business logic of the application. 45 | /// 46 | class MainCtrl : public QObject 47 | { 48 | Q_OBJECT 49 | 50 | public: // methods 51 | 52 | /// 53 | /// \brief Constructor. 54 | /// 55 | /// \param [in] parent Qt parent. 56 | /// \param [in] scene Handle of a zodiac::Scene. 57 | /// \param [in] propertyEditor Property editor. 58 | /// 59 | explicit MainCtrl(QObject *parent, zodiac::Scene* scene, PropertyEditor* propertyEditor); 60 | 61 | /// 62 | /// \brief Creates a new node in the graph. 63 | /// 64 | /// \param [in] name Name of the new node or empty to create a default name. 65 | /// 66 | /// \return Control for new node. 67 | /// 68 | NodeCtrl* createNode(const QString& name = ""); 69 | 70 | /// 71 | /// \brief Deletes an existing node from the graph. 72 | /// 73 | /// \param [in] node Node to delete. 74 | /// 75 | /// \return true if the node was removed -- false otherwise. 76 | /// 77 | bool deleteNode(NodeCtrl* node); 78 | 79 | /// 80 | /// \brief Returns the NodeCtrl that manages a given NodeHandle. 81 | /// 82 | /// If there is no corresponding NodeCtrl known to this manager, the nullptr is returned. 83 | /// 84 | /// \param [in] handle NodeHandle for which to find the corresponding NodeCtrl. 85 | /// 86 | /// \return Corresponding NodeCtrl or the nullptr if none was found. 87 | /// 88 | inline NodeCtrl* getCtrlForHandle(zodiac::NodeHandle handle) const { return m_nodes.value(handle, nullptr); } 89 | 90 | /// 91 | /// \brief Prints the current state of the zodiac::Scene. 92 | /// 93 | /// This is not a serialized form, even though it might in the future extend to be used in such a fashion. 94 | /// For now, it is used for helping me creating the initial "ZODIAC"-logo setup. 95 | /// 96 | void printZodiacScene(); 97 | 98 | /// 99 | /// \brief Must be called before closing the application. 100 | /// 101 | /// \return true if the manager was shut down successfully -- false if the user objected. 102 | /// 103 | bool shutdown(); 104 | 105 | public slots: 106 | 107 | /// 108 | /// \brief Creates a new node in the graph selects and activates it. 109 | /// 110 | void createDefaultNode(); 111 | 112 | private slots: 113 | 114 | /// 115 | /// \brief Called when the selection in the managed scene has changed. 116 | /// 117 | /// \param [in] selection Handles to all selected nodes. 118 | /// 119 | void selectionChanged(QList selection); 120 | 121 | private: // members 122 | 123 | /// 124 | /// \brief Handle to the zodiac::Scene representing the graph. 125 | /// 126 | zodiac::SceneHandle m_scene; 127 | 128 | /// 129 | /// \brief The Property Editor widget. 130 | /// 131 | PropertyEditor* m_propertyEditor; 132 | 133 | /// 134 | /// \brief NodeCtrls managed by this manager (but deleted through Qt). 135 | /// 136 | QHash m_nodes; 137 | 138 | /// 139 | /// \brief Ever increasing index value for default names of the nodes in this manager. 140 | /// 141 | uint m_nodeIndex; 142 | 143 | private: // static members 144 | 145 | /// 146 | /// \brief Default node name. "Node_" will result in a default name of "Node_12" for example. 147 | /// 148 | static QString s_defaultName; 149 | 150 | }; 151 | 152 | #endif // NODEMANAGER_H 153 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef MAINWINDOW_H 27 | #define MAINWINDOW_H 28 | 29 | /// \file mainwindow.h 30 | /// 31 | /// \brief Documentation for the mainwindow file. 32 | /// 33 | /// 34 | 35 | #include 36 | 37 | class MainCtrl; 38 | class PropertyEditor; 39 | class QSplitter; 40 | 41 | /// 42 | /// \brief A single instance of this class contains all other widgets of the application. 43 | /// 44 | /// It also contains the node Manager that in turn controls the business logic. 45 | /// 46 | class MainWindow : public QMainWindow 47 | { 48 | Q_OBJECT 49 | 50 | public: // methods 51 | 52 | /// 53 | /// \brief Constructor. 54 | /// 55 | /// \param parent Qt parent. 56 | /// 57 | MainWindow(QWidget *parent=0); 58 | 59 | protected: // methods 60 | 61 | /// 62 | /// \brief Called upon closing. 63 | /// 64 | /// \param event Qt event. 65 | /// 66 | void closeEvent(QCloseEvent* event); 67 | 68 | private slots: 69 | 70 | /// 71 | /// \brief Displays the About-window of the application. 72 | /// 73 | void displayAbout(); 74 | 75 | private: // methods 76 | 77 | /// 78 | /// \brief Reads GUI settings stored by QSettings. 79 | /// 80 | /// In Ubuntu the location of the settings file is: ~/.config/clemens-sielaff/ZodiacGraph_ExampleApp.ini 81 | /// In Windows the location of the sittings file is: %APPDATA%\Roaming\clemens-sielaff\ZodiacGraph_ExampleApp.ini 82 | /// 83 | void readSettings(); 84 | 85 | /// 86 | /// \brief Writes out GUI settings for the next instance of the application to read. 87 | /// 88 | /// For details, see readSettings(). 89 | /// 90 | void writeSettings(); 91 | 92 | private: // members 93 | 94 | /// 95 | /// \brief Main controller used for controlling the nodes (both visual and logical) of the graph. 96 | /// 97 | MainCtrl* m_mainCtrl; 98 | 99 | /// 100 | /// \brief Main splitter between the Zodiac Graph and the Property editor. 101 | /// 102 | QSplitter* m_mainSplitter; 103 | 104 | }; 105 | 106 | #endif // MAINWINDOW_H 107 | -------------------------------------------------------------------------------- /nodectrl.cpp: -------------------------------------------------------------------------------- 1 | #include "nodectrl.h" 2 | 3 | #include "mainctrl.h" 4 | 5 | typedef zodiac::PlugHandle PlugHandle; 6 | 7 | NodeCtrl::NodeCtrl(MainCtrl* manager, zodiac::NodeHandle node) 8 | : QObject(manager) 9 | , m_manager(manager) 10 | , m_node(node) 11 | , m_plugs(QHash>()) 12 | { 13 | // connect node signals 14 | connect(&m_node, SIGNAL(removalRequested()), this, SLOT(remove())); 15 | connect(&m_node, SIGNAL(inputConnected(zodiac::PlugHandle, zodiac::PlugHandle)), 16 | this, SLOT(inputConnected(zodiac::PlugHandle, zodiac::PlugHandle))); 17 | connect(&m_node, SIGNAL(outputConnected(zodiac::PlugHandle, zodiac::PlugHandle)), 18 | this, SLOT(outputConnected(zodiac::PlugHandle, zodiac::PlugHandle))); 19 | connect(&m_node, SIGNAL(inputDisconnected(zodiac::PlugHandle, zodiac::PlugHandle)), 20 | this, SLOT(inputDisconnected(zodiac::PlugHandle, zodiac::PlugHandle))); 21 | connect(&m_node, SIGNAL(outputDisconnected(zodiac::PlugHandle, zodiac::PlugHandle)), 22 | this, SLOT(outputDisconnected(zodiac::PlugHandle, zodiac::PlugHandle))); 23 | } 24 | 25 | void NodeCtrl::rename(const QString& name) 26 | { 27 | m_node.rename(name); 28 | } 29 | 30 | QString NodeCtrl::renamePlug(const QString& oldName, const QString& newName) 31 | { 32 | if(newName == oldName){ 33 | return oldName; 34 | } 35 | 36 | PlugHandle plug = m_node.getPlug(oldName); 37 | if(!plug.isValid()){ 38 | // return the empty string if there is no plug by the given name 39 | return ""; 40 | } 41 | 42 | // disconnect all connected plugs 43 | if(plug.isIncoming()){ 44 | for(PlugHandle otherPlug : m_plugs.value(plug)){ 45 | m_manager->getCtrlForHandle(otherPlug.getNode())->outputDisconnected(otherPlug, plug); 46 | } 47 | } else { 48 | for(PlugHandle otherPlug : m_plugs.value(plug)){ 49 | m_manager->getCtrlForHandle(otherPlug.getNode())->inputDisconnected(otherPlug, plug); 50 | } 51 | } 52 | 53 | // rename the plug 54 | QString actualName = plug.rename(newName); 55 | 56 | // reconnect other plugs 57 | if(plug.isIncoming()){ 58 | for(PlugHandle otherPlug : m_plugs.value(plug)){ 59 | m_manager->getCtrlForHandle(otherPlug.getNode())->outputConnected(otherPlug, plug); 60 | } 61 | } else { 62 | for(PlugHandle otherPlug : m_plugs.value(plug)){ 63 | m_manager->getCtrlForHandle(otherPlug.getNode())->inputConnected(otherPlug, plug); 64 | } 65 | } 66 | 67 | return actualName; 68 | } 69 | 70 | bool NodeCtrl::togglePlugDirection(const QString& name) 71 | { 72 | PlugHandle plug = m_node.getPlug(name); 73 | if(!plug.isValid() || !plug.toggleDirection()){ 74 | return false; 75 | } 76 | Q_ASSERT(m_plugs.contains(plug)); 77 | Q_ASSERT(m_plugs[plug].size() == 0); 78 | 79 | return true; 80 | } 81 | 82 | bool NodeCtrl::removePlug(const QString& name) 83 | { 84 | // get the plug that is about to be removed 85 | PlugHandle plug = m_node.getPlug(name); 86 | if(!plug.isValid() || !plug.isRemovable()){ 87 | return false; 88 | } 89 | 90 | // remove all references to the plug 91 | Q_ASSERT(m_plugs.contains(plug)); 92 | Q_ASSERT(m_plugs[plug].size() == 0); 93 | m_plugs.remove(plug); 94 | 95 | // remove the plug 96 | bool result = plug.remove(); 97 | Q_ASSERT(result); 98 | return result; 99 | } 100 | 101 | void NodeCtrl::setSelected(bool isSelected) 102 | { 103 | m_node.setSelected(isSelected); 104 | } 105 | 106 | bool NodeCtrl::remove() 107 | { 108 | return m_manager->deleteNode(this); 109 | } 110 | 111 | zodiac::PlugHandle NodeCtrl::addPlug(const QString& name, bool incoming) 112 | { 113 | PlugHandle newPlug; 114 | if(incoming){ 115 | newPlug = m_node.createIncomingPlug(name); 116 | } else { 117 | newPlug = m_node.createOutgoingPlug(name); 118 | } 119 | Q_ASSERT(newPlug.isValid()); 120 | m_plugs.insert(newPlug, QList()); 121 | return newPlug; 122 | } 123 | 124 | void NodeCtrl::inputConnected(PlugHandle myInput, PlugHandle otherOutput) 125 | { 126 | m_plugs[myInput].append(otherOutput); 127 | } 128 | 129 | void NodeCtrl::outputConnected(PlugHandle myOutput, PlugHandle otherInput) 130 | { 131 | m_plugs[myOutput].append(otherInput); 132 | } 133 | 134 | void NodeCtrl::inputDisconnected(PlugHandle myInput, PlugHandle otherOutput) 135 | { 136 | m_plugs[myInput].removeOne(otherOutput); 137 | Q_ASSERT(m_plugs[myInput].count(otherOutput) == 0); 138 | } 139 | 140 | void NodeCtrl::outputDisconnected(PlugHandle myOutput, PlugHandle otherInput) 141 | { 142 | m_plugs[myOutput].removeOne(otherInput); 143 | Q_ASSERT(m_plugs[myOutput].count(otherInput) == 0); 144 | } 145 | -------------------------------------------------------------------------------- /nodeproperties.cpp: -------------------------------------------------------------------------------- 1 | #include "nodeproperties.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "collapsible.h" 10 | #include "nodectrl.h" 11 | 12 | QString NodeProperties::s_defaultPlugName = "plug"; 13 | 14 | NodeProperties::NodeProperties(NodeCtrl *node, Collapsible *parent) 15 | : QWidget(parent) 16 | , m_node(node) 17 | , m_nextPlugIsIncoming(true) 18 | { 19 | // define the main layout 20 | QVBoxLayout* mainLayout = new QVBoxLayout(this); 21 | mainLayout->setContentsMargins(2,2,2,2); 22 | mainLayout->setSpacing(2); 23 | setLayout(mainLayout); 24 | 25 | // update the title of the collapsible container 26 | parent->updateTitle(m_node->getName()); 27 | 28 | // define the name edit 29 | QHBoxLayout* nameLayout = new QHBoxLayout(); 30 | m_nameEdit = new QLineEdit(m_node->getName(), this); 31 | connect(m_nameEdit, SIGNAL(editingFinished()), this, SLOT(renameNode())); 32 | nameLayout->addWidget(new QLabel("Name", this)); 33 | nameLayout->addWidget(m_nameEdit); 34 | nameLayout->setContentsMargins(0, 4, 0, 0); 35 | mainLayout->addLayout(nameLayout); 36 | 37 | // define the add plug button 38 | m_plugLayout = new QGridLayout(); 39 | m_plugLayout->setContentsMargins(0, 8, 0, 0); // leave space between the plug list and the name 40 | m_plugLayout->setColumnStretch(1,1); // so the add-plug button always stays on the far right 41 | m_addPlugButton = new QPushButton(this); 42 | m_addPlugButton->setIconSize(QSize(8, 8)); 43 | m_addPlugButton->setIcon(QIcon(":/icons/plus.svg")); 44 | m_addPlugButton->setFlat(true); 45 | m_plugLayout->addWidget(new QLabel("Plugs", this), 0, 0, 1, 2, Qt::AlignLeft); 46 | m_plugLayout->addWidget(m_addPlugButton, 0, 2); 47 | connect(m_addPlugButton, SIGNAL(pressed()), this, SLOT(createNewPlug())); 48 | 49 | // define the plugs 50 | for(zodiac::PlugHandle& plug : m_node->getPlugHandles()){ 51 | addPlugRow(plug); 52 | } 53 | mainLayout->addLayout(m_plugLayout); 54 | } 55 | 56 | void NodeProperties::renameNode() 57 | { 58 | QString newName = m_nameEdit->text(); 59 | if(m_node->getName() == newName){ 60 | return; 61 | } 62 | m_node->rename(newName); 63 | qobject_cast(parent())->updateTitle(newName); 64 | } 65 | 66 | void NodeProperties::createNewPlug() 67 | { 68 | // duplicate plug names are automatically resolved by the zodiac::Node 69 | if(m_nextPlugIsIncoming){ 70 | addPlugRow(m_node->addIncomingPlug(s_defaultPlugName)); 71 | } else { 72 | addPlugRow(m_node->addOutgoingPlug(s_defaultPlugName)); 73 | } 74 | m_nextPlugIsIncoming = !m_nextPlugIsIncoming; 75 | } 76 | 77 | void NodeProperties::addPlugRow(zodiac::PlugHandle plug) 78 | { 79 | int row = m_plugLayout->rowCount(); 80 | 81 | QPushButton* directionButton = new QPushButton(this); 82 | directionButton->setIconSize(QSize(16, 16)); 83 | directionButton->setFlat(true); 84 | directionButton->setStatusTip("Toggle the direction of the Plug from 'incoming' to 'outoing' and vice versa."); 85 | m_plugLayout->addWidget(directionButton, row, 0); 86 | 87 | QLineEdit* plugNameEdit = new QLineEdit(plug.getName(), this); 88 | m_plugLayout->addWidget(plugNameEdit, row, 1); 89 | 90 | QPushButton* removalButton = new QPushButton(this); 91 | removalButton->setIcon(QIcon(":/icons/minus.svg")); 92 | removalButton->setIconSize(QSize(8, 8)); 93 | removalButton->setFlat(true); 94 | removalButton->setStatusTip("Delete the Plug from its Node"); 95 | m_plugLayout->addWidget(removalButton, row, 2); 96 | 97 | m_plugRows.insert(plug.getName(), new PlugRow(this, plug, plugNameEdit, directionButton, removalButton)); 98 | } 99 | 100 | void NodeProperties::removePlugRow(const QString& plugName) 101 | { 102 | Q_ASSERT(m_plugRows.contains(plugName)); 103 | m_plugRows.remove(plugName); 104 | } 105 | 106 | 107 | PlugRow::PlugRow(NodeProperties* editor, zodiac::PlugHandle plug, 108 | QLineEdit* nameEdit, QPushButton* directionToggle, QPushButton* removalButton) 109 | : QObject(editor) 110 | , m_editor(editor) 111 | , m_plug(plug) 112 | , m_nameEdit(nameEdit) 113 | , m_directionToggle(directionToggle) 114 | , m_removalButton(removalButton) 115 | { 116 | connect(m_nameEdit, SIGNAL(editingFinished()), this, SLOT(renamePlug())); 117 | connect(m_directionToggle, SIGNAL(clicked()), this, SLOT(togglePlugDirection())); 118 | connect(m_removalButton, SIGNAL(clicked()), this, SLOT(removePlug())); 119 | 120 | updateDirectionIcon(); 121 | } 122 | 123 | void PlugRow::renamePlug() 124 | { 125 | m_nameEdit->setText(m_editor->getNode()->renamePlug(m_plug.getName(), m_nameEdit->text())); 126 | } 127 | 128 | 129 | void PlugRow::updateDirectionIcon() 130 | { 131 | if(m_plug.isIncoming()){ 132 | m_directionToggle->setIcon(QIcon(":/icons/incoming.svg")); 133 | } else { 134 | m_directionToggle->setIcon(QIcon(":/icons/outgoing.svg")); 135 | } 136 | } 137 | 138 | void PlugRow::togglePlugDirection() 139 | { 140 | if(!m_editor->getNode()->togglePlugDirection(m_plug.getName())){ 141 | return; 142 | } 143 | updateDirectionIcon(); 144 | } 145 | 146 | void PlugRow::removePlug() 147 | { 148 | // do nothing, if the plug cannot be removed 149 | if(!m_plug.isRemovable()){ 150 | return; 151 | } 152 | 153 | // unregister from the editor 154 | m_editor->removePlugRow(m_plug.getName()); 155 | 156 | // remove widgets from the editor 157 | QGridLayout* plugLayout = m_editor->getPlugLayout(); 158 | plugLayout->removeWidget(m_directionToggle); 159 | plugLayout->removeWidget(m_nameEdit); 160 | plugLayout->removeWidget(m_removalButton); 161 | 162 | // delete the widgets, they are no longer needed 163 | m_directionToggle->deleteLater(); 164 | m_nameEdit->deleteLater(); 165 | m_removalButton->deleteLater(); 166 | 167 | // finally, remove the plug from the logical node 168 | m_editor->getNode()->removePlug(m_plug.getName()); 169 | } 170 | -------------------------------------------------------------------------------- /nodeproperties.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef NODEPROPERTIES_H 27 | #define NODEPROPERTIES_H 28 | 29 | #include 30 | #include 31 | 32 | #include "zodiacgraph/nodehandle.h" 33 | #include "zodiacgraph/plughandle.h" 34 | 35 | class QGridLayout; 36 | class QLineEdit; 37 | class QPushButton; 38 | 39 | class Collapsible; 40 | class NodeCtrl; 41 | class PlugRow; 42 | 43 | /// 44 | /// \brief Node Property widget, is a display widget of a Collapsible. 45 | /// 46 | class NodeProperties : public QWidget 47 | { 48 | Q_OBJECT 49 | 50 | /// 51 | /// \brief The PlugRow class is a logical part of the this one, but has a 1-n relationship. 52 | /// 53 | friend class PlugRow; 54 | 55 | public: // methods 56 | 57 | /// 58 | /// \brief Constructor. 59 | /// 60 | /// \param [in] node Node whose properties to display. 61 | /// \param [in] parent Collapsible parent object. 62 | /// 63 | explicit NodeProperties(NodeCtrl* node, Collapsible *parent); 64 | 65 | private: // for friend 66 | 67 | /// 68 | /// \brief The controller of the node whose properties are displayed in this NodeProperties widget. 69 | /// 70 | /// \return Controller of the managed node. 71 | /// 72 | NodeCtrl* getNode() const {return m_node;} 73 | 74 | /// 75 | /// \brief The layout to be used by PlugRows to place their widgets. 76 | /// 77 | /// \return The layout of the widgets related to the plugs of the node. 78 | /// 79 | QGridLayout* getPlugLayout() const {return m_plugLayout;} 80 | 81 | /// 82 | /// \brief Removes a plug from the node and the PlugRow from the editor. 83 | /// 84 | /// \param [in] plugName Name of the plug to remove. 85 | /// 86 | void removePlugRow(const QString& plugName); 87 | 88 | private slots: 89 | 90 | /// 91 | /// \brief Called by the name edit, when the name of the node was changed through user input. 92 | /// 93 | void renameNode(); 94 | 95 | /// 96 | /// \brief Called by pressing the add-plug button. 97 | /// 98 | void createNewPlug(); 99 | 100 | /// 101 | /// \brief Creates a new entry in the plug list of this property editor alongside its PlugRow. 102 | /// 103 | void addPlugRow(zodiac::PlugHandle plug); 104 | 105 | private: // members 106 | 107 | /// 108 | /// \brief Controller of the edited node. 109 | /// 110 | NodeCtrl* m_node; 111 | 112 | /// 113 | /// \brief Node name edit. 114 | /// 115 | QLineEdit* m_nameEdit; 116 | 117 | /// 118 | /// \brief Layout of the widgets related to the plugs of the node. 119 | /// 120 | QGridLayout* m_plugLayout; 121 | 122 | /// 123 | /// \brief Button to add a new Plug to the node. 124 | /// 125 | QPushButton* m_addPlugButton; 126 | 127 | /// 128 | /// \brief All PlugRows contained in this editor. 129 | /// 130 | QHash m_plugRows; 131 | 132 | /// 133 | /// \brief Hitting the add-Plug button creates incoming and outgoing Plug%s alternately. 134 | /// 135 | /// This flag keeps track of what is next. 136 | /// 137 | bool m_nextPlugIsIncoming; 138 | 139 | private: // static members 140 | 141 | /// 142 | /// \brief Default plug name. 143 | /// 144 | static QString s_defaultPlugName; 145 | 146 | }; 147 | 148 | 149 | /// 150 | /// \brief An extension to the NodeProperties class, responsible to manage a single row of Plug-related widgets. 151 | /// 152 | class PlugRow : public QObject 153 | { 154 | Q_OBJECT 155 | 156 | public: // methods 157 | 158 | /// 159 | /// \brief Constructor. 160 | /// 161 | /// \param [in] editor NodeProperties that this PlugRow is part of. 162 | /// \param [in] plug Handle of the plug whose name to edit / display. 163 | /// \param [in] nameEdit Plug name edit. 164 | /// \param [in] directionToggle Plug-direction toggle button. 165 | /// \param [in] removalButton Plug-removal button. 166 | /// 167 | PlugRow(NodeProperties *editor, zodiac::PlugHandle plug, 168 | QLineEdit *nameEdit, QPushButton *directionToggle, QPushButton *removalButton); 169 | 170 | private slots: 171 | 172 | /// 173 | /// \brief Called when the name of the plug was changed through user input. 174 | /// 175 | void renamePlug(); 176 | 177 | /// 178 | /// \brief Called when the toggle-Plug-direction button is pressed. 179 | /// 180 | void togglePlugDirection(); 181 | 182 | /// 183 | /// \brief Called when the Plug-removal button is pressed. 184 | /// 185 | void removePlug(); 186 | 187 | private: // methods 188 | 189 | /// 190 | /// \brief Single access point for setting the direction icon. 191 | /// 192 | void updateDirectionIcon(); 193 | 194 | private: // members 195 | 196 | /// 197 | /// \brief Controller of the edited node. 198 | /// 199 | NodeProperties* m_editor; 200 | 201 | /// 202 | /// \brief Handle of the plug whose name to edit / display. 203 | /// 204 | zodiac::PlugHandle m_plug; 205 | 206 | /// 207 | /// \brief Plug name edit. 208 | /// 209 | QLineEdit* m_nameEdit; 210 | 211 | /// 212 | /// \brief Plug-direction toggle button. 213 | /// 214 | QPushButton* m_directionToggle; 215 | 216 | /// 217 | /// \brief Plug-removal button. 218 | /// 219 | QPushButton* m_removalButton; 220 | }; 221 | 222 | #endif // NODEPROPERTIES_H 223 | -------------------------------------------------------------------------------- /propertyeditor.cpp: -------------------------------------------------------------------------------- 1 | #include "propertyeditor.h" 2 | 3 | #include 4 | 5 | #include "collapsible.h" 6 | #include "nodeproperties.h" 7 | #include "mainctrl.h" 8 | 9 | PropertyEditor::PropertyEditor(QWidget *parent) 10 | : QScrollArea(parent) 11 | , m_mainCtrl(nullptr) 12 | { 13 | // setup the scroll area 14 | setFrameShape(QFrame::NoFrame); 15 | setWidgetResizable(true); 16 | setMaximumWidth(Collapsible::getMaximumWidth()+4); 17 | 18 | // set up the view widget 19 | QWidget* viewWidget = new QWidget(this); 20 | setWidget(viewWidget); 21 | 22 | // ... and the layout of the view widget 23 | m_layout = new QVBoxLayout(viewWidget); 24 | m_layout->setContentsMargins(QMargins(4,0,0,0)); 25 | m_layout->addStretch(); 26 | } 27 | 28 | void PropertyEditor::showNodes(const QList& selection) 29 | { 30 | Q_ASSERT(m_mainCtrl); 31 | 32 | // collect all collapsibles to remove 33 | QVector removed; 34 | for(QHash::iterator i = m_nodes.begin(); i != m_nodes.end(); ++i){ 35 | if(!selection.contains(i.key())){ 36 | m_layout->removeWidget(i.value()); 37 | removed.append(i.key()); 38 | } 39 | } 40 | // ... and remove them after the iteration has finished 41 | for(zodiac::NodeHandle node : removed){ 42 | m_nodes.take(node)->deleteLater(); 43 | } 44 | 45 | // then create the new collapsibles 46 | for(zodiac::NodeHandle node : selection){ 47 | if(!m_nodes.contains(node)){ 48 | Collapsible* collapsible = new Collapsible(this); 49 | collapsible->setWidget(new NodeProperties(m_mainCtrl->getCtrlForHandle(node), collapsible)); 50 | m_layout->insertWidget(0, collapsible); // insert the new Collapsible at the top 51 | m_nodes.insert(node, collapsible); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /propertyeditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef NODEPROPERTYEDITOR_H 27 | #define NODEPROPERTYEDITOR_H 28 | 29 | #include 30 | #include 31 | 32 | #include "zodiacgraph/nodehandle.h" 33 | 34 | class Collapsible; 35 | class MainCtrl; 36 | class QVBoxLayout; 37 | 38 | class PropertyEditor : public QScrollArea 39 | { 40 | Q_OBJECT 41 | 42 | public: // methods 43 | 44 | /// 45 | /// \brief Constructor. 46 | /// 47 | /// \param [in] parent Qt parent. 48 | /// 49 | explicit PropertyEditor(QWidget *parent); 50 | 51 | /// 52 | /// \brief Defines a new main controller managing this PropertyEditor. 53 | /// 54 | /// Should only be called once, after initialization. 55 | /// Is not part of the constructor to avoid the chicken-egg problem with the MainCtrl. 56 | /// 57 | /// \param [in] mainCtrl The main controller managing this PropertyEditor. 58 | /// 59 | void setMainCtrl(MainCtrl* mainCtrl) {Q_ASSERT(!m_mainCtrl); m_mainCtrl = mainCtrl;} 60 | 61 | /// 62 | /// \brief Shows 0-n NodeProperty%s in the PropertyEditor. 63 | /// 64 | /// \param [in] selection Handles of all nodes for which to display NodeProperty%s. 65 | /// 66 | void showNodes(const QList& selection); 67 | 68 | private: // members 69 | 70 | /// 71 | /// \brief The main controller managing this PropertyEditor. 72 | /// 73 | MainCtrl* m_mainCtrl; 74 | 75 | /// 76 | /// \brief Layout of the widget displayed in the scroll area, manages the individual Collapsible%s. 77 | /// 78 | QVBoxLayout* m_layout; 79 | 80 | /// 81 | /// \brief All Collapsible%s representing NodeCtrl%s, identified by their NodeHandle. 82 | /// 83 | QHash m_nodes; 84 | }; 85 | 86 | #endif // NODEPROPERTYEDITOR_H 87 | -------------------------------------------------------------------------------- /res/bucket.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 20 | 21 | -------------------------------------------------------------------------------- /res/icons.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | plus.svg 4 | play.svg 5 | bucket.svg 6 | minus.svg 7 | incoming.svg 8 | questionmark.svg 9 | outgoing.svg 10 | zodiac_logo.png 11 | 12 | 13 | -------------------------------------------------------------------------------- /res/incoming.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /res/minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /res/outgoing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /res/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /res/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /res/questionmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /res/zodiac_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clemenssielaff/ZodiacGraph/c6a5e30d8ddb17463c52e987f4489ba55c728dc3/res/zodiac_logo.png -------------------------------------------------------------------------------- /zodiacgraph/baseedge.cpp: -------------------------------------------------------------------------------- 1 | #include "baseedge.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "edgearrow.h" 7 | #include "edgelabel.h" 8 | #include "utils.h" 9 | #include "scene.h" 10 | 11 | namespace zodiac { 12 | 13 | qreal BaseEdge::s_width = 2.5; 14 | QColor BaseEdge::s_color = QColor("#cc5d4e"); 15 | qreal BaseEdge::s_secondaryFadeInDuration = 200.; 16 | qreal BaseEdge::s_secondaryFadeOutDuration = 400.; 17 | QEasingCurve BaseEdge::s_secondaryFadeInCurve = QEasingCurve::OutQuart; 18 | QEasingCurve BaseEdge::s_secondaryFadeOutCurve = QEasingCurve::InCubic; 19 | QPen BaseEdge::s_pen = QPen(QBrush(s_color), s_width, Qt::SolidLine, Qt::RoundCap); 20 | 21 | BaseEdge::BaseEdge(Scene* scene) 22 | : QGraphicsObject(nullptr) 23 | , m_scene(scene) 24 | , m_arrow(nullptr) 25 | , m_path(QPainterPath()) 26 | , m_secondaryOpacity(0.) 27 | , m_label(nullptr) 28 | { 29 | m_scene->addItem(this); 30 | 31 | // edges are always behind nodes 32 | setZValue(zStack::EDGE); 33 | 34 | // edges deform too much to be cached meaningfully 35 | setCacheMode(NoCache); 36 | 37 | // by default, edges react to hover events 38 | setAcceptHoverEvents(true); 39 | 40 | // construct edge arrow 41 | m_arrow = new EdgeArrow(this); 42 | 43 | // set up animations 44 | m_secondaryFadeIn.setTargetObject(this); 45 | m_secondaryFadeIn.setPropertyName("secondaryOpacity"); 46 | m_secondaryFadeIn.setEndValue(1.); 47 | m_secondaryFadeOut.setTargetObject(this); 48 | m_secondaryFadeOut.setPropertyName("secondaryOpacity"); 49 | m_secondaryFadeOut.setEndValue(0.); 50 | } 51 | 52 | BaseEdge::~BaseEdge() 53 | { 54 | setLabelText(""); 55 | } 56 | 57 | void BaseEdge::setLabelText(const QString& text) 58 | { 59 | if(text.isEmpty()){ 60 | // remove an existing label 61 | if(m_label){ 62 | m_arrow->setLabel(nullptr); 63 | m_scene->removeItem(m_label); 64 | delete m_label; 65 | m_label = nullptr; 66 | } 67 | } else { 68 | // create a new or modify an existing label 69 | if(!m_label){ 70 | m_label = new EdgeLabel(); 71 | m_scene->addItem(m_label); 72 | m_arrow->setLabel(m_label); 73 | } 74 | m_label->setText(text); 75 | } 76 | } 77 | 78 | void BaseEdge::setVisible(bool visible) 79 | { 80 | // if you turn invisible, make sure all secondaries are invisible too 81 | if(!visible){ 82 | m_secondaryFadeIn.stop(); // in case the secondaries are currently fading in 83 | updateSecondaryOpacity(0.); 84 | } 85 | return QGraphicsObject::setVisible(visible); 86 | } 87 | 88 | void BaseEdge::updateStyle() 89 | { 90 | if(m_label){ 91 | m_label->updateStyle(); 92 | } 93 | placeArrowAt(0.5); 94 | update(); 95 | } 96 | 97 | QRectF BaseEdge::boundingRect() const 98 | { 99 | qreal overdraw = s_width/2.; 100 | return m_path.boundingRect().marginsAdded(QMarginsF(overdraw,overdraw,overdraw,overdraw)); 101 | } 102 | 103 | void BaseEdge::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* /* widget */) 104 | { 105 | painter->setClipRect(option->exposedRect); 106 | painter->setPen(s_pen); 107 | painter->drawPath(m_path); 108 | } 109 | 110 | QPainterPath BaseEdge::shape() const 111 | { 112 | return QPainterPathStroker(s_pen).createStroke(m_path); 113 | } 114 | 115 | void BaseEdge::hoverEnterEvent(QGraphicsSceneHoverEvent* event) 116 | { 117 | if(m_label){ 118 | m_secondaryFadeIn.setStartValue(m_secondaryOpacity); 119 | m_secondaryFadeIn.setDuration((1.0-m_secondaryOpacity)*s_secondaryFadeInDuration); 120 | m_secondaryFadeIn.setEasingCurve(s_secondaryFadeInCurve); 121 | m_secondaryFadeIn.start(); 122 | } 123 | QGraphicsObject::hoverEnterEvent(event); 124 | } 125 | 126 | void BaseEdge::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) 127 | { 128 | if(m_label){ 129 | m_secondaryFadeOut.setStartValue(m_secondaryOpacity); 130 | m_secondaryFadeOut.setDuration(m_secondaryOpacity*s_secondaryFadeOutDuration); 131 | m_secondaryFadeOut.setEasingCurve(s_secondaryFadeOutCurve); 132 | m_secondaryFadeOut.start(); 133 | } 134 | QGraphicsObject::hoverLeaveEvent(event); 135 | } 136 | 137 | void BaseEdge::updateSecondaryOpacity(qreal opacity) 138 | { 139 | if(m_label){ 140 | m_label->setOpacity(opacity); 141 | } 142 | m_secondaryOpacity=opacity; 143 | } 144 | } // namespace zodiac 145 | -------------------------------------------------------------------------------- /zodiacgraph/bezieredge.cpp: -------------------------------------------------------------------------------- 1 | #include "bezieredge.h" 2 | 3 | #include "edgearrow.h" 4 | #include "node.h" 5 | #include "plug.h" 6 | 7 | #include 8 | 9 | namespace zodiac { 10 | 11 | qreal BezierEdge::s_maxCtrlDistance = 150.; 12 | qreal BezierEdge::s_ctrlExpansionFactor = 0.4; 13 | 14 | BezierEdge::BezierEdge(Scene* scene) 15 | : BaseEdge(scene) 16 | , m_startPoint(QPointF()) 17 | , m_ctrlPoint1(QPointF()) 18 | , m_ctrlPoint2(QPointF()) 19 | , m_endPoint(QPointF()) 20 | { 21 | // initialize the shape of the edge 22 | updateShape(); 23 | } 24 | 25 | void BezierEdge::placeArrowAt(qreal fraction) 26 | { 27 | /// Technically, you cannot place the arrow all the way at the end of the edge because at that point, the spline 28 | /// would not have a tangent to orient the arrow to. 29 | /// We could of course say that if fraction >= 1.0-VERY_SMALL_DELTA, the direction would have to be 30 | /// calculated using a point before the position ... but why bother? 31 | /// It doesn't make a visual difference and would only introduce unnecessary code branching. 32 | static const qreal VERY_SMALL_DELTA = 0.00001; 33 | 34 | qreal pos = qMin(1.0-VERY_SMALL_DELTA, qMax(0.0, fraction)); 35 | QPointF edgeCenter = m_path.pointAtPercent(pos); 36 | QPointF edgeDirection = m_path.pointAtPercent(qMin(pos+VERY_SMALL_DELTA, 1.))-edgeCenter; 37 | m_arrow->setTransformation(edgeCenter, std::atan2(edgeDirection.y(), edgeDirection.x())); 38 | } 39 | 40 | void BezierEdge::updateShape() 41 | { 42 | prepareGeometryChange(); 43 | 44 | // create the path 45 | QPainterPath bezierPath; 46 | bezierPath.moveTo(m_startPoint); 47 | bezierPath.cubicTo(m_ctrlPoint1, m_ctrlPoint2, m_endPoint); 48 | m_path.swap(bezierPath); 49 | 50 | placeArrowAt(0.5); 51 | } 52 | 53 | QPointF BezierEdge::getCtrlPointFor(Plug* plug) 54 | { 55 | qreal factor; 56 | switch (plug->getDirection()) { 57 | case PlugDirection::IN: 58 | factor = plug->getNode()->getIncomingExpansionFactor(); 59 | break; 60 | case PlugDirection::OUT: 61 | factor = plug->getNode()->getOutgoingExpansionFactor(); 62 | break; 63 | case PlugDirection::BOTH: 64 | factor = qMax(plug->getNode()->getIncomingExpansionFactor(), plug->getNode()->getOutgoingExpansionFactor()); 65 | break; 66 | default: 67 | factor = 0.0; // 68 | Q_ASSERT(false); // we shouldn't ever reach these lines.. 69 | } 70 | 71 | // calculate the control point distance 72 | qreal manhattanLength = (m_endPoint-m_startPoint).manhattanLength(); 73 | qreal ctrlDistance = qMin(s_maxCtrlDistance, manhattanLength*s_ctrlExpansionFactor); 74 | return plug->scenePos()+((plug->getNormal()*ctrlDistance).toPointF()*factor); 75 | } 76 | 77 | } // namespace zodiac 78 | -------------------------------------------------------------------------------- /zodiacgraph/bezieredge.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_BEZIEREDGE_H 27 | #define ZODIAC_BEZIEREDGE_H 28 | 29 | /// \file bezieredge.h 30 | /// 31 | /// \brief Contains the definition of the zodiac::BezierEdge class. 32 | /// 33 | 34 | #include "baseedge.h" 35 | 36 | namespace zodiac { 37 | 38 | class Plug; 39 | class Scene; 40 | 41 | /// 42 | /// \brief The base class for round edges in the graph: PlugEdge and DrawEdge. 43 | /// 44 | /// Other than the StraightEdge branch of the inheritance tree, this class uses a 45 | /// cubic bezier curve for drawing its edge. 46 | /// 47 | /// The position of the two control points (the ones in the middle) of the cubic bezier edge are calculated using 48 | /// the normal of their start and end Plug%s and the curve's arclength. 49 | /// Depending on the arclength of the curve, the control points are moved out from their respective end point in the 50 | /// direction of the Plug's normal until they have reached the \ref zodiac::BezierEdge::s_maxCtrlDistance 51 | /// "maximal control point distance". 52 | /// This is the distance between one of the end points of a BezierEdge and its nearest control point. 53 | /// Smaller numbers produce a sharper kink of the edge near its Plug, larger numbers produce rounder, larger bends. 54 | /// In the extremes, a value of zero would produce a straight edge, a very large value would produce very pronounced 55 | /// "S-shapes". 56 | /// 57 | /// The ratio between arclength and expansion is controlled using the \ref zodiac::BezierEdge::s_ctrlExpansionFactor 58 | /// "control point expansion factor". 59 | /// A value of 0 would mean that the control points never leave their respective end points. 60 | /// A very high value would cause them to jump out to their maximal distance as soon as the edge is stretched to a 61 | /// minimal length. 62 | /// 63 | /// For the calculation of the spline's arlength, we are using the 64 | /// Manhattan distance between its start and end point. 65 | /// If we were to use the actual spline arclength, the shape of the path would influence its length would influence its 66 | /// shape etc. 67 | /// This works eventually, but the spline requires a few iterations (=redraws) to settle down. 68 | /// Using the manhattan distance is visually similar, faster and works immediately 69 | /// 70 | /// Although the class is non-virtual, it is not intended to be instantiated as-is. 71 | /// Therefore its constructor is protected, so only derived classes can use it. 72 | /// 73 | class BezierEdge : public BaseEdge 74 | { 75 | 76 | Q_OBJECT 77 | 78 | public: // methods 79 | 80 | /// 81 | /// \brief Moves the EdgeArrow along the BezierEdge to a given fraction of the edge's arclength. 82 | /// 83 | /// \param [in] fraction Fraction [0 -> 1] of the edges arclength at which to place the arrow. 84 | /// 85 | void placeArrowAt(qreal fraction) override; 86 | 87 | public: // static methods 88 | 89 | /// 90 | /// \brief The maximal ctrl distance is the distance between one of the end points of a BezierEdge and its nearest 91 | /// control point. 92 | /// 93 | /// \return Maximal distance from the ctrl point to the Plug in pixels. 94 | /// 95 | static inline qreal getMaxCtrlDistance() {return s_maxCtrlDistance;} 96 | 97 | /// 98 | /// \brief Sets a new maximal distance from the ctrl point to the plug in pixels. 99 | /// 100 | /// \param [in] distance New maximal ctrl point distance to the plug, at least 0. 101 | /// 102 | static inline void setMaxCtrlDistance(qreal distance) {s_maxCtrlDistance=qMax(0.,distance);} 103 | 104 | /// 105 | /// \brief The ratio between the BezierEdge's arclength and the expansion of its control points. 106 | /// 107 | /// \return Control point expansion factor. 108 | /// 109 | static inline qreal getCtrlExpansionFactor() {return s_ctrlExpansionFactor;} 110 | 111 | /// 112 | /// \brief Sets a new ratio between the BezierEdge's arclength and the expansion of its control points. 113 | /// 114 | /// \param [in] factor New control point expansion factor. 115 | /// 116 | static inline void setCtrlExpansionFactor(qreal factor) {s_ctrlExpansionFactor=factor;} 117 | 118 | protected: // methods 119 | 120 | /// 121 | /// \brief Constructor. 122 | /// 123 | /// Protected, so a BezierEdge cannot be instantiated, as it does not have any meaningful functionality by itself. 124 | /// 125 | /// \param [in] scene Scene containing this edge. 126 | /// 127 | explicit BezierEdge(Scene *scene); 128 | 129 | /// 130 | /// \brief Updates the shape of this edge. 131 | /// 132 | virtual void updateShape() override; 133 | 134 | /// 135 | /// \brief Returns the position of the control point of the edge for a given Plug. 136 | /// 137 | /// This works regardless if it is the start or the end Plug of this edge. 138 | /// Calling this function requires the start and end point of the edge to be set correctly to calculate the spline's 139 | /// arclength. 140 | /// 141 | /// \param [in] plug Plug to calculate the control point position for. 142 | /// 143 | /// \return %Scene coordinates of the control point. 144 | /// 145 | QPointF getCtrlPointFor(Plug* plug); 146 | 147 | protected: // members 148 | 149 | /// 150 | /// \brief Start point of the BezierEdge in scene coordinates. 151 | /// 152 | QPointF m_startPoint; 153 | 154 | /// 155 | /// \brief First control point of the BezierEdge in scene coordinates. 156 | /// 157 | QPointF m_ctrlPoint1; 158 | 159 | /// 160 | /// \brief Second control point of the BezierEdge in scene coordinates. 161 | /// 162 | QPointF m_ctrlPoint2; 163 | 164 | /// 165 | /// \brief End point of the BezierEdge in scene coordinates. 166 | /// 167 | QPointF m_endPoint; 168 | 169 | private: // static members 170 | 171 | /// 172 | /// \brief Maximal distance of a control point from its respective end point. 173 | /// 174 | static qreal s_maxCtrlDistance; 175 | 176 | /// 177 | /// \brief Factor by which the control point of is moved outward along the normal of its Plug, based on the edge's 178 | /// arclengh. 179 | /// 180 | static qreal s_ctrlExpansionFactor; 181 | }; 182 | 183 | } // namespace zodiac 184 | 185 | #endif // ZODIAC_BEZIEREDGE_H 186 | -------------------------------------------------------------------------------- /zodiacgraph/drawedge.cpp: -------------------------------------------------------------------------------- 1 | #include "drawedge.h" 2 | 3 | #include "utils.h" 4 | #include "plug.h" 5 | 6 | namespace zodiac { 7 | 8 | DrawEdge::DrawEdge(Scene* scene) 9 | : BezierEdge(scene) 10 | , m_isReverse(false) 11 | { 12 | // the draw edge is always in front of the nodes 13 | setZValue(zStack::DRAW_EDGE); 14 | 15 | // the draw edge does not need to react to hover events 16 | setAcceptHoverEvents(false); 17 | } 18 | 19 | void DrawEdge::fromPlugToPoint(Plug* plug, const QPointF& endPoint) 20 | { 21 | // return early, if the shape of the edge has not changed 22 | QPointF startPoint = plug->scenePos(); 23 | if((startPoint==m_startPoint)&&(endPoint==m_endPoint)){ 24 | return; 25 | } 26 | 27 | // update the edge's ctrl points 28 | if(!m_isReverse){ 29 | m_startPoint = startPoint; 30 | m_endPoint = endPoint; 31 | m_ctrlPoint1 = getCtrlPointFor(plug); 32 | m_ctrlPoint2 = m_endPoint; 33 | } else { 34 | m_endPoint = startPoint; 35 | m_startPoint = endPoint; 36 | m_ctrlPoint2 = getCtrlPointFor(plug); 37 | m_ctrlPoint1 = m_startPoint; 38 | } 39 | 40 | // update the path 41 | updateShape(); 42 | } 43 | 44 | } // namespace zodiac 45 | -------------------------------------------------------------------------------- /zodiacgraph/drawedge.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_DRAWEDGE_H 27 | #define ZODIAC_DRAWEDGE_H 28 | 29 | /// \file drawedge.h 30 | /// 31 | /// \brief Contains the definition of the zodiac::DrawEdge class. 32 | /// 33 | 34 | #include "bezieredge.h" 35 | 36 | namespace zodiac { 37 | 38 | class Scene; 39 | 40 | /// 41 | /// \brief Edge used to draw a new edge. 42 | /// 43 | /// It is a specialized BezierEdge, as it does not have an end Plug and is only used to visually create a PlugEdge in 44 | /// the graph. 45 | /// 46 | /// The end point and second control point of the DrawEdge splin eare placed at the same position (usually the mouse 47 | /// cursor), giving the edge a "pointy" end. 48 | /// 49 | class DrawEdge : public BezierEdge 50 | { 51 | 52 | Q_OBJECT 53 | 54 | public: 55 | 56 | /// 57 | /// \brief Constructor. 58 | /// 59 | /// \param [in] scene Scene containing this DrawEdge. 60 | /// 61 | explicit DrawEdge(Scene* scene); 62 | 63 | /// 64 | /// \brief Reverses the display of the DrawEdge to accomodate drawing from an \ref zodiac::PlugDirection::IN 65 | /// "incoming" Plug. 66 | /// 67 | /// \param [in] isReverse true if the DrawEdge originates at an \ref zodiac::PlugDirection::IN "incoming" 68 | /// Plug -- false otherwise. 69 | /// 70 | inline void setReverse(bool isReverse){m_isReverse=isReverse;} 71 | 72 | /// 73 | /// \brief The DrawEdge is said to be reversed, if it originates in an \ref zodiac::PlugDirection::IN 74 | /// "incoming" Plug of a Node. 75 | /// 76 | /// \return true if the DrawEdge is currently reversed -- false if not. 77 | /// 78 | inline bool isReversed() const {return m_isReverse;} 79 | 80 | public: // methods 81 | 82 | /// 83 | /// \brief Used for creating a PlugEdge when its end is not connected to a Plug yet. 84 | /// 85 | /// The endPoint is usually the mouse cursor. 86 | /// 87 | /// \param [in] plug Plug, from where the DrawEdge originates. 88 | /// \param [in] endPoint Manually defined position of the end point and 2nd ctrl point of this spline. 89 | /// 90 | void fromPlugToPoint(Plug* plug, const QPointF& endPoint); 91 | 92 | private: // members 93 | 94 | /// 95 | /// \brief true if this DrawEdge originates in a \ref zodiac::PlugDirection::IN "incoming" Plug. 96 | /// 97 | bool m_isReverse; 98 | 99 | }; 100 | 101 | } // namespace zodiac 102 | 103 | #endif // ZODIAC_DRAWEDGE_H 104 | -------------------------------------------------------------------------------- /zodiacgraph/edgearrow.cpp: -------------------------------------------------------------------------------- 1 | #include "edgearrow.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "edgelabel.h" 8 | #include "baseedge.h" 9 | 10 | namespace zodiac { 11 | 12 | qreal EdgeArrow::s_doubleGap = 2.5; // same as BaseEdge::s_width 13 | qreal EdgeArrow::s_arrowHalfLength = 7.5; 14 | qreal EdgeArrow::s_arrowHalfWidth = 6.; 15 | QColor EdgeArrow::s_arrowColor = QColor("#cc5d4e"); 16 | QPolygonF EdgeArrow::s_originalArrow = QPolygonF() 17 | << QPointF(s_arrowHalfLength, 0.) 18 | << QPointF(-s_arrowHalfLength, -s_arrowHalfWidth) 19 | << QPointF(-s_arrowHalfLength, s_arrowHalfWidth); 20 | QPolygonF EdgeArrow::s_originalDoubleArrow = QPolygonF() 21 | << QPointF(s_arrowHalfLength, s_doubleGap) 22 | << QPointF(-s_arrowHalfLength, s_doubleGap) 23 | << QPointF(-s_arrowHalfLength, s_arrowHalfWidth + s_doubleGap) 24 | << QPointF(s_arrowHalfLength, s_doubleGap) 25 | << QPointF(-s_arrowHalfLength, -s_doubleGap) 26 | << QPointF(s_arrowHalfLength, -s_arrowHalfWidth - s_doubleGap) 27 | << QPointF(s_arrowHalfLength, -s_doubleGap) 28 | << QPointF(-s_arrowHalfLength, -s_doubleGap); 29 | 30 | EdgeArrow::EdgeArrow(BaseEdge* edge) 31 | : QGraphicsObject(edge) 32 | , m_edge(edge) 33 | , m_arrowPolygon(QPolygonF()) 34 | , m_kind(ArrowKind::SINGLE) 35 | , m_label(nullptr) 36 | { 37 | // doesn't do anything with it but needs this flag in order to pass the event on to the edge 38 | setAcceptHoverEvents(true); 39 | 40 | // like edges, arrows change too often to be cached meaningfully 41 | setCacheMode(NoCache); 42 | } 43 | 44 | void EdgeArrow::setTransformation(const QPointF &pos, qreal angle) 45 | { 46 | prepareGeometryChange(); 47 | 48 | // use a transformed original as the new arrow 49 | QTransform arrowTransform; 50 | arrowTransform.translate(pos.x(), pos.y()); 51 | arrowTransform.rotate(qRadiansToDegrees(angle)); 52 | 53 | // swap in a copy of the original arrow shape 54 | QPolygonF tempArrow; 55 | if(m_kind==ArrowKind::SINGLE){ 56 | tempArrow=arrowTransform.map(s_originalArrow); 57 | } else if(m_kind==ArrowKind::DOUBLE){ 58 | tempArrow=arrowTransform.map(s_originalDoubleArrow); 59 | } 60 | m_arrowPolygon.swap(tempArrow); 61 | 62 | // update the label 63 | if(m_label){ 64 | QPointF scenePos = mapToScene(pos); 65 | m_label->setPos(scenePos.x(), scenePos.y()); 66 | } 67 | 68 | // force an update here, otherwise the arrow seems to "swim" on top of the edge 69 | update(); 70 | } 71 | 72 | void EdgeArrow::defineArrow(qreal length, qreal width) 73 | { 74 | // update static members 75 | s_arrowHalfLength = qMax(0.,length/2.); 76 | s_arrowHalfWidth = qMax(0.,width/2.); 77 | 78 | // SINGLE arrow 79 | s_originalArrow = QPolygonF() 80 | << QPointF(s_arrowHalfLength, 0.) 81 | << QPointF(-s_arrowHalfLength, -s_arrowHalfWidth) 82 | << QPointF(-s_arrowHalfLength, s_arrowHalfWidth); 83 | 84 | // DOUBLE arrow 85 | s_originalDoubleArrow = QPolygonF() 86 | << QPointF(s_arrowHalfLength, s_doubleGap) 87 | << QPointF(-s_arrowHalfLength, s_doubleGap) 88 | << QPointF(-s_arrowHalfLength, s_arrowHalfWidth + s_doubleGap) 89 | << QPointF(s_arrowHalfLength, s_doubleGap) 90 | << QPointF(-s_arrowHalfLength, -s_doubleGap) 91 | << QPointF(s_arrowHalfLength, -s_arrowHalfWidth - s_doubleGap) 92 | << QPointF(s_arrowHalfLength, -s_doubleGap) 93 | << QPointF(-s_arrowHalfLength, -s_doubleGap); 94 | } 95 | 96 | QRectF EdgeArrow::boundingRect() const 97 | { 98 | return m_arrowPolygon.boundingRect(); 99 | } 100 | 101 | void EdgeArrow::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* /* widget */) 102 | { 103 | painter->setClipRect(option->exposedRect); 104 | painter->setPen(Qt::NoPen); 105 | painter->setBrush(QBrush(s_arrowColor)); 106 | painter->drawConvexPolygon(m_arrowPolygon); 107 | } 108 | 109 | QPainterPath EdgeArrow::shape() const 110 | { 111 | QPainterPath shape; 112 | shape.addPolygon(m_arrowPolygon); 113 | return shape; 114 | } 115 | 116 | void EdgeArrow::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) 117 | { 118 | // manually forward the double-click event to the parenting BaseEdge 119 | return m_edge->mouseDoubleClickEvent(event); 120 | } 121 | 122 | } // namespace zodiac 123 | -------------------------------------------------------------------------------- /zodiacgraph/edgearrow.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_EDGEARROW_H 27 | #define ZODIAC_EDGEARROW_H 28 | 29 | /// 30 | /// \file edgearrow.h 31 | /// 32 | /// \brief Contains the definition of the zodiac::EdgeArrow class and zodiac::ArrowKind enum. 33 | /// 34 | 35 | #include 36 | 37 | namespace zodiac { 38 | 39 | class EdgeLabel; 40 | class BaseEdge; 41 | 42 | /// 43 | /// \brief There are currently 2 visually different types of EdgeArrow%s used in the graph. 44 | /// 45 | /// Even though they are drawn differently, both are functionally the same. 46 | /// The only difference is the original arrow shape they use for generating their own.
47 | /// This enum is a numeration of these original arrow shapes. 48 | /// 49 | enum class ArrowKind { 50 | SINGLE = 0, ///< A single triangle pointing into the direction of the edge. 51 | DOUBLE = 1, ///< Two mirrored triangles, pointing along different directions of the edge. 52 | }; 53 | 54 | /// 55 | /// \brief The EdgeArrow is an additional item of the BaseEdge, identifying its direction. 56 | /// 57 | /// It is placed at the middle of the BaseEdge and comes in two flavors: \ref zodiac::ArrowKind::SINGLE "single" and 58 | /// \ref zodiac::ArrowKind::DOUBLE "double". 59 | /// 60 | class EdgeArrow : public QGraphicsObject 61 | { 62 | Q_OBJECT 63 | 64 | public: // methods 65 | 66 | /// 67 | /// \brief Constructor. 68 | /// 69 | explicit EdgeArrow(BaseEdge* edge); 70 | 71 | /// 72 | /// \brief Sets the transformation of this arrow. 73 | /// 74 | /// \param [in] pos Position. 75 | /// \param [in] angle Angle in radians. 76 | /// 77 | void setTransformation(const QPointF& pos, qreal angle); 78 | 79 | /// 80 | /// \brief Assings a label to this arrow. 81 | /// 82 | /// If a BaseEdge has an EdgeLabel, it transforms with the EdgeArrow. 83 | /// This way, the somewhat expensive calculation of a position along a spline is only performed once. 84 | /// 85 | /// \param [in] label EdgeLabel assigned to this EdgeArrow, can be nullptr to remove an existing label. 86 | /// 87 | inline void setLabel(EdgeLabel* label) {m_label=label;} 88 | 89 | /// 90 | /// \brief Sets the ArrowKind of this EdgeArrow. 91 | /// 92 | /// \param [in] kind New kind of arrow. 93 | /// 94 | inline void setKind(ArrowKind kind) {m_kind=kind;} 95 | 96 | public: // static methods 97 | 98 | /// 99 | /// \brief The length of the EdgeArrow in pixels. 100 | /// 101 | /// Is used for the calculation of the \ref zodiac::ArrowKind::SINGLE "single" and \ref zodiac::ArrowKind::DOUBLE 102 | /// "double" arrow. 103 | /// 104 | /// \return Arrow length in pixels. 105 | /// 106 | inline static qreal getArrowLength() {return s_arrowHalfLength * 2;} 107 | 108 | /// 109 | /// \brief The width of the EdgeArrow in pixels. 110 | /// 111 | /// Is used for the calculation of the \ref zodiac::ArrowKind::SINGLE "single" and \ref zodiac::ArrowKind::DOUBLE 112 | /// "double" arrow. 113 | /// 114 | /// \return Arrow width in pixels. 115 | /// 116 | inline static qreal getArrowWidth() {return s_arrowHalfWidth * 2;} 117 | 118 | /// 119 | /// \brief (Re-)defines the two original arrows. 120 | /// 121 | /// \param [in] length Length of the arrow in pixels (unscaled). 122 | /// \param [in] width Width of the arrow in pixels (unscaled). 123 | /// 124 | static void defineArrow(qreal length, qreal width); 125 | 126 | /// 127 | /// \brief The fill color of all EdgeArrow%s. 128 | /// 129 | /// \return The arrow's fill color. 130 | /// 131 | inline static QColor getArrowColor() {return s_arrowColor;} 132 | 133 | /// 134 | /// \brief Sets a new color for all EdgeArrow%s. 135 | /// 136 | /// \param [in] color New arrow fill color. 137 | /// 138 | inline static void setArrowColor(const QColor& color) {s_arrowColor=color;} 139 | 140 | protected: // methods 141 | 142 | /// 143 | /// \brief Rectangular outer bounds of the item, used for redraw testing. 144 | /// 145 | /// \return Boundary rectangle of the item. 146 | /// 147 | QRectF boundingRect() const; 148 | 149 | /// 150 | /// \brief Paints this item. 151 | /// 152 | /// \param [in] painter Painter used to paint the item. 153 | /// \param [in] option Provides style options for the item. 154 | /// \param [in] widget Optional widget that this item is painted on. 155 | /// 156 | void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); 157 | 158 | /// 159 | /// \brief Exact boundary of the item used for collision detection among other things. 160 | /// 161 | /// \return Shape in local coordinates. 162 | /// 163 | QPainterPath shape() const; 164 | 165 | /// 166 | /// \brief Called when this item is double-clicked. 167 | /// 168 | /// Manually passes the event on to the BaseEdge parenting this EdgeArrow. 169 | /// 170 | /// \param [in] event Qt event object. 171 | /// 172 | void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); 173 | 174 | private: // members 175 | 176 | /// 177 | /// \brief BaseEdge owning this EdgeArrow. 178 | /// 179 | BaseEdge* m_edge; 180 | 181 | /// 182 | /// \brief The polygon used to draw the arrow. 183 | /// 184 | QPolygonF m_arrowPolygon; 185 | 186 | /// 187 | /// \brief The kind of this EdgeArrow, defaults to \ref zodiac::ArrowKind::SINGLE "single". 188 | /// 189 | ArrowKind m_kind; 190 | 191 | /// 192 | /// \brief EdgeLabel assigned to this arrow or nullptr. 193 | /// 194 | EdgeLabel* m_label; 195 | 196 | private: // static members 197 | 198 | /// 199 | /// \brief Size of the gap between the two lines of a \ref zodiac::ArrowKind::DOUBLE "double" arrow. 200 | /// 201 | static qreal s_doubleGap; 202 | 203 | /// 204 | /// \brief Half the length of the edge arrow along the direction it is pointing. 205 | /// 206 | static qreal s_arrowHalfLength; 207 | 208 | /// 209 | /// \brief Half the width of the arrow perpendicular to the direction it is pointing. 210 | /// 211 | static qreal s_arrowHalfWidth; 212 | 213 | /// 214 | /// \brief Fill color of the edge arrow. 215 | /// 216 | static QColor s_arrowColor; 217 | 218 | /// 219 | /// \brief Original \ref zodiac::ArrowKind::SINGLE "single" arrow. 220 | /// 221 | /// Is shared by all edge arrows as base for their own. 222 | /// 223 | static QPolygonF s_originalArrow; 224 | 225 | /// 226 | /// \brief Original \ref zodiac::ArrowKind::DOUBLE "double" arrow. 227 | /// 228 | /// Is shared by all edge arrows as base for their own. 229 | /// 230 | static QPolygonF s_originalDoubleArrow; 231 | 232 | }; 233 | 234 | } // namespace zodiac 235 | 236 | #endif // ZODIAC_EDGEARROW_H 237 | -------------------------------------------------------------------------------- /zodiacgraph/edgegroup.cpp: -------------------------------------------------------------------------------- 1 | #include "edgegroup.h" 2 | 3 | #include "edgegrouppair.h" 4 | #include "labeltextfactory.h" 5 | #include "node.h" 6 | #include "plug.h" 7 | #include "plugedge.h" 8 | #include "straightedge.h" 9 | #include "scene.h" 10 | 11 | namespace zodiac { 12 | 13 | EdgeGroup::EdgeGroup(Scene* scene, 14 | Node* fromNode, Node* toNode, 15 | EdgeGroupPair* pair) 16 | : QObject(nullptr) 17 | , EdgeGroupInterface() 18 | , m_scene(scene) 19 | , m_fromNode(fromNode) 20 | , m_toNode(toNode) 21 | , m_pair(pair) 22 | , m_edges(QSet()) 23 | , m_straightEdge(nullptr) 24 | , m_bentEdgesCount(0) 25 | { 26 | // create an invisible StraightEdge 27 | m_straightEdge = new StraightEdge(m_scene, this, fromNode, toNode); 28 | m_straightEdge->setVisible(false); 29 | 30 | // let the StraightEdge request removal of this group 31 | connect(m_straightEdge, SIGNAL(removalRequested()), this, SLOT(removalRequested())); 32 | } 33 | 34 | EdgeGroup::~EdgeGroup() 35 | { 36 | // As EdgeGroups are always deleted before the rest of the QGraphicsView, this also work on shutdown 37 | 38 | m_straightEdge->getFromNode()->removeStraightEdge(m_straightEdge); 39 | m_straightEdge->getToNode()->removeStraightEdge(m_straightEdge); 40 | 41 | m_scene->removeItem(m_straightEdge); 42 | delete m_straightEdge; // valgrind seems to mind a call to deleteLater() here... I've read it's not real but this works also 43 | m_straightEdge = nullptr; 44 | } 45 | 46 | void EdgeGroup::addEdge(PlugEdge* edge) 47 | { 48 | #ifdef QT_DEBUG 49 | Q_ASSERT(edge->getStartPlug()->getNode() == m_fromNode); 50 | Q_ASSERT(edge->getEndPlug()->getNode() == m_toNode); 51 | #else 52 | if((edge->getStartPlug()->getNode() != m_fromNode) || (edge->getEndPlug()->getNode() != m_toNode)){ 53 | return; 54 | } 55 | #endif 56 | m_edges.insert(edge); 57 | 58 | // update the labels 59 | m_straightEdge->updateLabel(); 60 | m_pair->updateLabel(); 61 | } 62 | 63 | void EdgeGroup::removeEdge(PlugEdge* edge) 64 | { 65 | #ifdef QT_DEBUG 66 | Q_ASSERT(m_edges.contains(edge)); 67 | #else 68 | if(!m_edges.contains(edge)){ 69 | return; 70 | } 71 | #endif 72 | m_edges.remove(edge); 73 | 74 | // if the edge to be removed is the last one in the group, it might be 'unbent' 75 | // to avoid confusion, temporarily set the bent count to 1 before decreasing it again 76 | if((m_edges.count()==0) && (m_bentEdgesCount==0)){ 77 | m_bentEdgesCount = 1; 78 | } 79 | decreaseBentCount(); 80 | 81 | updateLabelText(); 82 | } 83 | 84 | void EdgeGroup::increaseBentCount() 85 | { 86 | ++m_bentEdgesCount; 87 | m_pair->hideDoubleEdge(); 88 | } 89 | 90 | void EdgeGroup::decreaseBentCount() 91 | { 92 | --m_bentEdgesCount; 93 | Q_ASSERT(m_bentEdgesCount>=0); 94 | updateVisibility(); 95 | m_pair->updateDoubleEdgeVisibility(); 96 | } 97 | 98 | void EdgeGroup::updateVisibility() 99 | { 100 | bool visibility = m_bentEdgesCount!=0; 101 | for(PlugEdge* edge : m_edges){ 102 | edge->setVisible(visibility); 103 | } 104 | if(m_edges.size()>0){ 105 | m_straightEdge->setVisible(!visibility); 106 | } 107 | } 108 | 109 | uint EdgeGroup::getHash() const 110 | { 111 | return getHashOf(m_fromNode, m_toNode); 112 | } 113 | 114 | bool EdgeGroup::isVisible() const 115 | { 116 | return m_straightEdge->isVisible(); 117 | } 118 | 119 | void EdgeGroup::setVisibility(bool visibility) 120 | { 121 | m_straightEdge->setVisible(visibility); 122 | } 123 | 124 | QString EdgeGroup::getLabelText() 125 | { 126 | return LabelTextFactory(m_edges).produceLabel(); 127 | } 128 | 129 | void EdgeGroup::updateLabelText() 130 | { 131 | m_straightEdge->updateLabel(); 132 | m_pair->updateLabel(); 133 | } 134 | 135 | void EdgeGroup::updateStyle() 136 | { 137 | m_straightEdge->updateStyle(); 138 | } 139 | 140 | void EdgeGroup::removalRequested() 141 | { 142 | // if there is only one edge in this group, tell the Scene to remove it 143 | if(m_edges.count()==1){ 144 | increaseBentCount(); 145 | m_scene->removeEdge((*m_edges.begin())); 146 | } 147 | // otherwise do nothing 148 | } 149 | 150 | } // namespace zodiac 151 | -------------------------------------------------------------------------------- /zodiacgraph/edgegroupinterface.cpp: -------------------------------------------------------------------------------- 1 | #include "edgegroupinterface.h" 2 | -------------------------------------------------------------------------------- /zodiacgraph/edgegroupinterface.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_EDGEGROUPINTERFACE_H 27 | #define ZODIAC_EDGEGROUPINTERFACE_H 28 | 29 | /// 30 | /// \file edgegroupinterface.h 31 | /// 32 | /// \brief Contains the definition of the zodiac::EdgeGroupInterface mixin class. 33 | /// 34 | 35 | class QString; 36 | 37 | namespace zodiac { 38 | 39 | /// 40 | /// \brief Mix-in interface for EdgeGroup and EdgeGroupPair. 41 | /// 42 | /// This is necessary to mirror the inheritance tree of StraightEdge and StraightDoubleEdge. 43 | /// Now, a StraightEdge can always ask its EdgeGroupInterface for a label text, even if the StraightEdge is really a 44 | /// StraightDoubleEdge. 45 | /// 46 | class EdgeGroupInterface 47 | { 48 | 49 | public: // method 50 | 51 | /// 52 | /// \brief Generates a text used for a group of edges. 53 | /// 54 | /// \return Text used for the EdgeLabel. 55 | /// 56 | virtual QString getLabelText() = 0; 57 | 58 | }; 59 | 60 | } // namespace zodiac 61 | 62 | #endif // ZODIAC_EDGEGROUPINTERFACE_H 63 | -------------------------------------------------------------------------------- /zodiacgraph/edgegrouppair.cpp: -------------------------------------------------------------------------------- 1 | #include "edgegrouppair.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "edgegroup.h" 7 | #include "labeltextfactory.h" 8 | #include "node.h" 9 | #include "plugedge.h" 10 | #include "scene.h" 11 | #include "straightdoubleedge.h" 12 | 13 | namespace zodiac { 14 | 15 | EdgeGroupPair::EdgeGroupPair(Scene* scene, Node* nodeA, Node* nodeB) 16 | : EdgeGroupInterface() 17 | , m_scene(scene) 18 | , m_firstGroup(new EdgeGroup(scene, nodeA, nodeB, this)) 19 | , m_secondGroup(new EdgeGroup(scene, nodeB, nodeA, this)) 20 | , m_edge(new StraightDoubleEdge(m_scene, this, nodeA, nodeB)) 21 | { 22 | // upon creation, the PlugEdge creating the first EdgeGroup is still bent and visible. 23 | m_edge->setVisible(false); 24 | } 25 | 26 | EdgeGroupPair::~EdgeGroupPair() 27 | { 28 | // delete the groups 29 | delete m_firstGroup; 30 | delete m_secondGroup; 31 | 32 | m_firstGroup=nullptr; 33 | m_secondGroup=nullptr; 34 | 35 | // As EdgeGroupPairs are always deleted before the rest of the QGraphicsView, this also work on shutdown 36 | 37 | // delete the double edge 38 | m_edge->getFromNode()->removeStraightEdge(m_edge); 39 | m_edge->getToNode()->removeStraightEdge(m_edge); 40 | 41 | m_scene->removeItem(m_edge); 42 | delete m_edge; // valgrind seems to mind a call to deleteLater() here... I've read it's not real but this works also 43 | m_edge=nullptr; 44 | } 45 | 46 | bool EdgeGroupPair::isEmpty() const 47 | { 48 | return((m_firstGroup->getEdgeCount()==0) && (m_secondGroup->getEdgeCount()==0)); 49 | } 50 | 51 | void EdgeGroupPair::updateDoubleEdgeVisibility() 52 | { 53 | // return early, if any of the two group edges is not visible 54 | if(!m_firstGroup->isVisible() || !m_secondGroup->isVisible()){ 55 | return; 56 | } 57 | 58 | // if both are visible, hide them and show the double edge in their place 59 | m_firstGroup->setVisibility(false); 60 | m_secondGroup->setVisibility(false); 61 | m_edge->setVisible(true); 62 | } 63 | 64 | void EdgeGroupPair::hideDoubleEdge() 65 | { 66 | // hide the double edge 67 | m_edge->setVisible(false); 68 | 69 | // let the edge groups determine by themselves if they need to become visible or not 70 | m_firstGroup->updateVisibility(); 71 | m_secondGroup->updateVisibility(); 72 | } 73 | 74 | void EdgeGroupPair::updateLabel() 75 | { 76 | m_edge->updateLabel(); 77 | } 78 | 79 | QString EdgeGroupPair::getLabelText() 80 | { 81 | LabelTextFactory firstLabelGroup(m_firstGroup->getEdges()); 82 | LabelTextFactory secondLabelGroup(m_secondGroup->getEdges()); 83 | int maxNameLength = qMax(firstLabelGroup.getMaxNameLength(), secondLabelGroup.getMaxNameLength()); 84 | int labelCount = firstLabelGroup.getLabelCount() + secondLabelGroup.getLabelCount() + 1; 85 | 86 | QStringList labelStrings; 87 | labelStrings.reserve(labelCount); 88 | labelStrings << firstLabelGroup.produceLabel(maxNameLength); 89 | labelStrings << LabelTextFactory::getHorizontalLine(maxNameLength); 90 | labelStrings << secondLabelGroup.produceLabel(maxNameLength); 91 | return labelStrings.join(LabelTextFactory::getNewlineChar()); 92 | 93 | } 94 | 95 | void EdgeGroupPair::updateStyle() 96 | { 97 | m_edge->updateStyle(); 98 | m_firstGroup->updateStyle(); 99 | m_secondGroup->updateStyle(); 100 | } 101 | 102 | } // namespace zodiac 103 | -------------------------------------------------------------------------------- /zodiacgraph/edgegrouppair.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_EDGEGROUPPAIR_H 27 | #define ZODIAC_EDGEGROUPPAIR_H 28 | 29 | /// 30 | /// \file edgegrouppair.h 31 | /// 32 | /// \brief Contains the definition of the zodiac::EdgeGroupPair class. 33 | /// 34 | 35 | #include "edgegroupinterface.h" 36 | 37 | namespace zodiac { 38 | 39 | class StraightDoubleEdge; 40 | class EdgeGroup; 41 | class Node; 42 | class Scene; 43 | 44 | /// 45 | /// \brief A pair of EdgeGroup%s, both connecting the same Node%s but in different directions. 46 | /// 47 | /// While a single EdgeGroup contains 1-n PlugEdge%s, an EdgeGroupPair contains and owns two EdgeGroup%s. 48 | /// 49 | class EdgeGroupPair : public EdgeGroupInterface 50 | { 51 | 52 | public: // methods 53 | 54 | /// 55 | /// \brief Constructor. 56 | /// 57 | /// \param [in] scene Scene owning this EdgeGroupPair. 58 | /// \param [in] nodeA One Node in the group. 59 | /// \param [in] nodeB The other Node in the group (direction does not matter). 60 | /// 61 | explicit EdgeGroupPair(Scene* scene, Node* nodeA, Node* nodeB); 62 | 63 | /// 64 | /// \brief Destructor. 65 | /// 66 | /// Destroys both EdgeGroup%s and their respective DoubleEdge%s / DoubleStraightEdge%s. 67 | /// 68 | virtual ~EdgeGroupPair(); 69 | 70 | /// 71 | /// \brief One of the two EdgeGroup%s in this pair. 72 | /// 73 | /// \return First EdgeGroup of the pair. 74 | /// 75 | inline EdgeGroup* getFirstGroup() const {return m_firstGroup;} 76 | 77 | /// 78 | /// \brief The other EdgeGroup in this pair. 79 | /// 80 | /// \return Second EdgeGroup of the pair. 81 | /// 82 | inline EdgeGroup* getSecondGroup() const {return m_secondGroup;} 83 | 84 | /// 85 | /// \brief Tests if none of the two EdgeGroup%s in this pair contain any PlugEdge%s. 86 | /// 87 | /// \return true if both EdgeGroup%s are empty -- false otherwise. 88 | /// 89 | bool isEmpty() const; 90 | 91 | /// 92 | /// \brief If both EdgeGroup%s of the pair are visible, shows the DoubleStraightEdge instead. 93 | /// 94 | void updateDoubleEdgeVisibility(); 95 | 96 | /// 97 | /// \brief Hides the DoubleStraightEdge of this EdgeGroupPair. 98 | /// 99 | void hideDoubleEdge(); 100 | 101 | /// 102 | /// \brief Updates the label of the DoubleStraightEdge. 103 | /// 104 | void updateLabel(); 105 | 106 | /// 107 | /// \brief Generates the label text for this EdgeGroupPair. 108 | /// 109 | /// \return Label text based on the PlugEdge%s contained in both EdgeGroup%s of this pair. 110 | /// 111 | QString getLabelText(); 112 | 113 | /// 114 | /// \brief Applies style changes in the class' static members to this instance. 115 | /// 116 | /// Is part of the scene-wide cascade of %updateStyle()-calls after a re-styling of the ZodiacGraph. 117 | /// 118 | void updateStyle(); 119 | 120 | private: // members 121 | 122 | /// 123 | /// \brief Scene containing this pair. 124 | /// 125 | Scene* m_scene; 126 | 127 | /// 128 | /// \brief First EdgeGroup of this pair, containing PlugEdge%s leading from Node A to Node B. 129 | /// 130 | /// Is owned by this EdgeGroupPair. 131 | /// 132 | EdgeGroup* m_firstGroup; 133 | 134 | /// 135 | /// \brief Second EdgeGroup of this pair, containing PlugEdge%s leading from Node B to Node A. 136 | /// 137 | /// Is owned by this EdgeGroupPair. 138 | /// 139 | EdgeGroup* m_secondGroup; 140 | 141 | /// 142 | /// \brief Double edge to display instead of two overlaying StraightEdge%s pointing in opposite directions. 143 | /// 144 | /// Is owned by this EdgeGroupPair. 145 | /// 146 | StraightDoubleEdge* m_edge; 147 | }; 148 | 149 | } // namespace zodiac 150 | 151 | #endif // ZODIAC_EDGEGROUPPAIR_H 152 | -------------------------------------------------------------------------------- /zodiacgraph/edgelabel.cpp: -------------------------------------------------------------------------------- 1 | #include "edgelabel.h" 2 | 3 | #include 4 | 5 | #include "utils.h" 6 | 7 | namespace zodiac { 8 | 9 | QFont EdgeLabel::s_font = QFont("DejaVu Sans Mono", 10, 75); 10 | QColor EdgeLabel::s_color = QColor(200,200,200,180); 11 | qreal EdgeLabel::s_verticalOffset = 0.5; 12 | 13 | EdgeLabel::EdgeLabel() 14 | : QGraphicsSimpleTextItem(nullptr) 15 | { 16 | // the label does not react to mouse events 17 | setAcceptHoverEvents(false); 18 | 19 | // cache the label 20 | setCacheMode(DeviceCoordinateCache); 21 | 22 | // define settings 23 | setOpacity(0.); // start out hidden 24 | setZValue(zStack::EDGE_LABEL); 25 | updateStyle(); 26 | } 27 | 28 | void EdgeLabel::setPos(qreal x, qreal y) 29 | { 30 | // apply offsets to the given position 31 | x -= boundingRect().width() * .5; 32 | y -= boundingRect().height() * (s_verticalOffset+.5); 33 | QGraphicsItem::setPos(x,y); 34 | } 35 | 36 | void EdgeLabel::updateStyle() 37 | { 38 | // update the font, if the static label font has changed 39 | if(font()!=s_font){ 40 | setFont(s_font); 41 | } 42 | 43 | // update the color, if the static label color has changed 44 | if(brush().color()!=s_color){ 45 | setBrush(s_color); 46 | } 47 | } 48 | 49 | } // namespace zodiac 50 | -------------------------------------------------------------------------------- /zodiacgraph/edgelabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_EDGELABEL_H 27 | #define ZODIAC_EDGELABEL_H 28 | 29 | /// 30 | /// \file edgelabel.h 31 | /// 32 | /// \brief Contains the definition of the zodiac::EdgeLabel class. 33 | /// 34 | 35 | #include 36 | #include 37 | 38 | namespace zodiac { 39 | 40 | /// 41 | /// \brief The label of a BaseEdge. 42 | /// 43 | /// EdgeLabel%s are only visible, when the mouse is hovering the BaseEdge or EdgeArrow. 44 | /// 45 | class EdgeLabel : public QGraphicsSimpleTextItem 46 | { 47 | 48 | public: // methods 49 | 50 | /// 51 | /// \brief Constructor. 52 | /// 53 | /// The EdgeLabel is a free-standing QGraphicsItem without a parent, even though it is closely managed by a 54 | /// BaseEdge instance. 55 | /// 56 | explicit EdgeLabel(); 57 | 58 | /// 59 | /// \brief Extends the QGraphcisSimpleTextItem by adding an offset in relation to the size of the label. 60 | /// 61 | /// The given position is the center of where the EdgeLabel is supposed to be. 62 | /// The unaltered QGraphicsSimpleTextItem implementation of setPos() would place the top left corner of the label at 63 | /// that position -- so we add an offset first. 64 | /// 65 | /// \param [in] x x-coordinate before offset. 66 | /// \param [in] y y-coordinate before offset. 67 | /// 68 | void setPos(qreal x, qreal y); 69 | 70 | /// 71 | /// \brief Applies style changes in the class' static members to this instance. 72 | /// 73 | /// Is part of the scene-wide cascade of %updateStyle()-calls after a re-styling of the ZodiacGraph. 74 | /// 75 | void updateStyle(); 76 | 77 | public: // static methods 78 | 79 | /// 80 | /// \brief Family of the font currently used to render the label text. 81 | /// 82 | /// \return Label font family. 83 | /// 84 | static inline QString getFontFamily() {return s_font.family();} 85 | 86 | /// 87 | /// \brief Defines a new font family to render the label text. 88 | /// 89 | /// \param [in] family New font family of the label. 90 | /// 91 | static inline void setFontFamily(const QString& family) {s_font.setFamily(family);} 92 | 93 | /// 94 | /// \brief Text size of the label in points. 95 | /// 96 | /// \return Label size in points. 97 | /// 98 | static inline qreal getPointSize() {return s_font.pointSizeF();} 99 | 100 | /// 101 | /// \brief Defines a new text size for the label in points. 102 | /// 103 | /// \param [in] pointSize New text size in points (fractions allowed). 104 | /// 105 | static inline void setPointSize(qreal pointSize) {s_font.setPointSizeF(qMax(0.,pointSize));} 106 | 107 | /// 108 | /// \brief Current weight (=boldness) of the font used to render the label text. 109 | /// 110 | /// \return Label font weight. 111 | /// 112 | static inline int getWeight() {return s_font.weight();} 113 | 114 | /// 115 | /// \brief Defines the weight (=boldness) of the font used to render the label text. 116 | /// 117 | /// See the Qt documentation for available 118 | /// input values. 119 | /// 120 | /// \param [in] weight New weight of the label font [0 -> 100]. 121 | /// 122 | static inline void setWeight(QFont::Weight weight) {s_font.setWeight(weight);} 123 | 124 | /// 125 | /// \brief Color to render the label text. 126 | /// 127 | /// \return Label color. 128 | /// 129 | static inline QColor getColor() {return s_color;} 130 | 131 | /// 132 | /// \brief Defines the color used to render the label text. 133 | /// 134 | /// \param [in] color New color to use for rendering the label text. 135 | /// 136 | static inline void setColor(const QColor& color) {s_color=color;} 137 | 138 | /// 139 | /// \brief Transparency (alpha) of the of the label text. 140 | /// 141 | /// \return Label transparency (alpha) [0 -> 1]. 142 | /// 143 | static inline qreal getTransparency() {return s_color.alphaF();} 144 | 145 | /// 146 | /// \brief Defines a new alpha value [0 -> 1] of the label text. 147 | /// 148 | /// \param [in] alpha New alpha value [0 -> 1] of the label text. 149 | /// 150 | static inline void setTransparency(qreal alpha) {s_color.setAlphaF(alpha);} 151 | 152 | /// 153 | /// \brief Vertical offset of the label text to the position given with setPos. 154 | /// 155 | /// See zodiac::EdgeLabel::setVerticalOffset() for details. 156 | /// 157 | /// \return Vertical label offset, in heights of the label 158 | /// 159 | static inline qreal getVerticalOffset() {return s_verticalOffset;} 160 | 161 | /// 162 | /// \brief Vertical offset of the label to the position given with setPos in heights of the label. 163 | /// 164 | /// A value of 0.0 centers the label on the position given with setPos().
165 | /// A value of 0.5 sets the bottom of the label at the position given with setPos().
166 | /// A value of -0.5 sets the top of the label at the position given with setPos().
167 | /// 168 | /// \param [in] offset New vertical offset, in heights of the label 169 | /// 170 | static inline void setVerticalOffset(qreal offset) {s_verticalOffset=offset;} 171 | 172 | private: // static members 173 | 174 | /// 175 | /// \brief Font used to draw the label. 176 | /// 177 | static QFont s_font; 178 | 179 | /// 180 | /// \brief Color used to draw the label. 181 | /// 182 | static QColor s_color; 183 | 184 | /// 185 | /// \brief Vertical offset of the label, in heights of the label. 186 | /// 187 | static qreal s_verticalOffset; 188 | 189 | }; 190 | 191 | } // namespace zodiac 192 | 193 | #endif // ZODIAC_EDGELABEL_H 194 | -------------------------------------------------------------------------------- /zodiacgraph/labeltextfactory.cpp: -------------------------------------------------------------------------------- 1 | #include "labeltextfactory.h" 2 | 3 | #include "node.h" 4 | #include "plug.h" 5 | #include "plugedge.h" 6 | 7 | namespace zodiac { 8 | 9 | const QString LabelTextFactory::s_arrowChar = QString::fromUtf8(" \xE2\x96\xB6 "); 10 | const QString LabelTextFactory::s_dotChar = "."; 11 | const QString LabelTextFactory::s_whitespaceChar = " "; 12 | const QString LabelTextFactory::s_newlineChar = "\n"; 13 | const QString LabelTextFactory::s_horizontalLineChar = "—"; 14 | 15 | LabelTextFactory::LabelTextFactory(const QSet& edges) 16 | : m_namePairs(QList>()) 17 | , m_labelCount(edges.count()) 18 | , m_maxNameLength(0) 19 | { 20 | // create and store the label names 21 | m_namePairs.reserve(m_labelCount); 22 | for(const PlugEdge* edge : edges){ 23 | Plug* startPlug = edge->getStartPlug(); 24 | Plug* endPlug = edge->getEndPlug(); 25 | QString fromName = startPlug->getNode()->getDisplayName() + s_dotChar + startPlug->getName(); 26 | QString toName = endPlug->getNode()->getDisplayName() + s_dotChar + endPlug->getName(); 27 | m_maxNameLength = qMax(m_maxNameLength, qMax(fromName.length(), toName.length())); 28 | m_namePairs.append(QPair(fromName, toName)); 29 | } 30 | } 31 | 32 | LabelTextFactory::LabelTextFactory(PlugEdge *edge) 33 | : LabelTextFactory(QSet({edge})) 34 | { 35 | } 36 | 37 | QString LabelTextFactory::produceLabel(int maxNameLength) const 38 | { 39 | // center the arrow by pre- or appending whitespace to the shorter name 40 | maxNameLength = qMax(maxNameLength, m_maxNameLength); 41 | QStringList labelStrings; 42 | labelStrings.reserve(m_labelCount); 43 | for(const QPair& labelPair : m_namePairs){ 44 | labelStrings.append( 45 | s_whitespaceChar.repeated(maxNameLength-labelPair.first.length()) 46 | + labelPair.first + s_arrowChar + labelPair.second + 47 | s_whitespaceChar.repeated(maxNameLength-labelPair.second.length())); 48 | } 49 | return labelStrings.join(s_newlineChar); 50 | } 51 | 52 | QString LabelTextFactory::getHorizontalLine(int maxNameLength) 53 | { 54 | /// 55 | /// \brief The horizontal line is always shorter than the longest label. 56 | /// 57 | /// Also, to ensure that it is centered, the number of characters in the horizontal line should be odd. 58 | /// Therefore, the next smaller, odd number with regards to the longest line is the base for the calculation. 59 | /// It may however be visually pleasing to remove more characters from both sides of the line. 60 | /// This is the number of characters removed from both sides after the next smaller, odd number was found. 61 | /// 62 | static const int UNDERLINE_SHORTAGE = 1; 63 | 64 | /// 65 | /// \brief Maximal length of the horizontal line in characters. 66 | /// 67 | static const int MAX_LENGTH = 7; 68 | 69 | int lineLength = (maxNameLength*2)+s_arrowChar.length(); 70 | int repeats = qMin(MAX_LENGTH, lineLength-((lineLength%2)+1+(UNDERLINE_SHORTAGE*2))); 71 | int whitespace = lineLength-repeats; 72 | int spacesBefore = whitespace/2; 73 | return s_whitespaceChar.repeated(spacesBefore) + 74 | s_horizontalLineChar.repeated(repeats) + 75 | s_whitespaceChar.repeated(spacesBefore+(whitespace%2)); 76 | } 77 | 78 | } // namespace zodiac 79 | 80 | -------------------------------------------------------------------------------- /zodiacgraph/labeltextfactory.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_LABELTEXTFACTORY_H 27 | #define ZODIAC_LABELTEXTFACTORY_H 28 | 29 | /// 30 | /// \file labeltextfactory.h 31 | /// 32 | /// \brief Contains the definition of the zodiac::LabelTextFactory class. 33 | /// 34 | 35 | #include 36 | #include 37 | 38 | namespace zodiac { 39 | 40 | class PlugEdge; 41 | 42 | /// 43 | /// \brief The LabelTextFactory is a helper class for creating label texts. 44 | /// 45 | /// The challenge is to produce text that appears centered around the arrow denoting the direction of the edge flow. 46 | /// 47 | class LabelTextFactory 48 | { 49 | 50 | public: // methods 51 | 52 | /// 53 | /// \brief Constructor. 54 | /// 55 | /// \param [in] edges The PlugEdge%s used to create the label text. 56 | /// 57 | explicit LabelTextFactory(const QSet& edges); 58 | 59 | /// 60 | /// \brief Constructor overload. 61 | /// 62 | /// \param [in] edge Single edge for which to create the labe. 63 | /// 64 | explicit LabelTextFactory(PlugEdge* edge); 65 | 66 | /// 67 | /// \brief Produces the label string. 68 | /// 69 | /// You can use the maxNameLength parameter to override the length of the longest name in the label. 70 | /// This is useful for EdgeGroupPair%s that have two LabelTextFactory%s that have to produce a single label that is 71 | /// centered around the arrow from both. 72 | /// 73 | /// \param [in] maxNameLength (optional) Overrides the default length of the longest name 74 | /// 75 | /// \return Label string. 76 | /// 77 | QString produceLabel(int maxNameLength = 0) const; 78 | 79 | /// 80 | /// \brief The number of individual labels in this factory. 81 | /// 82 | /// \return Number of labels. 83 | /// 84 | int getLabelCount() const {return m_labelCount;} 85 | 86 | /// 87 | /// \brief The number of characters in the longest name of all labels. 88 | /// 89 | /// \return Maximum name length. 90 | /// 91 | int getMaxNameLength() const {return m_maxNameLength;} 92 | 93 | public: // static methods 94 | 95 | /// 96 | /// \brief The newline-character used in the LabelTextFactory. 97 | /// 98 | /// Is the default one (\\n), but just in case, we have a single point of access for it. 99 | /// 100 | /// \return The newline-character. 101 | /// 102 | static const QString& getNewlineChar() {return s_newlineChar;} 103 | 104 | /// 105 | /// \brief Produces a horizontal line that can be used to separate the output of several LabelTextFactory%s. 106 | /// 107 | /// The maxNameLength paramter ensures that the length of the horizontal line does not exceed the rest of the label. 108 | /// 109 | /// \param [in] maxNameLength The length of the longest name. 110 | /// 111 | /// \return Unbroken, horizontal line, vertically centered. 112 | /// 113 | static QString getHorizontalLine(int maxNameLength); 114 | 115 | private: // members 116 | 117 | /// 118 | /// \brief List of a pair of label names. 119 | /// 120 | /// A name is one half of the label -- two names are connected using an arrow. 121 | /// 122 | QList> m_namePairs; 123 | 124 | /// 125 | /// \brief The number of labels taken into account. 126 | /// 127 | int m_labelCount; 128 | 129 | /// 130 | /// \brief Character count of the longest name in all labels. 131 | /// 132 | int m_maxNameLength; 133 | 134 | private: // static members 135 | 136 | /// 137 | /// \brief Character denoting the flow direction in an EdgeLabel. 138 | /// 139 | /// Should be something like: " -> " (including spaces) to produce: foo.a -> bar.b. 140 | /// 141 | static const QString s_arrowChar; 142 | 143 | /// 144 | /// \brief Character denoting the scope of a Plug in an EdgeLabel. 145 | /// 146 | /// Should be something like: "." to produce: foo.a -> bar.b. 147 | /// 148 | static const QString s_dotChar; 149 | 150 | /// 151 | /// \brief Character for whitespace fill in an EdgeLabel. 152 | /// 153 | /// If found a simple space works without problems. 154 | /// 155 | static const QString s_whitespaceChar; 156 | 157 | /// 158 | /// \brief Character for a linebreak in an EdgeLabel. 159 | /// 160 | static const QString s_newlineChar; 161 | 162 | /// 163 | /// \brief Character for an (somewhat) uninterrupted horizontal line. 164 | /// 165 | static const QString s_horizontalLineChar; 166 | 167 | }; 168 | 169 | } // namespace zodiac 170 | 171 | #endif // ZODIAC_LABELTEXTFACTORY_H 172 | -------------------------------------------------------------------------------- /zodiacgraph/nodehandle.cpp: -------------------------------------------------------------------------------- 1 | #include "nodehandle.h" 2 | 3 | #include "node.h" 4 | #include "plug.h" 5 | #include "scene.h" 6 | #include "scenehandle.h" 7 | 8 | namespace zodiac { 9 | 10 | NodeHandle::NodeHandle(Node* node) 11 | : QObject(nullptr) 12 | , m_node(node) 13 | , m_isValid(node!=nullptr) 14 | { 15 | connectSignals(); 16 | } 17 | 18 | NodeHandle& NodeHandle::operator = (const NodeHandle& other) 19 | { 20 | if(m_node){ 21 | m_node->disconnect(this); 22 | } 23 | m_node = other.data(); 24 | m_isValid = m_node != nullptr; 25 | connectSignals(); 26 | return *this; 27 | } 28 | 29 | bool NodeHandle::isRemovable() const 30 | { 31 | #ifdef QT_DEBUG 32 | Q_ASSERT(m_isValid); 33 | #else 34 | if(!m_isValid){ 35 | return false; 36 | } 37 | #endif 38 | return m_node->isRemovable(); 39 | } 40 | 41 | bool NodeHandle::remove() 42 | { 43 | #ifdef QT_DEBUG 44 | Q_ASSERT(m_isValid); 45 | #else 46 | if(!m_isValid){ 47 | return false; 48 | } 49 | #endif 50 | if(m_node->getScene()->removeNode(m_node)){ 51 | m_isValid = false; 52 | return true; 53 | } else { 54 | return false; 55 | } 56 | } 57 | 58 | const QUuid& NodeHandle::getId() const 59 | { 60 | #ifdef QT_DEBUG 61 | Q_ASSERT(m_isValid); 62 | #else 63 | if(!m_isValid){ 64 | QUuid(); 65 | } 66 | #endif 67 | return m_node->getUniqueId(); 68 | } 69 | 70 | QString NodeHandle::getName() const 71 | { 72 | #ifdef QT_DEBUG 73 | Q_ASSERT(m_isValid); 74 | #else 75 | if(!m_isValid){ 76 | return ""; 77 | } 78 | #endif 79 | return m_node->getDisplayName(); 80 | } 81 | 82 | void NodeHandle::rename(const QString& name) 83 | { 84 | #ifdef QT_DEBUG 85 | Q_ASSERT(m_isValid); 86 | #else 87 | if(!m_isValid){ 88 | return; 89 | } 90 | #endif 91 | m_node->setDisplayName(name); 92 | } 93 | 94 | PlugHandle NodeHandle::createIncomingPlug(const QString& name) 95 | { 96 | #ifdef QT_DEBUG 97 | Q_ASSERT(m_isValid); 98 | #else 99 | if(!m_isValid){ 100 | PlugHandle(); 101 | } 102 | #endif 103 | return PlugHandle(m_node->createPlug(name, PlugDirection::IN)); 104 | } 105 | 106 | PlugHandle NodeHandle::createOutgoingPlug(const QString& name) 107 | { 108 | #ifdef QT_DEBUG 109 | Q_ASSERT(m_isValid); 110 | #else 111 | if(!m_isValid){ 112 | PlugHandle(); 113 | } 114 | #endif 115 | return PlugHandle(m_node->createPlug(name, PlugDirection::OUT)); 116 | } 117 | 118 | QList NodeHandle::getPlugs() const 119 | { 120 | QList result; 121 | #ifdef QT_DEBUG 122 | Q_ASSERT(m_isValid); 123 | #else 124 | if(!m_isValid){ 125 | return result; 126 | } 127 | #endif 128 | QList plugs = m_node->getPlugs(); 129 | result.reserve(plugs.size()); 130 | for(Plug* plug : plugs){ 131 | result.append(PlugHandle(plug)); 132 | } 133 | return result; 134 | } 135 | 136 | PlugHandle NodeHandle::getPlug(const QString& name) const 137 | { 138 | #ifdef QT_DEBUG 139 | Q_ASSERT(m_isValid); 140 | #else 141 | if(!m_isValid){ 142 | return PlugHandle(); 143 | } 144 | #endif 145 | return PlugHandle(m_node->getPlug(name)); 146 | } 147 | 148 | void NodeHandle::setSelected(bool isSelected) 149 | { 150 | #ifdef QT_DEBUG 151 | Q_ASSERT(m_isValid); 152 | #else 153 | if(!m_isValid){ 154 | return; 155 | } 156 | #endif 157 | m_node->setSelected(isSelected); 158 | } 159 | 160 | SceneHandle NodeHandle::getScene() const 161 | { 162 | #ifdef QT_DEBUG 163 | Q_ASSERT(m_isValid); 164 | #else 165 | if(!m_isValid){ 166 | return SceneHandle(); 167 | } 168 | #endif 169 | return SceneHandle(m_node->getScene()); 170 | } 171 | 172 | QPointF NodeHandle::getPos() const 173 | { 174 | #ifdef QT_DEBUG 175 | Q_ASSERT(m_isValid); 176 | #else 177 | if(!m_isValid){ 178 | return QPointF(); 179 | } 180 | #endif 181 | return m_node->pos(); 182 | } 183 | 184 | void NodeHandle::setPos(qreal x, qreal y) 185 | { 186 | #ifdef QT_DEBUG 187 | Q_ASSERT(m_isValid); 188 | #else 189 | if(!m_isValid){ 190 | return; 191 | } 192 | #endif 193 | m_node->setPos(x, y); 194 | } 195 | 196 | void NodeHandle::connectSignals() 197 | { 198 | if(!m_isValid){ 199 | return; 200 | } 201 | connect(m_node, SIGNAL(destroyed()), this, SLOT(nodeWasDestroyed())); 202 | connect(m_node, SIGNAL(nodeActivated()), this, SIGNAL(nodeActivated())); 203 | connect(m_node, SIGNAL(nodeRenamed(QString)), this, SIGNAL(nodeRenamed(QString))); 204 | connect(m_node, SIGNAL(removalRequested()), this, SIGNAL(removalRequested())); 205 | connect(m_node, SIGNAL(inputConnected(Plug*,Plug*)), this, SLOT(passInputConnected(Plug*,Plug*))); 206 | connect(m_node, SIGNAL(inputDisconnected(Plug*,Plug*)), this, SLOT(passInputDisconnected(Plug*,Plug*))); 207 | connect(m_node, SIGNAL(outputConnected(Plug*,Plug*)), this, SLOT(passOutputConnected(Plug*,Plug*))); 208 | connect(m_node, SIGNAL(outputDisconnected(Plug*,Plug*)), this, SLOT(passOutputDisconnected(Plug*,Plug*))); 209 | } 210 | 211 | void NodeHandle::passInputConnected(Plug* myInput, Plug* otherOutput) 212 | { 213 | #ifdef QT_DEBUG 214 | Q_ASSERT(m_isValid); 215 | #endif 216 | emit inputConnected(PlugHandle(myInput), PlugHandle(otherOutput)); 217 | } 218 | 219 | void NodeHandle::passOutputConnected(Plug* myOutput, Plug* otherInput) 220 | { 221 | #ifdef QT_DEBUG 222 | Q_ASSERT(m_isValid); 223 | #endif 224 | emit outputConnected(PlugHandle(myOutput), PlugHandle(otherInput)); 225 | } 226 | 227 | void NodeHandle::passInputDisconnected(Plug* myInput, Plug* otherOutput) 228 | { 229 | #ifdef QT_DEBUG 230 | Q_ASSERT(m_isValid); 231 | #endif 232 | emit inputDisconnected(PlugHandle(myInput), PlugHandle(otherOutput)); 233 | } 234 | 235 | void NodeHandle::passOutputDisconnected(Plug* myOutput, Plug* otherInput) 236 | { 237 | #ifdef QT_DEBUG 238 | Q_ASSERT(m_isValid); 239 | #endif 240 | emit outputDisconnected(PlugHandle(myOutput), PlugHandle(otherInput)); 241 | } 242 | 243 | void NodeHandle::nodeWasDestroyed() 244 | { 245 | #ifdef QT_DEBUG 246 | Q_ASSERT(m_isValid); 247 | #endif 248 | m_isValid = false; 249 | this->disconnect(); 250 | } 251 | 252 | } // namespace zodiac 253 | -------------------------------------------------------------------------------- /zodiacgraph/nodelabel.cpp: -------------------------------------------------------------------------------- 1 | #include "nodelabel.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "node.h" 9 | 10 | namespace zodiac { 11 | 12 | QColor NodeLabel::s_textColor = QColor("#ffffff"); 13 | QColor NodeLabel::s_backgroundColor = QColor("#426998"); 14 | QColor NodeLabel::s_lineColor = QColor("#cdcdcd"); 15 | qreal NodeLabel::s_outlineWidth = 1.5; 16 | uint NodeLabel::s_roundEdgeRadius = 8; 17 | qreal NodeLabel::s_verticalMargin = 2.; 18 | qreal NodeLabel::s_horizontalMargin = 4.; 19 | QFont NodeLabel::s_font = QFont("DejaVu Sans Mono", 9, QFont::DemiBold); 20 | QPen NodeLabel::s_linePen = QPen(QBrush(s_lineColor), s_outlineWidth); 21 | 22 | NodeLabel::NodeLabel(Node* parent) 23 | : QGraphicsObject(parent) 24 | { 25 | // core only expands if you hover above it, not above the label 26 | setAcceptHoverEvents(false); 27 | 28 | // cache the label 29 | setCacheMode(DeviceCoordinateCache); 30 | 31 | // get the text from the node 32 | setText(parent->getDisplayName()); 33 | } 34 | 35 | void NodeLabel::setText(const QString& text) 36 | { 37 | // update the text 38 | m_text = QStaticText(text); 39 | m_text.setTextFormat(Qt::PlainText); 40 | QTextOption textOption = QTextOption(Qt::AlignHCenter | Qt::AlignBaseline); 41 | textOption.setUseDesignMetrics(false); 42 | m_text.setTextOption(textOption); 43 | updateStyle(); 44 | } 45 | 46 | void NodeLabel::updateStyle() 47 | { 48 | prepareGeometryChange(); 49 | 50 | // update the text position 51 | m_text.prepare(QTransform(), s_font); 52 | QSizeF textSize = m_text.size(); 53 | m_textPos = QPointF(textSize.width()/-2., textSize.height()/-2.); 54 | 55 | // update the label 56 | QSizeF labelSize = QSizeF(qMax(textSize.width(), Node::getCoreRadius()*2.), textSize.height()); 57 | m_outlineRect = QRectF((labelSize.width()/-2.)-s_horizontalMargin, m_textPos.y()-s_verticalMargin, 58 | labelSize.width()+(s_horizontalMargin*2.), textSize.height()+(s_verticalMargin*2.)); 59 | 60 | // update bounding rectangle 61 | qreal halfLine = s_outlineWidth*0.5; 62 | m_boundingRect = m_outlineRect.marginsAdded(QMarginsF(halfLine, halfLine, halfLine, halfLine)); 63 | 64 | update(); 65 | } 66 | 67 | QRectF NodeLabel::boundingRect() const 68 | { 69 | return m_boundingRect; 70 | } 71 | 72 | void NodeLabel::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* /* widget */) 73 | { 74 | painter->setClipRect(option->exposedRect); 75 | 76 | // draw the background 77 | painter->setPen(s_linePen); 78 | painter->setBrush(s_backgroundColor); 79 | painter->drawRoundedRect(m_outlineRect, s_roundEdgeRadius, s_roundEdgeRadius, Qt::AbsoluteSize); 80 | 81 | // draw the text 82 | painter->setFont(s_font); 83 | painter->setPen(s_textColor); 84 | painter->drawStaticText(m_textPos, m_text); 85 | } 86 | 87 | QPainterPath NodeLabel::shape() const 88 | { 89 | QPainterPath path; 90 | path.addRect(m_boundingRect); 91 | return path; 92 | } 93 | 94 | void NodeLabel::mousePressEvent(QGraphicsSceneMouseEvent* event) 95 | { 96 | QPointF p = event->pos(); 97 | if((p.x()*p.x())+(p.y()*p.y())<=(Node::getCoreRadius() * Node::getCoreRadius())){ 98 | // if the event is label was clicked inside the core, let the parent handle it 99 | event->ignore(); 100 | 101 | } else { 102 | // otherwise adjust the current selection "on-foot", since we cannot fall back on Qt's own selection mechanism 103 | if(event->modifiers() & Qt::ControlModifier){ 104 | parentItem()->setSelected(!parentItem()->isSelected()); // multi-selection 105 | } else { 106 | for(QGraphicsItem* item : scene()->selectedItems()){ // single selection 107 | item->setSelected(false); 108 | } 109 | parentItem()->setSelected(true); 110 | } 111 | event->accept(); 112 | } 113 | } 114 | 115 | } // namespace zodiac 116 | -------------------------------------------------------------------------------- /zodiacgraph/perimeter.cpp: -------------------------------------------------------------------------------- 1 | #include "perimeter.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "plug.h" 8 | #include "utils.h" 9 | #include "node.h" 10 | #include "view.h" 11 | 12 | namespace zodiac { 13 | 14 | qreal Perimeter::s_minRadius = 55; 15 | qreal Perimeter::s_maxOpacity = 0.5; 16 | QColor Perimeter::s_color = QColor("#2b517d"); 17 | Plug* Perimeter::s_closestPlugToMouse = nullptr; 18 | bool Perimeter::s_mouseWasDragged = false; 19 | 20 | Perimeter::Perimeter(Node* parent) 21 | : QGraphicsObject(parent) 22 | , m_node(parent) 23 | , m_radius(s_minRadius) 24 | { 25 | // the perimeter needs to stack behind the parent 26 | setFlag(ItemStacksBehindParent); 27 | setCacheMode(DeviceCoordinateCache); 28 | 29 | // doesn't do anything with them, but needs to be enabled so they get send up to the node 30 | setAcceptHoverEvents(true); 31 | 32 | // only becomes visible as it fades in 33 | setOpacity(0.); 34 | } 35 | 36 | QRectF Perimeter::boundingRect() const 37 | { 38 | return quadrat(m_radius); 39 | } 40 | 41 | void Perimeter::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* /* widget */) 42 | { 43 | painter->setClipRect(option->exposedRect); 44 | 45 | // draw perimeter 46 | painter->setBrush(s_color); 47 | painter->setPen(Qt::NoPen); 48 | painter->drawEllipse(quadrat(m_radius)); 49 | } 50 | 51 | QPainterPath Perimeter::shape() const 52 | { 53 | QPainterPath path; 54 | path.addEllipse(quadrat(m_radius)); 55 | return path; 56 | } 57 | 58 | void Perimeter::hoverMoveEvent(QGraphicsSceneHoverEvent* event) 59 | { 60 | Plug* closestPlug = m_node->getClosestPlugTo(event->pos(), PlugDirection::BOTH); 61 | if(closestPlug && closestPlug->isVisible()){ 62 | if(closestPlug!=s_closestPlugToMouse){ 63 | if(s_closestPlugToMouse){ 64 | s_closestPlugToMouse->setHighlight(false); 65 | } 66 | closestPlug->setHighlight(true); 67 | s_closestPlugToMouse=closestPlug; 68 | } 69 | } else if(s_closestPlugToMouse){ 70 | s_closestPlugToMouse->setHighlight(false); 71 | s_closestPlugToMouse = nullptr; 72 | } 73 | QGraphicsObject::hoverMoveEvent(event); 74 | } 75 | 76 | void Perimeter::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) 77 | { 78 | if(s_closestPlugToMouse){ 79 | s_closestPlugToMouse->setHighlight(false); 80 | s_closestPlugToMouse = nullptr; 81 | } 82 | QGraphicsObject::hoverLeaveEvent(event); 83 | } 84 | 85 | void Perimeter::mousePressEvent(QGraphicsSceneMouseEvent* event) 86 | { 87 | s_mouseWasDragged = false; 88 | if((event->buttons() & View::getSelectionButton()) && (s_closestPlugToMouse)){ 89 | s_closestPlugToMouse->aquireDrawEdge(); 90 | return; 91 | } 92 | 93 | QGraphicsObject::mousePressEvent(event); 94 | } 95 | 96 | void Perimeter::mouseMoveEvent(QGraphicsSceneMouseEvent* event) 97 | { 98 | s_mouseWasDragged = true; 99 | if(s_closestPlugToMouse){ 100 | s_closestPlugToMouse->advanceDrawEdge(event->scenePos()); 101 | } 102 | QGraphicsObject::mouseMoveEvent(event); 103 | } 104 | 105 | void Perimeter::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) 106 | { 107 | if(s_closestPlugToMouse){ 108 | // stop drawing the edge if the mouse was dragged 109 | s_closestPlugToMouse->releaseDrawEdge(); 110 | s_closestPlugToMouse->setHighlight(false); 111 | s_closestPlugToMouse=nullptr; 112 | } 113 | 114 | if((!s_mouseWasDragged) && (event->button() & View::getSelectionButton())){ 115 | // toggle node expansion on click 116 | m_node->toggleExpansion(); 117 | } 118 | 119 | QGraphicsObject::mouseReleaseEvent(event); 120 | } 121 | 122 | void Perimeter::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) 123 | { 124 | if(event->button() == View::getSelectionButton()){ 125 | m_node->toggleForcedExpansion(); 126 | } 127 | 128 | // calling the superclass is mandatory here, to treat doubleclick like normal click PLUS something else. 129 | // othwerise the draw edge does not get aquired properly 130 | return QGraphicsObject::mouseDoubleClickEvent(event); 131 | } 132 | 133 | } // namespace zodiac 134 | -------------------------------------------------------------------------------- /zodiacgraph/plugarranger.cpp: -------------------------------------------------------------------------------- 1 | #include "plugarranger.h" 2 | 3 | #include // DBL_MAX 4 | 5 | int getProblemRows(QVector& problemRows, const QVector& guess, const int rowCount) 6 | { 7 | int count = 0; 8 | for(int guessIndex = 0; guessIndex < rowCount; ++guessIndex){ 9 | if(guess.count(guess[guessIndex]) > 1){ 10 | problemRows[count++] = guessIndex; 11 | } 12 | } 13 | return count; 14 | } 15 | 16 | namespace zodiac { 17 | 18 | QVector arrangePlugs(const QVector& costTable, const int rowCount, const int columnCount) 19 | { 20 | // 21 | // create best possible solution as first guess - is most likely invalid 22 | QVector guess = QVector(rowCount); 23 | for(int row = 0; row < rowCount; ++row){ 24 | qreal minCost = DBL_MAX; 25 | for(int col = 0; col < columnCount; ++col){ 26 | qreal cell = costTable[(columnCount*row)+col]; 27 | if(cell < minCost){ 28 | minCost = cell; 29 | guess[row] = col; 30 | } 31 | } 32 | } 33 | 34 | // 35 | // find rows that have the smallest cost in the same column 36 | QVector problemRows = QVector(rowCount); 37 | int problemRowCount = getProblemRows(problemRows, guess, rowCount); 38 | 39 | // 40 | // find the empty columns 41 | QVector emptyColumns = QVector(columnCount-1); 42 | int emptyColumnCount = 0; 43 | for(int col = 0; col < columnCount; ++col){ 44 | if(!guess.contains(col)){ 45 | emptyColumns[emptyColumnCount++] = col; 46 | } 47 | } 48 | 49 | QVector deltaTable = QVector(rowCount * (columnCount-1)); 50 | while(problemRowCount > 0){ 51 | 52 | // 53 | // create the delta table 54 | for(int rowIndex = 0; rowIndex < problemRowCount; ++rowIndex){ 55 | int row = problemRows[rowIndex]; 56 | for(int colIndex = 0; colIndex < emptyColumnCount; ++colIndex){ 57 | int col = emptyColumns[colIndex]; 58 | deltaTable[(emptyColumnCount*rowIndex)+colIndex] = 59 | costTable[(columnCount*row)+col] - costTable[(columnCount*row)+guess[row]]; 60 | } 61 | } 62 | 63 | // 64 | // improve the guess by one problem row 65 | { 66 | qreal minCost = DBL_MAX; 67 | int inRow; 68 | int inCol; 69 | for(int row = 0; row < problemRowCount; ++row){ 70 | for(int col = 0; col < emptyColumnCount; ++col){ 71 | qreal cell = deltaTable[(emptyColumnCount*row)+col]; 72 | if(cell < minCost){ 73 | minCost = cell; 74 | inRow = row; 75 | inCol = col; 76 | } 77 | } 78 | } 79 | guess[problemRows[inRow]] = emptyColumns[inCol]; 80 | 81 | // remove the empty column 82 | --emptyColumnCount; 83 | for(int col = inCol; col < emptyColumnCount; ++col){ 84 | emptyColumns[col] = emptyColumns[col+1]; 85 | } 86 | } 87 | 88 | // update the problemRows 89 | problemRowCount = getProblemRows(problemRows, guess, rowCount); 90 | } 91 | 92 | // 93 | // trivial optimization by swapping where favorable 94 | for(int leftIndex = 0; leftIndex < rowCount; ++leftIndex){ 95 | for(int rightIndex = leftIndex+1; rightIndex < rowCount; ++rightIndex){ 96 | qreal currentSum = costTable[(columnCount*leftIndex)+guess[leftIndex]] + 97 | costTable[(columnCount*rightIndex)+guess[rightIndex]]; 98 | qreal swapSum = costTable[(columnCount*leftIndex)+guess[rightIndex]] + 99 | costTable[(columnCount*rightIndex)+guess[leftIndex]]; 100 | if(swapSum < currentSum){ 101 | qreal temp = guess[leftIndex]; 102 | guess[leftIndex] = guess[rightIndex]; 103 | guess[rightIndex] = temp; 104 | } 105 | } 106 | } 107 | 108 | return guess; 109 | } 110 | 111 | 112 | } // namespace zodiac 113 | -------------------------------------------------------------------------------- /zodiacgraph/plugarranger.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_PLUGARRANGER_H 27 | #define ZODIAC_PLUGARRANGER_H 28 | 29 | /// \file plugarranger.h 30 | /// 31 | /// \brief Contains the definition of the zodiac::arrangePlugs function. 32 | /// 33 | 34 | #include 35 | 36 | namespace zodiac { 37 | 38 | /// 39 | /// \brief Calculates the cheapest path through the cost-table. 40 | /// 41 | /// For example: 42 | /// With the given table: 43 | /// ~~~ 44 | /// |----|----|----| 45 | /// | 87 | 15 | 75 | 46 | /// | 41 | 32 | 68 | 47 | /// | 93 | 54 | 21 | 48 | /// |----|----|----| 49 | /// ~~~ 50 | /// 51 | /// This function would produce the Vector [1, 0, 2], indicating that the cheapest path is: 52 | /// ~~~ 53 | /// |----|----|----| 54 | /// | | 15 | | 55 | /// | 41 | | | 56 | /// | | | 21 | 57 | /// |----|----|----| 58 | /// ~~~ 59 | /// 60 | /// \param [in] costTable Reference to the cost table. 61 | /// \param [in] rowCount Numer of rows in the cost table. 62 | /// \param [in] columnCount Numer of columns in the cost table. 63 | /// 64 | /// \return Vector of column indices, ordered by row. 65 | /// 66 | QVector arrangePlugs(const QVector& costTable, const int rowCount, const int columnCount); 67 | 68 | } // namespace zodiac 69 | 70 | #endif // ZODIAC_PLUGARRANGER_H 71 | -------------------------------------------------------------------------------- /zodiacgraph/plugedge.cpp: -------------------------------------------------------------------------------- 1 | #include "plugedge.h" 2 | 3 | #include 4 | 5 | #include "edgegroup.h" 6 | #include "labeltextfactory.h" 7 | #include "node.h" 8 | #include "scene.h" 9 | #include "view.h" 10 | #include "plug.h" 11 | 12 | namespace zodiac { 13 | 14 | PlugEdge::PlugEdge(Scene* scene, Plug* startPlug, Plug* endPlug, EdgeGroup* edgeGroup) 15 | :BezierEdge(scene) 16 | , m_startPlug(startPlug) 17 | , m_endPlug(endPlug) 18 | , m_group(edgeGroup) 19 | , m_isBent(false) 20 | { 21 | // register with the plugs 22 | m_startPlug->addEdge(this); 23 | m_endPlug->addEdge(this); 24 | 25 | // register with your edge group 26 | m_group->addEdge(this); 27 | 28 | // create the label for this edge 29 | updateLabelText(); 30 | 31 | // initialize 32 | plugHasChanged(); 33 | } 34 | 35 | void PlugEdge::plugHasChanged() 36 | { 37 | // update the count of bent edges in the group, if necessary 38 | bool isBent = m_startPlug->isVisible() || m_endPlug->isVisible(); 39 | if(m_isBent!=isBent){ 40 | m_isBent = isBent; 41 | if(m_isBent){ 42 | m_group->increaseBentCount(); 43 | }else{ 44 | m_group->decreaseBentCount(); 45 | } 46 | } 47 | 48 | // return early, if the shape of the edge has not changed 49 | QPointF startPoint = m_startPlug->scenePos(); 50 | QPointF endPoint = m_endPlug->scenePos(); 51 | if((startPoint==m_startPoint)&&(endPoint==m_endPoint)){ 52 | return; 53 | } 54 | 55 | // update the edge's ctrl points 56 | m_startPoint = startPoint; 57 | m_endPoint = endPoint; 58 | m_ctrlPoint1 = getCtrlPointFor(m_startPlug); 59 | m_ctrlPoint2 = getCtrlPointFor(m_endPlug); 60 | 61 | // update the path 62 | updateShape(); 63 | } 64 | 65 | QString PlugEdge::getLabelText() 66 | { 67 | return LabelTextFactory(this).produceLabel(); 68 | } 69 | 70 | void PlugEdge::setLabelText(const QString& text) 71 | { 72 | BezierEdge::setLabelText(text); 73 | 74 | m_group->updateLabelText(); 75 | } 76 | 77 | void PlugEdge::mousePressEvent(QGraphicsSceneMouseEvent *event) 78 | { 79 | if(event->buttons() & View::getRemovalButton()){ 80 | // remove the edge on removal click 81 | event->accept(); 82 | m_scene->removeEdge(this); 83 | 84 | } else { 85 | // othwerise do whatever 86 | return BezierEdge::mousePressEvent(event); 87 | } 88 | } 89 | 90 | void PlugEdge::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) 91 | { 92 | if(event->buttons() & View::getSelectionButton()){ 93 | m_startPlug->getNode()->softSetExpansion(NodeExpansion::BOTH); 94 | m_endPlug->getNode()->softSetExpansion(NodeExpansion::BOTH); 95 | } 96 | BaseEdge::mouseDoubleClickEvent(event); 97 | } 98 | 99 | } // namespace zodiac 100 | -------------------------------------------------------------------------------- /zodiacgraph/plugedge.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_PLUGEDGE_H 27 | #define ZODIAC_PLUGEDGE_H 28 | 29 | /// \file plugedge.h 30 | /// 31 | /// \brief Contains the definition of the zodiac::PlugEdge class. 32 | /// 33 | 34 | #include "bezieredge.h" 35 | 36 | namespace zodiac { 37 | 38 | class EdgeGroup; 39 | class Scene; 40 | class Plug; 41 | 42 | /// 43 | /// \brief A PlugEdge in the Zodiac Graph connecting two Plug%s of different Node%s. 44 | /// 45 | /// This is the main edge class in the graph. 46 | /// However, most of its functionality resides in its base classes BezierEdge and BaseEdge. 47 | /// 48 | class PlugEdge : public BezierEdge 49 | { 50 | 51 | Q_OBJECT 52 | 53 | public: // methods 54 | 55 | /// 56 | /// \brief Constructor. 57 | /// 58 | /// \param [in] scene Scene containing this PlugEdge. 59 | /// \param [in] startPlug Plug from which this PlugEdge starts. 60 | /// \param [in] endPlug Plug into which this PlugEdge flows. 61 | /// \param [in] edgeGroup EdgeGroup that this PlugEdge belongs to. 62 | /// 63 | explicit PlugEdge(Scene* scene, Plug* startPlug, Plug* endPlug, EdgeGroup* edgeGroup); 64 | 65 | /// 66 | /// \brief Is called by a plug to notify the edge of a change in its state. 67 | /// 68 | void plugHasChanged(); 69 | 70 | /// 71 | /// \brief The start Plug of this PlugEdge is an \ref zodiac::PlugDirection::OUT "outgoing" Plug of a Node. 72 | /// 73 | /// \return Start Plug of this PlugEdge. 74 | /// 75 | inline Plug* getStartPlug() const {return m_startPlug;} 76 | 77 | /// 78 | /// \brief The end Plug of this PlugEdge is an \ref zodiac::PlugDirection::IN "incoming" Plug of a Node. 79 | /// 80 | /// \return End plug of this PlugEdge. 81 | /// 82 | inline Plug* getEndPlug() const {return m_endPlug;} 83 | 84 | /// 85 | /// \brief Every PlugEdge is part of an EdgeGroup managed by the Scene. 86 | /// 87 | /// The job of the EdgeGroup is to replace all PlugEdge%s flowing from Node A to Node B, once both 88 | /// Node%s are collapsed. 89 | /// At this stage, all PlugEdge%s are displayed as straight lines that completely overlay each other. 90 | /// This way, only a single edge is rendered and updated in the QGraphicsScene. 91 | /// 92 | /// \return EdgeGroup of this PlugEdge. 93 | /// 94 | inline EdgeGroup* getGroup() const {return m_group;} 95 | 96 | /// 97 | /// \brief Updates the EdgeLabel to reflect changes in the attached Plug%s and / or Node%s. 98 | /// 99 | inline void updateLabelText() {setLabelText(getLabelText());} 100 | 101 | /// 102 | /// \brief Generates the EdgeLabel's text by using information from the start and end Node of this PlugEdge. 103 | /// 104 | /// \return Text used for the EdgeLabel. 105 | /// 106 | QString getLabelText(); 107 | 108 | /// 109 | /// \brief Sets the text of this edge's EdgeLabel. 110 | /// 111 | /// Extends the base functionality by also taking care of renaming the EdgeGroup%s. 112 | /// 113 | /// \param [in] text Text to set this label to. Remove an existing EdgeLabel by passing "" (the empty string). 114 | /// 115 | virtual void setLabelText(const QString& text); 116 | 117 | protected: // methods 118 | 119 | /// 120 | /// \brief Called, when the mouse is pressed as the cursor is on this item. 121 | /// 122 | /// \param [in] event Qt event object. 123 | /// 124 | void mousePressEvent(QGraphicsSceneMouseEvent *event); 125 | 126 | /// 127 | /// \brief Called when this item is double-clicked. 128 | /// 129 | /// The user can expand both Node%s connected through this PlugEdge by double-clicking it. 130 | /// 131 | /// \param [in] event Qt event object. 132 | /// 133 | void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); 134 | 135 | private: // members 136 | 137 | /// 138 | /// \brief The start Plug of this PlugEdge. 139 | /// 140 | Plug* m_startPlug; 141 | 142 | /// 143 | /// \brief The end Plug of this PlugEdge. 144 | /// 145 | Plug* m_endPlug; 146 | 147 | /// 148 | /// \brief The EdgeGroup of this PlugEdge. 149 | /// 150 | EdgeGroup* m_group; 151 | 152 | /// 153 | /// \brief A PlugEdge is "bent" if at least one of its Plug%s is expanded from its Node. 154 | /// 155 | bool m_isBent; 156 | 157 | }; 158 | 159 | } // namespace zodiac 160 | 161 | #endif // ZODIAC_PLUGEDGE_H 162 | -------------------------------------------------------------------------------- /zodiacgraph/plughandle.cpp: -------------------------------------------------------------------------------- 1 | #include "plughandle.h" 2 | 3 | #include "node.h" 4 | #include "nodehandle.h" 5 | #include "plug.h" 6 | #include "plugedge.h" 7 | #include "scene.h" 8 | #include "scenehandle.h" 9 | 10 | namespace zodiac { 11 | 12 | PlugHandle::PlugHandle(Plug* plug) 13 | : QObject(nullptr) 14 | , m_plug(plug) 15 | , m_isValid(plug!=nullptr) 16 | { 17 | connectSignals(); 18 | } 19 | 20 | PlugHandle& PlugHandle::operator = (const PlugHandle& other) 21 | { 22 | if(m_plug){ 23 | m_plug->disconnect(this); 24 | } 25 | m_plug = other.data(); 26 | m_isValid = m_plug != nullptr; 27 | connectSignals(); 28 | return *this; 29 | } 30 | 31 | bool PlugHandle::isRemovable() const 32 | { 33 | #ifdef QT_DEBUG 34 | Q_ASSERT(m_isValid); 35 | #else 36 | if(!m_isValid){ 37 | return false; 38 | } 39 | #endif 40 | return m_plug->isRemovable(); 41 | } 42 | 43 | bool PlugHandle::remove() 44 | { 45 | #ifdef QT_DEBUG 46 | Q_ASSERT(m_isValid); 47 | #else 48 | if(!m_isValid){ 49 | return false; 50 | } 51 | #endif 52 | if(m_plug->getNode()->removePlug(m_plug)){ 53 | m_isValid = false; 54 | return true; 55 | } else { 56 | return false; 57 | } 58 | } 59 | 60 | QString PlugHandle::getName() const 61 | { 62 | #ifdef QT_DEBUG 63 | Q_ASSERT(m_isValid); 64 | #else 65 | if(!m_isValid){ 66 | return ""; 67 | } 68 | #endif 69 | return m_plug->getName(); 70 | } 71 | 72 | QString PlugHandle::rename(const QString& name) 73 | { 74 | #ifdef QT_DEBUG 75 | Q_ASSERT(m_isValid); 76 | #else 77 | if(!m_isValid){ 78 | return ""; 79 | } 80 | #endif 81 | return m_plug->getNode()->renamePlug(m_plug, name); 82 | } 83 | 84 | bool PlugHandle::toggleDirection() 85 | { 86 | #ifdef QT_DEBUG 87 | Q_ASSERT(m_isValid); 88 | #else 89 | if(!m_isValid){ 90 | return false; 91 | } 92 | #endif 93 | return m_plug->getNode()->togglePlugDirection(m_plug); 94 | } 95 | 96 | bool PlugHandle::isIncoming() const 97 | { 98 | #ifdef QT_DEBUG 99 | Q_ASSERT(m_isValid); 100 | #else 101 | if(!m_isValid){ 102 | return false; 103 | } 104 | #endif 105 | return m_plug->getDirection() == PlugDirection::IN; 106 | } 107 | 108 | bool PlugHandle::isOutgoing() const 109 | { 110 | #ifdef QT_DEBUG 111 | Q_ASSERT(m_isValid); 112 | #else 113 | if(!m_isValid){ 114 | return false; 115 | } 116 | #endif 117 | return m_plug->getDirection() == PlugDirection::OUT; 118 | } 119 | 120 | int PlugHandle::connectionCount() const 121 | { 122 | #ifdef QT_DEBUG 123 | Q_ASSERT(m_isValid); 124 | #else 125 | if(!m_isValid){ 126 | return 0; 127 | } 128 | #endif 129 | return m_plug->getEdgeCount(); 130 | } 131 | 132 | QList PlugHandle::getConnectedPlugs() const 133 | { 134 | #ifdef QT_DEBUG 135 | Q_ASSERT(m_isValid); 136 | #else 137 | if(!m_isValid){ 138 | return QList(); 139 | } 140 | #endif 141 | QList result; 142 | for(Plug* plug : m_plug->getConnectedPlugs()){ 143 | result.append(PlugHandle(plug)); 144 | } 145 | return result; 146 | } 147 | 148 | bool PlugHandle::connectPlug(PlugHandle other) 149 | { 150 | #ifdef QT_DEBUG 151 | Q_ASSERT(m_isValid); 152 | #else 153 | if(!m_isValid){ 154 | return false; 155 | } 156 | #endif 157 | if(m_plug->getDirection() == PlugDirection::OUT){ 158 | return m_plug->getNode()->getScene()->createEdge(m_plug, other.data()) != nullptr; 159 | } else { 160 | return m_plug->getNode()->getScene()->createEdge(other.data(), m_plug) != nullptr; 161 | } 162 | } 163 | 164 | 165 | bool PlugHandle::disconnectPlug(PlugHandle other) 166 | { 167 | #ifdef QT_DEBUG 168 | Q_ASSERT(m_isValid && other.isValid()); 169 | #else 170 | if(!m_isValid || !other.isValid()){ 171 | return false; 172 | } 173 | #endif 174 | PlugEdge* edge; 175 | Scene* scene = m_plug->getNode()->getScene(); 176 | if(m_plug->getDirection() == PlugDirection::OUT){ 177 | edge = scene->getEdge(m_plug, other.data()); 178 | } else { 179 | edge = scene->getEdge(other.data(), m_plug); 180 | } 181 | if(!edge){ 182 | return false; 183 | } 184 | scene->removeEdge(edge); 185 | return true; 186 | } 187 | 188 | void PlugHandle::disconnectAll() 189 | { 190 | #ifdef QT_DEBUG 191 | Q_ASSERT(m_isValid); 192 | #else 193 | if(!m_isValid){ 194 | return; 195 | } 196 | #endif 197 | Scene* scene = m_plug->getNode()->getScene(); 198 | for(Plug* plug : m_plug->getConnectedPlugs()){ 199 | PlugEdge* edge; 200 | if(m_plug->getDirection() == PlugDirection::OUT){ 201 | edge = scene->getEdge(m_plug, plug); 202 | } else { 203 | edge = scene->getEdge(plug, m_plug); 204 | } 205 | Q_ASSERT(edge); 206 | scene->removeEdge(edge); 207 | } 208 | } 209 | 210 | NodeHandle PlugHandle::getNode() const 211 | { 212 | #ifdef QT_DEBUG 213 | Q_ASSERT(m_isValid); 214 | #else 215 | if(!m_isValid){ 216 | return NodeHandle(); 217 | } 218 | #endif 219 | return NodeHandle(m_plug->getNode()); 220 | } 221 | 222 | SceneHandle PlugHandle::getScene() const 223 | { 224 | #ifdef QT_DEBUG 225 | Q_ASSERT(m_isValid); 226 | #else 227 | if(!m_isValid){ 228 | return SceneHandle(); 229 | } 230 | #endif 231 | return SceneHandle(m_plug->getNode()->getScene()); 232 | } 233 | 234 | void PlugHandle::connectSignals() 235 | { 236 | if(!m_isValid){ 237 | return; 238 | } 239 | connect(m_plug, SIGNAL(destroyed()), this, SLOT(plugWasDestroyed())); 240 | connect(m_plug, SIGNAL(plugRenamed(QString)), this, SIGNAL(plugRenamed(QString))); 241 | } 242 | 243 | void PlugHandle::plugWasDestroyed() 244 | { 245 | #ifdef QT_DEBUG 246 | Q_ASSERT(m_isValid); 247 | #endif 248 | m_isValid = false; 249 | this->disconnect(); 250 | } 251 | 252 | } // namespace zodiac 253 | 254 | -------------------------------------------------------------------------------- /zodiacgraph/pluglabel.cpp: -------------------------------------------------------------------------------- 1 | #include "pluglabel.h" 2 | 3 | #include // for M_PI 4 | #include 5 | #include 6 | 7 | #include "plug.h" 8 | 9 | namespace zodiac { 10 | 11 | QFont PlugLabel::s_font = QFont("DejaVu Sans Mono", 10, 75); 12 | qreal PlugLabel::s_labelDistance = 15.; 13 | QColor PlugLabel::s_color = QColor("#828688"); 14 | 15 | PlugLabel::PlugLabel(Plug* parent) 16 | : QGraphicsItem(parent) 17 | , m_plug(parent) 18 | , m_isHighlighted(false) 19 | { 20 | // the label does not react to mouse events 21 | setAcceptHoverEvents(false); 22 | 23 | // cache the label 24 | setCacheMode(DeviceCoordinateCache); 25 | 26 | // initialize 27 | setOpacity(0.); // to avoid flicker, the label starts out fully transparent 28 | updateShape(); 29 | } 30 | 31 | void PlugLabel::updateShape() 32 | { 33 | prepareGeometryChange(); 34 | 35 | // update the text 36 | m_text = QStaticText(m_plug->getName()); 37 | m_text.setTextFormat(Qt::PlainText); 38 | m_text.prepare(QTransform(), s_font); // prepare once to get the dimensions of the text 39 | 40 | // update the label transformation 41 | QVector2D normal = m_plug->getNormal(); 42 | qreal angle = atan2(-normal.y(), normal.x()); 43 | QSizeF textSize = m_text.size(); 44 | qreal xOffset; 45 | if(normal.x()>0){ // label is on the right 46 | xOffset = s_labelDistance; 47 | } else { // label is on the left 48 | xOffset = -textSize.width()-s_labelDistance; 49 | angle+=M_PI; 50 | } 51 | m_transform.reset(); 52 | m_transform.rotateRadians(-angle); 53 | m_transform.translate(xOffset, textSize.height()/-2.); 54 | 55 | m_text.prepare(m_transform, s_font); // prepare a second time with the correct transform matrix 56 | 57 | // update the bounding rect 58 | m_boundingRect = m_transform.mapRect(QRectF(0, 0, textSize.width(), textSize.height())); 59 | } 60 | 61 | void PlugLabel::updateStyle() 62 | { 63 | updateShape(); 64 | update(); 65 | } 66 | 67 | QRectF PlugLabel::boundingRect() const 68 | { 69 | return m_boundingRect; 70 | } 71 | 72 | void PlugLabel::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* /*widget*/) 73 | { 74 | painter->setClipRect(option->exposedRect); 75 | painter->setTransform(m_transform * painter->transform()); 76 | painter->setFont(s_font); 77 | painter->setPen(QPen(m_isHighlighted?Plug::getHighlightColor():s_color)); 78 | painter->drawStaticText(0,0,m_text); 79 | } 80 | 81 | QPainterPath PlugLabel::shape() const 82 | { 83 | QPainterPath path; 84 | path.addRect(m_boundingRect); 85 | return path; 86 | } 87 | 88 | } // namespace zodiac 89 | -------------------------------------------------------------------------------- /zodiacgraph/pluglabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_PLUGLABEL_H 27 | #define ZODIAC_PLUGLABEL_H 28 | 29 | /// 30 | /// \file pluglabel.h 31 | /// 32 | /// \brief Contains the definition of the zodiac::PlugLabel class. 33 | /// 34 | 35 | #include 36 | #include 37 | #include 38 | 39 | namespace zodiac { 40 | 41 | class Plug; 42 | 43 | /// 44 | /// \brief The label of a Plug. 45 | /// 46 | /// It is placed on the outside of the Plug and fades in and out with the Plug's expansion. 47 | /// A PlugLabel does not react to mouse events. 48 | /// 49 | class PlugLabel : public QGraphicsItem 50 | { 51 | 52 | public: // methods 53 | 54 | /// 55 | /// \brief Constructor. 56 | /// 57 | /// \param [in] parent Plug item parenting this label. 58 | /// 59 | explicit PlugLabel(Plug* parent); 60 | 61 | /// 62 | /// \brief Updates the Plug's label text and transformation using information from its Plug. 63 | /// 64 | void updateShape(); 65 | 66 | /// 67 | /// \brief Defines, whether to draw the PlugLabel as highlighted or not. 68 | /// 69 | /// \param [in] highlight true if the label is highlighted, false if it is not. 70 | /// 71 | inline void setHighlight(bool highlight) {m_isHighlighted=highlight; update();} 72 | 73 | /// 74 | /// \brief Applies style changes in the class' static members to this instance. 75 | /// 76 | /// Is part of the scene-wide cascade of %updateStyle()-calls after a re-styling of the ZodiacGraph. 77 | /// 78 | void updateStyle(); 79 | 80 | public: // static methods 81 | 82 | /// 83 | /// \brief Family of the font currently used to render the label text. 84 | /// 85 | /// \return Label font family. 86 | /// 87 | static inline QString getFontFamily() {return s_font.family();} 88 | 89 | /// 90 | /// \brief Defines a new font family to render the label text. 91 | /// 92 | /// \param [in] family New font family of the label. 93 | /// 94 | static inline void setFontFamily(const QString& family) {s_font.setFamily(family);} 95 | 96 | /// 97 | /// \brief Text size of the label in points. 98 | /// 99 | /// \return Label size in points. 100 | /// 101 | static inline qreal getPointSize() {return s_font.pointSizeF();} 102 | 103 | /// 104 | /// \brief Defines a new text size for the label in points. 105 | /// 106 | /// \param [in] pointSize New text size in points (fractions allowed). 107 | /// 108 | static inline void setPointSize(qreal pointSize) {s_font.setPointSizeF(qMax(0.,pointSize));} 109 | 110 | /// 111 | /// \brief Current weight (=boldness) of the font used to render the label text. 112 | /// 113 | /// \return Label font weight. 114 | /// 115 | static inline int getWeight() {return s_font.weight();} 116 | 117 | /// 118 | /// \brief Defines the weight (=boldness) of the font used to render the label text. 119 | /// 120 | /// See the Qt documentation for available 121 | /// input values. 122 | /// 123 | /// \param [in] weight New weight of the label font [0 -> 100]. 124 | /// 125 | static inline void setWeight(QFont::Weight weight) {s_font.setWeight(weight);} 126 | 127 | /// 128 | /// \brief Color to render the label text. 129 | /// 130 | /// \return Label color. 131 | /// 132 | static inline QColor getColor() {return s_color;} 133 | 134 | /// 135 | /// \brief Defines the color to render the label text. 136 | /// 137 | /// \\param [in] color Label color. 138 | /// 139 | static inline void setColor(const QColor& color) {s_color = color;} 140 | 141 | /// 142 | /// \brief Distance from the label to the Plug in pixels. 143 | /// 144 | /// \return Plug to PlugLabel distance in pixels 145 | /// 146 | static inline qreal getLabelDistance() {return s_labelDistance;} 147 | 148 | /// 149 | /// \brief Define a new distance from the PlugLabel to its Plug in pixels. 150 | /// 151 | /// \param [in] distance New distance from the PlugLabel to its Plug in pixels. 152 | /// 153 | static inline void setLabelDistance(qreal distance) {s_labelDistance=distance;} 154 | 155 | protected: // methods 156 | 157 | /// 158 | /// \brief Rectangular outer bounds of the item, used for redraw testing. 159 | /// 160 | /// \return Boundary rectangle of the item. 161 | /// 162 | QRectF boundingRect() const; 163 | 164 | /// 165 | /// \brief Paints this item. 166 | /// 167 | /// \param [in] painter Painter used to paint the item. 168 | /// \param [in] option Provides style options for the item. 169 | /// \param [in] widget Optional widget that this item is painted on. 170 | /// 171 | void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget); 172 | 173 | /// 174 | /// \brief Exact boundary of the item used for collision detection among other things. 175 | /// 176 | /// \return Shape of the item in local coordinates. 177 | /// 178 | QPainterPath shape() const; 179 | 180 | private: // members 181 | 182 | /// 183 | /// \brief Plug owning this PlugLabel. 184 | /// 185 | Plug* m_plug; 186 | 187 | /// 188 | /// \brief Text of this label. 189 | /// 190 | QStaticText m_text; 191 | 192 | /// 193 | /// \brief Bounding rectangle of this item. 194 | /// 195 | QRectF m_boundingRect; 196 | 197 | /// 198 | /// \brief Current transformation of the label text. 199 | /// 200 | QTransform m_transform; 201 | 202 | /// 203 | /// \brief true if this PlugLabel is drawn as highlighted -- false otherwise. 204 | /// 205 | bool m_isHighlighted; 206 | 207 | private: // static members 208 | 209 | /// 210 | /// \brief Font used to draw the label. 211 | /// 212 | static QFont s_font; 213 | 214 | /// 215 | /// \brief Color used to draw the label. 216 | /// 217 | static QColor s_color; 218 | 219 | /// 220 | /// \brief Distance from the PlugLabel to its Plug in pixels. 221 | /// 222 | static qreal s_labelDistance; 223 | }; 224 | 225 | } // namespace zodiac 226 | 227 | #endif // ZODIAC_PLUGLABEL_H 228 | -------------------------------------------------------------------------------- /zodiacgraph/scene.cpp: -------------------------------------------------------------------------------- 1 | #include "scene.h" 2 | 3 | #include 4 | 5 | #include "drawedge.h" 6 | #include "edgegroup.h" 7 | #include "edgegrouppair.h" 8 | #include "node.h" 9 | #include "plug.h" 10 | #include "plugedge.h" 11 | 12 | namespace zodiac { 13 | 14 | Scene::Scene(QObject *parent) 15 | : QGraphicsScene(parent) 16 | , m_drawEdge(nullptr) 17 | , m_nodes(QSet()) 18 | , m_edges(QHash, PlugEdge*>()) 19 | , m_edgeGroups(QHash()) 20 | , m_edgeGroupPairs(QSet()) 21 | { 22 | // add the draw edge to the scene 23 | m_drawEdge = new DrawEdge(this); 24 | m_drawEdge->setVisible(false); 25 | } 26 | 27 | Scene::~Scene() 28 | { 29 | // most members are implicitly removed through Qt's parent-child mechanism 30 | m_drawEdge = nullptr; 31 | m_nodes.clear(); 32 | m_edges.clear(); 33 | 34 | // EdgeGroups belong to EdgeGroupPairs, which we need to delete manually 35 | m_edgeGroups.clear(); 36 | for(EdgeGroupPair* edgeGroupPair : m_edgeGroupPairs){ 37 | delete edgeGroupPair; 38 | } 39 | m_edgeGroupPairs.clear(); 40 | } 41 | 42 | Node* Scene::createNode(const QString &name, const QUuid& uuid) 43 | { 44 | Node* newNode = new Node(this, name, uuid); 45 | m_nodes.insert(newNode); 46 | addItem(newNode); 47 | return newNode; 48 | } 49 | 50 | bool Scene::removeNode(Node* node) 51 | { 52 | #ifdef QT_DEBUG 53 | Q_ASSERT(m_nodes.contains(node)); 54 | #else 55 | if(!m_nodes.contains(node)){ 56 | return false; 57 | } 58 | #endif 59 | 60 | // return early if the node cannot be removed 61 | if(!node->isRemovable()){ 62 | return false; 63 | } 64 | 65 | // delete all references to the node and finally the node itself 66 | m_nodes.remove(node); 67 | removeItem(node); 68 | node->deleteLater(); 69 | 70 | return true; 71 | } 72 | 73 | PlugEdge* Scene::createEdge(Plug* fromPlug, Plug* toPlug) 74 | { 75 | // only allow edges between different plugs of different nodes 76 | Node* fromNode = fromPlug->getNode(); 77 | Node* toNode = toPlug->getNode(); 78 | if(fromNode==toNode){ 79 | return nullptr; 80 | } 81 | 82 | // do not create the same edge twice 83 | if(fromPlug->isConnectedWith(toPlug)){ 84 | return nullptr; 85 | } 86 | 87 | // make sure an outgoing plug is connecting with an incoming plug 88 | if((fromPlug->getDirection() != PlugDirection::OUT) || (toPlug->getDirection() != PlugDirection::IN)){ 89 | return nullptr; 90 | } 91 | 92 | // make sure that the incoming edge has no connections yet 93 | if(toPlug->getEdgeCount() != 0){ 94 | return nullptr; 95 | } 96 | 97 | // find the edge group for this edge, if it exists 98 | uint edgeGroupHash = EdgeGroup::getHashOf(fromNode, toNode); 99 | EdgeGroup* edgeGroup; 100 | if(m_edgeGroups.contains(edgeGroupHash)){ 101 | edgeGroup = m_edgeGroups[edgeGroupHash]; 102 | } else { 103 | 104 | // ... or create a new edge group pair for it 105 | EdgeGroupPair* newGroupPair = new EdgeGroupPair(this, fromNode, toNode); 106 | m_edgeGroupPairs.insert(newGroupPair); 107 | 108 | edgeGroup = newGroupPair->getFirstGroup(); 109 | m_edgeGroups.insert(edgeGroupHash, edgeGroup); 110 | 111 | EdgeGroup* oppositeEdgeGroup = newGroupPair->getSecondGroup(); 112 | m_edgeGroups.insert(oppositeEdgeGroup->getHash(), oppositeEdgeGroup); 113 | } 114 | 115 | // create the new edge 116 | PlugEdge* newEdge = new PlugEdge(this, fromPlug, toPlug, edgeGroup); 117 | m_edges.insert(QPair(fromPlug, toPlug), newEdge); 118 | 119 | // emit signals 120 | emit fromNode->outputConnected(fromPlug, toPlug); 121 | emit toNode->inputConnected(toPlug, fromPlug); 122 | 123 | return newEdge; 124 | } 125 | 126 | void Scene::removeEdge(PlugEdge* edge) 127 | { 128 | Plug* fromPlug = edge->getStartPlug(); 129 | Plug* toPlug = edge->getEndPlug(); 130 | QPair edgeKey(fromPlug, toPlug); 131 | #ifdef QT_DEBUG 132 | Q_ASSERT(m_edges.contains(edgeKey)); 133 | #else 134 | if(!m_edges.contains(edgeKey)){ 135 | return; 136 | } 137 | #endif 138 | 139 | // unregister from the connected plugs 140 | fromPlug->removeEdge(edge); 141 | toPlug->removeEdge(edge); 142 | 143 | // remove the edge from the Scene's register 144 | m_edges.remove(edgeKey); 145 | 146 | // remove the edge from its group 147 | EdgeGroup* edgeGroup = edge->getGroup(); 148 | edgeGroup->removeEdge(edge); 149 | 150 | // if the group is now empty, we can only delete it if the other group in the pair is also empty 151 | EdgeGroupPair* edgeGroupPair = edgeGroup->getEdgeGroupPair(); 152 | if(edgeGroupPair->isEmpty()){ 153 | uint firstHash = edgeGroupPair->getFirstGroup()->getHash(); 154 | uint secondHash = edgeGroupPair->getSecondGroup()->getHash(); 155 | Q_ASSERT(m_edgeGroups.contains(firstHash)); 156 | Q_ASSERT(m_edgeGroups.contains(secondHash)); 157 | m_edgeGroups.remove(firstHash); 158 | m_edgeGroups.remove(secondHash); 159 | m_edgeGroupPairs.remove(edgeGroupPair); 160 | delete edgeGroupPair; // also deletes the EdgeGroups 161 | edgeGroupPair = nullptr; 162 | } 163 | 164 | // lastly, remove the QGraphicsItem from the scene, thereby taking possession of the last pointer to the edge 165 | removeItem(edge); 166 | 167 | // delete the edge from memory (automatically deletes all Qt-children as well) 168 | edge->deleteLater(); 169 | 170 | // emit signals 171 | emit fromPlug->getNode()->outputDisconnected(fromPlug, toPlug); 172 | emit toPlug->getNode()->inputDisconnected(toPlug, fromPlug); 173 | } 174 | 175 | PlugEdge* Scene::getEdge(Plug* fromPlug, Plug* toPlug) 176 | { 177 | QPair edgeKey(fromPlug, toPlug); 178 | return m_edges.value(edgeKey, nullptr); 179 | } 180 | 181 | void Scene::collapseAllNodes() 182 | { 183 | for(Node* node : m_nodes){ 184 | node->forceCollapse(); 185 | } 186 | } 187 | 188 | void Scene::updateStyle() 189 | { 190 | for(Node* node : m_nodes){ 191 | node->updateStyle(); 192 | } 193 | for(EdgeGroupPair* pair : m_edgeGroupPairs){ 194 | pair->updateStyle(); 195 | } 196 | m_drawEdge->updateStyle(); 197 | } 198 | 199 | } // namespace zodiac 200 | -------------------------------------------------------------------------------- /zodiacgraph/scene.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_SCENE_H 27 | #define ZODIAC_SCENE_H 28 | 29 | /// \file scene.h 30 | /// 31 | /// \brief Contains the definition of the zodiac::Scene class. 32 | /// 33 | /// The visual design of the graph was in part inspired by a video from Marcin Ignac: 34 | /// http://marcinignac.com/experiments/ring/ 35 | /// 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | namespace zodiac { 42 | 43 | class DrawEdge; 44 | class PlugEdge; 45 | class Node; 46 | class Plug; 47 | class EdgeGroup; 48 | class EdgeGroupPair; 49 | 50 | /// 51 | /// \brief Scene class for the ZodiacGraph. 52 | /// 53 | /// This is the main interaction point for users of the graph with its content. 54 | /// It owns all instances of Node, as well as other QGraphicItems through Qt's parent-child mechanism 55 | /// Additionally, it owns all top-level supporting classes like EdgeGroupPair and manages the DrawEdge. 56 | /// 57 | class Scene : public QGraphicsScene 58 | { 59 | Q_OBJECT 60 | 61 | public: // methods 62 | 63 | /// 64 | /// \brief Constructor. 65 | /// 66 | /// \param [in] parent Qt parent object owning this Scene. 67 | /// 68 | /// The constructor also creates the shared DrawEdge instance used to let the user draw new PlugEdge instances in 69 | /// the graph. 70 | /// 71 | explicit Scene(QObject *parent); 72 | 73 | /// 74 | /// \brief Destructor. 75 | /// 76 | /// Also deletes all EdgeGroupPair instances, prior to releasing the QGraphicsObjects. 77 | /// 78 | virtual ~Scene(); 79 | 80 | /// 81 | /// \brief Creates and adds a new Node to the graph. 82 | /// 83 | /// \param [in] name Name of the new Node. 84 | /// \param [in] uuid (optional) The unique identifier of this Node. 85 | /// 86 | /// \return The new Node. 87 | /// 88 | Node* createNode(const QString& name, const QUuid& uuid = QUuid()); 89 | 90 | /// 91 | /// \brief Removes an existing Node from this Scene. 92 | /// 93 | /// A Node can only be removed if it has no PlugEdge%s attached to any of its Plug%s. 94 | /// If you want to test if the Node can be removed prior to calling Scene::removeNode(), use Node::isRemovable(). 95 | /// 96 | /// Make sure that this Scene actually contains the given Node. 97 | /// If it doesn't, calling this function returns false in release mode and will throw an assertion error 98 | /// in debug mode. 99 | /// 100 | /// After calling this function, all remaining pointers to the removed Node are to be discarded without further use. 101 | /// 102 | /// \param [in] node Node to remove. 103 | /// 104 | /// \return true if the Node could be removed -- false otherwise. 105 | /// 106 | bool removeNode(Node* node); 107 | 108 | /// 109 | /// \brief Returns all Node%s managed by the Scene. 110 | /// 111 | /// \return All Node%s managed by the Scene. 112 | /// 113 | QList getNodes() const {return m_nodes.toList();} 114 | 115 | /// 116 | /// \brief Creates and adds a new PlugEdge to the graph, connecting two Plug%s. 117 | /// 118 | /// Returns nullptr if the PlugEdge cannot be created, for example if a PlugEdge between fromPlug and 119 | /// toPlug already exists or both are part of the same Node. 120 | /// 121 | /// If this is the first connection between two Node%s, this function also creates two EdgeGroup%s (of which one is 122 | /// empty) and an EdgeGroupPair to manage them. 123 | /// If there is already a connection between the two Node instances, the corresponding existing EdgeGroup will be 124 | /// used instead. 125 | /// 126 | /// \param [in] fromPlug Start Plug of the PlugEdge. 127 | /// \param [in] toPlug End Plug of the PlugEdge. 128 | /// 129 | /// \return New PlugEdge or nullptr, if the PlugEdge could not be created. 130 | /// 131 | PlugEdge* createEdge(Plug* fromPlug, Plug* toPlug); 132 | 133 | /// 134 | /// \brief Removes a PlugEdge from the Scene, disconnecting its two Plug%s. 135 | /// 136 | /// Also takes care of deleting EdgeGroup and EdgeGroupPair instances that are empty after removing the PlugEdge. 137 | /// 138 | /// Make sure that this Scene actually contains the given PlugEdge. 139 | /// If it doesn't, calling this function will throw an assertion error in debug mode and do nothing in release mode. 140 | /// 141 | /// \param [in] edge PlugEdge to remove. 142 | /// 143 | void removeEdge(PlugEdge* edge); 144 | 145 | /// 146 | /// \brief Returns an existing PlugEdge from the Scene. 147 | /// 148 | /// \param [in] fromPlug Start Plug of the edge. 149 | /// \param [in] toPlug End Plug of the edge. 150 | /// 151 | /// \return PlugEdge from fromPlug to toPlug or nullptr, if no edge between the given Plug%s 152 | /// exists. 153 | /// 154 | PlugEdge* getEdge(Plug* fromPlug, Plug* toPlug); 155 | 156 | /// 157 | /// \brief Force-collapses all Node%s in the scene. 158 | /// 159 | void collapseAllNodes(); 160 | 161 | /// 162 | /// \brief Returns the DrawEdge used to draw new PlugEdge%s in the scene. 163 | /// 164 | /// \return The DrawEdge used to draw new PlugEdge%s in the scene. 165 | /// 166 | inline DrawEdge* getDrawEdge() {return m_drawEdge;} 167 | 168 | /// 169 | /// \brief Initiates a cascade of style updates of the complete Scene. 170 | /// 171 | void updateStyle(); 172 | 173 | private: // members 174 | 175 | /// 176 | /// \brief Edge shown when the user is creating a new PlugEdge. 177 | /// 178 | DrawEdge* m_drawEdge; 179 | 180 | /// 181 | /// \brief All Node instances in the graph. 182 | /// 183 | QSet m_nodes; 184 | 185 | /// 186 | /// \brief All PlugEdge instances in the graph. 187 | /// 188 | QHash, PlugEdge*> m_edges; 189 | 190 | /// 191 | /// \brief All EdgeGroup instances of the scene. 192 | /// 193 | /// EdgeGroup instances are stored in a hashmap with a hash value generated from EdgeGroup::getHashOf() as key. 194 | /// This way we can easily find the EdgeGroup for a directed connection between to Node%s in the graph. 195 | /// 196 | QHash m_edgeGroups; 197 | 198 | /// 199 | /// \brief All EdgeGroupPair%s owned by the scene. 200 | /// 201 | QSet m_edgeGroupPairs; 202 | 203 | }; 204 | 205 | } // namespace zodiac 206 | 207 | #endif // ZODIAC_SCENE_H 208 | -------------------------------------------------------------------------------- /zodiacgraph/scenehandle.cpp: -------------------------------------------------------------------------------- 1 | #include "scenehandle.h" 2 | 3 | #include "node.h" 4 | #include "scene.h" 5 | 6 | namespace zodiac { 7 | 8 | SceneHandle::SceneHandle(Scene* scene) 9 | : QObject(nullptr) 10 | , m_scene(scene) 11 | , m_isValid(scene!=nullptr) 12 | { 13 | connectSignals(); 14 | } 15 | 16 | SceneHandle& SceneHandle::operator = (const SceneHandle& other) 17 | { 18 | if(m_scene){ 19 | m_scene->disconnect(this); 20 | } 21 | m_scene = other.data(); 22 | m_isValid = m_scene != nullptr; 23 | connectSignals(); 24 | return *this; 25 | } 26 | 27 | NodeHandle SceneHandle::createNode(const QString& name, const QUuid& uuid) 28 | { 29 | #ifdef QT_DEBUG 30 | Q_ASSERT(m_isValid); 31 | #else 32 | if(!m_isValid){ 33 | return NodeHandle(); 34 | } 35 | #endif 36 | return NodeHandle(m_scene->createNode(name, uuid)); 37 | } 38 | 39 | QList SceneHandle::getNodes() const 40 | { 41 | QList result; 42 | #ifdef QT_DEBUG 43 | Q_ASSERT(m_isValid); 44 | #else 45 | if(!m_isValid){ 46 | return result; 47 | } 48 | #endif 49 | QList nodes = m_scene->getNodes(); 50 | result.reserve(nodes.size()); 51 | for(Node* node : nodes){ 52 | result.append(NodeHandle(node)); 53 | } 54 | return result; 55 | } 56 | 57 | void SceneHandle::deselectAll() const 58 | { 59 | #ifdef QT_DEBUG 60 | Q_ASSERT(m_isValid); 61 | #else 62 | if(!m_isValid){ 63 | return; 64 | } 65 | #endif 66 | for(QGraphicsItem* item : m_scene->selectedItems()){ 67 | item->setSelected(false); 68 | } 69 | } 70 | 71 | void SceneHandle::connectSignals() 72 | { 73 | if(!m_isValid){ 74 | return; 75 | } 76 | connect(m_scene, SIGNAL(destroyed()), this, SLOT(sceneWasDestroyed())); 77 | connect(m_scene, SIGNAL(selectionChanged()), this, SLOT(updateSelection())); 78 | } 79 | 80 | void SceneHandle::updateSelection() 81 | { 82 | QList selection; 83 | for(QGraphicsItem* item : m_scene->selectedItems()){ 84 | Node* selectedNode = qobject_cast(item->toGraphicsObject()); 85 | if(selectedNode){ 86 | selection.append(NodeHandle(selectedNode)); 87 | } 88 | } 89 | emit selectionChanged(selection); 90 | } 91 | 92 | void SceneHandle::sceneWasDestroyed() 93 | { 94 | m_isValid = false; 95 | } 96 | 97 | } // namespace zodiac 98 | -------------------------------------------------------------------------------- /zodiacgraph/scenehandle.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_SCENEHANDLE_H 27 | #define ZODIAC_SCENEHANDLE_H 28 | 29 | /// \file scenehandle.h 30 | /// 31 | /// \brief Contains the definition of the zodiac::SceneHandle class. 32 | /// 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | #include "nodehandle.h" 39 | 40 | namespace zodiac { 41 | 42 | class PlugHandle; 43 | class Scene; 44 | 45 | /// 46 | /// \brief A handle object for a zodiac::Node. 47 | /// 48 | /// Is a thin wrapper around a pointer but with a much nicer, outward facing interface than the real Node. 49 | /// 50 | /// SceneHandle, NodeHandle and PlugHandle should be the main interface for working with a Zodiac Graph. 51 | /// They are designed for ease of use and it should be hard to leave the graph in an inconsistent state using the 52 | /// provided methods. 53 | /// 54 | /// There is however a caveat in using handles.
55 | /// It is conceivable (if unlikely) that (in a multi-threated environment) the deletion of a managed object occurs in 56 | /// the middle of the execution of a handle method after the initial validation has succeeded. 57 | /// Consequently, the handle will fail to operate on a dangling pointer. 58 | /// In the future, it might be possible to use weak references and shared pointers to make sure that the managed pointer 59 | /// is always alive when calling a handle method, but since most Zodiac-classes are active participants in Qt's 60 | /// parent-child mechanism, using shared pointers on them is a dangerous thing to do.
61 | /// Keeping a shared pointer on a QGraphicsItem after its QGraphicsScene was removed will cause a segmentation error to 62 | /// occur and although you could completely separate the logical and graphical code (with a complete overhaul of the 63 | /// architecture and rewrite of the code), I will leave this exercise for subsequent versions.
64 | /// Instead each handle listens to its respective QObject and sets its internal pointer to nullptr, as soon as it 65 | /// receives the "deleted()" signal. 66 | /// In the meantime, this approach should cover 99% of all use-cases, as long as you don't access the graph using 67 | /// multiple threads at once. 68 | /// 69 | class SceneHandle : public QObject 70 | { 71 | 72 | Q_OBJECT 73 | 74 | public: // methods 75 | 76 | /// 77 | /// \brief Constructor. 78 | /// 79 | /// \param [in] scene Scene to manage through this handle 80 | /// 81 | explicit SceneHandle(Scene* scene = nullptr); 82 | 83 | /// 84 | /// \brief Copy constructor. 85 | /// 86 | /// \param [in] other Other SceneHandle to copy. 87 | /// 88 | SceneHandle(const SceneHandle& other) 89 | : SceneHandle(other.data()) {} 90 | 91 | /// 92 | /// \brief Assignment operator. 93 | /// 94 | /// \param [in] other Other SceneHandle to copy from. 95 | /// 96 | /// \return This. 97 | /// 98 | SceneHandle& operator = (const SceneHandle& other); 99 | 100 | /// 101 | /// \brief Equality operator. 102 | /// 103 | /// \param [in] other Other SceneHandle to test against. 104 | /// 105 | /// \return true if both handles handle the same object -- false otherwise. 106 | /// 107 | bool operator == (const SceneHandle& other) const {return other.data() == data();} 108 | 109 | /// 110 | /// \brief Direct pointer access. 111 | /// 112 | /// \return The pointer managed by this handle. 113 | /// 114 | inline Scene* data() const {return m_scene;} 115 | 116 | /// 117 | /// \brief Used for testing, whether the handle is still alive or not. 118 | /// 119 | /// \return true, if the SceneHandle is still managing an existing Scene -- false otherwise. 120 | /// 121 | inline bool isValid() const {return m_isValid;} 122 | 123 | /// 124 | /// \brief Creates and adds a new Node to the zodiac graph. 125 | /// 126 | /// \param [in] name Display name of the new Node. 127 | /// \param [in] uuid (optional) The unique identifier of this Node. 128 | /// 129 | /// \return Handle of the new Node. 130 | /// 131 | NodeHandle createNode(const QString& name, const QUuid& uuid = QUuid()); 132 | 133 | /// 134 | /// \brief Returns all Node%s managed by the Scene. 135 | /// 136 | /// \return All Node%s managed by the Scene. 137 | /// 138 | QList getNodes() const; 139 | 140 | /// 141 | /// \brief Clears the selection of the Scene. 142 | /// 143 | void deselectAll() const; 144 | 145 | signals: 146 | 147 | /// 148 | /// \brief Emitted when the selection in the Scene has changed. 149 | /// 150 | /// \param [out] selection Handles to all selected Node%s. 151 | /// 152 | void selectionChanged(QList selection); 153 | 154 | private: // methods 155 | 156 | /// 157 | /// \brief Connects the handle to its managed object. 158 | /// 159 | void connectSignals(); 160 | 161 | private slots: 162 | 163 | /// 164 | /// \brief Called, when the selection of the Scene was changed. 165 | /// 166 | void updateSelection(); 167 | 168 | /// 169 | /// \brief Called, when the mangaged Scene was destroyed. 170 | /// 171 | void sceneWasDestroyed(); 172 | 173 | private: // member 174 | 175 | /// 176 | /// \brief Managed scene. 177 | /// 178 | Scene* m_scene; 179 | 180 | /// 181 | /// \brief Validity flag. 182 | /// 183 | bool m_isValid; 184 | }; 185 | 186 | } // namespace zodiac 187 | 188 | /// 189 | /// \brief Returns the hash of a SceneHandle instance. 190 | /// 191 | /// The hash is calculated by taking the address of the Scene-pointer. 192 | /// This is also, why the pointer adress in the handle is never changed or set to nullptr. 193 | /// 194 | /// \param [in] key SceneHandle instance to hash. 195 | /// 196 | /// \return Unique identifier of a SceneHandle that can be used to determine equality. 197 | /// 198 | inline uint qHash(const zodiac::SceneHandle& key) 199 | { 200 | return qHash(size_t(key.data())); 201 | } 202 | 203 | #endif // ZODIAC_SCENEHANDLE_H 204 | -------------------------------------------------------------------------------- /zodiacgraph/straightdoubleedge.cpp: -------------------------------------------------------------------------------- 1 | #include "straightdoubleedge.h" 2 | 3 | #include 4 | 5 | #include "edgearrow.h" 6 | #include "edgegroupinterface.h" 7 | 8 | namespace zodiac { 9 | 10 | StraightDoubleEdge::StraightDoubleEdge(Scene* scene, EdgeGroupInterface* group, 11 | Node* fromNode, Node* toNode) 12 | : StraightEdge(scene, group, fromNode, toNode) 13 | { 14 | m_arrow->setKind(ArrowKind::DOUBLE); 15 | 16 | // initialize the shape 17 | // the StraightEdge Constructor does so as well, but at that time this part of the instance is not constructed yet 18 | // so the wrong function is called (it calls StraightEdge::updateShape instead). 19 | updateShape(); 20 | } 21 | 22 | void StraightDoubleEdge::updateLabel() 23 | { 24 | setLabelText(m_group->getLabelText()); 25 | placeArrowAt(.5); 26 | } 27 | 28 | void StraightDoubleEdge::updateShape() 29 | { 30 | prepareGeometryChange(); 31 | 32 | // calculate the perpendicular edge offset 33 | QVector2D direction = QVector2D(m_endPoint-m_startPoint); 34 | direction.normalize(); 35 | QPointF offset = QPointF(-direction.y(), direction.x()) * s_width; 36 | 37 | // update the path 38 | QPainterPath doubleLine; 39 | doubleLine.moveTo(m_startPoint+offset); 40 | doubleLine.lineTo(m_endPoint+offset); 41 | 42 | doubleLine.moveTo(m_startPoint-offset); 43 | doubleLine.lineTo(m_endPoint-offset); 44 | 45 | m_path.swap(doubleLine); 46 | 47 | // update the arrow 48 | placeArrowAt(.5); 49 | } 50 | 51 | } // namespace zodiac 52 | -------------------------------------------------------------------------------- /zodiacgraph/straightdoubleedge.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_STRAIGHTDOUBLEEDGE_H 27 | #define ZODIAC_STRAIGHTDOUBLEEDGE_H 28 | 29 | /// 30 | /// \file straightdoubleedge.h 31 | /// 32 | /// \brief Contains the definition of the zodiac::StraightDoubleEdge class. 33 | /// 34 | 35 | #include "straightedge.h" 36 | 37 | namespace zodiac { 38 | 39 | class Node; 40 | class Scene; 41 | class EdgeGroupInterface; 42 | 43 | /// 44 | /// \brief Edge displayed between to fully collapsed Node%s if they are connected with edges going back and forth. 45 | /// 46 | /// While a single StraightEdge replaces 1-n PlugEdge%s when the Node%s on both ends are collapsed, a 47 | /// StraightDoubleEdge replaces 2 StraightEdge%s that connect the same Node%s but flow in different directions. 48 | /// 49 | class StraightDoubleEdge : public StraightEdge 50 | { 51 | Q_OBJECT 52 | 53 | public: // methods 54 | 55 | /// 56 | /// \brief Constructor. 57 | /// 58 | /// \param [in] scene Scene containing this edge. 59 | /// \param [in] group EdgeGroupPair containing this edge. 60 | /// \param [in] fromNode Start Node of this edge. 61 | /// \param [in] toNode End Node of this edge. 62 | /// 63 | explicit StraightDoubleEdge(Scene* scene, EdgeGroupInterface* group, Node* fromNode, Node* toNode); 64 | 65 | /// 66 | /// \brief Generates and updates the text for the EdgeLabel of this edge. 67 | /// 68 | void updateLabel(); 69 | 70 | protected: // methods 71 | 72 | /// 73 | /// \brief Updates the shape of the edge. 74 | /// 75 | virtual void updateShape() override; 76 | }; 77 | 78 | } // namespace zodiac 79 | 80 | #endif // ZODIAC_STRAIGHTDOUBLEEDGE_H 81 | -------------------------------------------------------------------------------- /zodiacgraph/straightedge.cpp: -------------------------------------------------------------------------------- 1 | #include "straightedge.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "edgearrow.h" 7 | #include "edgegroupinterface.h" 8 | #include "node.h" 9 | #include "view.h" 10 | 11 | namespace zodiac { 12 | 13 | StraightEdge::StraightEdge(Scene* scene, EdgeGroupInterface* group, Node* fromNode, Node* toNode) 14 | : BaseEdge(scene) 15 | , m_group(group) 16 | , m_fromNode(fromNode) 17 | , m_toNode(toNode) 18 | , m_startPoint(QPointF()) 19 | , m_endPoint(QPointF()) 20 | { 21 | Q_ASSERT(fromNode!=toNode); 22 | 23 | // register with the nodes 24 | fromNode->addStraightEdge(this); 25 | toNode->addStraightEdge(this); 26 | 27 | // initialize the shape of the edge 28 | nodePositionHasChanged(); 29 | } 30 | 31 | void StraightEdge::nodePositionHasChanged() 32 | { 33 | // return early, if the shape has not changed 34 | QPointF startPoint = m_fromNode->scenePos(); 35 | QPointF endPoint = m_toNode->scenePos(); 36 | if((startPoint==m_startPoint)&&(endPoint==m_endPoint)){ 37 | return; 38 | } 39 | 40 | // update the edge's ctrl points 41 | m_startPoint = startPoint; 42 | m_endPoint = endPoint; 43 | 44 | updateShape(); 45 | } 46 | 47 | void StraightEdge::updateLabel() 48 | { 49 | setLabelText(m_group->getLabelText()); 50 | placeArrowAt(.5); 51 | } 52 | 53 | void StraightEdge::placeArrowAt(qreal fraction) 54 | { 55 | QPointF delta = m_endPoint-m_startPoint; 56 | QPointF centerPoint = m_startPoint + (delta*fraction); 57 | qreal angle = qAtan2(delta.y(), delta.x()); 58 | m_arrow->setTransformation(centerPoint, angle); 59 | } 60 | 61 | void StraightEdge::updateShape() 62 | { 63 | prepareGeometryChange(); 64 | 65 | // update the path 66 | QPainterPath straightLine; 67 | straightLine.moveTo(m_startPoint); 68 | straightLine.lineTo(m_endPoint); 69 | m_path.swap(straightLine); 70 | 71 | // update the arrow 72 | placeArrowAt(.5); 73 | } 74 | 75 | void StraightEdge::mousePressEvent(QGraphicsSceneMouseEvent* event) 76 | { 77 | if(event->buttons() & View::getRemovalButton()){ 78 | event->accept(); 79 | emit removalRequested(); 80 | } else { 81 | BaseEdge::mousePressEvent(event); 82 | } 83 | } 84 | 85 | void StraightEdge::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) 86 | { 87 | if(event->buttons() & View::getSelectionButton()){ 88 | m_fromNode->softSetExpansion(NodeExpansion::BOTH); 89 | m_toNode->softSetExpansion(NodeExpansion::BOTH); 90 | } 91 | BaseEdge::mouseDoubleClickEvent(event); 92 | } 93 | 94 | } // namespace zodiac 95 | -------------------------------------------------------------------------------- /zodiacgraph/straightedge.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_STRAIGHTEDGE_H 27 | #define ZODIAC_STRAIGHTEDGE_H 28 | 29 | /// 30 | /// \file straightedge.h 31 | /// 32 | /// \brief Contains the definition of the zodiac::StraightEdge class. 33 | /// 34 | 35 | #include "baseedge.h" 36 | 37 | namespace zodiac { 38 | 39 | class Node; 40 | class Scene; 41 | class EdgeGroupInterface; 42 | 43 | /// 44 | /// \brief Connects two nodes in a straight line. 45 | /// 46 | /// StraightEdge%s are used by EdgeGroup%s. 47 | /// 48 | class StraightEdge : public BaseEdge 49 | { 50 | Q_OBJECT 51 | 52 | public: // methods 53 | 54 | /// 55 | /// \brief Constructor. 56 | /// 57 | /// Registers this StraightEdge with its connected Node instances. 58 | /// 59 | /// \param [in] scene Scene containing this edge. 60 | /// \param [in] group EdgeGroup or EdgeGroupPair containing this edge. 61 | /// \param [in] fromNode Start Node of this edge. 62 | /// \param [in] toNode End Node of this edge. 63 | /// 64 | explicit StraightEdge(Scene* scene, EdgeGroupInterface* group, Node* fromNode, Node* toNode); 65 | 66 | /// 67 | /// \brief Is called from a Node to notify a connected StraightEdge of a change in position. 68 | /// 69 | void nodePositionHasChanged(); 70 | 71 | /// 72 | /// \brief Generates and updates the text for this edge's EdgeLabel. 73 | /// 74 | void updateLabel(); 75 | 76 | /// 77 | /// \brief The Node from which the PlugEdge%s in the EdgeGroup originate. 78 | /// 79 | /// \return Start Node of this StraightEdge. 80 | /// 81 | inline Node* getFromNode() const {return m_fromNode;} 82 | 83 | /// 84 | /// \brief The Node to which the PlugEdge%s in the EdgeGroup lead. 85 | /// 86 | /// \return End Node of this StraightEdge. 87 | /// 88 | inline Node* getToNode() const {return m_toNode;} 89 | 90 | /// 91 | /// \brief Places the EdgeArrow along the edge to a given fraction of the arclength. 92 | /// 93 | /// \param [in] fraction Fraction of arclength at which to place the arrow. 94 | /// 95 | void placeArrowAt(qreal fraction) override; 96 | 97 | signals: 98 | 99 | /// 100 | /// \brief Clicking this edge with the removal button emits this signal to be caught by the appropriate EdgeGroup. 101 | /// 102 | void removalRequested(); 103 | 104 | protected: // methods 105 | 106 | /// 107 | /// \brief Updates the shape of the StraightEdge. 108 | /// 109 | virtual void updateShape() override; 110 | 111 | /// 112 | /// \brief Called, when the mouse is pressed as the cursor is on this item. 113 | /// 114 | /// \param [in] event Qt event object. 115 | /// 116 | void mousePressEvent(QGraphicsSceneMouseEvent *event); 117 | 118 | /// 119 | /// \brief Called when this item is double-clicked. 120 | /// 121 | /// The user can expand both Node%s connected through this StraightEdge by double-clicking it. 122 | /// 123 | /// \param [in] event Qt event object. 124 | /// 125 | void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); 126 | 127 | protected: // members 128 | 129 | /// 130 | /// \brief EdgeGroup that this StraightEdge represents. 131 | /// 132 | EdgeGroupInterface* m_group; 133 | 134 | /// 135 | /// \brief The start Node of this StraightEdge. 136 | /// 137 | Node* m_fromNode; 138 | 139 | /// 140 | /// \brief The end Node of this StraightEdge. 141 | /// 142 | Node* m_toNode; 143 | 144 | /// 145 | /// \brief Start point of the StraightEdge in scene coordinates. 146 | /// 147 | QPointF m_startPoint; 148 | 149 | /// 150 | /// \brief End point of the StraightEdge in scene coordinates. 151 | /// 152 | QPointF m_endPoint; 153 | 154 | }; 155 | 156 | } // namespace zodiac 157 | 158 | #endif // ZODIAC_STRAIGHTEDGE_H 159 | -------------------------------------------------------------------------------- /zodiacgraph/utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZodiacGraph - A general-purpose, circular node graph UI module. 3 | // Copyright (C) 2015 Clemens Sielaff 4 | // 5 | // The MIT License 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | // this software and associated documentation files (the "Software"), to deal in 9 | // the Software without restriction, including without limitation the rights to 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | // of the Software, and to permit persons to whom the Software is furnished to do so, 12 | // subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | 26 | #ifndef ZODIAC_NODEUTILS_H 27 | #define ZODIAC_NODEUTILS_H 28 | 29 | /// 30 | /// \file utils.h 31 | /// 32 | /// \brief Common utils and enums that are used throughout the code. 33 | /// 34 | 35 | #include 36 | 37 | namespace zodiac { 38 | 39 | /// 40 | /// \brief Z-Positions of items in the node view. 41 | /// 42 | /// Is not an enum class, because this enum is often used with QGraphicItem's setZValue(), which requires an integer. 43 | /// And using a pure enum is easier. 44 | /// 45 | enum zStack { 46 | EDGE = -10, ///< Edges are all the way in the background. 47 | NODE_CLOSED = 0, ///< A closed Node is the base line depth at zero. 48 | NODE_EXPANDED = 10, ///< An expanded Node automatically overlays a closed one. 49 | NODE_ACTIVE = 20, ///< The active Node (the last selected one) overlays other expanded Nodes. 50 | EDGE_LABEL = 30, ///< EdgeLabel%s overlay all Node%s. 51 | DRAW_EDGE = 40 ///< The DrawEdge is drawn in front of overthing. 52 | }; 53 | 54 | } // namespace zodiac 55 | 56 | /// 57 | /// \brief Constructs a quadrat with a given side length. 58 | /// 59 | /// Solely a convenience function as the Zodiac Graph design requires many quadratic QRectF%s of various sidelengths. 60 | /// 61 | /// \param sidelength Length of one side of the quadrat 62 | /// 63 | /// \return Rectangle around zero with given side length. 64 | /// 65 | inline QRectF quadrat(qreal sidelength){ 66 | return QRectF(-sidelength, -sidelength, sidelength*2, sidelength*2); 67 | } 68 | 69 | #endif // ZODIAC_NODEUTILS_H 70 | -------------------------------------------------------------------------------- /zodiacgraph/view.cpp: -------------------------------------------------------------------------------- 1 | #include "view.h" 2 | 3 | /// 4 | /// Set to 1 to enable fps output to std out. 5 | /// 6 | #define PRINT_REDRAW_SPEED 0 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #if PRINT_REDRAW_SPEED 15 | #include 16 | #endif 17 | 18 | #include "scene.h" 19 | 20 | namespace zodiac { 21 | 22 | QColor View::s_backgroundColor = QColor("#191919"); 23 | qreal View::s_zoomSpeed = 0.001; 24 | Qt::MouseButton View::s_dragMoveButton = Qt::RightButton; 25 | Qt::MouseButton View::s_selectionButton = Qt::LeftButton; 26 | Qt::MouseButton View::s_removalButton = Qt::MiddleButton; 27 | int View::s_activationKey = Qt::Key_Return; 28 | qreal View::s_minZoomFactor = 0.1; 29 | qreal View::s_maxZoomFactor = 2.0; 30 | 31 | View::View(QWidget *parent) 32 | : QGraphicsView(parent) 33 | , m_zoomFactor(1.0) 34 | { 35 | setBackgroundBrush(QBrush(s_backgroundColor)); 36 | setCacheMode(QGraphicsView::CacheBackground); 37 | setRenderHints(QPainter::Antialiasing); 38 | setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); 39 | setTransformationAnchor(QGraphicsView::AnchorUnderMouse); 40 | setDragMode(QGraphicsView::RubberBandDrag); 41 | 42 | setAttribute(Qt::WA_AcceptTouchEvents); 43 | grabGesture(Qt::PanGesture); 44 | grabGesture(Qt::PinchGesture); 45 | } 46 | 47 | void View::updateStyle() 48 | { 49 | // update all affected members 50 | setBackgroundBrush(QBrush(s_backgroundColor)); 51 | 52 | // force a redraw for good measure 53 | resetCachedContent(); 54 | } 55 | 56 | bool View::event(QEvent* event) 57 | { 58 | event->accept(); 59 | 60 | switch(event->type()){ 61 | 62 | case QEvent::Gesture: 63 | setDragMode(QGraphicsView::NoDrag); 64 | return gestureEvent(static_cast(event)); 65 | 66 | case QEvent::TouchEnd: 67 | setDragMode(QGraphicsView::RubberBandDrag); 68 | break; 69 | 70 | default: 71 | break; 72 | } 73 | return QGraphicsView::event(event); 74 | } 75 | 76 | bool View::gestureEvent(QGestureEvent* event) 77 | { 78 | // 79 | // pinch has precedence 80 | if (QGesture *pinchEvent = event->gesture(Qt::PinchGesture)) { 81 | QPinchGesture* pinch = static_cast(pinchEvent); 82 | 83 | // 84 | // only pinch if the fingers have already moved a significant amount 85 | qreal totalScaleFactor = pinch->totalScaleFactor(); 86 | if((totalScaleFactor < 0.66) || (totalScaleFactor > 1.5)){ 87 | qreal zoomDelta = pinch->scaleFactor(); 88 | qreal resultZoom = m_zoomFactor * zoomDelta; 89 | if(resultZoom > s_maxZoomFactor){ 90 | zoomDelta = s_maxZoomFactor / m_zoomFactor; 91 | }else if(resultZoom < s_minZoomFactor){ 92 | zoomDelta = s_minZoomFactor / m_zoomFactor; 93 | } 94 | 95 | // scale the view 96 | scale(zoomDelta,zoomDelta); 97 | m_zoomFactor *= zoomDelta; 98 | 99 | return true; 100 | } 101 | } 102 | 103 | // 104 | // pan 105 | if (QGesture *panEvent = event->gesture(Qt::PanGesture)) { 106 | QPanGesture* pan = static_cast(panEvent); 107 | QPointF delta = pan->delta(); 108 | qreal factor = (1.0 / m_zoomFactor) * 0.9; 109 | 110 | QScrollBar* vScrollBar = verticalScrollBar(); 111 | vScrollBar->setValue(vScrollBar->value() - int(delta.y()/factor)); 112 | 113 | QScrollBar* hScrollBar = horizontalScrollBar(); 114 | hScrollBar->setValue(hScrollBar->value() - int(delta.x()/factor)); 115 | } 116 | 117 | return true; 118 | } 119 | 120 | bool View::viewportEvent(QEvent *event) 121 | { 122 | if((event->type()==QEvent::Leave) && (qApp->mouseButtons()!=Qt::NoButton)){ 123 | // leaving the window while dragging must not trigger a dragRelease event 124 | return true; 125 | } 126 | return QGraphicsView::viewportEvent(event); 127 | } 128 | 129 | void View::mousePressEvent(QMouseEvent* event) 130 | { 131 | static const QTransform nullTransform = QTransform(); 132 | 133 | if(event->button() == s_dragMoveButton){ 134 | // only allow scroll dragging if no item is clicked 135 | if(!scene()->itemAt(mapToScene(event->pos()), nullTransform)){ 136 | setDragMode(QGraphicsView::ScrollHandDrag); 137 | QMouseEvent fakeEvent(event->type(), event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); 138 | QGraphicsView::mousePressEvent(&fakeEvent); 139 | } 140 | return; 141 | 142 | } else if (event->button() != s_selectionButton){ 143 | // do not allow rubberband selection with any button other than the selection button 144 | setDragMode(QGraphicsView::NoDrag); 145 | } 146 | QGraphicsView::mousePressEvent(event); 147 | } 148 | 149 | void View::mouseReleaseEvent(QMouseEvent* event) 150 | { 151 | if (event->button() == s_dragMoveButton){ 152 | // disable scroll dragging, if it was enabled 153 | if(dragMode()==QGraphicsView::ScrollHandDrag){ 154 | QMouseEvent fakeEvent(event->type(), event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); 155 | QGraphicsView::mouseReleaseEvent(&fakeEvent); 156 | } 157 | } 158 | 159 | // make sure to reset the drag mode 160 | setDragMode(QGraphicsView::RubberBandDrag); 161 | 162 | QGraphicsView::mouseReleaseEvent(event); 163 | } 164 | 165 | void View::mouseDoubleClickEvent(QMouseEvent *event) 166 | { 167 | if(event->buttons() & s_selectionButton){ 168 | // double clicking into empty space collapse all nodes 169 | if(!itemAt(event->pos())){ 170 | static_cast(scene())->collapseAllNodes(); 171 | } 172 | } 173 | QGraphicsView::mouseDoubleClickEvent(event); 174 | } 175 | 176 | void View::wheelEvent(QWheelEvent *event) 177 | { 178 | // calculate the zoom factor and make sure it does not exceed its range 179 | qreal zoomDelta = 1. + (event->angleDelta().y() * s_zoomSpeed); 180 | qreal resultZoom = m_zoomFactor * zoomDelta; 181 | if(resultZoom > s_maxZoomFactor){ 182 | zoomDelta = s_maxZoomFactor / m_zoomFactor; 183 | }else if(resultZoom < s_minZoomFactor){ 184 | zoomDelta = s_minZoomFactor / m_zoomFactor; 185 | } 186 | 187 | // scale the view 188 | scale(zoomDelta,zoomDelta); 189 | m_zoomFactor *= zoomDelta; 190 | 191 | // do not call QGraphicsView::wheelEvent here, because it will scroll up or down as well as zoom 192 | return; 193 | } 194 | 195 | void View::paintEvent(QPaintEvent* event) 196 | { 197 | #if PRINT_REDRAW_SPEED 198 | static quint64 total=0; 199 | static quint64 divisor=1; 200 | QElapsedTimer timer; 201 | timer.start(); 202 | #endif 203 | 204 | QGraphicsView::paintEvent(event); 205 | 206 | #if PRINT_REDRAW_SPEED 207 | qint64 duration = timer.nsecsElapsed(); 208 | total+=duration; 209 | if(divisor%100==0){ 210 | qDebug() << (total/divisor) * 0.000001 << "ms"; 211 | total=0; 212 | divisor=0; 213 | } 214 | divisor++; 215 | #endif 216 | } 217 | 218 | void View::setScene(Scene *scene) 219 | { 220 | QGraphicsView::setScene(scene); 221 | } 222 | 223 | } // namespace zodiac 224 | --------------------------------------------------------------------------------