Efectos EspecialesRamblings, rants, musings, ideas and observations. Topics include (but are not limited to): programming (especially Python), books, games (especially CCGs and board games), astrology, design, writing, painting, etc.
To be honest, I liked my old blog better. Maybe not the design (although that snake was kinda cute :-), but so far, the new one doesn't seem to have much in the way of content. In other words, I need to write more. One of my goals for the new blog was to write when I have something to write about, rather than trying to write every day. It appears that that plan backfires... I end up not writing at all. So much for special effects.
I'm not sure if it means anything, but Tao of the Machine still has more Bloglines subscribers than Efectos Especiales. More than three times as much, in fact. Rumors of TotM's demise are greatly exaggerated?
[Update #1] I suspect that many people weren't aware they could leave comments, until I changed the label to the more obvious "N comments"... :-)
Refactoring can be a strange thing. You change code in ways that seemingly have nothing to do with the problem at hand... tear a long method apart, change names to something more meaningful, isolate code in a separate object. Then all of a sudden, you discover that the code has reached a state where solving your original problem is a piece of cake.
Another Wax release... I am trying to get to a stable core, which is easier said than done... I hear that wxPython 2.5.2 will revert some of the changes introduced in 22.214.171.124. Sigh.
Anyway, this is what's new since 0.2.15:
Menu.AppendMenureturn the MenuItem that was added.
- Added more functions to
- Added more global events (
OnResize(which is an alias for the former),
- Added events to
StyledTextBox. Found out that
EVT_STC_POSCHANGEDis deprecated. :-)
WaxConfig.default_fontcan now be a tuple with arguments for
Application.OnInitwill automatically convert this to a real
Fontinstance. See below.
sound.py, with a simple
Soundobject to play WAV files.
GridContainer.__getitem__now returns a control rather than a dict.
- Played with
MaskedTextBox, which might not make it into Wax, by the way.
toolsdirectory. Update your code if it uses this dialog.
Re Menu.Append: Getting the
MenuItem back seems useful, but note: When you click a menu item, then for some reason it's not possible to get the same
MenuItem back from the event. It's possible to get the *id* of the
MenuItem, and from there you can use
FindItemById, but this will give you a new instance of
MenuItem, rather than the one you created. I learned this the hard way, when I tried to add certain attributes to a menu item, and discovered they were gone later in the program.
It seems that wxPython creates a new instance every time you retrieve the
MenuItem. I don't know why. The id/address of the C++ object stays the same.
Re WaxConfig, fonts: Currently, it's possible to define fonts at the top of a file, before anything has been initialized yet. Like this:
WaxConfig.default_font = Font("Tahoma", 8) FIXED_WIDTH_1 = Font("Courier New", 10) ...later... app = Application(MainFrame, ...) app.Run()
Soon, this code will not be allowed anymore by wxPython/wxWidgets. Ditto for setting system-wide fonts in the WaxConfig object. The way I understand it, the reason is that the
wx "engine" needs to be started before any
wx objects can be created... this includes
wx.Fonts. I am not happy with this, but there's not much I can do. Except for allowing a similar construct. It's already possible (since 0.2.20) to do this:
WaxConfig.default_font = ("Courier New", 10)
I will add something like that for fonts too. In other words,
SetFont will allow a tuple as well as a
Font instance. It's a hack, but I don't want to give up this Very Useful Feature.
Hopefully wxPython 2.5.2 will offer more than just code breakage... a working version of
RegisterHotKey would be nice, for example. I have a program that uses it, and I'd like to release it. (Currently it uses an ugly hack that has the same effect... kind of. It's not working too well, sometimes it eats keys, etc.)
Grrr, it's really quite annoying if you're trying to keep your code stable and backward compatible, but you're dependent on a platform that has some developers who apparently don't care much about such goals. It gets a bit old when every major release breaks stuff.
The following code demonstrates how to add auto-indentation to a TextBox control. (Auto-indent is defined here as: when you press Enter, the new line will have the same indentation as the previous one.) There's one caveat: it only works in Wax 0.2.19, which is currently not released yet. That's why it's called a teaser. •1
However, since Wax is updated almost daily, a version where it will work should be available Real Soon Now.
So, without further ado, the code...
# editor-with-autoindent-1.py from wax import * class AutoIndentTextBox(TextBox): """ A TextBox with autoindent. When Enter is pressed, the next line will have the same indentation as the current line. """ def __init__(self, parent): TextBox.__init__(self, parent, multiline=1) def OnChar(self, event): if event.KeyCode() == keys.enter: # what's the indentation of the line we're in? indent = self.GetCurrentIndent() self.AppendText('\n') # indent the next line with the same number of spaces self.AppendText(indent) else: event.Skip() def GetCurrentIndent(self): """ Return the indent of the current line. This returns the actual indent *string*, e.g. a number of spaces, tabs, or a mix (hiss! boo!). """ lineno = self.GetCurrentLineNumber() line = self.GetLineText(lineno) indent = "" while line and line in " \t": indent = indent + line line = line[1:] return indent class MainFrame(Frame): def Body(self): self.editor = AutoIndentTextBox(self) self.editor.Size = (500, 400) self.editor.Font = Font("Courier New", 10) self.AddComponent(self.editor, expand=1, stretch=1) self.Pack() app = Application(MainFrame) app.Run()
Wax 0.2.15 is available in the usual place. New since 0.2.10:
WAX_VERSION_TUPLE, a very tiny but important addition.
- Added pseudo-properties.
- Changed the event system around, hopefully with minimal code breakage.
- Fixed a nasty bug in
WaxObject.__getattr__that caused problems in some programs. (My own, mostly. I don't think this bug was in 0.2.10.)
Re pseudo-properties: In short, if an object has methods
SetFoo, then a "property"
Foo can be used as a shorthand for these methods. For example:
print widget.Size # short for widget.GetSize() widget.Size = (200, 200) # short for widget.SetSize((200, 200))
These are not "real" properties like those introduced in Python 2.2. Rather, they are emulated with
Re event system: This is an important change that hopefully does not break too much code. (If it does, I'll be the first to notice, with my editor and blogging tool relying on Wax.)
The old system created a bunch of dummy methods for events. For example,
Button always had an
OnClick method, empty by default. There was also an
_OnClick method that functioned as a wrapper. This way worked, but had several drawbacks:
- There are lots of dummy methods that don't actually do anything, but might affect performance and/or memory usage. Even if no events are defined, those methods still exist, for no good reason.
- There are "global" events, that (almost) every control has, like mouseover events or keypresses. All controls had to define their own wrapper methods for these events, which seems bad programming practice.
The new system works differently:
BindEvents()method (which should be called upon instantation of a class) checks if a class has any methods with names like
OnFoo, and if this is the case, binds those methods to the appropriate event.
- Similarly, if a
WaxObjectinstance gets an attribute of the same form (
object.OnFoo = f, then
object.OnFoois bound to the appropriate event.
In other words, we can still define event methods like we used to:
class MyButton(Button): def OnClick(self, event): ... # will be bound to wx.EVT_BUTTON upon instantiation # or: b = Button() b.OnClick = f
There's currently no case for dynamically adding such methods to *classes*. So
Button.OnClick = f will not have the desired effect.
"Global" events are added to the new
events.py module. "Local" events (like
wx.EVT_BUTTON etc) are added to the
__events__ attribute of the appropriate class.
BindEvents() does the rest.
So, things changed behind the scenes, but the behavior should still be the same. It will be easier to add a number of methods now, as soon as I figure out what they do. :-)
Re PlainContainer etc: These are plain vanilla containers, that have no sizers associated with them. Instead, it is possible to add controls at a certain position:
p = PlainPanel(parent) b = Button(p, "Click me") p.AddComponent(10, 10, b) # add at position (10, 10) # add another widget at (30, 30) with size (150, 100): p.AddComponentAndSize(30, 30, 150, 100, widget)
(Come to think of it, maybe this should be
AddComponent(30, 30, widget, size=(150,100))?) Maybe so. Don't get too attached that
AddComponentAndSize method too much. :-)
I don't plan to use these containers much, especially not when writing cross-platform code, but I included them for completeness.
Since this weblog doesn't have real categories, Wax-related posts don't appear on a separate page anymore. You'll have to go through the torture of reading the whole blog. Apparently not everybody is aware of the changes yet. The "pyblagg" aggregator updated their links, but Planet Python currently still points to Tao of the Machine.
Like some other languages, Pop-11 definitely believes that there's more than one way to do it. Take function definitions, for example. The first way I learned in the Pop-11 primer was something like this:
: define foo(x) -> result; : x + 42 -> result; : enddefine; : : foo(4) => ** 46
The named result value is a nice touch, but sometimes feels a bit redundant. For example, what should its name be when we have a function named
area, to compute the area of a shape?
area as well? But that's already the name of the function, so you often end up using vague names like
result (and in fact this happens a few times in the tutorial).
Another way to define a function is more C-like:
: define foo(x); : return (x+42); : enddefine; : : foo(4) => ** 46
Note the parentheses around the return value... they're mandatory.
Pop-11 also allows for a Perl-like "shifting" of parameters passed to a function. For example:
: define bar(); : lvars x; : -> x; : pr('Parameter: '); pr(x); pr('\n'); : enddefine; : : bar(3); Parameter: 3 : bar('fred'); Parameter: fred
It somehow uses the open stack for this, and since a stack is last-in-first-out, calling function
bar with multiple parameters might yield surprising results:
: bar(1, 2, 3); Parameter: 3
At this point, I don't know yet how to get *all* the parameters passed. Of course, you can just use the "normal" way of defining arguments.
In my search for a language that is Different, Poplog seems promising. Actually, it's not a language, but a system that can be programmed in various languages... Pop-11, but also Lisp, Prolog and ML. One of its many interesting features is that code can be mixed between the languages. For example:
Sussex Poplog (Version 15.5 18/11/1996 12:12:27) Copyright (c) 1982-1996 University of Sussex. All rights reserved. Common Lisp (Version 2.0) Setlisp == (pop11) : define drizzle(x, y) -> result; : 2 * x + y -> result; : enddefine; : : clisp; == (pop11:drizzle 3 4) 10
Or mixing POP-11 and Prolog:
(C:\lang\Poplog-15.5\pop) $ pop11 +prolog Sussex Poplog (Version 15.5 18/11/1996 12:12:27) Copyright (c) 1982-1996 University of Sussex. All rights reserved. Prolog (Version 3.2) Setprolog ?- prolog_language(pop11). : /* now we are in pop11 */ : : define foo(a, b, c) -> result; : a * b + c -> result; : enddefine; : : :- prolog_language(prolog). | | foo(A, B, C, Result) :- | prolog_eval(foo(A, B, C), Result). | | ?- foo(1, 2, 3, X). X = 5 ? yes | ?- foo(3, 4, 5, X). X = 17 ? yes
All this is done from the interactive prompt, by the way, but of course it can be (and in fact, is) done in modules too.
Poplog is a strange, strange beast. It has a really "old" flavor to it, yet offers many things that are available in modern languages as well. (For example, years of Python programming has caused me to expect an interactive interpreter, and Poplog has one.) It kind of feels like a Perl gone right. POP-11 is a rich language, with features low-level enough that you can implement Lisp, ML and Prolog in it, yet with features high-level enough that you can use it instead of those languages. It has weird syntax quirks (
a + b -> c rather than
c = a + b), an open stack, first-class functions, strange operators, lists not unlike Lisp, an OO system based on Flavors, and much more. I love it.
<theorizing> Imagine if the open source version of Poplog could be made up to par with modern dynamic languages, e.g. by giving it a decent Windows version, a GUI, maybe an IDE, libraries for sockets, portable OS stuff, etc. You wouldn't need anything else anymore. Hack in POP-11, Lisp, Prolog, ML, or in a mixture of any of the above. Or implement your own language on top of it (Python?). Ah, one can always dream... </theorizing>
More later. In the meantime, here are some relevant links:
- POP11 Primer
- Information about POPLOG and POP11
- OpenPoplog at Sourceforge (haven't tried this yet)
- Free POPLOG/POP11 (Poplog for Windows)
- comp.lang.pop FAQ
- Poplog online documentation
- Help for the flavours library (including a tutorial)
- comp.lang.pop newsgroup
Apparently it's still possible to create an original game these days. Check out Gish. In short, you are a ball of tar (!) crawling through the sewers. Among other things, you can make yourself sticky to climb on things, or slick to slide through narrow holes, or heavy to break through floors. Very playable, very original.
I'd rather see them play like this and lose, than play like the previous game and draw...
Design and content © 2004 Electric Shock / Hans Nowak