Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre open source (publicaciones antiguas, página 6)

El momento ajá!

  1. Ha­­cer un "fa­­de in" de un wi­­dget

  2. Se­­tear una va­­ria­­ble

  3. Ha­­cer un "fa­­de in" de otro wi­­dget

Es im­por­tan­te ha­cer­lo en ese or­den y es im­por­tan­te que la apli­ca­ción si­ga res­pon­dien­do.

Acá es­tá el có­di­go que usé (sim­pli­fi­ca­do­):

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 mu­chos otros len­gua­jes se ha­ce lo mis­mo, y en Ja­vas­cript es un tru­co co­mú­n... ¡pe­ro Py­Qt es un wra­pper de C++!

Me pa­re­ce que es­te ti­po de uso mues­tra el va­lor agre­ga­do que Py­Qt te da, no es so­la­men­te que con py­thon evi­tás la com­pi­la­ción abu­rri­da, o que te­nés la in­creí­ble bi­blio­te­ca es­tán­da­r, sino que el len­gua­je mis­mo te de­ja ha­cer co­sas que no son prác­ti­cas 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 ve­ces pro­gra­mar es co­mo una ca­che­ta­da... te das cuen­ta que co­sas que usás sin pen­sar no son na­da tri­via­le­s.

Así que re­cuer­da jo­ven apren­di­z: po­dés ele­gir las he­rra­mien­ta­s. Ele­gí con cui­da­do.

Extendiendo Marave

En­ton­ces la so­lu­ció­n, en la an­ti­gua tra­di­ción de Ema­cs y Vim es... ha­cer­lo ex­ten­si­ble.

Soy un gran fan de los pro­gra­mas ex­ten­si­bles por el usua­rio.

Así que... acá es­tá la ana­to­mía de un plu­gin de Ma­ra­ve tal co­mo fun­cio­na aho­ra mis­mo en SVN trunk, lo que por su­pues­to pue­de cam­biar en cual­quier mo­men­to.

Creando un plugin

Só­lo hay que crear un ar­chi­vo .py en la car­pe­ta plu­gin­s. És­te es el plu­gin más bá­si­co, que no ha­ce na­da:

# -*- coding: utf-8 -*-

from plugins import Plugin
class Smarty(Plugin):
    name='smarty'
    shortcut='Ctrl+.'
    description='Smart quote and dash replacement'
    mode="qBde"

Va­lo­res por de­fault de al­go con­fi­gu­ra­ble (en es­te ca­so "mo­de") se po­nen en la cla­se.

Los cam­pos obli­ga­to­rio­s:

  • sho­r­­tcu­­t: un ata­­jo de te­­cla­­do que dis­­pa­­ra es­­te plu­­gin

  • na­­me: un no­m­­bre co­r­­to

  • des­­cri­p­­tio­­n: una des­­cri­p­­ción de una lí­­nea

¿Qué ha­ce es­to? Agre­ga el plu­gin a la lis­ta en el diá­lo­go de pre­fe­ren­cia­s, y se pue­de abrir el diá­lo­go de con­fi­gu­ra­ción del plu­gi­n, don­de se pue­de cam­biar el shor­tcu­t:

maraveplugin1

Si se ha­bi­li­ta es­te plu­gi­n, cuan­do el usua­rio use ese shor­tcu­t, se lla­ma al mé­to­do "run" del plu­gi­n.

Haciéndolo Configurable

És­te plu­gin so­por­ta dis­tin­tos mo­dos de ope­ra­ció­n. Pa­ra ha­cer que es­to sea ac­ce­si­ble al usua­rio, hay que im­ple­men­tar unos po­cos mé­to­dos ma­s.

El mé­to­do addCon­fi­gWi­dge­ts to­ma co­mo ar­gu­men­to un diá­lo­go, y agre­ga lo que uno quie­ra 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 en­ton­ces el diá­lo­go de con­fi­gu­ra­ción se ve así:

maraveplugin2

Tam­bién que­re­mos que esas op­cio­nes se pue­dan guar­dar en al­gún la­do, en­ton­ces reim­ple­men­ta­mos save­Con­fi­g:

@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 que­re­mos que esas op­cio­nes se lean an­tes de crear el plu­gi­n, en­ton­ce­s:

@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())

Que haga algo!

Y sí, hay que ha­cer que sir­va pa­ra al­go. El plu­gin tie­ne ac­ce­so a un "clien­t" que es la ven­ta­na prin­ci­pal de Ma­ra­ve. To­do es­tá ahí, en al­gu­na par­te ;-)

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 es­tá, si se ha­bi­li­ta el plu­gin smar­ty, se pue­den "a­rre­gla­r" las co­mi­lla­s, guio­nes y elip­sis con una com­bi­na­ción de te­clas :-)

Có­di­go fuen­te com­ple­to aquí: http://­co­de.­google.­co­m/­p/­ma­ra­ve/­sour­ce/­bro­wse/­trunk/­ma­ra­ve/­plu­gin­s/s­mar­ty.­py

Fal­ta ha­ce­r: otras ma­ne­ras de in­te­grar plu­gins en la in­ter­fa­ce, bo­to­nes, pa­ne­le­s, etc.

Afeitando yaks: 16/2/2010

Ha­ce un tiem­po es­cri­bí acer­ca de co­mo im­ple­men­té un re­sal­ta­dor de sin­ta­xis ge­ne­ra­li­za­do pa­ra Py­Qt usan­do pyg­men­ts.

Re­ci­bí un pe­di­do de un fea­tu­re si­mi­lar en Ma­ra­ve, así que des­en­te­rré ese có­di­go y... no sir­ve pa­ra na­da. Es de­ma­sia­do len­to pa­ra un uso ra­zo­na­ble.

En­ton­ces a es­te yak ya le cre­ció de nue­vo to­do el pe­lo, y ¡jus­to ten­go es­te par de ti­je­ras nue­va­s!

La me­ta es lo­grar re­sal­tar sin­ta­xis en un QPlain­TextE­dit de for­ma que:

  • No re­­quie­­ra pro­­­gra­­mar pa­­ra aña­­dir un nue­­vo re­s­al­­ta­­do­­­r.

  • No re­­quie­­ra pro­­­gra­­mar pa­­ra aña­­dir un es­­que­­ma de co­­­lo­­­res.

  • No re­­quie­­ra que me pa­­se el 2010 es­­cri­­bien­­­do re­s­al­­ta­­do­­­res pa­­ra len­­gua­­jes exis­­ten­­tes.

  • Sea su­­fi­­cien­­te­­men­­te rá­­pi­­do

Una bús­que­da rá­pi­da en google mues­tra que pa­ra C++ se pue­de usar Sour­ce hi­gh­li­ght qt que es­tá ba­sa­do en GNU sour­ce hi­gh­li­ght.

Ob­via­men­te, no hay bin­ding py­thon que yo vea, así que... ¡me es­cri­bí uno!

Acá es­tá: http://­ma­ra­ve.­google­co­de.­co­m/s­vn/­trunk/­ma­ra­ve/hi­gh­li­gh­t/

Y és­ta es una cap­tu­ra del pro­gra­ma de de­mo co­rrien­do, mos­tran­do­se a sí mis­mo en­te­ro:

Se pue­de crear un es­que­ma de co­lo­res usan­do CSS, un len­gua­je se de­fi­ne con un ar­chi­vo de tex­to, hay una pi­la ya he­cho­s, y pa­re­ce lo bas­tan­te rá­pi­do.

En­ton­ces de­cla­ro a es­te yak afei­ta­do, y otro fea­tu­re (no ter­mi­na­do­!) pa­ra Ma­ra­ve

Marave 0.5 publicado

¡Has­ta in­clu­ye un pe­que­ño re­pro­duc­tor mu­si­cal pa­ra que no ten­gas que pen­sar en otra apli­ca­ció­n!

Es­ta ver­sión tie­ne va­rios bugs arre­gla­do­s, y se ve un po­co más bo­ni­ta.

El nue­vo fea­tu­re es... in­ter­na­cio­na­li­za­ció­n. Por aho­ra só­lo una tra­duc­ción al cas­te­lla­no, pe­ro si al­guien quie­re ayu­dar tra­du­cién­do­lo a otro idio­ma, por fa­vo­r, ade­lan­te!

Hay só­lo unas 60 fra­ses, por lo que no de­be­ría ser más de una ho­ra de tra­ba­jo.

Aquí hay una cap­tu­ra de pan­ta­lla de es­ta ver­sió­n:

.. raw:: ht­ml marave7

Ma­ra­ve es so­ftwa­re li­bre ba­jo la GPL­v2, y de­be­ría fun­cio­nar en cual­quier pla­ta­for­ma don­de fun­cio­ne Py­Q­t, lo que quie­re de­cir Ma­c, Win­do­ws, y sis­te­mas ope­ra­ti­vos ti­po unix, por lo me­no­s.

Empaquetar es DIFÍCIL

O ni si­quie­ra eso, quie­ro que la gen­te ten­ga la opor­tu­ni­dad de usar­lo.

Eso sig­ni­fi­ca que quie­ro que fun­cio­ne en Win­do­ws (y tal vez en OSX al­gún día, si al­guien me da una ma­no­). Lo que sig­ni­fi­ca que ten­go que ha­cer una ver­sión pa­ra Win­do­ws.

ha­ga­mos una com­pa­ra­ción rá­pi­da des­de el pun­to de vis­ta del usua­rio y del de­sa­rro­lla­do­r.

El usuario, en Linux

Es­to es en Ar­ch Li­nu­x, que es lo que yo uso, en otras va­rian­tes es más o me­nos lo mis­mo una vez que Ma­ra­ve sea mas co­no­ci­do.

yaourt -S marave-svn --noconfirm

Eso ob­tie­ne el có­di­go de SVN (por aho­ra es lo me­jo­r, más ade­lan­te em­pa­que­ta­ré re­lea­ses), to­das las de­pen­den­cia­s, y lo ins­ta­la. Tar­da unos 15 se­gun­dos en mi no­te­book.

Des­pués de eso, te­nés un Ma­ra­ve fun­cio­nan­do.

En ca­so de que no es­té en tu dis­tro, te­nés que ins­ta­lar Py­Qt (que se­gu­ro si es­tá) y co­rre­r:

easy_install marave

El usuario, en windows

Vas a http://­ma­ra­ve.­google­co­de.­com, cli­ck en "Ma­ra­ve-0.5.wi­n32.exe" (No lo bus­ques, to­da­vía no es­tá) ba­jás un pro­gra­ma de 10M­B. Eso es un pro­gra­ma de 10MB por­que win­do­ws no cree en pa­que­tes y en de­pen­den­cia­s. En Li­nux un pa­que­te de Ma­ra­ve se­ría 1MB (ca­si to­do imá­ge­nes), y se­ría da­to­s, no eje­cu­ta­ble.

Por su­pues­to hoy en día un bro­w­ser no te eje­cu­ta un pro­gra­ma que ba­jas­te, asi que... ha­ga­mos una ga­le­ría!

110111105613-My-Desktop

Sí, guar­da­r.

11011111220-My-Desktop

Do­ble cli­ck pa­ra abri­r.

11011111417-My-Desktop

Sí, es­toy de acuer­do.

11011111514-My-Desktop

Hm­m­m, bue­no.

1101111167-My-Desktop

Bár­ba­ro­...

11011111750-My-Desktop

Ge­nia­l!

Aho­ra es­te Ma­ra­ve pue­de fun­cio­nar o no pe­ro eso es pa­ra más ade­lan­te...

El desarrollador, en Linux

Pri­me­ro, es­te es el ma­yor pro­ble­ma un "em­pa­que­ta­do­r" pue­de te­ner en Li­nu­x:

Co­mo Ma­ra­ve es una apli­ca­ción nue­va, y la de­sa­rro­llo en Ar­ch Li­nux que es me­dio cu­tting edge, usa fea­tu­res que só­lo es­tán en ver­sio­nes nue­vas de Py­Q­t. De he­cho no fun­cio­na con Py­Qt < 4.6, que no es­tá en al­gu­nas dis­tros "len­ta­s" o en un Ubun­tu que no es el úl­ti­mo.

So­lu­ció­n? Bue­no, po­dría ig­no­rar­lo, pe­ro que tan­to, va­mos a arre­glar­lo!

Graias a PyIns­ta­ller ni si­quie­ra es tan di­fí­ci­l, es­te es el ar­chi­vo spe­c:

a = Analysis([os.path.join(HOMEPATH,'support/_mountzlib.py'), os.path.join(HOMEPATH,'support/useUnicode.py'), 'marave/main.py'],
            pathex=['/home/ralsina/trunk/trunk'])

pyz = PYZ(a.pure)
exe = EXE(pyz,
        a.scripts,
        exclude_binaries=1,
        name=os.path.join('build/pyi.linux2/main', 'marave.exe'),
        debug=False,
        strip=False,
        upx=True,
        console=0 )

coll = COLLECT( exe,
            a.binaries,
            [('radios.txt','marave/radios.txt','DATA')],
            Tree('marave/icons','icons'),
            Tree('marave/backgrounds','backgrounds'),
            Tree('marave/clicks','clicks'),
            Tree('marave/stylesheets','stylesheets'),
            Tree('marave/themes','themes'),
            a.zipfiles,
            a.datas,
            strip=False,
            upx=True,
            name=os.path.join('dist', 'marave'))

Usan­do es­to, PyIns­ta­ller pro­du­ce una lin­da car­pe­ta lle­na de to­do lo que Ma­ra­ve ne­ce­si­ta pa­ra fun­cio­nar en cual­quier Li­nu­x.

Por otro la­do, si se pue­de con­tar con que ha­ya un Py­Qt re­cien­te dis­po­ni­ble, tam­bién es fá­ci­l. És­te es el ar­chi­vo de con­fi­gu­ra­ción pa­ra un pa­que­te si­mi­lar en Ar­ch Li­nux (to­da­vía no hi­ce uno pa­ra Ma­ra­ve). Pa­ra otros Li­nux es más o me­nos lo mis­mo, y nor­mal­men­te al­guien te lo ha­ce:

# Contributor: Roberto Alsina <ralsina@kde.org>
pkgname=python-rst2pdf
pkgver=0.12.1
pkgrel=4
pkgdesc="Create PDFs from simple text markup, no LaTeX required."
arch=('i686' 'x86_64')
url="http://rst2pdf.googlecode.com"
license=('custom')
depends=('python' 'setuptools' 'docutils' 'pygments' 'python-reportlab' 'python-simplejson' 'pil')
source=(http://rst2pdf.googlecode.com/files/rst2pdf-$pkgver.tar.gz LICENSE.txt)
optdepends=('uniconvertor: vector images support'
            'python-svglib: SVG support'
            'python-wordaxe: hyphenation'
            'pythonmagick: PDF images support')
build() {
cd $startdir/src/rst2pdf-$pkgver
python setup.py install --root=$startdir/pkg || return 1
install -D ../LICENSE.txt $startdir/pkg/usr/share/licenses/python-rst2pdf/COPYING
install -D doc/rst2pdf.1 $startdir/pkg/usr/share/man/man1/rst2pdf.1
}
md5sums=('ea6beda9a46f34ba42c4c94d48cc607a'
        '416f8046c66b9476cdbacda69a673afe')

Y eso es to­do lo que hay que sa­ber del pro­ce­so de em­pa­que­tar tu apli­ca­ción pa­ra Li­nu­x, es fá­cil de ha­ce­r, y la ma­yor par­te del tiem­po, fá­cil de ha­cer bien.

Aho­ra, la sec­ción fi­na­l...

Windows para el desarrollador

¿Pri­me­ro, te acor­dás de eso de de­pen­der de la ver­sión de sis­te­ma de Qt? Ol­ví­da­lo, no hay ver­sión de sis­te­ma. Tam­po­co hay Py­tho­n, así que no im­por­ta. Y na­die los va a ins­ta­lar pa­ra tu apli­ca­ció­n, así que te­ne­mos que me­ter to­do no­so­tro­s, o na­da.

Pe­ro lo bue­no es que PyIns­ta­ller fun­cio­na pa­ra Win­do­ws! En­ton­ce­s, usan­do el mis­mo spec fun­cio­na, no?

Bue­no, hay dos pro­ble­ma­s...

Problema 1: El instalador

Los usua­rios de Win­do­ws no van a abrir un zip y co­nec­tar el bi­na­rio con el me­nú de ini­cio ni na­da pa­re­ci­do, así que hay que ha­cer un ins­ta­la­do­r.

Es­to es lo que hi­ce pa­ra NSIS, un crea­dor de ins­ta­la­do­res gra­tui­to:

;--------------------------------
;Include Modern UI

!include "MUI2.nsh"

;--------------------------------
;General

;Name and file
Name "Marave"
OutFile "Marave-0.5.win32.exe"

;Default installation folder
InstallDir "$LOCALAPPDATA\Marave"

;Get installation folder from registry if available
InstallDirRegKey HKCU "Software\Marave" ""

;Request application privileges for Windows Vista
RequestExecutionLevel user

;--------------------------------
;Interface Settings

!define MUI_ABORTWARNING

;--------------------------------
;Pages

!insertmacro MUI_PAGE_LICENSE "LICENSE"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES

!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES

;--------------------------------
;Languages

!insertmacro MUI_LANGUAGE "English"

;--------------------------------
;Installer Sections

Section "Install"

SetOutPath "$INSTDIR"
File /r "dist\marave"


;Store installation folder
WriteRegStr HKCU "Software\Marave" "" $INSTDIR

;Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"

;Create shortcuts
CreateDirectory $SMPROGRAMS\Marave
CreateShortCut "$SMPROGRAMS\Marave\Marave.lnk" "$INSTDIR\marave\marave.exe" ; use defaults for parameters, icon, etc.
CreateShortCut "$SMPROGRAMS\Marave\Uninstall Marave.lnk" "$INSTDIR\Uninstall.exe" ; use defaults for parameters, icon, etc.

SectionEnd


;--------------------------------
;Uninstaller Section

Section "Uninstall"

Delete "$INSTDIR\Uninstall.exe"
RMDir /r "$INSTDIR"

DeleteRegKey /ifempty HKCU "Software\Marave"

SectionEnd

Es com­pa­ra­ble al es­fuer­zo de ha­cer un arhi­vo de em­pa­que­ta­do, ex­cep­to que ca­da vez que lo que­rés pro­ba­r... lo ins­ta­lá­s. No hay ma­ne­ra (que yo vea) de sa­ber qué hay aden­tro del ins­ta­la­dor ex­cep­to co­rrer­lo.

Cuan­do las co­sas fa­llan, no hay men­sa­jes de erro­r, por lo me­nos no del ti­po que es útil pa­ra un de­sa­rro­lla­do­r, el que ne­ce­si­ta sa­ber que salió mal.

Des­pués de que ter­mi­na, tal vez no fun­cio­ne por­que...

Problema 2: bibliotecas de sistema. Ja!

Los bi­na­rios de Py­thon 2.6 es­tán com­pi­la­dos con Vi­sual Stu­dio. Eso quie­re de­cir que ne­ce­si­tan el Vi­sual Stu­dio Runti­me, es­pe­cí­fi­ca­men­te MS­V­CR90.D­LL. És­ta con­tie­ne co­sas que en Li­nux se­rían con­si­de­ra­do par­te de la li­bc (li­nu­xe­ro: ima­gi­na­te apli­ca­cio­nes que de­pen­den de una li­bc es­pe­cí­fi­ca... ¡no es fá­ci­l!)

En Li­nux eso es par­te del sis­te­ma. Más aú­n, si lo ne­ce­si­ta­s, lo re­dis­tri­buís. En Win­do­ws... es di­fe­ren­te.

  1. Es pa­r­­te del "Vi­­sual C++ re­­dis­­tri­­bu­­ta­­ble­s"

  2. In­s­­ta­­la­r­­lo no es ga­­ran­­tía de que an­­de (sí, lo pro­­­bé)

  3. La li­­cen­­cia de esos 're­­dis­­tri­­bu­­ta­­ble­s' di­­ce que no lo po­­­dés ha­­cer dis­­po­­­ni­­ble pa­­ra des­­ca­r­­ga.

    Me han di­­cho que in­­cluír­­lo en tu in­s­­ta­­la­­dor es le­­ga­­l, pe­­ro a mí me pa­­re­­ce que eso es ha­­ce­r­­lo dis­­po­­­ni­­ble pa­­ra des­­ca­r­­ga!

¿Qué se ha­ce cuan­do ne­ce­si­tás una bi­blio­te­ca, no la po­dés dis­tri­buir y el usua­rio no la va a ins­ta­la­r?

Bue­no, por al­go no hay bi­na­rios de Ma­ra­ve pa­ra Win­do­ws to­da­vía ;-) Por su­pues­to si al­guien lo pue­de re­sol­ve­r, me en­can­ta­ría!


Contents © 2000-2023 Roberto Alsina