1 # -*- coding: utf-8 -*-
2 # CONFIGURATION MANAGEMENT OF EDF VERSION
3 # ======================================================================
4 # COPYRIGHT (C) 1991 - 2002 EDF R&D WWW.CODE-ASTER.ORG
5 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
6 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
7 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
8 # (AT YOUR OPTION) ANY LATER VERSION.
10 # THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
11 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
12 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
13 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.
15 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
16 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
17 # 1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
20 # ======================================================================
21 import os,sys,string,re,types,traceback
29 __version__="$Name: $"
30 __Id__="$Id: treewidget.py,v 1.18 2004/11/17 16:08:34 eficas Exp $"
33 Fonte_Standard = fontes.standard
36 def __init__(self,appli,jdc_item,scrolledcanvas,command = None,rmenu=None):
38 self.scrolledcanvas = scrolledcanvas
39 self.canvas = self.scrolledcanvas.component('canvas')
40 self.canvas.bind("<Key-Prior>", self.page_up)
41 self.canvas.bind("<Key-Next>", self.page_down)
42 self.canvas.bind("<Key-Up>", self.unit_up)
43 self.canvas.bind("<Key-Down>", self.unit_down)
44 self.canvas.bind("<Key-Left>", self.mot_up)
45 self.canvas.bind("<Key-Right>", self.mot_down)
46 self.canvas.bind("<1>", self.canvas_select)
48 self.command = command
53 self.node_selected = None
56 def canvas_select(self,event):
57 self.canvas.focus_set()
59 def page_up(self,event):
60 event.widget.yview_scroll(-1, "page")
61 def page_down(self,event):
62 event.widget.yview_scroll(1, "page")
63 def unit_up(self,event):
64 event.widget.yview_scroll(-1, "unit")
65 def unit_down(self,event):
66 event.widget.yview_scroll(1, "unit")
68 def mot_down(self,event):
69 self.select_next(None)
70 self.canvas.focus_set()
72 def mot_up(self,event):
73 self.node_selected.select_mot_previous()
74 self.canvas.focus_set()
76 def build_children(self):
77 """ Construit la liste des enfants de self """
79 child = self.item.itemNode(self,self.item,self.command,self.rmenu)
80 self.children.append(child)
81 child.state='expanded'
84 """ Dessine l'arbre """
87 for child in self.children:
89 lasty = child.lasty + 15
90 self.children[0].select()
91 self.resizescrollregion()
93 def deselectall(self):
94 """ déselectionne tous les éléments de l'arbre """
95 if self.node_selected :
96 self.node_selected.deselect()
99 """ Update tous les éléments de l'arbre """
100 for child in self.children:
103 def update_valid(self) :
104 """Cette methode a pour but de mettre a jour la validite du noeud
105 et de propager la demande de mise à jour à son parent
109 def resizescrollregion(self):
110 x0,y0,x1,y1=self.canvas.bbox(ALL)
111 # On ajoute une marge approximativement de la moitié du canvas
112 y1=y1+self.canvas.winfo_height()/2
113 self.canvas.configure(scrollregion = (x0,y0,x1,y1))
115 def select_next(self,event):
116 self.node_selected.select_next()
117 self.canvas.focus_set()
119 def select_previous(self,event):
120 self.node_selected.select_previous()
122 def full_creation(self,name,index):
123 # A changer lorsqu'il y aura plusieurs jdc ouverts en même temps
124 self.children[0].full_creation(name,index)
127 for child in self.children :
128 self.verif_all_children()
131 x1, y1, x2, y2=apply(self.canvas.bbox, items)
132 while x2 > self.canvas.canvasx(0)+self.canvas.winfo_width():
133 old=self.canvas.canvasx(0)
134 self.canvas.xview_scroll( 1, 'units')
135 # avoid endless loop if we can't scroll
136 if old == self.canvas.canvasx(0):
138 while y2 > self.canvas.canvasy(0)+self.canvas.winfo_height():
139 old=self.canvas.canvasy(0)
140 self.canvas.yview_scroll( 1, 'units')
141 if old == self.canvas.canvasy(0):
143 # done in this order to ensure upper-left of object is visible
144 while x1 < self.canvas.canvasx(0):
145 old=self.canvas.canvasx(0)
146 self.canvas.xview_scroll( -1, 'units')
147 if old == self.canvas.canvasx(0):
149 while y1 < self.canvas.canvasy(0):
150 old=self.canvas.canvasy(0)
151 self.canvas.yview_scroll( -1, 'units')
152 if old == self.canvas.canvasy(0):
156 def __init__(self,parent,item,command=None,rmenu=None):
159 self.command = command
161 self.tree = self.parent.tree
162 self.appli = self.parent.appli
163 self.canvas = self.parent.canvas
167 self.state='collapsed'
170 self.x = self.y =None
174 # etape = noeud d'étape auquel appartient self
175 # = self si c'est lui-même
176 if isinstance(self.parent,Tree) :
177 # on est sur un noeud de JDC
181 elif isinstance(self.parent.parent,Tree) :
182 # on est sur un noeud d'étape
183 self.racine = self.parent
185 self.nature = 'ETAPE'
187 # on est sur un noeud de mot-clé
188 self.racine = self.parent.racine
189 self.etape=self.parent.etape
190 self.nature = 'MOTCLE'
192 def build_children(self):
193 """ Construit la liste des enfants de self """
195 sublist = self.item._GetSubList()
196 if not sublist : return
197 for item in sublist :
198 child = item.itemNode(self,item,self.command,self.rmenu)
199 self.children.append(child)
201 #-----------------------------------------------
202 # Méthodes de sélection/déselection d'un noeud
203 #-----------------------------------------------
205 def select(self, event=None):
207 Rend le noeud courant (self) sélectionné et déselectionne
210 if not self.children : self.build_children()
211 self.tree.deselectall()
213 self.tree.node_selected = self
214 if self.command:apply(self.command,(self,))
218 def deselect(self, event=None):
219 """ Déselectionne self """
221 if self.displayed == 1 : self.dehighlight()
223 def make_visible(self):
224 """ Rend l'objet self visible cad déplace le scroll pour que self soit dans
227 lchild=self.last_child()
228 self.tree.see((self.image_id,lchild.image_id))
230 def select_next(self,ind=0):
231 """ on doit chercher à sélectionner dans l'ordre:
232 - son premier fils s'il est affiché
233 - son frère cadet s'il existe
234 - son oncle (benjamin de son père)
235 - ... appel récursif ...
237 if self.state=='expanded' and len(self.children) > ind:
238 self.children[ind].select()
240 index = self.parent.children.index(self) + 1
242 if isinstance(self.parent,TREE) :
244 self.children[ind].select()
246 self.children[0].select()
248 self.parent.select_next(index)
250 self.parent.select_next(index)
252 def select_mot_previous(self):
253 index = self.parent.children.index(self) - 1
256 self.parent.children[index].select()
262 def select_previous(self):
263 """ on doit d'abord sélectionner(dans l'ordre) :
267 index = self.parent.children.index(self) + 1
269 self.parent.children[index].select()
273 def popup(self,event=None):
275 Declenche le traitement associé au clic droit de la souris
278 if not self.rmenu:return
279 apply(self.rmenu,(self,event))
281 #-----------------------------------------------
282 # Méthodes de recherche d'informations
283 #-----------------------------------------------
284 def geticonimage(self,name=None):
286 Retourne l'image qui doit être associée à self
289 name = self.item.GetIconName()
290 if not name or name == 'aucune' :
292 return images.get_image(name)
294 def get_nb_children(self):
295 """ Retourne le nombre d'enfants affichés de self """
297 if self.state =='collapsed' : return nb
298 for child in self.children :
299 nb = nb + 1 + child.get_nb_children()
302 def get_liste_id(self):
303 """ Retourne la liste de tous les id (filiation comprise) de self """
305 for child in self.children:
306 liste.extend(child.get_liste_id())
309 def get_node_fils(self,name) :
310 """ Retourne le fils de self de nom name s'il existe"""
311 for child in self.children:
312 if child.item.get_nom() == name: return child
314 #-----------------------------------------------
315 # Méthodes d'affichage d'un noeud
316 #-----------------------------------------------
318 """ Permet de tracer le noeud self """
319 # le début du noeud est en x,y
325 # choix de l'icone à afficher : + ou -
326 if self.item.IsExpandable():
327 if self.state == 'expanded':
328 iconname = "minusnode"
329 callback = self.collapse
331 iconname = "plusnode"
332 callback = self.expand
333 image = self.geticonimage(name=iconname)
334 self.icone_id = self.canvas.create_image(self.x, self.y, image=image)
335 self.canvas.tag_bind(self.icone_id, "<1>", callback)
336 self.id.append(self.icone_id)
337 # création de la ligne horizontale
338 self.ligne_id = self.canvas.create_line(self.x,self.y,self.x+10,self.y)
339 self.id.append(self.ligne_id)
340 self.canvas.tag_lower(self.ligne_id)
341 # affichage de l'icone (carre ,rond, ovale ...) de couleur
342 image = self.geticonimage()
344 self.image_id = self.canvas.create_image(self.x+15,self.y,image = image)
345 self.canvas.tag_bind(self.image_id,"<1>",self.select)
346 self.canvas.tag_bind(self.image_id,"<3>",self.popup)
347 self.id.append(self.image_id)
350 # affichage du texte : nom de l'objet (ETAPE ou MOT-CLE) et sa valeur
352 if self.state == 'expanded' :
353 if not self.children : self.build_children()
354 if len(self.children) > 0:
356 self.lasty = self.children[-1].lasty
358 def drawchildren(self):
359 """ Dessine les enfants de self """
362 for child in self.children:
364 nb = child.get_nb_children()
369 """ Affiche les deux zones de texte après l'icône de couleur de l'objet """
370 if self.image_id != None :
375 # nom,fonte et couleur de l'objet du noeud à afficher
376 labeltext,fonte,couleur = self.item.GetLabelText()
377 if labeltext == '' : labeltext = ' '
378 if fonte == None : fonte = Fonte_Standard
379 if couleur == None : couleur = 'black'
380 # création du widget label
381 self.label = Label(self.canvas,
386 self.label_id = self.canvas.create_window(textx,texty,window=self.label,anchor='w')
387 self.id.append(self.label_id)
388 # bindings sur le widget label
389 self.label.bind("<1>", self.select)
390 self.label.bind("<3>", self.popup)
391 self.label.bind("<Enter>",self.enter)
392 self.label.bind("<Leave>",self.leave)
393 # valeur de cet objet à afficher
394 x0, y0, x1, y1 = self.canvas.bbox(self.label_id)
395 textx = max(x1, 200) + 10
396 text = self.item.GetText() or " "
397 self.text = Label(self.canvas, text=text,
398 bd=0, padx=2, pady=2,background='gray95',
404 self.text_id = self.canvas.create_window(textx, texty,anchor="w", window=self.text)
405 self.id.append(self.text_id)
407 def highlight(self,event=None):
408 """ Met en surbrillance self"""
409 if hasattr(self,'label'):
410 self.label.configure(fg='white',bg='#00008b')
412 def dehighlight(self,event=None):
413 """ Rétablit l'affichage normal de self"""
414 if hasattr(self,'label'):
415 self.label.configure(fg='black',bg='gray95')
417 def enter(self,event=None):
418 """ Met en surbrillance self et affiche le fr de l'objet """
420 fr = self.item.get_fr()
421 self.appli.affiche_infos(fr)
423 def leave(self,event=None):
424 """ Rétablit l'affichage normal de self et efface le fr de l'objet """
425 if not self.selected :
427 self.appli.affiche_infos('')
429 def collapse_children(self):
430 """ Collapse récursivement tous les descendants de self """
431 if not self.children : return
432 for child in self.children:
433 child.state='collapsed'
435 child.collapse_children()
437 def collapse(self,event = None):
438 """ Collapse self et descendants et retrace self """
439 nb = self.get_nb_children()
440 self.state = 'collapsed'
441 self.collapse_children()
445 def expand(self,event = None):
446 """ Expanse self et le retrace """
447 if not self.item.isactif() : return
448 if not self.children : self.build_children()
449 self.state = 'expanded'
450 nb = self.get_nb_children()
455 """ Redessine self : nb est le décalage à introduire
456 en dessous de self pour le redessiner """
457 # nb = nombre d'items de décalage
459 # on efface self et on le redessine
461 self.draw(self.x,self.y)
462 # Il n'est pas nécessaire d'appeler update
463 # il suffit d'updater les coordonnees et de retracer les lignes
464 self.racine.update_coords()
465 self.racine.trace_ligne()
467 self.tree.resizescrollregion()
469 def update_coords(self):
470 """ Permet d'updater les coordonnes de self et de tous ses enfants"""
471 if self.displayed == 0 : return
472 if self.image_id != None :
473 coords = self.canvas.coords(self.image_id)
474 self.x = coords[0]-15
476 coords = self.canvas.coords(self.label_id)
477 self.x = coords[0]-15
479 if self.state == 'expanded' :
480 for child in self.children:
481 if child.displayed != 0:
482 child.update_coords()
484 def update_icone(self):
485 """ Met à jour les icônes de tous les noeuds : teste la validité de l'objet
486 Cette méthode est très lente, trop !!"""
487 if self.image_id != None :
488 image = self.geticonimage()
489 self.canvas.itemconfig(self.image_id,image=image)
490 if self.state == 'expanded':
491 for child in self.children:
492 if child.displayed != 0:
495 def update_texte(self):
496 """ Met à jour les noms des SD et valeurs des mots-clés """
497 text = self.item.GetText()
498 if text == None : text = ''
499 self.text.configure(text=text)
500 if self.state == 'expanded' :
501 for child in self.children:
502 if child.displayed != 0 : child.update_texte()
504 def update_valid(self) :
505 """Cette methode a pour but de mettre a jour la validite du noeud
506 et de propager la demande de mise à jour à son parent
508 if self.image_id != None :
509 image = self.geticonimage()
510 self.canvas.itemconfig(self.image_id,image=image)
511 self.parent.update_valid()
513 def update(self,event=None) :
515 Cette méthode est appelée pour demander l update d un noeud
516 d'un jeu de commandes
517 Cette demande est transmise au noeud racine (le JDC) qui update
518 tout l arbre représentant le jeu de commandes
519 Pendant cette mise à jour, on appelle la méthode isvalid qui
520 fera l update de tous les objets déclarés modifiés lors des
522 La métode isvalid est en général appelée par l intermédiaire de
523 update_icone -> geticonimage -> GetIconName
525 self.racine.update_coords()
526 self.racine.trace_ligne()
527 self.racine.update_icone()
528 self.racine.update_texte()
529 self.tree.resizescrollregion()
532 """ Efface du canvas les id associés à self : cad les siens et ceux
535 self.canvas.delete(id)
536 if not self.children : return
537 for child in self.children:
541 """ Déplace de l'incrément dy tous les id en dessous de self """
542 # il faut marquer tous les suivants de self
543 bbox1 = self.canvas.bbox(ALL)
544 self.canvas.dtag(ALL,'move')
545 self.canvas.delete('line')
547 self.canvas.addtag_overlapping('move',bbox1[0],self.y +10,bbox1[2],bbox1[3])
549 print "Erreur dans move :"
552 print self.item.getObject()
553 print self.item.getObject().definition.label
556 # on déplace tous les items de dy
557 self.canvas.move('move',0,dy)
559 def trace_ligne(self):
560 """ Dessine les lignes verticales entre frères et entre père et premier fils"""
561 if self.state=='collapsed' : return
562 #if self.displayed == 0 : return
563 if len(self.children)==0 : return
564 # on est bien dans le cas d'un noeud expansé avec enfants ...
565 # il faut rechercher l'ordonnée du dernier fils de self
566 y_end = self.children[-1].y
567 ligne = self.canvas.create_line(self.x+15,self.y,self.x+15,y_end,tags='line')
568 self.canvas.tag_lower(ligne)
569 for child in self.children :
573 print "Erreur dans trace_ligne :"
575 print child.item.getObject()
577 def last_child(self):
579 if self.state == 'expanded' and self.children:
580 lchild= self.children[-1].last_child()
583 #------------------------------------------------------------------
584 # Méthodes de création et destruction de noeuds
585 # Certaines de ces méthodes peuvent être appelées depuis l'externe
586 #------------------------------------------------------------------
587 def replace_node(self,node1,node2):
588 """ Remplace le noeud 1 par le noeud 2 dans la liste des enfants de self"""
589 index= self.children.index(node1)
590 self.delete_node_child(node1)
591 self.children.insert(index,node2)
593 def replace_enfant(self,item):
594 """ Retourne le noeud fils à éventuellement remplacer """
595 return self.get_node_fils(item.get_nom())
597 def full_creation(self,name,pos=None):
599 Interface avec ACCAS : création de l'objet de nom name et
600 du noeud associé. Retourne le noeud fils ainsi créé
602 item = self.item.additem(name,pos)
603 if item == None or item == 0:
604 # impossible d'ajouter le noeud de nom : name
607 enfant = self.replace_enfant(item)
609 # un fils de même nom existe déjà : on le remplace
610 child = item.itemNode(self,item,self.command,self.rmenu)
611 self.replace_node(enfant,child)
613 child = item.itemNode(self, item,self.command,self.rmenu)
615 self.children.append(child)
617 self.children.insert(pos,child)
620 def append_brother(self,name,pos='after',retour='non'):
622 Permet d'ajouter un frère à self
623 par défaut on l'ajoute après self
626 # on veut ajouter le frère de nom name directement avant ou après self
627 index = self.parent.children.index(self)
633 print str(pos)," n'est pas un index valide pour append_brother"
635 return self.parent.append_child(name,pos=index,retour=retour)
637 def append_node_child(self,fils,pos=None,verif='oui'):
639 Fait appel à la création complète de fils et à la vérification
640 des conditions en fonction du contexte
641 Attention : fils peut être un nom ou déjà un object (cas d'une copie)
643 if not self.children : self.build_children()
645 if type(fils) == types.InstanceType:
646 pos = self.item.get_index_child(fils.nom)
648 pos = self.item.get_index_child(fils)
649 child = self.full_creation(fils,pos)
651 # on n'a pas pu créer le noeud fils
653 self.state = 'expanded'
655 if child.item.isactif():
656 child.state = 'expanded'
657 if not child.children : child.build_children()
659 child.verif_condition()
660 self.verif_condition()
663 def append_child(self,name,pos=None,verif='oui',retour='non'):
665 Permet d'ajouter un fils à self
666 on peut l'ajouter en fin de liste (défaut) ou en début
672 index = len(self.children)
673 elif pos != None and type(pos) == types.IntType :
674 # on donne la position depuis l'extérieur
675 # (appel de append_child par append_brother par exemple)
677 elif type(pos) == types.InstanceType:
678 # pos est un item. Il faut inserer name apres pos
679 index = self.item.get_index(pos) +1
681 if type(name) == types.InstanceType:
682 index = self.item.get_index_child(name.nom)
684 index = self.item.get_index_child(name)
685 nbold = self.get_nb_children()
686 self.state='expanded'
687 child = self.append_node_child(name,pos=index)
689 # on n'a pas pu créer le fils
691 nbnew = self.get_nb_children()
692 self.redraw(nbnew-nbold)
694 if retour == 'oui': return child
696 def delete_node_child(self,child):
697 """ Supprime child des enfants de self et les id associés """
700 self.children.remove(child)
703 def delete_child(self,child):
705 Supprime child des enfants de self, tous les id associés
708 if self.item.suppitem(child.item):
709 self.delete_node_child(child)
715 """ Méthode externe pour la destruction du noeud ET de l'objet
716 Gère l'update du canvas"""
718 nbold = pere.get_nb_children()
720 if self.parent.children.index(self) > 0 :
721 index = self.parent.children.index(self) - 1
724 if self.parent.delete_child(self):
725 if self.item.get_position() == 'global':
726 self.etape.verif_all()
727 elif self.item.get_position() == 'global_jdc':
728 self.racine.verif_all()
730 self.parent.verif_condition()
732 print 'Erreur dans la destruction de ',self.item.get_nom(),' dans delete'
734 nbnew = pere.get_nb_children()
735 pere.redraw(nbnew-nbold)
737 # Le noeud n'est pas au 1er niveau
738 if pere.parent.parent != None:
741 enfants = self.parent.children
743 enfants[index].select()
746 enfants[index+1].select()
751 def doPaste(self,node_selected):
752 self.appli.message="Vous ne pouvez copier que des commandes ou des mots-clés facteurs !"
755 def doPaste_Commande(self,objet_a_copier):
757 Réalise la copie de l'objet passé en argument qui est nécessairement
760 child = self.append_brother(objet_a_copier,retour='oui')
763 #--------------------------------------------------------------
764 # Méthodes de vérification du contexte et de validité du noeud
765 #--------------------------------------------------------------
767 self.verif_all_children()
769 def verif_all_children(self):
770 if not self.children : self.build_children()
772 for child in self.children :
773 child.verif_all_children()
777 Lance la vérification des conditions des blocs de self et le cas
778 échéant redessine self
780 nbold = self.get_nb_children()
781 test = self.verif_condition()
782 nbnew = self.get_nb_children()
784 self.redraw(nbnew-nbold)
786 def verif_condition(self):
788 on lance la vérification des conditions de chaque bloc de self
789 on crée ou supprime les noeuds concernés
790 (self est d'un niveau inférieur ou égal à l'ETAPE)
793 l_bloc_arajouter,l_bloc_aenlever = self.verif_condition_bloc()
794 if len(l_bloc_arajouter) > 0:
796 for mc in l_bloc_arajouter:
797 self.append_node_child(mc,verif='non')
798 if len(l_bloc_aenlever) > 0:
800 for mc in l_bloc_aenlever:
801 mocle = self.get_node_fils(mc)
802 self.delete_child(mocle)
803 l_mc_presents = self.item.get_liste_mc_presents()
804 l_mc_arajouter= self.verif_condition_regles(l_mc_presents)
805 if len(l_mc_arajouter) > 0:
807 for mc in l_mc_arajouter:
808 self.append_node_child(mc,verif='non')
809 if len(l_mc_arajouter)+len(l_bloc_arajouter)+len(l_bloc_aenlever) != 0 :
810 self.verif_condition()
813 def verif_condition_bloc(self):
814 return self.item.verif_condition_bloc()
816 def verif_condition_regles(self,l_mc_presents):
817 return self.item.verif_condition_regles(l_mc_presents)