コード例 #1
0
ファイル: digital_clock.py プロジェクト: Grumbel/clockgr
class DigitalClock(Desklet):

    def __init__(self):
        super().__init__()

        self.seconds = QGraphicsSimpleTextItem(self.root)
        self.time = QGraphicsSimpleTextItem(self.root)
        self.date = QGraphicsSimpleTextItem(self.root)

    def set_style(self, style):
        super().set_style(style)

        self.seconds.setBrush(style.foreground_color)
        self.time.setBrush(style.foreground_color)
        self.date.setBrush(style.foreground_color)

        font = QFont(style.font)
        font.setPixelSize(192 * 0.5)
        self.seconds.setFont(font)

        font = QFont(style.font)
        font.setPixelSize(192 * 0.75)
        self.time.setFont(font)

        font = QFont(style.font)
        font.setPixelSize(56)
        self.date.setFont(font)
        self.layout()

    def set_rect(self, rect):
        super().set_rect(rect)
        self.layout()

    def layout(self):
        time_fm = QFontMetrics(self.time.font())
        seconds_fm = QFontMetrics(self.seconds.font())

        x = self.rect.left()
        y = self.rect.top()

        self.time.setPos(x, y)
        self.seconds.setPos(x + time_fm.width("00:00") + 20,
                            y + time_fm.ascent() - seconds_fm.ascent())
        self.date.setPos(x, y + time_fm.ascent())

    def update(self, now):
        date = now.strftime("%A, %d. %B %Y")
        time = now.strftime("%H:%M")
        seconds = now.strftime("%S")

        self.time.setText(time)
        self.seconds.setText(seconds)
        self.date.setText(date)
コード例 #2
0
    def build_bonus_fields(self):
        for bonus_field in self._bonus_fields:
            brush = bonus_field["Brush"]
            pen = bonus_field["Pen"]
            bonus_fields = []

            for coords in bonus_field["Coords"]:
                label = bonus_field["Name"]
                if coords == Coords.central():
                    label = '✸'
                square = self._board_squares[coords]
                square.setZValue(2)
                bonus_fields.append(square)
                field_name = QGraphicsSimpleTextItem(label)
                font = field_name.font()
                font.setPointSize(10)
                if coords == Coords.central():
                    font.setPointSize(20)
                fm = QFontMetrics(font)
                field_name.setZValue(2.1)
                field_name.setFont(font)
                x = coords.x * SQUARE_SIZE + (SQUARE_SIZE - fm.width(label)) / 2
                y = coords.y * SQUARE_SIZE + (SQUARE_SIZE - fm.height()) / 2
                field_name.setPos(x, y)
                field_name.setBrush(bonus_field["Label brush"])

                self._labels[(x, y)] = field_name
                self.scene.addItem(field_name)

            paint_graphic_items(bonus_fields, pen, brush)
コード例 #3
0
ファイル: plots.py プロジェクト: trawl/gamelog
    def drawHRefs(self):
        if self.xvmax < self.xvmin or self.awidth <= 0:
            return
        minsep = 30
        factor = 1
        unitincrement = self.awidth/float(self.xvmax-self.xvmin)
        xmaxint = self.xvmax
        vx = int(self.xvmin)
        pstart = self.value2point(vx, self.yvmin)
        px = pstart.x()
        pystart = pstart.y()
        pend = self.value2point(xmaxint, self.yvmin)
        pxend = pend.x()
        pyend = pend.y()-2

        try:
            minsep = 10 * max([len(h) for h in self.hheaders])
        except Exception:
            pass

        while (unitincrement*factor < minsep):
            provfactor = 2*factor
            if(unitincrement*provfactor > minsep):
                factor = provfactor
                break
            provfactor = 5*factor
            if(unitincrement*provfactor > minsep):
                factor = provfactor
                break
            factor = 10*factor

#        px+=unitincrement*factor
#        vx +=factor

        while(px <= pxend):
            colour = QtGui.QColor(0, 0, 0, 255)
            PlotLine(px+0.5, pystart+2, px+0.5, pyend, 1.5, colour, self)
            try:
                header = self.hheaders[vx]
            except IndexError:
                header = vx
            nlabel = QGraphicsSimpleTextItem(
                "{}".format(header), self)
            font = nlabel.font()
            font.setPixelSize(20)
            nlabel.setFont(font)
            nlabelrect = nlabel.boundingRect()
            nlabel.setPos(px + 0.5 - nlabelrect.width()/2, pystart+3)
            px += unitincrement*factor
            vx += factor
コード例 #4
0
    def _draw_node(self, node: Node, x: float, y: float):
        """
        Drawing the Node on the map.
        """
        text_item = QGraphicsSimpleTextItem()

        font = text_item.font()
        font.setPointSize(font.pointSize() - 2)
        font.setPixelSize(self._font_size)

        text_item.setText(str(node.value()))
        text_item.setPos(x, y)
        text_item.setFont(font)

        self._scene.addItem(text_item)
コード例 #5
0
        def addRectText(x, w, parent, text="", level=0, tooltip=""):
            deltaH = LEVEL_HEIGHT if level else 0
            r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent, title=text)
            r.setPos(x, deltaH)

            txt = QGraphicsSimpleTextItem(text, r)
            f = txt.font()
            f.setPointSize(8)
            fm = QFontMetricsF(f)
            elidedText = fm.elidedText(text, Qt.ElideMiddle, w)
            txt.setFont(f)
            txt.setText(elidedText)
            txt.setPos(r.boundingRect().center() - txt.boundingRect().center())
            txt.setY(0)
            return r
コード例 #6
0
ファイル: storylineView.py プロジェクト: zieglerm/manuskript
        def addRectText(x, w, parent, text="", level=0, tooltip=""):
            deltaH = LEVEL_HEIGHT if level else 0
            r = OutlineRect(0, 0, w, parent.rect().height()-deltaH, parent, title=text)
            r.setPos(x, deltaH)

            txt = QGraphicsSimpleTextItem(text, r)
            f = txt.font()
            f.setPointSize(8)
            fm = QFontMetricsF(f)
            elidedText = fm.elidedText(text, Qt.ElideMiddle, w)
            txt.setFont(f)
            txt.setText(elidedText)
            txt.setPos(r.boundingRect().center() - txt.boundingRect().center())
            txt.setY(0)
            return r
コード例 #7
0
ファイル: plots.py プロジェクト: trawl/gamelog
    def drawVRefs(self):
        if self.yvmax < self.yvmin or self.aheight <= 0:
            return
        minsep = 30
        factor = 1
        try:
            unitincrement = self.aheight/float(self.yvmax-self.yvmin)
        except ZeroDivisionError:
            msg = "Division by zero in drawVRefs. Limits are {}-{}"
            print(msg.format(self.yvmin, self.yvmax))

        while (unitincrement*factor < minsep):
            provfactor = 2*factor
            if(unitincrement*provfactor > minsep):
                factor = provfactor
                break
            provfactor = 5*factor
            if(unitincrement*provfactor > minsep):
                factor = provfactor
                break
            factor = 10*factor
        if (self.yvmin <= 0):
            vy = int(self.yvmin/factor)*factor
        else:
            vy = (int(self.yvmin/factor)+1)*factor
        pstart = self.value2point(self.xvmin, vy)
        pxstart = pstart.x()
        py = pstart.y()
        pend = self.value2point(self.xvmax, self.yvmax)
        pxend = pend.x()
        pyend = pend.y()

        while(py > pyend):
            colour = QtGui.QColor(0, 0, 0, 200)
            if vy == 0:
                PlotLine(pxstart-2, py, pxend, py, 1.5, QtCore.Qt.black, self)
            else:
                PlotLine(pxstart-2, py, pxend, py, 0.5, colour, self)
            nlabel = QGraphicsSimpleTextItem("{}".format(vy), self)
            font = nlabel.font()
            font.setPixelSize(20)
            nlabel.setFont(font)
            nlabelrect = nlabel.boundingRect()
            nlabel.setPos(pxstart - nlabelrect.width() -
                          5, py-nlabelrect.height()/2)
            py -= unitincrement*factor
            vy += factor
コード例 #8
0
ファイル: stop_watch.py プロジェクト: Grumbel/clockgr
class StopWatch(Desklet):

    def __init__(self):
        super().__init__()

        self.timer = Timer()
        self.timeout_handle = None

        self.qtimer = QTimer()
        self.qtimer.timeout.connect(self.my_update)

        self.label = QGraphicsSimpleTextItem("Stopwatch:", self.root)
        self.time = QGraphicsSimpleTextItem("00:00", self.root)
        self.seconds = QGraphicsSimpleTextItem("00'00", self.root)

    def update(self):
        t = self.timer.get_time()
        time = "%02d:%02d" % (t.seconds / (60 * 60), (t.seconds % (60 * 60)) / 60)
        seconds = "%02d'%02d" % (t.seconds % 60, t.microseconds / 10000)

        self.time.setText(time)
        self.seconds.setText(seconds)

    def set_style(self, style):
        super().set_style(style)

        font = QFont(style.font)
        font.setPixelSize(24)
        self.time.setFont(font)
        self.label.setFont(font)

        font = QFont(style.font)
        font.setPixelSize(192 / 2)
        self.time.setFont(font)

        font = QFont(style.font)
        font.setPixelSize(192 / 2 * 0.6)
        self.seconds.setFont(font)

        self.label.setBrush(self.style.foreground_color)
        self.time.setBrush(self.style.foreground_color)
        self.seconds.setBrush(self.style.foreground_color)

        self.layout()

    def set_rect(self, rect):
        super().set_rect(rect)
        self.layout()

    def layout(self):
        x = self.rect.left()
        y = self.rect.top()

        fm = QFontMetrics(self.time.font())
        rect = fm.boundingRect("00:00")

        sfm = QFontMetrics(self.seconds.font())

        self.time.setPos(x, y + 20)
        self.seconds.setPos(x + 20 + rect.width(), y + 20 + fm.ascent() - sfm.ascent())

        self.label.setPos(x, y)

    def my_update(self):
        self.update()

    def is_running(self):
        return self.timer.is_running()

    def start_stop_watch(self):
        self.timer.start_stop()

        if self.timer.is_running():
            self.qtimer.setInterval(31)
            self.qtimer.start()
        else:
            self.qtimer.stop()

    def clear_stop_watch(self):
        self.timer.reset()
コード例 #9
0
 def create_info_display(self, x, y, attributes):
     """
     Creates view elements for the info display
     :param x: x position of the node
     :param y: y position of the node
     :param attributes: attributes that will be displayed in the view
     :return:
     """
     start_height = y + (self.NODE_HEIGHT / 2)
     # unfold dictionary values at the bottom of the list
     sorted_attributes = []
     for k, v in sorted(attributes.items(), key=lambda tup: isinstance(tup[1], dict)):
         if isinstance(v, dict):
             sorted_attributes.append((k, v))
             sorted_attributes.extend(v.items())
         else:
             sorted_attributes.append((k, v))
     # create property rows
     for i, (k, v) in enumerate(sorted_attributes):
         value_text = None
         value_height = 0
         if isinstance(v, dict):
             # display dictionary key as title
             text = "{}".format(k)
             if len(text) > 20:
                 text = text[:20] + "..."
             key_text = QGraphicsSimpleTextItem(text)
             f = key_text.font()
             f.setBold(True)
             key_text.setFont(f)
             text_width = key_text.boundingRect().width()
         else:
             key_text = QGraphicsSimpleTextItem("{}:".format(k) if k else " ")
             text = str(v)
             if len(text) > 20:
                 text = text[:20] + "..."
             value_text = QGraphicsSimpleTextItem(text)
             value_height = value_text.boundingRect().height()
             text_width = key_text.boundingRect().width() + value_text.boundingRect().width()
         # create box around property
         attribute_container = QGraphicsRectItem(x, start_height, text_width + 10,
                                                 max(key_text.boundingRect().height(),
                                                     value_height) + 10)
         attribute_container.setBrush(QBrush(Qt.white))
         self.total_height += attribute_container.rect().height()
         key_text.setParentItem(attribute_container)
         if value_text:
             value_text.setParentItem(attribute_container)
         self.max_width = max(self.max_width, attribute_container.rect().width())
         attribute_container.setParentItem(self)
         self.info_display.append(attribute_container)
         start_height += max(key_text.boundingRect().height(), value_height) + 10
     # calculate correct coordinates for positioning of the attribute boxes
     if self.max_width > self.NODE_MIN_WIDTH - 10:
         x -= (self.max_width + 10) / 2
         y -= self.total_height / 2
         self.max_width += 10
     else:
         x -= self.NODE_MIN_WIDTH / 2
         y -= self.total_height / 2
         self.max_width = self.NODE_MIN_WIDTH
     h = 0
     # position all the elements previously created
     for attribute_container in self.info_display:
         rect: QRectF = attribute_container.rect()
         rect.setX(x)
         rect_height = rect.height()
         rect.setY(y + self.NODE_HEIGHT + h)
         rect.setHeight(rect_height)
         key_child = attribute_container.childItems()[0]
         if len(attribute_container.childItems()) == 2:
             key_child.setX(x + 5)
             value_child = attribute_container.childItems()[1]
             value_child.setX(x + self.max_width - value_child.boundingRect().width() - 5)
             value_child.setY(y + self.NODE_HEIGHT + h + 5)
         else:
             key_child.setX(x - key_child.boundingRect().width() / 2 + self.max_width / 2)
         key_child.setY(y + self.NODE_HEIGHT + h + 5)
         h += rect.height()
         rect.setWidth(self.max_width)
         attribute_container.setRect(rect)
コード例 #10
0
class Node(QGraphicsItem):
    logger = logging.getLogger('ViewNode')

    i = 0
    NODE_MIN_WIDTH = 100
    NODE_MAX_WIDTH = 150
    NODE_HEIGHT = 50
    NODE_COLOR = (152, 193, 217)                # LIGHT BLUE

    TACTIC_COLOR = (255, 51, 51)                # RED
    STRATEGY_COLOR = (77, 255, 77)              # GREEN
    ROLE_COLOR = (166, 77, 255)                 # PURPLE
    KEEPER_COLOR = (255, 255, 26)               # YELLOW
    OTHER_SUBTREE_COLOR = (147, 147, 147)       # GREY
    DECORATOR_COLOR = (51, 51, 255)             # DARK BLUE
    COMPOSITE_COLOR = (255, 153, 0)             # ORANGE
    OTHER_NODE_TYPES_COLOR = (255, 102, 153)    # PINK
    DEFAULT_SIMULATOR_COLOR = Qt.white

    def __init__(self, x: float, y: float, scene: QGraphicsScene, model_node: ModelNode, title: str = None,
                 parent: QGraphicsItem = None, node_types: NodeTypes = None):
        """
        The constructor for a UI node
        :param x: x position for the center of the node
        :param y: y position for the center of the node
        :param title: title of the node displayed in the ui
        :param parent: parent of this graphics item
        """
        if title:
            self.title = title
        else:
            # give node a unique title
            self.title = "node {}".format(Node.i)
        self.id = model_node.id
        self.x = x
        self.y = y
        Node.i += 1
        self.scene = scene
        self.model_node = model_node
        self.children = []
        self.edges = []
        # store node positional data when detaching from parent
        self.expand_data = None
        # add node name label centered in the eclipse, elide if title is too long
        self.node_text = QGraphicsSimpleTextItem()
        metrics = QFontMetrics(self.node_text.font())
        elided_title = metrics.elidedText(self.title, Qt.ElideRight, self.NODE_MAX_WIDTH)
        self.node_text.setText(elided_title)
        self.node_text.setAcceptedMouseButtons(Qt.NoButton)
        self.node_text.setAcceptHoverEvents(False)
        self.text_width = self.node_text.boundingRect().width()
        self.text_height = self.node_text.boundingRect().height()
        self.node_text.setX(x - (self.text_width / 2))
        # call super function now we know the node size
        super(Node, self).__init__(parent)
        self.node_text.setParentItem(self)
        # indicates if node is being dragged
        self.dragging = False
        self.setCursor(Qt.PointingHandCursor)
        self.setAcceptHoverEvents(True)
        # give the node a default color
        self.brush = QBrush(QColor(*self.NODE_COLOR))
        self.simulator_brush = QBrush(self.DEFAULT_SIMULATOR_COLOR)
        # give node another color
        if node_types:
            # check for node types and color them
            types = node_types.get_node_type_by_name(model_node.title)
            if len(types) > 0:
                category, node_type = types[0]
                if category == 'decorators':
                    self.brush.setColor(QColor(*self.DECORATOR_COLOR))
                elif category == 'composites':
                    self.brush.setColor(QColor(*self.COMPOSITE_COLOR))
                else:
                    self.brush.setColor(QColor(*self.OTHER_NODE_TYPES_COLOR))
            # check for a strategy, role, tactic or keeper
            if 'name' in model_node.attributes.keys() or 'role' in model_node.attributes.keys():
                if model_node.title == 'Tactic':
                    self.brush.setColor(QColor(*self.TACTIC_COLOR))
                elif model_node.title == 'Strategy':
                    self.brush.setColor(QColor(*self.STRATEGY_COLOR))
                elif model_node.title == 'Keeper':
                    self.brush.setColor(QColor(*self.KEEPER_COLOR))
                elif model_node.title == 'Role':
                    self.brush.setColor(QColor(*self.ROLE_COLOR))
                else:
                    self.brush.setColor(QColor(*self.OTHER_SUBTREE_COLOR))
        self.info_display = []
        self.max_width = 0
        self.total_height = 0
        self.bottom_collapse_expand_button = None
        self.top_collapse_expand_button = None
        self._rect = None
        self.initiate_view()

    def initiate_view(self, propagate=False):
        """
        Initiates all the children for the current view
        :param propagate: Propagate initiate view signal to children
        """
        for rect in self.info_display:
            rect.setParentItem(None)
        if self.top_collapse_expand_button and self.bottom_collapse_expand_button:
            self.top_collapse_expand_button.setParentItem(None)
            self.bottom_collapse_expand_button.setParentItem(None)
        self.info_display = []
        self.max_width = self.text_width + 10
        self.total_height = self.NODE_HEIGHT
        if self.scene.info_mode:
            model_node = self.scene.gui.tree.nodes[self.id]
            self.create_info_display(self.x, self.y, model_node.attributes)
        if self.max_width > self.NODE_MIN_WIDTH - 10:
            self._rect = QRect(self.x - self.max_width / 2, self.y - self.total_height / 2, self.max_width,
                               self.total_height)
        else:
            self._rect = QRect(self.x - self.NODE_MIN_WIDTH / 2, self.y - self.total_height / 2, self.NODE_MIN_WIDTH,
                               self.total_height)
        # set node size based on children
        self.node_text.setY(self.y - self.total_height / 2 + self.NODE_HEIGHT / 2 - self.text_height / 2)
        self.create_expand_collapse_buttons()
        self.scene.update()
        if propagate:
            for c in self.children:
                c.initiate_view(True)
            for e in self.edges:
                e.change_position()

    def create_expand_collapse_buttons(self):
        """
        Creates the expand/collapse buttons of the node
        """
        # create the bottom collapse/expand button for this node
        if self.bottom_collapse_expand_button:
            bottom_collapsed = self.bottom_collapse_expand_button.isCollapsed
        else:
            bottom_collapsed = False
        self.bottom_collapse_expand_button = CollapseExpandButton(self)
        self.bottom_collapse_expand_button.setParentItem(self)
        self.bottom_collapse_expand_button.collapse.connect(self.collapse_children)
        self.bottom_collapse_expand_button.expand.connect(self.expand_children)
        self.bottom_collapse_expand_button.isCollapsed = bottom_collapsed
        # position the bottom button at the bottom-center of the node
        button_x = self.x - (self.bottom_collapse_expand_button.boundingRect().width() / 2)
        button_y = self.y + self.total_height / 2 - (self.bottom_collapse_expand_button.boundingRect().height() / 2)
        self.bottom_collapse_expand_button.setPos(button_x, button_y)
        # hidden by default, the button is only needed if the node has children
        if not self.children:
            self.bottom_collapse_expand_button.hide()
        # create the top collapse/expand button for this node
        if self.top_collapse_expand_button:
            top_collapsed = self.top_collapse_expand_button.isCollapsed
        else:
            top_collapsed = False
        self.top_collapse_expand_button = CollapseExpandButton(self)
        self.top_collapse_expand_button.setParentItem(self)
        self.top_collapse_expand_button.collapse.connect(self.collapse_upwards)
        self.top_collapse_expand_button.expand.connect(self.expand_upwards)
        self.top_collapse_expand_button.isCollapsed = top_collapsed
        if self.scene.root_ui_node == self or self in self.scene.disconnected_nodes \
                or self.scene.reconnecting_node == self:
            self.top_collapse_expand_button.hide()
        # position the top button at the top-center of the node
        button_x = self.x - (self.top_collapse_expand_button.boundingRect().width() / 2)
        button_y = self.y - self.total_height / 2 - (self.top_collapse_expand_button.boundingRect().height() / 2)
        self.top_collapse_expand_button.setPos(button_x, button_y)

    def create_info_display(self, x, y, attributes):
        """
        Creates view elements for the info display
        :param x: x position of the node
        :param y: y position of the node
        :param attributes: attributes that will be displayed in the view
        :return:
        """
        start_height = y + (self.NODE_HEIGHT / 2)
        # unfold dictionary values at the bottom of the list
        sorted_attributes = []
        for k, v in sorted(attributes.items(), key=lambda tup: isinstance(tup[1], dict)):
            if isinstance(v, dict):
                sorted_attributes.append((k, v))
                sorted_attributes.extend(v.items())
            else:
                sorted_attributes.append((k, v))
        # create property rows
        for i, (k, v) in enumerate(sorted_attributes):
            value_text = None
            value_height = 0
            if isinstance(v, dict):
                # display dictionary key as title
                text = "{}".format(k)
                if len(text) > 20:
                    text = text[:20] + "..."
                key_text = QGraphicsSimpleTextItem(text)
                f = key_text.font()
                f.setBold(True)
                key_text.setFont(f)
                text_width = key_text.boundingRect().width()
            else:
                key_text = QGraphicsSimpleTextItem("{}:".format(k) if k else " ")
                text = str(v)
                if len(text) > 20:
                    text = text[:20] + "..."
                value_text = QGraphicsSimpleTextItem(text)
                value_height = value_text.boundingRect().height()
                text_width = key_text.boundingRect().width() + value_text.boundingRect().width()
            # create box around property
            attribute_container = QGraphicsRectItem(x, start_height, text_width + 10,
                                                    max(key_text.boundingRect().height(),
                                                        value_height) + 10)
            attribute_container.setBrush(QBrush(Qt.white))
            self.total_height += attribute_container.rect().height()
            key_text.setParentItem(attribute_container)
            if value_text:
                value_text.setParentItem(attribute_container)
            self.max_width = max(self.max_width, attribute_container.rect().width())
            attribute_container.setParentItem(self)
            self.info_display.append(attribute_container)
            start_height += max(key_text.boundingRect().height(), value_height) + 10
        # calculate correct coordinates for positioning of the attribute boxes
        if self.max_width > self.NODE_MIN_WIDTH - 10:
            x -= (self.max_width + 10) / 2
            y -= self.total_height / 2
            self.max_width += 10
        else:
            x -= self.NODE_MIN_WIDTH / 2
            y -= self.total_height / 2
            self.max_width = self.NODE_MIN_WIDTH
        h = 0
        # position all the elements previously created
        for attribute_container in self.info_display:
            rect: QRectF = attribute_container.rect()
            rect.setX(x)
            rect_height = rect.height()
            rect.setY(y + self.NODE_HEIGHT + h)
            rect.setHeight(rect_height)
            key_child = attribute_container.childItems()[0]
            if len(attribute_container.childItems()) == 2:
                key_child.setX(x + 5)
                value_child = attribute_container.childItems()[1]
                value_child.setX(x + self.max_width - value_child.boundingRect().width() - 5)
                value_child.setY(y + self.NODE_HEIGHT + h + 5)
            else:
                key_child.setX(x - key_child.boundingRect().width() / 2 + self.max_width / 2)
            key_child.setY(y + self.NODE_HEIGHT + h + 5)
            h += rect.height()
            rect.setWidth(self.max_width)
            attribute_container.setRect(rect)

    def paint(self, painter: QPainter, style_options: QStyleOptionGraphicsItem, widget=None):
        """
        Paint the basic shape of the node (ellipse or rectangle)
        :param painter: painter used to paint objects
        :param style_options: Styling options for the graphics item
        :param widget: The widget being painted
        """
        painter.setPen(Qt.SolidLine)
        if self == self.scene.root_ui_node:
            pen = QPen(Qt.black, 2.0)
            pen.setStyle(Qt.DotLine)
            painter.setPen(pen)
        if self.scene.simulator_mode:
            brush = self.simulator_brush
        else:
            brush = self.brush
        painter.setBrush(brush)
        if self.scene.info_mode:
            painter.drawRect(self.rect().x(), self.rect().y(), self.rect().width(), self.NODE_HEIGHT)
        else:
            painter.drawEllipse(self.rect())

    def add_child(self, child):
        """
        Add a child node
        Inheritance looks like: parent > edge > child
        :param child: Another ui node
        """
        edge = Edge(self, child)
        edge.setParentItem(self)
        # edge should stay behind the expand/collapse button
        edge.stackBefore(self.bottom_collapse_expand_button)
        self.children.append(child)
        self.edges.append(edge)
        # show the expand/collapse button when the first child is added
        if not self.bottom_collapse_expand_button.isVisible():
            self.bottom_collapse_expand_button.show()
        if not child.top_collapse_expand_button.isVisible():
            child.top_collapse_expand_button.show()

    def remove_child(self, child):
        """
        Removes child from this node (no data changes)
        :param child: Child of this node
        """
        if child not in self.children:
            Node.logger.error("Incorrect child can not be removed from wrong parent.")
        edge = child.parentItem()
        child.setParentItem(None)
        self.children.remove(child)
        self.edges.remove(edge)
        edge.setParentItem(None)
        self.scene.removeItem(edge)
        if not self.children:
            self.bottom_collapse_expand_button.hide()

    def nodes_below(self):
        nodes = []
        for c in self.children:
            nodes.append(c)
            nodes.extend(c.nodes_below())
        return nodes

    def moveBy(self, x, y):
        super(Node, self).moveBy(x, y)
        # move edge correctly with node
        if self.parentItem() and isinstance(self.parentItem(), Edge):
            self.parentItem().change_position()

    def setPos(self, *args):
        super(Node, self).setPos(*args)
        # move edge correctly with node
        if self.parentItem() and isinstance(self.parentItem(), Edge):
            self.parentItem().change_position()

    def xoffset(self):
        """
        recursively adds the relative x distances from this node up until the root node.
        :return: the sum of the relative x distances
        """
        if self.parentItem():
            return self.pos().x() + self.parentItem().xoffset()
        else:
            return self.pos().x() + self.rect().x() + self.rect().width() / 2

    def yoffset(self):
        """
        recursively adds the relative y distances from this node up until the root node.
        :return: the sum of the relative y distances
        """
        if self.parentItem():
            return self.pos().y() + self.parentItem().yoffset()
        else:
            return self.pos().y() + self.rect().y() + self.rect().height() / 2

    def xpos(self):
        """
        Calculates the x position of this node using the x offset
        :return: the x position of the node
        """
        return self.xoffset()

    def ypos(self):
        """
        Calculates the y position of this node using the y offset
        :return: the y position of the node
        """
        return self.yoffset()

    def boundingRect(self):
        return QRectF(self._rect)

    def rect(self):
        return self._rect

    def detach_from_parent(self):
        """
        Detaches node from parent (no data changes)
        :return: Positional data that can be used to reattach node
        """
        if not self.parentItem() or not self.parentItem().parentItem():
            Node.logger.error("The node can't detach from parent, no parent")
            return
        # store attach data used to restore the state when attaching
        xpos, ypos = self.xpos(), self.ypos()
        root_item = self.scene.root_ui_node
        parent_node = self.parentItem().parentItem()
        attach_data = {
            "abs_pos": QPointF(xpos, ypos),
            "old_parent": parent_node,
            "top_level_item": self.topLevelItem(),
        }
        parent_node.remove_child(self)
        # move node to retain correct position
        self.setPos(0, 0)
        root_x = root_item.xpos() if root_item else self.scene.node_init_pos[0]
        root_y = root_item.ypos() if root_item else self.scene.node_init_pos[1]
        move_x = xpos - root_x - (self.scene.node_init_pos[0] - root_x)
        move_y = ypos - root_y - (self.scene.node_init_pos[1] - root_y)
        self.moveBy(move_x, move_y)
        return attach_data

    def attach_to_parent(self, data, parent=None):
        """
        Attaches node to parent (no data changes)
        :param: data: Positional data from detachment used for attaching
        """
        if not parent:
            parent = data['old_parent']
        new_abs_pos = QPointF(self.xpos(), self.ypos())
        # reset parent item
        e = Edge(parent, self)
        e.setParentItem(parent)
        parent.children.append(self)
        parent.edges.append(e)
        parent.sort_children()
        parent_abs_pos = QPointF(parent.xpos(), parent.ypos())
        # reset relative position to parent
        self.setPos(new_abs_pos - parent_abs_pos)

    def collapse_upwards(self):
        """
        Collapses the tree upwards only displaying this node and its children
        :return:
        """
        self.expand_data = self.detach_from_parent()
        # hide parent nodes
        self.expand_data['top_level_item'].hide()

    def expand_upwards(self):
        """
        Expands the tree upwards displaying all expanded parent nodes
        :return:
        """
        self.attach_to_parent(self.expand_data)
        # show expanded parent nodes
        self.topLevelItem().show()

    def collapse_children(self):
        """
        Collapses this node's children by hiding all child edges (and therefore the whole subtree)
        """
        for c in self.childItems():
            if isinstance(c, Edge):
                c.hide()

    def expand_children(self):
        """
        Expands this node's children by showing all child edges previously hidden by the collapse function
        """
        for c in self.childItems():
            if isinstance(c, Edge):
                c.show()

    def sort_children(self):
        """
        Sort child edges/nodes based on x position
        :return: The model nodes in order
        """
        # gather all the edges
        child_edges = [edge for edge in self.childItems() if isinstance(edge, Edge)]
        # sort edges by x position of the child nodes
        child_edges.sort(key=lambda c: c.end_node.xpos())
        # reset internal structure
        self.edges.clear()
        self.children.clear()
        # add children back in correct order
        for e in child_edges:
            e.setParentItem(None)
            self.edges.append(e)
            self.children.append(e.end_node)
        # set the parent of the children in the correct order
        for e in child_edges:
            e.setParentItem(self)
        # return the model nodes in the correct order
        model_nodes_order = [e.end_node.model_node for e in child_edges]
        return model_nodes_order

    def detect_order_change(self):
        """
        Detects if node order has changed and updates model accordingly
        """
        if not self.parentItem():
            # sort top level nodes, this prevents alignment issues
            self.scene.disconnected_nodes = sorted(self.scene.disconnected_nodes, key=lambda n: n.xpos())
        else:
            # parent node of self
            parent_node = self.parentItem().parentItem()
            parent_model_node = self.scene.gui.tree.nodes.get(parent_node.id)
            # own child index
            node_index = parent_node.children.index(self)
            # check if node is swapped with left neighbour
            try:
                if node_index - 1 >= 0:
                    # can throw IndexError if there is no left neighbour
                    left_node = parent_node.children[node_index - 1]
                    # check if node is swapped
                    if left_node.xpos() > self.xpos():
                        # sort children of parent
                        sorted_nodes = parent_node.sort_children()
                        # change model tree structure accordingly
                        parent_model_node.children = [n.id for n in sorted_nodes]
                        self.scene.gui.update_tree(parent_model_node)
            except IndexError:
                pass
            # check if node is swapped with right neighbour
            try:
                # can throw IndexError if there is no right neighbour
                right_node = parent_node.children[node_index + 1]
                # check if node is swapped
                if right_node.xpos() < self.xpos():
                    # sort children of parent
                    sorted_nodes = parent_node.sort_children()
                    # change model tree structure accordingly
                    parent_model_node.children = [n.id for n in sorted_nodes]
                    self.scene.gui.update_tree(parent_model_node)
            except IndexError:
                pass

    def delete_self(self):
        """
        Deletes this node and makes children disconnected subtrees/nodes
        """
        for c in self.children[:]:
            c.detach_from_parent()
            # add child to disconnected nodes
            if self in self.scene.disconnected_nodes:
                index = self.scene.disconnected_nodes.index(self)
                self.scene.disconnected_nodes.insert(index, c)
            else:
                self.scene.disconnected_nodes.insert(0, c)
            c.top_collapse_expand_button.hide()
        parent_model_node = None
        if self.parentItem():
            parent_node: Node = self.parentItem().parentItem()
            parent_node.remove_child(self)
            parent_model_node = self.scene.gui.tree.nodes.get(parent_node.id)
            parent_model_node.children.remove(self.id)
        if self in self.scene.disconnected_nodes:
            self.scene.disconnected_nodes.remove(self)
        self.scene.removeItem(self)
        self.scene.close_property_display()
        del self.scene.nodes[self.id]
        # reset root if this is the root
        if self.scene.gui.tree.root == self.id:
            self.scene.gui.tree.root = ''
        # remove node from internal tree structure
        del self.scene.gui.tree.nodes[self.id]
        if parent_model_node:
            self.scene.gui.update_tree(parent_model_node)

    def delete_subtree(self, delete_parent_relation=True, update_tree=True):
        """
        Deletes node and its children
        :param delete_parent_relation: Boolean indicating if parent relation should be modified
        :param update_tree: Boolean indicating if the tree needs an update
        """
        # remove children
        for c in self.children:
            c.delete_subtree(delete_parent_relation=False)
        # remove child reference from parent
        parent_node = None
        if delete_parent_relation and self.parentItem():
            parent_node: Node = self.parentItem().parentItem()
            parent_node.remove_child(self)
            try:
                self.scene.gui.tree.nodes[parent_node.id].children.remove(self.id)
            except ValueError:
                pass
        self.scene.removeItem(self)
        self.scene.close_property_display()
        if self in self.scene.disconnected_nodes:
            self.scene.disconnected_nodes.remove(self)
        self.scene.nodes.pop(self.id, None)
        if self.scene.gui.tree.root == self.id:
            self.scene.gui.tree.root = ''
        # remove node from internal tree structure
        self.scene.gui.tree.nodes.pop(self.id, None)
        if delete_parent_relation and parent_node and update_tree:
            node = self.scene.gui.tree.nodes.get(parent_node.id)
            self.scene.gui.update_tree(node)

    def reconnect_edge(self):
        """
        Starts edge reconnection process
        """
        if not self.parentItem() and self not in self.scene.disconnected_nodes:
            Node.logger.error("The edge trying to reconnect does not exist.")
        else:
            self.scene.start_reconnect_edge(self)

    def mousePressEvent(self, m_event):
        """
        Handles a mouse press on a node
        :param m_event: The mouse press event and its details
        """
        super(Node, self).mousePressEvent(m_event)
        tree = self.scene.gui.tree.nodes[self.id]
        if self.scene.view.parent().property_display:
            self.scene.view.parent().property_display.setParent(None)
            self.scene.view.parent().property_display.deleteLater()
        self.scene.view.parent().property_display = view.widgets.TreeViewPropertyDisplay(
            self.scene.view.parent().graphics_scene, tree.attributes, parent=self.scene.view.parent(), node_id=tree.id,
            node_title=tree.title)

    def mouseMoveEvent(self, m_event):
        """
        Handles a mouse move over a node
        :param m_event: The mouse move event and its details
        """
        super(Node, self).mouseMoveEvent(m_event)
        if self.dragging:
            # move the node with the mouse and adjust the edges to the new position
            dx = m_event.scenePos().x() - m_event.lastScenePos().x()
            dy = m_event.scenePos().y() - m_event.lastScenePos().y()
            self.setPos(self.pos().x() + dx, self.pos().y() + dy)
            # Set correct order for children if node has a parent and the order of disconnected nodes
            self.detect_order_change()
            # reposition incoming edge
            if isinstance(self.parentItem(), Edge):
                self.parentItem().change_position()

    def contextMenuEvent(self, menu_event):
        """
        Creates context menu for right clicks on this node
        :param menu_event: Context about the right click event
        """
        menu = QMenu()
        reconnect_edge_action = QAction("Reconnect Edge" if self.parentItem() else "Connect Edge")
        reconnect_edge_action.triggered.connect(self.reconnect_edge)
        menu.addAction(reconnect_edge_action)
        delete_action = QAction("Delete Node")
        delete_action.setToolTip('Delete only this node.')
        delete_action.triggered.connect(self.delete_self)
        menu.addAction(delete_action)
        delete_subtree_action = QAction("Delete Subtree")
        delete_subtree_action.setToolTip('Delete node and all its children.')
        delete_subtree_action.triggered.connect(lambda: self.delete_subtree())
        menu.addAction(delete_subtree_action)
        menu.exec(menu_event.screenPos())
        menu_event.setAccepted(True)
コード例 #11
0
ファイル: calendar.py プロジェクト: Grumbel/clockgr
class CalendarDesklet(Desklet):

    def __init__(self):
        super().__init__()

        self.model = CalendarModel()

        self.cursor_pos = None

        self.cursor = QGraphicsRectItem(self.root)
        self.header = QGraphicsSimpleTextItem(self.root)

        self.weekdays = []
        days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
        for day in days:
            self.weekdays.append(QGraphicsSimpleTextItem(day, self.root))

        self.header_line = QGraphicsLineItem(self.root)

        self.days = []
        for _ in range(0, 6 * 7):
            self.days.append(QGraphicsSimpleTextItem(self.root))

    def next_month(self):
        self.model.next_month()
        self.layout()

    def previous_month(self):
        self.model.previous_month()
        self.layout()

    def set_rect(self, rect):
        super().set_rect(rect)
        self.layout()

    def set_style(self, style):
        super().set_style(style)

        font = QFont(style.font)
        font.setPixelSize(48)
        self.header.setBrush(style.midcolor)
        self.header.setFont(font)

        font = QFont(style.font)
        font.setPixelSize(32)

        self.header_line.setPen(style.foreground_color)

        self.cursor.setBrush(style.midcolor)
        self.cursor.setPen(QPen(Qt.NoPen))

        for widget in self.weekdays:
            widget.setFont(font)
            widget.setBrush(style.foreground_color)

        for widget in self.days:
            widget.setFont(font)
            widget.setBrush(self.style.foreground_color)

        self.layout()

    def layout(self):
        cell_width = (self.rect.width()) / 7.0
        cell_height = (self.rect.height() - 64) / 7.0

        x = self.rect.left()
        y = self.rect.top()

        fm = QFontMetrics(self.header.font())
        rect = fm.boundingRect(self.header.text())
        self.header.setPos(x + self.rect.width() / 2 - rect.width() / 2,
                           y)

        y += fm.height()

        for row, day in enumerate(self.weekdays):
            fm = QFontMetrics(day.font())
            rect = fm.boundingRect(day.text())
            day.setPos(x + row * cell_width + cell_width / 2 - rect.width() / 2,
                       y)

        y += fm.height()
        self.header_line.setLine(x, y,
                                 x + self.rect.width() - 3, y)

        y += 8

        for n, widget in enumerate(self.days):
            col = n % 7
            row = n // 7

            rect = fm.boundingRect(widget.text())
            widget.setPos(x + col * cell_width + cell_width / 2 - rect.width() / 2,
                          y + row * cell_height + cell_height / 2 - fm.height() / 2)

            # if day.month != self.now.month:
            #    widget.setBrush(self.style.midcolor)
            # else:

        if self.cursor_pos is not None:
            self.cursor.setRect(x + self.cursor_pos[0] * cell_width,
                                y + self.cursor_pos[1] * cell_height,
                                cell_width,
                                cell_height)
            self.cursor.show()
        else:
            self.cursor.hide()

    def update(self, now):
        self.model.update(now)

        # update header
        self.header.setText(
            date(self.model.year, self.model.month, 1).strftime("%B %Y"))

        # calculate the date of the top/left calendar entry
        current_date = date(self.model.year, self.model.month, 1)
        current_date = current_date - timedelta(current_date.weekday())

        self.cursor_pos = None
        for n, widget in enumerate(self.days):
            col = n % 7
            row = n // 7

            if current_date == self.model.today:
                self.cursor_pos = (col, row)

            widget.setText("%d" % current_date.day)
            self.days[n] = widget
            current_date += timedelta(days=1)

        self.layout()