]> SALOME platform Git repositories - tools/eficas.git/blob - InterfaceQT4/editor.py
Salome HOME
0e885a6954544eaec69c82e2a7c37c6a0280784f
[tools/eficas.git] / InterfaceQT4 / editor.py
1 # -*- coding: utf-8 -*-
2 # ======================================================================
3 # COPYRIGHT (C) 1991 - 2002  EDF R&D                  WWW.CODE-ASTER.ORG
4 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
5 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
6 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
7 # (AT YOUR OPTION) ANY LATER VERSION.
8 #
9 # THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
10 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
11 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
12 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.
13 #
14 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
15 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
16 #    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
17 #
18 #
19 # ======================================================================
20
21 print "INTERFACEQT4"
22 import types,sys,os
23 import traceback
24 from PyQt4 import *
25 from PyQt4.QtGui  import *
26 from PyQt4.QtCore import *
27
28 # Modules Eficas
29
30 import convert,generator
31 from Editeur     import session
32 from Editeur     import comploader
33 from Editeur     import Objecttreeitem
34 import browser
35 import readercata
36 import qtCommun
37
38
39 VERSION_EFICAS  = "EFICAS v1.16"
40
41
42 class JDCEditor(QSplitter):
43 # -------------------------- #
44     """
45        Editeur de jdc
46     """        
47
48     def __init__ (self,appli,fichier = None, jdc = None, QWParent=None, units = None, include=0 , vm=None):          
49     #----------------------------------------------------------------------------------------------------------#
50
51         #print "fichier", fichier,"jdc",jdc,"units",units,"include",include
52         QSplitter.__init__(self, QWParent)
53         self.appliEficas = appli
54         self.appli       = appli  #---- attendu par IHM
55         self.vm          = vm
56         self.fichier     = fichier
57         self.jdc         = jdc
58         self.QWParent    = QWParent
59
60         self.test=0
61         VERSION_CODE    = session.d_env.cata
62         if appli != None :
63            self.salome =  self.appliEficas.salome
64            self.format =  self.appliEficas.format_fichier
65         else :
66            self.salome=0
67            print "dans JDC pas d appli ????????"
68
69         self.code = self.appliEficas.CONFIGURATION.code
70         self.version_code = VERSION_CODE
71         self.titre=VERSION_EFICAS + ' pour '+ self.code
72
73         self.dict_reels={}
74         self.liste_simp_reel=[]        
75         self.ihm="QT"
76         
77         import prefs
78         nameConf='configuration_'+prefs.code
79         configuration=__import__(nameConf)
80         self.CONFIGURATION = self.appliEficas.CONFIGURATION
81         self.CONFIGStyle =   self.appliEficas.CONFIGStyle
82
83         self.sb = None
84         if hasattr(self.appliEficas,"statusBar"):
85            self.sb = self.appliEficas.statusBar()
86       
87         self.fileInfo       = None
88         self.lastModified   = 0
89         
90         self.modified   = False
91         self.isReadOnly = False
92         self.tree = None
93         self.node_selected = None
94         
95         if not hasattr( readercata, 'reader' ) :
96             readercata.reader = readercata.READERCATA( self, self.appliEficas )
97         self.readercata = readercata.reader
98         self.Commandes_Ordre_Catalogue =self.readercata.Commandes_Ordre_Catalogue
99         
100         #------- construction du jdc --------------
101
102         jdc_item = None
103         self.mode_nouv_commande=self.readercata.mode_nouv_commande
104                         
105         nouveau=0
106         if self.fichier is not None:        #  fichier jdc fourni
107             self.fileInfo = QFileInfo(self.fichier)
108             self.fileInfo.setCaching(0)
109             if jdc==None :
110                self.jdc = self.readFile(self.fichier)
111             else :
112                self.jdc=jdc
113             if units is not None:
114                self.jdc.recorded_units=units
115                self.jdc.old_recorded_units=units
116         else: 
117             if not self.jdc:                   #  nouveau jdc
118                 if not include :
119                    self.jdc = self._newJDC(units=units)
120                 else :
121                    self.jdc = self._newJDCInclude(units=units)
122                 nouveau=1
123         
124         if self.jdc:            
125             self.jdc.appli = self
126             txt_exception  = None
127             if not jdc:
128                 self.jdc.analyse()            
129                 txt_exception = self.jdc.cr.get_mess_exception()            
130             if txt_exception:
131                 self.jdc = None
132                 qApp.restoreOverrideCursor()
133                 self.affiche_infos("Erreur fatale au chargement de %s" %fichier)                
134                 QMessageBox.critical( self, "Erreur fatale au chargement d'un fichier", txt_exception)                
135             else:
136                 comploader.charger_composants("QT")
137                 jdc_item=Objecttreeitem.make_objecttreeitem( self, "nom", self.jdc )
138
139                 if (not self.jdc.isvalid()) and (not nouveau) :
140                     self.viewJdcRapport()
141         if jdc_item:                        
142             self.tree = browser.JDCTree( jdc_item,  self )
143         
144     #--------------------------------#
145     def _newJDC( self ,units = None):        
146     #--------------------------------#
147         """
148         Initialise un nouveau JDC vierge
149         """
150         CONTEXT.unset_current_step()        
151         jdc=self.readercata.cata[0].JdC( procedure="",
152                                          appli=self,
153                                          cata=self.readercata.cata,
154                                          cata_ord_dico=self.readercata.cata_ordonne_dico,
155                                          rep_mat=self.CONFIGURATION.rep_mat
156                                         )                         
157         if units is not None:
158            jdc.recorded_units=units
159            jdc.old_recorded_units=units
160         jdc.analyse()        
161         return jdc
162         
163     #--------------------------------#
164     def _newJDCInclude( self ,units = None):        
165     #--------------------------------#
166         """
167         Initialise un nouveau JDC vierge
168         """
169         import Extensions.jdc_include
170         JdC_aux=Extensions.jdc_include.JdC_include
171         CONTEXT.unset_current_step()        
172
173         jaux=self.readercata.cata[0].JdC( procedure="",
174                                appli=self,
175                                cata=self.readercata.cata,
176                                cata_ord_dico=self.readercata.cata_ordonne_dico,
177                                rep_mat=self.CONFIGURATION.rep_mat,
178                               )
179         jaux.analyse()
180
181         J=JdC_aux( procedure="",
182                    appli=self,
183                    cata=self.readercata.cata,
184                    cata_ord_dico=self.readercata.cata_ordonne_dico,
185                    jdc_pere=jaux,
186                    rep_mat=self.CONFIGURATION.rep_mat,
187                    )
188         J.analyse()
189         if units is not None:
190            J.recorded_units=units
191            J.old_recorded_units=units
192         return J
193
194     #-----------------------#
195     def readFile(self, fn):
196     #--------------------------------#
197         """
198         Public slot to read the text from a file.
199         @param fn filename to read from (string or QString)
200         """        
201         fn = unicode(fn)        
202                         
203         # ------------------------------------------------------------------------------------
204         #                         charge le JDC
205         # ------------------------------------------------------------------------------------      
206         
207         jdcName=os.path.basename(fn)
208         # Il faut convertir le contenu du fichier en fonction du format
209         if convert.plugins.has_key( self.appliEficas.format_fichier ):
210              # Le convertisseur existe on l'utilise
211              appli = self 
212              p=convert.plugins[self.appliEficas.format_fichier]()
213              p.readfile(fn)         
214              text=p.convert('exec',appli)
215              if not p.cr.estvide():                 
216                 self.affiche_infos("Erreur à la conversion")
217         else :
218             self.affiche_infos("Type de fichier non reconnu")
219             QMessageBox.critical( self, "Type de fichier non reconnu","EFICAS ne sait pas ouvrir ce type de fichier")            
220             return None
221         
222         CONTEXT.unset_current_step()
223         jdc=self.readercata.cata[0].JdC(procedure=text,
224                                     appli=self,
225                                     cata=self.readercata.cata,
226                                     cata_ord_dico=self.readercata.cata_ordonne_dico,
227                                     nom=jdcName,
228                                     rep_mat=self.CONFIGURATION.rep_mat
229                                    )
230         # ----------------------------------------------------
231         #      charge le JDC fin
232         # ----------------------------------------------------
233         self.modified = False
234                         
235 #        qApp.restoreOverrideCursor()        
236         if self.fileInfo!= None : 
237            self.lastModified = self.fileInfo.lastModified()
238         else :
239            self.lastModified = 1
240         return jdc
241         
242
243     #-----------------------#
244     def get_source(self,file):
245     #-----------------------#
246         format=self.appliEficas.format_fichier
247
248         # Il faut convertir le contenu du fichier en fonction du format
249         if convert.plugins.has_key(format):
250             # Le convertisseur existe on l'utilise
251             p=convert.plugins[format]()
252             p.readfile(file)
253             text=p.convert('execnoparseur')
254             if not p.cr.estvide():
255                 self.affiche_infos("Erreur a la conversion")
256             return text
257         else:
258             # Il n'existe pas c'est une erreur
259             self.affiche_infos("Type de fichier non reconnu")
260             QMessageBox.critical( self, "Type de fichier non reconnu","EFICAS ne sait pas ouvrir ce type de fichier")            
261             return None
262
263     #----------------------------------------------#
264     def _viewText(self, txt, caption = "FILE_VIEWER"):    
265     #----------------------------------------------#
266         w = qtCommun.ViewText( self.QWParent )
267         w.setWindowTitle( caption )
268         w.setText(txt)
269         w.show()
270         
271     #-----------------------#
272     def viewJdcSource(self):        
273     #-----------------------#
274         format = self.appliEficas.format_fichier
275         f=open(self.fichier,'r')
276         texteSource=f.read()
277         f.close()
278         self._viewText(texteSource, "JDC_SOURCE")
279                 
280     #-----------------------#
281     def viewJdcPy(self):        
282     #-----------------------#
283         format = self.appliEficas.format_fichier
284         strSource = str( self.get_text_JDC(format) )       
285         self._viewText(strSource, "JDC_RESULTAT")
286                  
287     #-----------------------#
288     def viewJdcRapport(self):
289     #-----------------------#
290         strRapport = str( self.jdc.report() )
291         self._viewText(strRapport, "JDC_RAPPORT")        
292         
293     #----------------#
294     def closeIt(self):
295     #----------------#
296         """
297         Public method called by the viewmanager to finally get rid of us.
298         """
299         if self.jdc:
300             self.jdc.supprime()
301         self.close()
302     
303     #------------------------------#
304     def affiche_infos(self,message):
305     #------------------------------#
306         #PN --> devenu inutile avec QT4
307         #if self.salome :
308         #   if not hasattr(self.appliEficas,'MessageLabel') :
309         #      self.appliEficas.leLayout=QDockWidget(self.appliEficas)
310         #      self.appliEficas.MessageLabel = QLabel("MessageLabel",self.appliEficas.leLayout)
311         #      self.appliEficas.MessageLabel.setAlignment(Qt.AlignBottom)
312         #      self.appliEficas.leLayout.setAllowedAreas(Qt.BottomDockWidgetArea)
313         #      self.appliEficas.leLayout.setWidget(self.appliEficas.MessageLabel)
314         #      #self.appliEficas.moveDockWindow(self.appliEficas.leLayout,Qt.DockBottom)
315         #   self.appliEficas.MessageLabel.setText(message)
316         #   self.appliEficas.MessageLabel.show()
317         #   self.appliEficas.leLayout.show()
318         if self.sb:
319             self.sb.showMessage(message)#,2000)
320
321     #------------------------------#
322     def affiche_alerte(self,titre,message):
323     #------------------------------#
324     # appele par I_MACRO_ETAPE
325         QMessageBox.information( self, titre, message)
326
327     #-------------------#
328     def init_modif(self):
329     #-------------------#
330       """
331           Met l'attribut modified a 'o' : utilise par Eficas pour savoir
332           si un JDC doit etre sauvegarde avant destruction ou non
333       """
334       self.modified = True
335
336     #---------------------------------------#
337     def chercheNoeudSelectionne(self,copie=1):
338     #---------------------------------------#
339       """
340         appele par Cut et Copy pour positionner self.node_selected
341       """
342       self.node_selected=None
343       if len(self.tree.selectedItems()) == 0 : return
344       if len(self.tree.selectedItems()) != 1 :
345           QMessageBox.information( self, 
346                       "Copie impossible",
347                       "Cette version d'EFICAS permet uniquement la copie d un seul objet")
348           return
349       self.node_selected=self.tree.selectedItems()[0]
350       if copie == 0 : return
351       if not self.node_selected.item.iscopiable():
352           QMessageBox.information( self, 
353                       "Copie impossible",
354                       "Cette version d'EFICAS ne permet pas la copie de cet Objet")
355           self.node_selected=None
356           return
357     
358     
359     #---------------------#
360     def handleEditCut(self):
361     #---------------------#
362       """
363       Stocke dans Eficas.noeud_a_editer le noeud à couper
364       """
365       self.chercheNoeudSelectionne()
366       self.QWParent.edit="couper"
367       self.QWParent.noeud_a_editer = self.node_selected      
368     
369     #-----------------------#
370     def handleEditCopy(self):
371     #-----------------------#
372       """
373       Stocke dans Eficas.noeud_a_editer le noeud a copier
374       """
375       self.chercheNoeudSelectionne()
376       self.QWParent.edit="copier"
377       self.QWParent.noeud_a_editer = self.node_selected
378     
379     #------------------------#
380     def handleEditPaste(self):
381     #------------------------#
382       """
383       Lance la copie de l'objet place dans self.QWParent.noeud_a_editer
384       Ne permet que la copie d'objets de type Commande ou MCF
385       """
386       self.chercheNoeudSelectionne()
387       index_noeud_a_couper=self.QWParent.noeud_a_editer.treeParent.children.index(self.QWParent.noeud_a_editer)
388       if self.QWParent.noeud_a_editer == None :
389           QMessageBox.information( self, 
390                       "Copie impossible",
391                       "Aucun Objet n a ete copie ou colle ")
392           return
393       try:
394          child=self.QWParent.noeud_a_editer.doPaste(self.node_selected)
395       except:
396          traceback.print_exc()
397          QMessageBox.information( self, 
398                      "Copie impossible",         
399                      "L'action de coller apres un tel objet n'est pas permise")
400          return
401     
402      
403       if child == 0:
404           if self.message != '':             
405              QMessageBox.critical( self, "Copie refusee", self.message)
406              self.message = ''
407           self.affiche_infos("Copie refusée")
408           return
409     
410       # il faut declarer le JDCDisplay_courant modifie
411       self.init_modif()
412       # suppression eventuelle du noeud selectionne
413       # si possible on renomme l objet comme le noeud couper
414
415       if self.QWParent.edit == "couper":
416          print self.QWParent.noeud_a_editer.child
417          index_ajoute=child.treeParent.children.index(child)
418          if index_ajoute <= index_noeud_a_couper :
419             index_noeud_a_couper=index_noeud_a_couper + 1
420          item=self.QWParent.noeud_a_editer.item
421          noeud_a_supprimer=self.QWParent.noeud_a_editer.treeParent.children[index_noeud_a_couper]
422          noeud_a_supprimer.delete()
423          child.item.update(item)
424          #test,mess = child.item.nomme_sd(nom)
425          child.select()
426
427       # on rend la copie a nouveau possible en liberant le flag edit
428       self.QWParent.edit="copier"
429           
430     #---------------------#
431     def getFileName(self):
432     #---------------------#
433       return self.fichier
434
435     #---------------------------#
436     def get_file_variable(self) :
437     #---------------------------#
438      titre = "Choix d'un fichier XML"
439      texte = "Le fichier contient une commande INCLUDE\n"
440      texte = texte+'Donnez le nom du fichier XML qui contient la description des variables'
441      QMessageBox.information( self, titre,texte)
442                                         
443      fichier = QFileDialog.getOpenFileName(self.appliEficas,
444                    self.appliEficas.trUtf8('Ouvrir Fichier'),
445                    self.appliEficas.CONFIGURATION.savedir,
446                    self.appliEficas.trUtf8('Wrapper Files (*.xml);;''All Files (*)'))
447      print fichier
448      return  fichier
449       
450     #----------------------------------#
451     def writeFile(self, fn, txt = None):
452     #----------------------------------#
453         """
454         Public slot to write the text to a file.
455         
456         @param fn filename to write to (string or QString)
457         @return flag indicating success
458         """
459
460         fn = unicode(fn)
461
462         if txt == None :
463             txt = self.get_text_JDC(self.appliEficas.format_fichier)
464             eol = '\n'        
465             if len(txt) >= len(eol):
466                if txt[-len(eol):] != eol:
467                   txt += eol
468             else:
469                 txt += eol        
470         try:
471             f = open(fn, 'wb')
472             f.write(txt)
473             f.close()
474             return 1
475         except IOError, why:
476             QMessageBox.critical(self, self.trUtf8('Save File'),
477                 self.trUtf8('The file <b>%1</b> could not be saved.<br>Reason: %2')
478                     .arg(unicode(fn)).arg(str(why)))
479             return 0
480
481     #-----------------------------#
482     def get_text_JDC(self,format):
483     #-----------------------------#
484       if generator.plugins.has_key(format):
485          # Le generateur existe on l'utilise
486          self.generator=generator.plugins[format]()
487          jdc_formate=self.generator.gener(self.jdc,format='beautifie')
488          if not self.generator.cr.estvide():            
489             self.affiche_infos("Erreur à la generation")
490             QMessageBox.critical( self, "Erreur a la generation","EFICAS ne sait pas convertir ce JDC")
491             return ""
492          else:
493             return jdc_formate
494       else:         
495          # Il n'existe pas c'est une erreur
496          self.affiche_infos("Format %s non reconnu" % format)
497          QMessageBox.critical( self, "Format "+format+" non reconnu","EFICAS ne sait pas convertir le JDC selon le format "+format)
498          return ""
499       
500       
501     #-----------------------------------------#
502     def cherche_Groupes(self):
503         liste=self.get_text_JDC("GroupMA")
504         return liste
505     #-----------------------------------------#
506
507     #-----------------------------------------#
508     def saveFile(self, path = None, saveas= 0):
509     #-----------------------------------------#
510         """
511         Public slot to save the text to a file.
512         
513         @param path directory to save the file in (string or QString)
514         @return tuple of two values (boolean, string) giving a success indicator and
515             the name of the saved file
516         """        
517                 
518         if not self.modified and not saveas:
519             return (0, None)      # do nothing if text wasn't changed
520             
521         newName = None
522         if self.fichier is None or saveas:
523           if path is None: 
524              path=self.CONFIGURATION.savedir
525           selectedFilter = QString('')
526           fn = QFileDialog.getSaveFileName( self,
527                self.trUtf8("sauvegarde"), path,
528                self.trUtf8("JDC (*.comm);;" "All Files (*)"),None,
529                QFileDialog.DontConfirmOverwrite)
530           if fn.isNull(): return (0, None)
531
532           ext = QFileInfo(fn).suffix()
533           if ext.isEmpty(): fn.append(".comm")
534
535           if QFileInfo(fn).exists():
536                 abort = QMessageBox.warning(self,
537                        self.trUtf8("Sauvegarde du Fichier"),
538                        self.trUtf8("Le fichier <b>%1</b> existe deja.").arg(fn),
539                        self.trUtf8("&Ecraser"),
540                        self.trUtf8("&Abandonner"))
541                 if abort == 1 :  return (0, None)
542
543           fn = unicode(QDir.convertSeparators(fn))
544           newName = fn
545
546         else:
547             fn = self.fichier
548         
549         if self.writeFile(fn):
550             self.fichier = fn
551             self.modified  = False                        
552             if self.fileInfo is None or saveas:
553                 self.fileInfo = QFileInfo(self.fichier)
554                 self.fileInfo.setCaching(0)
555             self.lastModified = self.fileInfo.lastModified()
556             if newName is not None:
557                 self.appliEficas.addToRecentList(newName)
558                 self.tree.racine.item.getObject().nom=os.path.basename(newName)
559                 self.tree.racine.update_node_label()
560                
561             try : 
562             #if 1 :
563                fileXML = fn[:fn.rfind(".")] + '.xml'
564                self.generator.writeOpenturnsXML( fileXML )
565             except :
566             #else :
567                pass
568                
569             #PNPNPNPN A ecrire
570             try : 
571                fileSTD = fn[:fn.rfind(".")] + '.py'
572                self.generator.writeOpenturnsSTD( fileSTD )
573             except :
574                pass
575
576             try : 
577             #if 1 :
578                self.generator.writeCuve2DG()
579             #else :
580             except :
581                pass
582
583                 
584             try : 
585             #if 1 :
586                self.tubePy=self.generator.getTubePy()
587                fileTube = '/tmp/tube.py'
588                if self.tubePy != '' :
589                   f=open(fileTube,'w')
590                   f.write(self.tubePy)
591                   f.close()
592             except :
593             #else :
594                pass
595
596             if self.salome : 
597                self.appliEficas.addJdcInSalome( self.fichier)
598 #               if self.code == 'ASTER':
599 #                  self.QWParent.appli.createOrUpdateMesh(self)
600 #               #PN ; TODO
601 #
602             return (1, self.fichier)
603         else:
604             return (0, None)
605 #
606     #---------------------------------#
607     def saveFileAs(self, path = None):
608     #---------------------------------#
609         """
610         Public slot to save a file with a new name.
611         
612         @param path directory to save the file in (string or QString)
613         @return tuple of two values (boolean, string) giving a success indicator and
614             the name of the saved file
615         """
616         return self.saveFile(path,1)
617
618    
619         
620     #---------------------------------------------#
621     def get_file(self,unite=None,fic_origine = ''):
622     #---------------------------------------------#
623     # appele par I_JDC
624         ulfile  = None
625         jdcText = ""
626       
627         titre  = ""
628         
629         if unite :
630             titre = "Choix unite %d " %unite
631             texte = "Le fichier %s contient une commande INCLUDE \n" % fic_origine
632             texte = texte+'Donnez le nom du fichier correspondant\n à l unité logique %d' % unite
633             labeltexte = 'Fichier pour unite %d :' % unite
634         else:
635             titre = "Choix d'un fichier de poursuite"
636             texte = "Le fichier %s contient une commande %s\n" %(fic_origine,'POURSUITE')
637             texte = texte+'Donnez le nom du fichier dont vous \n voulez faire une poursuite'
638                                         
639         QMessageBox.information( self, titre,texte)
640         path=self.CONFIGURATION.savedir
641         fn = QFileDialog.getOpenFileName( self, titre,path)
642         
643         if fn.isNull(): 
644         # ce retour est impose par le get_file d'I_JDC
645            return None," "
646             
647         ulfile = os.path.abspath(unicode(fn))
648         # On utilise le convertisseur défini par format_fichier
649         source=self.get_source(ulfile)
650         if source:
651             # On a réussi à convertir le fichier self.ulfile                
652             jdcText = source
653         else:
654             # Une erreur a été rencontrée
655             jdcText = ''
656         return ulfile, jdcText
657
658         
659 if __name__=='__main__':    
660     import prefs # dans main
661     name='prefs_'+prefs.code
662     prefsCode=__import__(name)
663
664     if hasattr(prefsCode,'encoding'):
665        # Hack pour changer le codage par defaut des strings
666        import sys
667        reload(sys)
668        sys.setdefaultencoding(prefs.encoding)
669        del sys.setdefaultencoding
670        # Fin hack
671
672 #    #CS_pbruno note: fait implicitement des trucs ces imports (grr)
673 #    import styles
674 #    import import_code
675 #    import session
676 #
677 #    # Analyse des arguments de la ligne de commande
678 #    options=session.parse(sys.argv)
679 #    code=options.code
680 #        
681     app = QApplication(sys.argv)    
682     mw = JDCEditor(None,'azAster.comm')
683     app.setMainWidget(mw)
684     app.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()"))
685     mw.show()
686             
687     res = app.exec_loop()
688     sys.exit(res)