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)
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)
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
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)
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)
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.
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.