This discussion is archived
7 Replies Latest reply: Aug 19, 2010 8:34 AM by 843853 RSS

How to manage Undo with multiple models linked together

843853 Newbie
Currently Being Moderated
Hello,

I'm writing a java desktop application using MVC model. I'm using javax.swing.undo.UndoManager and related classes to manage the undo/redo capabilities. I know how to use it but I can't see how to implement it in a clean way when I have several models linked together. I hope someone can help...

I have 2 models M1 and M2. M2 depends on M1, so M2 listens to M1 changes.
Each model has its own view V1 and V2.
Model's methods which change the model's state create an UndoableEdit to restore the previous model's state.
UndoableEdits are combined together in a CompoundEdit created by the controller.

Upon a user action on V1:

1. M1 change
=> add UndoableEdit to current CompoundEdit
=> notify V1
=> notify M2
2. M2 change
=> add UndoableEdit to current CompoundEdit
=> notify V2

When we undo the CompoundEdit:

1. Undo M2 change
=> notify V2
2. Undo M1 change
=> notify V1
=> notify M2 !! PROBLEM
3. M2 change !! PROBLEM
=> notify V2 !! PROBLEM

And this is my problem: when undoing change on M1, I need to notify listeners (V1 and M2), but in this case M2 should not be notified because M2 has already restored its previous state in the CompoundEdit undo chain.

A solution might be to inhibit M2 listener if we're inside a undo/redo, but I feel it's not clean and not sure it will always work... Is there another way to solve this ? Or is it my architecture which is wrong ?

Thank you.

Edited by: gibi_31 on Aug 19, 2010 3:08 AM

Edited by: gibi_31 on Aug 19, 2010 3:11 AM
  • 1. Re: How to manage Undo with multiple models linked together
    jduprez Pro
    Currently Being Moderated
    Hello,
    Upon a user action on V1:

    1. M1 change
    => add UndoableEdit to current CompoundEdit
    => notify V1
    => notify M2
    2. M2 change
    => add UndoableEdit to current CompoundEdit
    Why?
    Do you need to be able to undo the M2 change alone in any other case than undoing M1's change?
    And if you don't add M2 change to the compound edit, doesn't the M2 update triggered by undoing the the M1 undo the M2 state automatically (thus performing the M2 undo)?
    And this is my problem: when undoing change on M1, I need to notify listeners (V1 and M2), but in this case M2 should not be notified because M2 has already restored its previous state in the CompoundEdit undo chain.
    A solution might be to inhibit M2 listener if we're inside a undo/redo, but I feel it's not clean and not sure it will always work...
    I agree it looks unclean (and quite dangerous actually if the app ever needs to support some form of multi-threaded model changes).

    Regards,

    J.
  • 2. Re: How to manage Undo with multiple models linked together
    843853 Newbie
    Currently Being Moderated
    jduprez wrote:
    Why?
    Do you need to be able to undo the M2 change alone in any other case than undoing M1's change?
    Yes M2 can change independently from M1.
    jduprez wrote:
    And if you don't add M2 change to the compound edit, doesn't the M2 update triggered by undoing the the M1 undo the M2 state automatically (thus performing the M2 undo)?
    No, for the same reason.


    To give an example:

    M1 and M2 are vectors filled with Integers (by default = 0)
    User can resize M1 and independently change the contents of M1 and M2.
    Size of M2 must always match the size of M1.

    Change1: user sets last Integer of M2 = 1;
    Change2: user reduces size of M1 by 1 (and indirectly reducing size of M2 as well)

    After undoing Change2, M1 and M2 should have their original size and M2's last Integer = 1

    This is what we get at end of step 2 of the CompoundEdit undo (see original post), but in my case we continue to Step 3 !
  • 3. Re: How to manage Undo with multiple models linked together
    843853 Newbie
    Currently Being Moderated
    When I think about it, the only "clean" solution I see for now is to make ALL listeners of M1 and M2 (it means V1 and V2) also create UndoableEdits to restore themselves their internal state. In this case I don't notify listeners anymore when a change is made as part of a undo/redo: when unrolling an undo each object will be asked to restore its state itself.

    But I feel this also weird: it means even simple stateless view components will have to implement undos ! mmm I must be wrong somewhere...
  • 4. Re: How to manage Undo with multiple models linked together
    782681 Newbie
    Currently Being Moderated
    I would consider Pessimistic or Optimistic Lock to solve problem like you describe.

    Brief descriptions for these are available at Fowler's site: [http://martinfowler.com/eaaCatalog/] If it appears well too brief to you, consider getting the book (PeAA) - if memory serves there was more detailed and much easier to follow explanation, using really helpful analogies with the way how version control systems handle conflicting/concurrent changes in source code.
  • 5. Re: How to manage Undo with multiple models linked together
    843853 Newbie
    Currently Being Moderated
    gnat wrote:
    I would consider Pessimistic or Optimistic Lock to solve problem like you describe.
    Sorry but I can't see how a Lock can help... Could you explain please ?

    For me a lock is useful to synchronize multiple access to a shared resource, but there is no resource to share here.
  • 6. Re: How to manage Undo with multiple models linked together
    782681 Newbie
    Currently Being Moderated
    - pessimistic: as soon as user starts working with M1, attempts to work M2 get rejected (until user ends work with M1)

    - optimistic: user is allowed to work with both, but after change in M1 attempts to change anything in M2 gets rejected or raise a conflict message / choice (iirc Fowler mentions providing options like abandon change, merge manual and merge automatic - though I don't recall much details)

    -----

    Did you ever had to deal with conflicting changes in version control system? the ones where you had to merge your code changes with ones done by someone else?
  • 7. Re: How to manage Undo with multiple models linked together
    843853 Newbie
    Currently Being Moderated
    Thanks for explanations gnat, but I think there is some confusion about what the problem is.
    gnat wrote:
    - pessimistic: as soon as user starts working with M1, attempts to work M2 get rejected (until user ends work with M1)
    A single undoable user action is directed either on M1 or M2, never both. So there can not be "attempts to work on M2".

    But because M2 depends on M1, a single user action on M1 can indirectly impact M2. And my problem is that undoing this kind of user action does not work with my current design. "Change2" above is an example of such user action.