Example #1
0
class RadarGraphicsScene(QtGui.QGraphicsScene):
    """
    Reimplemented to access the public methods to do my own thing.
    """
    sceneDoubleClicked = Signal(QtCore.QPointF)
    radarItemClicked = Signal(RadarGraphicsItem)
    radarItemAdded = Signal(RadarGraphicsItem)
    radarItemMoved = Signal(RadarGraphicsItem)

    def __init__(self,*args, **kwargs):
        super(RadarGraphicsScene, self).__init__(*args, **kwargs)
        self.mongoSceneHandle = None
        self.radarItemTableModel = None

        self.backgroundBrush = QtGui.QBrush(QtGui.QColor.fromRgb(34, 17, 17, 50))
        self.backgroundPenLines = QtGui.QPen(QtGui.QColor.fromRgb(0153, 38, 0, 25), 2)
        self.backgroundPenRings = QtGui.QPen(QtGui.QColor.fromRgb(153, 38, 0, 75), 2)
        self.backgroundPenLinesBold = QtGui.QPen(QtGui.QColor.fromRgb(153, 38, 0, 80), 2)

        self.setupBackground()
        self.setupAnimatedRadar()
        self.attributeEditor = None
        self.listPanel = None

        self._itemDict = {}

    def initScene(self, mongoSceneHandle, attribEditor, listPanel):
        """
        Setup the scene with the data form the db, builds the table model for the views to connect to.
        :param mongoSceneHandle: MongoSceneHandle, RadarAttributeEditor, RadarListPanel
        :return: None
        """
        self.mongoSceneHandle = mongoSceneHandle
        self.sourceModel = RadarItemsTableModel(mongoSceneHandle)
        self.proxyModel = ItemFilterProxyMode(self)
        self.proxyModel.setSourceModel(self.sourceModel)
        self.attributeEditor = attribEditor
        self.listPanel = listPanel
        self.attributeEditor.clearData()
        assert isinstance(self.attributeEditor, RadarAttributeEditor)
        assert isinstance(self.listPanel, RadarListPanel)
        self.listPanel.form.filter_lineEdit.textChanged.connect(self.proxyModel.setFilterRegExp)
        self.listPanel.radarListSelectionChanged.connect(self.selectRadarItemByID)
        self.proxyModel.setFilterKeyColumn(self.sourceModel.columns.index("name"))

        # Add all the items to the scene
        for i in self.mongoSceneHandle.items():
            graphicsItem = RadarGraphicsItem()
            graphicsItem.setId(i["_id"])
            graphicsItem.setPos(i["pos"][0], i["pos"][1])
            graphicsItem.setColour(QtGui.QColor(*i["colour"]))
            graphicsItem.record = i
            self.addItem(graphicsItem)
            self.sourceModel.updateGraphicsItemColour.connect(graphicsItem.updateColour)


    def addItem(self, item):
        idx = getattr(item, 'id', '')
        if idx:
            self._itemDict[str(idx())] = item
        return super(RadarGraphicsScene, self).addItem(item)

    def removeItem(self, item):
        idx = getattr(item, 'id', '')
        if idx:
            self._itemDict.pop(str(idx()), None)
        super(RadarGraphicsScene, self).removeItem(item)

    def setActive(self):
        if self.attributeEditor:
            self.attributeEditor.setGraphicsScene(self)
        if self.listPanel:
            self.listPanel.setGraphicsScene(self)

    def radarItemToProxyIndex(self, item, columnName='name'):
        index = self.radarItemToSourceIndex(item, columnName)
        if index.isValid() and index.model() is self.sourceModel:
            return self.proxyModel.mapFromSource(index)

    def radarItemToSourceIndex(self, item, columnName='name'):
        rowData = self.sourceModel.rowModelIndexFromId(item.id())
        if rowData:
            return rowData[columnName]

    def radarItemToSourceRowIndexData(self, item):
        return self.sourceModel.rowModelIndexFromId(item.id())

    def updateItemRecord(self, item):
        record = self.sourceModel.rawDataFromId(item.id())
        item.record = record

    def selectRadarItemByID(self, scene, idx):
        """
        Handles selecting of radar items in the scene by the item id from mongoDB.  Other views can pass the id
        via a signal.  This slot can then handle the the selection of the items in the scene from the id
        :param idx: str
        :return: None
        """
        # Grab the View from the current Tab and thus dig down into the scene graph.
        # The the method to extract the radarItem.

        if scene is self:
            log.debug("Select Radar Item by ID")
            item = self.itemFromID(idx)
            log.debug("Found GraphicsItem in Radar : {0}".format(item))
            if item:
                # It was difficult to find out the best way to select items in the scene.  This is like drawing
                # a box selection on the viewport and the contained items are selected.
                self.clearSelection()
                painterPath = QtGui.QPainterPath()
                painterPath.addRect(item.boundingRect())
                painterPath.translate(item.scenePos())
                self.attributeEditor.setRadarItem(item)
                self.setSelectionArea(painterPath, QtGui.QTransform())

    def maxRadarDiameter(self):
        return self.width() + self.height() / 2

    def setupBackground(self):
        """
        I was so tempted to just download a nice picture but in the end I manually drew the radar on the canvas
        :return: None
        """
        backgroundBrush = self.backgroundBrush
        backgroundPenLines = self.backgroundPenLines
        backgroundPenRings = self.backgroundPenRings
        diameter = self.maxRadarDiameter()
        topY = diameter / 2 * -1
        topX = diameter / 2 * -1
        bottomX = topX * -1
        bottomY = topY * -1
        ringCount = 10
        ringOffset = diameter / ringCount
        screenPosX = (diameter / 2) * -1
        screenPosY = (diameter / 2) * -1
        numberOfLines = ringCount * 2

        # draw the radar circles
        for i in range(ringCount):
            self.addEllipse(0-(diameter/2), 0-(diameter/2), diameter, diameter, backgroundPenRings, backgroundBrush)
            diameter -= ringOffset

        # overlay the grid
        for i in range(numberOfLines):
            if i not in [0, numberOfLines]:
                l = self.addLine(screenPosX, topX, screenPosX+1, bottomX, backgroundPenLines)
                ll = self.addLine(topX, screenPosY, bottomX, screenPosY+1, backgroundPenLines)

                ## Draw the darker center lines
                if screenPosX == 0:
                    l.setPen(self.backgroundPenLinesBold)
                if screenPosY == 0:
                    ll.setPen(self.backgroundPenLinesBold)
            screenPosX += ringOffset / 2
            screenPosY += ringOffset / 2

    def setupAnimatedRadar(self):
        pen = QtGui.QPen(QtGui.QColor.fromRgb(153, 38, 0, 25), 2)
        brush = QtGui.QBrush(QtGui.QColor.fromRgb(0, 150, 150, 25))
        rad = self.maxRadarDiameter() / 2
        self.radarAnimItem = self.addEllipse(0-rad, 0-rad, rad*2, rad*2, pen, brush)
        self.radarAnimItem.setPos(0,0)
        self.radarAnimItem.setSpanAngle(360 * 2)
        self.timeline = QtCore.QTimeLine(5000)
        self.timeline.setEasingCurve(QtCore.QEasingCurve.Linear)
        self.timeline.setLoopCount(0)
        self.timeline.setFrameRange(0, 1)
        self.animclip = QtGui.QGraphicsItemAnimation()
        self.animclip.setItem(self.radarAnimItem)
        self.animclip.setTimeLine(self.timeline)
        self.animclip.setPosAt(0, QtCore.QPointF(0, 0))
        self.animclip.setRotationAt(1, 360)
        self.timeline.start()
        self.timeline.valueChanged.connect(self.itemAnimUpdate)

    def itemAnimUpdate(self, f):
        collidingItems = [i for i in self.collidingItems(self.radarAnimItem) if i in self._itemDict.values()]
        if collidingItems:
            i = random.choice(collidingItems)
            i.play()

        nonCollidingItems = [i for i in self._itemDict.values() if i not in collidingItems]
        for i in nonCollidingItems:
            i.stop()


    def showHideAnimatedRadar(self, state):
        item = getattr(self, "radarAnimItem", None)
        if item:
            if state:
                self.timeline.stop()
                item.hide()
            else:
                item.show()
                self.timeline.start()

    def mouseDoubleClickEvent(self, QGraphicsSceneMouseEvent):
        if QGraphicsSceneMouseEvent.button() == QtCore.Qt.LeftButton and \
                QGraphicsSceneMouseEvent.modifiers() == QtCore.Qt.ControlModifier:
            self.addRadarItem(QGraphicsSceneMouseEvent.scenePos())
        return super(RadarGraphicsScene, self).mouseDoubleClickEvent(QGraphicsSceneMouseEvent)

    def mousePressEvent(self, QGraphicsSceneMouseEvent, **kwargs):
        item = self.itemAt(QGraphicsSceneMouseEvent.scenePos())
        if item:
            if getattr(item, "id", ""):
                item.cachePosition()
                self.updateItemRecord(item)
                self.listPanel.selectRadarItem(self, item)
                self.attributeEditor.setRadarItem(item)
            return super(RadarGraphicsScene, self).mousePressEvent(QGraphicsSceneMouseEvent)

    def mouseReleaseEvent(self, QGraphicsSceneMouseEvent):
        item = self.itemAt(QGraphicsSceneMouseEvent.scenePos())
        if getattr(item, "id", ""):
            if item.hasMoved():
                item.cachePosition()
                index = self.radarItemToSourceIndex(item, 'pos')
                log.debug("Item Moved")
                self.sourceModel.setData(index,
                                         [item.scenePos().x(), item.scenePos().y()],
                                         QtCore.Qt.EditRole)
                self.proxyModel.reset()

        return super(RadarGraphicsScene, self).mouseReleaseEvent(QGraphicsSceneMouseEvent)

    def addRadarItem(self, pos):
        record = self.sourceModel.addNewRadarItem()
        graphicsItem = RadarGraphicsItem()
        graphicsItem.setId(record["_id"])
        self.sourceModel.updateGraphicsItemColour.connect(graphicsItem.updateColour)
        self.addItem(graphicsItem)
        graphicsItem.setPos(pos)
        graphicsItem.record = record
        self.mongoSceneHandle.updatePosition(record["_id"], pos.x(), pos.y())
        self.radarItemAdded.emit(graphicsItem)

    def filterRadarItems(self):
        """
        :return:
        """
        # Probably should use layers!
        return [i for i in self.items() if getattr(i, "id", "")]

    def itemFromID(self, idx):
        if idx in self._itemDict:
            return self._itemDict[idx]
        log.debug("finding RadarGraphicsItem in scene by ID : {0}".format(idx))
        found = [i for i in self.filterRadarItems() if i.id() == idx]
        if found:
            return found[0]