1/*
2 *  SPDX-FileCopyrightText: 2014-2015 Andreas Cord-Landwehr <cordlandwehr@kde.org>
3 *
4 *  SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6
7import QtQuick 2.1
8import QtQuick.Controls 1.3
9import QtQuick.Layouts 1.0
10import QtQuick.Dialogs 1.0
11import QtQml.StateMachine 1.0 as DSM
12import org.kde.rocs.graphtheory 1.0
13
14Item {
15    id: root
16    width: 800
17    height: 600
18
19    focus: true
20
21    // element create/remove actions
22    signal createNode(real x, real y, int typeIndex);
23    signal createEdge(Node from, Node to, int typeIndex);
24    signal deleteNode(Node node);
25    signal deleteEdge(Edge edge);
26
27    // exec dialog signals
28    signal showNodePropertiesDialog(Node node);
29    signal showEdgePropertiesDialog(Edge edge);
30
31    Connections {
32        target: selectMoveAction
33        onToggled: {
34            dsmSelectMove.running = selectMoveAction.checked
35        }
36    }
37    Connections {
38        target: addEdgeAction
39        onToggled: {
40            dsmCreateEdge.running = addEdgeAction.checked
41        }
42    }
43
44    Keys.onPressed: {
45        switch(event.key) {
46        case Qt.Key_Escape:
47            scene.clearSelection();
48            event.accepted = true;
49            break;
50        case Qt.Key_Delete:
51            scene.deleteSelected()
52            event.accepted = true;
53            break;
54        case Qt.Key_A: //CTRL+A
55            if (event.modifiers & Qt.ControlModifier) {
56                scene.selectAll();
57                event.accepted = true;
58            }
59            break;
60        default:
61            break;
62        }
63    }
64
65    ExclusiveGroup {
66        id: editToolButton
67    }
68    ColumnLayout {
69        id: toolbar
70        ToolButton {
71            action: SelectMoveAction {
72                id: selectMoveAction
73                exclusiveGroup: editToolButton
74                checked: true
75            }
76        }
77        ToolButton {
78            action: AddNodeAction {
79                id: addNodeAction
80                exclusiveGroup: editToolButton
81            }
82        }
83        ToolButton {
84            action: AddEdgeAction {
85                id: addEdgeAction
86                exclusiveGroup: editToolButton
87                onCreateEdge: root.createEdge(from, to, edgeTypeSelector.currentIndex)
88            }
89        }
90        ToolButton {
91            action: DeleteAction {
92                id: deleteAction
93                exclusiveGroup: editToolButton
94            }
95        }
96    }
97    ScrollView {
98        id: sceneScrollView
99        width: root.width - toolbar.width - 20
100        height: root.height
101        anchors {
102            left: toolbar.right
103            leftMargin: 10
104        }
105
106
107        Rectangle { // white background
108            color: "white"
109            width: parent.width
110            height: parent.height
111        }
112
113        Item {
114            id: scene
115            width: sceneScrollView.width - 30
116            height: sceneScrollView.height - 20
117            z: -10 // must lie behind everything else
118            signal deleteSelected();
119            signal startMoveSelected();
120            signal finishMoveSelected();
121            signal updateSelection();
122            signal createEdgeUpdateFromNode();
123            signal createEdgeUpdateToNode();
124            function clearSelection()
125            {
126                selectionRect.from = Qt.point(0, 0)
127                selectionRect.to = Qt.point(0, 0)
128                updateSelection();
129            }
130            function setEdgeFromNode()
131            {
132                addEdgeAction.to = null
133                createEdgeUpdateFromNode();
134            }
135            function selectAll()
136            {
137                selectionRect.from = Qt.point(0, 0)
138                selectionRect.to = Qt.point(width,height)
139                updateSelection();
140            }
141
142            MouseArea {
143                id: sceneAction
144                anchors.fill: parent
145                z: -10 // must lie behind everything else
146
147                property variant lastMouseClicked: Qt.point(0, 0)
148                property variant lastMousePressed: Qt.point(0, 0)
149                property variant lastMouseReleased: Qt.point(0, 0)
150                property variant lastMousePosition: Qt.point(0, 0)
151                property bool nodePressed: false // if true, the current mouse-press event started at a node
152
153                onClicked: {
154                    lastMouseClicked = Qt.point(mouse.x, mouse.y)
155                    if (addNodeAction.checked) {
156                        mouse.accepted = true
157                        createNode(mouse.x, mouse.y, nodeTypeSelector.currentIndex);
158                        return
159                    }
160                }
161                onPressed: {
162                    lastMousePressed = Qt.point(mouse.x, mouse.y)
163                    lastMousePosition = Qt.point(mouse.x, mouse.y)
164                }
165                onPositionChanged: {
166                    lastMousePosition = Qt.point(mouse.x, mouse.y)
167                }
168                onReleased: {
169                    lastMouseReleased = Qt.point(mouse.x, mouse.y)
170                    sceneAction.nodePressed = false
171                }
172            }
173
174            SelectionRectangle {
175                id: selectionRect
176                visible: false
177            }
178
179            Line {
180                id: createLineMarker
181                visible: false
182                fromX: sceneAction.lastMousePressed.x
183                fromY: sceneAction.lastMousePressed.y
184                toX: sceneAction.lastMousePosition.x
185                toY: sceneAction.lastMousePosition.y
186            }
187
188            Repeater {
189                model: edgeModel
190                EdgeItem {
191                    id: edgeItem
192                    edge: model.dataRole
193                    z: -1 // edges must be below nodes
194
195                    EdgePropertyItem {
196                        anchors.centerIn: parent
197                        edge: model.dataRole
198                    }
199
200                    MouseArea {
201                        anchors.fill: parent
202                        propagateComposedEvents: true
203                        onPressed: {
204                            if (deleteAction.checked) {
205                                deleteEdge(edge)
206                                mouse.accepted = true
207                            }
208                        }
209                        onDoubleClicked: {
210                            showEdgePropertiesDialog(edgeItem.edge);
211                        }
212                    }
213                }
214            }
215
216            Repeater {
217                model: nodeModel
218                NodeItem {
219                    id: nodeItem
220                    node: model.dataRole
221                    highlighted: addEdgeAction.from == node || addEdgeAction.to == node
222                    property bool __modifyingPosition: false
223                    property variant __moveStartedPosition: Qt.point(0, 0)
224                    Connections {
225                        target: selectionRect
226                        onChanged: {
227                            if (selectMoveAction.checked && selectionRect.contains(x, y)) {
228                                highlighted = true
229                            } else {
230                                highlighted = false
231                            }
232                        }
233                    }
234                    Connections {
235                        target: scene
236                        onUpdateSelection: {
237                            if (selectionRect.contains(x, y)) {
238                                highlighted = true
239                            } else {
240                                highlighted = false
241                            }
242                        }
243                        onDeleteSelected: {
244                            if (highlighted) {
245                                deleteNode(node)
246                            }
247                        }
248                        onStartMoveSelected: {
249                            if (!highlighted) {
250                                return
251                            }
252                            __moveStartedPosition.x = node.x
253                            __moveStartedPosition.y = node.y
254                            node.x = Qt.binding(function() { return __moveStartedPosition.x + sceneAction.lastMousePosition.x - sceneAction.lastMousePressed.x })
255                            node.y = Qt.binding(function() { return __moveStartedPosition.y + sceneAction.lastMousePosition.y - sceneAction.lastMousePressed.y })
256                        }
257                        onFinishMoveSelected: {
258                            if (!highlighted) {
259                                return
260                            }
261                            node.x = __moveStartedPosition.x + sceneAction.lastMousePosition.x - sceneAction.lastMousePressed.x
262                            node.y = __moveStartedPosition.y + sceneAction.lastMousePosition.y - sceneAction.lastMousePressed.y
263                            __moveStartedPosition.x = 0
264                            __moveStartedPosition.y = 0
265                        }
266                        onCreateEdgeUpdateFromNode: {
267                            if (nodeItem.contains(Qt.point(sceneAction.lastMousePressed.x, sceneAction.lastMousePressed.y))) {
268                                node.highlighted = true
269                                addEdgeAction.from = node
270                            }
271                        }
272                        onCreateEdgeUpdateToNode: {
273                            if (nodeItem.contains(Qt.point(sceneAction.lastMouseReleased.x, sceneAction.lastMouseReleased.y))) {
274                                node.highlighted = true
275                                addEdgeAction.to = node
276                            }
277                        }
278                    }
279                    onXChanged: {
280                        if (x < 10) {
281                            nodeItem.__modifyingPosition = true;
282                            var delta = Math.max((10 - x), 10)
283                            scene.width += delta;
284                            nodeItem.__modifyingPosition = false;
285                            return;
286                        }
287                        if (x + width + 10 > scene.width) {
288                            nodeItem.__modifyingPosition = true;
289                            var delta = Math.max(scene.width - (x + width) + 10, 10);
290                            scene.width += delta;
291                            nodeItem.__modifyingPosition = false;
292                            return;
293                        }
294                    }
295                    onYChanged: {
296                        if (y < 10) {
297                            nodeItem.__modifyingPosition = true;
298                            var delta = (10 - y)
299                            scene.height += delta;
300                            nodeItem.__modifyingPosition = false;
301                            return;
302                        }
303                        if (y + height + 10 > scene.height) {
304                            nodeItem.__modifyingPosition = true;
305                            var delta = Math.max(scene.height - (y + height) + 10, 10);
306                            scene.height += delta;
307                            nodeItem.__modifyingPosition = false;
308                            return;
309                        }
310                    }
311                    NodePropertyItem {
312                        anchors.centerIn: parent
313                        node: model.dataRole
314                    }
315
316                    Drag.active: dragArea.drag.active
317                    MouseArea {
318                        id: dragArea
319                        anchors.fill: parent
320                        propagateComposedEvents: true
321                        drag.target: { // only enable drag when move/select checked
322                            selectMoveAction.checked ? parent : undefined
323                        }
324                        Loader {
325                            id: nodeDialogLoader
326                        }
327                        onDoubleClicked: {
328                            showNodePropertiesDialog(nodeItem.node);
329                            mouse.accepted = true
330                        }
331                        onPressed: {
332                            // never handle undefined actions signals
333                            if (!(selectMoveAction.checked || deleteAction.checked)) {
334                                mouse.accepted = false
335                                return
336                            }
337                            if (deleteAction.checked) {
338                                deleteNode(nodeItem.node)
339                                mouse.accepted = true
340                                return
341                            }
342                            // single-node move action: directly handle it
343                            sceneAction.nodePressed = true
344                            if (selectMoveAction.checked && !nodeItem.highlighted) {
345                                scene.clearSelection()
346                                nodeItem.highlighted = true
347                                mouse.accepted = true
348                                return
349                            }
350                            // multi-node move action: gets handled by state-machine
351                            if (selectMoveAction.checked && nodeItem.highlighted) {
352                                mouse.accepted = false
353                                return
354                            }
355                        }
356                        onReleased: {
357                            sceneAction.nodePressed = false
358                        }
359                    }
360                }
361            }
362        }
363    }
364
365    RowLayout {
366        id: extraToolbarCreateNode
367        visible: addNodeAction.checked
368        anchors {
369            top: sceneScrollView.top
370            right:sceneScrollView.right
371            topMargin: 10
372            rightMargin: 5
373        }
374        ComboBox {
375            id: nodeTypeSelector
376            model: nodeTypeModel
377            textRole: "titleRole"
378        }
379    }
380    RowLayout {
381        id: extraToolbarCreateEdge
382        visible: addEdgeAction.checked
383        anchors {
384            top: sceneScrollView.top
385            right:sceneScrollView.right
386            topMargin: 10
387            rightMargin: 5
388        }
389        ComboBox {
390            id: edgeTypeSelector
391            model: edgeTypeModel
392            textRole: "titleRole"
393        }
394    }
395
396    // state machine only for select/move
397    DSM.StateMachine {
398        id: dsmSelectMove
399        initialState: smStateIdle
400        running: true
401        DSM.State {
402            id: smStateIdle
403            DSM.SignalTransition {
404                targetState: smStateMoving
405                signal: sceneAction.onPressed
406                guard: sceneAction.nodePressed
407            }
408            DSM.SignalTransition {
409                targetState: smStateSelecting
410                signal: sceneAction.onPressed
411                guard: !sceneAction.nodePressed
412            }
413            DSM.SignalTransition {
414                signal: sceneAction.onClicked
415                onTriggered: {
416                    scene.clearSelection
417                }
418            }
419        }
420        DSM.State {
421            id: smStateSelecting
422            DSM.SignalTransition {
423                signal: sceneAction.onPositionChanged
424                onTriggered: {
425                    selectionRect.visible = true
426                    selectionRect.to = sceneAction.lastMousePosition
427                }
428            }
429            DSM.SignalTransition {
430                targetState: smStateIdle
431                signal: sceneAction.onReleased
432            }
433            onEntered: {
434                scene.clearSelection()
435                selectionRect.from = sceneAction.lastMousePressed
436            }
437            onExited: {
438                selectionRect.visible = false
439            }
440        }
441        DSM.State {
442            id: smStateMoving
443            DSM.SignalTransition {
444                targetState: smStateIdle
445                signal: sceneAction.onReleased
446            }
447            onEntered: {
448                scene.startMoveSelected();
449            }
450            onExited: {
451                scene.finishMoveSelected()
452            }
453        }
454    }
455
456    // state machine solely for edge creation
457    DSM.StateMachine {
458        id: dsmCreateEdge
459        initialState: ceStateIdle
460        running: false
461        DSM.State {
462            id: ceStateIdle
463            DSM.SignalTransition {
464                targetState: ceStateCreateEdgeTo
465                signal: sceneAction.onPressed
466            }
467            onExited: {
468                addEdgeAction.to = null
469                scene.createEdgeUpdateFromNode()
470            }
471        }
472        DSM.State {
473            id: ceStateCreateEdgeTo
474            DSM.SignalTransition {
475                targetState: ceStateIdle
476                signal: sceneAction.onReleased
477            }
478            onEntered: {
479                createLineMarker.visible = true
480            }
481            onExited: {
482                scene.createEdgeUpdateToNode()
483                addEdgeAction.apply()
484                createLineMarker.visible = false
485            }
486        }
487    }
488}
489