JTable/TableModel undoing

D

Deniz Dogan

Hello!

Does anyone know of a way to provide some sort of undo manager for
JTables or TableModels? This should of course also work for removing of
rows and adding of rows and not just the cells of the table.

-Deniz Dogan
 
T

Thomas Weidenfeller

Deniz said:
Hello!

Does anyone know of a way to provide some sort of undo manager for
JTables or TableModels?

The undo manager is not the problem. Java comes with
//javax.swing.undo.UndoManager//. The problem is that you will have to
implement your own //TableModel// that can talk to the manager by
providing the following.

a) Allows to register //UndoableEditListener//s with the model, so an
//UndoManager// can listen to changes.

b) Allows to de-register //UndoableEditListener//s with the model

c) Generates and fires //UndoableEditEvent//s for each relevant edit,

d) containing an //UndoableEdit// instance describing the edit and
providing the means to undo/redo it.

For bonus points:

e) Make the //UndoableEdit// a //CompoundEdit// for sequences of similar
and related edits (e.g. collapsing multiple single character deletions
into a "word deleted" edit).

All this is not much fun to implement.

The general problem you are facing here is that Sun once started with
the undo/redo system in the javax.swing.undo package, but didn't follow
through. They only implemented the necessary support in text components,
and skipped every other GUI elements.

<rant>
Unfortunately this is typical for Sun when it comes to Java. Them much
prefer to add a new shiny feature instead of first finishing what they
started :-(
</rant>

/Thomas
 
T

Thomas Weidenfeller

Thomas said:
The problem is that you will have to
implement your own //TableModel// that can talk to the manager by
providing the following.

A minor correction. You need a subclass of JTable to fire the
UndoableEditEvents, because they also need to report about
representation issues, like a change of a selection. And the TableModel
is not aware of such changes. But the TableModel is still part of the
game. A customized TableModel will likely need to support the customized
JTable for some types of edits, too.

/Thomas
 
M

Michael Rauscher

Thomas said:
The undo manager is not the problem. Java comes with
//javax.swing.undo.UndoManager//. The problem is that you will have to
implement your own //TableModel// that can talk to the manager by
providing the following.

a) Allows to register //UndoableEditListener//s with the model, so an
//UndoManager// can listen to changes.

b) Allows to de-register //UndoableEditListener//s with the model

c) Generates and fires //UndoableEditEvent//s for each relevant edit,

d) containing an //UndoableEdit// instance describing the edit and
providing the means to undo/redo it.

For bonus points:

e) Make the //UndoableEdit// a //CompoundEdit// for sequences of similar
and related edits (e.g. collapsing multiple single character deletions
into a "word deleted" edit).

Agreed with one exception: to me, e) makes little sense with respect to
tables. Either a cell (or row in the case of additions and deletions)
was updated or it wasn't.
All this is not much fun to implement.

But it's straight-forward if one implements a Decorator for ordinary
TableModels. Of course, in this case row additions/deletions get lost,
but I'd consider it as a starting point.

To substantiate my babbling, I've written a small implementation,
perhaps someone has some use for it ;) [should I GPL it now? - SCNR]

import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.undo.*;

public class UndoableTableModel extends AbstractTableModel
implements TableModelListener {
private TableModel delegate;
private UndoableEditSupport support = new UndoableEditSupport();

public UndoableTableModel( TableModel delegate ) {
setDelegate( delegate );
}

public void setDelegate( TableModel delegate ) {
if ( this.delegate != null )
this.delegate.removeTableModelListener( this );
this.delegate = delegate;
if ( this.delegate != null )
this.delegate.addTableModelListener( this );
fireTableStructureChanged();
}

public Class<?> getColumnClass( int columnIndex ) {
return delegate.getColumnClass( columnIndex );
}

public int getColumnCount() {
return delegate.getColumnCount();
}

public String getColumnName( int columnIndex ) {
return delegate.getColumnName( columnIndex );
}

public int getRowCount() {
return delegate.getRowCount();
}

public Object getValueAt( int rowIndex, int columnIndex ) {
return delegate.getValueAt( rowIndex, columnIndex );
}

public boolean isCellEditable( int rowIndex, int columnIndex ) {
return delegate.isCellEditable( rowIndex, columnIndex );
}

public void setValueAt( Object aValue,
int rowIndex, int columnIndex ) {
Object oldValue = delegate.getValueAt( rowIndex, columnIndex );
delegate.setValueAt( aValue, rowIndex, columnIndex );
Object newValue = delegate.getValueAt( rowIndex, columnIndex );
fireChangeEdit( rowIndex, columnIndex, oldValue, newValue );
}

public void addUndoableEditListener( UndoableEditListener l ) {
support.addUndoableEditListener( l );
}

public void removeUndoableEditListener( UndoableEditListener l ) {
support.removeUndoableEditListener( l );
}

public void tableChanged( TableModelEvent e ) {
TableModelEvent newEvent = new TableModelEvent( this,
e.getFirstRow(), e.getLastRow(), e.getColumn(),
e.getType() );
fireTableChanged( newEvent );
}

protected void fireChangeEdit( int row, int col, Object oldValue,
Object newValue ) {
UndoableEdit edit = new TableChangeEdit(row, col,
oldValue, newValue);
support.beginUpdate();
support.postEdit( edit );
support.endUpdate();
}

private class TableChangeEdit extends AbstractUndoableEdit {
private int columnIndex;
private int rowIndex;
private Object oldValue;
private Object newValue;

public TableChangeEdit( int rowIndex, int columnIndex,
Object oldValue, Object newValue ) {
this.columnIndex = columnIndex;
this.rowIndex = rowIndex;
this.oldValue = oldValue;
this.newValue = newValue;
}

public void undo() {
super.undo();
delegate.setValueAt( oldValue, rowIndex, columnIndex );
}

public void redo() {
super.redo();
delegate.setValueAt( newValue, rowIndex, columnIndex );
}
}
}

Bye
Michael
 
T

Thomas Weidenfeller

Michael said:
Agreed with one exception: to me, e) makes little sense with respect to
tables. Either a cell (or row in the case of additions and deletions)
was updated or it wasn't.

I was assuming table cells with editable text, where you probably want
to undo/redo text changes while you edit, but also undo/redo minor
operations like selection, deselection, and adjustments of selection.

E.g. what should happen if the user has edited some change in a cell,
the change has not been applied to the model, but the user invokes undo
(e.g. via a keyboard shortcut)? Here it seems to me that the JTable (and
not the TableModel) should have delivered change notifications for the
text edits to the undo manager. And these would be candidates for
/CompoundEdit/. Getting that right, however, would mean to have a close
look at the JTable's cell editing behavior. I am to lazy to do that :).
To substantiate my babbling, I've written a small implementation,
perhaps someone has some use for it ;) [should I GPL it now? - SCNR]

Ask a lawyer :)

/Thomas
 
M

Michael Rauscher

Thomas said:
I was assuming table cells with editable text, where you probably want
to undo/redo text changes while you edit, but also undo/redo minor
operations like selection, deselection, and adjustments of selection.

Your point.
To substantiate my babbling, I've written a small implementation,
perhaps someone has some use for it ;) [should I GPL it now? - SCNR]

Ask a lawyer :)

Perhaps Sun could lend me one :)

Bye
Michael
 
T

Thomas Weidenfeller

Michael said:
Perhaps Sun could lend me one :)

You do not want a Sun lawyer. These are the guys who write 70+ words of
gibberish in a single sentence, as can be witnessed in the JCP agreement.

/Thomas
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,013
Latest member
KatriceSwa

Latest Threads

Top