Outils pour utilisateurs

Outils du site


pyqt4_console_python

Warning: Undefined array key -1 in /home/clients/a4e6fc1ce1761b72982b805de0f418c4/web/python/mesrecettespython/inc/html.php on line 1458

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentes Révision précédente
pyqt4_console_python [2013/01/28 17:42]
tyrtamos
pyqt4_console_python [2013/02/07 13:11] (Version actuelle)
tyrtamos
Ligne 4: Ligne 4:
  
 [modification le 28/1/2013 16h] [modification le 28/1/2013 16h]
 +
 +[modification le 7/2/2013]
  
 ===== Objectif ===== ===== Objectif =====
Ligne 12: Ligne 14:
  
 Et ça marchera, même dans un programme autonome traité par cx_freeze, et exécuté sur un PC qui n'a pas Python! Et ça marchera, même dans un programme autonome traité par cx_freeze, et exécuté sur un PC qui n'a pas Python!
 +
 +Autre variante: utiliser cette fenêtre lancée par une icone située dans la zone de notification! Il y a une solution sur ce site dans le chapitre concernant PyQt4.
    
  
Ligne 18: Ligne 22:
 On utilise le module "code" qui est fait pour ça. On utilise le module "code" qui est fait pour ça.
  
-La fenêtre est toute simple: QWidget avec à l'intérieur un QTextEdit. Mais le QTextEdit est particulier, parce qu'il a fallu traiter certains caractères comme la touche "entrée" pour envoyer la dernière ligne tapée à l'interpréteur. J'ai donc sous-classé le QTextEdit pour ça: classe "Visu" dans le code ci-dessous. Cette classe devra aussi lancer le thread qui portera l'interpréteur, et recevoir ses réponses pour les afficher.+La fenêtre est toute simple: QMainWindow avec à l'intérieur un QTextEdit. Mais le QTextEdit est particulier, parce qu'il a fallu traiter l'interface avec l'interpréteur, et traiter certains caractères comme la touche "entrée" pour envoyer la dernière ligne tapée à l'interpréteur. J'ai donc sous-classé le QTextEdit pour ça: classe "Visu" dans le code ci-dessous. Cette classe devra aussi lancer le thread qui portera l'interpréteur, et recevoir ses réponses pour les afficher.
  
-l'interpréteur sera porté par un thread. Pourquoi un thread? Parce que l'attente de la prochaine instruction à exécuter (sorte de "raw_input") ferait figer le graphique. Et puis ce serait dommage qu'une exécution un peu longue fige aussi le graphique.+L'interpréteur sera porté par un thread. Pourquoi un thread? Parce que sinon, l'interpréteur aurait figé le graphique à chaque exécution et attente.
  
-Mais cela posera un problème particulier: un code s'exécutant dans un thread ne doit pas toucher directement au graphique PyQt4. Les échanges d'information se feront exclusivement par échange de messages. J'ai donc choisi une thread "QThread".+Mais cela posera un problème particulier: un code s'exécutant dans un thread ne doit pas toucher directement au graphique PyQt4. Les échanges d'information se feront exclusivement par échange de messages. J'ai donc choisi pour ça un thread "QThread".
  
-La partie interpréteur utilisée est la classe "InteractiveConsole" du module code. Elle est sous-classée et certaines méthodes surchargées pour assurer les échanges d'entrée/sortie avec la partie graphique, y compris avec un détournement des sys.stdout et sys.stderr.+La partie interpréteur utilisée est la classe "InteractiveConsole" du module code. Elle est ici sous-classée et certaines méthodes surchargées pour assurer les échanges d'entrée/sortie avec la partie graphique, y compris avec un détournement des sys.stdout et sys.stderr.
  
-Pour traiter les 2 points précédents, j'ai utilisé une classe, appelée ici "Interpy", qui hérite en même temps de "QtCore.QThread" et "code.InteractiveConsole". Les programmes en PyQt4 supportent très bien l'héritage multiple, à condition qu'il n'y ait pas plusieurs ancêtres issus de QWidget. +Concernant le détournement des sys.stdout et sys.stderr. Ce sont des objets de type "fichier". On va les détourner sur un "StringIO.StringIO"pour avoir aussi un type fichier, mais on va rediriger sa méthode write sur la méthode write de l'interpréteur.
- +
-Dernier point à résoudre, le détournement des sys.stdout et sys.stderr. Ce sont des objets de type "fichier". On peut toujours les détourner sur un "StringIO.StringIO" et récupérer le contenu après exécution pour l'afficher, mais l'impression à l'intérieur d'une boucle ne marchera pas. Il faut donc que chaque demande d'affichage soit satisfaite, même à l'intérieur d'une exécution complexe. La solution choisie est de sous-classer la classe StringIO, ce qui donne la classe "MonStringIO", et de surcharger sa méthode write.+
  
 Voilà pour les principes. le reste est largement commenté dans le code lui-même. Voilà pour les principes. le reste est largement commenté dans le code lui-même.
  
 Ce code fonctionne bien, mais il n'est pas encore parfait: il faut le considérer en version "beta". Les remarques sont bienvenues! Ce code fonctionne bien, mais il n'est pas encore parfait: il faut le considérer en version "beta". Les remarques sont bienvenues!
- 
-La seule chose que je n'ai pas réussi à faire, c'est d'arrêter un code trop long avec Ctrl-C. En effet, l'exécution se faisant dans le thread, l'exception KeyboardInterrupt n'a pas d'action sur lui. Ici, un Ctrl-C ferme la fenêtre: c'est tout ce que j'ai trouvé pour se débarrasser d'un code trop long ou planté. Je reste à l'écoute d'une solution plus intelligente!  
  
 L'idée initiale de ce code m'a été soufflée sur un forum ([[http://www.developpez.net/forums/d1301688/autres-langages/python-zope/gui/pyside-pyqt/console-python-qtextedit/]]): merci à wiztricks!  L'idée initiale de ce code m'a été soufflée sur un forum ([[http://www.developpez.net/forums/d1301688/autres-langages/python-zope/gui/pyside-pyqt/console-python-qtextedit/]]): merci à wiztricks! 
  
-Quelques modifications ont été faites depuis la toute 1ère publication (ce matin!). La plus importante est que l'on peut désormais fournir, dès le lancement, du code Python pour que l'interpréteur l'exécute au départ.  +On peut ajouter des lignes de code dans le fichier consolepy_init.py afin d'initialiser l'interpréteur: ces lignes seront exécutées comme si l'utilisateur les avait tapées au clavierOn peut dans ce fichier importer des modules spécialiséset donc de préparer la console à traiter des problèmes spécifiques au domaine dans lequel on est (scientifique, financier, technologiqueconstruction, etc...)
- +
-On peut ainsi: +
- +
-  * fournir une chaine de caractères: elle devra être le nom d'un fichier Python existant avec son cheminLe fichier sera lu et ses lignes exécutées une par une. +
- +
-  * fournir une liste (ou un tuple) d'instructions Python: ces instructions seront exécutées une par une. +
- +
-Dans les 2 cas, les instructions seront exécutées comme si l'utilisateur les avaient tapées lui-même +
- +
-si l'argument transmis est une chaine mais que le fichier n'existe pasou est n'importe quoi d'autre qu'une liste ou un tuplel'interpréteur n'en tiendra pas compte  +
  
-Voir les exemples fournis à la fin du code ci-dessous.+Le menu "Aide" de la fenêtre appelle le fichier "consolepy.html" (à créer) qui s'affiche dans le navigateur internet par défaut. On peut l'écrire facilement avec, par exemple, le logiciel gratuit "kompozer", ce qui permettra d'avoir l'aide en ligne sur les modules spécifiques qu'on aura ajoutés.
  
-Cette fonctionnalité va bien avec l'objectif de départ: quelque soit le domaine dans lequel on est (scientifique, financier, construction, etc...), on peut initialiser automatiquement la console avec des modules de fonctions adaptées, fonctions qu'on pourra utiliser dans la console comme avec une calculatrice spécialisée!+Il faudra aussi lui ajouter l'icone 'icone.pngque vous souhaitez.
  
 Voilà ce que ça va donner: Voilà ce que ça va donner:
Ligne 58: Ligne 48:
 {{:interpy.jpg?700|}} {{:interpy.jpg?700|}}
  
-Voilà le code.+Voilà le code. Il est en Python 2.7, mais il est facile à convertir en Python 3.2 grâce à 2to3.py (+ quelques modifications manuelles supplémentaires).
  
 ===== Code ===== ===== Code =====
Ligne 67: Ligne 57:
 from __future__ import division from __future__ import division
 # Python 2.7 # Python 2.7
 +
 +__programme__ = u"Console Python"
 +__logiciel__ = "consolepy"
 +__version__ = "1.00"
 +__date__ = "01/2013"
  
 import sys, os import sys, os
 import code import code
-import StringIO +import webbrowser 
-from time import sleep+from io import BytesIO as StringIO  
 +from Queue import Queue
  
 from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
  
 ############################################################################# #############################################################################
-# variables globales pour gérer les données communes avec le thread +class Interpy(code.InteractiveConsole): 
- +    """Interpréteur Python"""
-mutexokwrite = QtCore.QMutex() +
-okwrite = False +
- +
-############################################################################# +
-class MonStringIO(StringIO.StringIO): +
-     +
-    def __init__(self, orig, buf = ''): +
-        StringIO.StringIO.__init__(self, buf) +
-        self.orig = orig +
-         +
-    def write(self, s): +
-        # envoie la réponse sous forme de message à la fenêtre +
-        self.orig.write(s)  +
-            +
-############################################################################# +
-class Interpy(QtCore.QThread, code.InteractiveConsole):+
          
     #========================================================================     #========================================================================
-    def __init__(self, proprio, locals=None, filename="<console>"):  +    def __init__(self, tlanceur, locals=None, filename="<console>"):  
-        QtCore.QThread.__init__(self)+        """initialisation"""
         code.InteractiveConsole.__init__(self, locals=None, filename="<console>")         code.InteractiveConsole.__init__(self, locals=None, filename="<console>")
-        self.proprio proprio # adresse de l'objet qui a lancé le thread +        self.tlanceur tlanceur # adresse du thread qui a lancé l'interpréteur 
-        self.texte None # initialisation du texte disponible +         
-        # se préparer à recevoir les instructions du QTextEdit personnalisé +        # prépa de l'arrêt d'exécution de l'interpréteur sur demande 
-        self.connect(self.proprio, QtCore.SIGNAL("pourlire(PyQt_PyObject)"), self.textedispo) +        self.stopexecflag False 
 +         
 +        # rediriger sur le thread lanceur pour gagner du temps d'exécution 
 +        self.write = self.tlanceur.write 
 +        self.read = self.tlanceur.read 
 +        self.quit = self.tlanceur.quit
                  
-    #======================================================================== 
-    def run(self): 
-        """partie exécutée en asynchrone""" 
-        self.interact() 
-             
     #========================================================================     #========================================================================
     def runcode(self, code):     def runcode(self, code):
         """surcharge de runcode pour rediriger sys.stdout et sys.stderr"""         """surcharge de runcode pour rediriger sys.stdout et sys.stderr"""
         # redirection de la sortie d'affichage         # redirection de la sortie d'affichage
-        std_sav = sys.stdout, sys.stderr +        self.std_sav = sys.stdout, sys.stderr 
-        sys.stdout = sys.stderr = sio = MonStringIO(self+        sys.stdout = sys.stderr = sio = StringIO() 
-        # exécution de l'instruction Python        +        sio.write = self.write #2 rediriger l'écriture du fichier en RAM: sio 
 +         
 +        # exécution de l'instruction Python compilée
         try:         try:
-            exec code in self.locals+            sys.settrace(self.trace) # mise en place du traçage 
 +            exec code in self.locals # exécution du code 
 +            sys.settrace(None) # arrêt du traçage
         except SystemExit:         except SystemExit:
-            self.quit()+            sys.settrace(None) # arrêt du traçage 
 +            self.quit() # quitter le programme
         except:         except:
-            self.showtraceback()+            sys.settrace(None) # arrêt du traçage  
 +            self.showtraceback() # affichage de l'exception rencontrée
         finally:         finally:
             # remettre la sortie d'affichage initiale             # remettre la sortie d'affichage initiale
-            sys.stdout, sys.stderr = std_sav+            sys.stdout, sys.stderr = self.std_sav
             sio.close()             sio.close()
                  
 +    #========================================================================
 +    def trace(self, frame, event, arg):
 +        """méthode appelée à chaque ligne de code exécutée par exec"""
 +        if event == 'line':
 +            if self.stopexecflag:
 +                self.stopexecflag = False
 +                raise  KeyboardInterrupt ("Arret d'execution sur demande")
 +        return self.trace
 +
 +    #========================================================================
 +    def stopexec(self):
 +        """ méthode qu'on appelle pour demander l'arrêt de l'exécution"""
 +        self.stopexecflag = True
 +
     #========================================================================     #========================================================================
     def raw_input(self, prompt):     def raw_input(self, prompt):
-        """lire la prochaine ligne d'instruction""" +        """lire la prochaine ligne d'instruction Python""" 
-        try: +        return self.read(prompt)
-            line = self.read(prompt) +
-        except Exception as err: +
-            log.exception (err) +
-            raise SystemExit() +
-        else: +
-            return line+
    
     #========================================================================     #========================================================================
     def write(self, data):     def write(self, data):
-        """affiche data en envoyant un message à la fenêtre""" +        """(redirigé) affiche data par l'intermédiaire du thread""" 
-        global okwritemutexokwrite # drapeau pour le shake-hand d'affichage+        pass 
 + 
 +    #======================================================================== 
 +    def read(self, prompt): 
 +        """(redirigé) lit la chaine à interpréter par l'intermédiaire du thread""" 
 +        pass 
 + 
 +    #======================================================================== 
 +    def quit(self): 
 +        """(redirigé) ferme l'application par l'intermédiaire du thread""" 
 +        pass 
 + 
 +############################################################################# 
 +class Tipy(QtCore.QThread): 
 +    """thread Qt qui porte l'interpréteur Python""" 
 +     
 +    finconsole = QtCore.pyqtSignal() 
 +    pourafficher = QtCore.pyqtSignal(unicode)  
 +     
 +    #======================================================================== 
 +    def __init__(selfparent=None):  
 +        super(Tipy, self).__init__(parent) 
 +        # lancement de l'interpréteur Python (argument: l'adresse du thread) 
 +        self.interpy = Interpy(self) 
 +        # initialisation de la pile qui contiendra l'instruction à exécuter 
 +        self.instruction = Queue(maxsize=1) 
 +        initialisation du drapeau pour synchroniser l'affichage du QTextEdit 
 +        self.okwrite = False 
 +        self.mutexokwrite = QtCore.QMutex()
                  
 +    #========================================================================
 +    def run(self):
 +        """partie exécutée en asynchrone: la boucle de l'interpréteur Python"""
 +        self.interpy.interact()
 +            
 +    #========================================================================
 +    def write(self, data):
 +        """affiche data en envoyant un message à la fenêtre"""
         # pour être sûr que data est en unicode         # pour être sûr que data est en unicode
         if not isinstance(data, unicode):          if not isinstance(data, unicode): 
Ligne 151: Ligne 181:
         if data!=u"":         if data!=u"":
             # envoie la réponse sous forme de message (avec shake-hand)             # envoie la réponse sous forme de message (avec shake-hand)
-            mutexokwrite.lock() +            self.mutexokwrite.lock() 
-            okwrite = False +            self.okwrite = False 
-            mutexokwrite.unlock() +            self.mutexokwrite.unlock() 
-            self.emit(QtCore.SIGNAL("pourafficher(PyQt_PyObject)"), data)  +            # envoie data avec le signal 'pourafficher' 
-            while not okwrite: +            self.pourafficher.emit(data) 
-                pass +            # attend jusqu'à ce que le message soit effectivement affiché 
-         +            while not self.okwrite: 
-    #======================================================================== +                pass  
-    def textedispo(self, texte): +            
-        """exécuté à chaque message reçu "pourlire" accompagné du texte """ +
-        self.texte = texte +
-    +
     #========================================================================     #========================================================================
     def read(self, prompt):     def read(self, prompt):
-        """lit la chaine à interpréter""" +        """lit l'instruction Python à interpréter"""
-        +
         # envoi l'invite pour affichage         # envoi l'invite pour affichage
         self.write(prompt)         self.write(prompt)
- +        prend l'instruction dans la pile dès qu'il y en a une 
-        retourne la chaine dès qu'il y en a une de saisie +        return self.instruction.get()
-        while self.texte==None: +
-            sleep(0.1) +
-        texte = self.texte +
-        self.texte = None +
-        return texte+
  
 +    #========================================================================
 +    def stop(self):
 +        """appelé quand on veut arrêter l'exécution de l'interpréteur"""
 +        self.interpy.stopexec()
 +        
     #========================================================================     #========================================================================
     def quit(self):     def quit(self):
-        """appelé quand on veut fermer l'application"""+        """méthode appelée quand on veut fermer l'application""" 
 +        # arrête l'interpréteur s'il est en cours d'exécution 
 +        self.stop()
         # émet le signal de fin pour la fenêtre         # émet le signal de fin pour la fenêtre
-        self.emit(QtCore.SIGNAL("finconsole()")+        self.finconsole.emit() 
- +        
 ############################################################################# #############################################################################
 class Visu(QtGui.QTextEdit): class Visu(QtGui.QTextEdit):
 +    """sous-classement de QTextEdit pour communiquer avec l'interpréteur
 +       Python via un QThread
 +    """
          
-    def __init__(self, parent=None, initpy=[]): +    # signal pour recevoir du texte à afficher 
-        +    pourafficher = QtCore.pyqtSignal(unicode)  
 +    # signal pour recevoir une demande d'arrêt 
 +    finconsole = QtCore.pyqtSignal() 
 +    # signal pour émettre une demande de fermeture de la fenêtre 
 +    finfenetre = QtCore.pyqtSignal() 
 +     
 +    #======================================================================== 
 +    def __init__(self, parent=None, initpy=u"consolepy_init.py"):
         super(Visu, self).__init__(parent)         super(Visu, self).__init__(parent)
-        self.setAcceptRichText(False) +        # stockage de l'argument passé 
-        self.setLineWrapMode(QtGui.QTextEdit.NoWrap)  +        self.initpy initpy
-         +
-        prépa du lancement du thread qui porte l'interpréteur Python +
-        self.interpy = Interpy(self) +
-        # prépa pour recevoir de interpy un signal de fermeture de la fenêtre  +
-        self.connect(self.interpy, QtCore.SIGNAL("finconsole()"), self.quitter) +
-        # prépa pour recevoir du thread 'interpy' du texte à afficher  +
-        self.affichinit = False +
-        self.connect(self.interpy, QtCore.SIGNAL("pourafficher(PyQt_PyObject)"), self.affiche) +
-        # lancement du thread +
-        self.interpy.start() +
-         +
-        # initialisation de la position du curseur dans le texte +
-        self.pos1 = self.textCursor().position() +
-         +
-        # initialisation de l'historique des lignes d'instruction Python +
-        self.historique = [] +
-        self.ih 0+
                  
         # prépa de l'initialisation de l'interpréteur si demandé au lancement         # prépa de l'initialisation de l'interpréteur si demandé au lancement
-        self.initpy = initpy 
         if isinstance(self.initpy, (str, unicode)):         if isinstance(self.initpy, (str, unicode)):
             # si c'est une chaine: elle doit représenter un fichier à charger             # si c'est une chaine: elle doit représenter un fichier à charger
Ligne 219: Ligne 239:
                     for ligne in f:                     for ligne in f:
                         self.initpy.append(ligne.rstrip())                         self.initpy.append(ligne.rstrip())
-         
         else:         else:
             if not isinstance(self.initpy, (list, tuple)):             if not isinstance(self.initpy, (list, tuple)):
Ligne 225: Ligne 244:
                 self.initpy = []                 self.initpy = []
         self.lginitpy = len(self.initpy)         self.lginitpy = len(self.initpy)
-        self.nbinvites = -1 
                  
 +        # configuration du QTextEdit 
 +        self.setAcceptRichText(False) 
 +        self.setLineWrapMode(QtGui.QTextEdit.NoWrap)  
 +        # Changer la police de caractères et sa taille 
 +        font = QtGui.QFont() 
 +        font.setFamily(u"DejaVu Sans Mono"
 +        font.setPointSize(10) 
 +        self.setFont(font) 
 +         
 +        # lancement du thread qui porte l'interpréteur Python 
 +        self.tipy = Tipy() 
 +        # prépa pour recevoir de tipy un signal de fermeture de la fenêtre  
 +        self.tipy.finconsole.connect(self.quitter) 
 +        # prépa pour recevoir de tipy du texte à afficher  
 +        self.tipy.pourafficher.connect(self.affiche) 
 +        # démarrage du thread 
 +        self.tipy.start() 
 +         
 +        # initialisation de la position courante du curseur après invite 
 +        self.pos1 = self.textCursor().position() 
 +        # portera la position du curseur à la 1ère invite 
 +        self.pos0 = self.pos1 
 +         
 +        # initialisation de l'historique des lignes d'instruction Python 
 +        self.historique = [] 
 +        self.ih = 0 
 +         
 +        # compteur du nombre d'invites affichées   
 +        self.nbinvites = -1 
          
     #========================================================================     #========================================================================
 +    @QtCore.pyqtSlot(unicode)
     def affiche(self, texte):     def affiche(self, texte):
         """Affiche la chaine 'texte' dans le widget QTextEdit"""         """Affiche la chaine 'texte' dans le widget QTextEdit"""
-        global okwrite, mutextokwrite # drapeau pour le shake-hand d'affichage 
                  
         # ajoute la chaine unicode à la fin du QTextEdit         # ajoute la chaine unicode à la fin du QTextEdit
Ligne 244: Ligne 290:
                  
         # renvoie un accusé de réception de fin d'affichage (shake-hand)         # renvoie un accusé de réception de fin d'affichage (shake-hand)
-        mutexokwrite.lock() +        self.tipy.mutexokwrite.lock() 
-        okwrite = True +        self.tipy.okwrite = True 
-        mutexokwrite.unlock()+        self.tipy.mutexokwrite.unlock()
                  
         # envoie les lignes de code d'initialisation après la 1ère invite         # envoie les lignes de code d'initialisation après la 1ère invite
-        if self.nbinvites<self.lginitpy:+        if self.nbinvites < self.lginitpy: 
 +            if self.nbinvites==0: 
 +                # 1ère invite 
 +                self.pos0 = self.textCursor().position() 
             if self.nbinvites>=0:             if self.nbinvites>=0:
-                self.emit(QtCore.SIGNAL("pourlire(PyQt_PyObject)"), self.initpy[self.nbinvites])+                # il y a encore des lignes d'instruction initiales à exécuter 
 +                self.tipy.instruction.put(self.initpy[self.nbinvites])
             self.nbinvites += 1             self.nbinvites += 1
  
Ligne 269: Ligne 319:
             # débarrasse l'instruction 'texte' des éventuels fins de ligne             # débarrasse l'instruction 'texte' des éventuels fins de ligne
             while texte!=u"" and texte[0] in ['\n', '\r']: texte = texte[1:]             while texte!=u"" and texte[0] in ['\n', '\r']: texte = texte[1:]
-            texte = texte.rstrip() +            texte = texte.rstrip() # retire à droite fin de ligne et espaces 
-            #envoyer la ligne d'instruction à l'interpréteur Python +            # empile l'instruction pour qu'elle soit exécutée 
-            self.emit(QtCore.SIGNAL("pourlire(PyQt_PyObject)"), texte)+            self.tipy.instruction.put(texte)
             # conserver la ligne d'instruction dans l'historique             # conserver la ligne d'instruction dans l'historique
-            self.historique.append(texte) +            if texte != u"": 
-            self.ih = len(self.historique)-1 # pointe sur le dernier élément+                self.historique.append(texte) 
 +                self.ih = len(self.historique)-1 # pointe sur le dernier élément
             # sauvegarde la position du curseur de début d'instruction             # sauvegarde la position du curseur de début d'instruction
             self.pos1 = pos2             self.pos1 = pos2
Ligne 280: Ligne 331:
                          
         #--------------------------------------------------------------------         #--------------------------------------------------------------------
-        elif event.key()==QtCore.Qt.Key_Z and +        elif event.key()==QtCore.Qt.Key_Z and (event.modifiers() & QtCore.Qt.ControlModifier): 
-                         (event.modifiers() & QtCore.Qt.ControlModifier): +            # Ctrl-Z: annule ce qui vient d'être tapé
-            # ne fait rien, mais neutralise le Ctrl-Z+
             event.accept()             event.accept()
-        +            # exécute le Ctrl-Z normal du QTextEdit 
 +            QtGui.QTextEdit.keyPressEvent(self, event) 
 +            # déplace le curseur à la fin du texte 
 +            self.moveCursor(QtGui.QTextCursor.End, QtGui.QTextCursor.MoveAnchor) 
 +            # force le rafraichissement pour affichage en temps réel 
 +            QtCore.QCoreApplication.processEvents()  
 +            # met à jour la position du curseur dans le texte 
 +            self.pos1 = self.textCursor().position() 
 +            
         #--------------------------------------------------------------------         #--------------------------------------------------------------------
-        elif event.key()==QtCore.Qt.Key_C and \+        elif event.key()==QtCore.Qt.Key_S and \
                          (event.modifiers() & QtCore.Qt.ControlModifier):                          (event.modifiers() & QtCore.Qt.ControlModifier):
-            # Controle-arrête l'interpréteur et ferme la fenêtre +            # Ctrl-arrête l'interpréteur 
-            self.interpy.quit()+            self.tipy.stop()            
             event.accept()             event.accept()
-        +             
 +        #-------------------------------------------------------------------- 
 +        elif event.key()==QtCore.Qt.Key_E and \ 
 +                         (event.modifiers() & QtCore.Qt.AltModifier): 
 +            # Alt-E remet l'affichage au début à l'emplacement de la 1ère invite 
 +            texte = unicode(self.toPlainText()) 
 +            texte = texte[:self.pos0] 
 +            self.clear() 
 +            self.affiche(texte) 
 +            self.pos1 = self.pos0 
 +            event.accept() 
         #--------------------------------------------------------------------         #--------------------------------------------------------------------
         elif event.key()==QtCore.Qt.Key_Up:         elif event.key()==QtCore.Qt.Key_Up:
Ligne 334: Ligne 403:
                 event.accept()                     event.accept()    
                  
 +        #--------------------------------------------------------------------
 +        elif event.key()==QtCore.Qt.Key_Tab:
 +            """Tabulation: insérer 4 espaces à l'emplacement du curseur"""
 +            self.insertPlainText(u"    ")
 +        
 +        #--------------------------------------------------------------------
 +        elif event.key() in [QtCore.Qt.Key_Up, 
 +                             QtCore.Qt.Key_Down,
 +                             QtCore.Qt.Key_PageUp, 
 +                             QtCore.Qt.Key_PageDown]:
 +            """neutralisation de touches de QTextEdit inutile pour 
 +               l'interpréteur
 +            """
 +            event.accept() 
 +
         #--------------------------------------------------------------------         #--------------------------------------------------------------------
         else:         else:
             # n'importe quel autre caractère que ceux ci-dessus             # n'importe quel autre caractère que ceux ci-dessus
             event.ignore()             event.ignore()
-            # évènement transmis à l'ancêtre+            # évènement transmis à l'ancêtre QTextEdit
             QtGui.QTextEdit.keyPressEvent(self, event)             QtGui.QTextEdit.keyPressEvent(self, event)
-    +            
     #========================================================================     #========================================================================
 +    @QtCore.pyqtSlot()
     def quitter(self):     def quitter(self):
         """la fenêtre a reçu le signal de fermeture de l'interpréteur """         """la fenêtre a reçu le signal de fermeture de l'interpréteur """
         # on ré-émet le signal de fermeture pour la fenêtre         # on ré-émet le signal de fermeture pour la fenêtre
-        self.emit(QtCore.SIGNAL("finfenetre()"))+        #self.emit(QtCore.SIGNAL("finfenetre()")
 +        self.finfenetre.emit()
    
 ############################################################################# #############################################################################
-class Consolepy(QtGui.QWidget):+class Consolepy(QtGui.QMainWindow):
          
     #========================================================================     #========================================================================
-    def __init__(self, initpy=[], parent=None):+    def __init__(self, initpy=u"", parent=None):
         super(Consolepy, self).__init__(parent)         super(Consolepy, self).__init__(parent)
                  
         # instructions pour la fenêtre          # instructions pour la fenêtre 
-        self.setWindowTitle(u"Interpréteur Python")+        self.setWindowTitle(__programme__)
         self.resize(700, 700) # definit la taille de la fenetre         self.resize(700, 700) # definit la taille de la fenetre
-        QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Plastique')+        icone = QtGui.QIcon(u'icone.png') 
-        #self.setWindowIcon(QtGui.QIcon('iconefenetre.ico'))+        self.setWindowIcon(icone)
  
         # créer le QTextEdit personnalisé         # créer le QTextEdit personnalisé
         self.visu = Visu(self, initpy)         self.visu = Visu(self, initpy)
                  
-        # Changer la police de caractères et sa taille +        self.setCentralWidget(QtGui.QFrame())
-        font = QtGui.QFont(+
-        font.setFamily(u"DejaVu Sans Mono") +
-        font.setPointSize(10) +
-        self.visu.setFont(font) +
- +
-        # positionner dans la fenêtre+
         posit = QtGui.QGridLayout()         posit = QtGui.QGridLayout()
         posit.addWidget(self.visu, 0, 0)         posit.addWidget(self.visu, 0, 0)
-        self.setLayout(posit)+        self.centralWidget().setLayout(posit
 + 
 +        #-------------------------------------------------------------------- 
 +        # créer le menu principal de la fenêtre 
 +        menubar = self.menuBar() 
 +         
 +        aideMenu = menubar.addMenu('Aide'
 +         
 +        aideAction = QtGui.QAction('&Manuel', self) 
 +        aideAction.setShortcut(QtCore.Qt.Key_F1)         
 +        aideAction.setStatusTip(u"Manuel du programme"
 +        aideAction.triggered.connect(self.manuel_m) 
 +        aideMenu.addAction(aideAction) 
 +         
 +        aproposAction = QtGui.QAction('&A propos', self) 
 +        aproposAction.setStatusTip(u"A propos du programme"
 +        aproposAction.triggered.connect(self.apropos) 
 +        aideMenu.addAction(aproposAction) 
 +         
 +        #-------------------------------------------------------------------- 
 +        # initialisation de la barre de status (affiche le nom de page html) 
 +        self.statusBar().showMessage(u"")
  
         # se préparer à recevoir de self.visu un signal de fermeture         # se préparer à recevoir de self.visu un signal de fermeture
         self.connect(self.visu, QtCore.SIGNAL("finfenetre()"), self.quitter)         self.connect(self.visu, QtCore.SIGNAL("finfenetre()"), self.quitter)
 +
 +        # mettre le focus sur le QTextEdit
 +        self.visu.setFocus()
 +    
 +    #========================================================================
 +    def manuel_m(self):
 +        # affiche le manuel dans le navigateur web par défaut
 +        manuel = os.path.abspath("consolepy.html")
 +        if os.path.exists(manuel):
 +            webbrowser.open(manuel)
 +    
 +    #========================================================================
 +    def apropos(self):
 +        """Fenêtre 'à propos' """
 +        pf = sys.platform
 +        if pf=='win32': pf='Windows'
 +        elif pf=='linux2': pf = 'Linux'
 +        elif pf=='sunos5': pf='Sun OS'
 +        else: pass
 +        QtGui.QMessageBox.about(self, u"A propos du logiciel",
 +        u"""%s version %s  (%s)
 +    Copyright Jean-Paul Vidal 2013
 +    Licence GPL3
 +    Source sur demande ici: http://www.jpvweb.com
 +    
 +    Contexte d'exécution en cours:
 +    Système d'exploitation: %s
 +    Python version: %s
 +    Qt version: %s
 +    PyQt4 version: %s""" % (
 +            __logiciel__,
 +            __version__,
 +            __date__,
 +            pf,
 +            sys.version, 
 +            QtCore.QT_VERSION_STR, 
 +            QtCore.PYQT_VERSION_STR)
 +            )
  
     #========================================================================     #========================================================================
     def quitter(self):     def quitter(self):
-        """la fenêtre a reçu le signal de fermeture"""+        """fermeture de la fenêtre demandée par l'interpréteur (par signal)"""
         self.close()         self.close()
 +    
 +    #========================================================================
 +    def closeEvent(self, event):
 +        """ferme la fenêtre, même avec la croix ou le menu système"""
 +        event.accept()
                  
 ############################################################################# #############################################################################
 if __name__ == '__main__': if __name__ == '__main__':
  
 +    #========================================================================
     app = QtGui.QApplication(sys.argv)     app = QtGui.QApplication(sys.argv)
 +    # définition du style
 +    app.setStyle(QtGui.QStyleFactory.create(u"Plastique"))
 +    app.setPalette(QtGui.QApplication.style().standardPalette())
          
-    # possibilité de donner comme argument une liste d'instructions Python +    #======================================================================== 
 +    # pour assurer la traduction automatique du conversationnel à la locale 
 +    locale = QtCore.QLocale.system().name() 
 +    translator = QtCore.QTranslator () 
 +     
 +    if os.path.splitext(sys.argv[0])[1] in ['.py', '.pyw']: 
 +        # exécution par l'interpréteur normal 
 +        reptrad = unicode(QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)) 
 +    else: 
 +        # exécution de la version exécutable après cx_freeze (ou équivalent):  
 +        #=>les fichiers de traduction doivent se trouver dans "translations" 
 +        reptrad = unicode("translations"
 +     
 +    translator.load(QtCore.QString("qt_") + locale, reptrad) 
 +    app.installTranslator(translator) 
 + 
 +    #======================================================================== 
 +    # paramétrage: instructions Python à exécuter par la console au lancement 
 +    """
     initpy = [u"# -*- coding: utf-8 -*-",     initpy = [u"# -*- coding: utf-8 -*-",
               u"from __future__ import division",               u"from __future__ import division",
               u"from math import *"]               u"from math import *"]
-    # possibilité de donner comme argument un nom de fichier Python à exécuter +    """ 
-    #initpy = "consolepy_init.py" +    initpy = "consolepy_init.py"
-    # un argument d'un mauvais type ne sera pas considéré (ici un dictionnaire) +
-    #initpy = {}+
  
 +    #========================================================================
     fen = Consolepy(initpy)     fen = Consolepy(initpy)
     fen.show()     fen.show()
 +
 +    #========================================================================
     sys.exit(app.exec_())     sys.exit(app.exec_())
 </code> </code>
pyqt4_console_python.txt · Dernière modification: 2013/02/07 13:11 de tyrtamos