├── .gitignore
├── CMakeLists.txt
├── LICENCE
├── Piper.kdev4
├── README.md
├── data
└── theme.json
├── piper.png
├── ressources
├── add_tab.svg
├── arrow-down-disabled.svg
├── arrow-down-hover.svg
├── arrow-down.svg
├── arrow-up-disabled.svg
├── arrow-up-hover.svg
├── arrow-up.svg
├── resources.qrc
├── star.svg
└── style.qss
└── src
├── Attribute.cc
├── Attribute.h
├── AttributeMember.cc
├── AttributeMember.h
├── CreatorPopup.cc
├── CreatorPopup.h
├── EditorTab.cc
├── EditorTab.h
├── EditorWidget.cc
├── EditorWidget.h
├── EditorWidget.ui
├── ExportBackend.h
├── JsonExport.cc
├── JsonExport.h
├── Link.cc
├── Link.h
├── MainEditor.cc
├── MainEditor.h
├── MainEditor.ui
├── Node.cc
├── Node.h
├── NodeCreator.cc
├── NodeCreator.h
├── PropertyDelegate.cc
├── PropertyDelegate.h
├── Scene.cc
├── Scene.h
├── ThemeManager.cc
├── ThemeManager.h
├── Types.h
├── View.cc
├── View.h
└── main.cc
/.gitignore:
--------------------------------------------------------------------------------
1 | .kdev4
2 | build/
3 | /.vs
4 | /CMakeSettings.json
5 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10)
2 | project(Piper)
3 |
4 | # Find includes in corresponding build directories
5 | set(CMAKE_INCLUDE_CURRENT_DIR ON)
6 | # Instruct CMake to run moc automatically when needed.
7 | set(CMAKE_AUTOMOC ON)
8 | # Instruct CMake to create code from Qt designer ui files
9 | set(CMAKE_AUTOUIC ON)
10 | # Instruct CMake to run Qt ressource
11 | set(CMAKE_AUTORCC ON)
12 |
13 | if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
14 | add_definitions(/permissive-)
15 | endif()
16 |
17 | # Find the QtWidgets library
18 | find_package(Qt5Widgets CONFIG REQUIRED)
19 |
20 | set(piper_lib_src
21 | ${CMAKE_CURRENT_SOURCE_DIR}/src/Scene.cc
22 | ${CMAKE_CURRENT_SOURCE_DIR}/src/View.cc
23 | ${CMAKE_CURRENT_SOURCE_DIR}/src/Node.cc
24 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ThemeManager.cc
25 | ${CMAKE_CURRENT_SOURCE_DIR}/src/Attribute.cc
26 | ${CMAKE_CURRENT_SOURCE_DIR}/src/AttributeMember.cc
27 | ${CMAKE_CURRENT_SOURCE_DIR}/src/Link.cc
28 | ${CMAKE_CURRENT_SOURCE_DIR}/src/NodeCreator.cc
29 | ${CMAKE_CURRENT_SOURCE_DIR}/src/CreatorPopup.cc
30 | ${CMAKE_CURRENT_SOURCE_DIR}/src/JsonExport.cc
31 | ${CMAKE_CURRENT_SOURCE_DIR}/src/MainEditor.cc
32 | ${CMAKE_CURRENT_SOURCE_DIR}/src/EditorTab.cc
33 | ${CMAKE_CURRENT_SOURCE_DIR}/src/EditorWidget.cc
34 | ${CMAKE_CURRENT_SOURCE_DIR}/src/PropertyDelegate.cc
35 | ${CMAKE_CURRENT_SOURCE_DIR}/ressources/resources.qrc
36 | )
37 |
38 | set(piper_editor_src
39 | ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cc
40 | )
41 |
42 | # Tell CMake to create the helloworld executable
43 | add_library(piper ${piper_lib_src})
44 | target_include_directories(piper PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
45 | target_link_libraries(piper Qt5::Widgets)
46 | target_compile_options(piper PRIVATE -Wall)
47 |
48 | add_executable(piper_editor ${piper_editor_src})
49 | target_link_libraries(piper_editor piper)
50 | target_compile_options(piper_editor PRIVATE -Wall)
51 |
52 | file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
53 |
54 | # Install the executable
55 | install(TARGETS piper DESTINATION lib)
56 | install(TARGETS piper_editor DESTINATION bin)
57 | install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data DESTINATION bin)
58 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 |
2 | CeCILL-C FREE SOFTWARE LICENSE AGREEMENT
3 |
4 |
5 | Notice
6 |
7 | This Agreement is a Free Software license agreement that is the result
8 | of discussions between its authors in order to ensure compliance with
9 | the two main principles guiding its drafting:
10 |
11 | * firstly, compliance with the principles governing the distribution
12 | of Free Software: access to source code, broad rights granted to
13 | users,
14 | * secondly, the election of a governing law, French law, with which
15 | it is conformant, both as regards the law of torts and
16 | intellectual property law, and the protection that it offers to
17 | both authors and holders of the economic rights over software.
18 |
19 | The authors of the CeCILL-C (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre])
20 | license are:
21 |
22 | Commissariat � l'Energie Atomique - CEA, a public scientific, technical
23 | and industrial research establishment, having its principal place of
24 | business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France.
25 |
26 | Centre National de la Recherche Scientifique - CNRS, a public scientific
27 | and technological establishment, having its principal place of business
28 | at 3 rue Michel-Ange, 75794 Paris cedex 16, France.
29 |
30 | Institut National de Recherche en Informatique et en Automatique -
31 | INRIA, a public scientific and technological establishment, having its
32 | principal place of business at Domaine de Voluceau, Rocquencourt, BP
33 | 105, 78153 Le Chesnay cedex, France.
34 |
35 |
36 | Preamble
37 |
38 | The purpose of this Free Software license agreement is to grant users
39 | the right to modify and re-use the software governed by this license.
40 |
41 | The exercising of this right is conditional upon the obligation to make
42 | available to the community the modifications made to the source code of
43 | the software so as to contribute to its evolution.
44 |
45 | In consideration of access to the source code and the rights to copy,
46 | modify and redistribute granted by the license, users are provided only
47 | with a limited warranty and the software's author, the holder of the
48 | economic rights, and the successive licensors only have limited liability.
49 |
50 | In this respect, the risks associated with loading, using, modifying
51 | and/or developing or reproducing the software by the user are brought to
52 | the user's attention, given its Free Software status, which may make it
53 | complicated to use, with the result that its use is reserved for
54 | developers and experienced professionals having in-depth computer
55 | knowledge. Users are therefore encouraged to load and test the
56 | suitability of the software as regards their requirements in conditions
57 | enabling the security of their systems and/or data to be ensured and,
58 | more generally, to use and operate it in the same conditions of
59 | security. This Agreement may be freely reproduced and published,
60 | provided it is not altered, and that no provisions are either added or
61 | removed herefrom.
62 |
63 | This Agreement may apply to any or all software for which the holder of
64 | the economic rights decides to submit the use thereof to its provisions.
65 |
66 |
67 | Article 1 - DEFINITIONS
68 |
69 | For the purpose of this Agreement, when the following expressions
70 | commence with a capital letter, they shall have the following meaning:
71 |
72 | Agreement: means this license agreement, and its possible subsequent
73 | versions and annexes.
74 |
75 | Software: means the software in its Object Code and/or Source Code form
76 | and, where applicable, its documentation, "as is" when the Licensee
77 | accepts the Agreement.
78 |
79 | Initial Software: means the Software in its Source Code and possibly its
80 | Object Code form and, where applicable, its documentation, "as is" when
81 | it is first distributed under the terms and conditions of the Agreement.
82 |
83 | Modified Software: means the Software modified by at least one
84 | Integrated Contribution.
85 |
86 | Source Code: means all the Software's instructions and program lines to
87 | which access is required so as to modify the Software.
88 |
89 | Object Code: means the binary files originating from the compilation of
90 | the Source Code.
91 |
92 | Holder: means the holder(s) of the economic rights over the Initial
93 | Software.
94 |
95 | Licensee: means the Software user(s) having accepted the Agreement.
96 |
97 | Contributor: means a Licensee having made at least one Integrated
98 | Contribution.
99 |
100 | Licensor: means the Holder, or any other individual or legal entity, who
101 | distributes the Software under the Agreement.
102 |
103 | Integrated Contribution: means any or all modifications, corrections,
104 | translations, adaptations and/or new functions integrated into the
105 | Source Code by any or all Contributors.
106 |
107 | Related Module: means a set of sources files including their
108 | documentation that, without modification to the Source Code, enables
109 | supplementary functions or services in addition to those offered by the
110 | Software.
111 |
112 | Derivative Software: means any combination of the Software, modified or
113 | not, and of a Related Module.
114 |
115 | Parties: mean both the Licensee and the Licensor.
116 |
117 | These expressions may be used both in singular and plural form.
118 |
119 |
120 | Article 2 - PURPOSE
121 |
122 | The purpose of the Agreement is the grant by the Licensor to the
123 | Licensee of a non-exclusive, transferable and worldwide license for the
124 | Software as set forth in Article 5 hereinafter for the whole term of the
125 | protection granted by the rights over said Software.
126 |
127 |
128 | Article 3 - ACCEPTANCE
129 |
130 | 3.1 The Licensee shall be deemed as having accepted the terms and
131 | conditions of this Agreement upon the occurrence of the first of the
132 | following events:
133 |
134 | * (i) loading the Software by any or all means, notably, by
135 | downloading from a remote server, or by loading from a physical
136 | medium;
137 | * (ii) the first time the Licensee exercises any of the rights
138 | granted hereunder.
139 |
140 | 3.2 One copy of the Agreement, containing a notice relating to the
141 | characteristics of the Software, to the limited warranty, and to the
142 | fact that its use is restricted to experienced users has been provided
143 | to the Licensee prior to its acceptance as set forth in Article 3.1
144 | hereinabove, and the Licensee hereby acknowledges that it has read and
145 | understood it.
146 |
147 |
148 | Article 4 - EFFECTIVE DATE AND TERM
149 |
150 |
151 | 4.1 EFFECTIVE DATE
152 |
153 | The Agreement shall become effective on the date when it is accepted by
154 | the Licensee as set forth in Article 3.1.
155 |
156 |
157 | 4.2 TERM
158 |
159 | The Agreement shall remain in force for the entire legal term of
160 | protection of the economic rights over the Software.
161 |
162 |
163 | Article 5 - SCOPE OF RIGHTS GRANTED
164 |
165 | The Licensor hereby grants to the Licensee, who accepts, the following
166 | rights over the Software for any or all use, and for the term of the
167 | Agreement, on the basis of the terms and conditions set forth hereinafter.
168 |
169 | Besides, if the Licensor owns or comes to own one or more patents
170 | protecting all or part of the functions of the Software or of its
171 | components, the Licensor undertakes not to enforce the rights granted by
172 | these patents against successive Licensees using, exploiting or
173 | modifying the Software. If these patents are transferred, the Licensor
174 | undertakes to have the transferees subscribe to the obligations set
175 | forth in this paragraph.
176 |
177 |
178 | 5.1 RIGHT OF USE
179 |
180 | The Licensee is authorized to use the Software, without any limitation
181 | as to its fields of application, with it being hereinafter specified
182 | that this comprises:
183 |
184 | 1. permanent or temporary reproduction of all or part of the Software
185 | by any or all means and in any or all form.
186 |
187 | 2. loading, displaying, running, or storing the Software on any or
188 | all medium.
189 |
190 | 3. entitlement to observe, study or test its operation so as to
191 | determine the ideas and principles behind any or all constituent
192 | elements of said Software. This shall apply when the Licensee
193 | carries out any or all loading, displaying, running, transmission
194 | or storage operation as regards the Software, that it is entitled
195 | to carry out hereunder.
196 |
197 |
198 | 5.2 RIGHT OF MODIFICATION
199 |
200 | The right of modification includes the right to translate, adapt,
201 | arrange, or make any or all modifications to the Software, and the right
202 | to reproduce the resulting software. It includes, in particular, the
203 | right to create a Derivative Software.
204 |
205 | The Licensee is authorized to make any or all modification to the
206 | Software provided that it includes an explicit notice that it is the
207 | author of said modification and indicates the date of the creation thereof.
208 |
209 |
210 | 5.3 RIGHT OF DISTRIBUTION
211 |
212 | In particular, the right of distribution includes the right to publish,
213 | transmit and communicate the Software to the general public on any or
214 | all medium, and by any or all means, and the right to market, either in
215 | consideration of a fee, or free of charge, one or more copies of the
216 | Software by any means.
217 |
218 | The Licensee is further authorized to distribute copies of the modified
219 | or unmodified Software to third parties according to the terms and
220 | conditions set forth hereinafter.
221 |
222 |
223 | 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION
224 |
225 | The Licensee is authorized to distribute true copies of the Software in
226 | Source Code or Object Code form, provided that said distribution
227 | complies with all the provisions of the Agreement and is accompanied by:
228 |
229 | 1. a copy of the Agreement,
230 |
231 | 2. a notice relating to the limitation of both the Licensor's
232 | warranty and liability as set forth in Articles 8 and 9,
233 |
234 | and that, in the event that only the Object Code of the Software is
235 | redistributed, the Licensee allows effective access to the full Source
236 | Code of the Software at a minimum during the entire period of its
237 | distribution of the Software, it being understood that the additional
238 | cost of acquiring the Source Code shall not exceed the cost of
239 | transferring the data.
240 |
241 |
242 | 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE
243 |
244 | When the Licensee makes an Integrated Contribution to the Software, the
245 | terms and conditions for the distribution of the resulting Modified
246 | Software become subject to all the provisions of this Agreement.
247 |
248 | The Licensee is authorized to distribute the Modified Software, in
249 | source code or object code form, provided that said distribution
250 | complies with all the provisions of the Agreement and is accompanied by:
251 |
252 | 1. a copy of the Agreement,
253 |
254 | 2. a notice relating to the limitation of both the Licensor's
255 | warranty and liability as set forth in Articles 8 and 9,
256 |
257 | and that, in the event that only the object code of the Modified
258 | Software is redistributed, the Licensee allows effective access to the
259 | full source code of the Modified Software at a minimum during the entire
260 | period of its distribution of the Modified Software, it being understood
261 | that the additional cost of acquiring the source code shall not exceed
262 | the cost of transferring the data.
263 |
264 |
265 | 5.3.3 DISTRIBUTION OF DERIVATIVE SOFTWARE
266 |
267 | When the Licensee creates Derivative Software, this Derivative Software
268 | may be distributed under a license agreement other than this Agreement,
269 | subject to compliance with the requirement to include a notice
270 | concerning the rights over the Software as defined in Article 6.4.
271 | In the event the creation of the Derivative Software required modification
272 | of the Source Code, the Licensee undertakes that:
273 |
274 | 1. the resulting Modified Software will be governed by this Agreement,
275 | 2. the Integrated Contributions in the resulting Modified Software
276 | will be clearly identified and documented,
277 | 3. the Licensee will allow effective access to the source code of the
278 | Modified Software, at a minimum during the entire period of
279 | distribution of the Derivative Software, such that such
280 | modifications may be carried over in a subsequent version of the
281 | Software; it being understood that the additional cost of
282 | purchasing the source code of the Modified Software shall not
283 | exceed the cost of transferring the data.
284 |
285 |
286 | 5.3.4 COMPATIBILITY WITH THE CeCILL LICENSE
287 |
288 | When a Modified Software contains an Integrated Contribution subject to
289 | the CeCILL license agreement, or when a Derivative Software contains a
290 | Related Module subject to the CeCILL license agreement, the provisions
291 | set forth in the third item of Article 6.4 are optional.
292 |
293 |
294 | Article 6 - INTELLECTUAL PROPERTY
295 |
296 |
297 | 6.1 OVER THE INITIAL SOFTWARE
298 |
299 | The Holder owns the economic rights over the Initial Software. Any or
300 | all use of the Initial Software is subject to compliance with the terms
301 | and conditions under which the Holder has elected to distribute its work
302 | and no one shall be entitled to modify the terms and conditions for the
303 | distribution of said Initial Software.
304 |
305 | The Holder undertakes that the Initial Software will remain ruled at
306 | least by this Agreement, for the duration set forth in Article 4.2.
307 |
308 |
309 | 6.2 OVER THE INTEGRATED CONTRIBUTIONS
310 |
311 | The Licensee who develops an Integrated Contribution is the owner of the
312 | intellectual property rights over this Contribution as defined by
313 | applicable law.
314 |
315 |
316 | 6.3 OVER THE RELATED MODULES
317 |
318 | The Licensee who develops a Related Module is the owner of the
319 | intellectual property rights over this Related Module as defined by
320 | applicable law and is free to choose the type of agreement that shall
321 | govern its distribution under the conditions defined in Article 5.3.3.
322 |
323 |
324 | 6.4 NOTICE OF RIGHTS
325 |
326 | The Licensee expressly undertakes:
327 |
328 | 1. not to remove, or modify, in any manner, the intellectual property
329 | notices attached to the Software;
330 |
331 | 2. to reproduce said notices, in an identical manner, in the copies
332 | of the Software modified or not;
333 |
334 | 3. to ensure that use of the Software, its intellectual property
335 | notices and the fact that it is governed by the Agreement is
336 | indicated in a text that is easily accessible, specifically from
337 | the interface of any Derivative Software.
338 |
339 | The Licensee undertakes not to directly or indirectly infringe the
340 | intellectual property rights of the Holder and/or Contributors on the
341 | Software and to take, where applicable, vis-�-vis its staff, any and all
342 | measures required to ensure respect of said intellectual property rights
343 | of the Holder and/or Contributors.
344 |
345 |
346 | Article 7 - RELATED SERVICES
347 |
348 | 7.1 Under no circumstances shall the Agreement oblige the Licensor to
349 | provide technical assistance or maintenance services for the Software.
350 |
351 | However, the Licensor is entitled to offer this type of services. The
352 | terms and conditions of such technical assistance, and/or such
353 | maintenance, shall be set forth in a separate instrument. Only the
354 | Licensor offering said maintenance and/or technical assistance services
355 | shall incur liability therefor.
356 |
357 | 7.2 Similarly, any Licensor is entitled to offer to its licensees, under
358 | its sole responsibility, a warranty, that shall only be binding upon
359 | itself, for the redistribution of the Software and/or the Modified
360 | Software, under terms and conditions that it is free to decide. Said
361 | warranty, and the financial terms and conditions of its application,
362 | shall be subject of a separate instrument executed between the Licensor
363 | and the Licensee.
364 |
365 |
366 | Article 8 - LIABILITY
367 |
368 | 8.1 Subject to the provisions of Article 8.2, the Licensee shall be
369 | entitled to claim compensation for any direct loss it may have suffered
370 | from the Software as a result of a fault on the part of the relevant
371 | Licensor, subject to providing evidence thereof.
372 |
373 | 8.2 The Licensor's liability is limited to the commitments made under
374 | this Agreement and shall not be incurred as a result of in particular:
375 | (i) loss due the Licensee's total or partial failure to fulfill its
376 | obligations, (ii) direct or consequential loss that is suffered by the
377 | Licensee due to the use or performance of the Software, and (iii) more
378 | generally, any consequential loss. In particular the Parties expressly
379 | agree that any or all pecuniary or business loss (i.e. loss of data,
380 | loss of profits, operating loss, loss of customers or orders,
381 | opportunity cost, any disturbance to business activities) or any or all
382 | legal proceedings instituted against the Licensee by a third party,
383 | shall constitute consequential loss and shall not provide entitlement to
384 | any or all compensation from the Licensor.
385 |
386 |
387 | Article 9 - WARRANTY
388 |
389 | 9.1 The Licensee acknowledges that the scientific and technical
390 | state-of-the-art when the Software was distributed did not enable all
391 | possible uses to be tested and verified, nor for the presence of
392 | possible defects to be detected. In this respect, the Licensee's
393 | attention has been drawn to the risks associated with loading, using,
394 | modifying and/or developing and reproducing the Software which are
395 | reserved for experienced users.
396 |
397 | The Licensee shall be responsible for verifying, by any or all means,
398 | the suitability of the product for its requirements, its good working
399 | order, and for ensuring that it shall not cause damage to either persons
400 | or properties.
401 |
402 | 9.2 The Licensor hereby represents, in good faith, that it is entitled
403 | to grant all the rights over the Software (including in particular the
404 | rights set forth in Article 5).
405 |
406 | 9.3 The Licensee acknowledges that the Software is supplied "as is" by
407 | the Licensor without any other express or tacit warranty, other than
408 | that provided for in Article 9.2 and, in particular, without any warranty
409 | as to its commercial value, its secured, safe, innovative or relevant
410 | nature.
411 |
412 | Specifically, the Licensor does not warrant that the Software is free
413 | from any error, that it will operate without interruption, that it will
414 | be compatible with the Licensee's own equipment and software
415 | configuration, nor that it will meet the Licensee's requirements.
416 |
417 | 9.4 The Licensor does not either expressly or tacitly warrant that the
418 | Software does not infringe any third party intellectual property right
419 | relating to a patent, software or any other property right. Therefore,
420 | the Licensor disclaims any and all liability towards the Licensee
421 | arising out of any or all proceedings for infringement that may be
422 | instituted in respect of the use, modification and redistribution of the
423 | Software. Nevertheless, should such proceedings be instituted against
424 | the Licensee, the Licensor shall provide it with technical and legal
425 | assistance for its defense. Such technical and legal assistance shall be
426 | decided on a case-by-case basis between the relevant Licensor and the
427 | Licensee pursuant to a memorandum of understanding. The Licensor
428 | disclaims any and all liability as regards the Licensee's use of the
429 | name of the Software. No warranty is given as regards the existence of
430 | prior rights over the name of the Software or as regards the existence
431 | of a trademark.
432 |
433 |
434 | Article 10 - TERMINATION
435 |
436 | 10.1 In the event of a breach by the Licensee of its obligations
437 | hereunder, the Licensor may automatically terminate this Agreement
438 | thirty (30) days after notice has been sent to the Licensee and has
439 | remained ineffective.
440 |
441 | 10.2 A Licensee whose Agreement is terminated shall no longer be
442 | authorized to use, modify or distribute the Software. However, any
443 | licenses that it may have granted prior to termination of the Agreement
444 | shall remain valid subject to their having been granted in compliance
445 | with the terms and conditions hereof.
446 |
447 |
448 | Article 11 - MISCELLANEOUS
449 |
450 |
451 | 11.1 EXCUSABLE EVENTS
452 |
453 | Neither Party shall be liable for any or all delay, or failure to
454 | perform the Agreement, that may be attributable to an event of force
455 | majeure, an act of God or an outside cause, such as defective
456 | functioning or interruptions of the electricity or telecommunications
457 | networks, network paralysis following a virus attack, intervention by
458 | government authorities, natural disasters, water damage, earthquakes,
459 | fire, explosions, strikes and labor unrest, war, etc.
460 |
461 | 11.2 Any failure by either Party, on one or more occasions, to invoke
462 | one or more of the provisions hereof, shall under no circumstances be
463 | interpreted as being a waiver by the interested Party of its right to
464 | invoke said provision(s) subsequently.
465 |
466 | 11.3 The Agreement cancels and replaces any or all previous agreements,
467 | whether written or oral, between the Parties and having the same
468 | purpose, and constitutes the entirety of the agreement between said
469 | Parties concerning said purpose. No supplement or modification to the
470 | terms and conditions hereof shall be effective as between the Parties
471 | unless it is made in writing and signed by their duly authorized
472 | representatives.
473 |
474 | 11.4 In the event that one or more of the provisions hereof were to
475 | conflict with a current or future applicable act or legislative text,
476 | said act or legislative text shall prevail, and the Parties shall make
477 | the necessary amendments so as to comply with said act or legislative
478 | text. All other provisions shall remain effective. Similarly, invalidity
479 | of a provision of the Agreement, for any reason whatsoever, shall not
480 | cause the Agreement as a whole to be invalid.
481 |
482 |
483 | 11.5 LANGUAGE
484 |
485 | The Agreement is drafted in both French and English and both versions
486 | are deemed authentic.
487 |
488 |
489 | Article 12 - NEW VERSIONS OF THE AGREEMENT
490 |
491 | 12.1 Any person is authorized to duplicate and distribute copies of this
492 | Agreement.
493 |
494 | 12.2 So as to ensure coherence, the wording of this Agreement is
495 | protected and may only be modified by the authors of the License, who
496 | reserve the right to periodically publish updates or new versions of the
497 | Agreement, each with a separate number. These subsequent versions may
498 | address new issues encountered by Free Software.
499 |
500 | 12.3 Any Software distributed under a given version of the Agreement may
501 | only be subsequently distributed under the same version of the Agreement
502 | or a subsequent version.
503 |
504 |
505 | Article 13 - GOVERNING LAW AND JURISDICTION
506 |
507 | 13.1 The Agreement is governed by French law. The Parties agree to
508 | endeavor to seek an amicable solution to any disagreements or disputes
509 | that may arise during the performance of the Agreement.
510 |
511 | 13.2 Failing an amicable solution within two (2) months as from their
512 | occurrence, and unless emergency proceedings are necessary, the
513 | disagreements or disputes shall be referred to the Paris Courts having
514 | jurisdiction, by the more diligent Party.
515 |
516 |
517 | Version 1.0 dated 2006-09-05.
518 |
519 |
--------------------------------------------------------------------------------
/Piper.kdev4:
--------------------------------------------------------------------------------
1 | [Project]
2 | CreatedFrom=CMakeLists.txt
3 | Manager=KDevCMakeManager
4 | Name=Piper
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Piper is a C++/Qt5 library that let your easily create and edit nodes based graphs.
4 | Piper is under the CeCILL-C licence.
5 |
6 | ###
7 | ## Requirement
8 | You need Qt5 and CMake 3.10 or higher
9 |
10 | ###
11 | ## Build instructions
12 |
13 | cd /your/project/build
14 | cmake ../
15 | make
16 |
17 | ./editor
18 |
19 | ###
20 | ## Example
21 | main.cc is an example of how to use the Piper library in an application.
22 | Note that the example editor need works to be user friendly (i.e. the only way to create a new node is to press '=' key while the scene has the focus).
23 |
--------------------------------------------------------------------------------
/data/theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "node":
3 | {
4 | "name":
5 | {
6 | "font":
7 | {
8 | "name": "Noto",
9 | "weight": "Bold",
10 | "size": 12,
11 | "rgba": [255, 255, 255, 255]
12 | }
13 | },
14 | "border":
15 | {
16 | "normal":
17 | {
18 | "rgba": [50, 50, 50, 255]
19 | },
20 | "selected":
21 | {
22 | "rgba": [170, 80, 80, 255]
23 | }
24 | },
25 | "background":
26 | {
27 | "rgba": [80, 80, 80, 255]
28 | },
29 | "type":
30 | {
31 | "font":
32 | {
33 | "name": "Noto",
34 | "weight": "Normal",
35 | "size": 11,
36 | "rgba": [220, 220, 220, 255]
37 | },
38 | "background":
39 | {
40 | "rgba": [50, 50, 50, 160]
41 | }
42 | }
43 | },
44 | "attribute":
45 | {
46 | "minimize":
47 | {
48 | "font":
49 | {
50 | "name": "Noto",
51 | "weight": "Light",
52 | "size": 10,
53 | "rgba": [220, 220, 220, 255]
54 | },
55 | "connector":
56 | {
57 | "rgba": [0, 0, 0, 255],
58 | "width": 1
59 | }
60 | },
61 | "normal":
62 | {
63 | "font":
64 | {
65 | "name": "Noto",
66 | "weight": "Normal",
67 | "size": 10,
68 | "rgba": [220, 220, 220, 255]
69 | },
70 | "connector":
71 | {
72 | "rgba": [0, 0, 0, 255],
73 | "width": 1
74 | }
75 | },
76 | "highlight":
77 | {
78 | "font":
79 | {
80 | "name": "Noto",
81 | "weight": "Medium",
82 | "size": 10,
83 | "rgba": [220, 220, 220, 255]
84 | },
85 | "connector":
86 | {
87 | "rgba": [250, 250, 250, 255],
88 | "width": 2
89 | }
90 | },
91 | "background":
92 | {
93 | "rgba": [60, 60, 60, 255]
94 | },
95 | "background_alt":
96 | {
97 | "rgba": [70, 70, 70, 255]
98 | }
99 | },
100 | "data_type":
101 | {
102 | "enable_default":
103 | {
104 | "rgba": [255, 155, 0, 255]
105 | },
106 | "disable":
107 | {
108 | "rgba": [80, 80, 80, 255]
109 | },
110 | "neutral":
111 | {
112 | "rgba": [51, 190, 255, 255]
113 | },
114 |
115 | "enable_custom":
116 | {
117 | "customType": { "rgba": [ 0, 179, 57, 255] }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/piper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leducp/Piper/a51c0cbdbe4c864d279d35aa307b1763ee01bc37/piper.png
--------------------------------------------------------------------------------
/ressources/add_tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
75 |
--------------------------------------------------------------------------------
/ressources/arrow-down-disabled.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ressources/arrow-down-hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ressources/arrow-down.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ressources/arrow-up-disabled.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ressources/arrow-up-hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ressources/arrow-up.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ressources/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | add_tab.svg
4 | star.svg
5 |
6 |
7 | style.qss
8 | arrow-up.svg
9 | arrow-up-hover.svg
10 | arrow-up-disabled.svg
11 | arrow-down.svg
12 | arrow-down-hover.svg
13 | arrow-down-disabled.svg
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ressources/star.svg:
--------------------------------------------------------------------------------
1 |
2 |
56 |
--------------------------------------------------------------------------------
/ressources/style.qss:
--------------------------------------------------------------------------------
1 | QLineEdit
2 | {
3 | background-color: rgba(0, 0, 0, 0);
4 | border: 0.1ex;
5 | padding: 0.3ex;
6 | border-radius: 0.2ex;
7 | }
8 |
9 | QAbstractSpinBox
10 | {
11 | background-color: rgba(0, 0, 0, 0);
12 | border: 0.1ex;
13 | padding: 0.3ex;
14 | min-width: 7.5ex;
15 | border-radius: 0.2ex;
16 | }
17 |
18 | QSpinBox::up-button,
19 | QDoubleSpinBox::up-button
20 | {
21 | subcontrol-origin: content;
22 | subcontrol-position: right top;
23 | width: 10px;
24 | height: 7px;
25 | border-width: 1px;
26 | }
27 |
28 | QSpinBox::down-button,
29 | QDoubleSpinBox::down-button
30 | {
31 | subcontrol-origin: content;
32 | subcontrol-position: right bottom;
33 |
34 | width: 10px;
35 | height: 7px;
36 | border-width: 1px;
37 | border-top-width: 0;
38 | }
39 |
40 | QSpinBox::up-arrow,
41 | QDoubleSpinBox::up-arrow
42 | {
43 | border-image: url(:/arrow-up.svg);
44 | }
45 |
46 | QSpinBox::up-arrow:hover,
47 | QSpinBox::up-arrow:pressed,
48 | QDoubleSpinBox::up-arrow:hover,
49 | QDoubleSpinBox::up-arrow:pressed
50 | {
51 | border-image: url(:/arrow-up-hover.svg);
52 | }
53 |
54 | QSpinBox::up-arrow:disabled,
55 | QSpinBox::up-arrow:off,
56 | QDoubleSpinBox::up-arrow:disabled,
57 | QDoubleSpinBox::up-arrow:off
58 | {
59 | border-image: url(:/arrow-up-disabled.svg);
60 | }
61 |
62 | QSpinBox::down-arrow,
63 | QDoubleSpinBox::down-arrow
64 | {
65 | border-image: url(:/arrow-down.svg);
66 | }
67 |
68 | QSpinBox::down-arrow:hover,
69 | QSpinBox::down-arrow:pressed,
70 | QDoubleSpinBox::down-arrow:hover,
71 | QDoubleSpinBox::down-arrow:pressed
72 | {
73 | border-image: url(:/arrow-down-hover.svg);
74 | }
75 |
76 | QSpinBox::down-arrow:disabled,
77 | QSpinBox::down-arrow:off,
78 | QDoubleSpinBox::down-arrow:disabled,
79 | QDoubleSpinBox::down-arrow:off
80 | {
81 | border-image: url(:/arrow-down-disabled.svg);
82 | }
83 |
--------------------------------------------------------------------------------
/src/Attribute.cc:
--------------------------------------------------------------------------------
1 | #include "Attribute.h"
2 | #include "Link.h"
3 | #include "Node.h"
4 | #include "Scene.h"
5 | #include "ThemeManager.h"
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 |
15 | namespace piper
16 | {
17 | QDataStream& operator<<(QDataStream& out, AttributeInfo const& info)
18 | {
19 | out << info.name << info.dataType << info.type;
20 | return out;
21 | }
22 |
23 |
24 | QDataStream& operator>>(QDataStream& in, AttributeInfo& info)
25 | {
26 | int type;
27 | in >> info.name >> info.dataType >> type;
28 | info.type = static_cast(type);
29 | return in;
30 | }
31 |
32 |
33 | Attribute::Attribute(QGraphicsItem* parent, AttributeInfo const& info, QRect const& boundingRect)
34 | : QGraphicsItem(parent)
35 | , info_{info}
36 | , bounding_rect_{boundingRect}
37 | , background_rect_{bounding_rect_}
38 | , label_rect_{
39 | bounding_rect_.left() + 15, bounding_rect_.top(), bounding_rect_.width() - 30, bounding_rect_.height()}
40 | {
41 | AttributeTheme theme = ThemeManager::instance().getAttributeTheme();
42 | DataTypeTheme typeTheme = ThemeManager::instance().getDataTypeTheme(dataType());
43 |
44 | minimize_pen_.setStyle(Qt::SolidLine);
45 | minimize_pen_.setWidth(theme.minimize.connector.border_width);
46 | minimize_pen_.setColor(theme.minimize.connector.border_color);
47 | minimize_font_ = theme.minimize.font;
48 | minimize_font_pen_.setStyle(Qt::SolidLine);
49 | minimize_font_pen_.setColor(theme.minimize.font_color);
50 |
51 | normal_pen_.setStyle(Qt::SolidLine);
52 | normal_pen_.setWidth(theme.normal.connector.border_width);
53 | normal_pen_.setColor(theme.normal.connector.border_color);
54 | normal_brush_.setStyle(Qt::SolidPattern);
55 | normal_brush_.setColor(typeTheme.enable);
56 | normal_font_ = theme.normal.font;
57 | normal_font_pen_.setStyle(Qt::SolidLine);
58 | normal_font_pen_.setColor(theme.normal.font_color);
59 |
60 | highlight_pen_.setStyle(Qt::SolidLine);
61 | highlight_pen_.setWidth(theme.highlight.connector.border_width);
62 | highlight_pen_.setColor(theme.highlight.connector.border_color);
63 | highlight_brush_.setStyle(Qt::SolidPattern);
64 | highlight_brush_.setColor(typeTheme.enable);
65 | highlight_font_ = theme.highlight.font;
66 | highlight_font_pen_.setStyle(Qt::SolidLine);
67 | highlight_font_pen_.setColor(theme.highlight.font_color);
68 |
69 | prepareGeometryChange();
70 | }
71 |
72 |
73 | Attribute::~Attribute()
74 | {
75 | // Disconnect related links.
76 | QVector linksCopy = links_; //Create a copy: links_ as the list is altered while looping
77 | for (auto& link : linksCopy)
78 | {
79 | link->disconnect();
80 | }
81 | }
82 |
83 |
84 | void Attribute::setColor(QColor const& color)
85 | {
86 | normal_brush_.setColor(color);
87 | highlight_brush_.setColor(color);
88 | update();
89 | }
90 |
91 |
92 | void Attribute::connect(Link* link)
93 | {
94 | DataTypeTheme typeTheme = ThemeManager::instance().getDataTypeTheme(dataType());
95 | links_.append(link);
96 | link->setColor(typeTheme.enable);
97 | }
98 |
99 |
100 | void Attribute::updateRectSize(QRectF rectangle)
101 | {
102 | bounding_rect_ = rectangle;
103 | background_rect_ = bounding_rect_;
104 | }
105 |
106 |
107 | void Attribute::refresh()
108 | {
109 | for (auto& link : links_)
110 | {
111 | link->updatePath();
112 | }
113 | }
114 |
115 |
116 | void Attribute::applyFontStyle(QPainter* painter, DisplayMode mode)
117 | {
118 | switch (mode)
119 | {
120 | case DisplayMode::highlight:
121 | {
122 | painter->setFont(highlight_font_);
123 | painter->setPen(highlight_font_pen_);
124 | break;
125 | }
126 | case DisplayMode::normal:
127 | {
128 | painter->setFont(normal_font_);
129 | painter->setPen(normal_font_pen_);
130 | break;
131 | }
132 | case DisplayMode::minimize:
133 | {
134 | painter->setFont(minimize_font_);
135 | painter->setPen(minimize_font_pen_);
136 | break;
137 | }
138 | }
139 | }
140 |
141 | void Attribute::applyStyle(QPainter* painter, DisplayMode mode)
142 | {
143 | switch (mode)
144 | {
145 | case DisplayMode::highlight:
146 | {
147 | painter->setBrush(highlight_brush_);
148 | painter->setPen(highlight_pen_);
149 | break;
150 | }
151 | case DisplayMode::normal:
152 | {
153 | painter->setBrush(normal_brush_);
154 | painter->setPen(normal_pen_);
155 | break;
156 | }
157 | case DisplayMode::minimize:
158 | {
159 | painter->setBrush(background_brush_);
160 | painter->setPen(minimize_pen_);
161 | break;
162 | }
163 | }
164 | }
165 |
166 |
167 | void Attribute::paint(QPainter* painter, QStyleOptionGraphicsItem const*, QWidget*)
168 | {
169 | // NodeAttribute background.
170 | painter->setBrush(background_brush_);
171 | painter->setPen(Qt::NoPen);
172 | painter->drawRect(background_rect_);
173 |
174 | // NodeAttribute label.
175 | applyFontStyle(painter, mode_);
176 | painter->drawText(QPoint(bounding_rect_.left() + 15, bounding_rect_.top() + 20), name());
177 | int labelWitdth = painter->fontMetrics().width(name());
178 | label_rect_.setWidth(labelWitdth);
179 | }
180 |
181 |
182 |
183 | AttributeOutput::AttributeOutput(QGraphicsItem* parent, AttributeInfo const& info, QRect const& boundingRect)
184 | : Attribute(parent, info, boundingRect)
185 | {
186 | updateConnectorPosition();
187 |
188 | // Use data member to store connector position.
189 | setData(false);
190 |
191 | // Update bounding rect to include connector positions
192 | bounding_rect_ = bounding_rect_.united(connector_rect_left_);
193 | bounding_rect_ = bounding_rect_.united(connector_rect_right_);
194 | bounding_rect_ += QMargins(20, 0, 20, 0);
195 | prepareGeometryChange();
196 | }
197 |
198 |
199 | void AttributeOutput::updateConnectorPosition()
200 | {
201 | // Compute connector rectangle.
202 | qreal const length = bounding_rect_.height() / 4.0;
203 |
204 | connector_rect_left_ = QRectF{bounding_rect_.left() - length - 1, length, length * 2, length * 2};
205 |
206 | connector_rect_right_ = QRectF{bounding_rect_.right() - length + 1, length, length * 2, length * 2};
207 |
208 | setData(data_);
209 | }
210 |
211 |
212 | void AttributeOutput::setColor(QColor const& color)
213 | {
214 | Attribute::setColor(color);
215 | for (auto& link : links_)
216 | {
217 | link->setColor(color);
218 | }
219 | }
220 |
221 |
222 |
223 | void AttributeOutput::setData(QVariant const& data)
224 | {
225 | if (data.canConvert(QMetaType::Bool))
226 | {
227 | data_ = data;
228 | }
229 | else
230 | {
231 | data_ = false;
232 | }
233 |
234 | if (data_.toBool())
235 | {
236 | connectorRect_ = &connector_rect_left_;
237 | }
238 | else
239 | {
240 | connectorRect_ = &connector_rect_right_;
241 | }
242 | // Compute connector center to position the path.
243 | connectorPos_ = {connectorRect_->x() + connectorRect_->width() / 2.0,
244 | connectorRect_->y() + connectorRect_->height() / 2.0};
245 | update();
246 | }
247 |
248 |
249 | void AttributeOutput::paint(QPainter* painter, QStyleOptionGraphicsItem const*, QWidget*)
250 | {
251 | // Draw generic part (label and background).
252 | Attribute::paint(painter, nullptr, nullptr);
253 |
254 | applyStyle(painter, mode_);
255 | painter->drawEllipse(*connectorRect_);
256 | }
257 |
258 |
259 | void AttributeOutput::mousePressEvent(QGraphicsSceneMouseEvent* event)
260 | {
261 | Scene* pScene = static_cast(scene());
262 |
263 | if (connectorRect_->contains(event->pos()) and event->button() == Qt::LeftButton)
264 | {
265 | new_connection_ = new Link();
266 | new_connection_->connectFrom(this);
267 | new_connection_->setColor(normal_brush_.color());
268 | pScene->addLink(new_connection_);
269 |
270 | for (auto const& item : pScene->nodes())
271 | {
272 | item->highlight(this);
273 | }
274 |
275 | return;
276 | }
277 |
278 | if (event->button() == Qt::MiddleButton)
279 | {
280 | setData(not data_.toBool());
281 | }
282 |
283 | Attribute::mousePressEvent(event);
284 | }
285 |
286 |
287 | void AttributeOutput::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
288 | {
289 | if (new_connection_ == nullptr)
290 | {
291 | // Nothing to do
292 | Attribute::mouseMoveEvent(event);
293 | return;
294 | }
295 |
296 | new_connection_->updatePath(event->scenePos());
297 | }
298 |
299 |
300 | void AttributeOutput::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
301 | {
302 | Scene* pScene = static_cast(scene());
303 | if ((new_connection_ == nullptr) or (event->button() != Qt::LeftButton))
304 | {
305 | // Nothing to do
306 | Attribute::mouseReleaseEvent(event);
307 | return;
308 | }
309 |
310 | // Disable highlight
311 | for (auto const& item : pScene->nodes())
312 | {
313 | item->unhighlight();
314 | }
315 |
316 | AttributeInput* input = qgraphicsitem_cast(scene()->itemAt(event->scenePos(), QTransform()));
317 | if (input != nullptr)
318 | {
319 | if (input->accept(this))
320 | {
321 | new_connection_->connectTo(input);
322 | new_connection_ = nullptr; // connection finished.
323 | return;
324 | }
325 | }
326 |
327 | // cleanup unfinalized connection.
328 | delete new_connection_;
329 | new_connection_ = nullptr;
330 | }
331 |
332 |
333 |
334 | AttributeInput::AttributeInput(QGraphicsItem* parent, AttributeInfo const& info, QRect const& boundingRect)
335 | : Attribute(parent, info, boundingRect)
336 | {
337 | data_ = false; // Use data member to store connector position.
338 |
339 | updateConnectorPosition();
340 |
341 | bounding_rect_ += QMargins(2, 0, 2, 0);
342 | prepareGeometryChange();
343 |
344 | setData(false);
345 | }
346 |
347 |
348 | void AttributeInput::updateConnectorPosition()
349 | {
350 | // Compute input inputTriangle_
351 | qreal length = bounding_rect_.height() / 4.0;
352 | input_triangle_left_[0] = QPointF(bounding_rect_.left() - 1, length);
353 | input_triangle_left_[1] = QPointF(bounding_rect_.left() + length * 1.5, length * 2);
354 | input_triangle_left_[2] = QPointF(bounding_rect_.left() - 1, length * 3);
355 |
356 | input_triangle_right_[0] = QPointF(bounding_rect_.right() + 1, length);
357 | input_triangle_right_[1] = QPointF(bounding_rect_.right() - length * 1.5, length * 2);
358 | input_triangle_right_[2] = QPointF(bounding_rect_.right() + 1, length * 3);
359 |
360 | setData(data_);
361 | }
362 |
363 |
364 | void AttributeInput::setData(QVariant const& data)
365 | {
366 | if (data.canConvert(QMetaType::Bool))
367 | {
368 | data_ = data;
369 | }
370 | else
371 | {
372 | data_ = false;
373 | }
374 |
375 | if (data_.toBool())
376 | {
377 | input_triangle_ = input_triangle_right_;
378 | }
379 | else
380 | {
381 | input_triangle_ = input_triangle_left_;
382 | }
383 |
384 | // Compute connector center to position the path.
385 | qreal x = input_triangle_[0].x();
386 | qreal y = input_triangle_[2].y() - input_triangle_[0].y();
387 | connectorPos_ = {x, y};
388 | update();
389 | }
390 |
391 | bool AttributeInput::accept(Attribute* attribute) const
392 | {
393 | if (attribute->dataType() != dataType())
394 | {
395 | // Incompatible type.
396 | return false;
397 | }
398 |
399 | if (attribute->parentItem() == parentItem())
400 | {
401 | // can't be connected to another attribute of the same item.
402 | return false;
403 | }
404 |
405 | for (auto& link : links_)
406 | {
407 | if (link->from() == attribute)
408 | {
409 | // We are already connected to this guy.
410 | return false;
411 | }
412 | }
413 |
414 | return true;
415 | }
416 |
417 |
418 | void AttributeInput::paint(QPainter* painter, QStyleOptionGraphicsItem const*, QWidget*)
419 | {
420 | // Draw generic part (label and background).
421 | Attribute::paint(painter, nullptr, nullptr);
422 |
423 | applyStyle(painter, mode_);
424 | painter->drawConvexPolygon(input_triangle_, 3);
425 | }
426 |
427 |
428 | void AttributeInput::mousePressEvent(QGraphicsSceneMouseEvent* event)
429 | {
430 | if (event->button() == Qt::MiddleButton)
431 | {
432 | setData(not data_.toBool());
433 | }
434 | Attribute::mousePressEvent(event);
435 | }
436 | }
437 |
--------------------------------------------------------------------------------
/src/Attribute.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_ATTRIBUTE_H
2 | #define PIPER_ATTRIBUTE_H
3 |
4 | #include
5 | #include
6 |
7 | namespace piper
8 | {
9 | class Link;
10 |
11 | struct AttributeInfo
12 | {
13 | QString name;
14 | QString dataType;
15 | enum Type
16 | {
17 | input = 0,
18 | output = 1,
19 | member = 2
20 | } type;
21 | };
22 |
23 | QDataStream& operator<<(QDataStream& out, AttributeInfo const& info);
24 | QDataStream& operator>>(QDataStream& in, AttributeInfo& info);
25 |
26 | enum DisplayMode
27 | {
28 | minimize,
29 | normal,
30 | highlight
31 | };
32 |
33 |
34 | class Attribute : public QGraphicsItem
35 | {
36 | public:
37 | Attribute (QGraphicsItem* parent, AttributeInfo const& info, QRect const& boundingRect);
38 | virtual ~Attribute();
39 |
40 | AttributeInfo const& info() const { return info_; }
41 | QString const& name() const { return info_.name; }
42 | QString const& dataType() const { return info_.dataType; }
43 | bool isInput() const { return (info_.type == AttributeInfo::Type::input); }
44 | bool isOutput() const { return (info_.type == AttributeInfo::Type::output); }
45 | bool isMember() const { return (info_.type == AttributeInfo::Type::member); }
46 |
47 | void setBackgroundBrush(QBrush const& brush) { background_brush_ = brush; }
48 | virtual void setColor(QColor const& color);
49 | virtual void updateConnectorPosition(){}
50 |
51 |
52 | virtual QPointF connectorPos() const { return QPointF{}; }
53 | virtual bool accept(Attribute*) const { return false; }
54 | void connect(Link* link);
55 | void disconnect(Link* link) { links_.removeAll(link); }
56 | void refresh();
57 |
58 | // Highlight compatible attributes and geyed out other.
59 | void highlight();
60 |
61 | // Revert back the highlight state.
62 | void unhighlight();
63 |
64 | QVariant const& data() const { return data_; }
65 | virtual void setData(QVariant const& data) { data_ = data; }
66 |
67 | void setMode(DisplayMode mode) { mode_ = mode; }
68 | virtual void updateRectSize(QRectF rectangle);
69 | QRectF boundingRect() const override { return bounding_rect_; }
70 | QRectF labelRect() const { return label_rect_; }
71 | virtual qreal getFormBaseWidth() const { return 0; };
72 |
73 |
74 | // Enable the use of qgraphicsitem_cast with this item.
75 | enum { Type = UserType + 1 };
76 | int type() const override { return Type; }
77 |
78 | protected:
79 | void paint(QPainter* painter, QStyleOptionGraphicsItem const*, QWidget*) override;
80 |
81 | void applyFontStyle(QPainter* painter, DisplayMode mode);
82 | void applyStyle(QPainter* painter, DisplayMode mode);
83 |
84 | AttributeInfo info_;
85 | QVariant data_;
86 | DisplayMode mode_{DisplayMode::normal};
87 |
88 | QBrush background_brush_;
89 |
90 | QFont minimize_font_;
91 | QPen minimize_font_pen_;
92 | QPen minimize_pen_;
93 |
94 | QFont normal_font_;
95 | QPen normal_font_pen_;
96 | QBrush normal_brush_;
97 | QPen normal_pen_;
98 |
99 | QFont highlight_font_;
100 | QPen highlight_font_pen_;
101 | QBrush highlight_brush_;
102 | QPen highlight_pen_;
103 |
104 | QRectF bounding_rect_;
105 | QRectF background_rect_;
106 | QRectF label_rect_;
107 |
108 | QVector links_;
109 | };
110 |
111 |
112 | class AttributeOutput : public Attribute
113 | {
114 | public:
115 | AttributeOutput(QGraphicsItem* parent, AttributeInfo const& info, QRect const& boundingRect);
116 | virtual ~AttributeOutput() = default;
117 |
118 | void setColor(QColor const& color) override;
119 | void updateConnectorPosition() override;
120 | void setData(QVariant const& data) override;
121 | QPointF connectorPos() const override { return mapToScene(connectorPos_); }
122 |
123 | protected:
124 | void paint(QPainter* painter, QStyleOptionGraphicsItem const*, QWidget*) override;
125 | void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
126 | void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
127 | void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
128 |
129 | QRectF connector_rect_left_;
130 | QRectF connector_rect_right_;
131 | QRectF* connectorRect_;
132 | QPointF connectorPos_;
133 |
134 | Link* new_connection_{nullptr};
135 | };
136 |
137 |
138 | class AttributeInput : public Attribute
139 | {
140 | public:
141 | AttributeInput(QGraphicsItem* parent, AttributeInfo const& info, QRect const& boundingRect);
142 | virtual ~AttributeInput() = default;
143 |
144 | void setData(QVariant const& data) override;
145 | void updateConnectorPosition() override;
146 | bool accept(Attribute* attribute) const override;
147 | QPointF connectorPos() const override { return mapToScene(connectorPos_); }
148 |
149 | protected:
150 | void paint(QPainter* painter, QStyleOptionGraphicsItem const*, QWidget*) override;
151 | void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
152 |
153 | QPointF input_triangle_left_[3];
154 | QPointF input_triangle_right_[3];
155 | QPointF* input_triangle_;
156 | QPointF connectorPos_;
157 | };
158 | }
159 |
160 | #endif
161 |
--------------------------------------------------------------------------------
/src/AttributeMember.cc:
--------------------------------------------------------------------------------
1 | #include "AttributeMember.h"
2 | #include "Scene.h"
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | namespace piper
9 | {
10 | MemberForm::MemberForm(QGraphicsItem* parent, QVariant& data, QRectF const& boundingRect, QBrush const& brush)
11 | : QGraphicsProxyWidget(parent)
12 | , data_{data}
13 | , bounding_rect_{boundingRect}
14 | , brush_{brush}
15 | {
16 | }
17 |
18 | void MemberForm::updateFormWidth(qreal width)
19 | {
20 | bounding_rect_.setWidth(width);
21 | widget()->resize(static_cast(width), static_cast(bounding_rect_.height()));
22 | }
23 |
24 | void MemberForm::paint(QPainter* painter, QStyleOptionGraphicsItem const* option, QWidget* widget)
25 | {
26 | painter->setPen(Qt::NoPen);
27 | painter->setBrush(brush_);
28 | painter->drawRoundedRect(bounding_rect_, 8, 8);
29 |
30 | QGraphicsProxyWidget::paint(painter, option, widget);
31 | }
32 |
33 | void MemberForm::mousePressEvent(QGraphicsSceneMouseEvent* event)
34 | {
35 | (void)event;
36 | Scene* pScene = static_cast(scene());
37 |
38 | for (auto& item : pScene->selectedItems())
39 | {
40 | item->setSelected(false);
41 | }
42 | }
43 |
44 | void MemberForm::onDataUpdated(QVariant const& data)
45 | {
46 | data_ = data;
47 | int width = widget()->fontMetrics().boundingRect(data.toString()).width();
48 | AttributeMember* member = qgraphicsitem_cast(parentItem());
49 | member->setFormBaseWidth(static_cast(width));
50 | }
51 |
52 |
53 | AttributeMember::AttributeMember(QGraphicsItem* parent, AttributeInfo const& info, const QRect& boundingRect)
54 | : Attribute(parent, info, boundingRect)
55 | {
56 | // Construct the form (area, background color, widget, widgets options etc).
57 | QRectF formRect{0, 0, bounding_rect_.width(), bounding_rect_.height() - 10};
58 | QBrush brush{{180, 180, 180, 255}, Qt::SolidPattern};
59 | form_ = new MemberForm(this, data_, formRect, brush);
60 | baseWidth_ = 0;
61 |
62 | QWidget* widget = createWidget();
63 | if (widget != nullptr)
64 | {
65 | widget->setFont(normal_font_);
66 | widget->resize(static_cast(formRect.width()), static_cast(formRect.height()));
67 |
68 | QFile File(":/style.qss");
69 | File.open(QFile::ReadOnly);
70 | QString StyleSheet = QLatin1String(File.readAll());
71 | widget->setStyleSheet(StyleSheet);
72 | form_->setWidget(widget);
73 | }
74 | form_->setPos(boundingRect.right() - formRect.width(), label_rect_.top() + 5);
75 | }
76 |
77 | void AttributeMember::setFormBaseWidth(qreal width)
78 | {
79 | baseWidth_ = width;
80 | }
81 |
82 | void AttributeMember::updateRectSize(QRectF rectangle)
83 | {
84 | bounding_rect_ = rectangle;
85 | background_rect_ = bounding_rect_;
86 | form_->updateFormWidth(bounding_rect_.width() - label_rect_.width() - 20);
87 | form_->setPos(bounding_rect_.right() - form_->boundingRect().width(), label_rect_.top() + 5);
88 | }
89 |
90 | void AttributeMember::setData(QVariant const& data)
91 | {
92 | switch (data.type())
93 | {
94 | //QJsonValue always returns QVariant::Double with numbers
95 | //send both signals, the widget with corresponding type will receive values
96 | case QVariant::Int:
97 | case QVariant::Double:
98 | {
99 | form_->dataUpdated(data.toInt());
100 | form_->dataUpdated(data.toDouble());
101 | break;
102 | }
103 | case QVariant::String:
104 | {
105 | form_->dataUpdated(data.toString());
106 | break;
107 | }
108 | default:
109 | {
110 | qDebug() << "Incompatible type: " << data << ". Do nothing";
111 | }
112 | }
113 | }
114 |
115 | QWidget* AttributeMember::createWidget()
116 | {
117 | QStringList supportedTypes;
118 |
119 | supportedTypes << "int"
120 | << "integer"
121 | << "int32_t"
122 | << "int64_t";
123 | if (supportedTypes.contains(dataType()))
124 | {
125 | QSpinBox* box = new QSpinBox();
126 | data_ = box->value();
127 | box->setMaximum(std::numeric_limits::max());
128 | box->setMinimum(std::numeric_limits::min());
129 | QObject::connect(box, QOverload::of(&QSpinBox::valueChanged), form_, &MemberForm::onDataUpdated);
130 | QObject::connect(form_, SIGNAL(dataUpdated(int)), box, SLOT(setValue(int)));
131 | return box;
132 | }
133 |
134 | supportedTypes.clear();
135 | supportedTypes << "float"
136 | << "double"
137 | << "real"
138 | << "float32_t"
139 | << "float64_t";
140 | if (supportedTypes.contains(dataType()))
141 | {
142 | QDoubleSpinBox* box = new QDoubleSpinBox();
143 | data_ = box->value();
144 | box->setMaximum(std::numeric_limits::max());
145 | box->setMinimum(-std::numeric_limits::max());
146 | box->setDecimals(10);
147 | QObject::connect(
148 | box, QOverload::of(&QDoubleSpinBox::valueChanged), form_, &MemberForm::onDataUpdated);
149 | QObject::connect(form_, SIGNAL(dataUpdated(double)), box, SLOT(setValue(double)));
150 | return box;
151 | }
152 |
153 | supportedTypes.clear();
154 | supportedTypes << "string";
155 | if (supportedTypes.contains(dataType()))
156 | {
157 | QLineEdit* lineEdit = new QLineEdit();
158 | data_ = lineEdit->text();
159 | lineEdit->setFont(normal_font_);
160 | QObject::connect(lineEdit, &QLineEdit::textChanged, form_, &MemberForm::onDataUpdated);
161 | QObject::connect(form_, SIGNAL(dataUpdated(QString const&)), lineEdit, SLOT(setText(QString const&)));
162 | return lineEdit;
163 | }
164 |
165 | return nullptr;
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/AttributeMember.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_ATTRIBUTE_MEMBER_H
2 | #define PIPER_ATTRIBUTE_MEMBER_H
3 |
4 | #include "Attribute.h"
5 |
6 | #include
7 |
8 |
9 | namespace piper
10 | {
11 | /// \brief Handle the member form (draw the backround and own the dedicated QWidget)
12 | class MemberForm : public QGraphicsProxyWidget
13 | {
14 | Q_OBJECT
15 |
16 | public:
17 | MemberForm(QGraphicsItem* parent, QVariant& data, QRectF const& boundingRect, QBrush const& brush);
18 | virtual ~MemberForm() = default;
19 |
20 | QRectF boundingRect() const override { return bounding_rect_; }
21 | void updateFormWidth(qreal data);
22 |
23 | signals:
24 | void dataUpdated(int);
25 | void dataUpdated(double);
26 | void dataUpdated(QString const&);
27 |
28 | public slots:
29 | void onDataUpdated(QVariant const& data);
30 |
31 | protected:
32 | void paint(QPainter* painter, QStyleOptionGraphicsItem const*, QWidget*) override;
33 | void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
34 |
35 | private:
36 | QVariant& data_; // reference on attribute's data
37 | QRectF bounding_rect_;
38 | QBrush brush_;
39 | };
40 |
41 | ///\brief A Node attribute that can be edited by the user.
42 | class AttributeMember : public Attribute
43 | {
44 | public:
45 | AttributeMember(QGraphicsItem* parent, AttributeInfo const& info, QRect const& boundingRect);
46 | virtual ~AttributeMember() = default;
47 |
48 | // Set the data by working closely with the MemberForm class.
49 | void setData(QVariant const& data) override;
50 | void updateRectSize(QRectF rectangle) override;
51 | void setFormBaseWidth(qreal width);
52 | qreal getFormBaseWidth() const override { return baseWidth_; };
53 |
54 | private:
55 | QWidget* createWidget();
56 |
57 | MemberForm* form_;
58 | qreal baseWidth_;
59 | };
60 | }
61 |
62 | #endif
63 |
--------------------------------------------------------------------------------
/src/CreatorPopup.cc:
--------------------------------------------------------------------------------
1 | #include "CreatorPopup.h"
2 | #include "NodeCreator.h"
3 | #include "Scene.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | namespace piper
11 | {
12 | CreatorPopup::CreatorPopup(View* view)
13 | : QLineEdit(view)
14 | , view_{view}
15 | {
16 | model_ = new QStringListModel();
17 | QCompleter* completer = new QCompleter(model_, this);
18 | completer->setCaseSensitivity(Qt::CaseInsensitive);
19 | completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
20 | completer->setMaxVisibleItems(20);
21 | completer->popup()->setStyleSheet(
22 | "background:transparent; "
23 | "border: 1px solid #ff9b00; "
24 | "color: #F2F2F2; "
25 | "selection-background-color: #E68A00; "
26 | );
27 | setCompleter(completer);
28 | setStyleSheet(
29 | "QLineEdit { "
30 | "background:transparent; "
31 | "border: 1px solid #ff9b00; "
32 | "color: #F2F2F2; "
33 | "}"
34 | );
35 |
36 | QObject::connect(this, &QLineEdit::returnPressed, this, &CreatorPopup::onReturnPressed);
37 |
38 | popdown();
39 | }
40 |
41 |
42 | void CreatorPopup::popup()
43 | {
44 | // Adjust size and populate content.
45 | QStringList types;
46 | QSize targetSize = size();
47 | for (auto const& item : NodeCreator::instance().availableItems())
48 | {
49 | QSize fontSize = fontMetrics().boundingRect(item.type).size();
50 | types << item.type;
51 | targetSize.setWidth(std::max(targetSize.width(), fontSize.width() + 30)); // +30px for margin
52 | }
53 | model_->setStringList(types);
54 | resize(targetSize);
55 |
56 | QPoint position = parentWidget()->mapFromGlobal(QCursor::pos());
57 | move(position);
58 | clear();
59 | show();
60 | setFocus();
61 |
62 | // display all solution at first glance.
63 | completer()->complete();
64 | }
65 |
66 |
67 | void CreatorPopup::popdown()
68 | {
69 | hide();
70 | clear();
71 | view_->setFocus();
72 | }
73 |
74 |
75 | void CreatorPopup::onReturnPressed()
76 | {
77 | QPointF scenePos = view_->mapToScene(pos());
78 | Scene* piperScene = static_cast(view_->scene());
79 |
80 | QString type = text();
81 | popdown();
82 |
83 | QString nextName = type + "_" + QString::number(piperScene->nodes().size());
84 | Node* node = NodeCreator::instance().createItem(type, nextName, "", scenePos);
85 | if (node != nullptr)
86 | {
87 | piperScene->addNode(node);
88 | }
89 | }
90 |
91 |
92 | void CreatorPopup::focusOutEvent(QFocusEvent*)
93 | {
94 | popdown();
95 | }
96 |
97 |
98 | bool CreatorPopup::event(QEvent* event)
99 | {
100 | if (event->type() == QEvent::KeyPress)
101 | {
102 | QKeyEvent *keyEvent = static_cast(event);
103 | if (keyEvent->key() == Qt::Key_Tab)
104 | {
105 | QModelIndex index = completer()->currentIndex();
106 | QString type = model_->itemData(index)[Qt::EditRole].toString();
107 | setText(type);
108 | return true;
109 | }
110 | }
111 | return QWidget::event(event);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/CreatorPopup.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_CREATOR_POPUP_H
2 | #define PIPER_CREATOR_POPUP_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "View.h"
9 |
10 | namespace piper
11 | {
12 | class CreatorPopup : public QLineEdit
13 | {
14 | Q_OBJECT
15 |
16 | public:
17 | CreatorPopup(View* view);
18 | virtual ~CreatorPopup() = default;
19 |
20 | void popup();
21 | void popdown();
22 |
23 | public slots:
24 | void onReturnPressed();
25 |
26 | protected:
27 | void focusOutEvent(QFocusEvent*) override;
28 | bool event(QEvent *event) override;
29 |
30 | private:
31 | QStringListModel* model_;
32 | View* view_;
33 | };
34 | }
35 |
36 | #endif
37 |
38 |
--------------------------------------------------------------------------------
/src/EditorTab.cc:
--------------------------------------------------------------------------------
1 | #include "EditorTab.h"
2 | #include "EditorWidget.h"
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | namespace piper
10 | {
11 |
12 | TabHeaderEdit::TabHeaderEdit(QString const& text)
13 | : QLineEdit(text)
14 | { }
15 |
16 |
17 | void TabHeaderEdit::mousePressEvent(QMouseEvent *event)
18 | {
19 | QLineEdit::mousePressEvent(event);
20 | QWidget::mousePressEvent(event); // let the parent do its work too !
21 | }
22 |
23 |
24 | TabNameValidator::TabNameValidator(QObject* parent)
25 | : QValidator(parent)
26 | { }
27 |
28 |
29 | QValidator::State TabNameValidator::validate(QString& input, int& pos) const
30 | {
31 | for (auto const& c : input)
32 | {
33 | // Refuse non plain ascii name.
34 | if (c.unicode() > 127)
35 | {
36 | return QValidator::Invalid;
37 | }
38 | }
39 |
40 | return QValidator::Acceptable;
41 | }
42 |
43 |
44 | EditorTab::EditorTab(QWidget* parent)
45 | : QTabWidget(parent)
46 | , tabNameValidator_(new TabNameValidator(this))
47 | {
48 | // Create adder button
49 | QPushButton* tb = new QPushButton();
50 | tb->setFlat(true);
51 | tb->setIcon(QIcon(":/icon/add_tab.svg"));
52 | tb->setStyleSheet("* { icon-size: 30px 30px; }");
53 | setCornerWidget(tb, Qt::TopLeftCorner);
54 |
55 | // Ensure that button will be always visible.
56 | setStyleSheet("QTabBar::tab { height: 40px; }");
57 | tb->setMinimumSize(40, 40);
58 | tabBar()->setMinimumSize(40, 40);
59 |
60 | // Connect signal to manage tab creation/rename/deletion
61 | QObject::connect(tb, &QPushButton::clicked, this, &EditorTab::createNewEditorTab);
62 | QObject::connect(this, &QTabWidget::tabCloseRequested, this, &EditorTab::closeEditorTab);
63 | }
64 |
65 |
66 | QString EditorTab::name(int32_t index) const
67 | {
68 | TabHeaderEdit* edit = static_cast(tabBar()->tabButton(index, QTabBar::LeftSide));
69 | return edit->text();
70 | }
71 |
72 |
73 | void EditorTab::setName(int32_t index, QString const& name)
74 | {
75 | TabHeaderEdit* edit = static_cast(tabBar()->tabButton(index, QTabBar::LeftSide));
76 | edit->setText(name);
77 | }
78 |
79 |
80 | EditorWidget* EditorTab::createNewEditorTab()
81 | {
82 | EditorWidget* editor = new EditorWidget();
83 | int32_t index = addTab(editor, "");
84 |
85 | QString defaultText = "unamed pipeline";
86 | tabNameValidator_->fixup(defaultText);
87 |
88 | TabHeaderEdit* edit = new TabHeaderEdit(defaultText);
89 | edit->setFrame(false);
90 | edit->setStyleSheet("QLineEdit { background:transparent; }");
91 | edit->setValidator(tabNameValidator_);
92 | tabBar()->setTabButton(index, QTabBar::LeftSide, edit);
93 | QObject::connect(edit, &QLineEdit::editingFinished, this, &EditorTab::tabNameEdited);
94 |
95 | return editor;
96 | }
97 |
98 |
99 | void EditorTab::closeEditorTab(int32_t index)
100 | {
101 | widget(index)->deleteLater(); // Note: removeTab do not destroy the widget.
102 | }
103 |
104 |
105 | void EditorTab::tabNameEdited()
106 | {
107 | if (currentWidget() != nullptr)
108 | {
109 | currentWidget()->setFocus();
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/EditorTab.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_TAB_H
2 | #define PIPER_TAB_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | namespace piper
10 | {
11 | class EditorWidget;
12 |
13 | class TabHeaderEdit : public QLineEdit
14 | {
15 | Q_OBJECT
16 |
17 | public:
18 | TabHeaderEdit(QString const& text);
19 | virtual ~TabHeaderEdit() = default;
20 |
21 | void mousePressEvent(QMouseEvent *event) override;
22 | };
23 |
24 |
25 | class TabNameValidator : public QValidator
26 | {
27 | Q_OBJECT
28 |
29 | public:
30 | TabNameValidator(QObject* parent = nullptr);
31 | QValidator::State validate(QString& input, int& pos) const override;
32 | };
33 |
34 |
35 | class EditorTab : public QTabWidget
36 | {
37 | Q_OBJECT
38 | public:
39 | EditorTab(QWidget* parent = nullptr);
40 | virtual ~EditorTab() = default;
41 |
42 | QString name(int32_t index) const;
43 | void setName(int32_t index, QString const& name);
44 |
45 | public slots:
46 | EditorWidget* createNewEditorTab();
47 | void closeEditorTab(int32_t index);
48 | void tabNameEdited();
49 |
50 | private:
51 | TabNameValidator* tabNameValidator_;
52 | };
53 | }
54 |
55 | #endif
56 |
--------------------------------------------------------------------------------
/src/EditorWidget.cc:
--------------------------------------------------------------------------------
1 | #include "EditorWidget.h"
2 | #include "PropertyDelegate.h"
3 | #include "ui_EditorWidget.h"
4 |
5 | #include "Scene.h"
6 | #include "Node.h"
7 | #include "Link.h"
8 | #include "NodeCreator.h"
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | namespace piper
15 | {
16 | EditorWidget::EditorWidget(QWidget* parent)
17 | : QWidget(parent)
18 | , ui_(new Ui::EditorWidget)
19 | , scene_(new Scene(this))
20 | {
21 | srand(time(0));
22 |
23 | ui_->setupUi(this);
24 | ui_->view->setScene(scene_);
25 |
26 | QObject::connect(ui_->stage_add, &QPushButton::clicked, this, &EditorWidget::onAddStage);
27 | QObject::connect(ui_->stage_rm, &QPushButton::clicked, this, &EditorWidget::onRmStage);
28 | QObject::connect(ui_->stage_color, &QPushButton::clicked, this, &EditorWidget::onColorStage);
29 |
30 | ui_->stages->setModel(scene_->stages());
31 | ui_->stages->setEditTriggers(QAbstractItemView::AnyKeyPressed |
32 | QAbstractItemView::DoubleClicked);
33 | ui_->stages->setDragDropMode(QAbstractItemView::InternalMove);
34 |
35 | ui_->modes->setModel(scene_->modes());
36 | ui_->modes->setEditTriggers(QAbstractItemView::AnyKeyPressed |
37 | QAbstractItemView::DoubleClicked);
38 | ui_->modes->setDragDropMode(QAbstractItemView::InternalMove);
39 | QObject::connect(ui_->mode_add, &QPushButton::clicked, this, &EditorWidget::onAddMode);
40 | QObject::connect(ui_->mode_rm, &QPushButton::clicked, this, &EditorWidget::onRmMode);
41 | QObject::connect(ui_->modes, &QListView::clicked, scene_, &Scene::onModeSelected);
42 | //QObject::connect(ui_->modes, &QListView::customContextMenuRequested, scene_, &Scene::onModeSetDefault);
43 | QObject::connect(ui_->modes, &QListView::doubleClicked, scene_, &Scene::onModeSetDefault);
44 |
45 | QHash allFrom;
46 | for (auto const& item : NodeCreator::instance().availableItems())
47 | {
48 | auto it = allFrom.find(item.from);
49 | if (it == allFrom.end())
50 | {
51 | it = allFrom.insert(item.from, new QTreeWidgetItem({item.from}));
52 | }
53 |
54 | (*it)->addChild(new QTreeWidgetItem({item.type, item.category}));
55 | }
56 |
57 | for (auto const& root : allFrom)
58 | {
59 | ui_->items->addTopLevelItem(root);
60 | }
61 |
62 | QObject::connect(ui_->items, &QTreeWidget::itemDoubleClicked,
63 | [&](QTreeWidgetItem* item)
64 | {
65 | if (item->child(0) != nullptr)
66 | {
67 | // Only leaf are relevants
68 | return;
69 | }
70 |
71 | // Alias for a easier reading
72 | QString const type = item->text(0);
73 |
74 | // Center of the view in the scene coordinates
75 | QPointF const scenePos = ui_->view->mapToScene(ui_->view->viewport()->rect().center());
76 |
77 | QString nextName = type + "_" + QString::number(scene_->nodes().size());
78 | Node* node = NodeCreator::instance().createItem(type, nextName, "", scenePos);
79 | if (node != nullptr)
80 | {
81 | scene_->addNode(node);
82 | }
83 | });
84 |
85 | show(); // required to initialize the view in case of import.
86 | }
87 |
88 |
89 | void EditorWidget::onExport(ExportBackend& backend)
90 | {
91 | scene_->onExport(backend);
92 | }
93 |
94 |
95 | void EditorWidget::onAddStage()
96 | {
97 | QColor nextColor = generateRandomColor();
98 |
99 | QString nextName = "stage" + QString::number(scene_->stages()->rowCount());
100 |
101 | // Add item
102 | QStandardItem* item = new QStandardItem();
103 | item->setData(nextColor, Qt::DecorationRole);
104 | item->setData(nextName, Qt::DisplayRole);
105 | item->setDropEnabled(false);;
106 | scene_->stages()->appendRow(item);
107 |
108 | // Enable item selection and put it edit mode
109 | QModelIndex index = scene_->stages()->indexFromItem(item);
110 | ui_->stages->setCurrentIndex(index);
111 | ui_->stages->edit(index);
112 | }
113 |
114 |
115 | void EditorWidget::onRmStage()
116 | {
117 | int row = ui_->stages->currentIndex().row();
118 | scene_->stages()->removeRows(row, 1);
119 | scene_->onStageUpdated();
120 | }
121 |
122 |
123 | void EditorWidget::onColorStage()
124 | {
125 | QModelIndex index = ui_->stages->currentIndex();
126 | QColor current = scene_->stages()->data(index, Qt::DecorationRole).value();
127 |
128 | QColor newColor = QColorDialog::getColor(current);
129 | scene_->stages()->setData(index, newColor, Qt::DecorationRole);
130 | }
131 |
132 |
133 | void EditorWidget::onAddMode()
134 | {
135 | QString nextName = "mode" + QString::number(scene_->modes()->rowCount());
136 | QModelIndex index = scene_->addMode(nextName);
137 | ui_->modes->setCurrentIndex(index);
138 | ui_->modes->edit(index);
139 | }
140 |
141 |
142 | void EditorWidget::onRmMode()
143 | {
144 | int row = ui_->modes->currentIndex().row();
145 | scene_->modes()->removeRows(row, 1);
146 | }
147 |
148 |
149 | QDataStream& operator<<(QDataStream& out, EditorWidget const& editor)
150 | {
151 | out << *editor.scene_;
152 | return out;
153 | }
154 |
155 |
156 | QDataStream& operator>>(QDataStream& in, EditorWidget& editor)
157 | {
158 | in >> *editor.scene_;
159 | return in;
160 | }
161 |
162 |
163 | void EditorWidget::loadJson(QJsonObject& json)
164 | {
165 | scene_->onImportJson(json);
166 | QModelIndex index = scene_->modes()->index(0, 0);
167 | if (index.isValid())
168 | {
169 | ui_->modes->setCurrentIndex(index);
170 | scene_->onModeSelected(index);
171 | }
172 |
173 | ui_->view->goHome();
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/EditorWidget.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_EDITOR_WIDGET_H
2 | #define PIPER_EDITOR_WIDGET_H
3 |
4 | #include
5 | #include
6 |
7 | namespace Ui
8 | {
9 | class EditorWidget;
10 | }
11 |
12 | namespace piper
13 | {
14 | class Scene;
15 | class ExportBackend;
16 |
17 | class EditorWidget : public QWidget
18 | {
19 | Q_OBJECT
20 |
21 | friend QDataStream& operator<<(QDataStream& out, EditorWidget const& editor);
22 | friend QDataStream& operator>>(QDataStream& in, EditorWidget& editor);
23 |
24 | public:
25 | EditorWidget(QWidget* parent = nullptr);
26 | virtual ~EditorWidget() = default;
27 |
28 | void onExport(ExportBackend& backend);
29 | void loadJson(QJsonObject& json);
30 |
31 | public slots:
32 | void onAddStage();
33 | void onRmStage();
34 | void onColorStage();
35 |
36 | void onAddMode();
37 | void onRmMode();
38 |
39 | private:
40 | Ui::EditorWidget* ui_;
41 | Scene* scene_;
42 | };
43 |
44 | QDataStream& operator<<(QDataStream& out, EditorWidget const& editor);
45 | QDataStream& operator>>(QDataStream& in, EditorWidget& editor);
46 | }
47 |
48 | #endif
49 |
--------------------------------------------------------------------------------
/src/EditorWidget.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | EditorWidget
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1025
10 | 704
11 |
12 |
13 |
14 | Form
15 |
16 |
17 | -
18 |
19 |
20 | Qt::ClickFocus
21 |
22 |
23 | Qt::Horizontal
24 |
25 |
26 |
27 |
28 | 1
29 | 0
30 |
31 |
32 |
33 | Qt::ScrollBarAlwaysOff
34 |
35 |
36 | Qt::ScrollBarAlwaysOff
37 |
38 |
39 | QPainter::Antialiasing|QPainter::HighQualityAntialiasing|QPainter::NonCosmeticDefaultPen|QPainter::SmoothPixmapTransform|QPainter::TextAntialiasing
40 |
41 |
42 | QGraphicsView::FullViewportUpdate
43 |
44 |
45 |
46 |
47 | 2
48 |
49 |
50 |
51 | Stages
52 |
53 |
54 |
-
55 |
56 |
57 | Add
58 |
59 |
60 |
61 | -
62 |
63 |
64 | Rm
65 |
66 |
67 |
68 | -
69 |
70 |
71 | Color
72 |
73 |
74 |
75 | -
76 |
77 |
78 | QAbstractItemView::NoEditTriggers
79 |
80 |
81 | true
82 |
83 |
84 | true
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | Modes
93 |
94 |
95 | -
96 |
97 |
98 | Rm
99 |
100 |
101 |
102 | -
103 |
104 |
105 | Add
106 |
107 |
108 |
109 | -
110 |
111 |
112 |
113 |
114 |
115 |
116 | Page
117 |
118 |
119 | -
120 |
121 |
122 | true
123 |
124 |
125 | 2
126 |
127 |
128 | true
129 |
130 |
131 | 200
132 |
133 |
134 | true
135 |
136 |
137 | true
138 |
139 |
140 |
141 | Item
142 |
143 |
144 |
145 |
146 | Category
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | piper::View
161 | QGraphicsView
162 |
163 |
164 |
165 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/src/ExportBackend.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_EXPORT_BACKEND_H
2 | #define PIPER_EXPORT_BACKEND_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "Types.h"
9 |
10 | namespace piper
11 | {
12 | class ExportBackend
13 | {
14 | public:
15 | // init() is called befre anything else.
16 | virtual void init(QString const& filename) = 0;
17 |
18 | // finalize() is called when the export is finished.
19 | virtual void finalize(QString const& filename) = 0;
20 |
21 | // Start a new pipeline
22 | virtual void startPipeline(QString const& pipelineName) = 0;
23 |
24 | // Called when the pipeline was fully exported.
25 | virtual void endPipeline(QString const& pipelineName) = 0;
26 |
27 | // Stages are written from first to last.
28 | virtual void writeStages(QVector const& stages) = 0;
29 |
30 | // Each node is composed of its metadata and a map attributes/value
31 | virtual void writeNode(QString const& type, QString const& name, QString const& stage, QHash const& attributes) = 0;
32 |
33 | // one call per link
34 | virtual void writeLink(QString const& from, QString const& output, QString const& to, QString const& input, QString const& type) = 0;
35 |
36 | // one call per mode
37 | virtual void writeMode(QString const& name, QHash const& config) = 0;
38 |
39 | // one call per pipeline.
40 | virtual void writeDefaultMode(QString const& name) = 0;
41 | };
42 | }
43 |
44 | #endif
45 |
--------------------------------------------------------------------------------
/src/JsonExport.cc:
--------------------------------------------------------------------------------
1 | #include "JsonExport.h"
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | namespace piper
8 | {
9 | void JsonExport::init(QString const&)
10 | {
11 |
12 | }
13 |
14 | void JsonExport::finalize(QString const& filename)
15 | {
16 | QJsonDocument document(root_);
17 |
18 | QFile io(filename);
19 | if (not io.open(QIODevice::WriteOnly))
20 | {
21 | qDebug() << "Error while opening" << io.fileName();
22 | return;
23 | }
24 |
25 | io.write(document.toJson());
26 | }
27 |
28 | void JsonExport::startPipeline(QString const&)
29 | {
30 | pipeline_ = QJsonObject(); // cleanup
31 | nodes_ = QJsonObject();
32 | links_ = QJsonArray();
33 | modes_ = QJsonObject();
34 | }
35 |
36 | void JsonExport::endPipeline(QString const& pipelineName)
37 | {
38 | pipeline_["Nodes"] = nodes_;
39 | pipeline_["Links"] = links_;
40 | pipeline_["Modes"] = modes_;
41 | root_[pipelineName] = pipeline_;
42 | }
43 |
44 |
45 | void JsonExport::writeStages(QVector const& stages)
46 | {
47 | QJsonArray stagesArray;
48 | for (auto const& stage : stages)
49 | {
50 | stagesArray.append(stage);
51 | }
52 | pipeline_["Stages"] = stagesArray;
53 | }
54 |
55 |
56 | void JsonExport::writeNode(QString const& type, QString const& name, QString const& stage, QHash const& attributes)
57 | {
58 | QJsonObject node;
59 | for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it)
60 | {
61 | if (it.key() == "type") { qWarning() << "type is a reserved attribute. Skipping."; continue; }
62 | if (it.key() == "stage") { qWarning() << "stage is a reserved attribute. Skipping."; continue; }
63 | node[it.key()] = QJsonValue::fromVariant(it.value());
64 | }
65 |
66 | node["type"] = type;
67 | node["stage"] = stage;
68 | nodes_[name] = node;
69 | }
70 |
71 |
72 | void JsonExport::writeLink(QString const& from, QString const& output, QString const& to, QString const& input, QString const& type)
73 | {
74 | QJsonObject link;
75 | link["from"] = from;
76 | link["out"] = output;
77 | link["to"] = to;
78 | link["in"] = input;
79 | link["type"] = type;
80 | links_.append(link);
81 | }
82 |
83 |
84 | void JsonExport::writeMode(QString const& name, QHash const& config)
85 | {
86 | QJsonObject mode;
87 | QJsonObject configuration;
88 | mode["default"] = "Enable";
89 | for (auto it = config.constBegin(); it != config.constEnd(); ++it)
90 | {
91 | QString modeString;
92 | switch (it.value())
93 | {
94 | case Mode::enable: { continue; }
95 | case Mode::disable: { modeString = "Disable"; break; }
96 | case Mode::neutral: { modeString = "Neutral"; break; }
97 | default: { continue; }
98 | }
99 | configuration[it.key()] = modeString;
100 | }
101 | mode["configuration"] = configuration;
102 | modes_[name] = mode;
103 | }
104 |
105 | void JsonExport::writeDefaultMode(QString const& name)
106 | {
107 | QJsonValue defaultMode = name;
108 | modes_["default"] = defaultMode;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/JsonExport.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_JSON_EXPORT_H
2 | #define PIPER_JSON_EXPORT_H
3 |
4 | #include "ExportBackend.h"
5 |
6 | #include
7 | #include
8 |
9 | namespace piper
10 | {
11 | class JsonExport : public ExportBackend
12 | {
13 | public:
14 | JsonExport() = default;
15 | virtual ~JsonExport() = default;
16 |
17 | // init() is called befre anything else.
18 | void init(QString const& filename) override;
19 |
20 | // finalize() is called when the export is finished.
21 | void finalize(QString const& filename) override;
22 |
23 | // Start a new pipeline
24 | void startPipeline(QString const&) override;
25 |
26 | // Called when the pipeline was fully exported.
27 | void endPipeline(QString const& pipelineName) override;
28 |
29 | // Stages
30 | void writeStages(QVector const& stages) override;
31 |
32 | // Each node is composed of its metadata and a map attributes/value
33 | void writeNode(QString const& type, QString const& name, QString const& stage, QHash const& attributes) override;
34 |
35 | // one call per link
36 | void writeLink(QString const& from, QString const& output, QString const& to, QString const& input, QString const& type) override;
37 |
38 | // Mode
39 | void writeMode(QString const& name, QHash const& config) override;
40 |
41 | // Default mode.
42 | void writeDefaultMode(QString const& name) override;
43 |
44 | private:
45 | QJsonObject root_;
46 | QJsonObject pipeline_;
47 | QJsonObject nodes_;
48 | QJsonArray links_;
49 | QJsonObject modes_;
50 | };
51 | }
52 |
53 | #endif
54 |
--------------------------------------------------------------------------------
/src/Link.cc:
--------------------------------------------------------------------------------
1 | #include "Link.h"
2 | #include "Node.h"
3 | #include "Scene.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include
12 |
13 | namespace piper
14 | {
15 | Link::Link()
16 | {
17 | setFlag(QGraphicsItem::ItemIsSelectable);
18 | setFlag(QGraphicsItem::ItemIsFocusable);
19 |
20 | pen_.setStyle(Qt::SolidLine);
21 | pen_.setWidth(2);
22 |
23 | selected_.setStyle(Qt::SolidLine);
24 | selected_.setColor({255, 180, 180, 255}); //TODO theme manager
25 | selected_.setWidth(3);
26 | }
27 |
28 |
29 | Link::~Link()
30 | {
31 | disconnect();
32 | Scene* pScene = static_cast(scene());
33 | pScene->removeLink(this);
34 | }
35 |
36 |
37 | void Link::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
38 | {
39 | if (isSelected())
40 | {
41 | setPen(selected_);
42 | }
43 | else
44 | {
45 | setPen(pen_);
46 | }
47 |
48 | if (to_ != nullptr)
49 | {
50 | updatePath();
51 | }
52 | QGraphicsPathItem::paint(painter, option, widget);
53 | }
54 |
55 |
56 | void Link::connectFrom(Attribute* from)
57 | {
58 | from_ = from;
59 | from_->connect(this);
60 | }
61 |
62 |
63 | void Link::connectTo(Attribute* to)
64 | {
65 | to_ = to;
66 | to_->connect(this);
67 | updatePath();
68 | }
69 |
70 |
71 | void Link::disconnect()
72 | {
73 | if (from_ != nullptr)
74 | {
75 | from_->disconnect(this);
76 | from_ = nullptr;
77 | }
78 |
79 | if (to_ != nullptr)
80 | {
81 | to_->disconnect(this);
82 | to_ = nullptr;
83 | }
84 | }
85 |
86 |
87 | bool Link::isConnected()
88 | {
89 | if ((from_ == nullptr) or (to_ == nullptr))
90 | {
91 | return false;
92 | }
93 | return true;
94 | }
95 |
96 |
97 | void Link::updatePath()
98 | {
99 | updatePath(to_->connectorPos());
100 | }
101 |
102 |
103 | void Link::updatePath(QPointF const& end)
104 | {
105 | updatePath(from_->connectorPos(), end);
106 | setZValue(-1); // force path to be under nodes
107 | }
108 |
109 |
110 | void Link::setColor(QColor const& color)
111 | {
112 | pen_.setColor(color);
113 | }
114 |
115 |
116 | void Link::mousePressEvent(QGraphicsSceneMouseEvent* event)
117 | {
118 | Scene* pScene = static_cast(scene());
119 |
120 | setSelected(true);
121 |
122 | // disconnect from end.
123 | to_->disconnect(this);
124 |
125 | // snap the path end to this point.
126 | updatePath(event->scenePos());
127 |
128 | // highlight available connections
129 | for (auto& node : pScene->nodes())
130 | {
131 | node->highlight(from_);
132 | }
133 | }
134 |
135 |
136 | void Link::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
137 | {
138 | // snap the path end to this point.
139 | updatePath(event->scenePos());
140 | }
141 |
142 |
143 | void Link::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
144 | {
145 | Scene* pScene = static_cast(scene());
146 |
147 | // Disable highlight
148 | for (auto& node : pScene->nodes())
149 | {
150 | node->unhighlight();
151 | }
152 |
153 | // try to connect to the destinaton.
154 | AttributeInput* input = qgraphicsitem_cast(scene()->itemAt(event->scenePos(), QTransform()));
155 | if (input != nullptr)
156 | {
157 | if (input->accept(from_))
158 | {
159 | connectTo(input);
160 | }
161 | }
162 | else
163 | {
164 | connectTo(to_); // reset connection
165 | }
166 | }
167 |
168 |
169 | void Link::updatePath(QPointF const& start, QPointF const& end)
170 | {
171 | qreal dx = (end.x() - start.x()) * 0.5;
172 | qreal dy = (end.y() - start.y());
173 | QPointF c1{start.x() + dx, start.y() + dy * 0};
174 | QPointF c2{start.x() + dx, start.y() + dy * 1};
175 |
176 | QPainterPath path;
177 | path.moveTo(start);
178 | path.cubicTo(c1, c2, end);
179 |
180 | setPath(path);
181 | }
182 |
183 |
184 | void Link::computeControlPoint(QPointF const& p0, QPointF const& p1, QPointF const& p2, double t,
185 | QPointF& ctrl1, QPointF& ctrl2)
186 | {
187 | using namespace std;
188 |
189 | double d01 = sqrt(pow(p1.x()-p0.x(), 2) + pow(p1.y() - p0.y(), 2));
190 | double d12 = sqrt(pow(p2.x()-p1.x(), 2) + pow(p2.y() - p1.y(), 2));
191 |
192 | double fa = t * d01 / (d01 + d12); // scaling factor for triangle Ta
193 | double fb = t * d12 / (d01 + d12); // ditto for Tb, simplifies to fb=t-fa
194 |
195 | double p1x = p1.x() - fa * (p2.x() - p0.x()); // x2-x0 is the width of triangle T
196 | double p1y = p1.y() - fa * (p2.y() - p0.y()); // y2-y0 is the height of T
197 | ctrl1.setX(p1x);
198 | ctrl1.setY(p1y);
199 |
200 | double p2x = p1.x() + fb * (p2.x() - p0.x());
201 | double p2y = p1.y() + fb * (p2.y() - p0.y());
202 | ctrl2.setX(p2x);
203 | ctrl2.setY(p2y);
204 | }
205 |
206 |
207 | void Link::drawSplines(QVector const& waypoints, double t)
208 | {
209 | // Compute control points
210 | QVector controlPoints;
211 | for (int i = 0; i < waypoints.size() - 2; i += 1)
212 | {
213 | QPointF c1, c2;
214 | computeControlPoint(waypoints.at(i), waypoints.at(i + 1), waypoints.at(i + 2), t,
215 | c1, c2);
216 | controlPoints << c1 << c2;
217 | }
218 | auto nextWaypoint = waypoints.cbegin();
219 | auto ctrl = controlPoints.cbegin();
220 |
221 | // Prepare path -> first spline is a quadratic bezier curve
222 | QPainterPath path;
223 | path.moveTo(*(nextWaypoint++));
224 | path.quadTo(*(ctrl++), *(nextWaypoint++));
225 |
226 | // draw others
227 | for (int i = 2; i < waypoints.size() - 1; i += 1)
228 | {
229 | path.cubicTo(*ctrl, *(ctrl+1), *(nextWaypoint++));
230 | ctrl += 2;
231 | }
232 |
233 | // finalize: last one is a quadratic bezier (like the first one)
234 | path.quadTo(*ctrl, *nextWaypoint);
235 | setPath(path);
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/src/Link.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_LINK_H
2 | #define PIPER_LINK_H
3 |
4 | #include "Attribute.h"
5 |
6 | #include
7 |
8 | namespace piper
9 | {
10 | class Link : public QGraphicsPathItem
11 | {
12 | public:
13 | Link();
14 | virtual ~Link();
15 |
16 | void connectFrom(Attribute* from);
17 | void connectTo(Attribute* to);
18 | void disconnect();
19 | bool isConnected();
20 |
21 | void updatePath();
22 | void updatePath(QPointF const& end);
23 |
24 | void setColor(QColor const& color);
25 |
26 | Attribute const* from() const { return from_; }
27 | Attribute const* to() const { return to_; }
28 |
29 | private:
30 |
31 | void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
32 |
33 | void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
34 | void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
35 | void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
36 |
37 | void updatePath(QPointF const& start, QPointF const& end);
38 |
39 | // Compute bezier control point to 'glue' properly two bezier curves
40 | void computeControlPoint(QPointF const& p0, QPointF const& p1, QPointF const& p2, double t,
41 | QPointF& ctrl1, QPointF& ctrl2);
42 | void drawSplines(QVector const& waypoints, double t);
43 |
44 | QPen pen_;
45 | QPen selected_;
46 |
47 | Attribute* from_{nullptr};
48 | Attribute* to_{nullptr};
49 | };
50 | }
51 |
52 | #endif
53 |
--------------------------------------------------------------------------------
/src/MainEditor.cc:
--------------------------------------------------------------------------------
1 | #include "MainEditor.h"
2 | #include "ui_MainEditor.h"
3 | #include "EditorWidget.h"
4 | #include "JsonExport.h"
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | namespace piper
11 | {
12 | MainEditor::MainEditor(QWidget* parent)
13 | : QMainWindow(parent)
14 | , ui_(new Ui::MainEditor)
15 | {
16 | ui_->setupUi(this);
17 | ui_->editor_tab->createNewEditorTab();
18 |
19 | QObject::connect(ui_->actionsave, &QAction::triggered, this, &MainEditor::onSave);
20 | QObject::connect(ui_->actionsave_on, &QAction::triggered, this, &MainEditor::onSaveOn);
21 | QObject::connect(ui_->actionload, &QAction::triggered, this, &MainEditor::onLoad);
22 | QObject::connect(ui_->actionshowhelp, &QAction::triggered, this, &MainEditor::onShowHelp);
23 | QObject::connect(ui_->actionexport_json, &QAction::triggered, this, &MainEditor::onExportJson);
24 | QObject::connect(ui_->actionimport_json, &QAction::triggered, this, &MainEditor::onImportJson);
25 | }
26 |
27 |
28 | void MainEditor::onSaveOn()
29 | {
30 | QString filename = QFileDialog::getSaveFileName(this,tr("Save"), "", tr("Piper project (*.piper);;All Files (*)"));
31 | if (filename.isEmpty())
32 | {
33 | return; // nothing to do: user abort.
34 | }
35 |
36 | writeProjectFile(filename);
37 | }
38 |
39 |
40 | void MainEditor::onSave()
41 | {
42 | if (project_filename_.isEmpty())
43 | {
44 | project_filename_ = QFileDialog::getSaveFileName(this,tr("Save"), "", tr("Piper project (*.piper);;All Files (*)"));
45 | }
46 |
47 | if (project_filename_.isEmpty())
48 | {
49 | return; // nothing to do: user abort.
50 | }
51 |
52 | writeProjectFile(project_filename_);
53 | }
54 |
55 |
56 | void MainEditor::onLoad()
57 | {
58 | project_filename_ = QFileDialog::getOpenFileName(this,tr("Load"), "", tr("Piper project (*.piper);;All Files (*)"));
59 | if (project_filename_.isEmpty())
60 | {
61 | return; // nothing to do: user abort.
62 | }
63 |
64 | loadProjectFile(project_filename_);
65 | }
66 |
67 |
68 | void MainEditor::onExportJson()
69 | {
70 | QString filename = QFileDialog::getSaveFileName(this,tr("Export"), "", tr("JSON (*.json);;All Files (*)"));
71 | if (filename.isEmpty())
72 | {
73 | return; // nothing to do: user abort.
74 | }
75 |
76 | JsonExport backend;
77 | backend.init(filename);
78 |
79 | for (int i = 0; i < ui_->editor_tab->count(); ++i)
80 | {
81 | QString pipeline = ui_->editor_tab->name(i);
82 | backend.startPipeline(pipeline);
83 |
84 | // Export tab content.
85 | EditorWidget* editor = static_cast(ui_->editor_tab->widget(i));
86 | editor->onExport(backend);
87 |
88 | backend.endPipeline(pipeline);
89 | }
90 |
91 | backend.finalize(filename);
92 | }
93 |
94 |
95 | void MainEditor::onImportJson()
96 | {
97 | QString jsonFile = QFileDialog::getOpenFileName(this,tr("Load"), "", tr("Piper json (*.json);;All Files (*)"));
98 | if (jsonFile.isEmpty())
99 | {
100 | return; // nothing to do: user abort.
101 | }
102 |
103 | loadJson(jsonFile);
104 | }
105 |
106 |
107 | void MainEditor::onShowHelp()
108 | {
109 | QString help;
110 | help += "Press \"=\" to add a new node.\n";
111 | help += "Right click on a node to set its stage and current mode configuration\n";
112 | help += "Press mouse middle click on an input or output slot to reverse it\n";
113 | help += "Double click on a mode to set it as the default one\n";
114 |
115 | QMessageBox msgBox;
116 | msgBox.setText(help);
117 | msgBox.exec();
118 | }
119 |
120 |
121 | void MainEditor::loadProjectFile(const QString& filename)
122 | {
123 | QFile file(filename);
124 | file.open(QIODevice::ReadOnly);
125 |
126 | QDataStream in(&file);
127 |
128 | // reset editor - clear tabs
129 | while (ui_->editor_tab->count())
130 | {
131 | ui_->editor_tab->removeTab(ui_->editor_tab->currentIndex());
132 | }
133 |
134 |
135 | // load tab
136 | int tab_count;
137 | in >> tab_count;
138 | for (int i = 0; i < tab_count; ++i)
139 | {
140 | EditorWidget* editor = ui_->editor_tab->createNewEditorTab();
141 | QString tab_name;
142 | in >> tab_name >> *editor;
143 | ui_->editor_tab->setName(i, tab_name);
144 | }
145 | }
146 |
147 |
148 | void MainEditor::writeProjectFile(const QString& filename)
149 | {
150 | QFile file(filename);
151 | file.open(QIODevice::WriteOnly | QIODevice::Truncate);
152 |
153 | QDataStream out(&file);
154 |
155 | // Save tab number
156 | out << ui_->editor_tab->count();
157 | for (int i = 0; i < ui_->editor_tab->count(); ++i)
158 | {
159 | // Save tab name.
160 | out << ui_->editor_tab->name(i);
161 |
162 | // Save tab content.
163 | EditorWidget* editor = static_cast(ui_->editor_tab->widget(i));
164 | out << *editor;
165 | }
166 | }
167 |
168 |
169 | void MainEditor::loadJson(const QString& filename)
170 | {
171 | QFile file(filename);
172 | file.open(QIODevice::ReadOnly | QIODevice::Text);
173 |
174 | QByteArray dataJson = file.readAll();
175 | file.close();
176 |
177 | QJsonParseError errorPtr;
178 | QJsonDocument doc = QJsonDocument::fromJson(dataJson, &errorPtr);
179 | if (doc.isNull()) {
180 | qDebug() << "Parse failed of " << filename;
181 | }
182 |
183 | QJsonObject rootObj = doc.object();
184 |
185 | qDebug() << "size " << rootObj.size();
186 | qDebug() << rootObj.keys();
187 |
188 | // reset editor - clear tabs
189 | while (ui_->editor_tab->count())
190 | {
191 | ui_->editor_tab->removeTab(ui_->editor_tab->currentIndex());
192 | }
193 |
194 |
195 | int pipelineNumber = rootObj.size();
196 | for (int i = 0; i < pipelineNumber; ++i)
197 | {
198 | EditorWidget* editor = ui_->editor_tab->createNewEditorTab();
199 | ui_->editor_tab->setName(i, rootObj.keys()[i]);
200 |
201 | QJsonObject pipelineJson = rootObj[rootObj.keys()[i]].toObject();
202 | editor->loadJson(pipelineJson);
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/src/MainEditor.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_MAIN_EDITOR_H
2 | #define PIPER_MAIN_EDITOR_H
3 |
4 | #include
5 |
6 | namespace Ui
7 | {
8 | class MainEditor;
9 | }
10 |
11 | namespace piper
12 | {
13 | class MainEditor : public QMainWindow
14 | {
15 | Q_OBJECT
16 |
17 | public:
18 | explicit MainEditor(QWidget* parent = nullptr);
19 | virtual ~MainEditor() = default;
20 |
21 | public slots:
22 | void onSave();
23 | void onSaveOn();
24 | void onLoad();
25 | void onShowHelp();
26 | void onImportJson();
27 | void onExportJson();
28 |
29 | private:
30 | void writeProjectFile(QString const& filename);
31 | void loadProjectFile(QString const& filename);
32 | void loadJson(QString const& filename);
33 |
34 | Ui::MainEditor* ui_;
35 | QString project_filename_;
36 | };
37 | }
38 |
39 | #endif
40 |
--------------------------------------------------------------------------------
/src/MainEditor.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainEditor
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1147
10 | 821
11 |
12 |
13 |
14 | Piper editor
15 |
16 |
17 |
18 | -
19 |
20 |
21 | -1
22 |
23 |
24 | true
25 |
26 |
27 | true
28 |
29 |
30 |
31 |
32 |
33 |
61 |
62 |
63 |
64 | save
65 |
66 |
67 | Ctrl+S
68 |
69 |
70 |
71 |
72 | open
73 |
74 |
75 | Ctrl+O
76 |
77 |
78 |
79 |
80 | export to JSON
81 |
82 |
83 |
84 |
85 | save on
86 |
87 |
88 | Ctrl+Shift+S
89 |
90 |
91 |
92 |
93 | import from JSON
94 |
95 |
96 |
97 |
98 | show
99 |
100 |
101 |
102 |
103 |
104 | piper::EditorTab
105 | QTabWidget
106 |
107 | 1
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/src/Node.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #include "Node.h"
8 | #include "Link.h"
9 | #include "AttributeMember.h"
10 | #include "ThemeManager.h"
11 |
12 | namespace piper
13 | {
14 | constexpr int attributeHeight = 30;
15 | constexpr int baseHeight = 35;
16 | constexpr int baseWidth = 250;
17 |
18 |
19 | NodeName::NodeName(QGraphicsItem* parent) : QGraphicsTextItem(parent)
20 | {
21 | QTextOption options;
22 | options.setWrapMode(QTextOption::NoWrap);
23 | document()->setDefaultTextOption(options);
24 | }
25 |
26 |
27 | void NodeName::adjustPosition()
28 | {
29 | setPos(-(boundingRect().width() - parentItem()->boundingRect().width()) * 0.5, -boundingRect().height());
30 | }
31 |
32 | void NodeName::keyPressEvent(QKeyEvent* e)
33 | {
34 | if (e->key() == Qt::Key_Return)
35 | {
36 | clearFocus();
37 | return;
38 | }
39 |
40 | // Handle event (text change) and recompute position
41 | QGraphicsTextItem::keyPressEvent(e);
42 | adjustPosition();
43 | }
44 |
45 |
46 | Node::Node(QString const& type, QString const& name, QString const& stage)
47 | : QGraphicsItem(nullptr)
48 | , bounding_rect_{0, 0, baseWidth, baseHeight}
49 | , name_{new NodeName(this)}
50 | , type_{type}
51 | , stage_{stage}
52 | , mode_{Mode::enable}
53 | , width_{baseWidth}
54 | , height_{baseHeight}
55 | , attributes_{}
56 | {
57 | // Configure item behavior.
58 | setFlag(QGraphicsItem::ItemIsMovable);
59 | setFlag(QGraphicsItem::ItemIsSelectable);
60 | setFlag(QGraphicsItem::ItemIsFocusable);
61 |
62 | // Configure node name
63 | name_->setTextInteractionFlags(Qt::TextEditorInteraction);
64 | setName(name);
65 |
66 | createStyle();
67 |
68 | type_rect_ = QRectF{1, 17, width_ - 2.0, attributeHeight};
69 | height_ += attributeHeight; // Give some space for type section
70 | }
71 |
72 |
73 | Node::~Node()
74 | {
75 | Scene* pScene = static_cast(scene());
76 | pScene->removeNode(this);
77 | }
78 |
79 |
80 | void Node::highlight(Attribute* emitter)
81 | {
82 | for (auto& attr : attributes_)
83 | {
84 | if (attr == emitter)
85 | {
86 | // special case: do not change emitter mode.
87 | continue;
88 | }
89 |
90 | if (attr->accept(emitter))
91 | {
92 | attr->setMode(DisplayMode::highlight);
93 | }
94 | else
95 | {
96 | attr->setMode(DisplayMode::minimize);
97 | }
98 | attr->update();
99 | }
100 | }
101 |
102 |
103 | void Node::unhighlight()
104 | {
105 | for (auto& attr : attributes_)
106 | {
107 | attr->setMode(DisplayMode::normal);
108 | attr->update();
109 | }
110 | }
111 |
112 |
113 | void Node::createAttributes(QVector const& attributesInfo)
114 | {
115 | if (not attributes_.empty())
116 | {
117 | qWarning() << "Creating attributes in multiples call is not supported.";
118 | return;
119 | }
120 |
121 | // Compute width.
122 | QFont attributeFont = ThemeManager::instance().getAttributeTheme().normal.font;
123 | QFontMetrics metrics(attributeFont);
124 | QRect boundingRect{0, 0, width_ - 32, attributeHeight}; // -30 to keep space for attribute custom display / -2 for node border
125 | for (auto const& info : attributesInfo)
126 | {
127 | boundingRect = boundingRect.united(metrics.boundingRect(info.name));
128 | }
129 | // Adjust bounding rect position / width / height.
130 | boundingRect.setTopLeft({0, 0});
131 | boundingRect.setWidth(boundingRect.width() + 30); // add space again
132 | boundingRect.setHeight(attributeHeight);
133 |
134 | // Adjust node width
135 | width_ = boundingRect.width() + 2;
136 | type_rect_.setWidth(boundingRect.width());
137 |
138 | // Create attributes
139 | for (auto const& info : attributesInfo)
140 | {
141 | Attribute* attr{nullptr};
142 | if (info.name == "stage")
143 | {
144 | qWarning() << "Stage is a reserved attribute: skipping";
145 | continue;
146 | }
147 | switch (info.type)
148 | {
149 | case AttributeInfo::Type::input:
150 | {
151 | attr = new AttributeInput(this, info, boundingRect);
152 | break;
153 | }
154 | case AttributeInfo::Type::output:
155 | {
156 | attr = new AttributeOutput(this, info, boundingRect);
157 | break;
158 | }
159 | case AttributeInfo::Type::member:
160 | {
161 | attr = new AttributeMember(this, info, boundingRect);
162 | break;
163 | }
164 | }
165 | attr->setPos(1, 17 + attributeHeight * (attributes_.size() + 1));
166 | if (attributes_.size() % 2)
167 | {
168 | attr->setBackgroundBrush(attribute_brush_);
169 | }
170 | else
171 | {
172 | attr->setBackgroundBrush(attribute_alt_brush_);
173 | }
174 | height_ += attributeHeight;
175 | bounding_rect_ = QRectF(0, 0, width_, height_);
176 | bounding_rect_ += QMargins(1, 1, 1, 1);
177 | attributes_.append(attr);
178 | }
179 |
180 | prepareGeometryChange();
181 | // readjust name position.
182 | name_->adjustPosition();
183 |
184 | }
185 |
186 |
187 | void Node::createStyle()
188 | {
189 | NodeTheme node_theme = ThemeManager::instance().getNodeTheme();
190 | AttributeTheme attribute_theme = ThemeManager::instance().getAttributeTheme();
191 |
192 | qint32 border = 2;
193 |
194 | background_brush_.setStyle(Qt::SolidPattern);
195 | background_brush_.setColor(node_theme.background);
196 |
197 | pen_.setStyle(Qt::SolidLine);
198 | pen_.setWidth(border);
199 | pen_.setColor(node_theme.border);
200 |
201 | pen_selected_.setStyle(Qt::SolidLine);
202 | pen_selected_.setWidth(border);
203 | pen_selected_.setColor(node_theme.border_selected);
204 |
205 | name_->setFont(node_theme.name_font);
206 | name_->setDefaultTextColor(node_theme.name_color);
207 | name_->adjustPosition();
208 |
209 | attribute_brush_.setStyle(Qt::SolidPattern);
210 | attribute_brush_.setColor(attribute_theme.background);
211 | attribute_alt_brush_.setStyle(Qt::SolidPattern);
212 | attribute_alt_brush_.setColor(attribute_theme.background_alt);
213 |
214 | type_brush_.setStyle(Qt::SolidPattern);
215 | type_brush_.setColor(node_theme.type_background);
216 | type_pen_.setStyle(Qt::SolidLine);
217 | type_pen_.setColor(node_theme.type_color);
218 | type_font_ = node_theme.type_font;
219 | }
220 |
221 |
222 | QRectF Node::boundingRect() const
223 | {
224 | return bounding_rect_;
225 | }
226 |
227 |
228 | void Node::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
229 | {
230 | // Base shape.
231 | painter->setBrush(background_brush_);
232 |
233 | if (isSelected())
234 | {
235 | painter->setPen(pen_selected_);
236 | }
237 | else
238 | {
239 | painter->setPen(pen_);
240 | }
241 |
242 | qint32 radius = 10;
243 | type_rect_.setWidth(width_ - 2.0);
244 | painter->drawRoundedRect(0, 0, width_, height_, radius, radius);
245 |
246 | // type background.
247 | painter->setBrush(type_brush_);
248 | painter->setPen(Qt::NoPen);
249 | painter->drawRect(type_rect_);
250 |
251 | // type label.
252 | painter->setFont(type_font_);
253 | painter->setPen(type_pen_);
254 | painter->drawText(type_rect_, Qt::AlignCenter, type_);
255 |
256 | updateWidth();
257 | }
258 |
259 |
260 | void Node::mousePressEvent(QGraphicsSceneMouseEvent* event)
261 | {
262 | // Force selected node on top layer
263 | for (auto& item : scene()->items())
264 | {
265 | if (item->zValue() > 1)
266 | {
267 | item->setZValue(1);
268 | }
269 | }
270 | setZValue(2);
271 |
272 | QGraphicsItem::mousePressEvent(event);
273 | }
274 |
275 | void Node::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
276 | {
277 | for (auto& attr : attributes_)
278 | {
279 | attr->refresh(); // let the attribute refresh their data if required.
280 | }
281 |
282 | QGraphicsItem::mouseMoveEvent(event);
283 | }
284 |
285 |
286 | QString Node::name() const
287 | {
288 | return name_->toPlainText();
289 | }
290 |
291 |
292 | void Node::setName(QString const& name)
293 | {
294 | name_->setPlainText(name);
295 |
296 | // Compute position
297 | name_->adjustPosition();
298 | }
299 |
300 |
301 | void Node::setMode(Mode mode)
302 | {
303 | mode_ = mode;
304 |
305 | for (auto& attribute : attributes_)
306 | {
307 | DataTypeTheme theme = ThemeManager::instance().getDataTypeTheme(attribute->dataType());
308 |
309 | if (attribute->isOutput())
310 | {
311 | switch (mode)
312 | {
313 | case Mode::enable: { attribute->setColor(theme.enable); break; }
314 | case Mode::disable: { attribute->setColor(theme.disable); break; }
315 | case Mode::neutral: { attribute->setColor(theme.neutral); break; }
316 | }
317 | }
318 | }
319 | }
320 |
321 | void Node::updateWidth()
322 | {
323 | QList widths{name_->sceneBoundingRect().width(), baseWidth};
324 | for (auto& attribute : attributes_)
325 | {
326 | if (attribute->isMember())
327 | {
328 | widths.append(attribute->getFormBaseWidth() + attribute->labelRect().right() + 20);
329 | }
330 | }
331 |
332 | std::sort(widths.begin(), widths.end(), std::greater());
333 | width_ = static_cast(widths[0]);
334 | for (auto& attribute : attributes_)
335 | {
336 | QRectF rectangle = attribute->boundingRect();
337 | QPointF pose = QPointF{1, rectangle.top()};
338 | rectangle.setTopLeft(pose);
339 | rectangle.setWidth(width_ - 3.0);
340 | attribute->updateRectSize(rectangle);
341 | attribute->updateConnectorPosition();
342 | }
343 | bounding_rect_ = QRectF(0, 0, width_, height_);
344 | name_->adjustPosition();
345 | }
346 |
347 | void Node::keyPressEvent(QKeyEvent* event)
348 | {
349 | if (isSelected())
350 | {
351 | constexpr qreal moveFactor = 5;
352 | if ((event->key() == Qt::Key::Key_Up) and (event->modifiers() == Qt::NoModifier))
353 | {
354 | moveBy(0, -moveFactor);
355 | }
356 | if ((event->key() == Qt::Key::Key_Down) and (event->modifiers() == Qt::NoModifier))
357 | {
358 | moveBy(0, moveFactor);
359 | }
360 | if ((event->key() == Qt::Key::Key_Left) and (event->modifiers() == Qt::NoModifier))
361 | {
362 | moveBy(-moveFactor, 0);
363 | }
364 | if ((event->key() == Qt::Key::Key_Right) and (event->modifiers() == Qt::NoModifier))
365 | {
366 | moveBy(moveFactor, 0);
367 | }
368 |
369 | return;
370 | }
371 |
372 | QGraphicsItem::keyPressEvent(event);
373 | }
374 |
375 |
376 | void Node::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
377 | {
378 | Scene* pScene = static_cast(scene());
379 | QMenu menu;
380 |
381 | menu.setStyleSheet(""
382 | "QMenu::separator"
383 | "{"
384 | "height: 1px;"
385 | "background-color: #505F69;"
386 | "color: #F0F0F0;"
387 | "}"
388 | );
389 |
390 |
391 | // Create stage menu entries.
392 | menu.addSection("Stage");
393 |
394 | for (int i = 0; i < pScene->stages()->rowCount(); ++i)
395 | {
396 | QString stage = pScene->stages()->item(i, 0)->data(Qt::DisplayRole).toString();
397 | QAction* stageAction = menu.addAction(stage);
398 | stageAction->setCheckable(true);
399 | stageAction->setData(stage);
400 | if (stage == stage_)
401 | {
402 | stageAction->setChecked(true);
403 | }
404 |
405 | QObject::connect(stageAction, &QAction::triggered,
406 | [this, pScene, stage](bool isChecked)
407 | {
408 | if (isChecked)
409 | {
410 | stage_ = stage;
411 | }
412 | else
413 | {
414 | stage_ = "";
415 | }
416 |
417 | pScene->onStageUpdated();
418 | });
419 | }
420 |
421 |
422 | QStandardItem* currentMode = nullptr;
423 | for (int i = 0; i < pScene->modes()->rowCount(); ++i)
424 | {
425 | QStandardItem* mode = pScene->modes()->item(i, 0);
426 |
427 | // Search the current mode.
428 | if (mode->data(Qt::UserRole + 1).toBool() == false)
429 | {
430 | continue;
431 | }
432 |
433 | currentMode = mode;
434 | }
435 |
436 | if (currentMode != nullptr)
437 | {
438 | menu.addSection(currentMode->data(Qt::DisplayRole).toString());
439 |
440 | auto updateMode = [this, currentMode](enum Mode mode)
441 | {
442 | // Apply mode on display
443 | this->setMode(mode);
444 |
445 | // Save mode
446 | QHash nodeMode = currentMode->data(Qt::UserRole + 2).toHash();
447 | nodeMode[this->name()] = mode;
448 | currentMode->setData(nodeMode, Qt::UserRole + 2);
449 | };
450 |
451 | QAction* enable = menu.addAction("Enable");
452 | enable->setCheckable(true);
453 | if (mode_ == Mode::enable) { enable->setChecked(true); }
454 | QObject::connect(enable, &QAction::triggered, std::bind(updateMode, Mode::enable));
455 |
456 | QAction* disable = menu.addAction("Disable");
457 | disable->setCheckable(true);
458 | if (mode_ == Mode::disable) { disable->setChecked(true); }
459 | QObject::connect(disable, &QAction::triggered, std::bind(updateMode, Mode::disable));
460 |
461 | QAction* neutral = menu.addAction("Neutral");
462 | neutral->setCheckable(true);
463 | if (mode_ == Mode::neutral) { neutral->setChecked(true); }
464 | QObject::connect(neutral, &QAction::triggered, std::bind(updateMode, Mode::neutral));
465 | }
466 |
467 | (void) menu.exec(event->screenPos());
468 | }
469 |
470 |
471 | QDataStream& operator<<(QDataStream& out, Node const& node)
472 | {
473 | // Save node data
474 | out << node.type_ << node.name() << node.stage_ << node.pos();
475 |
476 | // save node attributes
477 | out << node.attributes().size();
478 | for (auto const& attr: node.attributes())
479 | {
480 | out << attr->info() << attr->data();
481 | }
482 |
483 | return out;
484 | }
485 |
486 |
487 | QDataStream& operator>>(QDataStream& in, Node& node)
488 | {
489 | // load node data
490 | QPointF pos;
491 | QString name;
492 | in >> node.type_ >> name >>node.stage_ >> pos;
493 | node.setPos(pos);
494 | node.setName(name);
495 |
496 | // load node attributes
497 | int attributesSize;
498 | in >> attributesSize;
499 | QVector attributesInfo;
500 | QVector attributesData;
501 | for (int j = 0; j < attributesSize; ++j)
502 | {
503 | AttributeInfo info;
504 | QVariant data;
505 | in >> info >> data;
506 | attributesInfo.append(info);
507 | attributesData.append(data);
508 | }
509 |
510 | node.createAttributes(attributesInfo);
511 | auto data = attributesData.constBegin();
512 | for (auto const& attribute : node.attributes())
513 | {
514 | attribute->setData(*data);
515 | ++data;
516 | }
517 | return in;
518 | }
519 | }
520 |
--------------------------------------------------------------------------------
/src/Node.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_NODE_H
2 | #define PIPER_NODE_H
3 |
4 | #include "Scene.h"
5 | #include "Attribute.h"
6 | #include "Types.h"
7 |
8 | namespace piper
9 | {
10 | class NodeName : public QGraphicsTextItem
11 | {
12 | public:
13 | NodeName(QGraphicsItem* parent);
14 | virtual ~NodeName() = default;
15 |
16 | void adjustPosition();
17 |
18 | protected:
19 | void keyPressEvent(QKeyEvent* e) override;
20 | };
21 |
22 | class Node : public QGraphicsItem
23 | {
24 | friend Link* connect(QString const& from, QString const& out, QString const& to, QString const& in);
25 | friend QDataStream& operator<<(QDataStream& out, Node const& node);
26 | friend QDataStream& operator>>(QDataStream& in, Node& node);
27 |
28 | public:
29 | Node (QString const& type = "", QString const& name = "", QString const& stage = "");
30 | virtual ~Node();
31 |
32 | // highlight attribute that are compatible with dataType
33 | void highlight(Attribute* emitter);
34 | void unhighlight();
35 |
36 | QString& stage() { return stage_; } // TODO const it (currently required for stage edition)
37 | QString name() const;
38 | QString const& nodeType() const { return type_; }
39 |
40 | void setMode(Mode mode);
41 | void setName(QString const& name);
42 | void setBackgroundColor(QColor const& color)
43 | {
44 | background_brush_.setColor(color);
45 | update();
46 | }
47 | void updateWidth();
48 |
49 | // Create attributes of this item.
50 | void createAttributes(QVector const& attributesInfo);
51 |
52 | QVector const& attributes() const { return attributes_; }
53 |
54 | QVector& attributes() { return attributes_; }
55 |
56 | protected:
57 | QRectF boundingRect() const override;
58 | void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
59 |
60 | void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
61 | void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
62 | void keyPressEvent(QKeyEvent* event) override;
63 | void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override;
64 |
65 |
66 | private:
67 | void createStyle();
68 |
69 | QRectF bounding_rect_;
70 |
71 | NodeName* name_;
72 | QString type_;
73 | QString stage_;
74 | Mode mode_;
75 |
76 | qint32 width_;
77 | qint32 height_;
78 |
79 | QBrush background_brush_;
80 | QPen pen_;
81 | QPen pen_selected_;
82 |
83 | QBrush attribute_brush_;
84 | QBrush attribute_alt_brush_;
85 |
86 | QPen type_pen_;
87 | QBrush type_brush_;
88 | QFont type_font_;
89 | QRectF type_rect_;
90 |
91 | QVector attributes_;
92 | };
93 |
94 | Link* connect(QString const& from, QString const& out, QString const& to, QString const& in);
95 |
96 | QDataStream& operator<<(QDataStream& out, Node const& node);
97 | QDataStream& operator>>(QDataStream& in, Node& node);
98 | }
99 |
100 | #endif
101 |
102 |
--------------------------------------------------------------------------------
/src/NodeCreator.cc:
--------------------------------------------------------------------------------
1 | #include "NodeCreator.h"
2 |
3 | #include
4 |
5 | namespace piper
6 | {
7 | NodeCreator& NodeCreator::instance()
8 | {
9 | static NodeCreator creator_;
10 | return creator_;
11 | }
12 |
13 | void NodeCreator::addItem(Item const& item)
14 | {
15 | auto it = available_items_.find(item.type);
16 | if (it != available_items_.end())
17 | {
18 | qDebug() << "Can't add the item. Type" << item.type << "already exists.";
19 | return;
20 | }
21 | auto newItem = available_items_.insert(item.type, item);
22 | if (newItem->from == "") { newItem->from = "unknown"; }
23 | if (newItem->category == "") { newItem->category = "unknown"; }
24 | }
25 |
26 | Node* NodeCreator::createItem(QString const& type, QString const& name, QString const& stage, const QPointF& pos)
27 | {
28 | auto it = available_items_.find(type);
29 | if (it == available_items_.end())
30 | {
31 | qDebug() << "Can't create the item" << name << ". Type" << type << "is unknown";
32 | return nullptr;
33 | }
34 |
35 | Node* node = new Node(type, name, stage);
36 | node->setPos(pos);
37 | node->createAttributes(it->attributes);
38 | node->setToolTip(it->help);
39 | return node;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/NodeCreator.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_NODE_CREATOR_H
2 | #define PIPER_NODE_CREATOR_H
3 |
4 | #include "Node.h"
5 |
6 | namespace piper
7 | {
8 | struct Item
9 | {
10 | QString type; // Item type - shall be unique!
11 | QString help; // Displayed text as a tooltip
12 | QString from; // The library that add the item
13 | QString category; // Item category to sort them in the interface
14 | QVector attributes; // Describe the behavior
15 | };
16 |
17 | class NodeCreator
18 | {
19 | public:
20 | static NodeCreator& instance();
21 |
22 | QList- availableItems() const { return available_items_.values(); }
23 | void addItem(Item const& item);
24 | Node* createItem(QString const& type, QString const& name, QString const& stage, QPointF const& pos);
25 |
26 | private:
27 | NodeCreator() = default;
28 | virtual ~NodeCreator() = default;
29 |
30 | QHash available_items_;
31 | };
32 | }
33 |
34 | #endif
35 |
--------------------------------------------------------------------------------
/src/PropertyDelegate.cc:
--------------------------------------------------------------------------------
1 | #include "PropertyDelegate.h"
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | namespace piper
8 | {
9 | QWidget* StagePropertyDelegate::createEditor(QWidget* parent, QStyleOptionViewItem const& option, QModelIndex const& index) const
10 | {
11 | QComboBox* cb = new QComboBox(parent);
12 |
13 | int row = 0;
14 | QModelIndex i = stages_->index(row, 0);
15 | while (i.isValid())
16 | {
17 | QString stage = stages_->data(i, Qt::DisplayRole).toString();
18 | cb->addItem(stage);
19 |
20 | ++row;
21 | i = stages_->index(row, 0);
22 | }
23 |
24 | connect(cb, static_cast(&QComboBox::currentIndexChanged),this, &StagePropertyDelegate::onIndexChange);
25 | return cb;
26 | }
27 |
28 |
29 | void StagePropertyDelegate::setEditorData(QWidget* editor, QModelIndex const& index) const
30 | {
31 | QComboBox* cb = qobject_cast(editor);
32 | QString value = index.data(Qt::EditRole).toString();
33 | int idx = cb->findText(value);
34 | if (idx >= 0)
35 | {
36 | cb->setCurrentIndex(idx);
37 | }
38 |
39 | cb->showPopup();
40 | }
41 |
42 |
43 | void StagePropertyDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, QModelIndex const& index) const
44 | {
45 | QComboBox* cb = qobject_cast(editor);
46 | model->setData(index, cb->currentText(), Qt::EditRole);
47 | }
48 |
49 |
50 | void StagePropertyDelegate::onIndexChange()
51 | {
52 | QComboBox* cb = static_cast(sender());
53 | emit commitData(cb);
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/PropertyDelegate.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_PROPERTY_DELEGATE_H
2 | #define PIPER_PROPERTY_DELEGATE_H
3 |
4 | #include
5 |
6 | class QStandardItemModel;
7 |
8 | namespace piper
9 | {
10 | class StagePropertyDelegate : public QStyledItemDelegate
11 | {
12 | Q_OBJECT
13 |
14 | public:
15 | QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, QModelIndex const& index) const override;
16 | void setEditorData(QWidget* editor, QModelIndex const& index) const override;
17 | void setModelData(QWidget* editor, QAbstractItemModel* model, QModelIndex const& index) const override;
18 |
19 | void setStageModel(QStandardItemModel const* stages) { stages_ = stages; }
20 |
21 | public slots:
22 | void onIndexChange();
23 |
24 | private:
25 | QStandardItemModel const* stages_;
26 | };
27 | }
28 |
29 | #endif
30 |
31 |
--------------------------------------------------------------------------------
/src/Scene.cc:
--------------------------------------------------------------------------------
1 | #include "Scene.h"
2 | #include "Node.h"
3 | #include "Link.h"
4 | #include "ExportBackend.h"
5 | #include "NodeCreator.h"
6 | #include "ThemeManager.h"
7 |
8 | #include
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 |
21 | namespace piper
22 | {
23 | QStack Scene::undoStack_{};
24 | QStack Scene::redoStack_{};
25 |
26 | Scene::Scene (QObject* parent)
27 | : QGraphicsScene(0, 0, 32000, 32000, parent)
28 | {
29 | // Prepare stage model
30 | stages_ = new QStandardItemModel(this);
31 | stages_->insertColumns(0, 1);
32 | QObject::connect(stages_, &QStandardItemModel::itemChanged, this, &Scene::onStageUpdated);
33 |
34 | // Prepare mode model
35 | modes_ = new QStandardItemModel(this);
36 | modes_->insertColumns(0, 1);
37 | QObject::connect(modes_, &QStandardItemModel::rowsRemoved, this, &Scene::onModeRemoved);
38 | }
39 |
40 |
41 | Scene::~Scene()
42 | {
43 | // Manually delete nodes and links because order are important
44 | QVector deleteNodes = nodes_;
45 | for (auto& node : deleteNodes)
46 | {
47 | delete node;
48 | }
49 |
50 | QVector deleteLinks = links_;
51 | for (auto& link : deleteLinks)
52 | {
53 | delete link;
54 | }
55 | }
56 |
57 | void Scene::drawBackground(QPainter* painter, QRectF const& rect)
58 | {
59 | QBrush brush(Qt::SolidPattern);
60 | brush.setColor({40, 40, 40}),
61 | painter->fillRect(rect, brush);
62 |
63 | QPen pen;
64 | pen.setColor({100, 100, 100});
65 | pen.setWidth(2);
66 | painter->setPen(pen);
67 |
68 | constexpr int gridSize = 20;
69 | qreal left = int(rect.left()) - (int(rect.left()) % gridSize);
70 | qreal top = int(rect.top()) - (int(rect.top()) % gridSize);
71 | QVector points;
72 | for (qreal x = left; x < rect.right(); x += gridSize)
73 | {
74 | for (qreal y = top; y < rect.bottom(); y += gridSize)
75 | {
76 | points.append(QPointF(x,y));
77 | }
78 | }
79 |
80 | painter->drawPoints(points.data(), points.size());
81 | }
82 |
83 |
84 | void Scene::keyReleaseEvent(QKeyEvent* keyEvent)
85 | {
86 | if (keyEvent->key() == Qt::Key::Key_Delete)
87 | {
88 | QByteArray copiedScene = copyCurrentScene();
89 | undoStack_.push(copiedScene);
90 | redoStack_ = QStack();
91 |
92 | for (auto& item : selectedItems())
93 | {
94 | delete item;
95 | }
96 | }
97 |
98 | // destroy orphans link
99 | QVector deleteLinks = links_;
100 | for (auto& link : deleteLinks)
101 | {
102 | if (not link->isConnected())
103 | {
104 | delete link;
105 | }
106 | }
107 | }
108 |
109 | QByteArray Scene::copyCurrentScene()
110 | {
111 | QByteArray copyCurrentScene;
112 | QDataStream stream(©CurrentScene, QIODevice::WriteOnly | QIODevice::Truncate);
113 | stream << nodes_.size();
114 | for (auto const& node : nodes_)
115 | {
116 | stream << *node;
117 | }
118 |
119 | stream << links_.size();
120 | for (auto const& link : links_)
121 | {
122 | stream << static_cast(link->from()->parentItem())->name() << link->from()->name();
123 | stream << static_cast(link->to()->parentItem())->name() << link->to()->name();
124 | }
125 |
126 | return copyCurrentScene;
127 | }
128 |
129 | void Scene::loadSceneFromStack(QStack stack)
130 | {
131 | QVector deleteNodes = nodes_;
132 | for (auto& node : deleteNodes)
133 | {
134 | delete node;
135 | }
136 |
137 | QVector deleteLinks = links_;
138 | for (auto& link : deleteLinks)
139 | {
140 | delete link;
141 | }
142 |
143 | struct LinkData
144 | {
145 | QString from;
146 | QString output;
147 | QString to;
148 | QString input;
149 | };
150 |
151 | QByteArray previousScene = stack.top();
152 | QDataStream stream(previousScene);
153 | QList copies;
154 | QList links;
155 |
156 | // preload nodes and links
157 | int nodeCount;
158 | stream >> nodeCount;
159 | for (int i = 0; i < nodeCount; ++i)
160 | {
161 | Node* node = new Node();
162 | stream >> *node;
163 | copies << node;
164 | }
165 |
166 | int linkCount;
167 | stream >> linkCount;
168 | for (int i = 0; i < linkCount; ++i)
169 | {
170 | LinkData link;
171 | stream >> link.from >> link.output >> link.to >> link.input;
172 | links << link;
173 | }
174 |
175 | for (auto const& copy : copies)
176 | {
177 | addNode(copy);
178 | }
179 |
180 | for (auto const& link : links)
181 | {
182 | connect(link.from, link.output, link.to, link.input);
183 | }
184 | }
185 |
186 | void Scene::undo()
187 | {
188 | if(not undoStack_.empty())
189 | {
190 | QByteArray currentScene = copyCurrentScene();
191 | redoStack_.push(currentScene);
192 |
193 | loadSceneFromStack(undoStack_);
194 |
195 | undoStack_.pop();
196 |
197 | onStageUpdated();
198 |
199 | }
200 | }
201 |
202 | void Scene::redo()
203 | {
204 | if(not redoStack_.empty())
205 | {
206 | QByteArray currentScene = copyCurrentScene();
207 | undoStack_.push(currentScene);
208 |
209 | loadSceneFromStack(redoStack_);
210 |
211 | redoStack_.pop();
212 |
213 | onStageUpdated();
214 | }
215 | }
216 |
217 |
218 | void Scene::resetStagesColor()
219 | {
220 | QColor default_background = ThemeManager::instance().getNodeTheme().background;
221 | for (auto& node : nodes_)
222 | {
223 | node->setBackgroundColor(default_background);
224 | }
225 | }
226 |
227 |
228 | void Scene::updateStagesColor(QString const& stage, QColor const& color)
229 | {
230 | for (auto& node : nodes_)
231 | {
232 | if (node->stage() == stage)
233 | {
234 | node->setBackgroundColor(color);
235 | }
236 | }
237 | }
238 |
239 |
240 | void Scene::onStageUpdated()
241 | {
242 | int row = 0;
243 | QModelIndex index = stages_->index(row, 0);
244 |
245 | resetStagesColor();
246 | while (index.isValid())
247 | {
248 | QString stage = stages_->data(index, Qt::DisplayRole).toString();
249 | QColor color = stages_->data(index, Qt::DecorationRole).value();
250 | updateStagesColor(stage, color);
251 |
252 | ++row;
253 | index = stages_->index(row, 0);
254 | }
255 | }
256 |
257 |
258 |
259 | void Scene::addNode(Node* node)
260 | {
261 | addItem(node);
262 | nodes_.append(node);
263 | }
264 |
265 |
266 | void Scene::removeNode(Node* node)
267 | {
268 | // Remove from mode
269 | for (int i = 0; i < modes_->rowCount(); ++i)
270 | {
271 | QStandardItem* mode = modes_->item(i, 0);
272 | QHash nodeMode = mode->data(Qt::UserRole + 2).toHash();
273 | nodeMode.remove(node->name());
274 | mode->setData(nodeMode, Qt::UserRole + 2);
275 | }
276 |
277 | removeItem(node);
278 | nodes_.removeAll(node);
279 | }
280 |
281 |
282 | void Scene::addLink(Link* link)
283 | {
284 | addItem(link);
285 | links_.append(link);
286 | }
287 |
288 |
289 | void Scene::removeLink(Link* link)
290 | {
291 | removeItem(link);
292 | links_.removeAll(link);
293 | }
294 |
295 |
296 | void Scene::connect(QString const& from, QString const& out, QString const& to, QString const& in)
297 | {
298 | auto const nodeFrom = std::find_if(nodes().begin(), nodes().end(),
299 | [&](Node const* node) { return (node->name() == from); }
300 | );
301 | auto const nodeTo = std::find_if(nodes().begin(), nodes().end(),
302 | [&](Node const* node) { return (node->name() == to); }
303 | );
304 |
305 | if (nodeFrom == nodes().end())
306 | {
307 | QString error = "Node " + from + " (from) not found";
308 | links_import_errors_.append(error);
309 | return;
310 | }
311 |
312 | if (nodeTo == nodes().end())
313 | {
314 | QString error = "Node " + to + " (to) not found";
315 | links_import_errors_.append(error);
316 | return;
317 | }
318 |
319 | Attribute* attrOut{nullptr};
320 | for (auto& attr : (*nodeFrom)->attributes())
321 | {
322 | if (attr->isOutput() and (attr->name() == out))
323 | {
324 | attrOut = attr;
325 | break;
326 | }
327 | }
328 |
329 | Attribute* attrIn{nullptr};
330 | for (auto& attr : (*nodeTo)->attributes())
331 | {
332 | if (attr->isInput() and (attr->name() == in))
333 | {
334 | attrIn = attr;
335 | break;
336 | }
337 | }
338 |
339 | if (attrIn == nullptr)
340 | {
341 | QString error = "Cannot find attribute " + in + " (in) in the node" + to;
342 | links_import_errors_.append(error);
343 | return;
344 | }
345 |
346 | if (attrOut == nullptr)
347 | {
348 | QString error = "Cannot find attribute " + out + " (out) in the node" + from;
349 | links_import_errors_.append(error);
350 | return;
351 | }
352 |
353 | if (not attrIn->accept(attrOut))
354 | {
355 | QString error = "Cannot connect node " + from + " to node " + to + ". Type mismatch";
356 | return;
357 | }
358 |
359 | Link* link = new Link;
360 | link->connectFrom(attrOut);
361 | link->connectTo(attrIn);
362 | addLink(link);
363 | }
364 |
365 |
366 | QModelIndex Scene::addMode(QString const& name)
367 | {
368 | // Add item
369 | QStandardItem* item = new QStandardItem();
370 | item->setData(name, Qt::DisplayRole);
371 | item->setDropEnabled(false);;
372 | modes_->appendRow(item);
373 |
374 | // Enable item selection and put it edit mode
375 | QModelIndex index = modes_->indexFromItem(item);
376 | onModeSelected(index);
377 | return index;
378 | }
379 |
380 |
381 | void Scene::onExport(ExportBackend& backend)
382 | {
383 | // -------- stages -------- //
384 | QVector stagesArray;
385 | for (int i = 0; i < stages()->rowCount(); ++i)
386 | {
387 | QString stage = stages()->item(i, 0)->data(Qt::DisplayRole).toString();
388 | stagesArray.append(stage);
389 | }
390 | backend.writeStages(stagesArray);
391 |
392 | // -------- nodes -------- //
393 | for (auto const& node : nodes_)
394 | {
395 | QHash attr;
396 | for (auto const& attribute : node->attributes())
397 | {
398 | if (not attribute->isMember())
399 | {
400 | continue;
401 | }
402 |
403 | attr.insert(attribute->name(), attribute->data());
404 | }
405 |
406 | backend.writeNode(node->nodeType(), node->name(), node->stage(), attr);
407 | }
408 |
409 | // -------- links -------- //
410 | for (auto const& link : links_)
411 | {
412 | Node const* from = static_cast(link->from()->parentItem());
413 | Node const* to = static_cast(link->to()->parentItem());
414 | backend.writeLink(from->name(), link->from()->name(), to->name(), link->to()->name(), link->from()->dataType());
415 | }
416 |
417 | // -------- modes -------- //
418 | for (int i = 0; i < modes()->rowCount(); ++i)
419 | {
420 | QStandardItem* mode = modes()->item(i, 0);
421 | QString modeName = mode->data(Qt::DisplayRole).toString();
422 | if (mode->data(Qt::DecorationRole).isValid())
423 | {
424 | backend.writeDefaultMode(modeName);
425 | }
426 |
427 | QHash config = mode->data(Qt::UserRole + 2).toHash();
428 | QHash exportConfig;
429 | for (auto it = config.constBegin(); it != config.constEnd(); ++it)
430 | {
431 | exportConfig[it.key()] = static_cast(it.value().toInt());
432 | }
433 |
434 | backend.writeMode(modeName, exportConfig);
435 | }
436 | }
437 |
438 |
439 | void Scene::onModeSelected(QModelIndex const& index)
440 | {
441 | // Reset select state.
442 | for (int i = 0; i < modes_->rowCount(); ++i)
443 | {
444 | modes_->item(i, 0)->setData(false, Qt::UserRole + 1);
445 | }
446 |
447 | QStandardItem* currentMode = modes_->itemFromIndex(index);
448 | currentMode->setData(true, Qt::UserRole + 1);
449 |
450 | // Update node display.
451 | QHash nodeMode = currentMode->data(Qt::UserRole + 2).toHash();
452 | for (auto& node : nodes_)
453 | {
454 | auto it = nodeMode.find(node->name());
455 | if (it == nodeMode.end())
456 | {
457 | // Default mode is enabled.
458 | node->setMode(Mode::enable);
459 | continue;
460 | }
461 |
462 | node->setMode(static_cast(it.value().toInt()));
463 | }
464 | }
465 |
466 |
467 | void Scene::onModeSetDefault(QModelIndex const& index)
468 | {
469 | // Reset select state.
470 | for (int i = 0; i < modes_->rowCount(); ++i)
471 | {
472 | modes_->item(i, 0)->setData(QVariant(), Qt::DecorationRole);
473 | }
474 |
475 | QStandardItem* currentMode = modes_->itemFromIndex(index);
476 | currentMode->setData(QIcon(":/icon/star.svg"), Qt::DecorationRole);
477 | }
478 |
479 |
480 | void Scene::onModeRemoved()
481 | {
482 | for (auto& node : nodes_)
483 | {
484 | node->setMode(Mode::enable);
485 | }
486 | }
487 |
488 |
489 | QDataStream& operator<<(QDataStream& out, Scene const& scene)
490 | {
491 | // save stages.
492 | out << scene.stages()->rowCount();
493 | for (int i = 0; i < scene.stages()->rowCount(); ++i)
494 | {
495 | out << *scene.stages()->item(i, 0);
496 | }
497 |
498 | // save modes.
499 | out << scene.modes()->rowCount();
500 | for (int i = 0; i < scene.modes()->rowCount(); ++i)
501 | {
502 | out << *scene.modes()->item(i, 0);
503 | }
504 |
505 | // save nodes.
506 | out << scene.nodes().size();
507 | for (auto const& node : scene.nodes())
508 | {
509 | out << *node;
510 | }
511 |
512 | // save links.
513 | out << scene.links().size();
514 | for (auto const& link : scene.links())
515 | {
516 | out << static_cast(link->from()->parentItem())->name() << link->from()->name();
517 | out << static_cast(link->to()->parentItem())->name() << link->to()->name();
518 | }
519 |
520 | return out;
521 | }
522 |
523 |
524 | QDataStream& operator>>(QDataStream& in, Scene& scene)
525 | {
526 | // Load stages.
527 | int stageCount;
528 | in >> stageCount;
529 | for (int i = 0; i < stageCount; ++i)
530 | {
531 | QStandardItem* item = new QStandardItem();
532 | in >> *item;
533 | scene.stages()->setItem(i, item);
534 | }
535 |
536 | // Load modes.
537 | int modeCount;
538 | in >> modeCount;
539 | for (int i = 0; i < modeCount; ++i)
540 | {
541 | QStandardItem* item = new QStandardItem();
542 | in >> *item;
543 | scene.modes()->setItem(i, item);
544 | }
545 |
546 | // Load nodes.
547 | int nodeCount;
548 | in >> nodeCount;
549 | for (int i = 0; i < nodeCount; ++i)
550 | {
551 | Node* node = new Node();
552 | in >> *node;
553 | scene.addNode(node);
554 | }
555 |
556 | // Load links.
557 | int linkCount;
558 | in >> linkCount;
559 | for (int i = 0; i < linkCount; ++i)
560 | {
561 | QString from, output;
562 | in >> from >> output;
563 |
564 | QString to, input;
565 | in >> to >> input;
566 |
567 | scene.connect(from, output, to, input);
568 | }
569 |
570 | scene.onStageUpdated();
571 |
572 | return in;
573 | }
574 |
575 |
576 | void Scene::onImportJson(QJsonObject& json)
577 | {
578 | // load stages
579 | QJsonArray stages= json["Stages"].toArray();
580 | for (int i = 0; i < stages.size(); ++i)
581 | {
582 | QStandardItem* item = new QStandardItem();
583 | item->setData(generateRandomColor(), Qt::DecorationRole);
584 | item->setData(stages[i].toString(), Qt::DisplayRole);
585 | item->setDropEnabled(false);;
586 | stages_->appendRow(item);
587 | }
588 |
589 | QJsonObject steps = json["Steps"].toObject();
590 | loadNodesJson(steps);
591 |
592 | // Organize nodes following their stages.
593 | onStageUpdated();
594 | placeNodesDefaultPosition();
595 |
596 | QJsonArray links= json["Links"].toArray();
597 | loadLinksJson(links);
598 |
599 | QJsonObject modes = json["Modes"].toObject();
600 | loadModesJson(modes);
601 |
602 |
603 | // Display import report if something wrong happened.
604 | if (nodes_import_errors_.isEmpty() and links_import_errors_.isEmpty())
605 | {
606 | return;
607 | }
608 |
609 | QString errors;
610 | errors += "Nodes:\n";
611 | for (auto const& err : nodes_import_errors_) { errors += (err + "\n"); }
612 | errors += "Links:\n";
613 | for (auto const& err : links_import_errors_) { errors += (err + "\n"); }
614 | QMessageBox::warning(nullptr, "Import report", errors);
615 | }
616 |
617 |
618 | void Scene::loadNodesJson(QJsonObject& steps)
619 | {
620 | for (QString stepName : steps.keys())
621 | {
622 | QJsonObject step = steps[stepName].toObject();
623 | QString type = step["type"].toString();
624 |
625 | Node* node = NodeCreator::instance().createItem(type, stepName, "", {0, 0});
626 | if (node == nullptr)
627 | {
628 | QString error = "Cannot create node " + stepName + ": type " + type + " is unknown.";
629 | nodes_import_errors_.append(error);
630 | continue;
631 | }
632 |
633 | for (QString member : step.keys())
634 | {
635 | if (member == "type")
636 | {
637 | continue;
638 | }
639 | if (member == "stage")
640 | {
641 | node->stage() = step["stage"].toString();
642 | continue;
643 | }
644 |
645 | QVector& attributes = node->attributes();
646 |
647 | auto cmp = [&member](Attribute* attr) { return attr->name() == member; };
648 |
649 | auto itObj = std::find_if(attributes.begin(), attributes.end(), cmp);
650 | if (itObj != attributes.end())
651 | {
652 | (*itObj)->setData(QVariant(step[member].toVariant()));
653 | }
654 | else
655 | {
656 | QString error = "Attribute " + member + " not found: version mismatch ?";
657 | nodes_import_errors_.append(error);
658 | }
659 | }
660 |
661 | addNode(node);
662 | }
663 | }
664 |
665 |
666 | void Scene::loadLinksJson(QJsonArray& links)
667 | {
668 | for (int i = 0; i < links.size(); ++i)
669 | {
670 | QJsonObject l = links[i].toObject();
671 | QString from = l["from"].toString();
672 | QString output = l["out"].toString();
673 | QString to = l["to"].toString();
674 | QString input = l["in"].toString();
675 | connect(from, output, to, input);
676 | }
677 | }
678 |
679 |
680 | void Scene::loadModesJson(QJsonObject& modes)
681 | {
682 | QString defaultMode;
683 | for (auto mode : modes.keys())
684 | {
685 | if (mode == "default")
686 | {
687 | // special case: the default key define the default mode to use at startup.
688 | defaultMode = modes.value(mode).toString();
689 | continue;
690 | }
691 |
692 | QStandardItem* item = new QStandardItem();
693 | item->setData(mode, Qt::DisplayRole);
694 | item->setDropEnabled(false);
695 |
696 | // Store mode configuration
697 | QHash nodeMode = item->data(Qt::UserRole + 2).toHash();
698 | QJsonObject modeObject = modes[mode].toObject();
699 | QJsonObject modeConfig = modeObject["configuration"].toObject();
700 | for (auto node : modeConfig.keys())
701 | {
702 | auto fromString = [](QString const& modeIn)
703 | {
704 | if (modeIn == "Neutral")
705 | {
706 | return static_cast(Mode::neutral);
707 | }
708 | if (modeIn == "Disable")
709 | {
710 | return static_cast(Mode::disable);
711 | }
712 | return static_cast(Mode::enable);
713 | };
714 |
715 | nodeMode[node] = fromString(modeConfig[node].toString());
716 | }
717 | item->setData(nodeMode, Qt::UserRole + 2);
718 |
719 | modes_->appendRow(item);
720 | }
721 |
722 | // apply default mode value.
723 | for (int i = 0; i < modes_->rowCount(); ++i)
724 | {
725 | QStandardItem* item = modes_->item(i, 0);
726 | if (item->data(Qt::DisplayRole).toString() == defaultMode)
727 | {
728 | onModeSetDefault(modes_->indexFromItem(item));
729 | break;
730 | }
731 | }
732 | }
733 |
734 |
735 | void Scene::placeNodesDefaultPosition()
736 | {
737 | QGraphicsView const* view = views().at(0);
738 | QPointF const scenePos = view->mapToScene(view->viewport()->rect().center());
739 |
740 | struct stageInfo {int column; int size;};
741 | QMap stageIndex;
742 |
743 | int row = 0;
744 | QModelIndex index = stages_->index(row, 0);
745 |
746 | while (index.isValid())
747 | {
748 | QString stage = stages_->data(index, Qt::DisplayRole).toString();
749 | stageIndex.insert(stage, {row, 0});
750 |
751 | ++row;
752 | index = stages_->index(row, 0);
753 | }
754 |
755 | // Handle steps without stage
756 | stageIndex.insert("", {row, 0});
757 |
758 | for (auto n : nodes_)
759 | {
760 | qreal x = scenePos.x() + 300 * stageIndex[n->stage()].column;
761 | qreal y = scenePos.y() + 200 * stageIndex[n->stage()].size;
762 |
763 | stageIndex[n->stage()].size++;
764 |
765 | n->setPos(x ,y);
766 | x += 300;
767 | y += 150;
768 | }
769 | }
770 |
771 |
772 | QColor generateRandomColor()
773 | {
774 | // procedural color generator: the gold ratio
775 | static double nextColorHue = 1.0 / (rand() % 100); // don't need a proper random here
776 | constexpr double golden_ratio_conjugate = 0.618033988749895; // 1 / phi
777 | nextColorHue += golden_ratio_conjugate;
778 | nextColorHue = std::fmod(nextColorHue, 1.0);
779 |
780 | QColor nextColor;
781 | nextColor.setHsvF(nextColorHue, 0.5, 0.99);
782 | return nextColor;
783 | }
784 | }
785 |
--------------------------------------------------------------------------------
/src/Scene.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_SCENE_H
2 | #define PIPER_SCENE_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | namespace piper
11 | {
12 | class Link;
13 | class Node;
14 | class ExportBackend;
15 |
16 | class Scene : public QGraphicsScene
17 | {
18 | Q_OBJECT
19 |
20 | public:
21 | Scene(QObject *parent = nullptr);
22 | virtual ~Scene();
23 |
24 | void resetStagesColor();
25 | void updateStagesColor(QString const& stage, QColor const& color);
26 |
27 | void addNode(Node* node);
28 | void removeNode(Node* node);
29 | QVector const& nodes() const { return nodes_; }
30 |
31 | QByteArray copyCurrentScene();
32 | void loadSceneFromStack(QStack stack);
33 | void undo();
34 | void redo();
35 | void addLink(Link* link);
36 | void removeLink(Link* link);
37 | QVector const& links() const { return links_; }
38 | void connect(QString const& from, QString const& out, QString const& to, QString const& in);
39 |
40 | QModelIndex addMode(QString const& name);
41 |
42 | QStandardItemModel* stages() const { return stages_; }
43 | QStandardItemModel* modes() const { return modes_; }
44 |
45 | void onExport(ExportBackend& backend);
46 | void onImportJson(QJsonObject& json);
47 |
48 | public slots:
49 | void onModeSelected(QModelIndex const& index);
50 | void onModeSetDefault(QModelIndex const& index);
51 | void onModeRemoved();
52 |
53 | void onStageUpdated();
54 |
55 | protected:
56 | void drawBackground(QPainter *painter, const QRectF &rect) override;
57 | void keyReleaseEvent(QKeyEvent *keyEvent) override;
58 |
59 | private:
60 | void loadNodesJson(QJsonObject& steps);
61 | void loadLinksJson(QJsonArray& links);
62 | void loadModesJson(QJsonObject& modes);
63 | void placeNodesDefaultPosition();
64 |
65 | QVector nodes_;
66 | QVector links_;
67 |
68 | QVector nodes_import_errors_;
69 | QVector links_import_errors_;
70 |
71 | QStandardItemModel* stages_;
72 | QStandardItemModel* modes_;
73 |
74 | static QStack undoStack_;
75 | static QStack redoStack_;
76 | };
77 |
78 | QDataStream& operator<<(QDataStream& out, Scene const& scene);
79 | QDataStream& operator>>(QDataStream& in, Scene& scene);
80 | QColor generateRandomColor();
81 | }
82 |
83 | #endif
84 |
85 |
--------------------------------------------------------------------------------
/src/ThemeManager.cc:
--------------------------------------------------------------------------------
1 | #include "ThemeManager.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | namespace piper
11 | {
12 | piper::ThemeManager& ThemeManager::instance()
13 | {
14 | static ThemeManager instance;
15 | return instance;
16 | }
17 |
18 |
19 | NodeTheme ThemeManager::getNodeTheme() const
20 | {
21 | return node_theme_;
22 | }
23 |
24 |
25 | AttributeTheme ThemeManager::getAttributeTheme() const
26 | {
27 | return attribute_theme_;
28 | }
29 |
30 |
31 | DataTypeTheme ThemeManager::getDataTypeTheme(QString const& dataType)
32 | {
33 | if (data_type_themes_.contains(dataType))
34 | {
35 | return data_type_themes_[dataType];
36 | }
37 | return data_type_themes_["default"];
38 | }
39 |
40 |
41 | bool ThemeManager::load(const QString& theme_filename)
42 | {
43 | QFile io(theme_filename);
44 | if (not io.open(QIODevice::ReadOnly))
45 | {
46 | qWarning() << "Can't open theme file" << theme_filename;
47 | return false;
48 | }
49 |
50 |
51 | QByteArray file_data = io.readAll();
52 | QJsonParseError error;
53 | QJsonDocument theme_document = QJsonDocument::fromJson(file_data, &error);
54 | if (error.error != QJsonParseError::NoError)
55 | {
56 | qWarning() << "Error while parsing theme file:" << error.errorString();
57 | return false;
58 | }
59 |
60 | if (not parseNode(theme_document.object()))
61 | {
62 | return false;
63 | }
64 |
65 | if (not parseAttribute(theme_document.object()))
66 | {
67 | return false;
68 | }
69 |
70 | if (not parseDataType(theme_document.object()))
71 | {
72 | return false;
73 | }
74 |
75 | return true;
76 | }
77 |
78 |
79 | bool ThemeManager::parseNode(QJsonObject const& json)
80 | {
81 | if (not (json.contains("node") and json["node"].isObject()))
82 | {
83 | qWarning() << "Can't parse node";
84 | return false;
85 | }
86 | QJsonObject node = json["node"].toObject();
87 |
88 | QJsonObject name = node["name"].toObject();
89 | node_theme_.name_font = parseFont(name["font"].toObject());
90 | node_theme_.name_color = parseColor(name["font"].toObject());
91 |
92 | node_theme_.background = parseColor(node["background"].toObject());
93 |
94 | QJsonObject border = node["border"].toObject();
95 | node_theme_.border = parseColor(border["normal"].toObject());
96 | node_theme_.border_selected = parseColor(border["selected"].toObject());;
97 |
98 | QJsonObject type = node["type"].toObject();
99 | node_theme_.type_font = parseFont(type["font"].toObject());
100 | node_theme_.type_color = parseColor(type["font"].toObject());
101 | node_theme_.type_background = parseColor(type["background"].toObject());;
102 |
103 | return true;
104 | }
105 |
106 |
107 | bool ThemeManager::parseAttribute(QJsonObject const& json)
108 | {
109 | if (not (json.contains("attribute") and json["attribute"].isObject()))
110 | {
111 | qWarning() << "Can't parse attribute";
112 | return false;
113 | }
114 | QJsonObject attribute = json["attribute"].toObject();
115 |
116 | attribute_theme_.background = parseColor(attribute["background"].toObject());
117 | attribute_theme_.background_alt = parseColor(attribute["background_alt"].toObject());
118 |
119 | auto parseMode = [this](QJsonObject const& json, AttributeTheme::Mode& mode)
120 | {
121 | mode.font = parseFont(json["font"].toObject());
122 | mode.font_color = parseColor(json["font"].toObject());
123 |
124 | QJsonObject connector = json["connector"].toObject();
125 | mode.connector.border_color = parseColor(json["connector"].toObject());
126 | mode.connector.border_width = connector["width"].toInt();
127 | };
128 |
129 | parseMode(attribute["minimize"].toObject(), attribute_theme_.minimize);
130 | parseMode(attribute["normal"].toObject(), attribute_theme_.normal);
131 | parseMode(attribute["highlight"].toObject(), attribute_theme_.highlight);
132 |
133 | return true;
134 | }
135 |
136 |
137 | bool ThemeManager::parseDataType(QJsonObject const& json)
138 | {
139 | if (not (json.contains("data_type") and json["data_type"].isObject()))
140 | {
141 | qWarning() << "Can't parse data type";
142 | return false;
143 | }
144 | QJsonObject data_type = json["data_type"].toObject();
145 |
146 | DataTypeTheme theme;
147 | theme.enable = parseColor(data_type["enable_default"].toObject());
148 | theme.disable = parseColor(data_type["disable"].toObject());
149 | theme.neutral = parseColor(data_type["neutral"].toObject());
150 | data_type_themes_["default"] = theme;
151 |
152 | QJsonObject enable_custom = data_type["enable_custom"].toObject();
153 | for (auto const& key : enable_custom.keys())
154 | {
155 | theme.enable = parseColor(enable_custom[key].toObject());
156 | data_type_themes_[key] = theme;
157 | }
158 |
159 | return true;
160 | }
161 |
162 |
163 | QColor ThemeManager::parseColor(QJsonObject const& json)
164 | {
165 | QJsonArray rgba = json["rgba"].toArray();
166 | return QColor{ rgba[0].toInt(), rgba[1].toInt(), rgba[2].toInt(), rgba[3].toInt()};
167 | }
168 |
169 |
170 | QFont ThemeManager::parseFont(QJsonObject const& json)
171 | {
172 | QString name = json["name"].toString();
173 | QString weight = json["weight"].toString();
174 | int size = json["size"].toInt();
175 |
176 | QFont::Weight qweight = QFont::Normal;
177 | if (weight == "Bold")
178 | {
179 | qweight = QFont::Bold;
180 | }
181 | else if (weight == "Medium")
182 | {
183 | qweight = QFont::Medium;
184 | }
185 | else if (weight == "Normal")
186 | {
187 | qweight = QFont::Normal;
188 | }
189 | else if (weight == "Light")
190 | {
191 | qweight = QFont::Light;
192 | }
193 | else
194 | {
195 | qWarning() << "Unknown font weight" << weight;
196 | }
197 |
198 | return QFont{ name, size, qweight };
199 | }
200 |
201 | }
202 |
--------------------------------------------------------------------------------
/src/ThemeManager.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_THEME_MANAGER_H
2 | #define PIPER_THEME_MANAGER_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | namespace piper
9 | {
10 | struct NodeTheme
11 | {
12 | QFont name_font;
13 | QColor name_color;
14 |
15 | QColor background;
16 |
17 | QFont type_font;
18 | QColor type_color;
19 | QColor type_background;
20 |
21 | QColor border;
22 | QColor border_selected;
23 | };
24 |
25 | struct AttributeTheme
26 | {
27 | QColor background;
28 | QColor background_alt;
29 |
30 | struct Mode
31 | {
32 | QFont font;
33 | QColor font_color;
34 | struct
35 | {
36 | int border_width;
37 | QColor border_color;
38 | } connector;
39 | };
40 |
41 | Mode minimize;
42 | Mode normal;
43 | Mode highlight;
44 | };
45 |
46 |
47 | struct DataTypeTheme
48 | {
49 | QColor enable;
50 | QColor disable;
51 | QColor neutral;
52 | };
53 |
54 |
55 | class ThemeManager
56 | {
57 | public:
58 | static ThemeManager& instance();
59 |
60 | bool load(QString const& theme_filename);
61 | NodeTheme getNodeTheme() const;
62 | AttributeTheme getAttributeTheme() const;
63 | DataTypeTheme getDataTypeTheme(QString const& dataType);
64 |
65 | private:
66 | ThemeManager() = default;
67 |
68 | bool parseNode(QJsonObject const& json);
69 | bool parseAttribute(QJsonObject const& json);
70 | bool parseDataType(QJsonObject const& json);
71 | QColor parseColor(QJsonObject const& json);
72 | QFont parseFont(QJsonObject const& json);
73 |
74 | NodeTheme node_theme_;
75 | AttributeTheme attribute_theme_;
76 | QHash data_type_themes_;
77 | };
78 | }
79 |
80 |
81 | #endif
82 |
83 |
--------------------------------------------------------------------------------
/src/Types.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_TYPES_H
2 | #define PIPER_TYPES_H
3 |
4 | namespace piper
5 | {
6 | // Possible modes of a node during execution
7 | enum Mode
8 | {
9 | enable, // The node is enable and run at its nominal behavior
10 | disable, // The node is disable and shall not interact with the pipeline (dead end)
11 | neutral // The node shall interact with the pipeline in a neutral step (i.e. passthrough, add 0, multiply with 1, etc.)
12 | };
13 | }
14 |
15 | #endif
16 |
--------------------------------------------------------------------------------
/src/View.cc:
--------------------------------------------------------------------------------
1 | #include "View.h"
2 | #include "Scene.h"
3 | #include "Node.h"
4 | #include "Link.h"
5 | #include "CreatorPopup.h"
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | namespace piper
14 | {
15 | QByteArray View::copy_{};
16 |
17 |
18 | View::View(QWidget* parent)
19 | : QGraphicsView(parent)
20 | {
21 | setFocusPolicy(Qt::ClickFocus);
22 | setDragMode(QGraphicsView::RubberBandDrag);
23 |
24 | creator_ = new CreatorPopup(this);
25 | }
26 |
27 |
28 | void View::goHome()
29 | {
30 | fitInView(scene()->itemsBoundingRect(), Qt::KeepAspectRatio);
31 | }
32 |
33 |
34 | void View::wheelEvent(QWheelEvent* event)
35 | {
36 | setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
37 |
38 | constexpr qreal inFactor = 1.15;
39 | constexpr qreal outFactor = 1 / inFactor;
40 |
41 | qreal zoomFactor = outFactor;
42 | if (event->delta() > 0)
43 | {
44 | zoomFactor = inFactor;
45 | }
46 |
47 | scale(zoomFactor, zoomFactor);
48 | }
49 |
50 |
51 | void View::keyPressEvent(QKeyEvent* event)
52 | {
53 | // Keyboard zoom
54 | setTransformationAnchor(QGraphicsView::AnchorViewCenter);
55 | constexpr qreal inFactor = 1.15;
56 | constexpr qreal outFactor = 1 / inFactor;
57 |
58 | if ((event->key() == Qt::Key::Key_Plus) and (event->modifiers() & Qt::ControlModifier))
59 | {
60 | scale(inFactor, inFactor);
61 | event->accept();
62 | }
63 | if ((event->key() == Qt::Key::Key_Minus) and (event->modifiers() & Qt::ControlModifier))
64 | {
65 | scale(outFactor, outFactor);
66 | event->accept();
67 | }
68 | if (event->key() == Qt::Key::Key_Equal)
69 | {
70 | creator_->popup();
71 | event->accept();
72 | }
73 | if (event->key() == Qt::Key::Key_Escape)
74 | {
75 | goHome();
76 | event->accept();
77 | }
78 | if ((event->key() == Qt::Key::Key_C) and (event->modifiers() & Qt::ControlModifier))
79 | {
80 | copy();
81 | event->accept();
82 | }
83 | if ((event->key() == Qt::Key::Key_V) and (event->modifiers() & Qt::ControlModifier))
84 | {
85 | paste();
86 | event->accept();
87 | }
88 | if ((event->key() == Qt::Key::Key_Z) and (event->modifiers() & Qt::ControlModifier))
89 | {
90 | undo();
91 | event->accept();
92 | }
93 | if ((event->key() == Qt::Key::Key_Y) and (event->modifiers() & Qt::ControlModifier))
94 | {
95 | redo();
96 | event->accept();
97 | }
98 |
99 | QGraphicsView::keyPressEvent(event);
100 | }
101 |
102 |
103 | void View::mousePressEvent(QMouseEvent* event)
104 | {
105 | if (event->button() == Qt::MiddleButton)
106 | {
107 | pan_ = true;
108 | panStartX_ = event->x();
109 | panStartY_ = event->y();
110 | viewport()->setCursor(Qt::ClosedHandCursor); // use viewport to workaround a refresh bug
111 | event->accept();
112 | return;
113 | }
114 | QGraphicsView::mousePressEvent(event);
115 | }
116 |
117 |
118 | void View::mouseMoveEvent(QMouseEvent* event)
119 | {
120 | if (pan_)
121 | {
122 | horizontalScrollBar()->setValue(horizontalScrollBar()->value() - (event->x() - panStartX_));
123 | verticalScrollBar()->setValue( verticalScrollBar()->value() - (event->y() - panStartY_));
124 | panStartX_ = event->x();
125 | panStartY_ = event->y();
126 | event->accept();
127 | return;
128 | }
129 | QGraphicsView::mouseMoveEvent(event);
130 | }
131 |
132 |
133 | void View::mouseReleaseEvent(QMouseEvent* event)
134 | {
135 | if (event->button() == Qt::MiddleButton)
136 | {
137 | pan_ = false;
138 | viewport()->setCursor(Qt::ArrowCursor); // use viewport to workaround a refresh bug
139 | event->accept();
140 | return;
141 | }
142 | QGraphicsView::mouseReleaseEvent(event);
143 | }
144 |
145 |
146 | void View::copy()
147 | {
148 | Scene* pScene = static_cast(scene());
149 |
150 | QList nodes;
151 | for (Node* node : pScene->nodes())
152 | {
153 | if (node->isSelected())
154 | {
155 | nodes << node;
156 | }
157 | }
158 |
159 | QList links;
160 | for (Link* link : pScene->links())
161 | {
162 | if (link->isSelected())
163 | {
164 | links << link;
165 | }
166 | }
167 |
168 |
169 | QDataStream stream(©_, QIODevice::WriteOnly | QIODevice::Truncate);
170 | stream << nodes.size();
171 | for (auto const& node : nodes)
172 | {
173 | stream << *node;
174 | }
175 |
176 | stream << links.size();
177 | for (auto const& link : links)
178 | {
179 | stream << static_cast(link->from()->parentItem())->name() << link->from()->name();
180 | stream << static_cast(link->to()->parentItem())->name() << link->to()->name();
181 | }
182 | }
183 |
184 |
185 | void View::paste()
186 | {
187 | Scene* pScene = static_cast(scene());
188 |
189 | // deselect and move to back all current items in the scene
190 | for (auto& item : pScene->selectedItems())
191 | {
192 | item->setSelected(false);
193 | item->setZValue(-1);
194 | }
195 |
196 | struct LinkData
197 | {
198 | QString from;
199 | QString output;
200 | QString to;
201 | QString input;
202 | };
203 |
204 | QDataStream stream(copy_);
205 | QList copies;
206 | QList links;
207 |
208 | // preload nodes and links
209 | int nodeCount;
210 | stream >> nodeCount;
211 | for (int i = 0; i < nodeCount; ++i)
212 | {
213 | Node* node = new Node();
214 | stream >> *node;
215 | copies << node;
216 | }
217 |
218 | int linkCount;
219 | stream >> linkCount;
220 | for (int i = 0; i < linkCount; ++i)
221 | {
222 | LinkData link;
223 | stream >> link.from >> link.output >> link.to >> link.input;
224 | links << link;
225 | }
226 |
227 |
228 | // compute unique name and insert nodes
229 | for (auto const& copy : copies)
230 | {
231 | for (auto const& node : pScene->nodes())
232 | {
233 | if (copy->name() == node->name())
234 | {
235 | QString oldName = copy->name();
236 | QString newName = copy->name() + "_" + QString::number(pScene->nodes().size());
237 | copy->setName(newName);
238 |
239 | for (auto& link : links)
240 | {
241 | if (link.from == oldName) { link.from = newName; }
242 | if (link.to == oldName) { link.to = newName; }
243 | qDebug() << "rename link !" << oldName << " " << newName;
244 | }
245 | break;
246 | }
247 | }
248 | copy->setSelected(true);
249 | copy->setZValue(1);
250 | pScene->addNode(copy);
251 | }
252 |
253 | // copy links
254 | for (auto const& link : links)
255 | {
256 | pScene->connect(link.from, link.output, link.to, link.input);
257 | }
258 |
259 | // get current curson position
260 | QPointF cursorScene = mapToScene(mapFromGlobal(QCursor::pos()));
261 | QPointF deltaToCursor;
262 |
263 | // get delta from cursor and pasted items
264 | QList items = pScene->selectedItems();
265 | QRectF boundingRectangle;
266 | for (auto& item : items)
267 | {
268 | boundingRectangle = boundingRectangle.united(item->sceneBoundingRect());
269 | }
270 |
271 | deltaToCursor = cursorScene - boundingRectangle.topLeft();
272 |
273 | // move pasted item to cursor position
274 | for (auto& item : items)
275 | {
276 | item->moveBy(deltaToCursor.x(), deltaToCursor.y());
277 | }
278 |
279 |
280 | // refresh
281 | pScene->onStageUpdated();
282 | }
283 |
284 |
285 | void View::undo()
286 | {
287 | Scene* pScene = static_cast(scene());
288 | pScene->undo();
289 | }
290 |
291 | void View::redo()
292 | {
293 | Scene* pScene = static_cast(scene());
294 | pScene->redo();
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/src/View.h:
--------------------------------------------------------------------------------
1 | #ifndef PIPER_VIEW_H
2 | #define PIPER_VIEW_H
3 |
4 | #include
5 | #include
6 |
7 |
8 | namespace piper
9 | {
10 | class CreatorPopup;
11 |
12 | class View : public QGraphicsView
13 | {
14 | public:
15 | View(QWidget* parent = nullptr);
16 | virtual ~View() = default;
17 |
18 | // Center the view on the items.
19 | void goHome();
20 |
21 | protected:
22 | void wheelEvent(QWheelEvent* event) override;
23 | void keyPressEvent(QKeyEvent * event) override;
24 |
25 | void mouseMoveEvent(QMouseEvent *event) override;
26 | void mousePressEvent(QMouseEvent *event) override;
27 | void mouseReleaseEvent(QMouseEvent *event) override;
28 |
29 | private:
30 | void copy();
31 | void paste();
32 | void undo();
33 | void redo();
34 |
35 | CreatorPopup* creator_;
36 | bool pan_{false};
37 | int panStartX_{};
38 | int panStartY_{};
39 |
40 | static QByteArray copy_;
41 | };
42 | }
43 |
44 | #endif
45 |
--------------------------------------------------------------------------------
/src/main.cc:
--------------------------------------------------------------------------------
1 | #include "MainEditor.h"
2 | #include "NodeCreator.h"
3 | #include "ThemeManager.h"
4 | #include
5 |
6 | using namespace piper;
7 |
8 | int main(int argc, char *argv[])
9 | {
10 | // Create node types for instance
11 | NodeCreator::instance().addItem(
12 | {
13 | "SinWave",
14 | "Sinus generator",
15 | "Example",
16 | "Generator",
17 | {
18 | {"output", "float", AttributeInfo::Type::output},
19 | {"amplitude", "float", AttributeInfo::Type::member},
20 | {"frequency", "float", AttributeInfo::Type::member},
21 | }
22 | });
23 |
24 | NodeCreator::instance().addItem(
25 | {
26 | "Random",
27 | "",
28 | "",
29 | "",
30 | {
31 | {"output", "float", AttributeInfo::Type::output},
32 | {"min", "float", AttributeInfo::Type::member},
33 | {"max", "float", AttributeInfo::Type::member},
34 | }
35 | });
36 |
37 | NodeCreator::instance().addItem(
38 | {
39 | "Add",
40 | "",
41 | "",
42 | "",
43 | {
44 | {"inputA", "float", AttributeInfo::Type::input},
45 | {"inputB", "float", AttributeInfo::Type::input},
46 | {"output", "float", AttributeInfo::Type::output},
47 | }
48 | });
49 |
50 | NodeCreator::instance().addItem(
51 | {
52 | "LowPass",
53 | "",
54 | "Example",
55 | "Filters",
56 | {
57 | {"inputA", "float", AttributeInfo::Type::input},
58 | {"output", "float", AttributeInfo::Type::output},
59 | {"Fc", "float", AttributeInfo::Type::member},
60 | }
61 | });
62 |
63 | NodeCreator::instance().addItem(
64 | {
65 | "cast",
66 | "Transform a float to integer \nWarning! take care of precision loss!",
67 | "",
68 | "",
69 | {
70 | {"input", "float", AttributeInfo::Type::input},
71 | {"output", "int", AttributeInfo::Type::output}
72 | }
73 | });
74 |
75 | NodeCreator::instance().addItem(
76 | {
77 | "cast",
78 | "",
79 | "",
80 | "",
81 | {
82 | {"input", "float", AttributeInfo::Type::input},
83 | {"output", "customType", AttributeInfo::Type::output}
84 | }
85 | });
86 |
87 | NodeCreator::instance().addItem(
88 | {
89 | "probe",
90 | "Record updates in the telemetry system",
91 | "",
92 | "",
93 | {
94 | {"input", "float", AttributeInfo::Type::input},
95 | }
96 | });
97 |
98 | NodeCreator::instance().addItem(
99 | {
100 | "probe",
101 | "Record updates in the telemetry system",
102 | "",
103 | "utilities",
104 | {
105 | {"input", "int", AttributeInfo::Type::input},
106 | }
107 | });
108 |
109 | NodeCreator::instance().addItem(
110 | {
111 | "probe",
112 | "Record updates in the telemetry system",
113 | "",
114 | "utilities",
115 | {
116 | {"input", "customType", AttributeInfo::Type::input},
117 | }
118 | });
119 |
120 | // Load theme
121 | if (not ThemeManager::instance().load("data/theme.json"))
122 | {
123 | return 1;
124 | }
125 |
126 | QApplication app(argc, argv);
127 | Q_INIT_RESOURCE(resources);
128 | MainEditor editor;
129 | editor.show();
130 |
131 | return app.exec();
132 | }
133 |
--------------------------------------------------------------------------------