Los widgets en un QTreeWidgetItems no se mueven.
Cuando uno pone widgets adentro de los ítems de un QTreeWidget (que no es muy común,
pero a veces es útil), los widgets no se mueven junto con el ítem.
Solución, usar la opción -graphicssystem raster. Hasta se la puede inyectar en
argv si la plataforma es darwin.
Three years ago, I started a series of long posts called "PyQt by Example". It reached five posts
before I abandoned for a series of reasons that don't matter anymore. That series is coming back
starting next week, rewritten, improved and extended.
It will do so in a new site, and the "old" posts will be retired to an archive page. Why? Well,
the technologies used in some of them are obsolete or don't quite work nowadays. So, the new
versions will be the preferred ones.
And while I am not promising anything, I have enough written to make this something quite longer,
more nicely layouted, more interesting and make it cover more ground. BUT, while doing some
checks on the traffic statistics for the old posts, some things popped out.
This was very popular
About 60% of my site's traffic goes to those five posts. Out of about 1200 posts over
12 years, 60% of the viewers go to the 0.4% of the pages. That is a lot.
It's a long tail
The traffic has not decreased in three years. If anything, it has increased
So, all this means there is a desire for PyQt documentation that is not satisfied. I am not
surprised: PyQt is great, and the recommended book is not free, so there is bound to be a lot
of demand.
And, here's the not-so-rosy bit: I had unobtrusive, relevant, out-of-the-way-but-visible ads
in those pages for more than two years. Of the 70000 unique visitors, not even one clicked on
an ad. Don't worry, I was not expecting to get money out of them (although I would love to
some day collect a $100 check instead of having google hold my money for me ad eternum).
But really? Not even one ad click? In more than two years, thousands of people? I have to
wonder if I just attract cheap people ;-)
Así que acá va un pequeño tutorial acerca de como usar algunas de esas APIs. Lo hice usando Python y PyQt por varios motivos:
Son excelentes herramientas para prototipos
Tienen excelente soporte para las cosas que necesito (DBus, HTTP, OAuth)
Es lo que sé y me gusta. Lo hice un domingo, no lo pienso hacer en PHP
y Gtk.
Dicho eso, no hay nada específico de python o de Qt en este código. Donde hago
un request HTTP usando QtNetwork, podés usar libsoup o lo que fuere.
Vayamos a los bifes entonces. Las piezas más importantes de Ubuntu One, desde el
punto de vista de infraestructura, son Ubuntu SSO Client, que se encarga de
login, registración, etc, y SyncDaemos que maneja la sincronización de archivos.
Para interactuar con ellas, en Linux, ofrecen interfaces DBus. Así que, por ejemplo,
este es un fragmento mostrando como obtener las credenciales de Ubuntu One (esto
normalmente sería parte del __init__ de un objeto):
# Get the session busbus=dbus.SessionBus():::# Get the credentials proxy and interfaceself.creds_proxy=bus.get_object("com.ubuntuone.Credentials","/credentials",follow_name_owner_changes=True)# Connect to signals so you get a call when something# credential-related happensself.creds_iface=dbus.Interface(self.creds_proxy,"com.ubuntuone.CredentialsManagement")self.creds_proxy.connect_to_signal('CredentialsFound',self.creds_found)self.creds_proxy.connect_to_signal('CredentialsNotFound',self.creds_not_found)self.creds_proxy.connect_to_signal('CredentialsError',self.creds_error)# Call for credentialsself._credentials=Noneself.get_credentials()
Tal vez notaste que get_credentials no devuelve las credenciales. Lo que hace es, le
dice a SyncDaemon que las obtenga, y entonces, si/cuando aparecen, se emite una de esas
señales, y uno de los métodos conectados se llama. Esto está bueno porque no tenemos
que preocuparnos de que se nos bloquee la aplicación mientras SyncDaemon está buscando
las credenciales.
¿Y qué hay en esos métodos? ¡No mucho!
defget_credentials(self):# Do we have them already? If not, get'emifnotself._credentials:self.creds_proxy.find_credentials()# Return what we've got, could be Nonereturnself._credentialsdefcreds_found(self,data):# Received credentials, save them.print"creds_found",dataself._credentials=data# Don't worry about get_quota yet ;-)ifnotself._quota_info:self.get_quota()defcreds_not_found(self,data):# No credentials, remove old ones.print"creds_not_found",dataself._credentials=Nonedefcreds_error(self,data):# No credentials, remove old ones.print"creds_error",dataself._credentials=None
Así que básicamente, self._credentials contiene unas credenciales, o None. Felicitaciones, ya
entramos a Ubuntu One.
¡Hagamos algo útil! ¿Que tal preguntar cuánto espacio libre hay en la cuenta? Para eso, no
podemos usar las APIs locales, si no conectarnos a los servers, que son los que saben
si estás excedido de quota o no.
El acceso se controla via OAuth, por lo que para acceder a esa API necesitamos firmar
nuestros pedidos. Aquí se ve como se hace. No es particularmente iluminador, yo no lo
escribí, solamente lo uso:
defsign_uri(self,uri,parameters=None):# Without credentials, return unsigned URLifnotself._credentials:returnuriifisinstance(uri,unicode):uri=bytes(iri2uri(uri))print"uri:",urimethod="GET"credentials=self._credentialsconsumer=oauth.OAuthConsumer(credentials["consumer_key"],credentials["consumer_secret"])token=oauth.OAuthToken(credentials["token"],credentials["token_secret"])ifnotparameters:_,_,_,_,query,_=urlparse(uri)parameters=dict(cgi.parse_qsl(query))request=oauth.OAuthRequest.from_consumer_and_token(http_url=uri,http_method=method,parameters=parameters,oauth_consumer=consumer,token=token)sig_method=oauth.OAuthSignatureMethod_HMAC_SHA1()request.sign_request(sig_method,consumer,token)print"SIGNED:",repr(request.to_url())returnrequest.to_url()
¿Y cómo pedimos el estado de quota? Accediendo al punto de entrada https://one.ubuntu.com/api/quota/
con la autorización adecuada, se obtiene un diccionario JSON con el espacio total y el usado.
Acá hay una muestra de como hacerlo:
# This is on __init__self.nam=QtNetwork.QNetworkAccessManager(self,finished=self.reply_finished):::defget_quota(self):"""Launch quota info request."""uri=self.sign_uri(QUOTA_API)url=QtCore.QUrl()url.setEncodedUrl(uri)self.nam.get(QtNetwork.QNetworkRequest(url))
De nuevo: get_quota no devuelve la quota. Sólo lanza un pedido HTTP a los servers de
Ubuntu One, que (eventualmente) responden con los datos. No querés que tu app se quede
ahí trabada mientras tanto, por eso QNetworkAccessManager va a llamar a self.reply_finished
cuando tenga la respuesta:
¿Qué más queremos? ¿Qué tal notificación cuando cambia el status de SyncDaemon? Por ejemplo,
cuando la sincronización está al día, o cuando te desconecta. De nuevo, esas son señales
DBus a las que uno se conecta en __init__:
self.status_proxy=bus.get_object('com.ubuntuone.SyncDaemon','/status')self.status_iface=dbus.Interface(self.status_proxy,dbus_interface='com.ubuntuone.SyncDaemon.Status')self.status_iface.connect_to_signal('StatusChanged',self.status_changed)# Get the status as of right nowself._last_status=self.process_status(self.status_proxy.current_status())
la función process_status is código aburrido para convertir la info de status de syncdaemon en
una cosa legible como "Sync is up-to-date", así que guardamos eso en self._last_status y
actualizamos el menú.
¿Qué menú? ¡Un menú contextual de un QSystemTrayIcon! Lo que leyeron son las piezas principales
que se necesitan para crear algo útil: una aplicación de SystemTray para Ubuntu One que se puede usar
en KDE, XFCE u Openbox. O, si estás en unity y tenés sni-qt instalado, un app indicator.
opt_parser=OptionParser()opt_parser.add_option("-q",dest="quickly",action="store_true",help="Do it quickly (default=False)")(options,args)=opt_parser.parse_args(sys.argv)app=QApplication(sys.argv):::
O tal vez incluso QApplication([]). Bueno, eso está mal. Y está mal en casi todos los tutoriales, también. ¿Porqué? Porque Qt (y por lo tanto PyQt) soporta un montón de opciones útiles. Al hacerlo como en ese primer listado, si le pasás "-style=oxygen" o lo que sea, va a pasar alguna de estas cosas:
OptParser te va a decir que es una opción inválida y abortar
Vas a ignorar la opción y no vas a hacer nada útil con ella
Vas a tener tu propia opción -style y vas a hacer dos cosas
Ninguna de esas opciones es la idea. La manera correcta de hacerlo es ésta:
opt_parser=OptionParser()opt_parser.add_option("-q",dest="quickly",action="store_true",help="Do it quickly (default=False)")app=QApplication(sys.argv)(options,args)=opt_parser.parse_args(app.arguments()):::
De esta manera, le das a PyQt la oportunidad de procesar las opciones que reconoce y después, vos manejás el resto, porque a app.arguments() ya le sacaron todas las opciones de Qt.
El lado malo es que --help va a ser mas lento, porque tiene que instanciar QApplication al divino botón, y vas a tener opciones no
documentadas. Soluciones para ambos problemas se dejan como ejercicio.
Por otro lado, escribí una serie muy popular de posts, llamada "PyQt en Ejemplos", que (adivinen) lleva mucho tiempo estancada.
El problema con el libro es que traté de cubrir demasiado terreno. Terminado sería un libro de 500 páginas, y eso incluye escribir media docena de apps de ejemplo, algunas de ellas en áreas en las que no soy experto.
El problema principal con los posts es que el ejemplo es pedorro (¡app de TODOs!) y expandirla es aburrido.
¡Qué mejor manera de resolver el problema que mezclar las dos cosas!
Voy a dejar Python No Muerde como está, y voy a hacer un libro nuevo, que se llame PyQt No Muerde. Va a mantener el tono y el lenguaje del anterior, y va a compartir varios capítulos, pero se va a enfocar en
desarrollar apps PyQt, en vez de apuntar a metas demasiado ambiciosas. Espero que sea de unas 200 páginas.
Tengo permiso de la superioridad (mi señora) para trabajar en esto un par de horas al día temprano a la mañana. Tal vez avance, tal vez no. Como siempre, yo no prometo, experimento.