Skip to main content

Ralsina.Me — Roberto Alsina's website

Posts about programming (old posts, page 60)

Marave 0.6 is out

Ver­sion 0.6 of Mar­ave, my peace­ful, fullscreen text ed­i­tor is now avail­able at the usu­al place: http://­mar­ave.­google­code.­com

New stuff:

  • Syn­­tax high­­­lighter

  • Plug­ins

  • Bugs fixed

  • Nicer an­i­­ma­­tions

  • Code cleanup

Gra­tu­itous screen­shot:

The aha! moment

I had a small task to­day in Mar­ave. The goal was:

  1. Fade in a wid­get

  2. Set a var­i­able

  3. Fade in an­oth­er one

It's im­por­tant that things are done in that or­der and it's al­so im­por­tant that the app re­mains in­ter­ac­tive.

And here's the code to do that (sim­pli­fied):

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")
    if thendo:

And this is how you use it:

def later():

fadein(widget1, thendo=later)

Isn't that lovely? Having functions as first class objects means I can just take later as a closure, along with widget2 and avar, which need only be defined in the local scope, and the call chain will work just as I wanted!

Yes, many oth­er lan­guages can do this, and in Javascript it's one of the most com­mon trick­s, but con­sid­er that PyQt is a wrap­per for a C++ li­brary!

I think this kind of us­age shows the re­al added val­ue PyQt brings to the table, it's not just that python avoids the bor­ing com­piles, or that you have the awe­some stan­dard li­brary to use, but that the lan­guage it­self en­ables you to do things that are not prac­ti­cal in C++.

In C++ the only way I can think of is creating a slot that's the equivalent of later, then chaining the signals... which means that this throwaway later becomes part of the interface of a class!

I would have to define later somewhere else on the file, separate from its only usage (maybe even inlined in the header file).

Even then, that's not equivalent: avalue may be something that was only avalable before the first call to fadein, (for example, the time of the first fadein): I would have to create a place to store it, make it reachable by later... and wht happens if you try to do this again while the first fadein is in progress?... it gets hairy.

Pro­gram­ming is like a slap in the face some­times... you re­al­ize that things you use with­out even notic­ing are far from triv­ial.

So, re­mem­ber young padawan: you can choose you tool­s. Choose wise­ly.

Extending Marave

Mar­ave is a text ed­i­tor. If there's one thing that's true of most text ed­i­tors, it's this: they lack the ex­act fea­tures you need.

So, the so­lu­tion, in the an­cient tra­di­tion of Emacs and Vim is... make it ex­ten­si­ble.

I am a big fan of pro­grams that can be ex­tend­ed by users.

So... here's the anato­my of a Mar­ave plug­in as it stands right now on SVN trunk, which of course can change any minute.

Creating a plugin

You just need to cre­ate a .py file in the plug­ins fold­er.

Here's the most ba­sic plug­in, which does noth­ing:

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

from plugins import Plugin
class Smarty(Plugin):
    description='Smart quote and dash replacement'

De­fault val­ues for any­thing con­fig­urable (in this case, "mod­e") is just added to the class.

The manda­to­ry field­s:

  • short­­­cut: a key­board short­­­cut that trig­gers this plug­in

  • name: a short name

  • de­scrip­­tion: a one-­­line de­scrip­­tion of what it does

What does it do? It adds the plug­in to the plug­in list in the prefs di­alog, and you can open its con­fig­u­ra­tion di­alog, where you can change the short­cut:


If you en­able this plug­in, when­ev­er the short­cut is used the "run" method of the plug­in is called.

Making the Plugin Configurable

This plug­in sup­ports dif­fer­ent modes of op­er­a­tion. To make this reach­able to the user, you need to im­ple­ment a few ex­tra meth­od­s.

The ad­d­Con­fig­Wid­gets method takes a di­a­log ar­gu­ment and adds what­ev­er you want there:

def addConfigWidgets(self, dialog):
    print 'Adding widgets to smarty config'
    self.q=QtGui.QCheckBox('Replace normal quotes'))
    if 'q' in self.mode:
    self.b=QtGui.QCheckBox('Replace backtick-style quotes (` and ``)'))
    if 'B' in self.mode:
    self.d=QtGui.QCheckBox('Replace -- by en-dash, --- by em-dash'))
    if 'd' in self.mode:
    self.e=QtGui.QCheckBox('Replace ellipses'))
    if 'e' in self.mode:

And then the con­fig di­a­log will look like this:


But then you need to save those op­tions some­where, which you do reim­ple­ment­ing save­Con­fig:

def saveConfig(self, dialog):

    self.settings.setValue('plugin-''-shortcut', self.shortcut)

    if self.q.isChecked():
    if self.b.isChecked():
    if self.d.isChecked():
    if self.e.isChecked():


And you need to load those set­tings and put them in your class, too:

def loadConfig(self):
    print 'SMARTY loadconfig', self.settings
    if self.settings:
        if sc.isValid():
        if mode.isValid():

Making it Work

And yes, you need to make it do some­thing use­ful. The plug­in has ac­cess to a "clien­t" which is Mar­ave's main win­dow. Ev­ery­thing is avail­able there, some­where ;-)

def run(self):
    print 'running smarty plugin'
    prog=QtGui.QProgressDialog("Applying smarty"),
    for i,l in enumerate(text):

And there it is, if you en­able the smar­ty plug­in, you can "fix" your quotes, dash­es and el­lip­sis with a key com­bi­na­tion :-)

Full source code here: http://­code.­google.­com/p/­mar­ave/­source/browse/trunk­/­mar­ave/­plu­g­in­s/s­mar­

Still to be done: oth­er ways to in­te­grate plug­ins in­to the UI, but­ton­s, pan­el­s, etc.

Yak Shavings for February 16, 2010

yak shaving

(id­iomat­ic) Any ap­par­ent­ly use­less ac­tiv­i­ty which, by al­low­ing you to over­come in­ter­me­di­ate dif­fi­cul­ties, al­lows you to solve a larg­er prob­lem.

A while ago, I wrote how I im­ple­ment­ed a gener­ic syn­tax high­lighter for PyQt us­ing Pyg­ments.

I got a re­quest for such a fea­ture in Mar­ave, so I digged that code and... it's freak­ing use­less. It's just too slow for rea­son­able use.

So, that yak's hair is all grown up again, and I just got this new pair of scis­sors!

The goal is a way to high­light syn­tax in a QPlain­TextE­d­it that:

  • Does­n't re­quire pro­­gram­ming to add a new high­­­lighter

  • Does­n't re­quire pro­­gram­ming to add a new col­or scheme

  • Does­n't re­quire me to spend a year writ­ing high­­­lighters for ex­ist­ing lan­guages

  • Is fast enough

A quick google shows that for C++ you can use Source high­light qt which is based on GNU source high­light.

Alas, no python bind­ing that I could find. So, let's write one!

Here it is: http://­mar­ave.­google­code.­com/svn/trunk­/­mar­ave/high­light/

And here's a screen­shot of the de­mo pro­gram run­ning, show­ing it­self in its en­tire­ty:

You can cre­ate a col­or scheme us­ing CSS, a lan­guage def­i­ni­tion is a text file, there are a bazil­lion al­ready writ­ten, it seems to be fast enough.

So, an­oth­er yak shaved, an­oth­er fea­ture (not fin­ished!) for Mar­ave

How to implement "replace all" in a QPlainTextEdit

This is not in­ter­est­ing for al­most noone, but since my google-­fu did­n't let me find it and it was a bit of a pain to do:

This is how you im­ple­ment 're­place al­l' in a QPlain­TextE­d­it (or a QTextE­d­it, for that mat­ter) us­ing PyQt (sim­i­lar for C++ of course).

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

    # Beginning of undo block

    # Use flags for case match
    if self.searchReplaceWidget.ui.matchCase.isChecked():

    # Replace all we can
    while True:
        # self.editor is the QPlainTextEdit
        if r:
            if qc.hasSelection():

    # Mark end of undo block

There are oth­er, eas­i­er ways to do it, but this one makes it all ap­pear as a sin­gle op­er­a­tion in the un­do stack and all that.

Contents © 2000-2023 Roberto Alsina