Un lecteur de News (RSS/RDF/Atom) avec PyQt
Un article de Mangue.org, l'encyclopéde libre.
L'objectif
L' objectif des prochains articles va être de développer un lecteur de News de type RSS, RDF et Atom. Dans un premier temps nous allons créer une simple fenêtre qui vous permettra de rentrer une URL puis d'afficher dans une table les entrées contenues dans le résultat de la requête. Vous pourrez aussi sauvegarder vos URLs favorites afin de les retrouver à chaque lancement de l'application.
Nous n'allons pas réaliser la requête ni le parsing des résultats nous même mais nous allons nous baser sur l'excellent feedparser de Mark Pilgrim (http://diveintomark.org/projects/feed_parser/). En effet ce module nous donne la possibilité de gérer plusieurs formats et différentes subtilités comme l'encoding, les différentes erreurs, etc.
Une fenêtre principale
Il est évident que cette fois nous n'allons pas nous contenter d'un label ou d'un bouton pour notre application. Nous allons nous baser sur la classe QMainWindow de Qt. Cette classe nous permet de définir une fenêtre principale qui sera reconnue en tant que telle par le gestionnaire de fenêtre et apparaîtra donc dans votre bar de tâches.
Nous allons créer une classe qui dérive de QMainWindow afin de l'adapter à nos besoins.
import sys from qt import * import feedparser class rssfetcher(QMainWindow): def __init__(self,parent = None,name = None,fl = 0): # Appel au constructeur de base pour initialiser la fenetre QMainWindow.__init__(self,parent,name,fl) # Definition dy layout principal en grille rssfetcherLayout = QGridLayout(self,1,1,11,6,"rssfetcherLayout") #Ce label ne contiendra simplement le texte URL self.textLabel1 = QLabel(self,"textLabel1") # Ajout du label dans le layout rssfetcherLayout.addWidget(self.textLabel1,0,0) # Ajout de la table (enfin QListView) qui va afficher les résultats self.itemList = QListView(self,"itemList") # Ajout des colonnes de cette table self.itemList.addColumn(self.__tr("Titre")) self.itemList.addColumn(self.__tr("Date")) self.itemList.addColumn(self.__tr("Description")) # On indique que lorsqu'une ligne de la table est selectionne, toutes les colonnes de cette ligne le sont self.itemList.setAllColumnsShowFocus(1) # Ajout au layout rssfetcherLayout.addMultiCellWidget(self.itemList,1,1,0,4) # Creation du bouton Quitter self.quitButton = QPushButton(self,"quitButton") # On evite que ce boutton soit celui par defaut self.quitButton.setAutoDefault(0) rssfetcherLayout.addMultiCellWidget(self.quitButton,2,2,0,1) self.saveButton = QPushButton(self,"saveButton") rssfetcherLayout.addWidget(self.saveButton,2,2) self.refreshButton = QPushButton(self,"refreshButton") rssfetcherLayout.addWidget(self.refreshButton,2,3) # Creation d'un element QSpacer qui permet d'eviter que les boutons s'etendent sur toute la largeur de la fenetre. spacer3 = QSpacerItem(91,20,QSizePolicy.Expanding,QSizePolicy.Minimum) rssfetcherLayout.addItem(spacer3,2,4) # Creation de la liste deroulante presentant les URLs, le 1 signifie qu'elle est editable self.urlCombo = QComboBox(1,self,"urlCombo") # Voir plus bas settings = QSettings() settings.setPath('Mangue.org','LecteurRSS') self.urls,state = settings.readListEntry('/URL/urls') if not state: self.urls = QStringList() self.urlCombo.insertStringList(self.urls) rssfetcherLayout.addMultiCellWidget(self.urlCombo,0,0,1,4) # On ajoute le texte des elements self.languageChange() # On redefinie la taille par defaut de la fenetre self.resize(QSize(512,531).expandedTo(self.minimumSizeHint())) self.clearWState(Qt.WState_Polished) # On definie les evenement # Methode attachee a la liste deroulante lorsqu'on tape une nouvelle URL self.connect(self.urlCombo.listBox(),SIGNAL("returnPressed(QListBoxItem *)"),self.enteredUrl) # Methode attachee a la liste deroulante lorsqu'on active une URL self.connect(self.urlCombo,SIGNAL("activated(const QString &)"),self.activateUrl) self.connect(self.refreshButton,SIGNAL("clicked()"),self.refreshUrl) self.connect(self.saveButton,SIGNAL("clicked()"),self.saveUrls) self.connect(self.quitButton,SIGNAL("clicked()"),qApp,SLOT("quit()")) def __parseURL(self,url): # Methode qui va realiser le travail de requete et d'affichage dans la table # D'abord on efface toutes les entrees de la table self.itemList.clear() # Requete sur l'URL data = feedparser.parse(str(url)) for entry in data['entries']: # Ajout entree par entree dans la table QListViewItem(self.itemList,entry['title'],entry['date'],entry['description']) def languageChange(self): self.setCaption(self.__tr("Lecteur RSS/RDF")) self.textLabel1.setText(self.__tr("URL :")) self.itemList.header().setLabel(0,self.__tr("Titre")) self.itemList.header().setLabel(1,self.__tr("Date")) self.itemList.header().setLabel(2,self.__tr("Description")) self.itemList.clear() self.quitButton.setText(self.__tr("Quitter")) self.saveButton.setText(self.__tr("Conserver l'URL")) self.refreshButton.setText(self.__tr("Rafraichir")) def enteredUrl(self,item): self.__parseURL(item.text()) def activateUrl(self,url): self.__parseURL(url) def refreshUrl(self): self.__parseURL(self.urlCombo.currentText()) def saveUrls(self): settings = QSettings() settings.setPath('Mangue.org','LecteurRSS') self.urls.append(self.urlCombo.currentText()) settings.writeEntry('/URL/urls',self.urls) def __tr(self,s,c = None): return qApp.translate("rssfetcher",s,c) if __name__ == "__main__": a = QApplication(sys.argv) QObject.connect(a,SIGNAL("lastWindowClosed()"),a,SLOT("quit()")) w = rssfetcher() a.setMainWidget(w) w.show() a.exec_loop()
Nous utilisons la classe QSettings afin de sauvegarder de manière transparente et simple les différentes URLs ajoutées. QSettings va sauvegarder :
- Dans un fichier de config dans le répertoire .qt sous Linux
- Dans la base de registre sous Windows
- En utilisant l'API Carbon sous Mac OSX
Son utilisation est simple. On définie un chemin unique en utilisant un domaine, puis le nom de l'application. Puis par la suite on ajoute des sections en utilisant les méthodes beginGroup() et endGroup(). On ajoute des entrées aux sections en utilisant writeEntry*(). Bien entendu la relecture se fait via readEntry*(). Par conséquent, dasn le constructeur de la fenêtre principale, on tente de lire les entrées pour les ajouter a la liste déroulante.
Un mot sur les layouts
Lorsque vous créez une application, vous souhaitez qu'elle possède une apparence cohérente peu importe la résolution de l'utilisateur final. la solution est donc d'utiliser des layouts offerts par Qt afin de laisser l'API positionner au mieux les éléments. Il existe plusieurs layouts de bases :
- Vertical
- Horizontal
- Grille
Le grille vous permet de coupler simplement les deux premiers. D'une manière générale plus vos applications deviendront complexes, plus vos layouts le seront aussi.
Ceci étant il est important de passer un temps de réflexion afin de décider du positionnement de vos éléments afin de le faire une fois pour toute puis de laisser Qt réaliser le travail ingrat. Lorsque vous souhaitez séparer deux widgets sans pour autant créer deux layouts. Par exemple un menu de gauche et une zone d'édition à droite comme dans un explorateur classique, une bonne alternative est le widget QSplitter. Nous verrons son utilisation la prochaine fois.
Lors du prochain article, nous améliorerons notre lecteur de News afin de le rendre plus utilisable. Nous verrons aussi comment afficher le résultats des liens HTML dans un QTextBrowser.


