The Hunted
![]() |
|
![]() |
|
Version 0.6 of Marave, my peaceful, fullscreen text editor is now available at the usual place: http://marave.googlecode.com
New stuff:
Syntax highlighter
Plugins
Bugs fixed
Nicer animations
Code cleanup
Gratuitous screenshot:
![]() |
|
I had a small task today in Marave. The goal was:
Fade in a widget
Set a variable
Fade in another one
It's important that things are done in that order and it's also important that the app remains interactive.
And here's the code to do that (simplified):
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)
And this is how you use it:
def later():
avar=avalue
fadein(widget2)
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 other languages can do this, and in Javascript it's one of the most common tricks, but consider that PyQt is a wrapper for a C++ library!
I think this kind of usage shows the real added value PyQt brings to the table, it's not just that python avoids the boring compiles, or that you have the awesome standard library to use, but that the language itself enables you to do things that are not practical 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.
Programming is like a slap in the face sometimes... you realize that things you use without even noticing are far from trivial.
So, remember young padawan: you can choose you tools. Choose wisely.
Marave is a text editor. If there's one thing that's true of most text editors, it's this: they lack the exact features you need.
So, the solution, in the ancient tradition of Emacs and Vim is... make it extensible.
I am a big fan of programs that can be extended by users.
So... here's the anatomy of a Marave plugin as it stands right now on SVN trunk, which of course can change any minute.
You just need to create a .py file in the plugins folder.
Here's the most basic plugin, which does nothing:
# -*- coding: utf-8 -*-
from plugins import Plugin
class Smarty(Plugin):
name='smarty'
shortcut='Ctrl+.'
description='Smart quote and dash replacement'
mode="qBde"
Default values for anything configurable (in this case, "mode") is just added to the class.
The mandatory fields:
shortcut: a keyboard shortcut that triggers this plugin
name: a short name
description: a one-line description of what it does
What does it do? It adds the plugin to the plugin list in the prefs dialog, and you can open its configuration dialog, where you can change the shortcut:
If you enable this plugin, whenever the shortcut is used the "run" method of the plugin is called.
This plugin supports different modes of operation. To make this reachable to the user, you need to implement a few extra methods.
The addConfigWidgets method takes a dialog argument and adds whatever you want there:
@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)
And then the config dialog will look like this:
But then you need to save those options somewhere, which you do reimplementing saveConfig:
@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()
And you need to load those settings and put them in your class, too:
@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())
And yes, you need to make it do something useful. The plugin has access to a "client" which is Marave's main window. Everything is available there, somewhere ;-)
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))
And there it is, if you enable the smarty plugin, you can "fix" your quotes, dashes and ellipsis with a key combination :-)
Full source code here: http://code.google.com/p/marave/source/browse/trunk/marave/plugins/smarty.py
Still to be done: other ways to integrate plugins into the UI, buttons, panels, etc.