This is an export which looks like `export {} from "module"`, and
although it doesn't have any real export entries it should still add
"module" to the required modules to load.
When the value for a SpinBox equals the max, disable the increment
button. Functionally, clicking the button doesn't do anything because
the set_value() clamps the value to min/max and updates the textbox.
However it is still nice to indicate to the user that they've reached
the max. Same goes for minimum value and the decrement button.
Previously the value of the SpinBox is re-evaluated after every change
to the TextBox control. This leads to very unintuitive behavior such as
the user deleting the contents of the box and it having no
visible effect. This happens because the TextBox no longer has a valid
number and so gets reset to the current m_value of the SpinBox.
By defering the update of to the SpinBox value until focus leaves the
control we provide a much more intuitive experience with the text box.
We do still validate when a user types something that it parses to an
int. If it does not we delete the most recent character. This in effect
prevents non-numeric numbers from being entered.
Upon losing focus the value will be checked. If empty we set the SpinBox
value to the minimum allowed value.
With a new DragCopy cursor icon being used on accepted events, this
caused a 'false assumption' that everything can be dropped into
AbstractView.
This will now only happen if the View is editable, which still isn't
perfect, but at least the Settings app will no longer change cursors.
Also note that we won't get "drag move" events as the comment below
says, which disables automatic scrolling when dragging an element.
... instead of in the center of the cursor bitmap.
It doesn't make much of a difference, as the default cursor hotspot is
center. But since now we switch between a normal Drag cursor and
DragCopy one that has set hotspot, this caused the overlay to shake.
Previously, the cursor would use a default cursor on window frames such
as the title bar and menu bar, which was not quite correct as drop
events were still handled there.
Layer::erase_selection used to erase the entire bounding box of the
selection. With the add/subtract merge modes for the selection tool it
is possible to create selections which are not rectangular. This leads
to deleting pixels that were not selected.
This change adjusts the erase behavior to walk the selection rect and
check if a pixel is selected or not before deleting.
Wand Selection tool uses similar logic to the Bucket Tool. Flood filling
and threshold calculations to determine the affected area just in this
case we do not set the pixels of the selected area, instead we use
those pixels to alter the selection mask.
In the future we can probably abstract out the shared flood logic so
both tools can share the code.
Before, the warning dialog would be opened after the NewProjectDialog,
leading to focus-fighting by the two windows. This fixes that and makes
the action more consistent with the standard serenity way of handling
unsaved changes by asking before the NewProjectDialog is brought up.
The way this is achieved avoids having to rewrite open_project as well.
This commit fixes a crash that would occur due to an unnamed file being
automatically saved via EditorWrapper::save(). Now, we throw up a
FilePicker::get_save_filepath.
Specializing point_position_to_preferred_cell for the
RectangleSelectTool as it selects a new cells with a rounding
behavior instead of a flooring behavior
This is done to allow querying the current active tool inside the
event_with_pan_and_scale_applied and event_adjusted_for_layer
functions without risking a null pointer dereference
This method is used to point a position at the preferred pixel of
the image. Certain tools may want to specify a different preferred
pixel for the same input position.
When the location currently displayed in FileManager is removed, find
the nearest existing parent path and open it in the window.
Without the fix, the FileManager window remained in the deleted
directory.
Changing the path in 'DirectoryView' object will automatically update
other components in the FileManager (breadcrumb bar, directory tree
view).
This commit fixes FileSystemModel behaviour when the root path of the
model has been deleted.
In this case, the model index resolved for the root path is invalid and
passing it to 'begin_delete_rows' would trigger assertion failure.
Instead of deleting all children rows one by one, we simply invalidate
the whole model.
And adjust some GML properties. Since a808cfa, splitters grow
opportunistically. Setting them to fixed sizes now quite literally
fixes them in place. Fixes immovable splitters missed in the
aforementioned commit.
Previously, for a regex such as /[a-sy-z]/i, we would incorrectly think
the character "u" fell into the range "a-s" because neither of the
conditions "u > s && U > s" or "u < a && U < a" would be true, resulting
in the lookup falling back to assuming the character is in the range.
Instead, first explicitly check if the character falls into the range,
rather than checking if it falls outside the range. If the explicit
checks fail, then we know the character is outside the range.
The Quake 3 port makes use of this extension to determine a more
efficient multitexturing strategy. Since LibSoftGPU supports it, let's
report the extension in LibGL. :^)
Instead of hardcoding all the property definitions in GlobalObject's
initialize() function, make it the standalone AO it is supposed to be
that can then be used by other global objects that don't inherit from
JS::GlobalObject.
This will later allow global objects not inheriting from the regular
JS::GlobalObject to pull in these functions without having to implement
them from scratch. The primary use case here is, again, a wrapper-less
HTML::Window in LibWeb :^)
Allocating these upfront now allows us to get rid of two hacks:
- The GlobalObject assigning Intrinsics private members after finishing
its initialization
- The GlobalObject defining the parseInt and parseFloat properties of
the NumberConstructor object, as they are supposed to be identical
with the global functions of the same name
This removes the requirement of having a global object that actually
inherits from JS::GlobalObject, which is now a perfectly valid scenario.
With the upcoming removal of wrapper objects in LibWeb, the HTML::Window
object will inherit from DOM::EventTarget, which means it cannot also
inherit from JS::GlobalObject.
The object is passed directly to NewObjectEnvironment, which has no
requirement for this being a JS::GlobalObject. This is needed for the
next change, which will make Realm store a plain Object as for the
global object as well.
This will allow us to move the underlying console from GlobalObject to
ConsoleObject without still having to do a 'console' property lookup on
the GlobalObject.
This was too restrictive and there are already UI elements that rely
on this behavior. Now Blocking modals will preempt interaction with
all windows in their modal chain except those descending from them.
Fixes crashing in FilePicker when permission is denied.
Just like tiling behavior during ongoing moves, now resizing
does not finish until a MouseUp event, letting you drag out of
undesired tile states. Resize tiling only works with vertical
and horizontal cursors now to cut down on unintentional tiling
from the corners.
Now they can be dismissed by clicking anywhere outside themselves,
including on their parent windows. This is a better default for
them since they don't have title bars to flash, and it's more
consistent with other frameless windows in the system.
When cropping to content with a layer not positioned at 0,0 the moved
layers content disappeared and the layers position was not updated
according to the crop offset.
There's probably an easier/more efficient way, but for my testcase this
improves the behavior.
Also added a local test for ensuring this behavior since it is unique to
browsers. Since we don't actually use WindowProxy anywhere yet we just
test on location for now.
Since LibUnicode depends on this data it used to include
Intl/AbstractOperations which in turn includes a number of other LibJS
headers. By moving this to its own header with minimal includes we can
save on rebuilding LibUnicode for unrelated LibJS header changes.
Intrinsics, i.e. mostly constructor and prototype objects, but also
things like empty and new object shape now live on a new heap-allocated
JS::Intrinsics object, thus completing the long journey of taking all
the magic away from the global object.
This represents the Realm's [[Intrinsics]] slot in the spec and matches
its existing [[GlobalObject]] / [[GlobalEnv]] slots in terms of
architecture.
In the majority of cases it should now be possibly to fully allocate a
regular object without the global object existing, and in fact that's
what we do now - the realm is allocated before the global object, and
the intrinsics between both :^)
In OpenGL this is called the (base) internal format which is an
expectation expressed by the client for the minimum supported texel
storage format in the GPU for textures.
Since we store everything as RGBA in a `FloatVector4`, the only thing
we do in this patch is remember the expected internal format, and when
we write new texels we fixate the value for the alpha channel to 1 for
two formats that require it.
`PixelConverter` has learned how to transform pixels during transfer to
support this.
For `GL_RED_BITS`, `GL_GREEN_BITS`, `GL_BLUE_BITS` and `GL_ALPHA_BITS`
we were reporting the values we use in LibSoftGPU for textures. This
fixes these context parameters to actually report the color buffer
bits.
A GPU (driver) is now responsible for reading and writing pixels from
and to user data. The client (LibGL) is responsible for specifying how
the user data must be interpreted or written to.
This allows us to centralize all pixel format conversion in one class,
`LibSoftGPU::PixelConverter`. For both the input and output image, it
takes a specification containing the image dimensions, the pixel type
and the selection (basically a clipping rect), and converts the pixels
from the input image to the output image.
Effectively this means we now support almost all OpenGL 1.5 formats,
and all custom logic has disappeared from:
- `glDrawPixels`
- `glReadPixels`
- `glTexImage2D`
- `glTexSubImage2D`
The new logic is still unoptimized, but on my machine I experienced no
noticeable slowdown. :^)
Instead we just use a specific constructor. With this set of
constructors using curly braces for constructing is highly recommended.
As then it will not do too many implicit conversions which could lead to
unexpected loss of data or calling the much slower double constructor.
Also to ensure we don't feed (Un)SignedBigInteger infinities we throw
RangeError earlier for Durations.
This means it can take any (un)signed word of size at most Word.
This means the constructor can be disambiguated if we were to add a
double constructor :^).
This requires a change in just one test.
This allows using different options for rounding, like IEEE
roundTiesToEven, which is the mode that JS requires.
Also fix that the last word read from the bigint for the mantissa could
be shifted incorrectly leading to incorrect results.
Applications using the Vim emulation engine now support line-wise text
selection.
We already have support for character-wise text selection, by pressing
`v` from normal mode.
However now can also trigger line-wise text selection by pressing
`shift+v` from normal mode, and then using vertical motion commands
(e.g. `j` or `k`) to expand the selection. This is a standard vim
feature.
In visual line mode the following operations are supported:
* `escape`: back to normal mode
* `u`: convert to lowercase
* `U`: convert to uppercase
* `~`: toggle case
* `ctrl+d`: move down by 50% of page height
* `ctrl+u`: move up by 50% of page height
* `d` or `x`: delete selection
* `c`: change selection
* `y`: copy selection
* `page up`: move up by 100% of page height
* `page down`: move down by 100% of page height
Notably I didn't implement pressing `v` to go to regular
(character-wise) visual mode straight from visual line mode. This is
tricky to implement in the current code base, and there's an
alternative, which is to take a detour via normal mode.
This commit fixes a bug found when passing exotic values in the
grid-template-columns (or grid-template-rows) which are not yet
supported.
The bug seems to have been something like:
grid-template-columns: 0 minmax(0, calc(10px - var(--some-color)));
so Dialogs can join the modal chain of the window parenting them.
Fixes apps that use FileSystemAccess to sandbox FilePicker not
respecting its blocking effect.
Refactors restore helper into move_to_front_and_make_active().
Fixes not bringing all modal children to the front when any modal
child or its modeless parent is clicked.
This was intentionally enabled with WindowModes as a new Taskbar
convenience, but on second thought, it doesn't add up visually.
Taskbar buttons show blockers' context menus when available,
which is a bit confusing when the window isn't visible. The
modeless window's disabled context menu options and inactive title
bar also contradict the button. So, this patch reenables the
restriction for now. Blocking modals you don't want to answer to
immediately can still be tucked away on another workspace.
This exception is necessary for ComboBoxes used in some blocking
Dialogs. CaptureInput is now the only mode which can spawn from
a blocking modal and it won't accept any children of its own.
This is an editorial change in the Temporal spec.
Now that this is spec'd as either an Object or undefined, we can change
the parameter type from arbitrary JS::Value to JS::Object*.
See: https://github.com/tc39/proposal-temporal/commit/cdfcffd
The protocol of the origin is used for checking if the a file://
iframe is allowed to be loaded (a document with a file:// origin
can load other files in iframes).
This used to be the case, but was changed in
6e71e400e6, which broke file:// iframes.
This is a normative change in the ECMA-262 spec. See:
https://github.com/tc39/ecma262/commit/35b7eb2
Note there is a bit of weirdness between the mainline spec and the set
notation proposal as the latter has not been updated with this change.
For now, this implements what the spec PR and other prototypes indicate
how the proposal will behave.
This is a really starter attempt at formatting the grid. It doesn't yet
take into account the computed_values of grid-template-rows, nor the
values in grid-column-start and like CSS properties.
But these changes are a start and make it so the examples in
display-grid.html work.
To be fleshed out further..
Add functionality to begin parsing grid-template-columns and
grid-template-rows. There are still things to be added, like parsing
functions, but I would say a couple of the major points are already
adressed like length, percentage, and flexible-length.
This works the same as in the Cube demo, and now allows enjoying
the eyes without any obstructions :^)
The window frame can now be disabled with the -h/--hide-window
command-line option, or via toggle in the GUI.
This was a cludge for ComboBox ListView windows when they were first
implemented. The behavior is no longer needed and not very ergonomic
when moving Normal windows around.
But do allow them to remain minimizable by a parent. This is a nice
ergonomics fix to allow a parent window to quickly minimize and
restore all its modal children.
And apply modal effects on move_to_front_and_make_active()
Instead of building a vector of windows every time we want to
loop over a group of related modals, simply recurse through
their ancestory. This lineage is now called a modal chain. Modal
chains begin at the first modeless parent, and a new chain
starts at every modeless child. This lets apps spawn child windows
independent of the main window's modal effects, restore state,
and workspace, yet still remain descendants.
Looping through a modal chain is recursive and includes the
modeless window which begins it.
with the CaptureInput WindowMode. This mode will serve the same
function as accessories: redirecting input while allowing parent
windows to remain active.
with the RenderAbove WindowMode. This mode will ensure child
windows always draw above their parents, even when focus is lost.
RenderAbove modals are automatically themed the same as the old
ToolWindows. Fixes ToolWindows rendering above ALL normal windows,
regardless of parent. We can't rely on WindowType to create these
sort of effects because of WindowManager's strict display hierarchy.
Previously, Windows only understood blocking modality: Windows were
either modal, i.e., in a blocking state, or not. Windows could also
be set as Accessories or ToolWindows, attributes which technically
applied modes to their parents but were implemented ad hoc. This patch
redefines these modal effects as WindowModes and sets up some helpers.
This will let us simplify a lot of modal logic in the upcoming patches
and make it easier to build new modal effects in the future.
Windows can now set 1 of 5 modes before reification:
-Modeless: No modal effect; begins a new modal chain
-Passive: Window joins its modal chain but has no effect
-RenderAbove: Window renders above its parent
-CaptureInput: Window captures the active input role from its parent
-Blocking: Window blocks all interaction with its modal chain
States like fullscreen and tiling are dynamic and don't alter behavior
in modal chains, so they aren't included.
Superceded by to_floating_cursor_position() as a more accurate way
to reposition windows on untile. Effectively made set_size_around()
dead code, so the remnants can be removed.
Positioning windows outside visible coordinates is valid if sometimes
curious behavior, but it shouldn't be considered misbehavior by default.
There are multiple ways to recover windows with obscured title bars,
and this function papers over actual resize bugs and is no longer
needed to normalize window size, so let's remove it for now.
The new layout system conveniently calculates these for us now.
In the case of Mandelbrot where it needs to be overriden, make
sure to disable obey min widget size first. In EmojiInputDialog's
case, the window needs to be resized instead to center correctly.
And remove unnecessary workarounds to the old limit of {50, 50} and
the cautious but arbitrary limit of {1, 1} for other WindowTypes.
Null rects are already the default when calculating minimum window
size and are the least restrictive but valid value.
Also returns early during minimum size calculations for frameless
windows, and verifies against negative minimum sizes and failure to
disable widget min size before setting a minimum window size. Layout
automatically overrides this setting each relayout otherwise.
SignedBigInteger can immediately use this by just negating the double if
the sign bit is set.
For simple cases (below 2^53) we can just convert via an u64, however
above that we need to extract the top 53 bits and use those as the
mantissa.
This function currently does not behave exactly as the JS spec specifies
however it is much less naive than the previous implementation.
Although this is much more complicated it does not seem to impact
performance that much even when looking only at values in which the
previous casting to i32 was correct.
This adds menu item icons for Add Mask, Flatten Image, Fit Image To
View, and Generic 5x5 Convolution.
This modifies the menu item icon for Swap Colors to make the action more
obvious and improve accessibility.
For the fuzzer build isnan was not usable in a constexpr context however
__builtin_isnan seems to always be.
Also while we're here add my name to the copyright since I forgot after
the Value rewrite.
This command finds the smallest non-empty content bounding rect
by looking for the outermost non-transparent pixels in the image,
and then crops the image to that rect.
It's implemented in a pretty naive way, but it's a start. :^)
Some header files use __BEGIN_DECLS without including sys/cdefs.h.
This causes issues for C code that compiles against these headers,
which may occur with Ports.
We previously had at least three different implementations for resolving
executables in the PATH, all of which had slightly different
characteristics.
Merge those into a single implementation to keep the behaviour
consistent, and maybe to make that implementation more configurable in
the future.
Removing the FIXME'd code in b99cc7d was a bit too eager, and relying on
the main thread VM's current realm only works when JS is being executed.
Restore a simplified version of the old code to determine the realm this
time instead of the global object, following the assumptions already
made in get_current_value_of_event_handler() regarding what kind of
event target 'this' can be.
The basic idea is that a global object cannot just come out of nowhere,
it must be associated to a realm - so get it from there, if needed.
This is to enforce the changes from all the previous commits by not
handing out global objects unless you actually have an initialized
realm (either stored somewhere, or the VM's current realm).
- Prefer VM::current_realm() over GlobalObject::associated_realm()
- Prefer VM::heap() over GlobalObject::heap()
- Prefer Cell::vm() over Cell::global_object()
- Prefer Wrapper::vm() over Wrapper::global_object()
- Inline Realm::global_object() calls used to access intrinsics as they
will later perform a direct lookup without going through the global
object
This is needed so that the allocated NativeFunction receives the correct
realm, usually forwarded from the Object's initialize() function, rather
than using the current realm.
Global object initialization is tightly coupled to realm creation, so
simply pass it to the function instead of relying on the non-standard
'associated realm' concept, which I'd like to remove later.
This works essentially the same way as regular Object::initialize() now.
Additionally this allows us to forward the realm to GlobalObject's
add_constructor() / initialize_constructor() helpers, so they set the
correct realm on the allocated constructor function object.
Similar to create() in LibJS, wrap() et al. are on a low enough level to
warrant passing a Realm directly instead of relying on the current realm
from the VM, as a wrapper may need to be allocated while no JS is being
executed.
Instead of passing a GlobalObject everywhere, we will simply pass a VM,
from which we can get everything we need: common names, the current
realm, symbols, arguments, the heap, and a few other things.
In some places we already don't actually need a global object and just
do it for consistency - no more `auto& vm = global_object.vm();`!
This will eventually automatically fix the "wrong realm" issue we have
in some places where we (incorrectly) use the global object from the
allocating object, e.g. in call() / construct() implementations. When
only ever a VM is passed around, this issue can't happen :^)
I've decided to split this change into a series of patches that should
keep each commit down do a somewhat manageable size.
This is a continuation of the previous six commits.
The global object is only needed to return it if the execution context
stack is empty, but that doesn't seem like a useful thing to allow in
the first place - if you're not currently executing JS, and the
execution context stack is empty, there is no this value to retrieve.
This is a continuation of the previous five commits.
A first big step into the direction of no longer having to pass a realm
(or currently, a global object) trough layers upon layers of AOs!
Unlike the create() APIs we can safely assume that this is only ever
called when a running execution context and therefore current realm
exists. If not, you can always manually allocate the Error and put it in
a Completion :^)
In the spec, throw exceptions implicitly use the current realm's
intrinsics as well: https://tc39.es/ecma262/#sec-throw-an-exception
This is a continuation of the previous four commits.
Passing a global object here is largely redundant, we definitely need
the interpreter but can get the VM and (later) current active realm from
there - and also the global object while we still need it, although I'd
like to remove Interpreter::global_object() in the future.
This now matches the bytecode interpreter's execute_impl() functions.
This is a continuation of the previous three commits.
Now that create() receives the allocating realm, we can simply forward
that to allocate(), which accounts for the majority of these changes.
Additionally, we can get rid of the realm_from_global_object() in one
place, with one more remaining in VM::throw_completion().
This is a continuation of the previous two commits.
As allocating a JS cell already primarily involves a realm instead of a
global object, and we'll need to pass one to the allocate() function
itself eventually (it's bridged via the global object right now), the
create() functions need to receive a realm as well.
The plan is for this to be the highest-level function that actually
receives a realm and passes it around, AOs on an even higher level will
use the "current realm" concept via VM::current_realm() as that's what
the spec assumes; passing around realms (or global objects, for that
matter) on higher AO levels is pointless and unlike for allocating
individual objects, which may happen outside of regular JS execution, we
don't need control over the specific realm that is being used there.
This is a continuation of the previous commit.
Calling initialize() is the first thing that's done after allocating a
cell on the JS heap - and in the common case of allocating an object,
that's where properties are assigned and intrinsics occasionally
accessed.
Since those are supposed to live on the realm eventually, this is
another step into that direction.
No functional changes - we can still very easily get to the global
object via `Realm::global_object()`. This is in preparation of moving
the intrinsics to the realm and no longer having to pass a global
object when allocating any object.
In a few (now, and many more in subsequent commits) places we get a
realm using `GlobalObject::associated_realm()`, this is intended to be
temporary. For example, create() functions will later receive the same
treatment and are passed a realm instead of a global object.
Like any other regular, non-inheriting web platform prototype, the
prototype's prototype should be Object.prototype, not the global object.
Another reason to get rid of "global object (an object) + prototype
object (also an object)"-style APIs for allocation :^)
The on_segment_change handler introduced in
a00fa793b3 was only getting called by
programmatically setting the segment, not by clicking a button or using
tab navigation.
This patch makes the handler's behavior closer to what can be expected
from it's name by not handling set_selected_segment if the segment is
already selected.
This deadlock would incorrectly change the queue from almost empty to
full on dequeue because someone else could empty the queue after we had
checked its non-emptyness. The test would deadlock on this, which
doesn't happen anymore.
The only accepted syntax for these seems to be
<color> <length percentage> <length percentage>, no other order.
But that's just gathered from looking at other browsers as though
these are supported by all major browsers, they don't appear in
the W3C spec.
Instead of having "Flip Horizontally" in both the Image and Layer menus,
we now have "Flip Image Horizontally" and "Flip Layer Horizontally".
This same concept applied to other, similar actions.
Monte-Carlo methods are known to intensively create nodes and in our
case each leaf of the tree stores a board. However, for this use case,
we don't need a full board object that also contains game information.
This patch adds a `clone_cleared()` method that return a clone without
game information and uses it when constructing the tree.
It allows the ChessEngine much more possibility before getting out of
memory.
This method temperate the habit of Monte-Carlo based algorithms to
repeatedly create new nodes.
It was first implemented in `Efficient Selectivity and Backup Operators
in Monte-Carlo Tree Search` by Rémi Coulom.
This was giving wonky results with images that do not fill the entire
card - and it's also unnecessary, since the Buggie image we have been
using, and the two I will be adding, are all small enough to not need
scaling anyway. :^)
`CardPainter::set_background_image_path()` immediately repaints the card
back and inverted card back bitmaps if they exist, so users just need
to `update()` that part of the screen. This is handled automatically by
`CardGame`, but other users will have to do this manually.
Instead of each card being responsible for painting its own bitmaps, we
now have a CardPainter which is responsible for this. It paints a given
card the first time it is requested, and then re-uses that bitmap when
requested in the future. This saves memory for duplicate cards (such as
in Spider where several sets of the same suit are used) or unused ones
(for example, the inverted cards which are only used by Hearts). It
also means we don't throw away bitmaps and then re-create identical
ones when starting a new game.
We get some nice memory savings from this:
| | Before | After | Before (Virtual) | After (Virtual) |
|:----------|---------:|---------:|-----------------:|----------------:|
| Hearts | 12.2 MiB | 9.3 MiB | 25.1 MiB | 22.2 MiB |
| Spider | 12.1 MiB | 10.1 MiB | 29.2 MiB | 22.9 MiB |
| Solitaire | 16.4 MiB | 9.0 MiB | 25.0 MiB | 21.9 MiB |
All these measurements taken from x86_64 build, from a fresh launch of
each game after the animation has finished, but without making any
moves. The Hearts value will go up once inverted cards start being
requested.
Because `card->value() == 11` is a lot less clear than `card->rank() ==
Cards::Rank::Queen`, and also safer.
Put this, along with the `Suit` enum, in the `Cards` namespace directly
instead of inside `Cards::Card`. Slightly less typing that way.
For now, the only feature of this is that it sets the background colour
from the `Games::Cards::BackgroundColor` config value. Later, other
card game configuration and shared behaviour can go here, to save
duplicating it in each game.
This currently has exactly one setting: The background colour for card
games. My thinking is, it's better to not have a Settings application
for each individual game we include in the system, since most will only
have a small number of settings, all Settings windows have tabs anyway,
and I don't want to flood the Settings app list unnecessarily.
As for having a single setting for all the card games: it's nice when
things match. :^)
Use Breadcrumbbars on_segment_change instead of on_segment_click.
This allows us to remove the manual handler invokation in the
open_child_directory_action
The Undo/Redo actions now tell you what kind of action will be
undone/redone. This is achieved by adding an "action text" field to the
ImageUndoCommand and having everyone who calls did_complete_action()
provide this text.