Entonces descubrí
wxpython, y parafraseando a
Eric S. Raymond, estoy convencido de que debería
convertirse en la GUI estándar de python.
Como decía mi madre, obras son amores, y no buenas
razones, así que en vez de vender wxpython, razonando
sus ventajas, voy a describir como realizar un programa
sencillito, consitente en una única ventana, con un
menú. El código ocupa 159 líneas,
incluidos comentarios, pero sirve perfectamente como toma de
contacto con wxpython.
Un comentario antes de empezar, no soy programador, no me
gusta leer documentación, y soy más bien vago. Lo
que quiero decir es que no es necesario tener muchos mimbres
para hacer esta cesta. A base de mirar ejemplos, y con un poco
de prueba y fallo, el script me ha llevado una hora escasa.
Así que si yo he sido capaz, cualquiera puede
hacerlo.
Dicho esto, empecemos:
import sys, os, time, re
from wxPython.wx import *
from poplib import POP3
refrom = re.compile("From:")
resubj = re.compile("Subject:")
lista = []
class main_window(wxFrame):
def __init__(self, parent, id, title):
wxFrame.__init__(self, parent, -1, title, size = (600, 410), \
style=wxDEFAULT_FRAME_STYLE|wxNO_FULL<U>REPAINT</u>ON_RESIZE)
class App(wxApp):
def OnInit(self):
frame = main_window(None, -1, "wxcorreo")
self.SetTopWindow(frame)
return true
app = App(0)
app.MainLoop()
Lo primero de todo es crear la estructura básica de
nuestra ventana. Todas los programas en wxpython tienen en
común la clase App, con el método OnInit, que en
este caso crea una ventana, con el título
"wxcorreo".
Vamos a añadirle algo más a la clase
main_window, para que realmente parezca algo, empecemos con una
barra de menús, y sus menús correspondientes:
self.mainmenu = wxMenuBar()
mainwindow = self
menu = wxMenu()
exitID = wxNewId()
menu.Append(exitID, 'Conexión', 'Conecta con el servidor')
EVT_MENU(self, exitID, self.OnConect)
exitID = wxNewId()
menu.AppendSeparator()
menu.Append(exitID, 'Salir', 'Cierra la aplicación')
EVT_MENU(self, exitID, self.OnExit)self.mainmenu.Append(menu, 'Principal')
menu = wxMenu()
exitID = wxNewId()
menu.Append(exitID, 'Acerca de', '')
EVT_MENU(self, exitID, self.OnAbout)self.mainmenu.Append(menu, 'Ayuda')
self.SetMenuBar(self.mainmenu)
A la vez que hemos creado la barra de menús, con sus
dos menús, "Principal" y "Ayuda", hemos indicado que
hacer con cada una de las opciones, mediante las líneas
EVT_MENU, que llaman a las funciones OnConect, OnExit y
OnAbout. Más adelante nos pondremos con ellas.
Ahora vamos a añadir una barra de estado a la
ventana. Como nuestros usuarios gustan de las pijaditas, vamos
a añadir también un reloj. La cuestión es
arropar bien el script.
sb = self.CreateStatusBar(2)
sb.SetStatusWidths([-1, 150])
self.timer = wxPyTimer(self.Notify)
self.timer.Start(1000)
self.Notify()
En realidad, lo que hemos hecho es crear una cuenta
atrás, que cada 1.000 milisegundos, llama a la
función Notify, que es la que realmente pone la fecha y
hora. Esta función la veremos después.
Finalmente, para terminar con nuestra ventana (de momento
sólo tenemos un marco, con una barra de menús y
una barra de estado), añadimos una ventanita de texto,
en la que se mostrará el resultado de ejecutar el script
(el script original, el que se lanzaba en consola, y que tan
poco gustaba a nuestros usuarios).
self.win = wxTextCtrl(self, 1, style = wxTE_MULTILINE|
wxTE_READONLY|
wxHSCROLL)
Vamos a echar una ojeada a las funciones.
def OnExit(self, event):
self.Close()
No tiene mucha complicación, ¿verdad? Queremos
cerrar, y eso es lo que hace. Punto.
def OnAbout(self, event):
dlg = wxMessageDialog(self, "(c) 2003 Benjamín Albiñana Pérez\n"
"foobar@escomposlinux.org\n",
"wxcorreo.py", wxOK | wxICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
La típica ventanita que aparece cuando se pulsa la
opción "Acerca de...", un texto, una tecla "Ok", y un
icono.
def Notify(self):
t = time.localtime(time.time())
st = time.strftime(" %d-%b-%Y %I:%M:%S", t)
self.SetStatusText(st, 1)
La función que llama la cuenta atrás, coge la
fecha y hora del sistema, y la muestra dentro de la barra de
estado.
El script original no tenía ningún misterio,
pedía al usuario que introdujera el servidor, su nombre
de usuario y su pass, y devolvía una lista de sus
correos, con el From: y el Subject:
wxpython tiene varias ventanas de diálogo "listas
para su uso", como hemos visto en la función OnAbout,
pero en este caso, necesitamos construirnos nuestro propio
diálogo, al necesitar tres campos de texto, incluyendo
uno que enmascare el texto con asteriscos. Para construir
nuestro propio diálogo creamos otra clase, con sus cajas
de texto y sus botones, en la función OnConect validamos
los datos introducidos (en realidad, le decimos que sí
el usuario ha pulsado Ok, tome el texto de las cajas), llamamos
al script original, convenientemente transformado en
función, y todo ello lo conectamos en el método
__init__ de la clase main_window.
Empecemos con la clase wxDialog.
class ConectServer(wxDialog):
def __init__(self, parent, id, title):
wxDialog.__init__(self, parent, -1, title, size = (300, 160))
wxStaticText(self, -1, "Servidor:", (10, 10))
self.servidor = wxTextCtrl(self, -1, "pop3.escomposlinux.org", (70, 10), (200, 20))
wxStaticText(self, -1, "Usuario:", (10, 35))
self.usuario = wxTextCtrl(self, -1, "foobar", (70, 35), (100, 20))
wxStaticText(self, -1, "Password:", (10, 60))
self.password = wxTextCtrl(self, -1, "", (70, 60), (100, 20), wxTE_PASSWORD)
wxButton(self, wxID_OK, " OK ", (50, 110)).SetDefault()
wxButton(self, wxID_CANCEL, " Cancelar ", (175, 110))
Los botones y las cajas se han puesto a ojímetro, y
es conveniente probar tanto en linux como en windows como
quedan. No tengo muy claro porqué, pero lo que en linux
se ve correctamente, en windows puede quedar demasiado
desplazado hacia abajo.
def OnConect(self, event):
val = self.connect.ShowModal()
if val == wxID_OK:
self.servidor = self.connect.servidor.GetValue()
self.usuario = self.connect.usuario.GetValue()
self.password = self.connect.password.GetValue()
self.Correo()
La función toma el valor del evento que resulta al
pulsar uno de los dos botones del diálogo anterior, y si
este es OK, asigna los valores pasados por el usuario a las
variables servidor, usuario y password, y llama a la
última función Correo. Para que todo esto
conecte, habremos añadido la línea
self.connect = ConectServer(self, -1, "Conexión al servidor")
al método __init__ de la clase main_window.
Finalmente, la función Correo, que mediante el
módulo poplib, conecta con el servidor y devuelve una
lista de los correos, mostrando las líneas "From:" y
"Subject:". Estas líneas se envían a la ventana
del programa.
def Correo(self):
m = POP3(self.servidor)
m.user(self.usuario)
m.pass_(self.password)
n = len(m.list()[1])
for j in range(n):
men = m.top(j+1, 0)[1]
for i in range(len(men)):
linea = refrom.search(men[i])
if linea != None:
lista.append(men[i])
linea = resubj.match(men[i])
if linea != None:
lista.append(men[i])
m.quit()
for k in range(len(lista)):
self.win.AppendText(lista[k] + '\n')
linea = resubj.match(lista[k])
if linea != None:
self.win.AppendText('\n')
Ya sólo queda lanzar el script y ver que pinta tiene.
Aquí se ve la aplicación con el menú
principal desplegado en linux, y aquí en windows, con
la ventana "Acerca de..."
Obviamente este script es sólo un ejemplo de lo que
se puede hacer con wxpython. Tengo muy claro los fallos que
tiene (sobre todo el pasar olímpicamente de las posibles
excepciones que se puedan producir), y, por supuesto, que no es
para tirar cohetes. Simplemente he querido compartir con todo
el mundo mi entusiasmo al descubrir wxpython.
Como ya he dicho al principio, no me gusta mirar
documentación, y prefiero con diferencia el estilo de
programación anárquico (programo como me sale de
los Objetos). En el paquete wxwin2.2-examples, uno de los
Suggests del paquete libwxgtk2.2-python en debian, se
encuentran un montón de ejemplos y sobre todo, el
programa demo.py. Imprescindible para arrancar con
wxpython.
Nada más, espero que toda esta parrafada haya servido
de algo. A mí me ha servido para tener otra entrada en
mi diario. Algo es algo.