Пример #1
0
    def hoverEnterEvent(self, event):
        super(HooverBar, self).hoverEnterEvent(event)

        if self.gi == None:
            self.gi = QGraphicsRectItem(0, 0, 100, 100)
            self.gi.setBrush(QBrush(QColor(0, 64, 0, 192)))
            self.gi.setPen(QPen(Qt.transparent))
            self.gi.setPos(event.scenePos().x() + 20,
                           event.scenePos().y() + 20)

            x = y = 10
            w = 0
            for t in self.description:
                description = QGraphicsSimpleTextItem()
                description.setFont(self.base_font)
                description.setBrush(QBrush(Qt.white))
                description.setText(t)
                description.setParentItem(self.gi)
                description.setPos(x, y)
                y += description.boundingRect().height()
                w = max(w, description.boundingRect().width())
            y += x
            w += 2 * x

            self.gi.setRect(0, 0, w, y)
            self.scene().addItem(self.gi)
Пример #2
0
    def hoverEnterEvent(self, event):  # QGraphicsSceneHoverEvent * event )
        # BUG I had a crash running this, I suspect ownership issues regarding
        # graphics items...

        global configuration
        #mainlog.debug("hoverEnterEvent pos={}-{}".format(event.scenePos().x(),event.scenePos().y()))
        # mainlog.debug("hoverEnterEvent data={}".format(self.data))
        super(HooverBar, self).hoverEnterEvent(event)

        if self.gi == None:
            if self.hoover_text:
                self.gi = QGraphicsRectItem(0, 0, 100, 100)
                self.gi.setBrush(QBrush(QColor(0, 64, 0, 192)))
                self.gi.setPen(QPen(Qt.transparent))
                self.gi.setPos(event.scenePos().x() + 20,
                               event.scenePos().y() + 20)

                # txt = [ "" ]

                # txt = [ u"{} {}".format(self.data.production_file.order_part.human_identifier,
                #                        date_to_dmy(self.data.production_file.order_part.deadline)),
                #         nstr(self.data.production_file.order_part.description),
                #         nstr(self.data.description),
                #         _("{}x{}={}h, rest:{}h").format(self.data.production_file.order_part.qty,
                #                                         nice_round(self.data.planned_hours),
                #                                         nice_round(self.data.production_file.order_part.qty*self.data.planned_hours),
                #                                         nice_round(self.data.planned_hours*self.data.production_file.order_part.qty - self.data.done_hours))]

                x = y = 10
                w = 0
                for t in self.hoover_text:
                    description = QGraphicsSimpleTextItem()
                    description.setFont(self.base_font)
                    description.setBrush(QBrush(Qt.white))
                    description.setText(t)
                    description.setParentItem(self.gi)
                    description.setPos(x, y)
                    y += description.boundingRect().height()
                    w = max(w, description.boundingRect().width())
                y += x
                w += 2 * x

                # description.setHtml(u"|{}| <b>{}</b><br/>{}<br>{}x{}={}h".format(
                #         self.data.production_file.order_part.human_identifier,
                #         self.data.production_file.order_part.description,
                #         self.data.description,
                #         self.data.production_file.order_part.qty, self.data.planned_hours, self.data.production_file.order_part.qty*self.data.planned_hours))

                # description.setDefaultTextColor(Qt.white)
                # br = description.boundingRect()

                self.gi.setRect(0, 0, w, y)
                self.scene().addItem(self.gi)
Пример #3
0
    def createQtNode(self, node, posx, posy, color=QColor(255, 150, 150)):
        """ Create a QtNode with given position, color for given node
        
        Arguments:
            
            - node: The graphviz node
            - posx: The x position from graphviz layout
            - posy: The y position from graphviz layout
            - color: The color of circle (red by default)
        
        """
        #dpi = float(self.gv.graph_attr['dpi'])
        dpi = 96
        try:
            width = float(node.attr['width'])
            height = float(node.attr['height'])
        except ValueError:
            #New created node
            width = 300 / 96
            height = 40 / 96

        qnode = QtNode(-width * dpi / 2, -height * dpi / 2, width * dpi,
                       height * dpi)
        qnode.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        qnode.setPos(posx, posy)
        qnode.setFlag(QGraphicsItem.ItemIsMovable)
        qnode.setCallback(weakref.ref(self))
        qnode.setNode(node)
        qnode.setBrush(color)
        txt = QGraphicsSimpleTextItem(qnode)
        font = txt.font()
        font.setPointSize(14)
        txt.setFont(font)
        txt.setText(node)
        txtwidth = QFontMetricsF(font).width(node)
        txtheight = QFontMetricsF(font).height()
        toLeft = (-width * dpi / 2) + (width * dpi - txtwidth) / 2
        toBottom = (-height * dpi / 2) + (height * dpi - txtheight) / 2
        txt.setPos(toLeft, toBottom)

        return qnode
Пример #4
0
    def redraw(self,
               base_time,
               all_tars,
               employee_id,
               additional_work_timetracks,
               additional_presence_timetracks,
               special_activities=None,
               view_title=""):

        scene = QGraphicsScene()

        # This scene line is a hack to make sure I can control the "centerOn"
        # execution as I wish. This is very hackish.
        margin = 30
        scene.addLine(
            QLineF(0, 0,
                   self.width() - margin,
                   self.height() - margin), QPen(Qt.white))

        # Dat is heeeel belangerijk om de goede computation te doen
        all_tars = sorted(all_tars, key=lambda tar: tar.time)

        # chrono.chrono_click("Redraw step 1")
        timetracks_tars = dao.task_action_report_dao.compute_activity_timetracks_from_task_action_reports(
            all_tars, employee_id)

        # We got (timetrack, reports) tuples
        timetracks = [tt[0] for tt in timetracks_tars]

        # presence_time, off_time, presence_timetracks = dao.task_action_report_dao.recompute_presence_on_tars(employee_id, all_tars)

        # chrono.chrono_click("Redraw step 2")

        presence_intervals = dao.task_action_report_dao.compute_man_presence_periods(
            employee_id, base_time, all_tars, timetracks=[], commit=True)

        # chrono.chrono_click("Redraw step 2B")

        presence_timetracks = dao.task_action_report_dao.convert_intervals_to_presence_timetracks(
            presence_intervals, employee_id)

        # chrono.chrono_click("Redraw step 3")

        # FIXME this will trigger a session open... Must use the ID without a query first!
        presence_task_id = dao.task_action_report_dao.presence_task_id_regular_time(
        )
        # chrono.chrono_click("redraw")

        # mainlog.debug("About to draw ...")
        # mainlog.debug("additional presence is")
        # for tt in additional_presence_timetracks:
        #     mainlog.debug(tt)

        # mainlog.debug("all_tars")
        # mainlog.debug(all_tars)
        # mainlog.debug("Timetracks...")
        # mainlog.debug(timetracks_tars)
        # mainlog.debug("Presenec Timetracks...")
        # mainlog.debug(presence_timetracks)

        if presence_timetracks == [] and additional_presence_timetracks:
            presence_timetracks += additional_presence_timetracks
        timetracks += additional_work_timetracks

        # mainlog.debug("Augmented  Timetracks...")
        # mainlog.debug(timetracks)
        # mainlog.debug("Augmented Presence Timetracks...")
        # for tt in presence_timetracks:
        #     mainlog.debug(tt)

        y = 0
        dy = 60

        # Title of the view

        if view_title:
            description = QGraphicsSimpleTextItem()
            description.setText(view_title)
            description.setFont(self.title_font)

            br = QRect(0, 0,
                       description.boundingRect().width(),
                       description.boundingRect().height())
            description.setPos(0, 0)  # y - br.height()/2)
            scene.addItem(description)
            y += max(br.height() * 2, dy)

        # Presence timeline

        pointages = []
        for tar in all_tars:
            # mainlog.debug(tar)
            # mainlog.debug(tar.kind == TaskActionReportType.presence)

            if tar.kind == TaskActionReportType.presence:
                pointages.append((tar.time, Timeline.NO_DIR, tar,
                                  self._hoover_text_for(tar)))
            elif tar.kind in (TaskActionReportType.day_in,
                              TaskActionReportType.start_task):
                pointages.append((tar.time, Timeline.START, tar,
                                  self._hoover_text_for(tar)))
            elif tar.kind in (TaskActionReportType.day_out,
                              TaskActionReportType.stop_task):
                pointages.append(
                    (tar.time, Timeline.END, tar, self._hoover_text_for(tar)))
            else:
                raise Exception("Unsupported TAR.kind. I get {}".format(
                    tar.kind))

        periods = []
        for tt in presence_timetracks:
            periods.append(
                (tt.start_time, tt.start_time + timedelta(tt.duration / 24.0),
                 None))

        # Show the presence timeline

        pointages_for_presence = [
            p for p in pointages
            if p[2].kind not in (TaskActionReportType.start_task,
                                 TaskActionReportType.stop_task)
        ]

        if pointages_for_presence:
            tl = Timeline(base_time, pointages_for_presence, periods, None,
                          _("Presence"),
                          QColor(Qt.green).darker(150))
            tl.draw(scene, y)
            y += dy

        # Special activities time line

        if special_activities:
            periods = []
            for sa in special_activities:
                desc = None
                if sa.activity_type:
                    desc = sa.activity_type.description
                periods.append((sa.start_time, sa.end_time, desc))
            tl = Timeline(base_time, None, periods, None, _("Absence"),
                          QColor(Qt.red).darker(150))
            tl.draw(scene, y)
            y += dy

        # Group task action reports according to their task

        task_tar = dict()
        for tar in all_tars:
            task_id = tar.task_id

            if task_id and task_id != presence_task_id:
                if not task_id in task_tar:
                    task_tar[task_id] = []

                if tar.kind == TaskActionReportType.start_task:
                    task_tar[task_id].append((tar.time, Timeline.START, tar,
                                              self._hoover_text_for(tar)))
                elif tar.kind == TaskActionReportType.stop_task:
                    task_tar[task_id].append((tar.time, Timeline.END, tar,
                                              self._hoover_text_for(tar)))

        # Group timetracks according to their task

        task_to_timetracks = dict()
        for timetrack in timetracks:
            task_id = timetrack.task_id
            if task_id and task_id != presence_task_id:
                if not task_id in task_to_timetracks:
                    task_to_timetracks[task_id] = []
                task_to_timetracks[task_id].append(timetrack)

        # Figure out all the tasks (because each task gives a timeline)
        # It is quite possible that some timetracks are not associated
        # to any TAR and vice versa.

        all_tasks = set()
        for t in timetracks:
            if t.task_id:
                all_tasks.add(t.task_id)

        for t in all_tars:
            if t.task_id:
                all_tasks.add(t.task_id)

        all_tasks = dao.task_dao.find_by_ids_frozen(all_tasks)

        # map(lambda t:t.task and all_tasks.add(t.task),timetracks)
        # map(lambda t:t.task and all_tasks.add(t.task),all_tars)

        # The presence stuff was drawn on a separate timeline => we won't draw
        # it here again.

        # Remove presence task (because it's already drawn).
        # FIXME I use the ID because of session handling

        all_tasks = list(
            filter(lambda t: t.task_id != presence_task_id, all_tasks))

        for task in list(sorted(all_tasks, key=lambda a: a.description)):

            # Not all TAR have a timetrack !
            periods = []
            if task.task_id in task_to_timetracks:
                for tt in task_to_timetracks[task.task_id]:
                    periods.append(
                        (tt.start_time,
                         tt.start_time + timedelta(tt.duration / 24.0), None))

            tars = []
            if task.task_id in task_tar:
                tars = task_tar[task.task_id]

            timeline_title = task.description
            # timeline_title = self._make_time_line_title(task) # This will provide nice and complete bar titles

            if task.type == TaskOnOperation:
                tl = Timeline(base_time, tars, periods, task, timeline_title,
                              Qt.blue)
            else:
                tl = Timeline(base_time, tars, periods, task, timeline_title,
                              Qt.red)

            tl.draw(scene, y)
            y += dy

        self.setScene(scene)

        # See the hack top of this method !
        self.centerOn(-(self.width() - margin) / 2,
                      (self.height() - margin) / 2)
Пример #5
0
    def draw(self, scene, y):
        span_height = 15
        flag_height = span_height * 1.2

        min_time, max_time = day_span(self.base_time)

        # Draw a time line

        nb_hours = 12

        length = self.time_to_x(self.base_time +
                                timedelta(float(nb_hours) / 24.0))
        scene.addLine(QLineF(0, y, length, y), QPen(Qt.gray))

        x = self.base_time - timedelta(0,
                                       seconds=self.base_time.second,
                                       minutes=self.base_time.minute,
                                       microseconds=self.base_time.microsecond)
        end_x = self.base_time + timedelta(hours=12)

        while x <= end_x:
            tx = self.time_to_x(x)
            scene.addLine(QLineF(tx, y - 5, tx, y + 5), QPen(Qt.gray))

            description = QGraphicsSimpleTextItem()
            description.setText(str(x.hour))
            description.setPos(tx + 5, y)  # y - br.height()/2)
            description.setBrush(QBrush(Qt.gray))
            scene.addItem(description)

            x = x + timedelta(hours=1)

        # Draw spans

        total_time = timedelta(0)

        for start, end, description in self.spans:

            mainlog.debug("Span : {} -> {}".format(start, end))
            s = self.time_to_x(max(min_time, start))
            e = self.time_to_x(min(max_time, end))
            total_time += end - start

            # mainlog.debug("Total time += {}".format(end - start))

            glass_path(scene, s, y - span_height / 2, e - s, span_height,
                       QColor(self.span_color))

            r = HooverBar(QRect(s, y - span_height / 2, e - s, span_height),
                          None)

            if not description:
                r.description = [
                    _("Duration"),
                    duration_to_hm((end - start).total_seconds() / 3600.0)
                ]
            elif isinstance(description, list):
                r.description = description
            else:
                r.description = [description]

            scene.addItem(r)

        # Make the timeline clickable

        r = QGraphicsRectItem(QRect(0, 0, length, 30), None)
        scene.addItem(r)
        r.setPos(0, y - 15)
        r.setPen(QPen(Qt.transparent))
        r.setCursor(Qt.PointingHandCursor)
        r.setFlags(r.flags() | QGraphicsItem.ItemIsSelectable)
        r.setData(0, self.task)

        # Draw flags

        for t, kind, data, hoover_text in self.flags:
            x = self.time_to_x(t)

            # mainlog.debug("Drawing a flag on {} at {}".format(t,x))

            l = QGraphicsLineItem(0.0, float(-flag_height), 0.0,
                                  float(+flag_height), None)
            l.setPen(QPen(Qt.black))
            scene.addItem(l)
            l.setPos(x, y)

            #scene.addLine ( QLineF(x,y-flag_height,x,y+flag_height), QPen(Qt.black) )

            if kind == Timeline.START:
                scene.addRect(QRect(x, y - flag_height, 5, 5), QPen(Qt.black),
                              QBrush(Qt.black))
                scene.addRect(QRect(x, y + flag_height - 5, 5, 5),
                              QPen(Qt.black), QBrush(Qt.black))
            elif kind == Timeline.END:
                scene.addRect(QRect(x - 5, y - flag_height, 5, 5),
                              QPen(Qt.black), QBrush(Qt.black))
                scene.addRect(QRect(x - 5, y + flag_height - 5, 5, 5),
                              QPen(Qt.black), QBrush(Qt.black))

            r = HooverBar(QRect(0, 0, 10, 2 * flag_height), None)
            r.description = hoover_text
            scene.addItem(r)

            r.setPos(x - 5, y - flag_height)
            r.setPen(QPen(Qt.transparent))
            # item = scene.addRect ( QRect(x-5,y-flag_height,10,2*flag_height), QPen(Qt.white))
            r.setCursor(Qt.PointingHandCursor)
            r.setFlags(r.flags() | QGraphicsItem.ItemIsSelectable)
            r.setData(0, data)

        # Timeline's text

        description = QGraphicsSimpleTextItem()

        duration = ""
        if total_time.seconds > 60 or total_time.days > 0:
            duration = " - " + duration_to_hm(
                total_time.total_seconds() / 3600.0)

        tname = self.task_name.replace('\n', ' ')
        if len(tname) > 80:
            tname = tname[0:80] + u"..."

        description.setText(u"{}{}".format(tname, duration))
        br = QRect(0, 0,
                   description.boundingRect().width(),
                   description.boundingRect().height())
        description.setPos(0,
                           y - br.height() - flag_height)  # y - br.height()/2)

        r = QGraphicsRectItem(QRect(0, 0,
                                    br.width() + 10,
                                    br.height() + 10), None)
        r.setPos(-5,
                 y - 5 - br.height() - flag_height)  # y - br.height()/2 - 5)
        r.setPen(QPen(Qt.transparent))
        r.setBrush(QBrush(QColor(255, 255, 255, 128)))

        scene.addItem(r)
        scene.addItem(description)
Пример #6
0
class CircuitItem(QGraphicsItem):
    """Graphical wrapper around the engine Circuit class."""

    textH = 12
    """Height of text."""
    ioH = 20
    """Height between to I/O pins."""
    ioW = 15
    """Length of I/O pins."""
    radius = 10
    """Radius of I/O pin heads."""

    def __init__(self, circuit):
        super(CircuitItem, self).__init__()
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        imgDir = filePath('icons/')
        self.data = circuit
        """The real info. The class CircuitItem is just a graphical container
        around it. data is saved / loaded to / from file.
        """
        self.image = QImage(imgDir + circuit.__class__.__name__ + '.png')
        """The graphical representation of our item on screen."""
        if not self.image:
            self.image = QImage(imgDir + 'Default.png')
            self.showCategory = True
        self.showName = True
        """Is the item's name shown on screen?"""
        self.showCategory = False
        """Is the item's category (circuit class) shown on screen?"""
        self.name = QGraphicsSimpleTextItem(self)
        # that won't rotate when the PlugItem is rotated by the user.
        self.name.setFlag(QGraphicsItem.ItemIgnoresTransformations)
        self.name.setText(self.data.name)
        self.category = QGraphicsSimpleTextItem(self)
        self.category.setFlag(QGraphicsItem.ItemIgnoresTransformations)
        self.category.setText(self.data.category)
        self.setupPaint()

    def boundingRect(self):
        """Qt requires overloading this when overloading QGraphicsItem."""
        W = 2 * self.radius + 2 * self.ioW + self.imgW
        ni = self.data.nb_inputs()
        no = self.data.nb_outputs()
        t = (1 - int(max(ni, no) / 2)) * self.ioH - self.radius / 2
        b = (1 + int(max(ni, no) / 2)) * self.ioH + self.radius / 2
        return (
            QRectF(-self.ioW - self.radius, t, W, b - t) if max(ni, no) > 1
            else QRectF(-self.ioW - self.radius, 0, W, self.image.height()))

    def handleAtPos(self, pos):
        """Is there an interactive handle where the mouse is? Return it."""
        for i in range(self.nIn):
            if self.inputPaths[i].contains(pos):
                return self.data.inputList[i]
        for i in range(self.nOut):
            if self.outputPaths[i].contains(pos):
                return self.data.outputList[i]

    def itemChange(self, change, value):
        """Warning view it will soon have to correct pos."""
        if change == QGraphicsItem.ItemPositionHasChanged:
            # Restart till we stop moving.
            self.scene().views()[0].timer.start()
        return QGraphicsItem.itemChange(self, change, value)

    def paint(self, painter, option, widget):
        """Draws the item."""
        painter.setPen(QPen(QColor('black'), 2))
        ni = self.data.nb_inputs()
        no = self.data.nb_outputs()
        for i in range(1 - int(ni / 2), 2 + int(ni / 2)):
            if i != 1 or ni % 2:
                painter.drawLine(-self.ioW, i * self.ioH, 0, i * self.ioH)
        for i in range(1 - int(no / 2), 2 + int(no / 2)):
            if i != 1 or no % 2:
                painter.drawLine(
                    self.imgW, i * self.ioH, self.imgW + self.ioW,
                    i * self.ioH)
        painter.drawImage(QRectF(0, 0, self.imgW, self.imgH), self.image)
        for i in range(ni):
            painter.drawPath(self.inputPaths[i])
        painter.drawLine(
            0, (1 - int(ni / 2)) * self.ioH, 0, (1 + int(ni / 2)) * self.ioH)
        for i in range(no):
            painter.drawPath(self.outputPaths[i])
        painter.drawLine(
            self.imgW,
            (1 - int(no / 2)) * self.ioH,
            self.imgW,
            (1 + int(no / 2)) * self.ioH)
        # Default selection box doesn't work; simple reimplementation.
        if option.state & QStyle.State_Selected:
            pen = QPen(Qt.black, 1, Qt.DashLine)
            painter.setPen(pen)
            painter.drawRect(self.boundingRect())

    def setCategoryVisibility(self, isVisible):
        """Show/Hide circuit category (mostly useful for user circuits)."""
        self.showCategory = isVisible
        self.setupPaint()

    def setNameVisibility(self, isVisible):
        """Shows/Hide the item name in the graphical view."""
        self.showName = isVisible
        self.setupPaint()

    def setNbInputs(self, nb):
        """Add/Remove inputs (for logical gates)."""
        if nb > self.data.nb_inputs():
            for x in range(nb - self.data.nb_inputs()):
                Plug(True, None, self.data)
        elif nb < self.data.nb_inputs():
            for x in range(self.data.nb_inputs() - nb):
                self.data.remove_input(self.data.inputList[0])
        self.setupPaint()

    def setupPaint(self):
        """Offscreen rather than onscreen redraw (few changes)."""
        self.nIn = self.data.nb_inputs()
        self.nOut = self.data.nb_outputs()
        # 3 sections with different heights must be aligned :
        self.imgH = self.image.size().height()   # central (png image)
        self.imgW = self.image.size().width()
        self.inH = (self.nIn - 1) * self.ioH + 2 * self.radius  # inputs
        self.outH = (self.nOut - 1) * self.ioH + 2 * self.radius    # outputs
        # therefore we calculate a vertical offset for each section :
        self.maxH = max(self.imgH, self.inH, self.outH)
        self.imgOff = (
            0 if self.maxH == self.imgH else (self.maxH - self.imgH) / 2.)
        self.inOff = (
            0 if self.maxH == self.inH else (self.maxH - self.inH) / 2.)
        self.outOff = (
            0 if self.maxH == self.outH else (self.maxH - self.outH) / 2.)
        # i/o mouseover detection. Create once, use on each mouseMoveEvent.
        self.inputPaths = []
        self.outputPaths = []
        ni = self.data.nb_inputs()
        no = self.data.nb_outputs()
        for i in range(1 - int(ni / 2), 2 + int(ni / 2)):
            if i != 1 or ni % 2:
                path = QPainterPath()
                path.addEllipse(
                    -self.ioW - self.radius, i * self.ioH - self.radius / 2,
                    self.radius, self.radius)
                self.inputPaths.append(path)
        for i in range(1 - int(no / 2), 2 + int(no / 2)):
            if i != 1 or no % 2:
                path = QPainterPath()
                path.addEllipse(
                    self.imgW + self.ioW, i * self.ioH - self.radius / 2,
                    self.radius, self.radius)
                self.outputPaths.append(path)
        self.name.setVisible(self.showName)
        self.category.setVisible(self.showCategory)
        if self.showName or self.showCategory:
            br = self.mapToScene(self.boundingRect())
            w = self.boundingRect().width()
            h = self.boundingRect().height()
            realX = min([i.x() for i in br])
            realY = min([i.y() for i in br])
            firstY = realY + (w if self.rotation() % 180 else h) + 1
            secondY = firstY + self.textH
            if self.showName:
                self.name.setBrush(QColor('red'))
                self.name.setText(self.data.name)
                self.name.setPos(self.mapFromScene(realX, firstY))
            if self.showCategory:
                self.category.setBrush(QColor('green'))
                self.category.setText(
                    self.data.category if self.data.category
                    else self.data.__class__.__name__)
                self.category.setPos(self.mapFromScene(
                    realX, secondY if self.showName else firstY))
        self.prepareGeometryChange()    # Must be called (cf Qt doc)
        self.update()       # Force onscreen redraw after changes.
Пример #7
0
class PlugItem(QGraphicsPathItem):
    """Graphical wrapper around the engine Plug class."""

    bodyW = 30
    """The width of the body of plugs."""
    pinW = 10
    """The width of the pin part of plugs."""

    def __init__(self, plug):
        super(PlugItem, self).__init__()
        self.data = plug
        """The real info. The class PlugItem is just a graphical container
        around it. data is saved / loaded to / from file.
        """
        self.showName = False
        """Is the name of the item shown on screen?"""
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        self.setAcceptsHoverEvents(True)
        self.setPen(QPen(QBrush(QColor(QColor('black'))), 2))
        # This path is needed at each mouse over event, to check if
        # the mouse is over a pin. We save it as an instance field,
        # rather than recreate it at each event.
        self.pinPath = QPainterPath()
        if self.data.isInput:
            self.pinPath.addEllipse(
                self.bodyW + self.pinW / 2, self.bodyW / 2,
                self.pinW, self.pinW)
        else:
            self.pinPath.addEllipse(
                self.pinW / 2, self.bodyW / 2, self.pinW, self.pinW)
        f = QFont('Times', 12, 75)
        # Name and value text labels.
        self.name = QGraphicsSimpleTextItem(self)
        # that won't rotate when the PlugItem is rotated by the user.
        self.name.setFlag(QGraphicsItem.ItemIgnoresTransformations)
        self.name.setText(self.data.name)
        self.name.setFont(f)
        self.value = QGraphicsSimpleTextItem(self)
        self.value.setFlag(QGraphicsItem.ItemIgnoresTransformations)
        # Or else value would get the clicks, instead of the PlugItem.
        self.value.setFlag(QGraphicsItem.ItemStacksBehindParent)
        self.value.setFont(f)
        self.setupPaint()

    def handleAtPos(self, pos):
        """Is there an interactive handle where the mouse is?
        Also return the Plug under this handle.
        """
        return self.data if self.pinPath.contains(pos) else None

    def itemChange(self, change, value):
        """Warning view it will soon have to correct pos."""
        if change == QGraphicsItem.ItemPositionHasChanged:
            # Restart till we stop moving.
            self.scene().views()[0].timer.start()
        return QGraphicsItem.itemChange(self, change, value)

    def setAndUpdate(self):
        """Change the undelying plug's value, and force updates items."""
        self.data.set(not self.data.value)
        for i in self.scene().items():
            if isinstance(i, PlugItem) or isinstance(i, WireItem):
                i.setupPaint()

    def setNameVisibility(self, isVisible):
        """Shows/Hide the item name in the graphical view."""
        self.showName = isVisible
        self.setupPaint()

    def setupPaint(self):
        """Offscreen rather than onscreen redraw (few changes)."""
        path = QPainterPath()
        if self.data.isInput:
            path.addEllipse(
                self.pinW / 2, self.pinW / 2, self.bodyW, self.bodyW)
        else:
            path.addRect(
                3 * self.pinW / 2 + 1, self.pinW / 2, self.bodyW, self.bodyW)
        path.addPath(self.pinPath)
        self.setPath(path)
        self.name.setVisible(self.showName)
        self.name.setText(self.data.name)
        br = self.mapToScene(self.boundingRect())
        w = self.boundingRect().width()
        h = self.boundingRect().height()
        realX = min([i.x() for i in br])
        realY = min([i.y() for i in br])
        self.name.setPos(self.mapFromScene(
            realX, realY + (w if self.rotation() % 180 else h) + 1))
        self.value.setText(
            str(int(self.data.value)) if self.data.value is not None else 'E')
        self.value.setPos(self.mapFromScene(realX + w / 3, realY + h / 3))
        self.value.setBrush(QColor('green' if self.data.value else 'red'))
        self.update()       # Force onscreen redraw after changes.