53 Commits

Author SHA1 Message Date
a9601a678a Untangle: fix a memory leak.
Spotted by Leak Sanitiser while re-testing after the previous commits:
the separately allocated 'crosses' array in game_state was never freed.
2024-09-27 13:29:44 +01:00
2ac951e70a Adapt Untangle into a graph editor.
This builds a secondary GUI program sharing most of the Untangle code,
similar to galaxieseditor. You can still drag points around as in the
actual Untangle game, but also you can right-drag to add or remove an
edge between two points. And the 'copy to clipboard' action generates
the Untangle game id corresponding to whatever you ended up with.

This could be used for hand-designing actual Untangle games, but my
more immediate use for it is as a convenient method of constructing
test cases for the new graph testing code.
2024-09-27 13:29:44 +01:00
c010ca122f Untangle: add cursor control interface.
The cursor keys navigate amongst the points. CURSOR_SELECT toggles dragging;
CURSOR_SELECT2 and the Tab key cycle through the points.

The cursor navigation scheme jumps to the nearest point within the quadrant
of the cursor direction; this seems to yield fairly intuitive gameplay.
Unfortunately, the "quadrant-nearest-neighbors" digraph produced by this
scheme is not necessarily fully reciprocal; that is, pressing opposite
cursor keys in sequence does not always return to the original point. There
doesn't seem to be any immediately obvious way around this.

As for connectivity (i.e. whether all points are reachable from any given
point), I could not find a counterexample, but I don't yet have a formal
proof that this is the case in general. Hence, I've added the ability to
cycle through all the points with Tab. (This will probably also be useful
in conjunction with the "Numbers" point drawing preference.)
2024-07-31 23:29:00 +01:00
cb8dcc34f7 Untangle: turn #define VERTEX_NUMBERS into a preference.
This compile-time definition switches the game into showing a distinct
non-negative integer for each vertex, instead of indistinguishable
blobs.

Its main use to me in the past has been when I'm trying to planarise
'real' graphs, that is, graphs I got from outside the game and wanted
a planar embedding of. Having made one in Untangle's UI I could then
read off which vertex was which.

That's an unusual use of the game, but _might_ be useful to someone
else. Perhaps a more interesting use of this feature would be to
direct someone else's play verbally - it would be much easier to tell
them which vertex to click on that way!
2023-11-18 13:56:42 +00:00
96d65e852c Untangle: turn #define SHOW_CROSSINGS into a preference.
I just found this #define in the Untangle source code, which I'd
completely forgotten was there. It causes each graph edge to be
highlighted in red if another edge crosses it, so that when you only
have a small number of crossings left to sort out, it's obvious where
they are.

Now we have a preferences system, there's no need to make this a
compile-time option! We can make it run-time selectable, for users who
want the extra help.
2023-11-14 12:59:44 +00:00
eeec6b867a Untangle: make snapping grid invariant under window resize
In grid-snapping mode, Untangle was still recording vertex positions
in increments of one pixel.  This meant that if you positioned
vertices on a small window, then enlarged the window and positioned
more vertices, the two sets of vertices generally wouldn't line up
with one another.  This was annoying, and obviously silly when
Untangle has a resolution-independent co-ordinate system.  So now the
snapped positions are recorded in a form that doesn't depend on the
tilesize.
2023-08-14 17:13:01 +01:00
be9e4f8957 Distinguish MOVE_UNUSED from MOVE_NO_EFFECT in Untangle 2023-08-13 16:44:24 +01:00
a9af3fda1d Rename UI_UPDATE as MOVE_UI_UPDATE
All the other constants named UI_* are special key names that can be
passed to midend_process_key(), but UI_UPDATE is a special return value
from the back-end interpret_move() function instead.  This renaming
makes the distinction clear and provides a naming convention for future
special return values from interpret_move().
2023-06-11 00:33:27 +01:00
e0bb6d3b85 Untangle: add a 'snap to grid' user preference.
Requested by a user who otherwise found themself spending too much
time struggling to get lines nicely horizontal or vertical.

The implementation is easy, but the question is what size of grid is
appropriate. Untangle's own generated games are constructed by making
a planar graph drawn on an extremely coarse grid - but snapping to
_that_ grid would give away information about the puzzle solution, and
also, Untangle wouldn't know any similar information about graphs
generated by any other method.

So a better approach is to choose a size of grid that guarantees that
_any_ graph with n vertices can be drawn on it with nonintersecting
straight edges. That sounds like a tricky maths problem - but happily,
the solution is given in a book I already had a copy of. References in
a comment (plus a proof of a pedantic followup detail about multiple
planar embeddings).
2023-05-01 14:30:54 +01:00
628cc6785b Untangle: replace manual int64 bodging with int64_t.
Where possible, that is. If our compilation environment has provided
int64_t, we can just make our int64 type be that, and not have to mess
around with multi-word arithmetic at all.
2023-05-01 14:30:54 +01:00
0058331aeb New backend functions: get_prefs and set_prefs.
These are similar to the existing pair configure() and custom_params()
in that get_prefs() returns an array of config_item describing a set
of dialog-box controls to present to the user, and set_prefs()
receives the same array with answers filled in and implements the
answers. But where configure() and custom_params() operate on a
game_params structure, the new pair operate on a game_ui, and are
intended to permit GUI configuration of all the settings I just moved
into that structure.

However, nothing actually _calls_ these routines yet. All I've done in
this commit is to add them to 'struct game' and implement them for the
functions that need them.

Also, config_item has new fields, permitting each config option to
define a machine-readable identifying keyword as well as the
user-facing description. For options of type C_CHOICES, each choice
also has a keyword. These keyword fields are only defined at all by
the new get_prefs() function - they're left uninitialised in existing
uses of the dialog system. The idea is to use them when writing out
the user's preferences into a configuration file on disk, although I
haven't actually done any of that work in this commit.
2023-04-23 13:25:06 +01:00
a4fca3286f Pass a game_ui to compute_size, print_size and print.
I'm about to move some of the bodgy getenv-based options so that they
become fields in game_ui. So these functions, which could previously
access those options directly via getenv, will now need to be given a
game_ui where they can look them up.
2023-04-21 16:18:04 +01:00
418cb3a567 Make encode_ui() and decode_ui() optional in back-ends
The majority of back-ends define encode_ui() to return NULL and
decode_ui() to do nothing.  This commit allows them to instead specify
the relevant function pointers as NULL, in which case the mid-end won't
try to call them.

I'm planning to add a parameter to decode_ui(), and if I'm going to have
to touch every back-end's version of decode_ui(), I may as well ensure
that most of them never need to be touched again.  And obviously
encode_ui() should go the same way for symmetry.
2023-04-08 20:08:16 +01:00
3b9cafa09f Fall back to <math.h> if <tgmath.h> doesn't work.
This fixes a build failure introduced by commit 2e48ce132e011e8
yesterday.

When I saw that commit I expected the most likely problem would be in
the NestedVM build, which is currently the thing with the most most
out-of-date C implementation. And indeed the NestedVM toolchain
doesn't have <tgmath.h> - but much more surprisingly, our _Windows_
builds failed too, with a compile error inside <tgmath.h> itself!

I haven't looked closely into the problem yet. Our Windows builds are
done with clang, which comes with its own <tgmath.h> superseding the
standard Windows one. So you'd _hope_ that clang could make sense of
its own header! But perhaps the problem is that this is an unusual
compile mode and hasn't been tested.

My fix is to simply add a cmake check for <tgmath.h> - which doesn't
just check the file's existence, it actually tries compiling a file
that #includes it, so it will detect 'file exists but is mysteriously
broken' just as easily as 'not there at all'. So this makes the builds
start working again, precisely on Ben's theory of opportunistically
using <tgmath.h> where possible and falling back to <math.h>
otherwise.

It looks ugly, though! I'm half tempted to make a new header file
whose job is to include a standard set of system headers, just so that
that nasty #ifdef doesn't have to sit at the top of almost all the
source files. But for the moment this at least gets the build working
again.
2023-04-06 07:08:04 +01:00
2e48ce132e Replace <math.h> with <tgmath.h> throughout
C89 provided only double-precision mathematical functions (sin() etc),
and so despite using single-precision elsewhere, those are what Puzzles
has traditionally used.  C99 introduced single-precision equivalents
(sinf() etc), and I hope it's been long enough that we can safely use
them.  Maybe they'll even be faster.

Rather than directly use the single-precision functions, though, we use
the magic macros from <tgmath.h> that automatically choose the precision
of mathematical functions based on their arguments.  This has the
advantage that we only need to change which header we include, and thus
that we can switch back again if some platform has trouble with the new
header.
2023-04-04 21:43:25 +01:00
26c7f3aa28 Miscellaneous const fixes
These are cases where -Wcast-qual complained and the only change needed
was to add or remove a "const" (or sometimes an entire cast).
2023-02-18 23:14:12 +00:00
19401e95e0 Don't leak duplicate edges in Untangle
Untangle game descriptions are allowed to contain duplicate edges, and
add234() can handle deduping them.  However, when add234() reports that
your newly-allocated edge is a duplicate, it's important to free it
again.
2023-02-13 11:08:14 +00:00
789e11f8f8 Remove various unused game functions
If can_configure is false, then the game's configure() and
custom_params() functions will never be called.  If can_solve is false,
solve() will never be called.  If can_format_as_text_ever is false,
can_format_as_text_now() and text_format() will never be called.  If
can_print is false, print_size() and print() will never be called.  If
is_timed is false, timing_state() will never be called.

In each case, almost all puzzles provided a function nonetheless.  I
think this is because in Puzzles' early history there was no "game"
structure, so the functions had to be present for linking to work.  But
now that everything indirects through the "game" structure, unused
functions can be left unimplemented and the corresponding pointers set
to NULL.

So now where the flags mentioned above are false, the corresponding
functions are omitted and the function pointers in the "game" structures
are NULL.
2023-01-31 23:25:05 +00:00
d3290195da Untangle: forbid descriptions that connect a node to itself
These cause an assertion failure in new_game(), so they should be
rejected by validate_desc().
2023-01-15 16:24:27 +00:00
d5b8a20def Last-ditch point-count limit for Untangle
Anything over INT_MAX/3 will cause an integer overflow, so put the
limit there for now.
2023-01-15 16:24:27 +00:00
a3310ab857 New backend function: current_key_label()
This provides a way for the front end to ask how a particular key should
be labelled right now (specifically, for a given game_state and
game_ui).  This is useful on feature phones where it's conventional to
put a small caption above each soft key indicating what it currently
does.

The function currently provides labels only for CURSOR_SELECT and
CURSOR_SELECT2.  This is because these are the only keys that need
labelling on KaiOS.

The concept of labelling keys also turns up in the request_keys() call,
but there are quite a few differences.  The labels returned by
current_key_label() are dynamic and likely to vary with each move, while
the labels provided by request_keys() are constant for a given
game_params.  Also, the keys returned by request_keys() don't generally
include CURSOR_SELECT and CURSOR_SELECT2, because those aren't necessary
on platforms with pointing devices.  It might be possible to provide a
unified API covering both of this, but I think it would be quite
difficult to work with.

Where a key is to be unlabelled, current_key_label() is expected to
return an empty string.  This leaves open the possibility of NULL
indicating a fallback to button2label or the label specified by
request_keys() in the future.

It's tempting to try to implement current_key_label() by calling
interpret_move() and parsing its output.  This doesn't work for two
reasons.  One is that interpret_move() is entitled to modify the
game_ui, and there isn't really a practical way to back those changes
out.  The other is that the information returned by interpret_move()
isn't sufficient to generate a label.  For instance, in many puzzles it
generates moves that toggle the state of a square, but we want the label
to reflect which state the square will be toggled to.  The result is
that I've generally ended up pulling bits of code from interpret_move()
and execute_move() together to implement current_key_label().

Alongside the back-end function, there's a midend_current_key_label()
that's a thin wrapper around the back-end function.  It just adds an
assertion about which key's being requested and a default null
implementation so that back-ends can avoid defining the function if it
will do nothing useful.
2022-12-09 20:48:30 +00:00
78bc9ea7f7 Add method for frontends to query the backend's cursor location.
The Rockbox frontend allows games to be displayed in a "zoomed-in"
state targets with small displays. Currently we use a modal interface
-- a "viewing" mode in which the cursor keys are used to pan around
the rendered bitmap; and an "interaction" mode that actually sends
keys to the game.

This commit adds a midend_get_cursor_location() function to allow the
frontend to retrieve the backend's cursor location or other "region of
interest" -- such as the player location in Cube or Inertia.

With this information, the Rockbox frontend can now intelligently
follow the cursor around in the zoomed-in state, eliminating the need
for a modal interface.
2020-12-07 19:40:06 +00:00
5f5b284c0b Use C99 bool within source modules.
This is the main bulk of this boolification work, but although it's
making the largest actual change, it should also be the least
disruptive to anyone interacting with this code base downstream of me,
because it doesn't modify any interface between modules: all the
inter-module APIs were updated one by one in the previous commits.
This just cleans up the code within each individual source file to use
bool in place of int where I think that makes things clearer.
2018-11-13 21:48:24 +00:00
a550ea0a47 Replace TRUE/FALSE with C99 true/false throughout.
This commit removes the old #defines of TRUE and FALSE from puzzles.h,
and does a mechanical search-and-replace throughout the code to
replace them with the C99 standard lowercase spellings.
2018-11-13 21:48:24 +00:00
a76d269cf2 Adopt C99 bool in the game backend API.
encode_params, validate_params and new_desc now take a bool parameter;
fetch_preset, can_format_as_text_now and timing_state all return bool;
and the data fields is_timed, wants_statusbar and can_* are all bool.
All of those were previously typed as int, but semantically boolean.

This commit changes the API declarations in puzzles.h, updates all the
games to match (including the unfinisheds), and updates the developer
docs as well.
2018-11-13 21:34:42 +00:00
60a929a250 Add a request_keys() function with a midend wrapper.
This function gives the front end a way to find out what keys the back
end requires; and as such it is mostly useful for ports without a
keyboard. It is based on changes originally found in Chris Boyle's
Android port, though some modifications were needed to make it more
flexible.
2018-04-22 17:04:50 +01:00
3276376d1b Assorted char * -> const char * API changes.
I went through all the char * parameters and return values I could see
in puzzles.h by eye and spotted ones that surely ought to have been
const all along.
2017-10-01 16:35:00 +01:00
b3243d7504 Return error messages as 'const char *', not 'char *'.
They're never dynamically allocated, and are almost always string
literals, so const is more appropriate.
2017-10-01 16:34:41 +01:00
de67801b0f Use a proper union in struct config_item.
This allows me to use different types for the mutable, dynamically
allocated string value in a C_STRING control and the fixed constant
list of option names in a C_CHOICES.
2017-10-01 16:34:41 +01:00
eeb2db283d New name UI_UPDATE for interpret_move's return "".
Now midend.c directly tests the returned pointer for equality to this
value, instead of checking whether it's the empty string.

A minor effect of this is that games may now return a dynamically
allocated empty string from interpret_move() and treat it as just
another legal move description. But I don't expect anyone to be
perverse enough to actually do that! The main purpose is that it
avoids returning a string literal from a function whose return type is
a pointer to _non-const_ char, i.e. we are now one step closer to
being able to make this code base clean under -Wwrite-strings.
2017-10-01 15:18:14 +01:00
a7dc17c425 Rework the preset menu system to permit submenus.
To do this, I've completely replaced the API between mid-end and front
end, so any downstream front end maintainers will have to do some
rewriting of their own (sorry). I've done the necessary work in all
five of the front ends I keep in-tree here - Windows, GTK, OS X,
Javascript/Emscripten, and Java/NestedVM - and I've done it in various
different styles (as each front end found most convenient), so that
should provide a variety of sample code to show downstreams how, if
they should need it.

I've left in the old puzzle back-end API function to return a flat
list of presets, so for the moment, all the puzzle backends are
unchanged apart from an extra null pointer appearing in their
top-level game structure. In a future commit I'll actually use the new
feature in a puzzle; perhaps in the further future it might make sense
to migrate all the puzzles to the new API and stop providing back ends
with two alternative ways of doing things, but this seemed like enough
upheaval for one day.
2017-04-26 21:51:23 +01:00
251b21c418 Giant const patch of doom: add a 'const' to every parameter in every
puzzle backend function which ought to have it, and propagate those
consts through to per-puzzle subroutines as needed.

I've recently had to do that to a few specific parameters which were
being misused by particular puzzles (r9657, r9830), which suggests
that it's probably a good idea to do the whole lot pre-emptively
before the next such problem shows up.

[originally from svn r9832]
[r9657 == 3b250baa02a7332510685948bf17576c397b8ceb]
[r9830 == 0b93de904a98f119b1a95d3a53029f1ed4bfb9b3]
2013-04-13 10:37:32 +00:00
0b93de904a Add 'const' to the game_params arguments in validate_desc and
new_desc. Oddities in the 'make test' output brought to my attention
that a few puzzles have been modifying their input game_params for
various reasons; they shouldn't do that, because that's the
game_params held permanently by the midend and it will affect
subsequent game generations if they modify it. So now those arguments
are const, and all the games which previously modified their
game_params now take a copy and modify that instead.

[originally from svn r9830]
2013-04-12 17:11:49 +00:00
8cfcbac027 Split Untangle's background colour into two. COL_BACKGROUND is still
defined the same way and used to draw the normal background of the
puzzle, but there's a new slightly darker COL_SYSBACKGROUND which is
not used for anything at all in the puzzle backend but which occupies
colour slot #0 so that the system will use it to fill around the
playable area if the puzzle is imprecisely resized.

This doesn't _sensibly fix_ the annoyance in Untangle where a non-
square window gives rise to unusable dead space, but it at least works
around it by making the dead space obviously something to stay away
from.

[originally from svn r9823]
2013-04-07 10:44:56 +00:00
3b250baa02 New rule: interpret_move() is passed a pointer to the game_drawstate
basically just so that it can divide mouse coordinates by the tile
size, but is definitely not expected to _write_ to it, and it hadn't
previously occurred to me that anyone might try. Therefore,
interpret_move() now gets a pointer to a _const_ game_drawstate
instead of a writable one.

All existing puzzles cope fine with this API change (as long as the
new const qualifier is also added to a couple of subfunctions to which
interpret_move delegates work), except for the just-committed Undead,
which somehow had ds->ascii and ui->ascii the wrong way round but is
otherwise unproblematic.

[originally from svn r9657]
2012-09-09 18:40:12 +00:00
73daff3937 Changed my mind about midend_is_solved: I've now reprototyped it as
midend_status(), and given it three return codes for win, (permanent)
loss and game-still-in-play. Depending on what the front end wants to
use it for, it may find any or all of these three states worth
distinguishing from each other.

(I suppose a further enhancement might be to add _non_-permanent loss
as a fourth distinct status, to describe situations in which you can't
play further without pressing Undo but doing so is not completely
pointless. That might reasonably include dead-end situations in Same
Game and Pegs, and blown-self-up situations in Mines and Inertia.
However, I haven't done this at present.)

[originally from svn r9179]
2011-06-19 13:43:35 +00:00
980880be1f Add a function to every game backend which indicates whether a game
state is in a solved position, and a midend function wrapping it.

(Or, at least, a situation in which further play is pointless. The
point is, given that game state, would it be a good idea for a front
end that does that sort of thing to proactively provide the option to
start a fresh game?)

[originally from svn r9140]
2011-04-02 16:19:12 +00:00
1d661ec46b Patch from James H providing lots more paranoid casting. Also one
actual behaviour change: Untangle now permits dragging with the
right mouse button, which has exactly the same effect as it does
with the left. (Harmless on desktop platforms, but helpful when
"right-click" is achieved by press-and-hold; now the drag takes
place even if you hesitate first.)

[originally from svn r8177]
2008-09-13 18:29:20 +00:00
a7431c0b7c New infrastructure feature. Games are now permitted to be
_conditionally_ able to format the current puzzle as text to be sent
to the clipboard. For instance, if a game were to support playing on
a square grid and on other kinds of grid such as hexagonal, then it
might reasonably feel that only the former could be sensibly
rendered in ASCII art; so it can now arrange for the "Copy" menu
item to be greyed out depending on the game_params.

To do this I've introduced a new backend function
(can_format_as_text_now()), and renamed the existing static backend
field "can_format_as_text" to "can_format_as_text_ever". The latter
will cause compile errors for anyone maintaining a third-party front
end; if any such person is reading this, I apologise to them for the
inconvenience, but I did do it deliberately so that they'd know to
update their front end.

As yet, no checked-in game actually uses this feature; all current
games can still either copy always or copy never.

[originally from svn r8161]
2008-09-06 09:27:56 +00:00
7b1f7d3e01 HTML Help support for Puzzles, with the same kind of automatic
fallback behaviour as PuTTY's support.

[originally from svn r7009]
2006-12-24 15:56:47 +00:00
eb2013efc0 Cleanup: it was absolutely stupid for game_wants_statusbar() to be a
function, since it took no parameters by which to vary its decision,
and in any case it's hard to imagine a game which only
_conditionally_ wants a status bar. Changed it into a boolean data
field in the backend structure.

[originally from svn r6417]
2005-10-22 16:52:16 +00:00
40fcf516f4 Cleanup: remove the game_state parameter to game_colours(). No game
was actually using it, and also it wasn't being called again for
different game states or different game parameters, so it would have
been a mistake to depend on anything in that game state. Games are
now expected to commit in advance to a single fixed list of all the
colours they will ever need, which was the case in practice already
and simplifies any later port to a colour-poor platform. Also this
change has removed a lot of unnecessary faff from midend_colours().

[originally from svn r6416]
2005-10-22 16:44:38 +00:00
b7f192eea3 Cleanup: the `mouse_priorities' field in the back end has been a
more general-purpose flags word for some time now. Rename it to
`flags'.

[originally from svn r6414]
2005-10-22 16:35:23 +00:00
af59dcf685 Substantial infrastructure upheaval. I've separated the drawing API
as seen by the back ends from the one implemented by the front end,
and shoved a piece of middleware (drawing.c) in between to permit
interchange of multiple kinds of the latter. I've also added a
number of functions to the drawing API to permit printing as well as
on-screen drawing, and retired print.py in favour of integrated
printing done by means of that API.

The immediate visible change is that print.py is dead, and each
puzzle now does its own printing: where you would previously have
typed `print.py solo 2x3', you now type `solo --print 2x3' and it
should work in much the same way.

Advantages of the new mechanism available right now:
 - Map is now printable, because the new print function can make use
   of the output from the existing game ID decoder rather than me
   having to replicate all those fiddly algorithms in Python.
 - the new print functions can cope with non-initial game states,
   which means each puzzle supporting --print also supports
   --with-solutions.
 - there's also a --scale option permitting users to adjust the size
   of the printed puzzles.

Advantages which will be available at some point:
 - the new API should permit me to implement native printing
   mechanisms on Windows and OS X.

[originally from svn r6190]
2005-08-18 17:50:14 +00:00
c321a88408 Cleanups to completion flashes: all four of these games used to
redraw the whole window _every_ time game_redraw() was called during
a flash. Now they only redraw the whole window every time the
background colour actually changes. Thanks to James H for much of
the work.

[originally from svn r6166]
2005-08-05 17:17:23 +00:00
414330d9ad Cleanups from James H: a few missing statics, a precautionary cast
or two, a debugging fix, a couple of explicit initialisations of
variables that were previously read uninitialised, and a fix for a
whopping great big memory leak in Slant owing to me having
completely forgotten to write free_game().

[originally from svn r6159]
2005-08-03 12:44:51 +00:00
cc06b70328 The very core of cross() is capable of suffering integer overflow on
large puzzles. Resort to hand-hacked 64-bit arithmetic for doing dot
products; everything else remains in `long' for the moment.

(Ideally I'd auto-detect the presence of `long long' and use it in
place of my cheap plastic imitation where possible, but since I
currently don't have a configure mechanism that'll have to wait.)

[originally from svn r6137]
2005-07-24 10:39:33 +00:00
3b5711c6a5 Another diagnostic mode for Untangle: if compiled with
`-DSHOW_CROSSINGS', it will show each edge in red if it is crossed
by anything, and in black otherwise. Distracting and not
particularly useful during play, but occasionally handy for
debugging cross().

[originally from svn r6136]
2005-07-24 10:09:04 +00:00
bed038e17f The `Solve' operation now rotates and/or reflects the solution grid
to bring it as close as possible to the current game state. This
means that if you request `Solve' after solving a puzzle yourself,
with the intention of finding out how similar your solution is to
the program's, then you will mostly see the differences in _shape_
rather than those being masked by the fact that yours happened to be
the other way up.

[originally from svn r6126]
2005-07-22 11:55:50 +00:00
1ff15ba8ad The Untangle completion flash was weedy and anaemic; beef it up a
bit. In particular, it now flashes between _two_ specially picked
colours (white and mid-grey), meaning that it should be visible even
if your default background colour is white; and it also flashes
twice rather than once.

[originally from svn r6121]
2005-07-20 11:05:35 +00:00