Affichage du node au lancement de l'application
Avant-propos
Voir documentation de QGraphicsItem, QGraphicsTextItem. QCheckBox, QGraphicsProxyWidget.
Mode de remplissage Qt.WindingFill, Qt.OddEvenFill
Description
pkl
du graphe, puis parcours des grappes.ctrl_scene.py > CtrlScene.setup()
:
""" Code provisoire pour afficher un node. """
self.provisoire()
self.provisoire()
n'existe pas ⇒ Ajoutons-la.ctrl_scene.py > CtrlScene.provisoire()
:
def provisoire(self):
""" Provisoire. Ajout d'un node pour la mise au point. A supprimer après le coding du drag & drop. """
from nodes.generateurs.signaux import Node
Node(self, 'Node1')
Node
qui n'existe pas ⇒ Ajoutons-la./nodes/generateurs/signaux.py
:
from pc.ctrl_node import CtrlNode
d_datas = {
'name': 'Signaux',
'icon': 'signaux.png',
}
class Node(CtrlNode):
def __init__(self, o_scene, s_id):
super().__init__(o_scene, s_id)
self.setup()
def setup(self):
# Code spécifique ...
super().setup()
Node
hérite de la classe CtrlNode
qui n'existe pas ⇒ Créons-la./pc/ctrl_node.py
. Copier-coller le code suivant :
from ui_node import UiNode
class CtrlNode:
def __init__(self, o_scene, s_id):
self.o_scene = o_scene
self.o_gr_node = None
def setup(self):
self.o_gr_node = UiNode(self) # Gestion de l'UI (Interface utilisateur) : dessin couleurs, ...
self.o_scene.o_gr_scene.addItem(self.o_gr_node) # Incorporation dans la scène.
UiNode
, nécessaire pour l'affichage, n'existe pas ⇒ Créons-la./pc/ui_node.py
. Copier-coller le code suivant :
from PyQt5.QtWidgets import QGraphicsItem
from PyQt5.QtCore import QRectF
from PyQt5.QtGui import QPainterPath
import pyqtgraph as pg
class UiNode(QGraphicsItem):
def __init__(self, o_node):
super().__init__()
self.o_node = o_node
self.path_outline = QPainterPath()
self.path_outline.addRoundedRect(0, 0, 96, 60, 6, 6)
def boundingRect(self):
return QRectF(0, 0, 96, 60).normalized()
def paint(self, painter, QStyleOptionGraphicsItem, widget=None):
painter.setPen(pg.mkPen(color="#000", width=4))
painter.setBrush(pg.mkBrush(color='#8888bbaa')) # Opacité = 'aa'
painter.drawPath(self.path_outline.simplified())
UiNode
pour un meilleur découpage.init_ui()
réservée aux affectations, pour décharger la méthode __init__()
réservée aux déclarations (autant que faire se peut). Remplacement de la méthode __init__()
actuelle par :UiNode.__init__()
:
def __init__(self, o_node, parent=None):
super().__init__(parent)
self.o_node = o_node
self.path_outline = QPainterPath()
self.init_ui()
def init_ui(self):
self.path_outline.addRoundedRect(0, 0, 96, 60, 6, 6)
""" Flags. """
self.setFlag(QGraphicsItem.ItemIsMovable) # Déplaçable
ItemIsMovable
.drawLines(self, xxxxxxx, *): not enough arguments
UiView.init_ui()
section Flags (Solution trouvée dans un forum sur internet) :
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) # Sans cette ligne, warning dans drawBackground.
Vérifier le bon fonctionnement du drag.
mouseReleaseEvent()
.UiNode
.mouseReleaseEvent()
et save_pos()
dans la classe UiNode
:
def save_pos(self):
x, y = self.pos().x(), self.pos().y()
self.od_pkl.write([self.s_id, 'position'], (x, y))
self.o_node.o_scene.o_pkl.backup()
def mouseReleaseEvent(self, ev):
super().mouseReleaseEvent(ev)
""" Demande à la scène d'enregistrer tous les nodes, en cas de multiselection déplacée à la souris.
- La fonction broadcastée par la scène (à chaque node) est save_pos(), ci-dessus. """
self.save_pos()
od_pkl
et s_id
dans UiNode
et CtrlNode
⇒ ajouter ces lignes :ui_node.py > UiNode.__init__()
:
self.s_id = self.o_node.s_id
self.od_pkl = self.o_node.od_pkl
ctrl_node.py > CtrlNode.__init__()
:
self.s_id = s_id
self.od_pkl = self.o_scene.o_pkl.od_pkl
self.pos = self.od_pkl.read([self.s_id, 'position'], (0, 0))
UiNode.init_ui()
.ui_node.py > UiNode.init_ui()
:
def init_ui(self):
""" Position. """
self.setPos(*self.o_node.pos)
""" Dessin du node : rectangle aux coins arrondis. """
self.path_outline.addRoundedRect(0, 0, 96, 60, 6, 6)
""" Flags. """
self.setFlag(QGraphicsItem.ItemIsMovable) # Déplaçable
Tester.
UiNode.save_pos()
doit être modifiée :ui_node.py > UiNode.save_pos()
:
def save_pos(self):
x, y = self.pos().x(), self.pos().y()
b_magnetic = self.o_node.o_scene.get_param('Grille magnétique')
if b_magnetic:
mesh = int(self.o_node.o_scene.get_param('Pas de la grille', 16))
x, y = mesh * round(x/mesh), mesh * round(y/mesh)
self.setPos(x, y)
self.od_pkl.write([self.s_id, 'position'], (x, y))
self.o_node.o_scene.o_pkl.backup()
CtrlScene.get_param()
qui n'existe pas ⇒ Créons-la :ctrl_scene.py > CtrlScene.get_param()
:
def get_param(self, l_keys, v_default=None):
od_params = Dictionary(self.o_params.od_params[self.main_key])
return od_params.read(l_keys, v_default)
CtrlScene.refresh()
.
ctrl_scene.py > CtrlScene.refresh()
:
def refresh(self, l_keys):
self.o_gr_scene.set_params()
""" Titre de l'onglet. """
if l_keys[-1] == "Nom de l'onglet":
titre = self.o_params.od_params.read(l_keys)
indx = self.o_main.tabGraphs.currentIndex()
self.o_main.tabGraphs.setTabText(indx, titre)
""" Propagation. """
if l_keys[-1] == 'Grille magnétique' or l_keys[-1] == 'Pas de la grille':
for o_gr_node in self.get_gr_nodes():
o_gr_node.save_pos()
CtrlScene.get_gr_nodes()
, qui n'existe pas ⇒ Créons-la :ctrl_scene.py > CtrlScene.get_gr_nodes()
:
def get_gr_nodes(self):
""" Renvoie la liste de tous les o_gr_nodes contenus dans la scène. """
l_items = self.o_gr_scene.items()
l_gr_nodes = list()
for o_gr_node in l_items:
if o_gr_node.__class__.__name__ == 'UiNode':
l_gr_nodes.append(o_gr_node)
return l_gr_nodes
UiNode.init_ui()
, ajouter la gestion du titre :
""" Titre. """
self.title_item.setFont(QFont('Arial', 9)) # Police et taille
self.title_item.setPos(10, 1)
self.title_item.setPlainText('Le titre') # Titre 'en dur', provisoirement.
Ajouter QFont
dans les imports.
self.title_item
qui n'est pas déclaré ⇒ Ajouter sa déclaration dans __init__()
.UiNode.__init__()
, avant self.init_ui()
:
self.title_item = QGraphicsTextItem(self)
Ajouter QGraphicsTextItem
dans les imports.
UiNode.init_ui()
:
""" Titre. """
self.path_title.addRoundedRect(0, 0, 96, 24, 6, 6)
self.title_item.setFont(QFont('Arial', 9)) # Police et taille
self.title_item.setPos(10, 2)
self.title_item.setPlainText('Le titre') # Titre 'en dur', provisoirement.
Code 4 - Déclaration de l'attribut path_title sans UiNode.__init__() :
self.path_title = QPainterPath()
Code 5 - Dans UiNode.paint()
ajouter la couleur de fond du titre.
Remplacer tout le code de UiNode.paint()
par celui-ci :
def paint(self, painter, QStyleOptionGraphicsItem, widget=None):
""" Titre. """
painter.setPen(Qt.NoPen)
painter.setBrush(pg.mkBrush(color='#bbbbbb'))
painter.drawPath(self.path_title.simplified())
""" Contenu. """
painter.setPen(pg.mkPen(color="#000", width=4.))
painter.setBrush(pg.mkBrush(color='#8888bbaa')) # Opacité = 'aa'
painter.drawPath(self.path_outline.simplified())
UiNode.init_ui()
:
""" Titre. """
self.path_title.addRoundedRect(0, 0, 96, 24, 6, 6)
self.path_title.setFillRule(Qt.WindingFill)
self.path_title.addRect(0, 18, 96, 6)
# |_ Enlève les arrondis du bas du titre, pour éviter un effet disgracieux (Commenter cette ligne pour le voir)
self.title_item.setFont(QFont('Arial', 9)) # Police et taille
self.title_item.setPos(10, 0)
self.title_item.setPlainText('Le titre') # Titre 'en dur', provisoirement.
UiNode.init_ui()
. La section flags devient :
""" Flags. """
self.setFlag(QGraphicsItem.ItemIsMovable) # Peut être déplacé.
self.setFlag(QGraphicsItem.ItemIsSelectable) # Peut être sélectionné.
self.setAcceptHoverEvents(True) # Accepte le survol.
UiNode
:
def hoverEnterEvent(self, ev):
self.b_hover = True
""" Curseur souris : main. """
self.setCursor(Qt.PointingHandCursor)
self.update() # Force l'affichage.
def hoverLeaveEvent(self, ev):
self.b_hover = False
""" Curseur souris : flèche. """
self.setCursor(Qt.ArrowCursor)
self.update() # Force l'affichage.
b_hover
doit être déclaré ⇒ dans UiNode.__init__()
, ajouter cette ligne :
self.b_hover = False
UiNode.__init__()
:
self.on_off = QCheckBox()
Ajouter QCheckBox
dans les imports.UiNode.init_ui()
:
""" On / Off """
QGraphicsProxyWidget(self).setWidget(self.on_off)
self.on_off.move(86, -4)
self.on_off.setChecked(True)
Ajouter QGraphicsProxyWidget
dans les imports.UiNode.init_ui()
:
""" Événements. """
self.on_off.stateChanged.connect(self.o_node.set_checked)
Oui mais ... la méthode CtrlNode.set_checked()
est appelée par cet événement, mais elle n'existe pas ⇒ Créons-la.
CtrlNode.set_checked()
:
def set_checked(self, val):
self.b_chk = val
self.b_on = self.b_chk
Oui mais ... les booléens b_chk
et b_on
n'ont pas encore été déclarés ⇒ Ajoutons-les :
CtrlNode.__init__()
, à la fin :
self.b_chk = True
self.b_on = True
b_on
: On (normal) / Offb_hover
: Survolé / Non survolé.isSelected()
: Sélectionné / Non sélectionné.self.path_title.addRoundedRect(0, 0, 96, 24, 6, 6)
d_display
à CtrlNode
. C'est un dictionnaire de dictionnaires.CtrlNode
:
@property
def d_display(self):
""" Attributs par défaut pouvant être surchargés dans les classes dérivées. """
return {
'geometry': {'width': 96, 'height': 60, 'h_title': 24, 'round': 6},
'col_pen': {'select': '#ffa637', 'hover': '#888', 'on': "#000", 'off': '#aaaa00'}, # ordre = priorité.
'col_brush': {'on': "#8888bbaa", 'off': '#ccccccaa'},
'thick_pen': {'hover': 4., 'leave': 2.}
}
UiNode
:
@property
def state_pen(self):
color = 'select' if self.isSelected() else ('hover' if self.b_hover else ('on' if self.o_node.b_on else 'off'))
thick = self.o_node.d_display['thick_pen']['hover'] if self.b_hover else self.o_node.d_display['thick_pen'][
'leave']
return pg.mkPen(color=self.o_node.d_display['col_pen'][color], width=thick)
@property
def state_brush(self):
color = self.o_node.d_display['col_brush']['on'] if self.o_node.b_on else self.o_node.d_display['col_brush']['off']
return pg.mkBrush(color=color)
Les méthodes qui utilisent des valeurs 'en dur' doivent donc être réécrites :
UiNode.init_ui()
:
def init_ui(self):
""" Paramètres. """
width, height = self.o_node.d_display['geometry']['width'], self.o_node.d_display['geometry']['height']
h_title, n_round = self.o_node.d_display['geometry']['h_title'], self.o_node.d_display['geometry']['round']
""" Position. """
self.setPos(*self.o_node.pos)
""" Titre. """
geometry = (0, 0, width, h_title, n_round, n_round)
self.path_title.setFillRule(Qt.WindingFill)
self.path_title.addRoundedRect(*geometry)
self.path_title.addRect(0, h_title - n_round, width, n_round)
# |_ Enlève les arrondis du bas du titre, pour éviter un effet disgracieux (Commenter cette ligne pour le voir)
self.title_item.setFont(QFont('Arial', 9)) # Police et taille
self.title_item.setPos(10, 1)
self.title_item.setPlainText('Le titre')
""" Dessin du node : rectangle aux coins arrondis. """
self.path_outline.addRoundedRect(0, 0, width, height, n_round, n_round)
""" On / Off """
QGraphicsProxyWidget(self).setWidget(self.on_off)
self.on_off.move(width-10, -4)
self.on_off.setChecked(True)
""" Flags. """
self.setFlag(QGraphicsItem.ItemIsMovable) # Peut être déplacé.
self.setFlag(QGraphicsItem.ItemIsSelectable) # Peut être sélectionné.
self.setAcceptHoverEvents(True) # Accepte le survol.
""" Événements. """
self.on_off.stateChanged.connect(self.o_node.set_checked)
UiNode.boundingRect()
et UiNode.paint()
:
def boundingRect(self):
return QRectF(0, 0, self.o_node.d_display['geometry']['width'],
self.o_node.d_display['geometry']['height']).normalized()
def paint(self, painter, QStyleOptionGraphicsItem, widget=None):
""" Titre - Valeurs fixes 'en dur'. """
painter.setPen(Qt.NoPen)
painter.setBrush(pg.mkBrush(color='#bbbbbbaa'))
painter.drawPath(self.path_title.simplified())
""" Contenu - Valeurs paramétrées. """
painter.setPen(self.state_pen)
painter.setBrush(self.state_brush)
painter.drawPath(self.path_outline.simplified())
Enfin, la propriété d_display
de CtrlNode
peut être surchargé dans les classes dérivées.
Par exemple, dans la classe dérivée signaux.py > Node
:
@property
def d_display(self):
""" Attributs par défaut, surchargés. """
d_parent = super().d_display.copy()
d_parent['geometry'].update({'height': 160}),
return d_parent
Chacune des classes dérivées peut donc avoir ses propres dimensions, couleurs, épaisseurs de trait, ...)
N'hésitez pas à en ajouter, à les modifier ou même en créer de nouvelles.
Tester différents états du même node :
Bonjour les codeurs !