Utilitzar els diàlegs de l’escriptori a PyQt4

Encara que PyQt4 siga uns dels toolkits més potents (opinió totalment subjectiva), també cal dir que els seus diàlegs de fitxers a Linux són deficients: son molt simples i no arriben a integrar-se amb l’escriptori.

Gràcies al desenvolupament d’Elltube m’he vist en la necessitat de solucionar aquest problema. El codi que mostraré a continuació, encara que una mica llarg, fa 3 comprovacions per assegurar-se si utilitzar els diàlegs que ofereix  zenity (Gnome o XFCE) o bé els de kdialog (KDE).

El codi està creat per una aplicació en concret, s’utilitza QProcess en lloc de subprocess i els diàlegs estan destinats a seleccionar directoris, no fitxers. Si necessiteu implementar aquest codi per a una aplicació no PyQt i utilitzar subprocess en lloc de QProcess o necessiteu altres paràmetres per als diàlegs, sols heu d’informar-me i vos ajudaré en el que puga.

I encara que no cal dir-ho, esteu autoritzats a utilitzar, canviar i distribuir el codi sense cap tipus de restriccions.


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import os, os.path, sys

class MainWindow(QMainWindow):
    def __init__(self, parent = None):
        QMainWindow.__init__(self, parent)

        self.centralwidget = QWidget()
        self.setCentralWidget(self.centralwidget)
        self.vlayout = QVBoxLayout(self.centralwidget)

        # Botó i connexió per obrir el diàleg
        self.button = QPushButton(self.tr("Select place"))
        self.connect(self.button, SIGNAL("clicked()"), self.SelectPlace)

        # Etiqueta on mostrarem el camí seleccionat
        self.label = QLabel()

        self.vlayout.addWidget(self.button)
        self.vlayout.addWidget(self.label)

    # Funció engegada pel botó que obrirà el diàleg
    def SelectPlace(self):

        # Per defecte utilitzarem QFileDialog
        Dialog = "qt"

        # Sols intentarem la detecció si estem a Linux
        if os.name == "posix":

            # Títol de la finestra
            FileDialogTitle = self.tr("Select place")
            # Lloc on volem que siga obert el diàleg
            FileDialogPath = "/home/lesergi"

            has_kdialog = False
            has_zenity = False

            # Cerquen al PATH els exectables de kdialog i zenity
            for directory in os.environ["PATH"].split(":"):

                # Si trobem kdialog
                if os.access(os.path.join(directory, "kdialog"), os.X_OK):
                    has_kdialog = True

                # Si trobem zenity
                if os.access(os.path.join(directory, "zenity"), os.X_OK):
                    has_zenity = True

            # Si sols hem detectat kdialog l'utilitzarem
            if has_kdialog and not has_zenity:
                Dialog = "kdialog"

            # Si sols hem detectat kdialog l'utilitzarem
            elif has_zenity and not has_kdialog:
                Dialog = "zenity"

            # Si s'han detectat els dos
            elif has_zenity and has_kdialog:

                # Provarem amb la variable de sistema DESKTOP_SESSION
                # DESKTOP_SESSION pot contenir l'escriptori que estem fent servir
                if os.environ.has_key("DESKTOP_SESSION"):
                    CurrentDesktop = os.environ["DESKTOP_SESSION"]

                # Si DESKTOP_SESSION conté kde farem servir kdialog
                if CurrentDesktop == "kde" and has_kdialog:
                    Dialog = "kdialog"

                # Si DESKTOP_SESSION conté gnome o xfce farem servir zenity
                elif CurrentDesktop == "gnome" or CurrentDesktop == "xfce" and has_zenity:
                    Dialog = "zenity"

                # Si DESKTOP_SESSION no ens ha aclarit res contarem
                # el nombre de processos relacionats amb KDE i Gnome
                # i farem servir el diàleg del que tinga més
                else:
                    p1 = QProcess(self)
                    p2 = QProcess(self)
                    p3 = QProcess(self)

                    p1.setStandardOutputProcess(p2)
                    p2.setStandardOutputProcess(p3)

                    # Contem els processos Gnome
                    p1.start('ps',['-A'])
                    p1.waitForFinished(-1)
                    p2.start('grep',['gnome'])
                    p2.waitForFinished(-1)
                    p3.start('wc',['-l'])
                    p3.waitForFinished(-1)
                    count_gnome = int(p3.readAllStandardOutput())

                    # Contem els processos KDE
                    p1.start('ps',['-A'])
                    p1.waitForFinished(-1)
                    p2.start('grep',['kde'])
                    p2.waitForFinished(-1)
                    p3.start('wc',['-l'])
                    p3.waitForFinished(-1)
                    count_kde = int(p3.readAllStandardOutput())

                    # Si tenim més processos de Gnome que KDE, farem servir zenity                
                    if count_gnome > count_kde:
                        Dialog = "zenity"
                    # Si tenim més processos de KDE que Gnome, farem servir kdialog        
                    else:
                        Dialog = "kdialog"

            # Finalment definim la línia d'arguments en cada cas
            if Dialog == "zenity":
                Cmd = ['zenity','--file-selection','--directory','--title', FileDialogTitle]
            elif Dialog == "kdialog":
                Cmd = ['kdialog','--caption', FileDialogTitle ,'--getexistingdirectory', "."]

        # Si no s'ha trobat al PATH ni kdialog ni zenity utilitzarem la class QFileDialog
        if Dialog == "qt":
            path = QFileDialog.getExistingDirectory(self, FileDialogTitle, FileDialogPath, QFileDialog.ShowDirsOnly)

            # Ja podem emprar la variable "path" per on ho necessitem
            if path:
                self.label.setText(path)

        # Si s'ha trobat kdialog o zenity
        else:
            # Iniciem una instància
            self.Process = QProcess(self)
            # Inhabilitem el botó per evitar obrir més d'un diàleg
            self.connect(self.Process, SIGNAL("started()"), self.DisableButton)
            # Creem una connexió que ens permetrà usar el camí obtingut
            # un cop el diàleg estiga tancat sense blocar l'aplicació
            self.connect(self.Process, SIGNAL("finished(int)"), self.FileDialogClosed)
            # Definim el directori on volem obrir el diàleg
            self.Process.setWorkingDirectory(FileDialogPath)
            # Engeguem el procés
            self.Process.start(Cmd[0], Cmd[1:])

    # Inhabilitem el botó per evitar obrir més d'un diàleg
    def DisableButton(self):
        self.button.setDisabled(True)

    # S'executarà quan el diàleg es tanque
    def FileDialogClosed(self):
        # Habilitem el botó
        self.button.setEnabled(True)

        # Per si de cas, esperarem a obtindre la lectura del camí
        self.Process.waitForReadyRead()

        # Agafem el camí del stdout
        path = str(self.Process.readAllStandardOutput())

        # Ja podem emprar la variable "path" per on ho necessitem
        if path:
            self.label.setText(unicode(path, "utf-8"))

app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Anuncis

Deixa un comentari

Fill in your details below or click an icon to log in:

WordPress.com Logo

Esteu comentant fent servir el compte WordPress.com. Log Out / Canvia )

Twitter picture

Esteu comentant fent servir el compte Twitter. Log Out / Canvia )

Facebook photo

Esteu comentant fent servir el compte Facebook. Log Out / Canvia )

Google+ photo

Esteu comentant fent servir el compte Google+. Log Out / Canvia )

Connecting to %s

%d bloggers like this: