Integrando Pilas a una Aplicación Qt¶
En esta sección vamos a mostrar como integrar Pilas como un widget dentro de tu aplicación desarrollada con PyQt.
Nota: En este capitulo asumimos que el programador ya conoce PyQt y Pilas.
Antes de empezar vamos a establecer algunos objetivos:
- Trataremos que la programación de la parte Pilas sea lo mas Pilas-Like.
- Pilas nos brinda un solo widget; por el objetivo anterior intentaremos mantener esto.
- La programación de la parte PyQt trataremos que se mantenga lo mas PyQt-Like.
Con esto en mente vamos a proponernos un proyecto:
Desarrollaremos una aplicación PyQt que muestre algunos actores en pantalla y al hacerle click sobre alguno nos permita seleccionar una imagen desde un archivo para reemplazar a la del actor.
La estructura de objetos que manejaremos sera la siguiente:

Donde el objetivo de cada clase es el siguiente:
MainWindow: Es un widget PyQt4 que hereda de PyQt4.QtGui.QMainWindow. Se encargara de recibir el evento de cuando un ActorVacio fue “clickeado” y mostrara la ventana emergente para seleccionar la imagen que luego sera asignada en el actor que lanzo el evento.
PilasProxy: Esta clase es un singleton que cada vez que es destruida finje su destrucción y solo limpia el widget principal de Pilas, para que cuando sea reutilizada, parezca que esta como nueva. Tendrá 3 métodos/propiedades imporantes implementara:
widget: Propiedad que referencia al widget principal de Pilas.
__getattr__: Método que delegara todas las llamadas que no posea el proxy al widget principal de Pilas.
destroy: Método que ocultara la implementación de destroy del widget principal de Pilas.
actor_clickeado: evento de pilas que enviara como parámetro el actor que fue clickeado.
- agregar_actor: permitirá agregar un actor al proxy y conectará
las señales del actor con la señal del proxy.
borrar_actor: borra un actor de los manejados por el proxy
ActorVacio: Subclase de pilas.actores.Actor que emitirá un evento al ser clickeada sobre si misma.
Código¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#===============================================================================
# IMPORTS
#===============================================================================
import sys
import os
import random
# importamos todos los modulos necesarios de PyQt
from PyQt4 import QtGui, QtCore
#===============================================================================
# CONFIGURACIÓN INICIAL
#===============================================================================
# antes de importar pilas creamos la app QT del programa
# si tenemos los componentes pilas en otro modulo puede llegar a ser conveniente
# importar ese modulo (el que usa pilas) dentro de un metodo de clase o una
# funcion. Tambien para que phonon no se queje es bueno setearle una nombre a
# nuestro QApplication
app = QtGui.QApplication(sys.argv[1:])
app.setApplicationName(__name__)
# Importamos pilas con un motor que sirva para embeber
# 'qt' y 'qtgl' crean y auto-arrancan la aplicacion
# mientras que 'qtsugar' y 'qtsugargl' solo crean
# los widget necesarios para embeber pilas
import pilas
pilas.iniciar(usar_motor="qtsugar")
#===============================================================================
# CLASE ACTOR
#===============================================================================
# nuestra clase actor
class ActorVacio(pilas.actores.Actor):
def __init__(self, *args, **kwargs):
super(ActorVacio, self).__init__(*args, **kwargs)
# El evento que emitiremos cuando clickean al actor
self.me_clickearon = pilas.evento.Evento("me_clickearon")
# Conectamos el evento genérico de click del mouse con un
# validador que se encargara de determinar si el click
# sucedió sobre el actor
pilas.eventos.click_de_mouse.conectar(self._validar_click)
def _validar_click(self, evt):
# extraemos las coordenadas donde sucedió el click
x, y = evt["x"], evt["y"]
# vemos si el actor colisiona con el punto donde
# se hizo click y de ser asi se lanza el evento
# me_clickearon pasando como parámetro al mismo
# actor
if self.colisiona_con_un_punto(x, y):
self.me_clickearon.emitir(actor=self)
#===============================================================================
# PROXY CONTRA PILAS
#===============================================================================
class PilasProxy(object):
# esta variable de clase guardara la única instancia que genera esta clase.
_instance = None
# redefinimos __new__ para que solo haya una instancia de pilas proxy
@staticmethod
def __new__(cls, *args, **kwargs):
if not PilasProxy._instance:
PilasProxy._instance = super(PilasProxy, cls).__new__(cls, *args, **kwargs)
return PilasProxy._instance
def __init__(self):
self._actores = set() # aca almacenaremos todos los actores
self.click_en_actor = pilas.evento.Evento("click_en_actor")
def __getattr__(self, k):
# todo lo que no pueda resolver la clase se lo delega al widget.
# Con esto el proxy puede ser usado trasparentemenente
return getattr(self.widget, k)
def agregar_actor(self, actor):
# Validamos que el actor sea un ActorVacio
assert isinstance(actor, ActorVacio)
# conectamos la señal del actor con la señal del proxy
actor.me_clickearon.conectar(
self._clickearon_actor
)
# agregamos el actor a la coleccion de actores
self._actores.add(actor)
def _clickearon_actor(self, evt):
# método que recibe a que actor clickearon y emite la señal
# de que clickearon al actor desde el proxy
self.click_en_actor.emitir(**evt)
def borrar_actor(self, actor):
if actor in self._actores:
# si el actor exist en los manejados por el proxy
# deconectamos las señales y destruimos el actor
actor.me_clickearon.desconectar(self.click_en_actor)
self._actores.remove(actor)
actor.destruir()
# prevenimos que al ejecutarse destroy sobre el widget subyacente
def destroy(self):
self.widget.setParent(None)
for act in self._actores:
self.borrar_actor(act)
@property
def widget(self):
return pilas.mundo.motor.ventana
#===============================================================================
# VENTANA PRINCIPAL
#===============================================================================
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow, self).__init__()
self.pilas = PilasProxy() # traemos nuestro proxy
self.setCentralWidget(self.pilas.widget) # lo agregamos a la ventana
self.resize(self.pilas.widget.size())
# creamos entre 5 y 10 actores
actores = ActorVacio() * random.randint(5,10)
for a in actores:
self.pilas.agregar_actor(a)
# conectamos el evento click en el actor
self.pilas.click_en_actor.conectar(self.on_actor_clickeado)
def on_actor_clickeado(self, evt):
# este slot va a abrir el selector de archivos de imagen
# y asignar esa imagen al actor que llego como parametro
actor = evt["actor"]
filename = QtGui.QFileDialog.getOpenFileName(
self, self.tr("Imagen de Actor"),
os.path.expanduser("~"),
self.tr("Imagenes (*.png *.jpg)")
)
if filename:
actor.imagen = pilas.imagenes.cargar_imagen(
unicode(filename)
)
#===============================================================================
# PONEMOS A CORRER TODO
#===============================================================================
win = MainWindow()
win.show()
sys.exit(app.exec_())