Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre qt

Qt Mac Tips

Los diá­lo­gos na­ti­vos no an­dan. Usan­do QFi­le­Dia­lo­g.­ge­tE­xis­tin­gDi­rec­to­ry no­ta­mos és­tos sín­to­ma­s:

  • Si no ha­­cés na­­da, el diá­­lo­­­go des­a­pa­­ra­­se por su cuen­­ta en más o me­­nos 20 se­­gun­­do­­s.

  • Des­­pués de usar­­lo una ve­­z, tal vez apa­­re­z­­ca y des­a­pa­­re­z­­ca in­­me­­dia­­ta­­men­­te. O no.

So­lu­ció­n: usar la op­ción Don­tU­seNa­ti­ve­Dia­log op­tio­n.

Los wi­dge­ts en un QTreeWi­dge­tI­te­ms no se mue­ven.

Cuan­do uno po­ne wi­dge­ts aden­tro de los íte­ms de un QTreeWi­dget (que no es muy co­mú­n, pe­ro a ve­ces es úti­l), los wi­dge­ts no se mue­ven jun­to con el íte­m.

Solución, usar la opción -gra­phi­css­ys­tem ras­ter. Hasta se la puede inyectar en argv si la plataforma es darwin.

The Future of PyQt by Example

Sys­tem Me­ss­age: ERRO­R/3 (<s­­tri­n­­g>, li­­ne 1)

Do­cu­ment or sec­tion may not be­gin wi­th a tran­si­tio­n.


Th­ree years ago, I started a se­ries of long pos­ts ca­lled "P­y­Qt by Exam­ple". It rea­ched fi­ve pos­ts be­fo­re I aban­do­ned for a se­ries of rea­sons that do­n't ma­tter an­y­mo­re. That se­ries is co­ming ba­ck star­ting next week, rew­ri­tten, im­pro­ved and ex­ten­de­d.

It wi­ll do so in a new si­te, and the "ol­d" pos­ts wi­ll be re­ti­red to an ar­chi­ve pa­ge. Wh­y? We­ll, the te­ch­no­lo­gies us­ed in so­me of them are ob­so­le­te or do­n't qui­te wo­rk no­wa­da­ys. So, the new ver­sions wi­ll be the pre­fe­rred ones.

And whi­le I am not pro­mi­sing an­y­thin­g, I ha­ve enou­gh wri­tten to make this so­me­thing qui­te lon­ge­r, mo­re ni­ce­ly la­youte­d, mo­re in­te­res­ting and make it co­ver mo­re groun­d. BU­T, whi­le doing so­me che­cks on the tra­ffic sta­tis­ti­cs for the old pos­ts, so­me things po­pped ou­t.

This was very popular

About 60% of my si­te's tra­ffic goes to tho­se fi­ve pos­ts. Out of about 1200 pos­ts over 12 year­s, 60% of the viewers go to the 0.4% of the pa­ges. That is a lo­t.

It's a long tail

The tra­ffic has not de­crea­sed in th­ree year­s. If an­y­thin­g, it has in­crea­sed

https://p.twimg.com/Aw0MHhoCAAAXmro.png:large

A long and ta­ll tai­l.

So, all this means the­re is a de­si­re for Py­Qt do­cu­men­ta­tion that is not sa­tis­fie­d. I am not sur­pri­s­e­d: Py­Qt is grea­t, and the re­co­m­men­ded book is not free, so the­re is bound to be a lot of de­man­d.

An­d, he­re's the no­t-­so­-­ro­sy bi­t: I had unob­tru­si­ve, re­le­van­t, ou­t-o­f-­the-wa­y-­bu­t-­vi­si­ble ads in tho­se pa­ges for mo­re than two year­s. Of the 70000 uni­que vi­si­tor­s, not even one cli­cked on an ad. Do­n't wo­rr­y, I was not ex­pec­ting to get mo­ney out of them (al­thou­gh I would lo­ve to so­me day co­llect a $100 che­ck ins­tead of ha­ving google hold my mo­ney for me ad eter­nu­m).

But rea­ll­y? Not even one ad cli­ck? In mo­re than two year­s, thou­san­ds of peo­ple? I ha­ve to won­der if I just attract cheap peo­ple ;-)

APIs de Ubuntu One en Ejemplos (parte 1)

Así que acá va un pe­que­ño tu­to­rial acer­ca de co­mo usar al­gu­nas de esas APIs. Lo hi­ce usan­do Py­thon y Py­Qt por va­rios mo­ti­vo­s:

  • Son ex­­ce­­len­­tes he­­rra­­mien­­tas pa­­ra pro­­­to­­­ti­­pos

  • Tie­­nen ex­­ce­­len­­te so­­­po­r­­te pa­­ra las co­­sas que ne­­ce­­si­­to (DBus, HTTP, OAu­­th)

  • Es lo que sé y me gus­­ta. Lo hi­­ce un do­­­mi­n­­go, no lo pien­­so ha­­cer en PHP y Gtk.

Di­cho eso, no hay na­da es­pe­cí­fi­co de py­thon o de Qt en es­te có­di­go. Don­de ha­go un re­quest HTTP usan­do QtNe­two­rk, po­dés usar lib­soup o lo que fue­re.

Va­ya­mos a los bi­fes en­ton­ce­s. Las pie­zas más im­por­tan­tes de Ubun­tu One, des­de el pun­to de vis­ta de in­fra­es­truc­tu­ra, son Ubun­tu SSO Clien­t, que se en­car­ga de lo­gi­n, re­gis­tra­ció­n, etc, y Syn­c­Dae­mos que ma­ne­ja la sin­cro­ni­za­ción de ar­chi­vo­s.

Pa­ra in­te­rac­tuar con ella­s, en Li­nu­x, ofre­cen in­ter­fa­ces DBus. Así que, por ejem­plo, es­te es un frag­men­to mos­tran­do co­mo ob­te­ner las cre­den­cia­les de Ubun­tu One (es­to nor­mal­men­te se­ría par­te del __i­ni­t__ de un ob­je­to­):

# Get the session bus
bus = dbus.SessionBus()

:
:
:

# Get the credentials proxy and interface
self.creds_proxy = bus.get_object("com.ubuntuone.Credentials",
                        "/credentials",
                        follow_name_owner_changes=True)

# Connect to signals so you get a call when something
# credential-related happens
self.creds_iface = dbus.Interface(self.creds_proxy,
    "com.ubuntuone.CredentialsManagement")
self.creds_proxy.connect_to_signal('CredentialsFound',
    self.creds_found)
self.creds_proxy.connect_to_signal('CredentialsNotFound',
    self.creds_not_found)
self.creds_proxy.connect_to_signal('CredentialsError',
    self.creds_error)

# Call for credentials
self._credentials = None
self.get_credentials()

Tal vez no­tas­te que ge­t_­cre­den­tials no de­vuel­ve las cre­den­cia­le­s. Lo que ha­ce es, le di­ce a Syn­c­Dae­mon que las ob­ten­ga, y en­ton­ce­s, si/­cuan­do apa­re­cen, se emi­te una de esas se­ña­le­s, y uno de los mé­to­dos co­nec­ta­dos se lla­ma. Es­to es­tá bue­no por­que no te­ne­mos que preo­cu­par­nos de que se nos blo­quee la apli­ca­ción mien­tras Syn­c­Dae­mon es­tá bus­can­do las cre­den­cia­le­s.

¿Y qué hay en esos mé­to­do­s? ¡No mu­cho!

def get_credentials(self):
    # Do we have them already? If not, get'em
    if not self._credentials:
        self.creds_proxy.find_credentials()
    # Return what we've got, could be None
    return self._credentials

def creds_found(self, data):
    # Received credentials, save them.
    print "creds_found", data
    self._credentials = data
    # Don't worry about get_quota yet ;-)
    if not self._quota_info:
        self.get_quota()

def creds_not_found(self, data):
    # No credentials, remove old ones.
    print "creds_not_found", data
    self._credentials = None

def creds_error(self, data):
    # No credentials, remove old ones.
    print "creds_error", data
    self._credentials = None

Así que bá­si­ca­men­te, se­l­f._­cre­den­tials con­tie­ne unas cre­den­cia­le­s, o No­ne. Fe­li­ci­ta­cio­nes, ya en­tra­mos a Ubun­tu One.

¡Ha­ga­mos al­go úti­l! ¿Que tal pre­gun­tar cuán­to es­pa­cio li­bre hay en la cuen­ta? Pa­ra eso, no po­de­mos usar las APIs lo­ca­le­s, si no co­nec­tar­nos a los ser­ver­s, que son los que sa­ben si es­tás ex­ce­di­do de quo­ta o no.

El ac­ce­so se con­tro­la via OAu­th, por lo que pa­ra ac­ce­der a esa API ne­ce­si­ta­mos fir­mar nues­tros pe­di­do­s. Aquí se ve co­mo se ha­ce. No es par­ti­cu­lar­men­te ilu­mi­na­do­r, yo no lo es­cri­bí, so­la­men­te lo uso:

def sign_uri(self, uri, parameters=None):
    # Without credentials, return unsigned URL
    if not self._credentials:
        return uri
    if isinstance(uri, unicode):
        uri = bytes(iri2uri(uri))
    print "uri:", uri
    method = "GET"
    credentials = self._credentials
    consumer = oauth.OAuthConsumer(credentials["consumer_key"],
                                   credentials["consumer_secret"])
    token = oauth.OAuthToken(credentials["token"],
                             credentials["token_secret"])
    if not parameters:
        _, _, _, _, query, _ = urlparse(uri)
        parameters = dict(cgi.parse_qsl(query))
    request = oauth.OAuthRequest.from_consumer_and_token(
                                        http_url=uri,
                                        http_method=method,
                                        parameters=parameters,
                                        oauth_consumer=consumer,
                                        token=token)
    sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
    request.sign_request(sig_method, consumer, token)
    print "SIGNED:", repr(request.to_url())
    return request.to_url()

¿Y có­mo pe­di­mos el es­ta­do de quo­ta? Ac­ce­dien­do al pun­to de en­tra­da http­s://o­ne.u­bun­tu.­co­m/a­pi/­quo­ta/ con la au­to­ri­za­ción ade­cua­da, se ob­tie­ne un dic­cio­na­rio JSON con el es­pa­cio to­tal y el usa­do. Acá hay una mues­tra de co­mo ha­cer­lo:

    # This is on __init__
    self.nam = QtNetwork.QNetworkAccessManager(self,
        finished=self.reply_finished)

:
:
:

def get_quota(self):
    """Launch quota info request."""
    uri = self.sign_uri(QUOTA_API)
    url = QtCore.QUrl()
    url.setEncodedUrl(uri)
    self.nam.get(QtNetwork.QNetworkRequest(url))

De nue­vo: ge­t_­quo­ta no de­vuel­ve la quo­ta. Só­lo lan­za un pe­di­do HTTP a los ser­vers de Ubun­tu One, que (e­ven­tual­men­te) res­pon­den con los da­to­s. No que­rés que tu app se que­de ahí tra­ba­da mien­tras tan­to, por eso QNe­two­rkAc­ce­ss­Ma­na­ger va a lla­mar a se­l­f.­re­pl­y_­fi­nis­hed cuan­do ten­ga la res­pues­ta:

def reply_finished(self, reply):
    if unicode(reply.url().path()) == u'/api/quota/':
        # Handle quota responses
        self._quota_info = json.loads(unicode(reply.readAll()))
        print "Got quota: ", self._quota_info
        # Again, don't worry about update_menu yet ;-)
        self.update_menu()

¿Qué más que­re­mo­s? ¿Qué tal no­ti­fi­ca­ción cuan­do cam­bia el sta­tus de Syn­c­Dae­mo­n? Por ejem­plo, cuan­do la sin­cro­ni­za­ción es­tá al día, o cuan­do te des­co­nec­ta. De nue­vo, esas son se­ña­les DBus a las que uno se co­nec­ta en __i­ni­t__:

self.status_proxy = bus.get_object(
    'com.ubuntuone.SyncDaemon', '/status')
self.status_iface = dbus.Interface(self.status_proxy,
    dbus_interface='com.ubuntuone.SyncDaemon.Status')
self.status_iface.connect_to_signal(
    'StatusChanged', self.status_changed)

# Get the status as of right now
self._last_status = self.process_status(
    self.status_proxy.current_status())

Y es­ta es sta­tus_­chan­ge­d:

def status_changed(self, status):
    print "New status:", status
    self._last_status = self.process_status(status)
    self.update_menu()

la fun­ción pro­ce­ss_s­ta­tus is có­di­go abu­rri­do pa­ra con­ver­tir la in­fo de sta­tus de syn­c­dae­mon en una co­sa le­gi­ble co­mo "S­ync is up-­to­-­da­te", así que guar­da­mos eso en se­l­f._­las­t_s­ta­tus y ac­tua­li­za­mos el me­nú.

¿Qué me­nú? ¡Un me­nú con­tex­tual de un QS­ys­te­m­Tra­yI­co­n! Lo que le­ye­ron son las pie­zas prin­ci­pa­les que se ne­ce­si­tan pa­ra crear al­go úti­l: una apli­ca­ción de Sys­te­m­Tray pa­ra Ubun­tu One que se pue­de usar en KDE, XFCE u Open­bo­x. O, si es­tás en uni­ty y te­nés sni-­qt ins­ta­la­do, un app in­di­ca­to­r.

http://ubuntuone.com/7iXTbysoMM9PIUS9Ai4TNn

El in­di­ca­dor en ac­ció­n.

El có­di­go fuen­te del ejem­plo com­ple­to es­tá en mi pro­yec­to u1-­to­ys en laun­ch­pad y és­te es el có­di­go fuen­te com­ple­to (ex­cep­to los ico­nos, ba­jen­se el re­po, me­jo­r)

Vi­nien­do pron­to (es­pe­ro­), más apps de ejem­plo, y co­sas co­pa­das que se pue­den ha­cer con nues­tras APIs.

PyQt Quickie: parsear línea de comandos

opt_parser = OptionParser()
opt_parser.add_option("-q", dest="quickly", action="store_true",
    help="Do it quickly (default=False)")
(options, args) = opt_parser.parse_args(sys.argv)
app = QApplication(sys.argv)
:
:
:

O tal vez incluso QA­ppli­ca­tio­n([]). Bueno, eso está mal. Y está mal en casi todos los tutoriales, también. ¿Porqué? Porque Qt (y por lo tanto PyQt) soporta un montón de opciones útiles. Al hacerlo como en ese primer listado, si le pasás "-style=oxygen" o lo que sea, va a pasar alguna de estas cosas:

  1. Op­­tPa­r­­ser te va a de­­cir que es una op­­ción in­­vá­­li­­da y abo­r­­tar

  2. Vas a ig­­no­­­rar la op­­ción y no vas a ha­­cer na­­da útil con ella

  3. Vas a te­­ner tu pro­­­pia op­­ción -s­­ty­­le y vas a ha­­cer dos co­­sas

Nin­gu­na de esas op­cio­nes es la idea. La ma­ne­ra co­rrec­ta de ha­cer­lo es és­ta:

opt_parser = OptionParser()
opt_parser.add_option("-q", dest="quickly", action="store_true",
    help="Do it quickly (default=False)")
app = QApplication(sys.argv)
(options, args) = opt_parser.parse_args(app.arguments())
:
:
:

De esta manera, le das a PyQt la oportunidad de procesar las opciones que reconoce y después, vos manejás el resto, porque a app.arguments() ya le sacaron todas las opciones de Qt.

El lado malo es que --help va a ser mas lento, porque tiene que instanciar QApplication al divino botón, y vas a tener opciones no documentadas. Soluciones para ambos problemas se dejan como ejercicio.

Escribir, y qué escribir.

Por otro la­do, es­cri­bí una se­rie muy po­pu­lar de pos­ts, lla­ma­da "P­y­Qt en Ejem­plo­s", que (a­di­vi­nen) lle­va mu­cho tiem­po es­tan­ca­da.

El pro­ble­ma con el li­bro es que tra­té de cu­brir de­ma­sia­do te­rreno. Ter­mi­na­do se­ría un li­bro de 500 pá­gi­na­s, y eso in­clu­ye es­cri­bir me­dia do­ce­na de apps de ejem­plo, al­gu­nas de ellas en áreas en las que no soy ex­per­to.

El pro­ble­ma prin­ci­pal con los pos­ts es que el ejem­plo es pe­do­rro (¡a­pp de TO­DO­s!) y ex­pan­dir­la es abu­rri­do.

¡Qué me­jor ma­ne­ra de re­sol­ver el pro­ble­ma que mez­clar las dos co­sas!

Voy a de­jar Py­thon No Muer­de co­mo es­tá, y voy a ha­cer un li­bro nue­vo, que se lla­me Py­Qt No Muer­de. Va a man­te­ner el tono y el len­gua­je del an­te­rio­r, y va a com­par­tir va­rios ca­pí­tu­lo­s, pe­ro se va a en­fo­car en de­sa­rro­llar apps Py­Q­t, en vez de apun­tar a me­tas de­ma­sia­do am­bi­cio­sas. Es­pe­ro que sea de unas 200 pá­gi­na­s.

Ten­go per­mi­so de la su­pe­rio­ri­dad (mi se­ño­ra) pa­ra tra­ba­jar en es­to un par de ho­ras al día tem­prano a la ma­ña­na. Tal vez avan­ce, tal vez no. Co­mo siem­pre, yo no pro­me­to, ex­pe­ri­men­to.


Contents © 2000-2020 Roberto Alsina