class QNEMainWindow(QMainWindow):
    def __init__(self, parent):
        super(QNEMainWindow, self).__init__(parent)

        quitAct = QAction("&Quit", self, shortcut="Ctrl+Q",
            statusTip="Exit the application", triggered=self.close)

        addAct = QAction("&Add", self, statusTip="Add a block", triggered=self.addBlock)

        fileMenu = self.menuBar().addMenu("&File")
        fileMenu.addAction(addAct)
        fileMenu.addSeparator()
        fileMenu.addAction(quitAct)

        self.setWindowTitle("Node Editor")

        self.scene = QGraphicsScene(self)
        self.view = QGraphicsView(self)
        self.view.setScene(self.scene)
        self.view.setRenderHint(QPainter.Antialiasing)
        self.setCentralWidget(self.view)

        self.nodesEditor = QNodesEditor(self)
        self.nodesEditor.install(self.scene)

        block = QNEBlock(None)
        self.scene.addItem(block)
        block.addPort("test", 0, QNEPort.NamePort)
        block.addPort("TestBlock", 0, QNEPort.TypePort)
        block.addInputPort("in1");
        block.addInputPort("in2");
        block.addInputPort("in3");
        block.addOutputPort("out1");
        block.addOutputPort("out2");
        block.addOutputPort("out3");

        block = block.clone()
        block.setPos(150,0)

        block = block.clone()
        block.setPos(150,150)


    def addBlock(self):
        import random
        import math

        block = QNEBlock(None)

        self.scene.addItem(block)
        names = ["Vin", "Voutsadfasdf", "Imin", "Imax", "mul", "add", "sub", "div", "Conv", "FFT"]
        for i in range(0,math.floor(random.uniform(3,8))):
            block.addPort(random.choice(names), random.random()>0.5)
        block.setPos(self.view.sceneRect().center().toPoint())
Exemple #2
0
class mainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("SimulationZ")
        self.setMinimumSize(700,500)
        self.workspaceLayout = QGridLayout()
        self.workspace = QTabWidget()
        self.workspace.setTabsClosable(True)
        
        self.graphScene = QGraphicsScene()
        self.sceneView = QGraphicsView()
        self.sceneView.setScene(self.graphScene)
        
        self.Editor = Editor(self.sceneView)
        
        self.menuBar = menuBar.menuBar(self)
        self.setMenuBar(self.menuBar)

        self.mainArea = Layout.layout(self.Editor,self.sceneView)
        self.setCentralWidget(self.mainArea)
        
        self.toolBar = self.mainArea.childAt(1,0)
        
    def exitFile(self):     
        if self.i==0:         
            userInfo =QMessageBox.question(self,self.language.Exit,self.language.RUSURE,QMessageBox.Yes | QMessageBox.No)
            if userInfo == QMessageBox.Yes: 
                self.close()
            else:
                pass
        else:
            userInfo =QMessageBox.question(self,self.language.Exit,self.language.OTab,QMessageBox.Yes | QMessageBox.No)
            if userInfo == QMessageBox.Yes: 
                self.close()
            else:
                pass
Exemple #3
0
class QNEMainWindow(QMainWindow):
    def __init__(self, parent):
        super(QNEMainWindow, self).__init__(parent)

        self.logger = logging.getLogger("zne")
        self.logger.setLevel(logging.DEBUG)

        self.setMinimumSize(560,360)
        self.setWindowTitle("ZOCP Node Editor")
        self.setWindowIcon(QIcon('assets/icon.png'))

        self.scene = QGraphicsScene(self)
        self.view = QGraphicsView(self)
        self.view.setScene(self.scene)
        self.setCentralWidget(self.view)

        self.nodesEditor = QNodesEditor(self, self.scene, self.view)

        self.nodesEditor.onAddConnection = self.onAddConnection
        self.nodesEditor.onRemoveConnection = self.onRemoveConnection
        self.nodesEditor.onBlockMoved = self.onBlockMoved

        self.scale = 1
        self.installActions()

        self.initZOCP()

        self.nodes = {}
        self.pendingSubscribers = {}

        QTimer.singleShot(250, lambda: self.scene.invalidate())


    def closeEvent(self, *args):
        self.zocp.stop()


    def installActions(self):
        quitAct = QAction("&Quit", self, shortcut="Ctrl+Q",
            statusTip="Exit the application", triggered=self.close)
        if zconfigmanager_found:
            openAct = QAction("&Open...", self, shortcut="Ctrl+O",
                statusTip="Restore the network from a saved description", triggered=self.readNetwork)
            saveAct = QAction("&Save...", self, shortcut="Ctrl+S",
                statusTip="Write a description of the network to disc", triggered=self.writeNetwork)

        fileMenu = self.menuBar().addMenu("&File")
        if zconfigmanager_found:
            fileMenu.addAction(openAct)
            fileMenu.addAction(saveAct)
            fileMenu.addSeparator()
        fileMenu.addAction(quitAct)

        # for shortcuts
        self.view.addAction(quitAct)

        selectAllAct = QAction("Select &All", self, shortcut="Ctrl+A",
            triggered=self.nodesEditor.selectAll)
        selectNoneAct = QAction("Select &None", self, shortcut="Ctrl+D",
            triggered=self.nodesEditor.selectNone)
        selectInverseAct = QAction("Select &Inverse", self, shortcut="Ctrl+I",
            triggered=self.nodesEditor.selectInverse)
        deleteSelectedAct = QAction("&Delete Selected", self, shortcut="Del",
            triggered=self.nodesEditor.deleteSelected)

        editMenu = self.menuBar().addMenu("&Edit")
        editMenu.addAction(selectAllAct)
        editMenu.addAction(selectNoneAct)
        editMenu.addAction(selectInverseAct)
        editMenu.addSeparator()
        editMenu.addAction(deleteSelectedAct)

        self.view.addAction(selectAllAct)
        self.view.addAction(selectNoneAct)
        self.view.addAction(selectInverseAct)
        self.view.addAction(deleteSelectedAct)

        zoomInAct = QAction("Zoom &In", self, shortcut="Ctrl++",
            triggered=self.zoomIn)
        zoomOutAct = QAction("Zoom &Out", self, shortcut="Ctrl+-",
            triggered=self.zoomOut)
        zoomResetAct = QAction("&Reset Zoom", self, shortcut="Ctrl+0",
            triggered=self.zoomReset)

        viewMenu = self.menuBar().addMenu("&View")
        viewMenu.addAction(zoomInAct)
        viewMenu.addAction(zoomOutAct)
        viewMenu.addSeparator()
        viewMenu.addAction(zoomResetAct)

        self.view.addAction(zoomInAct)
        self.view.addAction(zoomOutAct)
        self.view.addAction(zoomResetAct)

        aboutAct = QAction("&About", self,
             triggered=self.about)

        helpMenu = self.menuBar().addMenu("&Help")
        helpMenu.addAction(aboutAct)

        self.view.addAction(aboutAct)


    def writeNetwork(self):
        fileName, filter = QFileDialog.getSaveFileName(self,
                                                       caption="Save as",
                                                       filter="ZOCP (*.zocp);;JSON (*.json)",
                                                       selectedFilter="ZOCP (*.zocp)")
        if fileName:
            # setup ZOCP node, and run it for some time to discover
            # the current network
            configManager = ZConfigManagerNode("ConfigManager@%s" % socket.gethostname())
            configManager.discover(0.5)

            # write network description to file
            configManager.write(fileName)

            # shut down ZOCP node
            configManager.stop()
            configManager = None


    def readNetwork(self):
        fileName, filter = QFileDialog.getOpenFileName(self,
                                                       caption="Open",
                                                       filter="All files (*.*);;ZOCP (*.zocp);;JSON (*.json)",
                                                       selectedFilter="ZOCP (*.zocp)")
        if fileName:
            # setup ZOCP node, and run it for some time to discover
            # the current network
            configManager = ZConfigManagerNode("ConfigManager@%s" % socket.gethostname())
            configManager.discover(0.5)

            # write network description to file
            configManager.read(fileName)

            # shut down ZOCP node
            configManager.stop()
            configManager = None


    def zoomIn(self):
        if self.scale < 4:
            self.scale *= 1.2
            self.view.scale(1.2, 1.2)


    def zoomOut(self):
        if self.scale > 0.1:
            self.scale /= 1.2
            self.view.scale(1/1.2, 1/1.2)


    def zoomReset(self):
        self.scale = 1
        self.view.setTransform(QTransform())


    def about(self):
        QMessageBox.about(self, "About ZOCP Node Editor",
            "<p>A monitor/editor for ZOCP nodes, implemented in PySide"
             "(Python/Qt4).</p><p>ZOCP is the Z25 Orchestration Control "
             "Protocol, currently in development at "
             "<a href='http://z25.org'>z25.org</a></p>")


    #########################################
    # Node editor callbacks
    #########################################
    def onAddConnection(self, connection, fromPort, toPort):
        fromBlock = fromPort.block()
        toBlock = toPort.block()

        emitter = fromPort.portName()
        emit_peer = fromBlock.uuid()
        receiver = toPort.portName()
        recv_peer = toBlock.uuid()

        self.zocp.signal_subscribe(recv_peer, receiver, emit_peer, emitter)

        self.logger.debug("added subscription from %s on %s to %s on %s" %
               (receiver, fromBlock.name(), emitter, toBlock.name()))


    def onRemoveConnection(self, connection, fromPort, toPort):
        fromBlock = fromPort.block()
        toBlock = toPort.block()

        emitter = fromPort.portName()
        emit_peer = fromBlock.uuid()
        receiver = toPort.portName()
        recv_peer = toBlock.uuid()

        self.zocp.signal_unsubscribe(recv_peer, receiver, emit_peer, emitter)

        self.logger.debug("removed subscription from %s on %s to %s on %s" %
               (receiver, fromBlock.name(), emitter, toBlock.name()))


    def onBlockMoved(self, block):
        pos = block.pos()
        peer = block.uuid()
        self.zocp.peer_set(peer, {"_zne_position": [pos.x(), pos.y()]})


    def onChangeValue(self, block, port, value):
        self.logger.debug("block %s port %s changed to %s" % (block.name(), port.portName(), value))
        peer = block.uuid()
        portName = port.portName()
        capability = self.zocp.peers_capabilities[peer][portName]
        typeHint = capability["typeHint"]
        validValue = True
        if typeHint == "int":
            try:
                value = int(float(value))
            except:
                validValue = False
        elif typeHint == "flt":
            try:
                value = float(value.strip())
            except:
                validValue = False
        elif typeHint == "percent":
            try:
                value = float(value.strip())
            except:
                validValue = False
        elif typeHint == "bool":
            value = (value.strip().lower() in ["true", "yes", "1"])
        elif typeHint == "string":
            pass
        elif typeHint.startswith("vec" ) and typeHint.endswith("f") and len(typeHint) == 5:
            try:
                value = [float(num) for num in ((value.strip())[1:-1]).split(",")]
            except:
                validValue = False

            if validValue:
                if len(value) != int(typeHint[3]):
                    validValue = False

        if validValue:
            self.zocp.peer_set(peer, {portName: {"value": value}})
            port.setValue(str(value))
        else:
            port.setValue(str(capability["value"]))



    #########################################
    # ZOCP implementation
    #########################################
    def initZOCP(self):
        self.zocp = ZOCP("ZOCP Node Editor@%s" % socket.gethostname())
        self.notifier = QSocketNotifier(
            self.zocp.inbox.getsockopt(zmq.FD),
            QSocketNotifier.Read)
        self.notifier.setEnabled(True)
        self.notifier.activated.connect(self.onZOCPEvent)
        self.zocp.on_peer_enter = self.onPeerEnter
        self.zocp.on_peer_exit = self.onPeerExit
        self.zocp.on_peer_modified = self.onPeerModified
        self.zocp.on_peer_signaled = self.onPeerSignaled
        self.zocp.start()

        zl = logging.getLogger("zocp")
        zl.setLevel(logging.INFO)


    def onZOCPEvent(self):
        self.zocp.run_once(0)


    def onPeerEnter(self, peer, name, *args, **kwargs):
        # Subscribe to any and all value changes
        self.zocp.signal_subscribe(self.zocp.uuid(), None, peer, None)

        # Add named block; ports are not known at this point
        block = QNEBlock(None)
        self.scene.addItem(block)
        block.setNodeEditor(self)
        block.setName(name)
        block.setUuid(peer)
        block.addPort(name, False, False, QNEPort.NamePort)
        block.setVisible(False)

        node = {}
        node["block"] = block
        node["ports"] = dict()

        self.nodes[peer.hex] = node


    def onPeerExit(self, peer, name, *args, **kwargs):
        # Unsubscribe from value changes
        self.zocp.signal_unsubscribe(self.zocp.uuid(), None, peer, None)

        # Remove block
        if peer.hex in self.nodes:
            self.nodes[peer.hex]["block"].delete()
            self.nodes.pop(peer.hex)


    def onPeerModified(self, peer, name, data, *args, **kwargs):
        for portname in data:
            portdata = data[portname]

            if portname not in self.nodes[peer.hex]["ports"]:
                if "access" in portdata:
                    hasInput = "s" in portdata["access"]
                    hasOutput = "e" in portdata["access"]
                    port = self.nodes[peer.hex]["block"].addPort(portname, hasInput, hasOutput)
                    port.setValue(str(portdata["value"]))
                    port.setAccess(str(portdata["access"]))
                    self.nodes[peer.hex]["ports"][portname] = port

                else:
                    # Metadata, not a capability
                    if portname == "_zne_position":
                        block = self.nodes[peer.hex]["block"]
                        block.setPos(portdata[0], portdata[1])

            else:
                port = self.nodes[peer.hex]["ports"][portname]
                if "value" in portdata:
                    port.setValue(str(portdata["value"]))
                if "access" in portdata:
                    port.setAccess(str(portdata["access"]))

            if "subscribers" in portdata:
                self.updateSubscribers(port, portdata["subscribers"])

        if len(self.nodes[peer.hex]["ports"]) > 0:
            self.nodes[peer.hex]["block"].setVisible(True)
        self.updatePendingSubscribers(peer)


    def onPeerSignaled(self, peer, name, data, *args, **kwargs):
        [portname, value] = data
        if portname in self.nodes[peer.hex]["ports"]:
            self.nodes[peer.hex]["ports"][portname].setValue(str(value))


    def updateSubscribers(self, port, subscribers):
        port1 = port.outputPort

        # check if any current connections should be removed
        connections = port.connections()
        for connection in connections:
            if(connection.port1() == port1):
                port2 = connection.port2()
            else:
                port2 = connection.port1()

            if not port2.isOutput():
                subscriber = [port2.block().uuid().hex, port2.portName()]
                if subscriber not in subscribers:
                    connection.delete()
                    self.logger.debug("peer removed subscription from %s on %s to %s on %s" %
                        (port1.portName(), port1.block().name(), port2.portName(), port2.block().name()))

        # add new connections for new subscriptions
        for subscriber in subscribers:
            [uuid, portname] = subscriber
            if uuid in self.nodes:
                node = self.nodes[uuid]
                if portname in node["ports"]:
                    port2 = node["ports"][portname]
                    if not port2.isConnected(port1):
                        # create new connection
                        connection = QNEConnection(None)
                        connection.setPort1(port1)
                        connection.setPort2(port2)
                        connection.updatePosFromPorts()
                        connection.updatePath()
                        self.scene.addItem(connection)
                        self.logger.debug("peer added subscription from %s on %s to %s on %s" %
                            (port1.portName(), port1.block().name(), port2.portName(), port2.block().name()))
                    continue

            # if the connection could not be made yet, add it to a list of
            # pending subscriber-connections
            if uuid not in self.pendingSubscribers:
                self.pendingSubscribers[uuid] = []
            self.pendingSubscribers[uuid].append([port1, portname])


    def updatePendingSubscribers(self, peer):
        if peer.hex in self.pendingSubscribers:
            for subscriber in self.pendingSubscribers[peer.hex]:
                [port1, portname] = subscriber
                if peer.hex in self.nodes and portname in self.nodes[peer.hex]["ports"]:
                    port2 = self.nodes[peer.hex]["ports"][portname]

                    connection = QNEConnection(None)
                    connection.setPort1(port1)
                    connection.setPort2(port2)
                    connection.updatePosFromPorts()
                    connection.updatePath()
                    self.scene.addItem(connection)
                else:
                    # TODO: handle case where port is still not available
                    pass

            self.pendingSubscribers.pop(peer.hex)
Exemple #4
0
class SkyPython(QMainWindow):
    '''
    The central widget and heart of the application.
    Performs all of the initialization of components 
    and contains updates and event handling. 
    '''
    class RendererModelUpdateClosure():
        '''
        A runnable class that updates the state of the
        model after a change has occurred. Is placed
        on the event queue.
        '''
        def run(self):
            pointing = self.model.get_pointing()
            direction_x = pointing.gc_line_of_sight.x
            direction_y = pointing.gc_line_of_sight.y
            direction_z = pointing.gc_line_of_sight.z
            
            up_x = pointing.gc_perpendicular.x
            up_y = pointing.gc_perpendicular.y
            up_z = pointing.gc_perpendicular.z
            
            self.renderer_controller.queue_set_view_orientation(direction_x, direction_y, direction_z, \
                                                                up_x, up_y, up_z)
            
            acceleration = self.model.acceleration
            self.renderer_controller.queue_text_angle(math.atan2(-acceleration.x, -acceleration.y))
            self.renderer_controller.queue_viewer_up_direction(self.model.get_zenith().copy())
            
            field_of_view = self.model.field_of_view
            self.renderer_controller.queue_field_of_view(field_of_view)
        
        def __init__(self, m_model, controller):
            '''
            constructor
            '''
            self.model = m_model
            self.renderer_controller = controller
    
    layer_manager = None
    model = None
    controller = None
    sky_renderer = None
    renderer_controller = None
    
    def pref_change(self, prefs, manager, layers, event):
        '''
        If a preference button has been pressed, change
        the preferences according to the selection..
        '''
        for layer in layers:
            prefs.PREFERENCES[layer] = not prefs.PREFERENCES[layer]
            manager.on_shared_preference_change(prefs, layer)
        
        return True
    
    def eventFilter(self, source, event):
        '''
        Check if the event is a button press in the UI, if not
        then check if it was a mouse or key press in DRZ_detector.
        '''
        pref_pressed = self.pref_buttons.checkForButtonPress(source, event)
        zoom_pressed = self.zoom_button.checkForButtonPress(source, event, self.controller)
        
        if pref_pressed or zoom_pressed:
            self.show_menus_func()
            
            if pref_pressed:
                self.pref_change(self.shared_prefs, self.layer_manager, 
                                 pref_pressed, event)
        else:
            update = self.DRZ_detector.on_motion_event(event)
        
        if pref_pressed or zoom_pressed or update:
            self.update_rendering()
            return True
        
        return QMainWindow.eventFilter(self, source, event)
    
    def initialize_model_view_controller(self):
        '''
        Set up the graphics view/scene and set the GLWidget.
        Also sets up the the model and the controller for the model.
        '''
        self.model = AstronomerModel(ZMDC())
        
        # There is no onResume in python so start the controller group here
        self.controller = create_controller_group(self.shared_prefs)
        self.controller.set_model(self.model)
        self.controller.start()
        
        self.layer_manager = instantiate_layer_manager(self.model, self.shared_prefs)
        
        self.view = QGraphicsView()
        self.scene = QGraphicsScene()
        
        if self.DEBUG_MODE != None:
            self.sky_renderer = SkyRenderer(self.DEBUG_MODE)
        else:
            self.sky_renderer = SkyRenderer()
        
        self.sky_renderer.setAutoFillBackground(False)
        
        # set up the view with the glwidget inside
        self.view.setViewport(self.sky_renderer)
        self.view.setScene(self.scene)
        self.setCentralWidget(self.view)
        
        self.renderer_controller = RendererController(self.sky_renderer, None)
        self.renderer_controller.add_update_closure(\
            self.RendererModelUpdateClosure(self.model, self.renderer_controller))
        
        self.layer_manager.register_with_renderer(self.renderer_controller)
        
        self.wire_up_screen_controls()
        
        # NOTE: THIS BOOLEAN WILL NEED TO BE REMOVED EVENTUALLY
        self.magnetic_switcher = MDCS(self.model, self.USE_AUTO_MODE)
        
        self.run_queue()
        
    def wire_up_screen_controls(self):
        '''
        Set up all of the screen controls, so that the user can zoom in/out
        and select which layers they wish to view.
        '''
        screen_width = self.sky_renderer.render_state.screen_width
        screen_height = self.sky_renderer.render_state.screen_height
        
        self.pref_buttons = PreferencesButton(self.view)
        self.zoom_button = ZoomButton(self.view)
        
        position_y = ((screen_height - 336) / 2) + 1
        self.pref_buttons.setGeometry(QtCore.QRect(1, position_y, 55, 336))
        
        position_x = ((screen_width - 221) / 2) + 1
        position_y = ((screen_height - 31) * 9/10) + 1
        self.zoom_button.setGeometry(QtCore.QRect(position_x, position_y, 221, 31))
        
        self.pref_buttons_fader = WidgetFader(self.pref_buttons, 2500)
        self.zoom_button_fader = WidgetFader(self.zoom_button, 2500)
        
        self.map_mover = MapMover(self.model, self.controller, self.shared_prefs, 
                                  screen_height)
        self.DRZ_detector = DRZDetector(self.map_mover, self.show_menus_func)
        
    def show_menus_func(self):
        self.pref_buttons_fader.make_active()
        self.zoom_button_fader.make_active()
    
    def update_rendering(self):
        '''
        re-render the sky and run updates
        '''
        self.sky_renderer.updateGL()
        self.run_queue()
        self.sky_renderer.updateGL()
    
    def run_queue(self):
        '''
        In the absence of another thread doing updates,
        force the updates by running all the runnables on the queue
        '''
        num = len(list(self.renderer_controller.queuer.queue))
        while num > 0:
            runnable = self.renderer_controller.queuer.get()
            runnable.run()
            self.renderer_controller.queuer.task_done()
            num -= 1
    
    def __init__(self, debug_index=None):
        '''
        Set up all state and control that the application requires.
        '''
        QMainWindow.__init__(self)
        self.setAttribute(QtCore.Qt.WA_AcceptTouchEvents)
        
        self.DEBUG_MODE = debug_index
        self.USE_AUTO_MODE = False
        self.shared_prefs = SharedPreferences()
        
        self.initialize_model_view_controller()
        self.controller.set_auto_mode(self.USE_AUTO_MODE)
        
        # put the window at the screen position (100, 30)
        # with size 480 by 800
        self.setGeometry(100, 30, 480, 800)
        self.show()
        
        # every 5 seconds re-render the sky and run updates
        self.update_rendering()
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.update_rendering)
        self.timer.setInterval(5000)
        self.timer.start()
class QNEMainWindow(QMainWindow):
    def __init__(self, parent):
        super(QNEMainWindow, self).__init__(parent)

        quitAct = QAction("&Quit",
                          self,
                          shortcut="Ctrl+Q",
                          statusTip="Exit the application",
                          triggered=self.close)

        addAct = QAction("&Add",
                         self,
                         statusTip="Add a block",
                         triggered=self.addBlock)

        fileMenu = self.menuBar().addMenu("&File")
        fileMenu.addAction(addAct)
        fileMenu.addSeparator()
        fileMenu.addAction(quitAct)

        self.setWindowTitle("Node Editor")

        self.scene = QGraphicsScene(self)
        self.view = QGraphicsView(self)
        self.view.setScene(self.scene)
        self.view.setRenderHint(QPainter.Antialiasing)
        self.setCentralWidget(self.view)

        self.nodesEditor = QNodesEditor(self)
        self.nodesEditor.install(self.scene)

        block = QNEBlock(None)
        self.scene.addItem(block)
        block.addPort("test", 0, QNEPort.NamePort)
        block.addPort("TestBlock", 0, QNEPort.TypePort)
        block.addInputPort("in1")
        block.addInputPort("in2")
        block.addInputPort("in3")
        block.addOutputPort("out1")
        block.addOutputPort("out2")
        block.addOutputPort("out3")

        block = block.clone()
        block.setPos(150, 0)

        block = block.clone()
        block.setPos(150, 150)

    def addBlock(self):
        import random
        import math

        block = QNEBlock(None)

        self.scene.addItem(block)
        names = [
            "Vin", "Voutsadfasdf", "Imin", "Imax", "mul", "add", "sub", "div",
            "Conv", "FFT"
        ]
        for i in range(0, math.floor(random.uniform(3, 8))):
            block.addPort(random.choice(names), random.random() > 0.5)
        block.setPos(self.view.sceneRect().center().toPoint())
Exemple #6
0
class QNEMainWindow(QMainWindow):
    def __init__(self, parent):
        super(QNEMainWindow, self).__init__(parent)

        self.logger = logging.getLogger("zne")
        self.logger.setLevel(logging.DEBUG)

        self.setMinimumSize(560, 360)
        self.setWindowTitle("ZOCP Node Editor")
        self.setWindowIcon(QIcon('assets/icon.png'))

        self.scene = QGraphicsScene(self)
        self.view = QGraphicsView(self)
        self.view.setScene(self.scene)
        self.setCentralWidget(self.view)

        self.nodesEditor = QNodesEditor(self, self.scene, self.view)

        self.nodesEditor.onAddConnection = self.onAddConnection
        self.nodesEditor.onRemoveConnection = self.onRemoveConnection
        self.nodesEditor.onBlockMoved = self.onBlockMoved

        self.scale = 1
        self.installActions()

        self.initZOCP()

        self.nodes = {}
        self.pendingSubscribers = {}

        QTimer.singleShot(250, lambda: self.scene.invalidate())

    def closeEvent(self, *args):
        self.zocp.stop()

    def installActions(self):
        quitAct = QAction("&Quit",
                          self,
                          shortcut="Ctrl+Q",
                          statusTip="Exit the application",
                          triggered=self.close)

        fileMenu = self.menuBar().addMenu("&File")
        fileMenu.addAction(quitAct)

        # for shortcuts
        self.view.addAction(quitAct)

        selectAllAct = QAction("Select &All",
                               self,
                               shortcut="Ctrl+A",
                               triggered=self.nodesEditor.selectAll)
        selectNoneAct = QAction("Select &None",
                                self,
                                shortcut="Ctrl+D",
                                triggered=self.nodesEditor.selectNone)
        selectInverseAct = QAction("Select &Inverse",
                                   self,
                                   shortcut="Ctrl+I",
                                   triggered=self.nodesEditor.selectInverse)
        deleteSelectedAct = QAction("&Delete Selected",
                                    self,
                                    shortcut="Del",
                                    triggered=self.nodesEditor.deleteSelected)

        editMenu = self.menuBar().addMenu("&Edit")
        editMenu.addAction(selectAllAct)
        editMenu.addAction(selectNoneAct)
        editMenu.addAction(selectInverseAct)
        editMenu.addSeparator()
        editMenu.addAction(deleteSelectedAct)

        self.view.addAction(selectAllAct)
        self.view.addAction(selectNoneAct)
        self.view.addAction(selectInverseAct)
        self.view.addAction(deleteSelectedAct)

        zoomInAct = QAction("Zoom &In",
                            self,
                            shortcut="Ctrl++",
                            triggered=self.zoomIn)
        zoomOutAct = QAction("Zoom &Out",
                             self,
                             shortcut="Ctrl+-",
                             triggered=self.zoomOut)
        zoomResetAct = QAction("&Reset Zoom",
                               self,
                               shortcut="Ctrl+0",
                               triggered=self.zoomReset)

        viewMenu = self.menuBar().addMenu("&View")
        viewMenu.addAction(zoomInAct)
        viewMenu.addAction(zoomOutAct)
        viewMenu.addSeparator()
        viewMenu.addAction(zoomResetAct)

        self.view.addAction(zoomInAct)
        self.view.addAction(zoomOutAct)
        self.view.addAction(zoomResetAct)

        aboutAct = QAction("&About", self, triggered=self.about)

        helpMenu = self.menuBar().addMenu("&Help")
        helpMenu.addAction(aboutAct)

        self.view.addAction(aboutAct)

    def zoomIn(self):
        if self.scale < 4:
            self.scale *= 1.2
            self.view.scale(1.2, 1.2)

    def zoomOut(self):
        if self.scale > 0.1:
            self.scale /= 1.2
            self.view.scale(1 / 1.2, 1 / 1.2)

    def zoomReset(self):
        self.scale = 1
        self.view.setTransform(QTransform())

    def about(self):
        QMessageBox.about(
            self, "About ZOCP Node Editor",
            "<p>A monitor/editor for ZOCP nodes, implemented in PySide"
            "(Python/Qt4).</p><p>ZOCP is the Z25 Orchestration Control "
            "Protocol, currently in development at "
            "<a href='http://z25.org'>z25.org</a></p>")

    #########################################
    # Node editor callbacks
    #########################################
    def onAddConnection(self, connection, fromPort, toPort):
        fromBlock = fromPort.block()
        toBlock = toPort.block()

        emitter = toPort.portName()
        emit_peer = toBlock.uuid()
        receiver = fromPort.portName()
        recv_peer = fromBlock.uuid()

        self.zocp.signal_subscribe(recv_peer, receiver, emit_peer, emitter)

        self.logger.debug(
            "added subscription from %s on %s to %s on %s" %
            (receiver, fromBlock.name(), emitter, toBlock.name()))

    def onRemoveConnection(self, connection, fromPort, toPort):
        fromBlock = fromPort.block()
        toBlock = toPort.block()

        emitter = toPort.portName()
        emit_peer = toBlock.uuid()
        receiver = fromPort.portName()
        recv_peer = fromBlock.uuid()

        self.zocp.signal_unsubscribe(recv_peer, receiver, emit_peer, emitter)

        self.logger.debug(
            "removed subscription from %s on %s to %s on %s" %
            (receiver, fromBlock.name(), emitter, toBlock.name()))

    def onBlockMoved(self, block):
        pos = block.pos()
        peer = block.uuid()
        self.zocp.peer_set(peer, {"_zne_position": [pos.x(), pos.y()]})

    def onChangeValue(self, block, port, value):
        self.logger.debug("block %s port %s changed to %s" %
                          (block.name(), port.portName(), value))
        peer = block.uuid()
        portName = port.portName()
        capability = self.zocp.peers_capabilities[peer][portName]
        typeHint = capability["typeHint"]
        validValue = True
        if typeHint == "int":
            try:
                value = int(float(value))
            except:
                validValue = False
        elif typeHint == "flt":
            try:
                value = float(value.strip())
            except:
                validValue = False
        elif typeHint == "percent":
            try:
                value = float(value.strip())
            except:
                validValue = False
        elif typeHint == "bool":
            value = (value.strip() in ["true", "yes", "1"])
        elif typeHint == "string":
            pass
        elif typeHint == "vec2f" or typeHint == "vec3f" or typeHint == "vec4f":
            try:
                value = [
                    float(num) for num in ((value.strip())[1:-1]).split(",")
                ]
            except:
                validValue = False

            if validValue:
                if len(value) != int(typeHint[3]):
                    validValue = False

        if validValue:
            self.zocp.peer_set(peer, {portName: {"value": value}})
            port.setValue(str(value))
        else:
            port.setValue(str(capability["value"]))

    #########################################
    # ZOCP implementation
    #########################################
    def initZOCP(self):
        self.zocp = ZOCP()
        self.zocp.set_name("ZOCP Node Editor@%s" % socket.gethostname())
        self.notifier = QSocketNotifier(self.zocp.inbox.getsockopt(zmq.FD),
                                        QSocketNotifier.Read)
        self.notifier.setEnabled(True)
        self.notifier.activated.connect(self.onZOCPEvent)
        self.zocp.on_peer_enter = self.onPeerEnter
        self.zocp.on_peer_exit = self.onPeerExit
        self.zocp.on_peer_modified = self.onPeerModified
        self.zocp.on_peer_signaled = self.onPeerSignaled
        self.zocp.start()

        zl = logging.getLogger("zocp")
        zl.setLevel(logging.INFO)

    def onZOCPEvent(self):
        self.zocp.run_once(0)

    def onPeerEnter(self, peer, name, *args, **kwargs):
        # Subscribe to any and all value changes
        self.zocp.signal_subscribe(self.zocp.get_uuid(), None, peer, None)

        # Add named block; ports are not known at this point
        block = QNEBlock(None)
        self.scene.addItem(block)
        block.setNodeEditor(self)
        block.setName(name)
        block.setUuid(peer)
        block.addPort(name, False, False, QNEPort.NamePort)
        block.setVisible(False)

        node = {}
        node["block"] = block
        node["ports"] = dict()

        self.nodes[peer.hex] = node

    def onPeerExit(self, peer, name, *args, **kwargs):
        # Unsubscribe from value changes
        self.zocp.signal_unsubscribe(self.zocp.get_uuid(), None, peer, None)

        # Remove block
        if peer.hex in self.nodes:
            self.nodes[peer.hex]["block"].delete()
            self.nodes.pop(peer.hex)

    def onPeerModified(self, peer, name, data, *args, **kwargs):
        for portname in data:
            portdata = data[portname]

            if portname not in self.nodes[peer.hex]["ports"]:
                if "access" in portdata:
                    hasInput = "s" in portdata["access"]
                    hasOutput = "e" in portdata["access"]
                    port = self.nodes[peer.hex]["block"].addPort(
                        portname, hasInput, hasOutput)
                    port.setValue(str(portdata["value"]))
                    self.nodes[peer.hex]["ports"][portname] = port

                else:
                    # Metadata, not a capability
                    if portname == "_zne_position":
                        block = self.nodes[peer.hex]["block"]
                        block.setPos(portdata[0], portdata[1])

            else:
                port = self.nodes[peer.hex]["ports"][portname]
                if "value" in portdata:
                    port.setValue(str(portdata["value"]))

            if "subscribers" in portdata:
                self.updateSubscribers(port, portdata["subscribers"])

        if len(self.nodes[peer.hex]["ports"]) > 0:
            self.nodes[peer.hex]["block"].setVisible(True)
        self.updatePendingSubscribers(peer)

    def onPeerSignaled(self, peer, name, data, *args, **kwargs):
        [portname, value] = data
        if portname in self.nodes[peer.hex]["ports"]:
            self.nodes[peer.hex]["ports"][portname].setValue(str(value))

    def updateSubscribers(self, port, subscribers):
        port1 = port.outputPort

        # check if any current connections should be removed
        connections = port.connections()
        for connection in connections:
            if (connection.port1() == port1):
                port2 = connection.port2()
            else:
                port2 = connection.port1()

            if not port2.isOutput():
                subscriber = [port2.block().uuid().hex, port2.portName()]
                if subscriber not in subscribers:
                    connection.delete()
                    self.logger.debug(
                        "peer removed subscription from %s on %s to %s on %s" %
                        (port1.portName(), port1.block().name(),
                         port2.portName(), port2.block().name()))

        # add new connections for new subscriptions
        for subscriber in subscribers:
            [uuid, portname] = subscriber
            if uuid in self.nodes:
                node = self.nodes[uuid]
                if portname in node["ports"]:
                    port2 = node["ports"][portname]
                    if not port2.isConnected(port1):
                        # create new connection
                        connection = QNEConnection(None)
                        connection.setPort1(port1)
                        connection.setPort2(port2)
                        connection.updatePosFromPorts()
                        connection.updatePath()
                        self.scene.addItem(connection)
                        self.logger.debug(
                            "peer added subscription from %s on %s to %s on %s"
                            % (port1.portName(), port1.block().name(),
                               port2.portName(), port2.block().name()))
                    continue

            # if the connection could not be made yet, add it to a list of
            # pending subscriber-connections
            if uuid not in self.pendingSubscribers:
                self.pendingSubscribers[uuid] = []
            self.pendingSubscribers[uuid].append([port1, portname])

    def updatePendingSubscribers(self, peer):
        if peer.hex in self.pendingSubscribers:
            for subscriber in self.pendingSubscribers[peer.hex]:
                [port1, portname] = subscriber
                if peer.hex in self.nodes and portname in self.nodes[
                        peer.hex]["ports"]:
                    port2 = self.nodes[peer.hex]["ports"][portname]

                    connection = QNEConnection(None)
                    connection.setPort1(port1)
                    connection.setPort2(port2)
                    connection.updatePosFromPorts()
                    connection.updatePath()
                    self.scene.addItem(connection)
                else:
                    # TODO: handle case where port is still not available
                    pass

            self.pendingSubscribers.pop(peer.hex)
Exemple #7
0
class PostViewWidget(HorsePanel):
    def __init__(self, parent, order_overview_widget, find_order_slot):
        global configuration

        super(PostViewWidget, self).__init__(parent)

        self.set_panel_title(_("Post overview"))
        self.bold_font = QFont(self.font())
        self.bold_font.setBold(True)
        self.nb_cols = 8  # Number of columns in the operation definition table

        self.order_overview_widget = order_overview_widget

        self.button = QPushButton(_("Refresh"), self)
        self.button.clicked.connect(self.refresh_action)
        self.sort_by_deadline_button = QRadioButton(_("By deadline"), self)
        self.sort_by_deadline_button.toggled.connect(self.sort_by_deadline)
        self.sort_by_size_button = QRadioButton(_("By hours left to do"), self)
        self.sort_by_size_button.toggled.connect(self.sort_by_size)

        # hlayout = QHBoxLayout()
        # hlayout.setObjectName("halyout")
        # hlayout.setContentsMargins(0,0,0,0)
        # hlayout.addWidget(self.sort_by_deadline_button)
        # hlayout.addWidget(self.sort_by_size_button)
        # hlayout.addWidget(self.button)
        # hlayout.addStretch()

        self.navbar = NavBar(self, [(self.sort_by_deadline_button, None),
                                    (self.sort_by_size_button, None),
                                    (self.button, None),
                                    (_("Find"), find_order_slot)])
        self.navbar.buttons[3].setObjectName("specialMenuButton")

        self.vlayout = QVBoxLayout(self)
        self.vlayout.setObjectName("Vlayout")
        self.vlayout.addWidget(
            TitleWidget(_("Posts Overview"), self, self.navbar))

        self._table_model = QStandardItemModel(1, self.nb_cols, self)
        self.table_view = QTableView(None)
        self.table_view.setModel(self._table_model)
        self.table_view.selectionModel().currentChanged.connect(
            self.operation_selected)

        self.table_view.verticalHeader().hide()
        self.table_view.horizontalHeader().hide()
        self.table_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows)

        # This forces Qt to expand layout once I fill data in
        # FIXME dirty but I really don't get why setting
        # the mini width to something smaller (that happens at
        # startup, on first refresh) doesn't work
        self.table_view.setMinimumWidth(1)
        self.table_view.setMaximumWidth(1)

        self.post_view_scene = PostViewScene(self, order_overview_widget)
        self.post_view_scene_view = QGraphicsView(self)
        self.post_view_scene_view.setScene(self.post_view_scene)
        self.post_view_scene_view.setSizePolicy(QSizePolicy.Expanding,
                                                QSizePolicy.Expanding)

        self.splitter = QSplitter(Qt.Horizontal)
        self.splitter.addWidget(
            SubFrame(_("Posts"), self.table_view, self.splitter))
        self.splitter.addWidget(
            SubFrame(_("Workload"), self.post_view_scene_view, self.splitter))
        # self.splitter.setStretchFactor(0,1)
        self.splitter.setStretchFactor(1, 1)
        self.vlayout.addWidget(self.splitter)

        # hlayout = QHBoxLayout()
        # hlayout.addWidget(SubFrame(_("Posts"),self.table_view,self))
        # hlayout.addWidget(SubFrame(_("Workload"),self.post_view_scene_view,self))
        # hlayout.setStretch(1,1)
        # self.vlayout.addLayout(hlayout)

        self.vlayout.setStretch(0, 0)
        self.vlayout.setStretch(1, 1)

        self.setLayout(self.vlayout)

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.slidePostsScene)

        self.current_view_y = 0

    def _data_load(self):
        global dao

        all_operations = dao.operation_dao.load_all_operations_ready_for_production(
        )
        operation_definitions = dao.operation_definition_dao.all_direct_frozen(
        )

        return operation_definitions, all_operations

    def _reset_operation_definitions(self, operations):
        self._table_model.setColumnCount(1)
        self._table_model.setRowCount(len(operations))

        # BUG This should be refreshed on reload() too

        row = col = 0
        first_active = None

        for opdef in operations:

            if opdef.operation_definition_id in self.post_view_scene.drawn_operations_data:
                # currently total planned time
                t = self.post_view_scene.drawn_operations_data[
                    opdef.operation_definition_id]
                ndx = self._table_model.index(row, col)
                if not first_active:
                    first_active = ndx
                self._table_model.setData(
                    ndx, u"{} {}".format(opdef.description, t), Qt.DisplayRole)
                # self._table_model.setData(ndx,self.bold_font,Qt.FontRole)

                self._table_model.setData(self._table_model.index(row, col),
                                          opdef.operation_definition_id,
                                          Qt.UserRole)
                row += 1

            else:
                pass
                # self._table_model.setData(self._table_model.index(row,col),opdef.description,Qt.DisplayRole)

            # = col + 1
            # if col == self.nb_cols:
            #     col = 0
            #     row += 1

        self._table_model.setRowCount(row)

        # self.table_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        # self.vlayout.setStretch(0,0)
        # self.vlayout.setStretch(1,10)
        # self.vlayout.setStretch(2,10000)

        # height = 0
        # for c in range(self.table_view.model().rowCount()):
        #     height += self.table_view.rowHeight(c) + 1 # +1 for cell border
        # self.table_view.setMinimumHeight(height)
        # self.table_view.setMaximumHeight(height)
        for i in range(self.nb_cols):
            self.table_view.resizeColumnToContents(i)
        self.table_view.setMaximumWidth(self.table_view.columnWidth(0))
        self.table_view.setMinimumWidth(self.table_view.columnWidth(0))
        self.table_view.setSizePolicy(QSizePolicy.Maximum,
                                      QSizePolicy.Preferred)

        self.table_view.update()
        self.splitter.update()

        return first_active

    def slide_to_operation(self, opdef_id):
        if opdef_id in self.post_view_scene.posts_offsets:
            self.slide_target_opdef_id = opdef_id
            # mainlog.debug("Target y = {}".format(self.post_view_scene.posts_offsets[self.slide_target_opdef]))
            self.timer.start(20)

    @Slot()
    def slidePostsScene(self):
        if self.slide_target_opdef_id is None:
            return

        # self.post_view_scene_view
        self.post_view_scene.set_cursor_on(
            self.slide_target_opdef_id
        )  # This done here also aviod some screen trashing

        v = self.post_view_scene_view.verticalScrollBar().value()
        # mainlog.debug( "slidePostsScene : {}".format(v))

        r = self.post_view_scene.posts_offsets[self.slide_target_opdef_id]
        target_y = r.y() + r.height() / 2
        delta = (target_y - self.current_view_y) * 0.4
        self.current_view_y = self.current_view_y + delta
        self.post_view_scene_view.centerOn(0, self.current_view_y)
        # mainlog.debug( "slidePostsScene : {} / {}".format(target_y, self.current_view_y))

        if self.post_view_scene_view.verticalScrollBar().value() == v:
            # Close enough => stop moving
            # FIXME not correct because we must stop when the view stops moving, not when the goal we set for centerOn is reached
            self.timer.stop()

    @Slot(QModelIndex, QModelIndex)
    def operation_selected(self, ndx_cur, ndx_old):
        if ndx_cur.isValid():
            opdef = self._table_model.data(ndx_cur, Qt.UserRole)
            if opdef:
                self.slide_to_operation(
                    self._table_model.data(ndx_cur, Qt.UserRole))

    @Slot()
    def refresh_action(self):
        # FIXME reload operations as well

        operation_definitions, all_operations = self._data_load()

        # mainlog.debug("reload")
        if self.sort_by_deadline_button.isChecked():
            self.post_view_scene.reload(self, operation_definitions,
                                        all_operations, 1)
        elif self.sort_by_size_button.isChecked():
            self.post_view_scene.reload(self, operation_definitions,
                                        all_operations, 2)
        else:
            self.post_view_scene.reload(self, operation_definitions,
                                        all_operations, 0)

        # mainlog.debug("reset")
        first_active = self._reset_operation_definitions(operation_definitions)
        # self.table_view.selectionModel().currentChanged.connect(self.operation_selected)
        if first_active:
            self.table_view.setCurrentIndex(first_active)

        # mainlog.debug("done reset")

    @Slot(bool)
    def sort_by_deadline(self, checked):
        if checked:
            self.refresh_action()

    @Slot(bool)
    def sort_by_size(self, checked):
        if checked:
            self.refresh_action()

    order_part_double_clicked = Signal(int)

    # Callback that will be called by HooverBar
    def set_on_order_part(self, order_part_id):
        self.order_part_double_clicked.emit(order_part_id)
class UVViewer(QDialog):
    def __init__(self, model, w, h, parent=None):
        super(UVViewer, self).__init__(parent)
        self.w = w
        self.h = h
        self.mdl = model
        self.white_b = QBrush(Qt.white)
        self.black_b = QBrush(Qt.black)
        self.pen_width = 2
        self.initUI()

    def initUI(self):
        mainlay = QVBoxLayout()
        scn = QGraphicsScene(0, 0, self.w, self.h)

        self.view = QGraphicsView()
        self.view.setScene(scn)
        self.view.setSceneRect(QRectF(0, 0, self.w, self.h))
        self.view.setMaximumWidth(self.w)
        self.view.setMaximumHeight(self.h)

        mainlay.addWidget(self.view)

        btns = QHBoxLayout()
        btns.addStretch()

        self.pen_w = QSpinBox()
        self.pen_w.setValue(self.pen_width)
        redraw = QPushButton('Redraw')
        redraw.clicked.connect(self.draw_uvs)
        save = QPushButton('Save')
        save.clicked.connect(self.save)
        close = QPushButton('Close')
        close.clicked.connect(self.close)

        btns.addWidget(QLabel('Stroke Width'))
        btns.addWidget(self.pen_w)
        btns.addWidget(redraw)
        btns.addWidget(save)
        btns.addWidget(close)

        mainlay.addLayout(btns)

        self.draw_uvs()

        self.setLayout(mainlay)
        self.setGeometry(340, 340, 512, 560)
        self.setWindowTitle('MSH Suite UV Viewer')
        self.show()

    def draw_uvs(self):
        self.img = QPixmap(QSize(self.w, self.h))
        pen = QPen()
        pen.setWidth(int(self.pen_w.text()))
        pen.setBrush(QBrush(Qt.white))
        pen.setColor(QColor('white'))
        painter = QPainter()
        painter.begin(self.img)
        painter.setPen(pen)
        coords = self.get_coords()
        for face in coords:
            for n in xrange(len(face) - 1):
                print face[n][0], face[n][1], face[n + 1][0], face[n + 1][1]
                painter.drawLine(face[n][0], face[n][1], face[n + 1][0], face[n + 1][1])
        painter.end()
        self.view.scene().addPixmap(self.img)

    def get_coords(self):
        coords = []
        for seg in self.mdl.segments:
            if seg.classname == 'SegmentGeometry':
                print 'doing stuff'
                vcoll = seg.vertices
                for face in seg.faces:
                    face_coords = []
                    for v in face.SIindices():
                        face_coords.append((vcoll[v].u * self.w, (1 - vcoll[v].v) * self.h))
                    face_coords.append((vcoll[face.vertices[0]].u * self.w,
                                        (1 - vcoll[face.vertices[0]].v) * self.h))
                    coords.append(face_coords)
                    #print face_coords
        return coords

    def save(self):
        filename, _ = QFileDialog.getSaveFileName(self, 'Save UV Mesh', os.getcwd(), 'PNG Files (*.png)')
        if not filename:
            return
        self.img.save(filename, 'PNG')
        self.close()
Exemple #9
0
class OrderWorkflowDialog(QDialog):

    def _makeArrow( self, ra, rb, both_ways=True):

        ca = QPointF( ra.x() + float(ra.width()) / 2.0, ra.y() + float(ra.height()) / 2.0)
        cb = QPointF( rb.x() + rb.width() / 2.0, rb.y() + rb.height() / 2.0)

        dx = cb.x() - ca.x()
        dy = cb.y() - ca.y()
        d = dist(dx,dy)


        a = math.atan2(float(dy),float(dx)) # The result is between -pi and pi

        # ra_rad = dist(ra.width() / 2, ra.height() / 2)
        # rb_rad = dist(rb.width() / 2, rb.height() / 2)

        x,y = squircle(ra.width() / 2, ra.height() / 2, to_squircle_angle(ra.width() / 2, ra.height() / 2,a))
        ra_rad = dist(x,y) + 10
        # painter.drawLine(ca,QPoint(ca.x() + x, ca.y() + y))

        na = a
        if a < 0:
            na += math.pi
        else:
            na -= math.pi

        x,y = squircle(float(rb.width()) / 2.0, float(rb.height()) / 2.0, to_squircle_angle(float(rb.width()) / 2.0, float(rb.height()) / 2.0,na))
        rb_rad = dist(x,y)+10

        # ra_rad = rb_rad = 0


        # painter.drawLine(ca,cb)
        # painter.drawRect(ra)
        # painter.setPen(Qt.GlobalColor.red)
        # painter.drawRect(rb)
        # painter.setPen(Qt.GlobalColor.black)

        # for t in range(100):
        #     ang = 6.28/100.0*float(t)
        #     x,y = squircle( 200,50,ang)
        #     painter.drawPoint(300 + x,100 + y)

        qp = QPolygonF()
        h = 5

        v = []
        v.append(QPointF(ra_rad,0))

        if not both_ways:
            v.append(QPointF(ra_rad,-h/2))
        else:
            v.append(QPointF(ra_rad + 2*h,-2*h))
            v.append(QPointF(ra_rad + 2*h,-h/2))


        v.append(QPointF(d - rb_rad - 2*h,-h/2))
        v.append(QPointF(d - rb_rad - 2*h,-2*h))
        v.append(QPointF(d - rb_rad, 0))
        v.append(QPointF(d - rb_rad - 2*h,+2*h))
        v.append(QPointF(d - rb_rad - 2*h,+h/2))

        if not both_ways:
            v.append(QPointF(ra_rad,+h/2))
        else:
            v.append(QPointF(ra_rad + 2*h,+h/2))
            v.append(QPointF(ra_rad + 2*h,+2*h))

        v.append(QPointF(ra_rad,0))

        p = QPolygonF(v)
        item = QGraphicsPolygonItem(p)
        item.translate(ca.x(),ca.y())
        item.rotate(math.degrees(a))
        return item


    def _drawNodes(self,scene,selected_state,initial_state):
        for i in scene.items():
            scene.removeItem(i)

        pos = dict()

        sx = 1
        sy = 1
        if configuration.font_select:
            sx = sx * 2
            sy = sy

        pos[OrderStatusType.preorder_definition] = (200*sx,200*sy)
        pos[OrderStatusType.order_definition] = (10*sx,300*sy)
        pos[OrderStatusType.order_ready_for_production] = (300*sx,300*sy)
        pos[OrderStatusType.order_production_paused] = (200*sx,550*sy)
        pos[OrderStatusType.order_completed] = (400*sx,600*sy)
        pos[OrderStatusType.order_aborted] = (10*sx,600*sy)

        mainlog.debug("_drawNodes : selected_state {}".format(selected_state))
        mainlog.debug("_drawNodes : initial_state {}".format(initial_state))

        for org in OrderStatusType.symbols():
            x,y = pos[org]

            item = QGraphicsSimpleTextItem(org.description)

            if (selected_state and org in OrderStatusType.next_states(selected_state)) or org == initial_state or org == selected_state:
                item = MyQGraphicsSimpleTextItem(org, self)
                item.setFlags( item.flags() | QGraphicsItem.ItemIsSelectable)

                if org == selected_state:
                    mainlog.debug("Preselcting {}".format(org))
                    item.setSelected(True)

            item.setFont(self.thinfont)
            item.setPos(x,y)
            scene.addItem(item)

            pos[org] = item.boundingRegion( item.sceneTransform()).boundingRect()

            # scene.addRect(pos[org])


        g_orgs = dict()
        for org in OrderStatusType.symbols():
            g_orgs[org] = OrderStatusType.next_states(org)


        # Draw all arrows which don't end up or leave the selected_state
        drawn = []
        for org,dests in g_orgs.iteritems():
            for dest in dests:
                if selected_state != org and ((org,dest) not in drawn) and ((dest,org) not in drawn):
                    # If an arrow must have two directions, we draw it
                    # like that directly.
                    item = self._makeArrow( pos[org],pos[dest],dest in g_orgs and org in g_orgs[dest])
                    item.setPen(self.grey_pen)
                    scene.addItem( item)
                    drawn.append((org,dest))



        if initial_state:
            scene.addRect(pos[initial_state])

        if selected_state:
            for dest in OrderStatusType.next_states(selected_state):
                item = self._makeArrow( pos[selected_state],pos[dest],False)
                item.setBrush(QBrush(Qt.green))
                scene.addItem( item)
                drawn.append((org,dest))

    @Slot()
    def selectionChanged(self):
        pass

    def __init__(self,parent):
        global configuration
        super(OrderWorkflowDialog,self).__init__(parent)

        self.grey_pen = QPen(Qt.gray)

        title = _("Order workflow")
        self.setWindowTitle(title)
        self.title_widget = TitleWidget(title,self)

        self.buttons = QDialogButtonBox()
        self.buttons.addButton( QDialogButtonBox.Ok)
        self.buttons.addButton( QDialogButtonBox.Cancel)

        self.thinfont = QFont()
        if configuration.font_select:
            self.thinfont.setPointSize(self.thinfont.pointSize()*2)

        self.scene = QGraphicsScene()
        self.scene.selectionChanged.connect(self.selectionChanged)

        self.view = QGraphicsView(self)
        self.view.setRenderHint(QPainter.Antialiasing)
        self.view.setScene(self.scene)

        top_layout = QVBoxLayout()
        top_layout.addWidget(self.title_widget)
        top_layout.addWidget(self.view)
        top_layout.addWidget(self.buttons)

        self.setLayout(top_layout) # QWidget takes ownership of the layout
        self.buttons.accepted.connect(self.save_and_accept)
        self.buttons.rejected.connect(self.cancel)

        self.initial_state = None
        self.selected_state = None
        self._drawNodes(self.scene,self.selected_state,self.initial_state)

    def set_selected_state(self,selected_state,initial_state):
        mainlog.debug("selected_state {}".format(selected_state))
        mainlog.debug("initial_state {}".format(initial_state))

        self.initial_state = initial_state
        self.selected_state = selected_state
        self._drawNodes(self.scene,selected_state,self.initial_state)


    @Slot()
    def cancel(self):
        return super(OrderWorkflowDialog,self).reject()

    @Slot()
    def save_and_accept(self):
        super(OrderWorkflowDialog,self).accept()

        s = self.scene.selectedItems()
        if s:
            # In cas there are more than one item selected,
            # we pick the first one (arbitrarily)
            self.selected_state = s[0].state
        else:
            self.selected_state = self.initial_state