gimp/app/paths_dialog.c: CVS-1.30.
gimp/app/bezier_select.c: CVS-1.45

Package: gimp
Version: 1.1.9 (interface age: 0, binary age: 0)

Name........: Garry R. Osgood
Email.......: gosgood@idt.net
Platform....: SGI O2(IP32)/SGI Indigo (IP20) IRIX 6.5.4 
GIMP Version: 1.1.9 (interface age: 0, binary age: 0)
GTK Version.: 1.2.4 (interface age: 4, binary age: 2)

----

Summary

The trashcan function button on the "Paths" tab of the
Layers & Channel dialog box orphans the last bezier curve;
with the deletion of the last path, a bezier curve
representation remains in the drawing area, phoenix like: it
refuses to die even with clipped wings, for curve
manipulation tools on the path dialog box are ghosted. (Somewhat) fortunately,
the handlers connected to the Bezier Select Tool are still active,
and hooks they have into the paths dialog box code recreate the
path anew. Still, this makes for a confusing, then irritating experience
for the end user trying to delete that last damn curve.


----

What goes wrong

The path_dialog box relies on event handlers connected to
path widets residing in its clist field. In particular, when
the user clicks on the Path dialog trashcan,
path_dialog_delete path_callback() [paths_dialog.c line
1360] invokes gtk_clist_remove() on this
collection. Ideally, the side effects invoked by callbacks
connected to these path widgets and the path_dialog box
automagically synchronize the currently selected bezier
(residing in bezier_select.c static variables) 
with the new currently selected path (See "Background").

When the path_dialog box has more than one path,
gtk_clist_remove() successfully performs two list
maintenance chores. Both arise from event handlers that
paths_dialog_create() [paths_dialog.c: line 263] first
attaches to the GTK CLIST that contains path widgets. (the
"paths_list", following).
 
(1) When the doomed path widget is currently selected, it becomes
unselected and gtk_clist_remove() invokes deselection
handlers connected with the doomed item. For path_lists
elements, this implicitly calls paths_unselect_row()
[paths_dialog.c line 1005], with the doomed row number as a
parameter.

(2) gtk_clist_remove() then designates the next higher item
of the list as the new selected element. (or the new last
item, had the doomed item been last). In this context,
gtk_clist_remove() invokes selection handlers connected with
the newly selected item, paths_select_row() [paths_dialog.c
line 903]. This selection handler synchronizes
bezier_select.c internals with the paths_dialog box; it
converts the newly selected path to a BezierSelect structure
and invokes bezier_paste_bezierselect_to_current()
[bezier_select.c: line 2435]. That routine "installs" a new
selected bezier curve, refreshing the selected bezier curve
state in local statics curSel, curTool, curGdisp, and
curCore [bezier_select.c: line 146 ff], creating and
initializing a new Bezier tool, and setting it up to render
into the image. This lengthly chunk of housecleaning occurs
automagically as a consequence of reselecting an item on the
path_lists.

The bug arises from the boundary condition of one-item
path_lists; none of the extensive housecleaning associated
with selecting a new path_list item takes place. In
particular, gtk_clist_remove() invokes only removal
handlers, and not selection handlers; obviously, for there
is nothing left to select. This leaves path_list internals
with no paths whatsoever, but bezier_select.c internals
(curSel curTool, curGdisp, curCore) with an active
BezierSelect item on the display, an orphan of a dead
path_list item. One cannot do anything with the orphan using
tools on the path dialog box (they are ghosted), but one can
still manipulate the orphan with the bezier select tool. In
that context a Miracle rescues the user. Handlers associated
with the bezier select tool call path_dialog box hooks
to cover the possibility of a user creating a bezier
curve on the fly. These ensure that an otherwise empty
path_dialog box has a path corresponded to the currently
selected bezier.

In this fasion, a single path that a user wants to get rid
of magically survives as an orphan in the image display, and
arises from the ashes - as a path - once the user
manipulates any one of the control points of the bezier
curve on the image display.

----

The Patch

I've elected to modify paths_dialog_delete_path_callback()
in paths_dialog.c, which already performs some housecleaning
when it discovers that the deletion has removed all
content. Since BezierSelects constitutes the medium of
exchange between the paths_dialog and the selected_bezier
residing in bezier_select.c, the least disruptive
alternative seemed to be to instance and empty BezierSelect
and install it via
bezier_paste_bezierselect_to_current(GDisplay*,
BezierSelect*), which generally seems to install a new
BezierSelect object, and rethinks related structures.


This has been submitted to ftp.gimp.org as patch gimp-gosgood-990916-0.patch

-----

Additional Notes

Gimp has two structures representing bezier
curves. BezierSelect dates to Gimp-1.0.x which claimed that
only one bezier curve could be in existence on the planet at
a time.

Gimp-1.1.x is more permissive. It allows any number of
coexistent paths; these live in the Gimp 1.1.x path_dialog
box where they are defined as PATHP structures. This new
structure differs from BezierSelects; they maintain abstract
geometry without fields that support point selectors or
bitmap rendering. The BezierSelect still lives in
Gimp-1.1.x; it is now the analog of a "currently selected
path" and is manifest in bezier_select.c as four static
pointers, one to a BezierSelect buffer, (curSel), the
currently active bezier curve, one to a Tool that can
manipulate the bezier curve's points and handles (curTool),
one to a GDisplay, where the curve can be painted, one to a
current display, (curGdisp), and one to a bag of graphics
context, DrawCore (curCore) to help render the curve. In the
following, these four buffers are collectively referred to
as the "selected_bezier"; this term also encompasses the
support largely implemented in bezier_select.c [CVS-1.45].

The selected_bezier and the currently selected path in the
path_dialog box constitute what should be equivalent "bezier
states"; if the paths_dialog box happens to be empty, then
there is no "selected_bezier" state (at least in theory).

Synchronization between paths and the currently
"selected_bezier" occurs largely through handlers connected
to path widgets, the paths_dialog box, or handlers
associated with the bezier select tool. Those associated
with the path_dialog box ensure that the currently selected
path is always "installed" as the selected_bezier; they create
BezierSelect objects and paste them into the
selected_bezier, as needed. The handlers associated with
the bezier select tool ensure that the curve the user is
manipulating is accurately reflected in the currently
selected PATHP object; it has paths_dialog hooks to ensure this. 

 
