Пример #1
0
class DiagramScene(QDMGraphicsScene):
    InsertItem, InsertLine, MoveItem, SelectItem = list(range(4))

    # itemInserted = pyqtSignal(DiagramItem)
    itemSelected = pyqtSignal(QGraphicsItem)
    cuteSignal = pyqtSignal(str)
    resetModeSignal = pyqtSignal()
    editSignal = pyqtSignal()

    def __init__(self, cKey):
        super(DiagramScene, self).__init__()
        self.controllerKey = cKey
        self.showDynamicMenu = True
        self.myMode = self.MoveItem
        self.myItemColor = Qt.white
        self.myLineColor = Qt.black
        self.line = None
        self.virtualRect = None
        self.selectRect = None
        self.errorRect = None
        self.selectPoint = None
        self.myFont = QFont()
        self.undoStack = QUndoStack(self)
        # 用户hash node id -> node的映射
        # 方便根据node id来查找到node
        self.cache = {}
        self.uuididxmap = {}
        self.isGlobal = None
        self.isRun = False

        self.breakPoints = []  # [uuid]

    def setUuidIdxMap(self, uuididx):
        self.uuididxmap = uuididx

    def createContextMenu(self):
        """创建右键菜单栏"""
        self.menu = QMenu()

        menuTreeData = ControllerManager().menuTree.tree
        self.genDynamicMenu(menuTreeData, self.menu)
        for submenu in self.menu.children()[1:]:
            if submenu.isEmpty():
                self.menu.removeAction(submenu.menuAction())

    def genDynamicMenu(self, data, parentMenu):
        for category in list(data.keys()):
            if type(data[category]) == OrderedDict:
                subMenu = parentMenu.addMenu(category)
                self.genDynamicMenu(data[category], subMenu)
            else:
                action_id = category
                action_name = data[category][0]
                action = parentMenu.addAction(action_name)
                action.triggered.connect(
                    partial(self.chooseInsertItem, [action_name, action_id]))

    def contextMenuEvent(self, event):
        transform = QTransform(1, 0, 0, 0, 1, 0, 0, 0, 1)
        if self.itemAt(event.scenePos(), transform):
            QGraphicsScene.contextMenuEvent(self, event)
        else:
            if not self.showDynamicMenu:
                return
            self.menu.exec_(event.screenPos())
        super().contextMenuEvent(event)

    def chooseInsertItem(self, name):
        controller = ControllerManager().getController(self.controllerKey)
        controller.selectedItemType = ItemType(*name)
        self.setMode(DiagramScene.InsertItem)

    def cuteMessage(self, message):
        print('in cute')

    def setLineColor(self, color):
        self.myLineColor = color
        if self.isItemChange(Arrow):
            item = self.selectedItems()[0]
            item.setColor(self.myLineColor)
            self.update()

    def setItemColor(self, color):
        self.myItemColor = color
        if self.isItemChange(DiagramItem):
            item = self.selectedItems()[0]
            item.setBrush(self.myItemColor)

    def setItemTitle(self, title):
        for item in self.selectedItems():
            if isinstance(item, DiagramItem):
                item.setTitle(title)
                break

    def setMode(self, mode):
        self.myMode = mode

    def mousePressEvent(self, event):
        if event.button() != Qt.LeftButton:
            return

        if self.myMode == self.InsertItem:
            self.addDiagramItem(event.scenePos())
            if self.virtualRect is not None:
                self.removeItem(self.virtualRect)
            self.virtualRect = None
        elif self.myMode == self.InsertLine:
            # self.line = QGraphicsLineItem(QLineF( event.scenePos(),
            #	event.scenePos()))
            self.line = CosineLine(QLineF(event.scenePos(), event.scenePos()))
            self.addItem(self.line)
        elif self.myMode == self.MoveItem:
            mousePos = event.scenePos()
            item = self.itemAt(mousePos.x(), mousePos.y(), QTransform())

            if isinstance(item, DiagramItemOutput) or \
                    isinstance(item, DiagramItemRow):
                self.myMode = self.InsertLine
                self.line = CosineLine(
                    QLineF(event.scenePos(), event.scenePos()))
                self.addItem(self.line)
            elif item is None:
                self.myMode = self.SelectItem
                self.selectPoint = event.scenePos()
                self.selectRect = SelectionRect(
                    QRectF(self.selectPoint, QSizeF(1, 1)))
                self.addItem(self.selectRect)

        super(DiagramScene, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if self.myMode == self.InsertLine and self.line:
            newLine = QLineF(self.line.line().p1(), event.scenePos())
            self.line.setLine(newLine)
        elif self.myMode == self.MoveItem:
            super(DiagramScene, self).mouseMoveEvent(event)
        elif self.myMode == self.InsertItem:
            if self.virtualRect is None:
                self.virtualRect = VirtualRect()
                self.addItem(self.virtualRect)
                self.virtualRect.setPos(event.scenePos())
            else:
                self.virtualRect.setPos(event.scenePos())
        elif self.myMode == self.SelectItem:
            v = event.scenePos() - self.selectPoint
            rect = QRectF(self.selectPoint, QSizeF(v.x(), v.y()))
            newRect = rect.normalized()
            self.selectRect.setRect(newRect)

    def mouseReleaseEvent(self, event):
        if self.myMode == self.InsertLine and self.line:
            startItems = self.items(self.line.line().p1())
            if len(startItems) and startItems[0] == self.line:
                startItems.pop(0)
            endItems = self.items(self.line.line().p2())
            if len(endItems) and endItems[0] == self.line:
                endItems.pop(0)

            self.removeItem(self.line)

            if len(startItems) and len(
                    endItems) and startItems[0] != endItems[0]:
                # isinstance(startItems[0],DiagramItemRow) and \
                # isinstance(endItems[0],DiagramItemRow) and \
                # 此处由于连线的原因,
                # startItem 是Output
                # endItem 是 Input
                # 建立连接时,应该调换过来
                self.makeConnection(startItems[0], endItems[0])
            self.resetMode()
        elif self.myMode == self.MoveItem:
            mousePos = event.scenePos()
            item = self.itemAt(mousePos.x(), mousePos.y(), QTransform())
        elif self.myMode == self.SelectItem:
            if self.selectRect is not None:
                gitems = self.items(self.selectRect.rect(),
                                    Qt.IntersectsItemShape)

                hasSelects = False

                for gitem in gitems:
                    if isinstance(gitem, DiagramItem) or \
                            isinstance(gitem, CosineConnection) or \
                            isinstance(gitem, FreeCommentItem):
                        gitem.setSelected(True)
                        hasSelects = True

                if not hasSelects:
                    # 点选连线附近,可以选中连线
                    mousePos = event.scenePos()
                    topLeftPoint = QPointF(mousePos.x(),
                                           mousePos.y()) + QPointF(-5, -5)
                    tinyRect = QRectF(topLeftPoint, QSizeF(10, 10))
                    titems = self.items(tinyRect, Qt.IntersectsItemShape)
                    for titem in titems:
                        if isinstance(titem, CosineConnection):
                            titem.setSelected(True)

                self.removeItem(self.selectRect)
                self.resetMode()
                self.selectRect = None
                self.selectPoint = None

        self.line = None
        super(DiagramScene, self).mouseReleaseEvent(event)

    def resetMode(self):
        self.setMode(self.MoveItem)
        self.resetModeSignal.emit()

    def addDiagramItem(self, scenePos):
        controller = ControllerManager().getController(self.controllerKey)
        data = {'type': '%s' % controller.selectedItemType.typeId}
        data.update({'pos': {'x': scenePos.x(), 'y': scenePos.y()}})
        command = CommandAdd(self, data)
        self.undoStack.push(command)

        # 跟踪计数
        ControllerManager().tracker.trackAdd(
            controller.selectedItemType.typeId)
        self.resetMode()

    def findAnyInputTypeSet(self, item):
        """
        找到
        """
        anyItemInputTypes = []
        for chItem in item.childItems():
            if isinstance(chItem, DiagramItemInput) and \
                    chItem.itemContent.contentType == 'Any':
                for arrow in chItem.arrows:
                    anyItemInputTypes.append(arrow.connectionType)

        return anyItemInputTypes if len(anyItemInputTypes) != 0 else None

    def makeConnection(self, startItem, endItem):
        """
        带Undo/Redo功能的建立连接
        """
        restrictValueTypes = None
        if startItem.itemContent.contentType == 'Any':
            # 搜索大节点的所有输入值,找到Any类型的输入的附带值类型集合
            restrictValueTypes = self.findAnyInputTypeSet(
                startItem.parentItem())
        # print 'restrictValueTypes', restrictValueTypes

        if not mechanism.canConnect(startItem, endItem, restrictValueTypes):
            return

        controller = ControllerManager().getController(self.controllerKey)
        if endItem.itemContent.contentType == 'Event':
            # Event,可以有任意多的入边
            edgeData = {
                'start': startItem.parentItem().uuid,
                'end': endItem.parentItem().uuid,
                'startItemTypeId': startItem.itemType.typeId,
                'endItemTypeId': endItem.itemType.typeId
            }

            command = CommandLink(self, [deepcopy(edgeData)],
                                  description='add a link')
            self.undoStack.push(command)
            self.editSignal.emit()

        else:
            # 别的类型,只能有一条入边
            # 且后续如果有别的边想要连入,必须删除之前连入的边
            if len(endItem.arrows) == 0:
                # 没有连入的边
                edgeData = {
                    'start': startItem.parentItem().uuid,
                    'end': endItem.parentItem().uuid,
                    'startItemTypeId': startItem.itemType.typeId,
                    'endItemTypeId': endItem.itemType.typeId
                }
                command = CommandLink(self, [deepcopy(edgeData)],
                                      description='add a link')
                self.undoStack.push(command)
                self.editSignal.emit()
            else:
                # 有连入的边,删除原来的边
                oldArrow = endItem.arrows[0]
                edgeData = {
                    'start': oldArrow.startItem().parentItem().uuid,
                    'end': oldArrow.endItem().parentItem().uuid,
                    'startItemTypeId': oldArrow.startItem().itemType.typeId,
                    'endItemTypeId': oldArrow.endItem().itemType.typeId
                }
                delCommand = CommandUnLink(self, [edgeData],
                                           description='remove an arrow')
                self.undoStack.push(delCommand)

                # 再添加新边
                arrow = CosineConnection(startItem, endItem)
                if startItem.itemContent.contentType != 'Any':
                    arrow.setConnectionType(startItem.itemContent.contentType)
                    arrow.setColor(startItem.itemContentColor)
                else:
                    arrow.setConnectionType(endItem.itemContent.contentType)
                    arrow.setColor(endItem.itemContentColor)
                # endItem.itemContent.contentValue = None
                edgeData = {
                    'start': arrow.startItem().parentItem().uuid,
                    'end': arrow.endItem().parentItem().uuid,
                    'startItemTypeId': arrow.startItem().itemType.typeId,
                    'endItemTypeId': arrow.endItem().itemType.typeId
                }
                command = CommandLink(self, [deepcopy(edgeData)],
                                      description='add a link')
                self.undoStack.push(command)
                self.editSignal.emit()

    def simpleMakeConnection(self, startItem, endItem):
        """
        不带Undo/Redo功能的建立连接,做必要的检查
        """
        if not mechanism.canConnect(startItem, endItem):
            return

        controller = ControllerManager().getController(self.controllerKey)

        if endItem.itemContent.contentType == 'Event':
            # Event,可以有任意多的入边
            self.directConnection(startItem, endItem)
        else:
            # 别的类型,只能有一条入边
            # 且后续如果有别的边想要连入,必须删除之前连入的边
            if len(endItem.arrows) == 0:
                # 没有连入的边
                self.directConnection(startItem, endItem)
            else:
                # 有连入的边,删除原来的边
                oldArrow = endItem.arrows[0]
                delCommand = CommandUnLink(controller,
                                           oldArrow,
                                           description='remove an arrow')
                self.undoStack.push(delCommand)

                # 在添加新边
                self.directConnection(startItem, endItem)
        self.editSignal.emit()

    def directConnection(self, startItem, endItem):
        """
        直接建立连接,不做任何检查
        """
        controller = ControllerManager().getController(self.controllerKey)

        arrow = CosineConnection(startItem, endItem)
        # print 'start type', startItem.itemContent.contentType
        if startItem.itemContent.contentType != 'Any':
            arrow.setConnectionType(startItem.itemContent.contentType)
            arrow.setColor(startItem.itemContentColor)
        else:
            arrow.setConnectionType(endItem.itemContent.contentType)
            arrow.setColor(endItem.itemContentColor)

        # controller.addEdge(startItem.parentItem(),
        # 		endItem.parentItem(),
        # 		startItem.itemType,
        # 		endItem.itemType,
        # 		startItem.itemContent.contentType)
        controller.addEdge(startItem.parentItem(), endItem.parentItem(),
                           startItem.itemType, endItem.itemType,
                           arrow.connectionType)
        startItem.addArrow(arrow)
        endItem.addArrow(arrow)
        arrow.setZValue(-1000.0)
        self.addItem(arrow)
        arrow.updatePosition()
        # 连线后不再具有值
        endItem.itemContent.contentValue = None
        startItem.update()
        endItem.update()

        self.editSignal.emit()

    def simpleDisconnection(self, arrow):
        """
        简单地删除连接,不包含Undo/Redo功能
        """
        controller = ControllerManager().getController(self.controllerKey)
        controller.removeEdge(arrow.startItem().parentItem(),
                              arrow.endItem().parentItem(),
                              arrow.startItem().itemType.typeId,
                              arrow.endItem().itemType.typeId)
        arrow.startItem().removeArrow(arrow)
        arrow.endItem().removeArrow(arrow)
        # 恢复默认值
        arrow.endItem().itemContent.contentValue = arrow.endItem(
        ).itemContent.defaultValue
        arrow.startItem().update()
        arrow.endItem().update()
        self.removeItem(arrow)

        self.editSignal.emit()

    def isItemChange(self, type):
        for item in self.selectedItems():
            if isinstance(item, type):
                return True
        return False

    def deleteSelectedItems(self):
        """
        删除所选择的items,先删除边,后删除节点,
        """
        edgesData = []
        controller = ControllerManager().getController(self.controllerKey)
        for item in self.selectedItems():
            if isinstance(item, ConnectionBase):
                if item.startItem().parentItem().isSelected() or item.endItem(
                ).parentItem().isSelected():
                    continue

                edgeData = {
                    'start': item.startItem().parentItem().uuid,
                    'end': item.endItem().parentItem().uuid,
                    'startItemTypeId': item.startItem().itemType.typeId,
                    'endItemTypeId': item.endItem().itemType.typeId
                }
                edgesData.append(edgeData)

        if len(edgesData) != 0:
            command = CommandUnLink(self,
                                    edgesData,
                                    description='remove an/several arrows')
            self.undoStack.push(command)

        itemDatasWithEdge = []

        for item in self.selectedItems():
            if isinstance(item, DiagramItem):
                itemData = self.itemToDataWithEdge(item)
                itemDatasWithEdge.append(itemData)
            elif isinstance(item, FreeCommentItem):
                controller.removeFreeComment(item)
                self.removeItem(item)

        if len(itemDatasWithEdge) != 0:
            command = CommandDelete(self,
                                    itemDatasWithEdge,
                                    description='delete an or serveral items')
            self.undoStack.push(command)

    def selectedItemsCenter(self):
        poses = []
        for item in self.selectedItems():
            poses.append(item.pos())

        if len(poses) == 0:
            return None

        avgx, avgy = 0.0, 0.0
        for p in poses:
            avgx += p.x()
            avgy += p.y()
        avgx /= len(poses)
        avgy /= len(poses)

        return QPointF(avgx, avgy)

    def selectedNodesCenter(self):
        # This function is to replace selectedItemsCenter in copy and past items
        # because selectedItemsCenter has two disadvantages:
        # 1.contains edges, which makes the center out of the cluster of nodes
        # 2.actually the result is not center, but the weight center
        xRange = []
        yRange = []
        for item in self.selectedItems():
            if isinstance(item, DiagramItem):
                pos = item.pos()
                xRange.append(pos.x())
                yRange.append(pos.y())
        if xRange:
            avgX = (min(xRange) + max(xRange)) / 2.0
            avgY = (min(yRange) + max(yRange)) / 2.0
        else:
            avgX = 0.0
            avgY = 0.0
        return QPointF(avgX, avgY)

    def selectedItemsBoundingRect(self):
        hasFlag = False
        xmin, xmax = 0.0, 0.0
        ymin, ymax = 0.0, 0.0
        for item in self.selectedItems():
            if isinstance(item, CosineConnection):
                continue

            hasFlag = True
            polygon = item.polygon()
            for i in [polygon[0], polygon[2]]:
                p = i + item.pos()
                if p.x() < xmin:
                    xmin = p.x()
                if p.x() > xmax:
                    xmax = p.x()
                if p.y() < ymin:
                    ymin = p.y()
                if p.y() > ymax:
                    ymax = p.y()

        if not hasFlag:
            return None

        return QRectF(QPointF(xmin, ymin), QSizeF(xmax - xmin, ymax - ymin))

    def getSelectedNodes(self):
        nodes = []
        for item in self.selectedItems():
            if isinstance(item, DiagramItem):
                nodes.append(item)
        return nodes

    def cmp_(self, x, y):
        if x.depth < y.depth:
            return -1
        elif x.depth == y.depth:
            return 0
        else:
            return 1

    def _sortSelectedDiagramItemByDepth(self):
        selected = []
        for item in self.selectedItems():
            if isinstance(item, DiagramItem):
                selected.append(item)
        selected = sorted(selected, key=cmp_to_key(self.cmp_))
        # selected = sorted(selected, self.cmp_(x, y))
        return selected

    def copyItem(self):
        """
        拷贝项目
        1. 求得当前选中的所有项目的外接矩形的中心 center。
        2. 将选中的每个项目依次转化成带边数据itemDataWithEdge,
           转化数据时,利用center和item本身的位置计算出相对位置。
        """
        selected = []
        center = self.selectedNodesCenter()
        for item in self._sortSelectedDiagramItemByDepth():
            selected.append(self.itemToDataWithEdge(item, center))
        if len(selected) == 0:
            return

        for item in self.selectedItems():
            item.setSelected(False)

        itemDatasWithEdge = deepcopy(selected)

        GraphisClipboard().setGraphicsItems(itemDatasWithEdge)

    def cutItem(self):
        selected = []
        center = self.selectedItemsCenter()
        for item in self._sortSelectedDiagramItemByDepth():
            selected.append(self.itemToDataWithEdge(item, center))
        if len(selected) == 0:
            return

        GraphisClipboard().setGraphicsItems(selected)
        self.deleteSelectedItems()

    def pasteItem(self, mousePos=None):
        """
        粘贴项目
        从剪贴板中拿到带边的节点数据,替换原有id后,交给CommandAdd处理,
        """
        if not GraphisClipboard().hasGraphicsItems():
            return

        itemDatasWithEdge = GraphisClipboard().graphicsItems()

        controller = ControllerManager().getController(self.controllerKey)

        # 生成和替换id
        old2NewIds = {}
        for itemData in itemDatasWithEdge:
            oldId = itemData['node']['id']
            newId = controller.genUUID()
            old2NewIds[oldId] = newId

        self.replaceWithIdMap(itemDatasWithEdge, old2NewIds)

        command = CommandAdd(self,
                             itemDatasWithEdge,
                             description='add one/serveral items',
                             mousePosition=mousePos)
        self.undoStack.push(command)

    def findSubItem(self, item, subItemTypeId):
        for subItem in item.childItems():
            if isinstance(subItem, DiagramItemInput) or isinstance(
                    subItem, DiagramItemOutput):
                if subItem.itemType.typeId == subItemTypeId:
                    return subItem

        return None

    def replaceWithIdMap(self, itemDatasWithEdge, old2NewIds):
        for oldId, newId in list(old2NewIds.items()):
            # 替换入边中的 id
            for itemData in itemDatasWithEdge:
                inEdgesData = itemData['inEdgeData']
                outEdgeData = itemData['outEdgeData']

                for edgeData in inEdgesData:
                    if edgeData['start'] == oldId: edgeData['start'] = newId
                    if edgeData['end'] == oldId: edgeData['end'] = newId

                for edgeData in outEdgeData:
                    if edgeData['start'] == oldId: edgeData['start'] = newId
                    if edgeData['end'] == oldId: edgeData['end'] = newId

                if itemData['node']['id'] == oldId:
                    itemData['node']['id'] = newId

    def itemToData(self, item, center):
        controller = ControllerManager().getController(self.controllerKey)
        itemData = controller.nodeToData(item, center)

        return itemData

    def itemToDataWithEdge(self, item, center=None):
        controller = ControllerManager().getController(self.controllerKey)
        itemDataWithEdge = controller.nodeToDataWithEdge(item, center)

        return itemDataWithEdge

    def widerSelectedItems(self):
        boundingRect = self.selectedItemsBoundingRect()
        for item in self.selectedItems():
            if isinstance(item, DiagramItem):
                item.wider()

        if boundingRect is not None:
            self.update(boundingRect)

    def thinnerSelectedItems(self):
        boundingRect = self.selectedItemsBoundingRect()
        for item in self.selectedItems():
            if isinstance(item, DiagramItem):
                item.thinner()

        if boundingRect is not None:
            self.update(boundingRect)

    def commentSelectedItems(self):
        for item in self.selectedItems():
            if isinstance(item, DiagramItem):
                item.commentMe()

    def freeComment(self):
        view = self.views()[0]
        sz = view.size()
        width, height = sz.width(), sz.height()
        topLeft = view.mapToScene(0, 0)
        topLeftF = QPointF(topLeft.x(), topLeft.y())
        fCommentPos = topLeftF + QPointF(width / 3, height / 3)
        freec = FreeCommentItem('自由注释')
        self.addItem(freec)
        freec.setPos(fCommentPos)

        controller = ControllerManager().getController(self.controllerKey)
        controller.addFreeComment(freec)

    def foldSelectedItems(self):
        boundingRect = self.selectedItemsBoundingRect()

        for item in self.selectedItems():
            if isinstance(item, DiagramItem):
                item.fold()

        if boundingRect is not None:
            self.update(boundingRect)

    def unfoldSelectedItems(self):
        boundingRect = self.selectedItemsBoundingRect()

        for item in self.selectedItems():
            if isinstance(item, DiagramItem):
                item.unfold()

        if boundingRect is not None:
            self.update(boundingRect)

    def locateError(self, iD, subItemId):
        """
        定位导表错误的位置,并用红色矩形框将错误项框出
        """
        # 找到父节点
        if self.errorRect is None:
            self.errorRect = ErrorRect(QRectF(0, 0, 10, 10))
            self.addItem(self.errorRect)

        assert iD in self.cache, '%s not in item cache' % iD
        targetItem = self.cache[iD]
        for subItem in targetItem.childItems():
            if isinstance(
                    subItem,
                    DiagramItemInput) and subItem.itemType.typeId == subItemId:
                pos = subItem.scenePos()
                self.views()[0].centerOn(pos)
                self.errorRect.setRect(subItem.boundingRect())
                self.errorRect.setPos(pos)
                return

    def clearError(self):
        self.removeItem(self.errorRect)
        self.errorRect = None

    def locateFind(self, node):
        pos = node.scenePos()
        self.views()[0].centerOn(pos)
        node.setHighLight(1)

    def locateFindNode(self, prevNodeList, nextNodeList):
        for prevNode in prevNodeList:
            prevNode.setHighLight(2)
        for nextNode in nextNodeList:
            nextNode.setHighLight(2)

    def locateFindEdge(self, prevEdgeList, nextEdgeList):
        for edge in prevEdgeList:
            edge.setHighLight()
        for edge in nextEdgeList:
            edge.setHighLight()

    def clearFind(self, targetNode, prevNodeList, nextNodeList, prevEdgeList,
                  nextEdgeList):
        if targetNode is None:
            return
        targetNode.removeHighLight()
        for prevNode in prevNodeList:
            prevNode.removeHighLight()
        for nextNode in nextNodeList:
            nextNode.removeHighLight()
        for prevEdge in prevEdgeList:
            prevEdge.removeHighLight()
        for nextEdge in nextEdgeList:
            nextEdge.removeHighLight()

    def addToCache(self, item):
        if item.uuid not in self.cache:
            self.cache[item.uuid] = item
        else:
            print(item.uuid, 'already existes in scene')

    def removeFromCache(self, item):
        if item.uuid in self.cache:
            del self.cache[item.uuid]
        else:
            print('error', item.uuid, 'do not exist in scene')
Пример #2
0
class TemplateScene(QDMGraphicsScene):
    MoveItem, SelectItem = list(range(2))

    itemSelected = pyqtSignal(QGraphicsItem)
    editSignal = pyqtSignal()

    def __init__(self):
        super(self.__class__, self).__init__()
        self.showDynamicMenu = True
        self.virtualRect = None
        self.selectRect = None
        self.selectPoint = None
        self.myMode = self.MoveItem

        self.autoGenAndLayoutMetaItems()

    def autoGenAndLayoutMetaItems(self):
        nodesDefData = loadJsonData('meta/nodes.json')
        if os.path.exists('meta/template.json'):
            self.templateData = loadJsonData('meta/template.json')
        else:
            self.templateData = {}

        leftMark = QPointF(-2000, -2050)
        topLeft = QPointF(-2000, -2050)
        lastCategory = None
        lastMaxHeight = 0

        for nodeDef in nodesDefData:
            category = nodeDef['category']
            if category != lastCategory:
                leftMark = leftMark + QPointF(0, lastMaxHeight + 50)
                topLeft = leftMark
                lastCategory = category
                lastMaxHeight = 0

            item = self.addDiagramItem(nodeDef)
            itemRect = item.boundingRect()
            w, h = itemRect.width(), itemRect.height()
            item.setPos(topLeft + QPointF(w / 2, h / 2))
            if h > lastMaxHeight:
                lastMaxHeight = h

            topLeft = topLeft + QPoint(w + 30, 0)

    def saveTemplate(self):
        dumpJsonData('meta/template.json', self.templateData)

    def mousePressEvent(self, event):
        if event.button() != Qt.LeftButton:
            return

        mousePos = event.scenePos()
        item = self.itemAt(mousePos.x(), mousePos.y(), QTransform())

        if item is None:
            self.myMode = self.SelectItem
            self.selectPoint = event.scenePos()
            self.selectRect = SelectionRect(
                QRectF(self.selectPoint, QSizeF(1, 1)))
            self.addItem(self.selectRect)

        super(self.__class__, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if self.myMode == self.MoveItem:
            super(self.__class__, self).mouseMoveEvent(event)
        elif self.myMode == self.SelectItem:
            v = event.scenePos() - self.selectPoint
            rect = QRectF(self.selectPoint, QSizeF(v.x(), v.y()))
            newRect = rect.normalized()
            self.selectRect.setRect(newRect)

    def mouseReleaseEvent(self, event):
        if self.myMode == self.MoveItem:
            mousePos = event.scenePos()
            item = self.itemAt(mousePos.x(), mousePos.y(), QTransform())
        elif self.myMode == self.SelectItem:
            if self.selectRect is not None:
                gitems = self.items(self.selectRect.rect(),
                                    Qt.IntersectsItemShape)

                hasSelects = False

                for gitem in gitems:
                    if isinstance(gitem, DiagramItem) or \
                            isinstance(gitem, FreeCommentItem):
                        gitem.setSelected(True)
                        hasSelects = True

                self.removeItem(self.selectRect)
                self.resetMode()
                self.selectRect = None
                self.selectPoint = None

        self.line = None
        super(self.__class__, self).mouseReleaseEvent(event)

    def resetMode(self):
        self.myMode = self.MoveItem

    def addDiagramItem(self, data):
        item = DiagramItem(data=data, mode='template')
        self.addItem(item)

        return item

    def updateItemTextNote(self, typeId, note):
        assert type(note) == list
        print('update note for ', typeId, note)
        self.templateData[typeId] = note
        self.editSignal.emit()

    def getNoteText(self, typeId):
        note = self.templateData.get(typeId, None)
        return note[0] if note is not None else ''

    def getNote(self, typeId):
        return self.templateData.get(typeId, None)