Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre programming (publicaciones antiguas, página 60)

Marave 0.6 en la calle

Co­sas nue­va­s:

  • Re­s­al­­ta­­do de si­n­­ta­­xis

  • Plu­­gins

  • Bugs arre­­gla­­dos

  • Ani­­ma­­cio­­­nes más bo­­­ni­­tas

  • Li­m­­pie­­za de có­­­di­­go

Cap­tu­ra de pan­ta­lla gra­tui­ta:

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

Como implementar "reemplazar todos" en un QPlainTextEdit

Así es co­mo se im­ple­men­ta 'reem­pla­zar to­do­s' en un QPlain­TextE­dit (o un QTextE­di­t, pro­ba­ble­men­te) usan­do Py­Qt (es si­mi­lar pa­ra C++).

def doReplaceAll(self):
    # Replace all occurences without interaction

    # Here I am just getting the replacement data
    # from my UI so it will be different for you
    old=self.searchReplaceWidget.ui.text.text()
    new=self.searchReplaceWidget.ui.replaceWith.text()

    # Beginning of undo block
    cursor=self.editor.textCursor()
    cursor.beginEditBlock()

    # Use flags for case match
    flags=QtGui.QTextDocument.FindFlags()
    if self.searchReplaceWidget.ui.matchCase.isChecked():
        flags=flags|QtGui.QTextDocument.FindCaseSensitively

    # Replace all we can
    while True:
        # self.editor is the QPlainTextEdit
        r=self.editor.find(old,flags)
        if r:
            qc=self.editor.textCursor()
            if qc.hasSelection():
                qc.insertText(new)
        else:
            break

    # Mark end of undo block
    cursor.endEditBlock()

Hay otras ma­ne­ras más fá­ci­le­s, pe­ro es­ta ha­ce que to­do apa­rez­ca en una so­la ope­ra­ción en la pi­la un­do­/­re­do y esas co­sas.


Contents © 2000-2024 Roberto Alsina