├── .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 |
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 |
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 |
7 |
--------------------------------------------------------------------------------
/res/minus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/res/outgoing.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/res/play.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/res/plus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/res/questionmark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------