Skip to content

Instantly share code, notes, and snippets.

@kkroesch
Created October 1, 2015 09:07
Show Gist options
  • Save kkroesch/6997e26e59b2ce295d52 to your computer and use it in GitHub Desktop.
Save kkroesch/6997e26e59b2ce295d52 to your computer and use it in GitHub Desktop.
Base class to enable undo-support for JTable
/*
* Copyright © 2005 by Karsten Kroesch.
*/
import java.util.List;
import java.awt.Toolkit;
import java.awt.datatransfer.*;
import java.io.IOException;
import javax.swing.event.UndoableEditListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEditSupport;
/**
* Base class to enable undo-support for <code>JTable</code>s.
*
* Simply inherit your table model from this class and implement the same methods
* as in <code>AbstractTableModel</code>. The objects held in this table are
* stored in <code>rowObjects</code>.
*
* Since version 2.0 this table model is instrumented for clipboard operations
* as well. Simply call the <code>cut</code>, <code>copy</code> and <code>paste</code>
* methods.
*
* @author Karsten Kroesch
* @version 2.0
*/
public abstract class AbstractUndoTableModel
extends AbstractTableModel
implements ClipboardOwner
{
/**
* The table data. The accessible fields of each object are stored in
* this Collection.
*/
protected List rowObjects;
/**
* The clipboard is the place where <code>cut</code>, <code>copy</code>
* and <code>paste</code> store their information.
*/
Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
public int getRowCount() {
return rowObjects.size();
}
/**
* Returns the number of accessible fields in your "row object".
*/
public abstract int getColumnCount();
/**
* Maps the table model <code>getValueAt</code> to the object's getter method.
*/
public abstract Object getValueAt(int row, int col);
/**
* This is the modified method with posting changes to undo manager.
* For your own modifications,
* override <code>updateValueAt(Object value, int row, int col)</code>.
*/
public final void setValueAt(Object value, int row, int col) {
undoableEditSupport.postEdit(new CommandEdit(row, col));
updateValueAt(value, row, col);
}
/**
* Maps the table model <code>getValueAt</code> to the object's setter method.
* You may <code>fireTableCellUpdated(row, col)</code> after your modifications
* at the end of your implementation for changes becoming visible instantly.
*/
protected abstract void updateValueAt(Object value, int row, int col);
/**
* By default, all cells are editable.
* Override this for customized behaviour.
*/
public boolean isCellEditable(int row, int col) {
return true;
}
/**
* Inserting a row into the table model before specified row.
*/
public void insertRow(Object rowObject, int row) {
undoableEditSupport.postEdit(new CommandInsert(row));
rowObjects.add(row, rowObject);
this.fireTableRowsInserted(row, row);
}
/**
* Removing a row from the table model
*/
public final void removeRow(int row) {
if (row < 0) {
// Ignore command
return;
}
else {
undoableEditSupport.postEdit(new CommandDelete(row));
rowObjects.remove(row);
this.fireTableRowsDeleted(row, row);
}
}
/**
* Notifies this object that it is no longer the owner of
* the contents of the clipboard.
*
* @param clipboard the clipboard that is no longer owned
* @param contents the contents which this owner had placed on the clipboard
*
* @since 2.0
*/
public void lostOwnership(Clipboard clipboard, Transferable contents) {
// Intentionally left blank
}
/**
* Moves the <code>rowObject</code> at given position (presumably selected row) to clipboard.
*
* @since 2.0
*/
public void cut(int row) {
systemClipboard.setContents(getTransferableAt(row), this);
removeRow(row);
}
/**
* Copies the <code>rowObject</code> at given position (presumably selected row) to clipboard.
*
* @since 2.0
*/
public void copy(int row) {
systemClipboard.setContents(getTransferableAt(row), this);
}
/**
* Inserts the clipboard's content before given position (presumably selected row) to clipboard.
*
* @throws UnsupportedFlavorException Should never happen since the method only succeeds if supported flavor matches.
* @throws IOException If the data is no longer available in the requested flavor.
* @since 2.0
*/
public void paste(int row)
throws UnsupportedFlavorException, IOException
{
Transferable clipboardContent = systemClipboard.getContents(this);
DataFlavor[] supportedDataFlavors = getTransferableAt(row).getTransferDataFlavors();
boolean isSupported = false;
DataFlavor supportedFlavor = null;
for (int index = 0; index < supportedDataFlavors.length; index++) {
isSupported = clipboardContent.isDataFlavorSupported(supportedDataFlavors[index]);
supportedFlavor = isSupported ? supportedDataFlavors[index] : null;
}
// Cannot insert
if (!isSupported) return;
insertRow(clipboardContent.getTransferData(supportedFlavor), row);
}
/**
* Returns the <code>Transferable</code> object for given row.
*
* This implementation returns only the String representation alue of the copied row.
* Override this to support cut/copy/paste operations for your desired concept classes.
*
* @see #TransferableTarget
*/
public Transferable getTransferableAt(int row) {
String value = rowObjects.get(row).toString();
return new StringSelection(value);
}
//
// declarations for undo support
//
protected UndoableEditSupport undoableEditSupport = new UndoableEditSupport(this);
public void addUndoableEditListener(UndoableEditListener undoableEditListener) {
undoableEditSupport.addUndoableEditListener(undoableEditListener);
}
public void removeUndoableEditListener(UndoableEditListener undoableEditListener) {
undoableEditSupport.removeUndoableEditListener(undoableEditListener);
}
/**
* Returns true, if value is valid for assignment to an element in passed
* column. Default version always returns true.
*/
public boolean isValidColumnValue(Object aValue, int row, int col) {
return true;
}
/**
* getter Method for rowObjects
* this Collection.
*/
public List getRowObjects(){
return rowObjects;
}
//
// Nested Classes
//
/**
* A command class for setting values in table cells
*/
public class CommandEdit
extends AbstractUndoableEdit {
Object value, savedValue;
int row, col;
public String getPresentationName() {
return "setValueAt(" + value + ", " + row + ", " + col + ")";
}
public CommandEdit(int row, int col) {
this.value = getValueAt(row, col);
this.row = row;
this.col = col;
}
public void redo() throws CannotRedoException {
super.redo();
if (savedValue == null) {
// Should never get here, as super() doesn't permit redoing
throw new CannotRedoException();
}
else {
updateValueAt(savedValue, row, col);
savedValue = null;
}
}
public void undo() throws CannotUndoException {
super.undo();
savedValue = getValueAt(row, col); // for redo operations
updateValueAt(value, row, col);
}
}
/**
* A command class for inserting rows
*/
public class CommandInsert
extends AbstractUndoableEdit {
Object savedRowObject;
int row;
public CommandInsert(int row) {
this.row = row;
}
public String getPresentationName() {
return "Inserted row " + row;
}
public void redo() throws CannotRedoException {
super.redo();
rowObjects.add(row, savedRowObject);
fireTableRowsInserted(row, row);
}
public void undo() throws CannotUndoException {
super.undo();
savedRowObject = rowObjects.get(row);
rowObjects.remove(row);
fireTableRowsDeleted(row, row);
}
}
/**
* A command class for deleting rows
*/
public class CommandDelete extends AbstractUndoableEdit {
Object savedRowObject;
int row;
public CommandDelete(int row) {
if (row < 0) {
System.out.println("could not delete row " + row);
}
else {
savedRowObject = rowObjects.get(row);
this.row = row;
}
}
public String getPresentationName() {
return "Deleted row " + row;
}
public void redo() throws CannotRedoException {
if (row < 0) {
System.out.println("could not redo row " + row);
}
else {
super.redo();
rowObjects.remove(row);
fireTableRowsDeleted(row, row);
}
}
public void undo() throws CannotUndoException {
if (row < 0) {
System.out.println("could not undo row " + row);
}
else {
super.undo();
rowObjects.add(row, savedRowObject);
fireTableRowsInserted(row, row);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment