pilas.motores.motor_qt

Package pilas :: Package motores :: Module motor_qt |
[hide private]
[frames] | no frames]

# Source Code for Module pilas.motores.motor_qt

       1  # -*- coding: utf-8 -*-
       2  # Pilas engine - A video game framework.
       3  #
       4  # Copyright 2010 - Hugo Ruscitti
       5  # License: LGPLv3 (see http://www.gnu.org/licenses/lgpl.html)
       6  #
       7  # Website - http://www.pilas-engine.com.ar
       8
       9  from PyQt4 import QtCore, QtGui
      10  from PyQt4.QtCore import Qt
      11  #from PyQt4.QtOpenGL import QGLWidget
      12  from PyQt4.QtGui import QWidget as QGLWidget
      13  from pilas import actores, colores, depurador, eventos, fps
      14  from pilas import imagenes, simbolos, utils
      15  import copy
      16  import os
      17  import pilas
      18  import sys
      19  import traceback
      20
      21
      22 -class Ventana(QtGui.QMainWindow):
      23
      24 -    def __init__(self, parent=None):
      25          QtGui.QMainWindow.__init__(self, parent)
      26          self.setStyleSheet("QWidget {background-color : #222}")
      27
      28 -    def set_canvas(self, canvas):
      29          self.canvas = canvas
      30          self.canvas.setParent(self)
      31
      32 -    def resizeEvent(self, event):
      33          self.canvas.resize_to(self.width(), self.height())
      34
      35
      36 -class CanvasWidget(QGLWidget):
      37
      38 -    def __init__(self, motor, lista_actores, ancho, alto, gestor_escenas, permitir_depuracion, rendimiento):
      39          QGLWidget.__init__(self, None)
      40          self.painter = QtGui.QPainter()
      41          self.setMouseTracking(True)
      42
      43          self.pausa_habilitada = False
      44          self.mouse_x = 0
      45          self.mouse_y = 0
      46          self.motor = motor
      47          self.lista_actores = lista_actores
      48          self.fps = fps.FPS(rendimiento, True)
      49
      50          if permitir_depuracion:
      51              self.depurador = depurador.Depurador(motor.obtener_lienzo(), self.fps)
      52          else:
      53              self.depurador = depurador.DepuradorDeshabilitado()
      54
      55          self.original_width = ancho
      56          self.original_height = alto
      57          self.escala = 1
      58          self.startTimer(1000/100.0)
      59
      60          self.gestor_escenas = gestor_escenas
      61
      62 -    def resize_to(self, w, h):
      63          escala_x = w / float(self.original_width)
      64          escala_y = h / float(self.original_height)
      65          escala = min(escala_x, escala_y)
      66
      67          final_w = self.original_width * escala
      68          final_h = self.original_height * escala
      69          self.escala = escala
      70
      71          x = w - final_w
      72          y = h - final_h
      73
      74          self.setGeometry(x/2, y/2, final_w, final_h)
      75
      76 -    def paintEvent(self, event):
      77          self.painter.begin(self)
      78          self.painter.scale(self.escala, self.escala)
      79
      80          self.painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True)
      81          self.painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)
      82          self.painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
      83
      84          self.painter.fillRect(0, 0, self.original_width, self.original_height, QtGui.QColor(128, 128, 128))
      85          self.depurador.comienza_dibujado(self.motor, self.painter)
      86
      87          actores_a_eliminar = []
      88
      89          if self.gestor_escenas.escena_actual():
      90              actores_de_la_escena = self.gestor_escenas.escena_actual().actores
      91              for actor in actores_de_la_escena:
      92                  #if actor._vivo:
      93                  try:
      94                      if not actor.esta_fuera_de_la_pantalla():
      95                          actor.dibujar(self.painter)
      96                  except Exception:
      97                      print traceback.format_exc()
      98                      print sys.exc_info()[0]
      99                      actor.eliminar()
     100
     101                  self.depurador.dibuja_al_actor(self.motor, self.painter, actor)
     102                  #else:
     103                  #    actores_a_eliminar.append(actor)
     104
     105                  #for x in actores_a_eliminar:
     106                  #    actores_de_la_escena.remove(x)
     107
     108          self.depurador.termina_dibujado(self.motor, self.painter)
     109          self.painter.end()
     110
     111 -    def timerEvent(self, event):
     112          try:
     113              self._realizar_actualizacion_logica()
     114          except Exception:
     115              print traceback.format_exc()
     116              print sys.exc_info()[0]
     117
     118          self.update()
     119
     120 -    def _realizar_actualizacion_logica(self):
     121          for x in range(self.fps.actualizar()):
     122              if not self.pausa_habilitada:
     123                  self._actualizar_eventos_y_actores()
     124                  self._actualizar_escena()
     125
     126 -    def _actualizar_escena(self):
     127          self.gestor_escenas.actualizar()
     128
     129 -    def _actualizar_eventos_y_actores(self):
     130          eventos.actualizar.emitir()
     131
     132          try:
     133              for actor in self.gestor_escenas.escena_actual().actores:
     134                  actor.pre_actualizar()
     135                  actor.actualizar()
     136          except Exception:
     137              print traceback.format_exc()
     138              print sys.exc_info()[0]
     139
     140 -    def mouseMoveEvent(self, e):
     141          escala = self.escala
     142          x, y = utils.convertir_de_posicion_fisica_relativa(e.pos().x()/escala, e.pos().y()/escala)
     143
     144          dx, dy = x - self.mouse_x, y - self.mouse_y
     145
     146          izquierda, derecha, arriba, abajo = utils.obtener_bordes()
     147
     148          #x = max(min(derecha, x), izquierda)
     149          #y = max(min(arriba, y), abajo)
     150
     151          x += pilas.mundo.motor.camara_x
     152          y += pilas.mundo.motor.camara_y
     153
     154          self.gestor_escenas.escena_actual().mueve_mouse.emitir(x=x, y=y, dx=dx, dy=dy)
     155
     156          self.mouse_x = x
     157          self.mouse_y = y
     158          self.depurador.cuando_mueve_el_mouse(x, y)
     159
     160 -    def keyPressEvent(self, event):
     161          codigo_de_tecla = self._obtener_codigo_de_tecla_normalizado(event.key())
     162
     163          # Se mantiene este lanzador de eventos por la clase Control
     164          if event.key() == QtCore.Qt.Key_Escape:
     165              eventos.pulsa_tecla_escape.emitir()
     166          if event.key() == QtCore.Qt.Key_P and event.modifiers() == QtCore.Qt.AltModifier:
     167              self.alternar_pausa()
     168          if event.key() == QtCore.Qt.Key_F and event.modifiers() == QtCore.Qt.AltModifier:
     169              self.alternar_pantalla_completa()
     170
     171          eventos.pulsa_tecla.emitir(codigo=codigo_de_tecla, es_repeticion=event.isAutoRepeat(), texto=event.text())
     172          self.depurador.cuando_pulsa_tecla(codigo_de_tecla, event.text())
     173
     174 -    def keyReleaseEvent(self, event):
     175          codigo_de_tecla = self._obtener_codigo_de_tecla_normalizado(event.key())
     176          # Se mantiene este lanzador de eventos por la clase Control
     177          eventos.suelta_tecla.emitir(codigo=codigo_de_tecla, es_repeticion=event.isAutoRepeat(), texto=event.text())
     178
     179 -    def wheelEvent(self, e):
     180          self.gestor_escenas.escena_actual().mueve_rueda.emitir(delta=e.delta() / 120)
     181
     182 -    def mousePressEvent(self, e):
     183          escala = self.escala
     184          x, y = utils.convertir_de_posicion_fisica_relativa(e.pos().x()/escala, e.pos().y()/escala)
     185          boton_pulsado = e.button()
     186
     187          x += pilas.mundo.motor.camara_x
     188          y += pilas.mundo.motor.camara_y
     189
     190          self.gestor_escenas.escena_actual().click_de_mouse.emitir(x=x, y=y, dx=0, dy=0, boton=boton_pulsado)
     191
     192 -    def mouseReleaseEvent(self, e):
     193          escala = self.escala
     194          x, y = utils.convertir_de_posicion_fisica_relativa(e.pos().x()/escala, e.pos().y()/escala)
     195          boton_pulsado = e.button()
     196
     197          x += pilas.mundo.motor.camara_x
     198          y += pilas.mundo.motor.camara_y
     199
     200          self.gestor_escenas.escena_actual().termina_click.emitir(x=x, y=y, dx=0, dy=0, boton=boton_pulsado)
     201
     202 -    def _obtener_codigo_de_tecla_normalizado(self, tecla_qt):
     203          teclas = {
     204              QtCore.Qt.Key_Left: simbolos.IZQUIERDA,
     205              QtCore.Qt.Key_Right: simbolos.DERECHA,
     206              QtCore.Qt.Key_Up: simbolos.ARRIBA,
     207              QtCore.Qt.Key_Down: simbolos.ABAJO,
     208              QtCore.Qt.Key_Space: simbolos.ESPACIO,
     209              QtCore.Qt.Key_Return: simbolos.SELECCION,
     210              QtCore.Qt.Key_F1: simbolos.F1,
     211              QtCore.Qt.Key_F2: simbolos.F2,
     212              QtCore.Qt.Key_F3: simbolos.F3,
     213              QtCore.Qt.Key_F4: simbolos.F4,
     214              QtCore.Qt.Key_F5: simbolos.F5,
     215              QtCore.Qt.Key_F6: simbolos.F6,
     216              QtCore.Qt.Key_F7: simbolos.F7,
     217              QtCore.Qt.Key_F8: simbolos.F8,
     218              QtCore.Qt.Key_F9: simbolos.F9,
     219              QtCore.Qt.Key_F10: simbolos.F10,
     220              QtCore.Qt.Key_F11: simbolos.F11,
     221              QtCore.Qt.Key_F12: simbolos.F12,
     222              QtCore.Qt.Key_A: simbolos.a,
     223              QtCore.Qt.Key_B: simbolos.b,
     224              QtCore.Qt.Key_C: simbolos.c,
     225              QtCore.Qt.Key_D: simbolos.d,
     226              QtCore.Qt.Key_E: simbolos.e,
     227              QtCore.Qt.Key_F: simbolos.f,
     228              QtCore.Qt.Key_G: simbolos.g,
     229              QtCore.Qt.Key_H: simbolos.h,
     230              QtCore.Qt.Key_I: simbolos.i,
     231              QtCore.Qt.Key_J: simbolos.j,
     232              QtCore.Qt.Key_K: simbolos.k,
     233              QtCore.Qt.Key_L: simbolos.l,
     234              QtCore.Qt.Key_M: simbolos.m,
     235              QtCore.Qt.Key_N: simbolos.n,
     236              QtCore.Qt.Key_O: simbolos.o,
     237              QtCore.Qt.Key_P: simbolos.p,
     238              QtCore.Qt.Key_Q: simbolos.q,
     239              QtCore.Qt.Key_R: simbolos.r,
     240              QtCore.Qt.Key_S: simbolos.s,
     241              QtCore.Qt.Key_T: simbolos.t,
     242              QtCore.Qt.Key_U: simbolos.u,
     243              QtCore.Qt.Key_V: simbolos.v,
     244              QtCore.Qt.Key_W: simbolos.w,
     245              QtCore.Qt.Key_X: simbolos.x,
     246              QtCore.Qt.Key_Y: simbolos.y,
     247              QtCore.Qt.Key_Z: simbolos.z,
     248          }
     249
     250          if teclas.has_key(tecla_qt):
     251              return teclas[tecla_qt]
     252          else:
     253              return tecla_qt
     254
     255 -    def pantalla_completa(self):
     256          self.motor.ventana.showFullScreen()
     257
     258 -    def pantalla_modo_ventana(self):
     259          self.motor.ventana.showNormal()
     260
     261 -    def esta_en_pantalla_completa(self):
     262          return self.motor.ventana.isFullScreen()
     263
     264 -    def alternar_pausa(self):
     265          if self.pausa_habilitada:
     266              self.pausa_habilitada = False
     267              self.actor_pausa.eliminar()
     268              eventos.pulsa_tecla.desconectar_por_id('tecla_en_pausa')
     269          else:
     270              self.pausa_habilitada = True
     271              self.actor_pausa = actores.Pausa()
     272              self.actor_pausa.fijo = True
     273              self.id_evento = eventos.pulsa_tecla.conectar(self.avanzar_un_solo_cuadro_de_animacion, id='tecla_en_pausa')
     274
     275 -    def avanzar_un_solo_cuadro_de_animacion(self, evento):
     276          self._actualizar_eventos_y_actores()
     277
     278 -    def alternar_pantalla_completa(self):
     279          """Permite cambiar el modo de video.
     280
     281          Si está en modo ventana, pasa a pantalla completa y viceversa.
     282          """
     283          if self.esta_en_pantalla_completa():
     284              self.pantalla_modo_ventana()
     285          else:
     286              self.pantalla_completa()
     287
     288 -class CanvasWidgetSugar(CanvasWidget):
     289
     290 -    def _iniciar_aplicacion(self):
     291          self.app = None
     292
     293 -    def ejecutar_bucle_principal(self, mundo, ignorar_errores):
     294          pass
     295
     296 -class BaseActor(object):
     297
     298 -    def __init__(self):
     299          self._rotacion = 0
     300          self._transparencia = 0
     301          self.centro_x = 0
     302          self.centro_y = 0
     303          self._escala_x = 1
     304          self._escala_y = 1
     305          self._espejado = False
     306          self.fijo = 0
     307
     308 -    def definir_centro(self, x, y):
     309          self.centro_x = x
     310          self.centro_y = y
     311
     312 -    def obtener_posicion(self):
     313          return self.x, self.y
     314
     315 -    def definir_posicion(self, x, y):
     316          self.x, self.y = x, y
     317
     318 -    def obtener_escala(self):
     319          return self._escala_x
     320
     321 -    def definir_escala(self, s):
     322          self._escala_x = s
     323          self._escala_y = s
     324
     325 -    def definir_escala_x(self, s):
     326          self._escala_x = s
     327
     328 -    def definir_escala_y(self, s):
     329          self._escala_y = s
     330
     331 -    def definir_transparencia(self, nuevo_valor):
     332          self._transparencia = nuevo_valor
     333
     334 -    def obtener_transparencia(self):
     335          return self._transparencia
     336
     337 -    def obtener_rotacion(self):
     338          return self._rotacion
     339
     340 -    def definir_rotacion(self, r):
     341          self._rotacion = r
     342
     343 -    def set_espejado(self, espejado):
     344          self._espejado = espejado
     345
     346
     347 -class Imagen(object):
     348
     349 -    def __init__(self, ruta):
     350          self.ruta_original = ruta
     351
     352          if ruta.lower().endswith("jpeg") or ruta.lower().endswith("jpg"):
     353              try:
     354                  self._imagen = self.cargar_jpeg(ruta)
     355              except:
     356                  self._imagen = QtGui.QPixmap(ruta)
     357          else:
     358              self._imagen = QtGui.QPixmap(ruta)
     359
     360 -    def cargar_jpeg(self, ruta):
     361          from PIL import Image
     362          import StringIO
     363
     364          pilImage = Image.open(ruta)
     365          stringIO = StringIO.StringIO()
     366          pilImage.save(stringIO, format="png")
     367
     368          pixmapImage = QtGui.QPixmap()
     369          pixmapImage.loadFromData(stringIO.getvalue())
     370
     371          return pixmapImage
     372
     373 -    def ancho(self):
     374          return self._imagen.size().width()
     375
     376 -    def alto(self):
     377          return self._imagen.size().height()
     378
     379 -    def centro(self):
     380          "Retorna una tupla con la coordenada del punto medio del la imagen."
     381          return (self.ancho()/2, self.alto()/2)
     382
     383 -    def avanzar(self):
     384          pass
     385
     386 -    def dibujar(self, painter, x, y, dx=0, dy=0, escala_x=1, escala_y=1, rotacion=0, transparencia=0):
     387          """Dibuja la imagen sobre la ventana que muestra el motor.
     388
     389             x, y: indican la posicion dentro del mundo.
     390             dx, dy: es el punto centro de la imagen (importante para rotaciones).
     391             escala_x, escala_yindican cambio de tamano (1 significa normal).
     392             rotacion: angulo de inclinacion en sentido de las agujas del reloj.
     393          """
     394
     395          painter.save()
     396          centro_x, centro_y = pilas.mundo.motor.centro_fisico()
     397          painter.translate(x + centro_x, centro_y - y)
     398          painter.rotate(rotacion)
     399          painter.scale(escala_x, escala_y)
     400
     401          if transparencia:
     402              painter.setOpacity(1 - transparencia/100.0)
     403
     404          self._dibujar_pixmap(painter, -dx, -dy)
     405          painter.restore()
     406
     407 -    def _dibujar_pixmap(self, painter, x, y):
     408          painter.drawPixmap(x, y, self._imagen)
     409
     410 -    def __str__(self):
     411          nombre_imagen = os.path.basename(self.ruta_original)
     412          return "<Imagen del archivo '%s'>" %(nombre_imagen)
     413
     414
     415 -class Grilla(Imagen):
     416
     417      """Representa una grilla regular, que se utiliza en animaciones.
     418
     419         La grilla regular se tiene que crear indicando la cantidad
     420         de filas y columnas. Una vez definida se puede usar como
     421         una imagen normal, solo que tiene dos metodos adicionales
     422         para ``definir_cuadro`` y ``avanzar`` el cuadro actual.
     423      """
     424
     425 -    def __init__(self, ruta, columnas=1, filas=1):
     426          Imagen.__init__(self, ruta)
     427          self.cantidad_de_cuadros = columnas * filas
     428          self.columnas = columnas
     429          self.filas = filas
     430          self.cuadro_ancho = Imagen.ancho(self) / columnas
     431          self.cuadro_alto = Imagen.alto(self) / filas
     432          self.definir_cuadro(0)
     433
     434 -    def ancho(self):
     435          return self.cuadro_ancho
     436
     437 -    def alto(self):
     438          return self.cuadro_alto
     439
     440 -    def _dibujar_pixmap(self, painter, x, y):
     441          painter.drawPixmap(x, y, self._imagen, self.dx, self.dy, self.cuadro_ancho, self.cuadro_alto)
     442
     443 -    def definir_cuadro(self, cuadro):
     444          self._cuadro = cuadro
     445
     446          frame_col = cuadro % self.columnas
     447          frame_row = cuadro / self.columnas
     448
     449          self.dx = frame_col * self.cuadro_ancho
     450          self.dy = frame_row * self.cuadro_alto
     451
     452 -    def avanzar(self):
     453          ha_avanzado = True
     454          cuadro_actual = self._cuadro + 1
     455
     456          if cuadro_actual >= self.cantidad_de_cuadros:
     457              cuadro_actual = 0
     458              ha_avanzado = False
     459
     460          self.definir_cuadro(cuadro_actual)
     461          return ha_avanzado
     462
     463 -    def obtener_cuadro(self):
     464          return self._cuadro
     465
     466 -    def dibujarse_sobre_una_pizarra(self, pizarra, x, y):
     467          pizarra.pintar_parte_de_imagen(self, self.dx, self.dy, self.cuadro_ancho, self.cuadro_alto, x, y)
     468
     469
     470 -class Texto(Imagen):
     471      CACHE_FUENTES = {}
     472
     473 -    def __init__(self, texto, magnitud, motor, vertical=False, fuente=None):
     474          self.vertical = vertical
     475          self.fuente = fuente
     476          self._ancho, self._alto = motor.obtener_area_de_texto(texto, magnitud, vertical, fuente)
     477
     478 -    def _dibujar_pixmap(self, painter, dx, dy):
     479          if self.fuente:
     480              nombre_de_fuente = Texto.cargar_fuente_desde_cache(self.fuente)
     481          else:
     482              nombre_de_fuente = painter.font().family()
     483
     484          fuente = QtGui.QFont(nombre_de_fuente, self.magnitud)
     485          metrica = QtGui.QFontMetrics(fuente)
     486
     487          r, g, b, a = self.color.obtener_componentes()
     488          painter.setPen(QtGui.QColor(r, g, b))
     489          painter.setFont(fuente)
     490
     491          if self.vertical:
     492              lines = [t for t in self.texto]
     493          else:
     494              lines = self.texto.split('\n')
     495
     496          for line in lines:
     497              painter.drawText(dx, dy + self._alto, line)
     498              dy += metrica.height()
     499
     500      @classmethod
     501 -    def cargar_fuente_desde_cache(kclass, fuente_como_ruta):
     502          """Carga o convierte una fuente para ser utilizada dentro del motor.
     503
     504          Permite a los usuarios referirse a las fuentes como ruta a archivos, sin
     505          tener que preocuparse por el font-family.
     506
     507          :param fuente_como_ruta: Ruta al archivo TTF que se quiere utilizar.
     508
     509          Ejemplo:
     510
     511              >>> Texto.cargar_fuente_desde_cache('myttffile.ttf')
     512              'Visitor TTF1'
     513          """
     514
     515          if not fuente_como_ruta in Texto.CACHE_FUENTES.keys():
     516              ruta_a_la_fuente = pilas.utils.obtener_ruta_al_recurso(fuente_como_ruta)
     517              fuente_id = QtGui.QFontDatabase.addApplicationFont(ruta_a_la_fuente)
     518              Texto.CACHE_FUENTES[fuente_como_ruta] = fuente_id
     519          else:
     520              fuente_id = Texto.CACHE_FUENTES[fuente_como_ruta]
     521
     522          return str(QtGui.QFontDatabase.applicationFontFamilies(fuente_id)[0])
     523
     524 -    def ancho(self):
     525          return self._ancho
     526
     527 -    def alto(self):
     528          return self._alto
     529
     530
     531 -class Lienzo(Imagen):
     532
     533 -    def __init__(self):
     534          pass
     535
     536 -    def texto(self, painter, cadena, x=0, y=0, magnitud=10, fuente=None, color=colores.negro):
     537          "Imprime un texto respespetando el desplazamiento de la camara."
     538          self.texto_absoluto(painter, cadena, x, y, magnitud, fuente, color)
     539
     540 -    def texto_absoluto(self, painter, cadena, x=0, y=0, magnitud=10, fuente=None, color=colores.negro):
     541          "Imprime un texto sin respetar al camara."
     542          x, y = utils.hacer_coordenada_pantalla_absoluta(x, y)
     543
     544          r, g, b, a = color.obtener_componentes()
     545          painter.setPen(QtGui.QColor(r, g, b))
     546
     547          if not fuente:
     548              fuente = painter.font().family()
     549
     550          painter.setFont(QtGui.QFont(fuente, magnitud))
     551          painter.drawText(x, y, cadena)
     552
     553 -    def pintar(self, painter, color):
     554          r, g, b, a = color.obtener_componentes()
     555          ancho, alto = pilas.mundo.motor.obtener_area()
     556          painter.fillRect(0, 0, ancho, alto, QtGui.QColor(r, g, b))
     557
     558 -    def linea(self, painter, x0, y0, x1, y1, color=colores.negro, grosor=1):
     559          x0, y0 = utils.hacer_coordenada_pantalla_absoluta(x0, y0)
     560          x1, y1 = utils.hacer_coordenada_pantalla_absoluta(x1, y1)
     561
     562          r, g, b, a = color.obtener_componentes()
     563          color = QtGui.QColor(r, g, b)
     564          pen = QtGui.QPen(color, grosor)
     565          painter.setPen(pen)
     566          painter.drawLine(x0, y0, x1, y1)
     567
     568 -    def poligono(self, motor, puntos, color=colores.negro, grosor=1, cerrado=False):
     569          x, y = puntos[0]
     570          if cerrado:
     571              puntos.append((x, y))
     572
     573          for p in puntos[1:]:
     574              nuevo_x, nuevo_y = p
     575              self.linea(motor, x, y, nuevo_x, nuevo_y, color, grosor)
     576              x, y = nuevo_x, nuevo_y
     577
     578 -    def cruz(self, painter, x, y, color=colores.negro, grosor=1):
     579          t = 3
     580          self.linea(painter, x - t, y - t, x + t, y + t, color, grosor)
     581          self.linea(painter, x + t, y - t, x - t, y + t, color, grosor)
     582
     583 -    def circulo(self, painter, x, y, radio, color=colores.negro, grosor=1):
     584          x, y = utils.hacer_coordenada_pantalla_absoluta(x, y)
     585
     586          r, g, b, a = color.obtener_componentes()
     587          color = QtGui.QColor(r, g, b)
     588          pen = QtGui.QPen(color, grosor)
     589          painter.setPen(pen)
     590          painter.drawEllipse(x -radio, y-radio, radio*2, radio*2)
     591
     592 -    def rectangulo(self, painter, x, y, ancho, alto, color=colores.negro, grosor=1):
     593          x, y = utils.hacer_coordenada_pantalla_absoluta(x, y)
     594
     595          r, g, b, a = color.obtener_componentes()
     596          color = QtGui.QColor(r, g, b)
     597          pen = QtGui.QPen(color, grosor)
     598          painter.setPen(pen)
     599          painter.drawRect(x, y, ancho, alto)
     600
     601
     602 -class Superficie(Imagen):
     603
     604 -    def __init__(self, ancho, alto):
     605          self._imagen = QtGui.QPixmap(ancho, alto)
     606          self._imagen.fill(QtGui.QColor(255, 255, 255, 0))
     607          self.canvas = QtGui.QPainter()
     608
     609 -    def pintar(self, color):
     610          r, g, b, a = color.obtener_componentes()
     611          self._imagen.fill(QtGui.QColor(r, g, b, a))
     612
     613 -    def pintar_parte_de_imagen(self, imagen, origen_x, origen_y, ancho, alto, x, y):
     614          self.canvas.begin(self._imagen)
     615          self.canvas.drawPixmap(x, y, imagen._imagen, origen_x, origen_y, ancho, alto)
     616          self.canvas.end()
     617
     618 -    def pintar_imagen(self, imagen, x=0, y=0):
     619          self.pintar_parte_de_imagen(imagen, 0, 0, imagen.ancho(), imagen.alto(), x, y)
     620
     621 -    def texto(self, cadena, x=0, y=0, magnitud=10, fuente=None, color=colores.negro):
     622          self.canvas.begin(self._imagen)
     623          r, g, b, a = color.obtener_componentes()
     624          self.canvas.setPen(QtGui.QColor(r, g, b))
     625          dx = x
     626          dy = y
     627
     628          if not fuente:
     629              fuente = self.canvas.font().family()
     630
     631          font = QtGui.QFont(fuente, magnitud)
     632          self.canvas.setFont(font)
     633          metrica = QtGui.QFontMetrics(font)
     634
     635          for line in cadena.split('\n'):
     636              self.canvas.drawText(dx, dy, line)
     637              dy += metrica.height()
     638
     639          self.canvas.end()
     640
     641 -    def circulo(self, x, y, radio, color=colores.negro, relleno=False, grosor=1):
     642          self.canvas.begin(self._imagen)
     643
     644          r, g, b, a = color.obtener_componentes()
     645          color = QtGui.QColor(r, g, b)
     646          pen = QtGui.QPen(color, grosor)
     647          self.canvas.setPen(pen)
     648
     649          if relleno:
     650              self.canvas.setBrush(color)
     651
     652          self.canvas.drawEllipse(x -radio, y-radio, radio*2, radio*2)
     653          self.canvas.end()
     654
     655 -    def rectangulo(self, x, y, ancho, alto, color=colores.negro, relleno=False, grosor=1):
     656          self.canvas.begin(self._imagen)
     657
     658          r, g, b, a = color.obtener_componentes()
     659          color = QtGui.QColor(r, g, b)
     660          pen = QtGui.QPen(color, grosor)
     661          self.canvas.setPen(pen)
     662
     663          if relleno:
     664              self.canvas.setBrush(color)
     665
     666          self.canvas.drawRect(x, y, ancho, alto)
     667          self.canvas.end()
     668
     669 -    def linea(self, x, y, x2, y2, color=colores.negro, grosor=1):
     670          self.canvas.begin(self._imagen)
     671
     672          r, g, b, a = color.obtener_componentes()
     673          color = QtGui.QColor(r, g, b)
     674          pen = QtGui.QPen(color, grosor)
     675          self.canvas.setPen(pen)
     676
     677          self.canvas.drawLine(x, y, x2, y2)
     678          self.canvas.end()
     679
     680 -    def poligono(self, puntos, color, grosor, cerrado=False):
     681          x, y = puntos[0]
     682
     683          if cerrado:
     684              puntos.append((x, y))
     685
     686          for p in puntos[1:]:
     687              nuevo_x, nuevo_y = p
     688              self.linea(x, y, nuevo_x, nuevo_y, color, grosor)
     689              x, y = nuevo_x, nuevo_y
     690
     691 -    def dibujar_punto(self, x, y, color=colores.negro):
     692          self.circulo(x, y, 3, color=color, relleno=True)
     693
     694 -    def limpiar(self):
     695          self._imagen.fill(QtGui.QColor(0, 0, 0, 0))
     696
     697
     698 -class Actor(BaseActor):
     699
     700 -    def __init__(self, imagen="sin_imagen.png", x=0, y=0):
     701
     702          if isinstance(imagen, str):
     703              self.imagen = imagenes.cargar(imagen)
     704          else:
     705              self.imagen = imagen
     706
     707          self.x = x
     708          self.y = y
     709          BaseActor.__init__(self)
     710
     711 -    def definir_imagen(self, imagen):
     712          # permite que varios actores usen la misma grilla.
     713          if isinstance(imagen, Grilla):
     714              self.imagen = copy.copy(imagen)
     715          else:
     716              self.imagen = imagen
     717
     718 -    def obtener_imagen(self):
     719          return self.imagen
     720
     721 -    def dibujar(self, painter):
     722          escala_x, escala_y = self._escala_x, self._escala_y
     723
     724          if self._espejado:
     725              escala_x *= -1
     726
     727          if not self.fijo:
     728              x = self.x - pilas.mundo.motor.camara_x
     729              y = self.y - pilas.mundo.motor.camara_y
     730          else:
     731              x = self.x
     732              y = self.y
     733
     734          self.imagen.dibujar(painter, x, y, self.centro_x, self.centro_y,
     735                  escala_x, escala_y, self._rotacion, self._transparencia)
     736
     737
     738  ## Backend: audio deshabilitado
     739
     740 -class SonidoDeshabilitado:
     741      deshabilitado = True
     742
     743 -    def __init__(self, player, ruta):
     744          pass
     745
     746 -    def reproducir(self):
     747          pass
     748
     749 -    def detener(self):
     750          pass
     751
     752 -    def pausar(self):
     753          pass
     754
     755 -    def continuar(self):
     756          pass
     757
     758 -class MusicaDeshabilitada(SonidoDeshabilitado):
     759
     760 -    def __init__(self, media, ruta):
     761          SonidoDeshabilitado.__init__(self, media, ruta)
     762
     763  ## Backend: gstreamer
     764
     765 -class SonidoGST:
     766      deshabilitado = False
     767
     768 -    def __init__(self, player, ruta):
     769          self.ruta = ruta
     770          self.sonido = player
     771          self.sonido.set_property('uri','file://'+ruta)
     772
     773 -    def reproducir(self):
     774          import gst
     775          if not self.deshabilitado:
     776              self.sonido.set_state(gst.STATE_NULL)
     777              self.sonido.set_state(gst.STATE_PLAYING)
     778
     779 -    def detener(self):
     780          import gst
     781          self.sonido.set_state(gst.STATE_NULL)
     782
     783
     784 -class MusicaGST(SonidoGST):
     785
     786 -    def __init__(self, media, ruta):
     787          SonidoGST.__init__(self, media, ruta)
     788
     789  ## Backend: audio phonon
     790
     791 -class SonidoPhonon:
     792      deshabilitado = False
     793
     794 -    def __init__(self, media, ruta):
     795          from PyQt4 import phonon
     796          self.media = media
     797          self.ruta = ruta
     798
     799          self.source = phonon.Phonon.MediaSource(ruta)
     800          self.sonido = phonon.Phonon.createPlayer(phonon.Phonon.GameCategory, self.source)
     801
     802 -    def reproducir(self):
     803          if not self.deshabilitado:
     804              self.sonido.seek(0)
     805              self.sonido.play()
     806
     807 -    def detener(self):
     808          "Detiene el audio."
     809          self.sonido.stop()
     810
     811 -    def pausar(self):
     812          "Hace una pausa del audio."
     813          self.sonido.pause()
     814
     815 -    def continuar(self):
     816          "Continúa reproduciendo el audio."
     817          if not self.deshabilitado:
     818              self.sonido.play()
     819
     820
     821 -class MusicaPhonon(SonidoPhonon):
     822
     823 -    def __init__(self, media, ruta):
     824          SonidoPhonon.__init__(self, media, ruta)
     825
     826
     827 -class Motor(object):
     828      """Representa la ventana principal de pilas.
     829
     830      Esta clase construirá el objeto apuntado por el atributo
     831      ``pilas.motor``, asi que será el representante de todas
     832      las funcionalidades multimedia.
     833
     834      Internamente, este motor, tratará de usar OpenGl para acelerar
     835      el dibujado en pantalla si la tarjeta de video lo soporta.
     836      """
     837
     838 -    def __init__(self, usar_motor, permitir_depuracion, audio):
     839          if usar_motor not in ['qtwidget', 'qtsugar']:
     840              self._iniciar_aplicacion()
     841
     842          self.usar_motor = usar_motor
     843
     844          self.nombre = usar_motor
     845          self.permitir_depuracion = permitir_depuracion
     846
     847          self._inicializar_variables()
     848          self._inicializar_sistema_de_audio(audio)
     849
     850 -    def _iniciar_aplicacion(self):
     851          self.app = QtGui.QApplication([])
     852          self.app.setApplicationName("pilas")
     853
     854 -    def _inicializar_variables(self):
     855          self.camara_x = 0
     856          self.camara_y = 0
     857
     858 -    def _inicializar_sistema_de_audio(self, audio):
     859          sistemas_de_sonido = ['deshabilitado', 'phonon', 'gst']
     860
     861          if audio not in sistemas_de_sonido:
     862              error = "El sistema de audio '%s' es invalido" %(audio)
     863              sugerencia = ". Use alguno de los siguientes: %s" %(str(sistemas_de_sonido))
     864              raise Exception(error + sugerencia)
     865
     866          if audio == 'gst':
     867              try:
     868                  import gst
     869              except ImportError:
     870                  print "Nota: El sistema de audio gstreamer no está disponible, usando phonon en su lugar."
     871                  audio = 'phonon'
     872
     873          if audio == 'deshabilitado':
     874              self.player = None
     875              self.clase_sonido = SonidoDeshabilitado
     876              self.clase_musica = MusicaDeshabilitada
     877          elif audio == 'phonon':
     878              from PyQt4 import phonon
     879              self.media = phonon.Phonon.MediaObject()
     880              self.audio = phonon.Phonon.AudioOutput(phonon.Phonon.MusicCategory)
     881              self.path = phonon.Phonon.createPath(self.media, self.audio)
     882              self.player = self.media
     883              self.clase_sonido = SonidoPhonon
     884              self.clase_musica = MusicaPhonon
     885          elif audio == 'gst':
     886              import gst
     887              self.player = gst.element_factory_make("playbin", "player")
     888              self.clase_sonido = SonidoGST
     889              self.clase_musica = MusicaGST
     890
     891 -    def terminar(self):
     892          self.ventana.close()
     893
     894 -    def iniciar_ventana(self, ancho, alto, titulo, pantalla_completa, gestor_escenas, rendimiento):
     895          self.ventana = Ventana()
     896          self.ventana.resize(ancho, alto)
     897
     898          if self.usar_motor in ['qtwidget', 'qtsugar']:
     899              mostrar_ventana = False
     900              self.canvas = CanvasWidgetSugar(self, actores.todos, ancho, alto, gestor_escenas, self.permitir_depuracion, rendimiento)
     901          else:
     902              mostrar_ventana = True
     903              self.canvas = CanvasWidget(self, actores.todos, ancho, alto, gestor_escenas, self.permitir_depuracion, rendimiento)
     904
     905          self.ventana.set_canvas(self.canvas)
     906          self.canvas.setFocus()
     907
     908          self.ancho_original = ancho
     909          self.alto_original = alto
     910          self.titulo = titulo
     911          self.ventana.setWindowTitle(self.titulo)
     912
     913          if mostrar_ventana:
     914              self.ventana.show()
     915              self.ventana.raise_()
     916
     917          if pantalla_completa:
     918              self.canvas.pantalla_completa()
     919
     920 -    def modificar_ventana(self, ancho, alto, titulo, pantalla_completa):
     921          self.titulo = titulo
     922          self.ventana.setWindowTitle(self.titulo)
     923          self.canvas.original_width = ancho
     924          self.canvas.original_height = alto
     925          self.ventana.resize(ancho, alto)
     926
     927          if pantalla_completa:
     928              self.canvas.pantalla_completa()
     929          else:
     930              self.canvas.pantalla_modo_ventana()
     931
     932 -    def ocultar_puntero_del_mouse(self):
     933          self.canvas.setCursor(QtGui.QCursor(Qt.BlankCursor))
     934
     935 -    def mostrar_puntero_del_mouse(self):
     936          self.canvas.setCursor(QtGui.QCursor(Qt.ArrowCursor))
     937
     938 -    def ejecutar_bucle_principal(self, mundo, ignorar_errores):
     939          if getattr(self, 'app', None):
     940              sys.exit(self.app.exec_())
     941
     942 -    def definir_centro_de_la_camara(self, x, y):
     943          self.camara_x = x
     944          self.camara_y = y
     945
     946 -    def obtener_centro_de_la_camara(self):
     947          return (self.camara_x, self.camara_y)
     948
     949 -    def centro_fisico(self):
     950          "Centro de la ventana para situar el punto (0, 0)"
     951          return self.ancho_original/2, self.alto_original/2
     952
     953 -    def obtener_area(self):
     954          return (self.ancho_original, self.alto_original)
     955
     956 -    def obtener_area_de_texto(self, texto, magnitud=10, vertical=False, fuente=None):
     957          ancho = 0
     958          alto = 0
     959
     960          if fuente:
     961              nombre_de_fuente = Texto.cargar_fuente_desde_cache(fuente)
     962          else:
     963              nombre_de_fuente = ''
     964
     965          fuente = QtGui.QFont(nombre_de_fuente, magnitud)
     966          metrica = QtGui.QFontMetrics(fuente)
     967
     968          if vertical:
     969              lineas = [t for t in texto]
     970          else:
     971              lineas = texto.split('\n')
     972
     973          for linea in lineas:
     974              ancho = max(ancho, metrica.width(linea))
     975              alto += metrica.height()
     976
     977          return ancho, alto
     978
     979 -    def obtener_actor(self, imagen, x, y):
     980          return Actor(imagen, x, y)
     981
     982 -    def obtener_texto(self, texto, magnitud, vertical=False, fuente=None):
     983          return Texto(texto, magnitud, self, vertical, fuente)
     984
     985 -    def obtener_grilla(self, ruta, columnas, filas):
     986          return Grilla(ruta, columnas, filas)
     987
     988 -    def cargar_sonido(self, ruta):
     989          return self.clase_sonido(self.player, ruta)
     990
     991 -    def cargar_musica(self, ruta):
     992          return self.clase_musica(self.player, ruta)
     993
     994 -    def deshabilitar_sonido(self, estado=True):
     995          self.clase_sonido.deshabilitado = estado
     996
     997 -    def deshabilitar_musica(self, estado=True):
     998          self.clase_musica.deshabilitado = estado
     999
    1000 -    def cargar_imagen(self, ruta):
    1001          return Imagen(ruta)
    1002
    1003 -    def obtener_lienzo(self):
    1004          return Lienzo()
    1005
    1006 -    def obtener_superficie(self, ancho, alto):
    1007          return Superficie(ancho, alto)
    1008