Outils pour utilisateurs

Outils du site


pyqt4_console_python

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.png' ​que 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 par tyrtamos