def __init__(self, ipf_graph, parent=None):
     super(GraphGrid, self).__init__(parent)
     if ipf_graph is None:
         self.ipf_graph = IPFGraph()
     else:
         self.ipf_graph = ipf_graph
     self.adjust_grid_size()
     
     # Set of GraphBlock objects
     self.graph_blocks = set()
     
     # Dummy block used to show dragged block position
     self.dummy_block = QtGui.QGraphicsRectItem(self)
     self.dummy_block.setRect(0, 0, self.cell_width, self.cell_height)
     self.dummy_block.hide()
     
     # Temporary connection arrow
     self.temp_arrow = QtGui.QGraphicsLineItem(self)
     self.temp_arrow.hide() 
     self.temp_arrow.setZValue(20)
     
     # Connection lines
     self.connection_arrows = []
     
     # Current selected block
     self.selected_block = None
     
     self.paint_mode = GraphBlock.TEXT_PAINT_MODE
     
     # Current selected arrow
     self.selected_arrow = None
class GraphGrid(QtGui.QGraphicsRectItem):
    """ Graphics grid class, used in GraphScheme
    
    """
    
    cell_width = 100
    cell_height = 120
    left_margin = 40
    top_margin = 40
    
    
    # Inner class for represent connection arrow between blocks     
    class ConnectionArrow(QtGui.QGraphicsPathItem):
    
        NORMAL_COLOR = QtGui.QColor(113, 153, 213)
        SELECTED_COLOR = QtGui.QColor(240, 30, 20)
        ROUND_CORNER_RADIUS = 5
        
        
        def __init__(self, parent, connection):
            super(GraphGrid.ConnectionArrow, self).__init__(parent)

            self.connection_ref = weakref.ref(connection)
            iport_name = connection._iport()._owner_block()\
                            .get_port_name(connection._iport())
            oport_name = connection._oport()._owner_block()\
                            .get_port_name(connection._oport())
            iblock_prim = parent.get_block_primitive_from_block(
                            connection._iport()._owner_block)
            oblock_prim = parent.get_block_primitive_from_block(
                            connection._oport()._owner_block)
            if iblock_prim is None or oblock_prim is None:
                raise ValueError("IPFBlock not found in scheme")
            
            # Find port primitives
            iport_prim = iblock_prim.input_ports_items[iport_name]
            oport_prim = oblock_prim.output_ports_items[oport_name]
            begin = oport_prim.get_port_center()
            end = iport_prim.get_port_center()
            row = int( (begin.y() - GraphGrid.top_margin) / GraphGrid.cell_height) + 1
            grid_line_y = GraphGrid.top_margin + row * GraphGrid.cell_height
            
            path = self._create_path(begin.x(), begin.y(),
                                     end.x(), end.y(), grid_line_y)
            self.setPath(path)
            pen = self.pen()
            pen.setWidth(4)
            pen.setColor( self.NORMAL_COLOR)
            pen.setJoinStyle(QtCore.Qt.RoundJoin)
            pen.setCapStyle(QtCore.Qt.RoundCap)
            self.setPen(pen)
            self.selected = False
            self.setZValue(-1)
            
            
        def mousePressEvent(self, event):
            grid = self.parentItem()
            grid.connection_arrow_selected(self)
            
            
        def update(self):
            pen = self.pen()
            if self.selected:
                pen.setColor(self.SELECTED_COLOR)
            else:
                pen.setColor(self.NORMAL_COLOR)
            self.setPen(pen)
        
        def _create_path(self, beg_x, beg_y, end_x, end_y, corner_y):
            path = QtGui.QPainterPath()
            if beg_x == end_x:
                # Begin and end arranged vertically
                path.moveTo(beg_x, beg_y)
                path.lineTo(end_x, end_y)
                return path
            
            if beg_y > end_y:
                # Swap begin and end points
                beg_x, end_x = end_x, beg_x
                beg_y, end_y = end_y, beg_y
            
            r = self.ROUND_CORNER_RADIUS    
            # Choose line type (left-to right or right-to-left)
            if beg_x < end_x:
                # Left-to-right line
                path.moveTo(beg_x, beg_y)
                path.lineTo(beg_x, corner_y - r)
                path.arcTo(beg_x, corner_y - 2*r, 2*r, 2*r, 180, 90)
                path.moveTo(beg_x + r, corner_y)
                path.lineTo(end_x - r, corner_y)
                if end_y < corner_y:
                    # Backward line
                    path.arcTo(end_x - 2*r, corner_y-2*r, 2*r, 2*r, 270, 90)
                    path.moveTo(end_x, corner_y - r)
                else:
                    path.arcTo(end_x - 2*r, corner_y, 2*r, 2*r, 0, 90)
                    path.moveTo(end_x, corner_y + r)
                path.lineTo(end_x, end_y)
            elif beg_x > end_x:
                # Right-to-left line
                path.moveTo(beg_x, beg_y)
                path.lineTo(beg_x, corner_y - r)
                path.arcTo(beg_x - 2*r, corner_y - 2*r, 2*r, 2*r, 270, 90)
                path.moveTo(beg_x - r, corner_y)
                path.lineTo(end_x + r, corner_y)
                if end_y < corner_y:
                    path.arcTo(end_x, corner_y - 2*r, 2*r, 2*r, 180, 90)
                    path.moveTo(end_x, corner_y - r)
                else:
                    path.arcTo(end_x, corner_y, 2*r, 2*r, 90, 90)
                    path.moveTo(end_x, corner_y + r)
                path.lineTo(end_x, end_y)
            return path
    
    
    def __init__(self, ipf_graph, parent=None):
        super(GraphGrid, self).__init__(parent)
        if ipf_graph is None:
            self.ipf_graph = IPFGraph()
        else:
            self.ipf_graph = ipf_graph
        self.adjust_grid_size()
        
        # Set of GraphBlock objects
        self.graph_blocks = set()
        
        # Dummy block used to show dragged block position
        self.dummy_block = QtGui.QGraphicsRectItem(self)
        self.dummy_block.setRect(0, 0, self.cell_width, self.cell_height)
        self.dummy_block.hide()
        
        # Temporary connection arrow
        self.temp_arrow = QtGui.QGraphicsLineItem(self)
        self.temp_arrow.hide() 
        self.temp_arrow.setZValue(20)
        
        # Connection lines
        self.connection_arrows = []
        
        # Current selected block
        self.selected_block = None
        
        self.paint_mode = GraphBlock.TEXT_PAINT_MODE
        
        # Current selected arrow
        self.selected_arrow = None
        
        
    def paint(self, painter, option, widget):
        pen = painter.pen()
        pen.setColor( QtGui.QColor(200, 200, 200) )
        pen.setWidth(1)
        painter.setPen(pen)
        
        grid_width, grid_height = self.ipf_graph.get_grid_size()
        width = self.cell_width * grid_width + 2 * self.left_margin
        height = self.cell_height * grid_height + 2 * self.top_margin
        for x in range(self.left_margin, width, self.cell_width):
            painter.drawLine(x, 0, x, height)
        for y in range(self.top_margin, height, self.cell_height):
            painter.drawLine(0, y, width, y)
        
    
    def get_cell_in_point(self, point):
        column = int((point[0] - self.left_margin) / self.cell_width)
        row = int((point[1] - self.top_margin) / self.cell_height)
        return (row, column)
    
    
    def adjust_grid_size(self):
        grid_width, grid_height = self.ipf_graph.get_grid_size()
        self.setRect(0, 
                     0, 
                     grid_width * self.cell_width + 2 * self.left_margin,
                     grid_height * self.cell_height + 2 * self.top_margin)
        
    
    def add_block(self, block, row, column):
        """ Add GraphBlock to scheme into specified row and column
        
        """   
        block.setParentItem(self)
        self.graph_blocks.add(block)
        self.update_block_positions()
            
    
    def move_block(self, block, row, column):
        from_cell = self.ipf_graph.get_block_cell(block.block_name)
        if from_cell is not None and self.ipf_graph.grid_cell_empty(row, column):
            self.ipf_graph.move_block(block.block_name, row, column)
            self.update_block_positions()
            self.disable_dummy_block()
            # Enlarge grid if block moved to last row / column
            grid_width, grid_height = self.ipf_graph.get_grid_size()
            if (row == grid_height - 1) and grid_height < self.ipf_graph.max_grid_height:
                self.ipf_graph.add_row()
            if (column == grid_width - 1) and grid_width < self.ipf_graph.max_grid_width:
                self.ipf_graph.add_column()
            self.adjust_grid_size() 
            self.update_connection_arrows()
    
    
    def remove_block(self, block):
        block_name = block.block_name
        block.setParentItem(None)
        self.scene().removeItem(block)
        self.ipf_graph.remove_block(block_name)
        self.graph_blocks.remove(block)
        self.update_connection_arrows()
        self.selected_block = None
    

    def remove_arrow(self, arrow):
        arrow.setParentItem(None)
        self.scene().removeItem(arrow)
        self.ipf_graph.delete_connection(arrow.connection_ref())
        self.connection_arrows.remove(arrow)
        self.update_connection_arrows()
    

    
    def update_block_positions(self):
        for graph_block in self.graph_blocks:
            row, column = self.ipf_graph.get_block_cell(graph_block.block_name)
            x, y = self.get_block_position(row, \
                                           column, \
                                           GraphBlock.block_width, \
                                           GraphBlock.block_width)
            graph_block.setPos(x, y)
                    
    
    def get_block_position(self, row, column, block_width, block_height):
        x = self.cell_width * column + self.left_margin
        y = self.cell_height * row + self.top_margin
        shift_x = (self.cell_width - block_width) / 2
        shift_y = (self.cell_height - block_height) / 2
        x += shift_x
        y += shift_y
        return (x, y)
            
        
    def enable_dummy_block(self):
        self.dummy_block.show()
        
        
    def disable_dummy_block(self):
        self.dummy_block.hide()
        
        
    def set_dummy_block_cell(self, row, column):
        grid_width, grid_height = self.ipf_graph.get_grid_size()
        if row < 0 or \
           row >= grid_height or \
           column < 0 or\
           column >= grid_width:
            # Don`t allow move block outside grid
            return
        rect = self.dummy_block.rect()
        width = rect.width()
        height = rect.height()
        x, y = self.get_block_position(row, column, width, height)
        self.dummy_block.setPos(x, y)
        pen = self.dummy_block.pen()
        if not self.ipf_graph.grid_cell_empty(row, column):
            pen.setColor( QtCore.Qt.red)
        else:
            pen.setColor( QtCore.Qt.black)
        self.dummy_block.setPen(pen)
    
        
    def enable_temp_arrow(self):
        self.temp_arrow.show()
        
        
    def disable_temp_arrow(self):
        self.temp_arrow.hide()
        
        
    def set_temp_arrow_begin(self, x, y):
        self.temp_arrow.setLine(x, y, x, y)
        
        
    def set_temp_arrow_end(self, x, y):
        line = self.temp_arrow.line()
        line.setP2( QtCore.QPoint(x, y) )
        self.temp_arrow.setLine(line)
        
        
    def highlight_arrow(self, highlight):
        pen = self.temp_arrow.pen()
        if highlight:
            pen.setColor(QtCore.Qt.green)
        else:
            pen.setColor(QtCore.Qt.black)
        self.temp_arrow.setPen(pen)
    
        
    def get_port_at_point(self, pos):
        items = self.scene().items(pos)
        for item in items:
            if item is not None:
                if type(item) == graphblock.PortPrimitive:
                    return item
    
    
    def create_connection(self, port1, port2):
        iport_prim = None
        oport_prim = None
        if type(port1.ipf_port) == IPort:
            iport_prim = port1
            oport_prim = port2
        else:
            iport_prim = port2
            oport_prim = port1
        
        self.ipf_graph.add_connection(oport_prim.ipf_port, iport_prim.ipf_port)
        self.update_connection_arrows()
        
        # Notify main_form about graph changing
        main_form = self.scene().parent()
        main_form.graph_changed()
        
        
    def update_connection_arrows(self):
        """ Recreate all connection lines in GraphScheme
        
        """
        
        for arrow in self.connection_arrows:
            arrow.setParentItem(None)
            self.scene().removeItem(arrow)
        self.connection_arrows = []

        grid_width, grid_height = self.ipf_graph.get_grid_size()
        
        for connection in self.ipf_graph.connections:
            arrow = self.create_connection_arrow(connection)
            self.connection_arrows.append(arrow)
       
            
    def create_connection_arrow(self, connection):
        arrow = GraphGrid.ConnectionArrow(self, connection)
        return arrow
    
    
    def block_selected(self, block_primitive):
        """ Notification of block selection change
        
        """
        if self.selected_block is not None:
            self.selected_block.selected = False
            self.selected_block.update()
            
        if self.selected_arrow is not None:
            self.selected_arrow.selected = False
            self.selected_arrow.update()
            self.selected_arrow = None
            
        self.selected_block = block_primitive
        self.selected_block.selected = True
        self.selected_block.update()
        
        graph_block_ref = weakref.ref(block_primitive.parentItem())
        main_form = self.scene().parent()
        main_form.block_selected(graph_block_ref)
    
    
    def connection_arrow_selected(self, arrow):
        """ Notification of connection arrow selected
        
        """
        if self.selected_block is not None:
            self.selected_block.selected = False
            self.selected_block.update()
            self.selected_block = None 

        if self.selected_arrow is not None:
            self.selected_arrow.selected = False
            self.selected_arrow.update()

        self.selected_arrow = arrow
        self.selected_arrow.selected = True
        self.selected_arrow.update()


    def get_block_primitive_from_block(self, ipf_block_ref):
        for graph_block in self.graph_blocks:
            if ipf_block_ref == graph_block.ipf_block_ref:
                return graph_block
        return None
    
    
    def get_block_cell(self, block_name):
        return self.ipf_graph.get_block_cell(block_name)
    
    
    def get_grid_size(self):
        return self.ipf_graph.get_grid_size()
            
        
    def delete_selected(self):
        if self.selected_block is not None:
            self.remove_block(self.selected_block.parentItem())
            del self.selected_block
            self.selected_block = None
        if self.selected_arrow is not None:
            self.remove_arrow(self.selected_arrow)
            del self.selected_arrow
            self.selected_arrow = None

    
    def get_selected_block(self):
        if self.selected_block is not None:
            return self.selected_block.parentItem().ipf_block_ref()
        else:
            return None
    
    
    def load_graph(self, ipf_graph):
        self.selected_block = None
        self.ipf_graph = ipf_graph
        for graph_block in self.graph_blocks:
            graph_block.setParent(None)
            del graph_block
        self.graph_blocks = set()
        for block_name in self.ipf_graph.blocks():
            row, column = self.ipf_graph.get_block_cell(block_name)
            block = graphblock.GraphBlock(
                    weakref.ref(self.ipf_graph.get_block(block_name)), 
                    block_name)
            self.add_block(block, row, column)
        self.update_connection_arrows()
            
    
    def set_block_paint_mode(self, mode):
        self.paint_mode = mode
        for block in self.graph_blocks:
            block.set_paint_mode(mode)
        
    
    def update(self):
        for block in self.graph_blocks:
            block.set_paint_mode(self.paint_mode)
            block.update_preview_image()
    
    
    def rearrange(self):
        self.ipf_graph.rearrange()
        self.update_block_positions()
        self.update_connection_arrows()