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
27 from Ihm import CONNECTOR
30 __version__="$Name: $"
31 __Id__="$Id: treewidget.py,v 1.25 2005/06/16 09:27:25 eficas Exp $"
34 Fonte_Standard = fontes.standard
37 def __init__(self,appli,jdc_item,scrolledcanvas,command = None,rmenu=None):
39 self.scrolledcanvas = scrolledcanvas
40 self.canvas = self.scrolledcanvas.component('canvas')
41 self.id_up=self.canvas.bind("<Key-Prior>", self.page_up)
42 self.id_down=self.canvas.bind("<Key-Next>", self.page_down)
43 self.id_uup=self.canvas.bind("<Key-Up>", self.unit_up)
44 self.id_udown=self.canvas.bind("<Key-Down>", self.unit_down)
45 self.id_um=self.canvas.bind("<Key-Left>", self.mot_up)
46 self.id_dm=self.canvas.bind("<Key-Right>", self.mot_down)
47 self.id_s=self.canvas.bind("<1>", self.canvas_select)
49 self.command = command
54 self.node_selected = None
57 def canvas_select(self,event):
58 self.canvas.focus_set()
60 def page_up(self,event):
61 event.widget.yview_scroll(-1, "page")
62 def page_down(self,event):
63 event.widget.yview_scroll(1, "page")
64 def unit_up(self,event):
65 event.widget.yview_scroll(-1, "unit")
66 def unit_down(self,event):
67 event.widget.yview_scroll(1, "unit")
69 def mot_down(self,event):
70 self.select_next(None)
71 self.canvas.focus_set()
73 def mot_up(self,event):
74 self.node_selected.select_mot_previous()
75 self.canvas.focus_set()
77 def build_children(self):
78 """ Construit la liste des enfants de self """
80 child = self.item.itemNode(self,self.item,self.command,self.rmenu)
81 self.children.append(child)
82 child.state='expanded'
85 """ Dessine l'arbre """
88 for child in self.children:
90 lasty = child.lasty + 15
91 self.children[0].select()
92 self.resizescrollregion()
94 def deselectall(self):
95 """ déselectionne tous les éléments de l'arbre """
96 if self.node_selected :
97 self.node_selected.deselect()
100 """ Update tous les éléments de l'arbre """
101 for child in self.children:
105 """ supprime tous les éléments de l'arbre """
106 #print "supprime",self
107 self.canvas.unbind("<Key-Prior>",self.id_up)
108 self.canvas.unbind("<Key-Next>",self.id_down)
109 self.canvas.unbind("<Key-Up>",self.id_uup)
110 self.canvas.unbind("<Key-Down>",self.id_udown)
111 self.canvas.unbind("<Key-Left>",self.id_um)
112 self.canvas.unbind("<Key-Right>",self.id_dm)
113 self.canvas.unbind("<1>",self.id_s)
116 self.node_selected = None
118 self.scrolledcanvas = None
122 for child in self.children:
126 def update_valid(self) :
127 """Cette methode a pour but de mettre a jour la validite du noeud
128 et de propager la demande de mise à jour à son parent
132 def resizescrollregion(self):
133 x0,y0,x1,y1=self.canvas.bbox(ALL)
134 # On ajoute une marge approximativement de la moitié du canvas
135 y1=y1+self.canvas.winfo_height()/2
136 self.canvas.configure(scrollregion = (x0,y0,x1,y1))
138 def select_next(self,event):
139 self.node_selected.select_next()
140 self.canvas.focus_set()
142 def select_previous(self,event):
143 self.node_selected.select_previous()
146 x1, y1, x2, y2=apply(self.canvas.bbox, items)
147 while x2 > self.canvas.canvasx(0)+self.canvas.winfo_width():
148 old=self.canvas.canvasx(0)
149 self.canvas.xview_scroll( 1, 'units')
150 # avoid endless loop if we can't scroll
151 if old == self.canvas.canvasx(0):
153 while y2 > self.canvas.canvasy(0)+self.canvas.winfo_height():
154 old=self.canvas.canvasy(0)
155 self.canvas.yview_scroll( 1, 'units')
156 if old == self.canvas.canvasy(0):
158 # done in this order to ensure upper-left of object is visible
159 while x1 < self.canvas.canvasx(0):
160 old=self.canvas.canvasx(0)
161 self.canvas.xview_scroll( -1, 'units')
162 if old == self.canvas.canvasx(0):
164 while y1 < self.canvas.canvasy(0):
165 old=self.canvas.canvasy(0)
166 self.canvas.yview_scroll( -1, 'units')
167 if old == self.canvas.canvasy(0):
171 # print "__del__",self
175 def __init__(self,parent,item,command=None,rmenu=None):
180 self.command = command
182 self.tree = self.parent.tree
183 self.appli = self.parent.appli
184 self.canvas = self.parent.canvas
188 self.state='collapsed'
191 self.x = self.y =None
195 if self.parent is self.tree:
198 self.racine = self.parent.racine
201 self.item.connect("add",self.onAdd,())
202 self.item.connect("supp",self.onSupp,())
203 self.item.connect("valid",self.onValid,())
206 # print "__del__",self
208 def force_select(self):
210 # le noeud est selectionné. On force la reconstruction du panel associé
211 if self.command:apply(self.command,(None,))
215 #print "onValid : l'item a changé de validité ",self.item,self.item.object,self.item.object.isvalid()
216 self.update_node_valid()
217 self.update_node_label()
218 self.update_node_texte()
219 if self.selected and self.command:
222 def onAdd(self,objet):
223 #print "onAdd : un objet a été ajouté aux fils de l'item ",self.item.object,objet
225 old_nodes=self.children
227 self.redraw_children(old_nodes)
230 def onSupp(self,objet):
231 #print "onSupp : un objet a été supprimé des fils de l'item ",self.item.object,objet
233 old_nodes=self.children
235 self.redraw_children(old_nodes)
238 def update_nodes(self):
239 #print "update_nodes",self
241 inodes=iter(self.children)
242 sublist=self.item._GetSubList()
249 if old_item in sublist:break
250 #print "item supprime",old_item
252 if item is old_item:break
253 #print "item ajoute",item
254 child = item.itemNode(self,item,self.command,self.rmenu)
255 newnodes.append(child)
257 if old_item is None and item is None:break
259 #print "item conserve",item
260 newnodes.append(node)
262 self.children=newnodes
266 #print "supprime",self
271 if not self.children : return
272 for child in self.children:
276 def redraw_children(self,old_nodes):
277 #print "redraw_children",old_nodes
283 inodes=iter(old_nodes)
284 iliste=iter(self.children)
285 # on parcourt la liste des anciens noeuds (node)
286 # et la liste des nouveaux noeuds (new_node) en parallele (iterateurs)
291 #print "ancien noeud",node
292 if node in self.children:break # ancien noeud toujours present
293 #print "noeud supprime",node,node.item.GetLabelText()[0]
294 dy=node.y-node.lasty -20
295 #print "deplacer noeuds",y,dy
296 node.move_nodes(y,dy)
298 #supp_nodes.append(node)
300 for new_node in iliste:
301 #print "nouveau noeud",new_node
302 if new_node in old_nodes: break # nouveau noeud deja present
303 #print "noeud ajoute",new_node,new_node.item.GetLabelText()[0]
304 y=self.draw_node(new_node,x,y)
306 if node is None and new_node is None : break
308 if node is new_node: # ancien noeud
309 #print "noeud conserve",node
310 node.update_node_label()
311 y=y+node.lasty-node.y +20
313 self.racine.update_coords()
314 self.canvas.delete('line')
315 self.racine.trace_ligne()
316 self.tree.resizescrollregion()
317 # Mettre à 1 pour verifier les cycles entre objets node
320 #from Misc import Cyclops
321 #z = Cyclops.CycleFinder()
323 #for o in supp_nodes:
331 def tag_move_nodes(self,y):
332 """ Marque pour deplacement tous les noeuds au dela de l'ordonnée y """
333 self.canvas.dtag(ALL,'move')
334 # on marque tous les ids au dela de y
335 x0, y0, x1, y1 = self.canvas.bbox(ALL)
336 self.canvas.addtag_overlapping('move',x0,y,x1,y1)
338 def move_nodes(self,y,dy):
339 """ Déplace de l'incrément dy les noeuds au dela de l'ordonnée y """
340 self.tag_move_nodes(y)
341 # on déplace tous les items de dy
342 self.canvas.move('move',0,dy)
344 def draw_node(self,new_node,x,y):
345 """ Dessine le noeud new_node en x,y en deplacant les noeuds existants
347 Retourne la position du premier des noeuds deplaces
349 self.tag_move_nodes(y)
350 #if new_node.item.isactif():
351 #new_node.state = 'expanded'
352 new_node.state = 'expanded'
354 dy=(new_node.get_nb_children()+1)*20
355 #print "deplacer noeuds",y,dy
356 self.canvas.move('move',0,dy)
357 return new_node.lasty+20
359 def build_children(self):
360 """ Construit la liste des enfants de self """
362 sublist = self.item._GetSubList()
363 if not sublist : return
364 for item in sublist :
365 child = item.itemNode(self,item,self.command,self.rmenu)
366 self.children.append(child)
368 #-----------------------------------------------
369 # Méthodes de sélection/déselection d'un noeud
370 #-----------------------------------------------
372 def select(self, event=None):
374 Rend le noeud courant (self) sélectionné et déselectionne
378 if not self.children : self.build_children()
379 self.tree.deselectall()
381 self.tree.node_selected = self
382 if self.command:apply(self.command,(self,))
386 def deselect(self, event=None):
387 """ Déselectionne self """
389 if self.displayed == 1 : self.dehighlight()
391 def make_visible(self):
392 """ Rend l'objet self visible cad déplace le scroll pour que self soit dans
395 lchild=self.last_child()
396 self.tree.see((self.image_id,lchild.image_id))
398 def select_next(self,ind=0):
399 """ on doit chercher à sélectionner dans l'ordre:
400 - son premier fils s'il est affiché
401 - son frère cadet s'il existe
402 - son oncle (benjamin de son père)
403 - ... appel récursif ...
405 if self.state=='expanded' and len(self.children) > ind:
406 self.children[ind].select()
408 index = self.parent.children.index(self) + 1
410 if isinstance(self.parent,TREE) :
412 self.children[ind].select()
414 self.children[0].select()
416 self.parent.select_next(index)
418 self.parent.select_next(index)
420 def select_mot_previous(self):
421 index = self.parent.children.index(self) - 1
424 self.parent.children[index].select()
430 def select_previous(self):
431 """ on doit d'abord sélectionner(dans l'ordre) :
435 index = self.parent.children.index(self) + 1
437 self.parent.children[index].select()
441 def popup(self,event=None):
443 Declenche le traitement associé au clic droit de la souris
446 if not self.rmenu:return
447 apply(self.rmenu,(self,event))
449 #-----------------------------------------------
450 # Méthodes de recherche d'informations
451 #-----------------------------------------------
452 def geticonimage(self,name=None):
454 Retourne l'image qui doit être associée à self
457 name = self.item.GetIconName()
458 if not name or name == 'aucune' :
460 return images.get_image(name)
462 def get_nb_children(self):
463 """ Retourne le nombre d'enfants affichés de self """
465 if self.state =='collapsed' : return nb
466 for child in self.children :
467 nb = nb + 1 + child.get_nb_children()
470 def get_liste_id(self):
471 """ Retourne la liste de tous les id (filiation comprise) de self """
473 for child in self.children:
474 liste.extend(child.get_liste_id())
477 def get_node_fils(self,name) :
478 """ Retourne le fils de self de nom name s'il existe"""
479 for child in self.children:
480 if child.item.get_nom() == name: return child
483 #-----------------------------------------------
484 # Méthodes d'affichage d'un noeud
485 #-----------------------------------------------
487 """ Permet de tracer le noeud self """
488 # le début du noeud est en x,y
494 # choix de l'icone à afficher : + ou -
495 if self.item.IsExpandable():
496 if self.state == 'expanded':
497 iconname = "minusnode"
498 callback = self.collapse
500 iconname = "plusnode"
501 callback = self.expand
502 image = self.geticonimage(name=iconname)
503 self.icone_id = self.canvas.create_image(self.x, self.y, image=image)
504 self.callback_id=self.canvas.tag_bind(self.icone_id, "<1>", callback)
505 self.id.append(self.icone_id)
506 # création de la ligne horizontale
507 self.ligne_id = self.canvas.create_line(self.x,self.y,self.x+10,self.y)
508 self.id.append(self.ligne_id)
509 self.canvas.tag_lower(self.ligne_id)
510 # affichage de l'icone (carre ,rond, ovale ...) de couleur
511 image = self.geticonimage()
513 self.image_id = self.canvas.create_image(self.x+15,self.y,image = image)
514 self.select_id2=self.canvas.tag_bind(self.image_id,"<1>",self.select)
515 self.popup_id2=self.canvas.tag_bind(self.image_id,"<3>",self.popup)
516 self.id.append(self.image_id)
519 # affichage du texte : nom de l'objet (ETAPE ou MOT-CLE) et sa valeur
521 if self.state == 'expanded' :
522 if not self.children : self.build_children()
523 if len(self.children) > 0:
525 self.lasty = self.children[-1].lasty
527 def drawchildren(self):
528 """ Dessine les enfants de self """
531 for child in self.children:
533 nb = child.get_nb_children()
538 """ Affiche les deux zones de texte après l'icône de couleur de l'objet """
539 if self.image_id != None :
544 # nom,fonte et couleur de l'objet du noeud à afficher
545 labeltext,fonte,couleur = self.item.GetLabelText()
546 if labeltext == '' : labeltext = ' '
547 if fonte == None : fonte = Fonte_Standard
548 if couleur == None : couleur = 'black'
549 # création du widget label
550 self.label = Label(self.canvas,
555 self.label_id = self.canvas.create_window(textx,texty,window=self.label,anchor='w')
556 self.id.append(self.label_id)
557 # bindings sur le widget label
558 self.select_id=self.label.bind("<1>", self.select)
559 self.popup_id=self.label.bind("<3>", self.popup)
560 self.enter_id=self.label.bind("<Enter>",self.enter)
561 self.leave_id=self.label.bind("<Leave>",self.leave)
562 # valeur de cet objet à afficher
563 x0, y0, x1, y1 = self.canvas.bbox(self.label_id)
564 textx = max(x1, 200) + 10
565 text = self.item.GetText() or " "
566 self.text = Label(self.canvas, text=text,
567 bd=0, padx=2, pady=2,background='gray95',
573 self.text_id = self.canvas.create_window(textx, texty,anchor="w", window=self.text)
574 self.id.append(self.text_id)
576 def highlight(self,event=None):
577 """ Met en surbrillance self"""
578 if hasattr(self,'label'):
579 self.label.configure(fg='white',bg='#00008b')
581 def dehighlight(self,event=None):
582 """ Rétablit l'affichage normal de self"""
583 if hasattr(self,'label'):
584 self.label.configure(fg='black',bg='gray95')
586 def enter(self,event=None):
587 """ Met en surbrillance self et affiche le fr de l'objet """
589 fr = self.item.get_fr()
590 self.appli.affiche_infos(fr)
592 def leave(self,event=None):
593 """ Rétablit l'affichage normal de self et efface le fr de l'objet """
594 if not self.selected :
596 self.appli.affiche_infos('')
598 def collapse_children(self):
599 """ Collapse récursivement tous les descendants de self """
600 if not self.children : return
601 for child in self.children:
602 child.state='collapsed'
603 child.collapse_children()
605 def collapse(self,event = None):
606 """ Collapse self et descendants et retrace self """
607 nb = self.get_nb_children()
608 self.state = 'collapsed'
609 self.collapse_children()
613 def expand_node(self,event = None):
614 """ Expanse self et le retrace """
615 if self.state == 'expanded':return
616 #if not self.item.isactif() : return
617 if not self.children : self.build_children()
618 self.state = 'expanded'
619 nb = self.get_nb_children()
622 def expand(self,event = None):
623 """ Expanse self et le retrace """
628 """ Redessine self : nb est le décalage à introduire
629 en dessous de self pour le redessiner """
630 # nb = nombre d'items de décalage
632 # on efface self et on le redessine
634 self.draw(self.x,self.y)
635 # Il n'est pas nécessaire d'appeler update
636 # il suffit d'updater les coordonnees et de retracer les lignes
637 self.racine.update_coords()
638 self.racine.trace_ligne()
640 self.tree.resizescrollregion()
642 def update_coords(self):
643 """ Permet d'updater les coordonnes de self et de tous ses enfants"""
644 if self.displayed == 0 : return
645 if self.image_id != None :
646 coords = self.canvas.coords(self.image_id)
647 self.x = coords[0]-15
649 coords = self.canvas.coords(self.label_id)
650 self.x = coords[0]-15
653 if self.state == 'expanded' :
654 for child in self.children:
655 if child.displayed != 0:
656 child.update_coords()
657 self.lasty = child.lasty
659 def update_icone(self):
660 """ Met à jour les icônes de tous les noeuds : teste la validité de l'objet
661 Cette méthode est très lente, trop !!"""
662 if self.image_id != None :
663 image = self.geticonimage()
664 self.canvas.itemconfig(self.image_id,image=image)
665 if self.state == 'expanded':
666 for child in self.children:
667 if child.displayed != 0:
670 def update_label_texte(self):
671 """ Met a jour le label du noeud et celui de tous ses fils ouverts """
672 self.update_node_label()
673 if self.state == 'expanded' :
674 for child in self.children:
675 if child.displayed != 0 : child.update_label_texte()
677 def update_texte(self):
678 """ Met à jour les noms des SD et valeurs des mots-clés """
679 self.update_node_texte()
680 if self.state == 'expanded' :
681 for child in self.children:
682 if child.displayed != 0 : child.update_texte()
684 def update_node_label(self):
685 """ Met a jour le label du noeud """
686 if self.displayed == 0 : return
687 # nom,fonte et couleur de l'objet du noeud à afficher
688 labeltext,fonte,couleur = self.item.GetLabelText()
689 if labeltext == '' : labeltext = ' '
690 if fonte == None : fonte = Fonte_Standard
691 if couleur == None : couleur = 'black'
692 self.label.configure(text=labeltext,font=fonte)
694 def update_node_texte(self):
695 """ Met à jour les noms des SD et valeurs des mots-clés """
696 if self.displayed == 0 : return
697 text = self.item.GetText()
698 if text == None : text = ''
699 self.text.configure(text=text)
701 def update_node_valid(self) :
702 """Cette methode remet a jour la validite du noeud (icone)
705 if self.displayed == 0 : return
706 if self.image_id != None :
707 image = self.geticonimage()
708 self.canvas.itemconfig(self.image_id,image=image)
710 def update_valid(self) :
711 """Cette methode a pour but de mettre a jour la validite du noeud
712 et de propager la demande de mise à jour à son parent
714 self.update_node_valid()
715 self.parent.update_valid()
717 def update(self,event=None) :
719 Cette méthode est appelée pour demander l update d un noeud
720 d'un jeu de commandes
721 Cette demande est transmise au noeud racine (le JDC) qui update
722 tout l arbre représentant le jeu de commandes
723 Pendant cette mise à jour, on appelle la méthode isvalid qui
724 fera l update de tous les objets déclarés modifiés lors des
726 La métode isvalid est en général appelée par l intermédiaire de
727 update_icone -> geticonimage -> GetIconName
730 #traceback.print_stack()
731 self.racine.update_coords()
732 self.racine.trace_ligne()
733 self.racine.update_icone()
734 self.racine.update_texte()
735 self.racine.update_label_texte()
736 self.tree.resizescrollregion()
738 def efface_node(self):
739 if self.displayed != 0:
740 self.label.unbind("<1>", self.select_id)
741 self.label.unbind("<3>", self.popup_id)
742 self.label.unbind("<Enter>",self.enter_id)
743 self.label.unbind("<Leave>",self.leave_id)
744 self.canvas.tag_unbind(self.image_id,"<1>",self.select_id2)
745 self.canvas.tag_unbind(self.image_id,"<3>",self.popup_id2)
746 if self.item.IsExpandable():
747 self.canvas.tag_unbind(self.icone_id, "<1>", self.callback_id)
752 self.canvas.delete(id)
763 """ Efface du canvas les id associés à self : cad les siens et ceux
766 if not self.children : return
767 for child in self.children:
771 """ Déplace de l'incrément dy tous les id en dessous de self """
772 # il faut marquer tous les suivants de self
773 bbox1 = self.canvas.bbox(ALL)
774 self.canvas.dtag(ALL,'move')
775 self.canvas.delete('line')
777 self.canvas.addtag_overlapping('move',bbox1[0],self.y +10,bbox1[2],bbox1[3])
779 print "Erreur dans move :"
782 print self.item.getObject()
783 print self.item.getObject().definition.label
786 # on déplace tous les items de dy
787 self.canvas.move('move',0,dy)
789 def trace_ligne(self):
790 """ Dessine les lignes verticales entre frères et entre père et premier fils"""
791 if self.state=='collapsed' : return
792 if len(self.children)==0 : return
793 # on est bien dans le cas d'un noeud expansé avec enfants ...
794 # il faut rechercher l'ordonnée du dernier fils de self
795 y_end = self.children[-1].y
796 ligne = self.canvas.create_line(self.x+15,self.y,self.x+15,y_end,tags='line')
797 self.canvas.tag_lower(ligne)
798 for child in self.children :
802 print "Erreur dans trace_ligne :"
804 print child.item.getObject()
806 def last_child(self):
808 if self.state == 'expanded' and self.children:
809 lchild= self.children[-1].last_child()
812 #------------------------------------------------------------------
813 # Méthodes de création et destruction de noeuds
814 # Certaines de ces méthodes peuvent être appelées depuis l'externe
815 #------------------------------------------------------------------
816 def append_brother(self,name,pos='after',retour='non'):
818 Permet d'ajouter un objet frère à l'objet associé au noeud self
819 par défaut on l'ajoute immédiatement après
822 # on veut ajouter le frère de nom name directement avant ou après self
823 index = self.parent.children.index(self)
829 print str(pos)," n'est pas un index valide pour append_brother"
831 return self.parent.append_child(name,pos=index)
833 def append_child(self,name,pos=None,verif='oui',retour='non'):
835 Methode pour ajouter un objet fils à l'objet associé au noeud self.
836 On peut l'ajouter en début de liste (pos='first'), en fin (pos='last')
837 ou en position intermédiaire.
838 Si pos vaut None, on le place à la position du catalogue.
840 #print "append_child",self,self.children
844 index = len(self.children)
845 elif type(pos) == types.IntType :
848 elif type(pos) == types.InstanceType:
849 # pos est un item. Il faut inserer name apres pos
850 index = self.item.get_index(pos) +1
851 elif type(name) == types.InstanceType:
852 index = self.item.get_index_child(name.nom)
854 index = self.item.get_index_child(name)
855 obj=self.item.additem(name,index)
859 #print "append_child",index,self.children
860 child=self.children[index]
866 Méthode externe pour la destruction de l'objet associé au noeud
867 La mise à jour des noeuds est faite par onSupp sur notification
869 index = self.parent.children.index(self) - 1
870 if index < 0 : index =0
873 ret=parent.item.suppitem(self.item)
876 brothers=parent.children
878 toselect=brothers[index]