def onFileOpen(self): #dir = QFileDialog.getExistingDirectory(None, 'Select a directory', "/desktop", QFileDialog.ShowDirsOnl | QFileDialog.DontResolveSymlinks) # self.getFileDialogDirectory() #TODO: скопировать в строчку ниже fnames, filter = QFileDialog.getOpenFileNames( self, 'Open graph from file', '/Users/ouroboros/Desktop/', self.getFileDialogFilter()) try: for fname in fnames: if fname: existing = self.findMdiChild(fname) if existing: self.mdiArea.setActiveSubWindow(existing) else: # we need to create new subWindow and open the file nodeeditor = CalculatorSubWindow() if nodeeditor.fileLoad(fname): self.statusBar().showMessage( "File %s loaded" % fname, 5000) nodeeditor.setTitle() subwnd = self.createMdiChild(nodeeditor) subwnd.show() self.active_flag = True self.updateToolButtons() self.subwindow_counter += 1 else: nodeeditor.close() except Exception as e: dumpException(e)
def onDrop(self, event): if event.mimeData().hasFormat(LISTBOX_MIMETYPE): eventData = event.mimeData().data(LISTBOX_MIMETYPE) dataStream = QDataStream(eventData, QIODevice.ReadOnly) pixmap = QPixmap() dataStream >> pixmap op_code = dataStream.readInt() text = dataStream.readQString() mouse_position = event.pos() scene_position = self.scene.grScene.views()[0].mapToScene(mouse_position) if DEBUG: print("GOT DROP: [%d] '%s'" % (op_code, text), "mouse:", mouse_position, "scene:", scene_position) try: node = get_class_from_opcode(op_code)(self.scene) node.setPos(scene_position.x(), scene_position.y()) self.scene.history.storeHistory("Created node %s" % node.__class__.__name__) except Exception as e: dumpException(e) event.setDropAction(Qt.MoveAction) event.accept() else: # print(" ... drop ignored, not requested format '%s'" % LISTBOX_MIMETYPE) event.ignore()
def startDrag(self, *args, **kwargs): try: item = self.currentItem() op_code = item.data(Qt.UserRole + 1) pixmap = QPixmap(item.data(Qt.UserRole)) itemData = QByteArray() dataStream = QDataStream(itemData, QIODevice.WriteOnly) dataStream << pixmap dataStream.writeInt(op_code) dataStream.writeQString(item.text()) mimeData = QMimeData() mimeData.setData(LISTBOX_MIMETYPE, itemData) drag = QDrag(self) drag.setMimeData(mimeData) drag.setHotSpot(QPoint(pixmap.width() / 2, pixmap.height() / 2)) drag.setPixmap(pixmap) drag.exec_(Qt.MoveAction) except Exception as e: dumpException(e)
def remove(self, silent_for_socket: 'Socket' = None, silent=False): """ Safely remove this Edge. Removes `Graphics Edge` from the ``QGraphicsScene`` and it's reference to all GC to clean it up. Notifies vertices previously connected :class:`~nodeeditor.node_node.Node` (s) about this event. Triggers Nodes': - :py:meth:`~nodeeditor.node_node.Node.onEdgeConnectionChanged` - :py:meth:`~nodeeditor.node_node.Node.onInputChanged` :param silent_for_socket: :class:`~nodeeditor.node_socket.Socket` of a :class:`~nodeeditor.node_node.Node` which won't be notified, when this ``Edge`` is going to be removed :type silent_for_socket: :class:`~nodeeditor.node_socket.Socket` :param silent: ``True`` if no events should be triggered during removing :type silent: ``bool`` """ old_sockets = [self.start_socket, self.end_socket] # ugly hack, since I noticed that even when you remove grEdge from scene, # sometimes it stays there! How dare you Qt! if DEBUG: print(" - hide grEdge") #self.node.edges_in_self.discard(self.id) self.grEdge.hide() if DEBUG: print(" - remove grEdge", self.grEdge) self.scene.grScene.removeItem(self.grEdge) if DEBUG: print(" grEdge:", self.grEdge) self.scene.grScene.update() if DEBUG: print("# Removing Edge", self) if DEBUG: print(" - remove edge from all sockets") self.remove_from_sockets() if DEBUG: print(" - remove edge from scene") try: self.scene.removeEdge(self) except ValueError: pass if DEBUG: print(" - everything is done.") try: # notify vertices from old sockets for socket in old_sockets: if socket and socket.node: if silent: continue if silent_for_socket is not None and socket == silent_for_socket: # if we requested silence for Socket and it's this one, skip notifications continue # notify Socket's Node socket.node.onEdgeConnectionChanged(self) #if socket.is_input: socket.node.onInputChanged(socket) except Exception as e: dumpException(e)
def deserialize(self, data, hashmap={}): res = super().deserialize(data, hashmap) try: value = data['value'] self.edit.setText(value) return True & res except Exception as e: dumpException(e) return res
def edgeDragEnd(self, item: 'QGraphicsItem'): """Code handling the end of dragging an `Edge` operation. In this code return True if skip the rest of the mouse event processing :param item: Item in the `Graphics Scene` where we ended dragging an `Edge` :type item: ``QGraphicsItem`` """ self.mode = MODE_NOOP if DEBUG: print('View::edgeDragEnd ~ End dragging edge') if self.drag_edge is not None: self.drag_edge.remove( silent=True) # don't notify sockets about removing drag_edge self.drag_edge = None try: if isinstance(item, QDMGraphicsSocket): if item.socket != self.drag_start_socket: # if we released dragging on a socket (other then the beginning socket) ## First remove old edges / send notifications for socket in (item.socket, self.drag_start_socket): if not socket.is_multi_edges: if socket.is_input: # print("removing SILENTLY edges from input socket (is_input and !is_multi_edges) [DragStart]:", item.socket.edges) socket.removeAllEdges(silent=True) else: socket.removeAllEdges(silent=False) ## Create new Edge if item.socket.is_input: new_edge = Edge(self.grScene.scene, self.drag_start_socket, item.socket, edge_type=EDGE_TYPE_BEZIER) else: print("wrong end socket") if DEBUG: print("View::edgeDragEnd ~ created new edge:", new_edge, "connecting", new_edge.start_socket, "<-->", new_edge.end_socket) ## Send notifications for the new edge # for socket in [self.drag_start_socket, item.socket]: # @TODO: Add possibility (ie when an input edge was replaced) to be silent and don't trigger change # socket.node.onEdgeConnectionChanged(new_edge) #if socket.is_input: socket.node.onInputChanged(socket) self.grScene.scene.history.storeHistory( "Created new edge by dragging", setModified=True) return True except Exception as e: dumpException(e) if DEBUG: print('View::edgeDragEnd ~ everything done.') return False
def onFileNew(self): try: subwnd = self.createMdiChild() subwnd.widget().fileNew() subwnd.show() self.active_flag = False self.updateToolButtons() self.subwindow_counter += 1 except Exception as e: dumpException(e)
def leftMouseButtonRelease(self, event: QMouseEvent): """When Left mouse button was released""" # get item which we release mouse button on item = self.getItemAtClick(event) try: # logic if hasattr(item, "node") or isinstance( item, QDMGraphicsEdge) or item is None: if event.modifiers() & Qt.ShiftModifier: event.ignore() fakeEvent = QMouseEvent( event.type(), event.localPos(), event.screenPos(), Qt.LeftButton, Qt.NoButton, event.modifiers() | Qt.ControlModifier) super().mouseReleaseEvent(fakeEvent) return if self.mode == MODE_EDGE_DRAG: if self.distanceBetweenClickAndReleaseIsOff(event): res = self.edgeDragEnd(item) if res: return if self.mode == MODE_EDGE_CUT: self.cutIntersectingEdges() self.cutline.line_points = [] self.cutline.update() QApplication.setOverrideCursor(Qt.ArrowCursor) self.mode = MODE_NOOP return if self.rubberBandDraggingRectangle: self.rubberBandDraggingRectangle = False current_selected_items = self.grScene.selectedItems() if current_selected_items != self.grScene.scene._last_selected_items: if current_selected_items == []: self.grScene.itemsDeselected.emit() else: self.grScene.itemSelected.emit() self.grScene.scene._last_selected_items = current_selected_items return # otherwise deselect everything if item is None: self.grScene.itemsDeselected.emit() except: dumpException() super().mouseReleaseEvent(event)
def updateEditMenu(self): try: # print("update Edit Menu") active = self.getCurrentNodeEditorWidget() hasMdiChild = (active is not None) self.actPaste.setEnabled(hasMdiChild) self.actCut.setEnabled(hasMdiChild and active.hasSelectedItems()) self.actCopy.setEnabled(hasMdiChild and active.hasSelectedItems()) self.actDelete.setEnabled(hasMdiChild and active.hasSelectedItems()) self.actUndo.setEnabled(hasMdiChild and active.canUndo()) self.actRedo.setEnabled(hasMdiChild and active.canRedo()) except Exception as e: dumpException(e)
def edgeDragStart(self, item: 'QGraphicsItem'): """Code handling the start of dragging an `Edge` operation""" try: if DEBUG: print('View::edgeDragStart ~ Start dragging edge') if DEBUG: print('View::edgeDragStart ~ assign Start Socket to:', item.socket) self.drag_start_socket = item.socket if self.drag_start_socket.is_input == False: self.drag_edge = Edge(self.grScene.scene, item.socket, None, EDGE_TYPE_BEZIER) if DEBUG: print('View::edgeDragStart ~ dragEdge:', self.drag_edge) else: self.drag_edge = None except Exception as e: dumpException(e)
def getInput(self, index: int = 0) -> ['Node', None]: """ Get the **first** `Node` connected to the Input specified by `index` :param index: Order number of the `Input Socket` :type index: ``int`` :return: :class:`~nodeeditor.node_node.Node` which is connected to the specified `Input` or ``None`` if there is no connection of index is out of range :rtype: :class:`~nodeeditor.node_node.Node` or ``None`` """ try: input_socket = self.inputs[index] if len(input_socket.edges) == 0: return None connecting_edge = input_socket.edges[0] other_socket = connecting_edge.getOtherSocket(self.inputs[index]) return other_socket.node except Exception as e: dumpException(e) return None
def contextMenuEvent(self, event): try: item = self.scene.getItemAt(event.pos()) if DEBUG_CONTEXT: print(item) if type(item) == QGraphicsProxyWidget: item = item.widget() if hasattr(item, 'node') or hasattr(item, 'socket'): self.handleNodeContextMenu(event) elif hasattr(item, 'edge'): self.handleEdgeContextMenu(event) #elif item is None: else: self.handleNewNodeContextMenu(event) return super().contextMenuEvent(event) except Exception as e: dumpException(e)
def getInputWithSocketIndex(self, index: int = 0) -> ('Node', int): """ Get the **first** `Node` connected to the Input specified by `index` and the connection `Socket` :param index: Order number of the `Input Socket` :type index: ``int`` :return: Tuple containing :class:`~nodeeditor.node_node.Node` and :class:`~nodeeditor.node_socket.Socket` which is connected to the specified `Input` or ``None`` if there is no connection of index is out of range :rtype: (:class:`~nodeeditor.node_node.Node`, int) """ try: edge = self.inputs[index].edges[0] socket = edge.getOtherSocket(self.inputs[index]) return socket.node, socket.index except IndexError: # print("EXC: Trying to get input with socket index %d, but none is attached to" % index, self) return None, None except Exception as e: dumpException(e) return None, None
def loadFromFile(self, filename: str): """ Load `Scene` from a file on disk :param filename: from what file to load the `Scene` :type filename: ``str`` :raises: :class:`~nodeeditor.node_scene.InvalidFile` if there was an error decoding JSON file """ with open(filename, "r") as file: raw_data = file.read() try: data = json.loads(raw_data, encoding='utf-8') self.deserialize(data) self.has_been_modified = False except json.JSONDecodeError: raise InvalidFile("%s is not a valid JSON file" % os.path.basename(filename)) except Exception as e: dumpException(e)
def getInputWithSocket(self, index: int = 0 ) -> [('Node', 'Socket'), (None, None)]: """ Get the **first** `Node` connected to the Input specified by `index` and the connection `Socket` :param index: Order number of the `Input Socket` :type index: ``int`` :return: Tuple containing :class:`~nodeeditor.node_node.Node` and :class:`~nodeeditor.node_socket.Socket` which is connected to the specified `Input` or ``None`` if there is no connection of index is out of range :rtype: (:class:`~nodeeditor.node_node.Node`, :class:`~nodeeditor.node_socket.Socket`) """ try: input_socket = self.inputs[index] if len(input_socket.edges) == 0: return None, None connecting_edge = input_socket.edges[0] other_socket = connecting_edge.getOtherSocket(self.inputs[index]) return other_socket.node, other_socket except Exception as e: dumpException(e) return None, None
def deserialize(self, data: dict, hashmap: dict = {}, restore_id: bool = True) -> bool: try: if restore_id: self.id = data['id'] hashmap[data['id']] = self self.setPos(data['pos_x'], data['pos_y']) #self.title = data['title'] data['inputs'].sort(key=lambda socket: socket['index'] + socket[ 'position'] * 10000) data['outputs'].sort(key=lambda socket: socket['index'] + socket[ 'position'] * 10000) num_inputs = len(data['inputs']) num_outputs = len(data['outputs']) # print("> deserialize node, num inputs:", num_inputs, "num outputs:", num_outputs) # pp(data) # possible way to do it is reuse existing sockets... # dont create new ones if not necessary for socket_data in data['inputs']: found = None for socket in self.inputs: # print("\t", socket, socket.index, "=?", socket_data['index']) if socket.index == socket_data['index']: found = socket break if found is None: # print("deserialization of socket data has not found input socket with index:", socket_data['index']) # print("actual socket data:", socket_data) # we can create new socket for this found = self.__class__.Socket_class( node=self, index=socket_data['index'], position=socket_data['position'], socket_type=socket_data['socket_type'], count_on_this_node_side=num_inputs, is_input=True) self.inputs.append( found) # append newly created input to the list found.deserialize(socket_data, hashmap, restore_id) for socket_data in data['outputs']: found = None for socket in self.outputs: # print("\t", socket, socket.index, "=?", socket_data['index']) if socket.index == socket_data['index']: found = socket break if found is None: # print("deserialization of socket data has not found output socket with index:", socket_data['index']) # print("actual socket data:", socket_data) # we can create new socket for this found = self.__class__.Socket_class( node=self, index=socket_data['index'], position=socket_data['position'], socket_type=socket_data['socket_type'], count_on_this_node_side=num_outputs, is_input=False) self.outputs.append( found) # append newly created output to the list found.deserialize(socket_data, hashmap, restore_id) except Exception as e: dumpException(e) for curve in data['curves_in_self']: self.edges_in_self.add(curve) # also deseralize the content of the node # so far the rest was ok, now as last step the content... if isinstance(self.content, Serializable): res = self.content.deserialize(data['name'], hashmap) return res return True