The Hunted
![]() |
|
![]() |
|
![]() |
|
Hacer un "fade in" de un widget
Setear una variable
Hacer un "fade in" de otro widget
Es importante hacerlo en ese orden y es importante que la aplicación siga respondiendo.
Acá está el código que usé (simplificado):
def fadein(thing, target=1., thendo=None):
"""
* thing is a QWidget
* thing.proxy is a QGraphicsWidget
* thendo is callable
* target is the desired opacity
"""
thing.anim=QtCore.QPropertyAnimation(thing.proxy, "opacity")
thing.anim.setDuration(200)
thing.anim.setStartValue(thing.proxy.opacity())
thing.anim.setEndValue(target)
thing.anim.start()
thing.anim.finished.connect(thing.anim.deleteLater)
if thendo:
thing.anim.finished.connect(thendo)
Y se usa así:
def later():
avar=avalue
fadein(widget2)
fadein(widget1, thendo=later)
¿No es lindo? Tener funciones como objetos de primera clase significa que puedo tomar later
como un closure, junto con widget2
y avar
que sólo necesitan estar definidas en el scope local, y la cadena de llamadas funciona ¡exactamente como quiero!
Sí, en muchos otros lenguajes se hace lo mismo, y en Javascript es un truco común... ¡pero PyQt es un wrapper de C++!
Me parece que este tipo de uso muestra el valor agregado que PyQt te da, no es solamente que con python evitás la compilación aburrida, o que tenés la increíble biblioteca estándar, sino que el lenguaje mismo te deja hacer cosas que no son prácticas en C++.
La única manera que se me ocurre de hacer esto en C++ es crear un slot que sea el equivalente de later
, y encadenarlo a la señal... lo que quiere decir que ese later
descartable se convierte en parte de la interface de la clase. (!?)
Habría que definir later
en algún otro lado del archivo, separado de su único uso (tal vez inine en el header).
Aún así, eso no es equivalente: avalue
podría ser algo no fácil de acceder cuando se ejecuta later
(por ejemplo, el timestamp del primer fadein), habría que buscar donde guardarlo para que later
lo encuentre, no se puede volver a hacer esto hasta después que se ejecute later
... se pone complicado.
A veces programar es como una cachetada... te das cuenta que cosas que usás sin pensar no son nada triviales.
Así que recuerda joven aprendiz: podés elegir las herramientas. Elegí con cuidado.
Entonces la solución, en la antigua tradición de Emacs y Vim es... hacerlo extensible.
Soy un gran fan de los programas extensibles por el usuario.
Así que... acá está la anatomía de un plugin de Marave tal como funciona ahora mismo en SVN trunk, lo que por supuesto puede cambiar en cualquier momento.
Sólo hay que crear un archivo .py en la carpeta plugins. Éste es el plugin más básico, que no hace nada:
# -*- coding: utf-8 -*-
from plugins import Plugin
class Smarty(Plugin):
name='smarty'
shortcut='Ctrl+.'
description='Smart quote and dash replacement'
mode="qBde"
Valores por default de algo configurable (en este caso "mode") se ponen en la clase.
Los campos obligatorios:
shortcut: un atajo de teclado que dispara este plugin
name: un nombre corto
description: una descripción de una línea
¿Qué hace esto? Agrega el plugin a la lista en el diálogo de preferencias, y se puede abrir el diálogo de configuración del plugin, donde se puede cambiar el shortcut:
Si se habilita este plugin, cuando el usuario use ese shortcut, se llama al método "run" del plugin.
Éste plugin soporta distintos modos de operación. Para hacer que esto sea accesible al usuario, hay que implementar unos pocos métodos mas.
El método addConfigWidgets toma como argumento un diálogo, y agrega lo que uno quiera ahí:
@classmethod
def addConfigWidgets(self, dialog):
print 'Adding widgets to smarty config'
l=dialog.ui.layout
self.q=QtGui.QCheckBox(dialog.tr('Replace normal quotes'))
if 'q' in self.mode:
self.q.setChecked(True)
self.b=QtGui.QCheckBox(dialog.tr('Replace backtick-style quotes (` and ``)'))
if 'B' in self.mode:
self.b.setChecked(True)
self.d=QtGui.QCheckBox(dialog.tr('Replace -- by en-dash, --- by em-dash'))
if 'd' in self.mode:
self.d.setChecked(True)
self.e=QtGui.QCheckBox(dialog.tr('Replace ellipses'))
if 'e' in self.mode:
self.e.setChecked(True)
l.addWidget(self.q)
l.addWidget(self.b)
l.addWidget(self.d)
l.addWidget(self.e)
Y entonces el diálogo de configuración se ve así:
También queremos que esas opciones se puedan guardar en algún lado, entonces reimplementamos saveConfig:
@classmethod
def saveConfig(self, dialog):
self.shortcut=unicode(dialog.ui.shortcut.text())
self.settings.setValue('plugin-'+self.name+'-shortcut', self.shortcut)
newmode=""
if self.q.isChecked():
newmode+='q'
if self.b.isChecked():
newmode+='B'
if self.d.isChecked():
newmode+='d'
if self.e.isChecked():
newmode+='e'
self.mode=newmode
self.settings.setValue('plugin-smarty-mode',self.mode)
self.settings.sync()
Y queremos que esas opciones se lean antes de crear el plugin, entonces:
@classmethod
def loadConfig(self):
print 'SMARTY loadconfig', self.settings
if self.settings:
sc=self.settings.value('plugin-'+self.name+'-shortcut')
if sc.isValid():
self.shortcut=unicode(sc.toString())
mode=self.settings.value('plugin-smarty-mode')
if mode.isValid():
self.mode=unicode(mode.toString())
Y sí, hay que hacer que sirva para algo. El plugin tiene acceso a un "client" que es la ventana principal de Marave. Todo está ahí, en alguna parte ;-)
def run(self):
print 'running smarty plugin'
text=unicode(self.client.editor.toPlainText()).splitlines()
prog=QtGui.QProgressDialog(self.client.tr("Applying smarty"),
self.client.tr("Cancel"),
0,len(text),
self.client)
prog.show()
output=[]
for i,l in enumerate(text):
output.append(unescape(smartyPants(l,self.mode)))
prog.setValue(i)
QtGui.QApplication.instance().processEvents()
prog.hide()
self.client.editor.setPlainText('\n'.join(output))
Y ya está, si se habilita el plugin smarty, se pueden "arreglar" las comillas, guiones y elipsis con una combinación de teclas :-)
Código fuente completo aquí: http://code.google.com/p/marave/source/browse/trunk/marave/plugins/smarty.py
Falta hacer: otras maneras de integrar plugins en la interface, botones, paneles, etc.