Skip to main content

JXTreeTable repaint issue while using UndoManager

23 replies [Last post]
vladimir_m
Offline
Joined: 2010-09-08

I faced annoying issue with JXTreeTable repaint when using it with UndoManager.

When I undo operation related to JXTreeTable node insertion, it happens that CellEditor appears below line which was deleted. It is even possible to type in this cell editor. If you click on other place in a tree table, this cell editor disappears in few seconds.

I tried several kinds of TreeModelListener notifications, but nothing helps.
The only work-around I found it is collapse and expand related tree node.

I implemented example depicting this issue. I tried to make it small and clear, for easy understanding, but I'm not sure that it has been achieved :) sorry it is still very long.

(Double click on the line with -- edit here --, type something, press Enter and press Enter Ctrl+Z)

Thank you in advance,

-----------------------------
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;

import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.treetable.AbstractTreeTableModel;

/**
*
* @author vladimir
*/
public class DemoPanel extends JFrame {

private static int nodeCount = 0;

private UndoableEditSupport support;
private UndoManager manager;
private TTModel ttModel = new TTModel();

public DemoPanel() {
getContentPane().setLayout(new BorderLayout());
JXTreeTable treeTable = new JXTreeTable(ttModel);
JScrollPane sp = new JScrollPane();
sp.setViewportView(treeTable);
getContentPane().add(sp, BorderLayout.CENTER);
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setSize(600, 400);
setLocationRelativeTo(null);

manager = new UndoManager();
Action undoAction = new UndoAction();
Action redoAction = new RedoAction();

support = new UndoableEditSupport(ttModel);
support.addUndoableEditListener(manager);
treeTable.expandRow(0);
// Assign the actions to keys
((JPanel)getContentPane()).registerKeyboardAction(undoAction,
KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW);
((JPanel)getContentPane()).registerKeyboardAction(redoAction,
KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW);

}

public static void main(String args[]) {
new DemoPanel().setVisible(true);
}

private class TTModel extends AbstractTreeTableModel {

Node rootNode;

TTModel() {
rootNode = new Node();
Node node1 = new Node();
rootNode.addChild(node1);
node1.addChild(new Node());
node1.addChild(new Node());
rootNode.addChild(new EditableLeaf(node1));
}

@Override
public Object getRoot() {
return rootNode;
}

public int getColumnCount() {
return 2;
}

public Object getValueAt(Object o, int i) {
return ((Node)o).getValueAt(i);
}

public Object getChild(Object o, int index) {
return ((Node)o).getChild(index);
}

public int getChildCount(Object o) {
return ((Node)o).getChildCount();
}

public int getIndexOfChild(Object o, Object child) {
return ((Node)o).getIndexOfChild(child);
}

@Override
public boolean isCellEditable(Object o, int column) {
return ((Node)o).isEditable(column);
}

@Override
public void setValueAt(Object value, Object node, int column) {
UndoableEdit edit = ((Node)node).editName(value.toString());
edit.redo();
support.postEdit(edit);
}

private void structureChanged(Node friendNode) {
for (TreeModelListener l : getTreeModelListeners()) {
l.treeStructureChanged(new TreeModelEvent(this, new Object[]{rootNode, friendNode}));
}
}

}

private class Node {

List children = new ArrayList();
String name;

Node() {
name = "Node" + nodeCount;
nodeCount++;
}

Node(Object [] _children) {
this();
this.children.addAll(Arrays.asList(_children));
}

void addChild(Node node) {
children.add(node);
}

void removeChild(Node node) {
children.remove(node);
}

Object getValueAt(int i) {
if (i == 0)
return name;
return "1";
}

Object getChild(int i) {
return children.get(i);
}

int getChildCount() {
return children.size();
}

int getIndexOfChild(Object child) {
return children.indexOf(child);
}

UndoableEdit editName(final String nName) {
return null;
}

public void setName(String nName) {
name = nName;
}

boolean isEditable(int column) {
return true;
}
}

private class EditableLeaf extends Node {

private Node friendNode;

EditableLeaf(Node node) {
this.friendNode = node;
}

Object getValueAt(int i) {
if (i == 0)
return "-- edit here --";
return "";
}

boolean isEditable(int column) {
if (column == 0)
return true;
return false;
}

UndoableEdit editName(final String nName) {
return new AbstractUndoableEdit() {

private Node node = new Node();

public boolean canRedo() { return true; }
public boolean canUndo() { return true; }

public void redo() throws CannotRedoException {
node.setName(nName);
friendNode.addChild(node);
ttModel.structureChanged(friendNode);
}

public void undo() throws CannotUndoException {
friendNode.removeChild(node);
ttModel.structureChanged(friendNode);
}
};

}

}

private class UndoAction extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
try {
manager.undo();
} catch (CannotUndoException e) { }
}
}

private class RedoAction extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
try {
manager.redo();
} catch (CannotRedoException e) { }
}
}

}

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
vladimir_m
Offline
Joined: 2010-09-08

Karl,

thank you for your attention to my issue and your work for SwingLabs.
Your advise about using DefaultTreeTableModel is very useful and I'm going to do some refactoring in my code on this weekends.
The only thing I need to figure out it is how to implement undo/redo support with them gracefully.

With Best Regards,
Vladimir

kschaefe
Offline
Joined: 2006-06-08

Sorry, we're in a semantic debate about the best way to tackle the problem. The simple way would be to subclass DefaultTreeTableModel add UndoSupport. Override each mutator, obtain the original value, set the new value, post the edit with undo support. Ensure that your UndoManager is an UndoableEditListener on the model.

Karl

vladimir_m
Offline
Joined: 2010-09-08

Karl,

I'm new to this forum. I think that my question is answered, however I have some other questions to discuss concerning UndoManager support. How do you think, should I mark this topic as answered and post another topic ?

Vladimir.

kschaefe
Offline
Joined: 2006-06-08

Yes. And if the it question is not related to SwingLabs/SwingX, you should post it on the standard AWT/Swing forum.

Karl

vladimir_m
Offline
Joined: 2010-09-08

aephyr,

Thank you for your advise, using of putClientProperty("JTable.autoStartsEdit", false) does solve the problem.
I will figure out how to extend isCellEditable(EventObject) method.

Thank you for your time and code. It is very useful.

vladimir_m
Offline
Joined: 2010-09-08

Please take a look on following code. It became nice and clean with using DefaultTreeTableModel, however problem still the same.

Please help.

[code]
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
import org.jdesktop.swingx.treetable.TreeTableModel;

/**
*
* @author vladimir
*/
public class DefaultModelsSample extends JFrame {

private static int nodeCount = 0;

private UndoableEditSupport support;
private UndoManager manager;

public DefaultModelsSample() {
getContentPane().setLayout(new BorderLayout());
JXTreeTable treeTable = new JXTreeTable(createModel());
JScrollPane sp = new JScrollPane();
sp.setViewportView(treeTable);
getContentPane().add(sp, BorderLayout.CENTER);
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setSize(600, 400);
setLocationRelativeTo(null);

manager = new UndoManager();
Action undoAction = new UndoAction();
Action redoAction = new RedoAction();

support = new UndoableEditSupport(this);
support.addUndoableEditListener(manager);
treeTable.expandRow(0);
// Assign the actions to keys
((JPanel)getContentPane()).registerKeyboardAction(undoAction,
KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW);
((JPanel)getContentPane()).registerKeyboardAction(redoAction,
KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW);

}

private TreeTableModel createModel() {
final DefaultTreeTableModel model = new DefaultTreeTableModel();
final DefaultMutableTreeTableNode root = new DefaultMutableTreeTableNode("root",true);
final DefaultMutableTreeTableNode child = new DefaultMutableTreeTableNode("node "+nodeCount++, true);
child.add(new DefaultMutableTreeTableNode("subnode " +nodeCount++, false));
child.add(new DefaultMutableTreeTableNode("subnode " +nodeCount++, false));
root.add(child);
root.add(new DefaultMutableTreeTableNode("edit here") {

@Override
public void setValueAt(final Object aValue, int column) {
UndoableEdit edit = new AbstractUndoableEdit() {
private DefaultMutableTreeTableNode node
= new DefaultMutableTreeTableNode(aValue, true);

public boolean canRedo() { return true; }
public boolean canUndo() { return true; }

public void redo() throws CannotRedoException {
int idx = child.getChildCount();
model.insertNodeInto(node, child, idx);
}

public void undo() throws CannotUndoException {
model.removeNodeFromParent(node);
}
};
edit.redo();
support.postEdit(edit);
}
});
model.setRoot(root);
return model;
}

public static void main(String args[]) {
new DefaultModelsSample().setVisible(true);
}

private class UndoAction extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
try {
manager.undo();
} catch (CannotUndoException e) { }
}
}

private class RedoAction extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
try {
manager.redo();
} catch (CannotRedoException e) { }
}
}
}
[/code]

vladimir_m
Offline
Joined: 2010-09-08

Hi martinm1000,

thank you for your attention and recommendation. I reworked this sample using AbstractTreeTable.modelSupport.fireXXX methods, but this doesn't help.
Actually in my real application I'm using more precise ways instead of structureChanged. As I can see I changed my example to [b]fireChildAdded[/b] and [b]fireChildRemoved[/b] events, still same problem with repaint.

So I think it is a bug in tree table implementation.

[code]
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.tree.TreePath;

import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.treetable.AbstractTreeTableModel;

/**
*
* @author vladimir
*/
public class DemoPanel extends JFrame {

private static int nodeCount = 0;

private UndoableEditSupport support;
private UndoManager manager;
private TTModel ttModel = new TTModel();

public DemoPanel() {
getContentPane().setLayout(new BorderLayout());
JXTreeTable treeTable = new JXTreeTable(ttModel);
JScrollPane sp = new JScrollPane();
sp.setViewportView(treeTable);
getContentPane().add(sp, BorderLayout.CENTER);
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setSize(600, 400);
setLocationRelativeTo(null);

manager = new UndoManager();
Action undoAction = new UndoAction();
Action redoAction = new RedoAction();

support = new UndoableEditSupport(ttModel);
support.addUndoableEditListener(manager);
treeTable.expandRow(0);
// Assign the actions to keys
((JPanel)getContentPane()).registerKeyboardAction(undoAction,
KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW);
((JPanel)getContentPane()).registerKeyboardAction(redoAction,
KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW);

}

public static void main(String args[]) {
new DemoPanel().setVisible(true);
}

private class TTModel extends AbstractTreeTableModel {

Node rootNode;

TTModel() {
rootNode = new Node();
Node node1 = new Node();
rootNode.addChild(node1);
node1.addChild(new Node());
node1.addChild(new Node());
rootNode.addChild(new EditableLeaf(node1));
}

@Override
public Object getRoot() {
return rootNode;
}

public int getColumnCount() {
return 2;
}

public Object getValueAt(Object o, int i) {
return ((Node)o).getValueAt(i);
}

public Object getChild(Object o, int index) {
return ((Node)o).getChild(index);
}

public int getChildCount(Object o) {
return ((Node)o).getChildCount();
}

public int getIndexOfChild(Object o, Object child) {
return ((Node)o).getIndexOfChild(child);
}

@Override
public boolean isCellEditable(Object o, int column) {
return ((Node)o).isEditable(column);
}

@Override
public void setValueAt(Object value, Object node, int column) {
UndoableEdit edit = ((Node)node).editName(value.toString());
edit.redo();
support.postEdit(edit);
}

public void nodeRemoved(Node friendNode, Node node, int idx) {
modelSupport.fireChildRemoved(new TreePath(new Object[] {rootNode, friendNode}), idx, node);
}

public void nodeAdded(Node friendNode, Node node, int idx) {
modelSupport.fireChildAdded(new TreePath(new Object[] {rootNode, friendNode}), idx, node);
}

}

private class Node {

List children = new ArrayList();
String name;

Node() {
name = "Node" + nodeCount;
nodeCount++;
}

Node(Object [] _children) {
this();
this.children.addAll(Arrays.asList(_children));
}

int addChild(Node node) {
if (children.add(node))
return children.indexOf(node);
else
return -1;
}

void removeChild(Node node) {
children.remove(node);
}

Object getValueAt(int i) {
if (i == 0)
return name;
return "1";
}

Object getChild(int i) {
return children.get(i);
}

int getChildCount() {
return children.size();
}

int getIndexOfChild(Object child) {
return children.indexOf(child);
}

UndoableEdit editName(final String nName) {
return null;
}

public void setName(String nName) {
name = nName;
}

boolean isEditable(int column) {
return true;
}
}

private class EditableLeaf extends Node {

private Node friendNode;

EditableLeaf(Node node) {
this.friendNode = node;
}

Object getValueAt(int i) {
if (i == 0)
return "-- edit here --";
return "";
}

boolean isEditable(int column) {
if (column == 0)
return true;
return false;
}

UndoableEdit editName(final String nName) {
return new AbstractUndoableEdit() {

private Node node = new Node();

public boolean canRedo() { return true; }
public boolean canUndo() { return true; }

public void redo() throws CannotRedoException {
node.setName(nName);
int idx = friendNode.addChild(node);
ttModel.nodeAdded(friendNode, node, idx);
}

public void undo() throws CannotUndoException {
int idx = friendNode.getIndexOfChild(node);
friendNode.removeChild(node);
ttModel.nodeRemoved(friendNode, node, idx);
}
};

}

}

private class UndoAction extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
try {
manager.undo();
} catch (CannotUndoException e) { }
}
}

private class RedoAction extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
try {
manager.redo();
} catch (CannotRedoException e) { }
}
}

}
[/code]

kschaefe
Offline
Joined: 2006-06-08

Per my earlier suggestion, use DefaultTreeTableModel. Is there something it's lacking that you need? It seems to be me like it would solve your issues about notification.

Karl

vladimir_m
Offline
Joined: 2010-09-08

Karl,

I'm looking for a way how to make TreeTableModel changes undoable. In my sample I can undo operation which starts from TreeTableModel.setValueAt() (user entering text for a new node and after editing new node appears). I can't figure out how it is possible with DefaultMutableTreeTableModel.

Also, I noticed in swingx-1.6.1 the following section about DefaultMutableTreeTable.

"DefaultTreeTableModel is a concrete implementation of AbstractTreeTableModel and is provided purely as a convenience. Applications that use JXTreeTable [b]are expected to provide their own implementation[/b] of a TreeTableModel."

Together with DefaultMutableTreeTableNode following part in DefaultMutableTreeTableNode documentaion, makes me doubt about using DefaultMutableTreeTable.

"A default implementation of an AbstractMutableTreeTableNode that returns getUserObject().toString() for all value queries. This implementation is designed mainly for testing. It is NOT recommended to use this implementation. Any user that needs to create TreeTableNodes should consider directly extending AbstractMutableTreeTableNode or directly implementing the interface."

However, I will try to use your advise and I will check does this happens if DefaultTreeTableModel is used.

Thank you for your advise,

Vladimir

kschaefe
Offline
Joined: 2006-06-08

Vladimir,

DefaultTreeTableModel should say:
DefaultTreeTableModel is a concrete implementation of AbstractTreeTableModel and is provided purely as a convenience for use with TreeTableNodes. Applications that use JXTreeTable without TreeTableNodes are expected to provide their own implementation of a TreeTableModel.

I have update the JavaDoc to reflect that.

You are NEVER supposed to use DefaultMutableTreeTableNode and the JavaDoc is correct. You should extend AbstractMutableTreeTableNode. Given that you designed a node, you should redesigned that as a subclass of AbstractMutableTreeTableNode. Doing so will give you a model that is guaranteed to update correctly.

As for adding UndoManager capabilities, I think the best thing is to build a wrapper for the model that handles adding the UndoableEdits to the UndoManager. This way the model and the undoable can be handled in separate layers. First, you are sure that the base model works. Then, you can be sure that the undo works.

Thinking out loud, should we provide wrappers for handle undoable events in SwingX?

Karl

aephyr
Offline
Joined: 2009-11-20

@OP

The reason the cell editor becomes visible is because JTable starts editing when you press any key. You have to subclass the cell editor and override the isCellEditable(EventObject) method to change that behavior. Alternatively you can call putClientProperty("JTable.autoStartsEdit", false) on the tree table but I doubt that is actually documented so may not work in the future.

As far as the undoable implementation goes, it should be done in the model. AbstractTreeTableModel really goes out of its way to make it as painful as possible to do so. Major facepalm @ TreeModelSupport. Anyway a proper implementation would be more along the lines of:
[code]
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import javax.swing.undo.*;
import org.jdesktop.swingx.*;
import org.jdesktop.swingx.treetable.*;

public class DefaultModelSample extends JFrame {

private static int nodeCount = 0;

private UndoManager manager;

public DefaultModelSample() {
manager = new UndoManager();
JXTreeTable treeTable = new JXTreeTable(createModel());
treeTable.putClientProperty("JTable.autoStartsEdit", false);
Action deleteAction = new AbstractAction("Delete") {
public void actionPerformed(ActionEvent e) {
JXTreeTable treeTable = (JXTreeTable)e.getSource();
int row = treeTable.getSelectedRow();
TreePath path = treeTable.getPathForRow(row);
MutableTreeTableNode node = (MutableTreeTableNode)path.getLastPathComponent();
Model model = (Model)treeTable.getTreeTableModel();
model.removeNodeFromParent(node);
}
};
treeTable.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), deleteAction);
treeTable.getActionMap().put(deleteAction, deleteAction);
JScrollPane sp = new JScrollPane();
sp.setViewportView(treeTable);
getContentPane().add(sp, BorderLayout.CENTER);
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setSize(600, 400);
setLocationRelativeTo(null);

Action undoAction = new UndoAction();
Action redoAction = new RedoAction();

treeTable.expandRow(0);
// Assign the actions to keys
((JPanel)getContentPane()).registerKeyboardAction(undoAction,
KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW);
((JPanel)getContentPane()).registerKeyboardAction(redoAction,
KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW);
}

private Model createModel() {
final Model model = new Model();
model.addUndoableListener(manager);
final DefaultMutableTreeTableNode root = new DefaultMutableTreeTableNode("root",true);
final DefaultMutableTreeTableNode child = new DefaultMutableTreeTableNode("node "+nodeCount++, true);
child.add(new DefaultMutableTreeTableNode("subnode " +nodeCount++, false));
child.add(new DefaultMutableTreeTableNode("subnode " +nodeCount++, false));
root.add(child);
model.setRoot(root);
return model;
}

public static void main(String args[]) {
new DefaultModelSample().setVisible(true);
}

private class UndoAction extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
try {
manager.undo();
} catch (CannotUndoException e) { }
}
}

private class RedoAction extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
try {
manager.redo();
} catch (CannotRedoException e) { }
}
}

private static class Model extends DefaultTreeTableModel {

protected EventListenerList listenerList = new EventListenerList();

public void addUndoableListener(UndoableEditListener l) {
listenerList.add(UndoableEditListener.class, l);
}

public void removeUndoableListener(UndoableEditListener l) {
listenerList.remove(UndoableEditListener.class, l);
}

@Override
public void insertNodeInto(MutableTreeTableNode newChild,
MutableTreeTableNode parent, int index) {
super.insertNodeInto(newChild, parent, index);
fireNodeJourney(Type.INSERT, newChild, parent, index);
}

@Override
public void removeNodeFromParent(MutableTreeTableNode node) {
TreeTableNode parent = node.getParent();
int index = parent.getIndex(node);
super.removeNodeFromParent(node);
if (parent instanceof MutableTreeTableNode)
fireNodeJourney(Type.REMOVE, node, (MutableTreeTableNode)parent, index);
}

@Override
public void valueForPathChanged(TreePath path, Object newValue) {
// need to do something here ?
System.out.println("valueForPathChanged");
super.valueForPathChanged(path, newValue);
}

@Override
public void setValueAt(Object value, Object node, int column) {
MutableTreeTableNode tNode = (MutableTreeTableNode)node;
Object oldValue = tNode.getValueAt(column);
super.setValueAt(value, node, column);
fireNodeChanged(tNode, column, oldValue);
}

protected void fireNodeJourney(Type type, MutableTreeTableNode node, MutableTreeTableNode parent, int index) {
Object[] listeners = listenerList.getListenerList();
UndoableEditEvent event = null;
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==UndoableEditListener.class) {
if (event == null)
event = new UndoableEditEvent(this, new TreeNodeEdit(type, node, parent, index));
((UndoableEditListener)listeners[i+1]).undoableEditHappened(event);
}
}
}

protected void fireNodeChanged(MutableTreeTableNode node, int column, Object oldValue) {
Object[] listeners = listenerList.getListenerList();
UndoableEditEvent event = null;
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==UndoableEditListener.class) {
if (event == null)
event = new UndoableEditEvent(this, new TreeNodeEdit(node, column, oldValue));
((UndoableEditListener)listeners[i+1]).undoableEditHappened(event);
}
}
}

void superInsertNodeInto(MutableTreeTableNode newChild, MutableTreeTableNode parent, int index) {
super.insertNodeInto(newChild, parent, index);
}

void superRemoveNodeFromParent(MutableTreeTableNode node) {
super.removeNodeFromParent(node);
}

void superSetValueAt(Object value, Object node, int column) {
super.setValueAt(value, node, column);
}

public static enum Type {
INSERT, REMOVE, UPDATE
}

private class TreeNodeEdit extends AbstractUndoableEdit {

TreeNodeEdit(Type type, MutableTreeTableNode node, MutableTreeTableNode parent, int index) {
this.type = type;
this.node = node;
this.parent = parent;
this.index = index;
}

TreeNodeEdit(MutableTreeTableNode node, int column, Object oldValue) {
this.type = Type.UPDATE;
this.node = node;
this.index = column;
this.oldValue = oldValue;
}

private Type type;

private MutableTreeTableNode node;

private MutableTreeTableNode parent;

private int index;

private Object oldValue;

public void undo() {
switch (type) {
case INSERT: remove(); break;
case REMOVE: insert(); break;
case UPDATE: update(); break;
}
}

public void redo() {
switch (type) {
case INSERT: insert(); break;
case REMOVE: remove(); break;
case UPDATE: update(); break;
}
}

private void insert() {
superInsertNodeInto(node, parent, index);
}

private void remove() {
superRemoveNodeFromParent(node);
}

private void update() {
Object value = getValueAt(node, index);
superSetValueAt(oldValue, node, index);
oldValue = value;
}
}
}
}
[/code]

kschaefe
Offline
Joined: 2006-06-08

You need to be careful exposing TreeEdit to the UndoManager. As an inner class it could potentially keep the entire tree table model and its contents in memory, even after the tree table model is remove from the tree table. This could lead to a memory leak as UndoManager has no good way to force and edit to be removed.

If you keep it as an inner class, there is no need to have the superXXX methods; simply call Model.super.XXX instead. It's less krufty.

I still don't see a reason that the subclass approach is any better than a model wrapper. Your subclass is only usable with DefaultTreeTableModels and TreeTableNodes. Almost every model I have ever created already uses objects that have a predefined tree nature. Creating custom models to wrap that nature is much easier than wrapping the objects in TreeTableNodes.

Karl

aephyr
Offline
Joined: 2009-11-20

> You need to be careful exposing TreeEdit to the
> UndoManager. As an inner class it could potentially
> keep the entire tree table model and its contents in
> memory, even after the tree table model is remove
> from the tree table. This could lead to a memory
> leak as UndoManager has no good way to force and edit
> to be removed.

There is the protected void trimEdits(int from, int to) method. Obviously you'd have to subclass UndoManager to use it. A model change for a tree table that supports undo should be included in the undo stack anyway IMO.

> If you keep it as an inner class, there is no need to
> have the superXXX methods; simply call
> Model.super.XXX instead. It's less krufty.
Good point.

> I still don't see a reason that the subclass approach
> is any better than a model wrapper. Your subclass is
> only usable with DefaultTreeTableModels and
> TreeTableNodes. Almost every model I have ever
> created already uses objects that have a predefined
> tree nature. Creating custom models to wrap that
> nature is much easier than wrapping the objects in
> TreeTableNodes.
>
> Karl

Ideally I would have wanted to extend AbstractTreeTableModel, but because of TreeModelSupport, I had no way to override the fireChildAdded/Removed methods. Your wrapper approach is going to have the same problem.

Edit: The only mutate method in TreeTableModel is the setValueAt method. So the only thing a wrapper could support is value changes.

Message was edited by: aephyr

kschaefe
Offline
Joined: 2006-06-08

> Ideally I would have wanted to extend
> AbstractTreeTableModel, but because of
> TreeModelSupport, I had no way to override the
> fireChildAdded/Removed methods. Your wrapper approach
> is going to have the same problem.

Why? I don't see what advantage that has. You don't need any of that information to create an edit.

Karl

aephyr
Offline
Joined: 2009-11-20

> Why? I don't see what advantage that has. You don't
> need any of that information to create an edit.
>
> Karl
Define edit. I consider node insertion/removal to be edits. You certainly need that information if you are going to include those.

kschaefe
Offline
Joined: 2006-06-08

I do to. And I still don't see the problem.

Karl

aephyr
Offline
Joined: 2009-11-20

There are no methods in TreeTableModel for inserting/removing nodes so there is not any method to wrap to fire an UndoableEditEvent for insertion/deletion of nodes...

kschaefe
Offline
Joined: 2006-06-08

True. But, you're not thinking about this from a framework standpoint. What's missing? Swing data models are not CRUD-based interfaces; they only support RU. Adding a set of mutable data models would allow us to create fully CRUDable interfaces.

You can easily reverse engineer such models, using the DefaultXXXModels.
DefaultTableModel:
- addRow
- insertRow
- deleteRow
- moveRow (if you want to be exotic)
DefaultTreeModel/DefaultTreeTableModel
- setRoot
- insertNodeInto
- removeNodeFromParent

Once in place, it's easy to tackle wrapping any model.

Karl

aephyr
Offline
Joined: 2009-11-20

So you create CRUDTreeTableModel and UndoableCRUDTreeTableModel that wraps a CRUDTreeTableModel. Why not just add the undoable support to CRUDTreeTableModel? Better yet, add it to AbstractTreeTableModel That way it will work retroactively with the tree table models already in existence that extend AbstractTreeTableModel.

Edit: Just remembered that AbstractTreeTableModel wouldn't work as it doesn't contain the insert/remove node methods.

Message was edited by: aephyr

kschaefe
Offline
Joined: 2006-06-08

I don't think that it is appropriate to add undo support in the most basic abstract class; there is conceptual weight that is not necessary. Willing to entertain arguments to the contrary. Though I do agree that undo support should not have to be created by users, it should be easily handled by core Swing. Unfortunately, it's not.

Karl

martinm1000
Offline
Joined: 2003-06-12

You should use AbstractTreeTableModel.modelSupport.fireXXX to fire events.
I can't hack this quickly in your code, it looks like there is stuff missing. But I think this might help you. I'm thinking that you should send more precise events and not just rely on a structureChanged event.

Also, you can use "[ code ][ / code ]" tags to post code next time !

kschaefe
Offline
Joined: 2006-06-08

Since you are creating Nodes, you should use DefaultTreeTableModel and create your node as a subclass of AbstractMutableTreeTableNode. The heavy lifting has been done for you in these classes. You are redoing a lot of work here.

Karl

vladimir_m
Offline
Joined: 2010-09-08

Hi Karl,

thank you for your advise, you are right, I should extends AbstractMutableTreeTableNode, I will think about to change my real application code with your advise. However, I'm sure this will note cure this problem.

Vladimir.