Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / GUI / March 2005

Tip: Looking for answers? Try searching our database.

UndoManager addEdit problem

Thread view: 
jonck@vanderkogel.net - 03 Mar 2005 17:36 GMT
Hi,
I'm using the UndoManager to undo/redo the input of a user to a form
with multiple JTextFields. The standard UndoableEdit's produced by the
JTextFields are one character at a time however and I would like to
change this behavior so that multiple characters can be undone/redone
with a single undo/redo action.

So I wrote a class that extends AbstractUndoableEdit, and overrides the
addEdit method. When a user has just entered a new JTextField
(determined by a FocusListener) the first edit will be marked as that
it should add edits, all subsequent edits in this JTextField will thus
be merged into one edit.

This works great, however, when the user then switches to a different
JTextField the new UndoableEdits are still being absorbed by the first
edit. My question therefore is, how do I notify to the UndoManager (or
the last UndoableEdit in the UndoManager's queue) that I would like the
last edit to stop absorbing any new edits and start a new edit?

Thanks for any help, Jonck
John McGrath - 04 Mar 2005 02:41 GMT
> So I wrote a class that extends AbstractUndoableEdit, and overrides the
> addEdit method. When a user has just entered a new JTextField
[quoted text clipped - 7 lines]
> the last UndoableEdit in the UndoManager's queue) that I would like the
> last edit to stop absorbing any new edits and start a new edit?

You are generating your own UndoableEdits?  I presume that they must know
the Document that they apply to, since they need to know that in order to
undo/redo the edit.

In that case, all you should need to do is, in addEdit(), check whether
the Document for the new edit is the same as the Document for the existing
edit.  If not, do not absorb the edit.

Signature

Regards,

John McGrath

jonck@vanderkogel.net - 04 Mar 2005 11:01 GMT
Right, but the problem is that once you state that an edit should
absorb the next by returning true in its addEdit method, all edits that
are from then on added to the UndoManager will be absorbed (even if I
let the next edits return false in their addEdit method). So how do I
tell it not to absorb any more edits?

Thanks, Jonck
John McGrath - 04 Mar 2005 13:57 GMT
> Right, but the problem is that once you state that an edit should
> absorb the next by returning true in its addEdit method, all edits that
> are from then on added to the UndoManager will be absorbed (even if I
> let the next edits return false in their addEdit method).

That is not how it should work.  When you add the edit to the UndoManager,
it will try adding the edit to the most recent edit.  If that addEdit()
call returns true, then the UndoManager just returns - the edit has been
added.  If it returns false, it will add the edit to the end of its list
of edits.  (Actually, there is also the replaceEdit() processing, but I
assume you have not overridden that).

How have you implemented your addEdit() method?  Typically, it would just
merge the new edit into itself, if possible.  If the last edit did an
[insert "a"] and you add an edit to it that does an [insert "b"], then
addEdit() on the last edit would change the string that it inserts from
"a" to "ab" and return true.  However, if you tried adding a different
type of edit, say a [remove "c"], it would return false.  Then the
UndoManager would just add the "remove" edit to its list.

How are you getting the UndoableEdits to your UndoManager?  Did you just
add the UndoManager as a listener, or did you create your own listener
that adds the edits?  If the latter, could you post that code?

Signature

Regards,

John McGrath

jonck@vanderkogel.net - 12 Mar 2005 11:23 GMT
Sorry for the late reply, I was out with the flu all week.

Here is the code for my addEdit method:

public boolean addEdit(UndoableEdit anEdit) {
       if (shouldAdd()) {
           UndoableGpsJTextFieldEdit edit =
(UndoableGpsJTextFieldEdit) anEdit;
           edit.setAfter(getAfter());
       }
       return shouldAdd();
   }

The shouldAdd() method returns whether or not this particalur edit
should be added to the previous one or not (set at constructor time).
The setAfter() and getAfter() methods store and retrieve the value of
the TextField after the edit took place.
So in other words, here's what I intended to happen: the first edit of
a TextField I set add to false, so a new edit should be put in the
UndoManager. Subsequent edits I then tell to be added. The addEdit
method sets the first edit's after value to the new value of the
TextField. When a TextField loses focus I reset the component, so that
should it regain focus a new edit will be created, etc...

But this is not at all how things are working. A new edit is never
created, all edits are being absorbed by the first one (while I am 100%
sure that when the user switches to a new TextField, the add property
is set to false).

Regards, Jonck
P.S. I have simply set the UndoManager as a listener for new edits, so
therefore I did not post any of that code.
John McGrath - 12 Mar 2005 22:12 GMT
> Here is the code for my addEdit method:
>
[quoted text clipped - 6 lines]
>         return shouldAdd();
>     }

I have a few questions about this code.

>             edit.setAfter(getAfter());

Shouldn't this be the following?

             setAfter( edit.getAfter() );

The edit for which the addEdit() method is executing is the previous edit,
the one that is already in the UndoManager queue.  It is the "this" edit
that is supposed to absorb anEdit, not the other way around, so the "this"
value should be modified.

I also wonder about the way that you call shouldAdd() twice.  First, it is
not the most efficient way to do things.  Why compute the value twice?
But far more important than that is the question of whether it will return
the same value from both calls.  Even if you know that it will, it is
better if this code is written so that the inconsistency cannot happen.

Finally, the shouldAdd() method cannot see the "anEdit" parameter that is
passed to addEdit(), so I wonder, how is it supposed to determine whether
the edit can be added?  It looks like you will get a ClassCastException if
an edit other than an UndoableGpsJTextFieldEdit were passed to addEdit().

For comparison, here is the addEdit() method from an UndoableEdit
implementation that I wrote a while back:

     public boolean addEdit( UndoableEdit edit ) {
        if ( edit instanceof MoveEdit ) {
           MoveEdit e = (MoveEdit) edit;
           if ( target == e.target ) {
              newPosition = e.newPosition;
              return true;
           }
        }
        return false;
     }

It occurs to me now that I probably should have added an e.die() just
before the return true.  I do not think it is critical, but it would be
appropriate.

> P.S. I have simply set the UndoManager as a listener for new edits, so
> therefore I did not post any of that code.

That works, but I usually want to update the state of the undo and redo
Actions, so that the user knows whether undo and/or redo can be performed.
So I use a listener something like this:

  private UndoableEditListener editListener = new UndoableEditListener() {
     public void undoableEditHappened( UndoableEditEvent event ) {
        undoManager.addEdit( event.getEdit() );
        updateUndoActions();
     }
  };

Signature

Regards,

John McGrath

jonck@vanderkogel.net - 13 Mar 2005 12:16 GMT
> The edit for which the addEdit() method is executing is the previous edit,
> the one that is already in the UndoManager queue.  It is the "this" edit
> that is supposed to absorb anEdit, not the other way around, so the "this"
> value should be modified.

Aha, that explains it! Yes, it works as I would expect it now. Thank
you!

> Finally, the shouldAdd() method cannot see the "anEdit" parameter that is
> passed to addEdit(), so I wonder, how is it supposed to determine whether
> the edit can be added?

The shouldAdd() method is just the getter for a boolean field, which I
set at construction time. I guess it would have been better if I would
have adhered to the standard, and had named this method getAdd() or
isAdd() (in this particular case shouldAdd() seemed appropriate, but
taken out of it's context I can understand how it becomes confusing).
Whether or not the edit should be added is determined in the JTextField
(a subclass of JTextField to be more precise). It contains a
FocusListener, and by keeping track of when a component gains or loses
focus it determines whether an edit is the first edit (and thus should
not be added) or whether an edit is a follow-up (in which case I want
the edit to be added to the last).

>  It looks like you will get a ClassCastException if
> an edit other than an UndoableGpsJTextFieldEdit were passed to addEdit().

Yes, to simplify my posting I took out all "non-relevant" code like
type checking, error handling, etc...

> That works, but I usually want to update the state of the undo and redo
> Actions, so that the user knows whether undo and/or redo can be performed.

I have extended the UndoManager so that listeners can be added to it,
and it passes the edit on to any listeners attached to it. So in my GUI
code I have attached a listener to the UndoManager which updates the
state of the undo/redo buttons. So in other words, the UndoManager
listens to the components on the form and the GUI listens to the
UndoManager.

Thank you very much for your excellent help, I am glad we got to the
bottom of this!

Regards, Jonck
John McGrath - 14 Mar 2005 02:43 GMT
> Aha, that explains it! Yes, it works as I would expect it now. Thank
> you!

Great!

> The shouldAdd() method is just the getter for a boolean field,
> which I set at construction time.

I have some doubts about this.  Deciding when you create the edit may
work, given what you know about the global environment in which they are
being created, but that environment may change, breaking this code.

I would think that the more appropriate way to decide whether an edit
should be absorbed would be based on the current edit and the new edit.
The edit should know what field it applies to, so it could just compare
the two fields and decide not to absorb the edit if the fields do not
match.

> I have extended the UndoManager so that listeners can be added to it,
> and it passes the edit on to any listeners attached to it.

That sounds very reasonable.  I have often wondered why the standard
UndoManager does not already have this feature.  Adding a listener would
make a lot more sense than creating an intermediary to post the edits.

Signature

Regards,

John McGrath

jonck@vanderkogel.net - 14 Mar 2005 14:21 GMT
> I would think that the more appropriate way to decide whether an edit

> should be absorbed would be based on the current edit and the new edit.
> The edit should know what field it applies to, so it could just compare
> the two fields and decide not to absorb the edit if the fields do not

> match.

Very good suggestion, this does indeed sound like a much better design.
Thanks!
Alan Moore - 04 Mar 2005 11:10 GMT
>Hi,
>I'm using the UndoManager to undo/redo the input of a user to a form
[quoted text clipped - 16 lines]
>
>Thanks for any help, Jonck

I suppose the simplest solution would be to assign a separate
UndoManager to each textfield.  But it sounds like UndoManager is
overkill for this problem anyway.  It would prpbably be more
appropriate to create an action that restores the textfield to the
state it was in when the form was displayed, and bind the action to
(e.g.) the ESC key.
jonck@vanderkogel.net - 04 Mar 2005 12:58 GMT
The UndoManager is definitely not overkill, since I not only have
JTextFields on my form, but also JComboBoxes, JPanels with pictures,
etc... and I want the user to be able to have a long chain of
undos/redos available to him should he need them.

I have solved the problem btw, by turning things around. Instead of
telling the first edit of a textfield to absorb the next edits, I tell
the first edit that it should not replace the last edit and all
following edits of the same textfield will replace this edit. If the
user switches to a next textfield, a new edit that does not replace the
last one is sent to the UndoManager etc... In this way there is one
edit per textfield (per time the user focuses on that textfield) that
is able to restore the state to the last value it was at.

But anyway, I do find the current discussion interesting and I would
like to find an answer to it nonetheless, purely out of curiosity.

<back to the old problem>

If I do assign a seperate UndoManager to each textfield (leaving the
other components out of the formula for now), keeping track of which
undo/redo action is next in line would be quite a chore (imagine the
following situation: user makes a change to field1 --> edit1 of
UndoManager1, then he edits field2 --> edit2 of UndoManager2, then goes
back to field1 and does another edit --> edit2 of UndoManager1).
Doable, but it seems like a very inelegant solution imho.

Regards, Jonck
John McGrath - 05 Mar 2005 05:35 GMT
> But anyway, I do find the current discussion interesting and I would
> like to find an answer to it nonetheless, purely out of curiosity.

Based on your description of the code with the addEdit(), it should have
worked.  Without a runnable example of the problem, it is hard to tell
what is wrong.

Signature

Regards,

John McGrath



Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.