From c263aa45a89f8c97d21b6271947336023a7ac31f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sat, 5 Oct 2024 00:33:40 +0200 Subject: [PATCH] Easy GUI - Library panel --- .../decompiler/flash/easygui/EasyGuiMain.java | 71 +++ .../flash/easygui/LibraryTreeTable.java | 265 ++++++++++ .../decompiler/flash/easygui/MainFrame.java | 75 +++ .../jpexs/decompiler/flash/easygui/View.java | 30 ++ src/com/jpexs/decompiler/flash/gui/Main.java | 5 + .../javagl/treetable/AbstractCellEditor.java | 135 +++++ .../treetable/AbstractTreeTableModel.java | 1 + src/de/javagl/treetable/JTreeTable.java | 461 ++++++++++++++++++ src/de/javagl/treetable/TreeTableModel.java | 125 +++++ .../treetable/TreeTableModelAdapter.java | 203 ++++++++ src/de/javagl/treetable/package-info.java | 9 + 11 files changed, 1380 insertions(+) create mode 100644 src/com/jpexs/decompiler/flash/easygui/EasyGuiMain.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/LibraryTreeTable.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/MainFrame.java create mode 100644 src/com/jpexs/decompiler/flash/easygui/View.java create mode 100644 src/de/javagl/treetable/AbstractCellEditor.java create mode 100644 src/de/javagl/treetable/AbstractTreeTableModel.java create mode 100644 src/de/javagl/treetable/JTreeTable.java create mode 100644 src/de/javagl/treetable/TreeTableModel.java create mode 100644 src/de/javagl/treetable/TreeTableModelAdapter.java create mode 100644 src/de/javagl/treetable/package-info.java diff --git a/src/com/jpexs/decompiler/flash/easygui/EasyGuiMain.java b/src/com/jpexs/decompiler/flash/easygui/EasyGuiMain.java new file mode 100644 index 000000000..19014492e --- /dev/null +++ b/src/com/jpexs/decompiler/flash/easygui/EasyGuiMain.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.easygui; + +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.gui.View; +import java.awt.GraphicsEnvironment; +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +/** + * + * @author JPEXS + */ +public class EasyGuiMain { + public static MainFrame mainFrame; + + public static void main(String[] args) { + + if (GraphicsEnvironment.isHeadless()) { + System.err.println("Error: Your system does not support Graphic User Interface"); + System.exit(1); + } + + System.setProperty("sun.java2d.d3d", "false"); + System.setProperty("sun.java2d.noddraw", "true"); + + System.setProperty("sun.java2d.uiScale", "1.0");; + + System.setProperty("sun.java2d.opengl", "false"); + + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException + | IllegalAccessException ignored) { + //ignored + } + + UIManager.put("Tree.expandedIcon", View.getIcon("expand16")); + UIManager.put("Tree.collapsedIcon", View.getIcon("collapse16")); + + mainFrame = new MainFrame(); + mainFrame.setVisible(true); + + try { + mainFrame.open(new File(args[1])); + } catch (IOException ex) { + Logger.getLogger(EasyGuiMain.class.getName()).log(Level.SEVERE, null, ex); + } catch (InterruptedException ex) { + Logger.getLogger(EasyGuiMain.class.getName()).log(Level.SEVERE, null, ex); + } + } +} diff --git a/src/com/jpexs/decompiler/flash/easygui/LibraryTreeTable.java b/src/com/jpexs/decompiler/flash/easygui/LibraryTreeTable.java new file mode 100644 index 000000000..5941ce90d --- /dev/null +++ b/src/com/jpexs/decompiler/flash/easygui/LibraryTreeTable.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2024 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.easygui; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import de.javagl.treetable.JTreeTable; +import de.javagl.treetable.TreeTableModel; +import java.awt.Component; +import javax.swing.JLabel; +import javax.swing.JTree; +import javax.swing.event.TreeModelListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.TreePath; + +/** + * + * @author JPEXS + */ +public class LibraryTreeTable extends JTreeTable { + + private SWF swf; + private LibraryTreeTableModel model; + + public LibraryTreeTable() { + super(new LibraryTreeTableModel(null)); + getTree().setCellRenderer(new LibraryTreeCellRenderer()); + getTree().setRootVisible(false); + getTree().setShowsRootHandles(true); + } + + public void setSwf(SWF swf) { + this.swf = swf; + setTreeTableModel(new LibraryTreeTableModel(swf)); + } + + + private static class LibraryTreeCellRenderer extends DefaultTreeCellRenderer { + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { + JLabel label = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + if (!leaf) { + if (expanded) { + label.setIcon(View.getIcon("folderopen16")); + } else { + label.setIcon(View.getIcon("folder16")); + } + } + if (value instanceof DefaultMutableTreeNode) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; + Object object = node.getUserObject(); + if (object instanceof ImageTag) { + ImageTag it = (ImageTag) object; + label.setIcon(View.getIcon("image16")); + label.setText("image " + it.getCharacterId() + it.getImageFormat().getExtension()); + } + if (object instanceof ShapeTag) { + ShapeTag st = (ShapeTag) object; + label.setIcon(View.getIcon("shape16")); + label.setText("graphic " + st.getCharacterId()); + } + if (object instanceof MorphShapeTag) { + MorphShapeTag mst = (MorphShapeTag) object; + label.setIcon(View.getIcon("morphshape16")); + label.setText("shapeTween " + mst.getCharacterId()); + } + if (object instanceof DefineSpriteTag) { + DefineSpriteTag st = (DefineSpriteTag) object; + label.setIcon(View.getIcon("sprite16")); + label.setText("movieClip " + st.getCharacterId()); + } + if (object instanceof SoundTag) { + SoundTag st = (SoundTag) object; + label.setIcon(View.getIcon("sound16")); + label.setText("sound" + (st.getCharacterId() == -1 ? "" : " " + st.getCharacterId())); + } + if (object instanceof DefineVideoStreamTag) { + DefineVideoStreamTag vt = (DefineVideoStreamTag) object; + label.setIcon(View.getIcon("movie16")); + label.setText("video " + vt.getCharacterId()); + } + } + return label; + } + } + + private static class LibraryTreeTableModel implements TreeTableModel { + + private DefaultMutableTreeNode root; + + public LibraryTreeTableModel(SWF swf) { + DefaultMutableTreeNode root = new DefaultMutableTreeNode("SWF"); + + DefaultMutableTreeNode imagesNode = new DefaultMutableTreeNode("images"); + DefaultMutableTreeNode graphicsNode = new DefaultMutableTreeNode("graphics"); + DefaultMutableTreeNode shapeTweensNode = new DefaultMutableTreeNode("shapeTweens"); + DefaultMutableTreeNode movieClipsNode = new DefaultMutableTreeNode("movieClips"); + DefaultMutableTreeNode soundsNode = new DefaultMutableTreeNode("sounds"); + DefaultMutableTreeNode videosNode = new DefaultMutableTreeNode("videos"); + + + this.root = root; + + if (swf == null) { + return; + } + for (Tag t : swf.getTags()) { + DefaultMutableTreeNode node = new DefaultMutableTreeNode(t); + if (t instanceof ImageTag) { + imagesNode.add(node); + } + if (t instanceof ShapeTag) { + graphicsNode.add(node); + } + if (t instanceof MorphShapeTag) { + shapeTweensNode.add(node); + } + if (t instanceof DefineSpriteTag) { + movieClipsNode.add(node); + } + if (t instanceof SoundTag) { + soundsNode.add(node); + } + if (t instanceof DefineVideoStreamTag) { + videosNode.add(node); + } + } + + if (!imagesNode.isLeaf()) { + root.add(imagesNode); + } + if (!graphicsNode.isLeaf()) { + root.add(graphicsNode); + } + if (!shapeTweensNode.isLeaf()) { + root.add(shapeTweensNode); + } + if (!movieClipsNode.isLeaf()) { + root.add(movieClipsNode); + } + if (!soundsNode.isLeaf()) { + root.add(soundsNode); + } + if (!videosNode.isLeaf()) { + root.add(videosNode); + } + } + + + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public String getColumnName(int column) { + switch (column) { + case 0: + return "Name"; + case 1: + return "AS Linkage"; + default: + return null; + } + } + + @Override + public Class getColumnClass(int column) { + switch (column) { + case 0: + return TreeTableModel.class; + default: + return String.class; + } + + } + + @Override + public Object getValueAt(Object node, int column) { + switch (column) { + case 0: + return node.toString(); + case 1: + return ""; + default: + return null; + } + } + + @Override + public boolean isCellEditable(Object node, int column) { + if (column == 0) { + return true; + } + return false; + } + + @Override + public void setValueAt(Object value, Object node, int column) { + + } + + @Override + public Object getRoot() { + return root; + } + + @Override + public Object getChild(Object parent, int index) { + return ((DefaultMutableTreeNode) parent).getChildAt(index); + } + + @Override + public int getChildCount(Object parent) { + return ((DefaultMutableTreeNode) parent).getChildCount(); + } + + @Override + public boolean isLeaf(Object node) { + return ((DefaultMutableTreeNode) node).isLeaf(); + } + + @Override + public void valueForPathChanged(TreePath path, Object newValue) { + + } + + @Override + public int getIndexOfChild(Object parent, Object child) { + return ((DefaultMutableTreeNode) parent).getIndex((DefaultMutableTreeNode)child); + } + + @Override + public void addTreeModelListener(TreeModelListener l) { + } + + @Override + public void removeTreeModelListener(TreeModelListener l) { + } + + } +} diff --git a/src/com/jpexs/decompiler/flash/easygui/MainFrame.java b/src/com/jpexs/decompiler/flash/easygui/MainFrame.java new file mode 100644 index 000000000..5a1da132e --- /dev/null +++ b/src/com/jpexs/decompiler/flash/easygui/MainFrame.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.easygui; + +import com.jpexs.decompiler.flash.SWF; +import de.javagl.treetable.JTreeTable; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.UIManager; + +/** + * + * @author JPEXS + */ +public class MainFrame extends JFrame { + private File file; + private SWF swf; + private LibraryTreeTable libraryTreeTable; + private JSplitPane splitPane; + public MainFrame() { + setTitle("JPEXS FFDec Easy GUI"); + setSize(1024, 768); + setDefaultCloseOperation(EXIT_ON_CLOSE); + + Container cnt = getContentPane(); + cnt.setLayout(new BorderLayout()); + + libraryTreeTable = new LibraryTreeTable(); + JScrollPane scrollPane = new JScrollPane(libraryTreeTable); + splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JPanel(), scrollPane); + scrollPane.getViewport().setBackground(UIManager.getColor("Tree.background")); + cnt.add(splitPane, BorderLayout.CENTER); + } + + public void open(File file) throws IOException, InterruptedException { + this.file = file; + try(FileInputStream fis = new FileInputStream(file)) { + swf = new SWF(fis, true); + } + + libraryTreeTable.setSwf(swf); + + } + + @Override + public void setVisible(boolean b) { + super.setVisible(b); + splitPane.setDividerLocation(0.7); + } + + +} diff --git a/src/com/jpexs/decompiler/flash/easygui/View.java b/src/com/jpexs/decompiler/flash/easygui/View.java new file mode 100644 index 000000000..a0e4ef7e5 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/easygui/View.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.easygui; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +/** + * + * @author JPEXS + */ +public class View { + public static Icon getIcon(String name) { + return new ImageIcon(com.jpexs.decompiler.flash.gui.View.class.getClassLoader().getResource("com/jpexs/decompiler/flash/gui/graphics/" + name + ".png")); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index d1a146d90..94e2b0cfd 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -44,6 +44,7 @@ import com.jpexs.decompiler.flash.configuration.SwfSpecificConfiguration; import com.jpexs.decompiler.flash.configuration.SwfSpecificCustomConfiguration; import com.jpexs.decompiler.flash.console.CommandLineArgumentParser; import com.jpexs.decompiler.flash.console.ContextMenuTools; +import com.jpexs.decompiler.flash.easygui.EasyGuiMain; import com.jpexs.decompiler.flash.exporters.modes.ExeExportMode; import com.jpexs.decompiler.flash.gfx.GfxConvertor; import com.jpexs.decompiler.flash.gui.debugger.DebugAdapter; @@ -2918,6 +2919,10 @@ public class Main { */ public static void main(String[] args) throws IOException { decodeLaunch5jArgs(args); + if (args.length > 0 && args[0].equals("-easy")) { + EasyGuiMain.main(args); + return; + } setSessionLoaded(false); diff --git a/src/de/javagl/treetable/AbstractCellEditor.java b/src/de/javagl/treetable/AbstractCellEditor.java new file mode 100644 index 000000000..70a5f511f --- /dev/null +++ b/src/de/javagl/treetable/AbstractCellEditor.java @@ -0,0 +1,135 @@ +/* + * www.javagl.de - JTreeTable + * + * Copyright (c) 2016 Marco Hutter - http://www.javagl.de + * + * This library is based on the code from the article "Creating TreeTables" + * by Sun Microsystems (now known as Oracle). + * + * The original copyright header: + * + * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Sun Microsystems nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package de.javagl.treetable; + +import java.util.EventObject; + +import javax.swing.CellEditor; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.EventListenerList; + + +/** + * Abstract base implementation of a CellEditor for a {@link JTreeTable}. + */ +public class AbstractCellEditor implements CellEditor +{ + /** + * The list of CellEditorListeners + */ + private final EventListenerList listenerList = new EventListenerList(); + + @Override + public Object getCellEditorValue() + { + return null; + } + + @Override + public boolean isCellEditable(EventObject e) + { + return true; + } + + @Override + public boolean shouldSelectCell(EventObject anEvent) + { + return false; + } + + @Override + public boolean stopCellEditing() + { + return true; + } + + @Override + public void cancelCellEditing() + { + // Empty default implementation + } + + @Override + public final void addCellEditorListener(CellEditorListener l) + { + listenerList.add(CellEditorListener.class, l); + } + + @Override + public final void removeCellEditorListener(CellEditorListener l) + { + listenerList.remove(CellEditorListener.class, l); + } + + /** + * Notify all listeners that have registered interest for notification on + * this event type. + */ + protected final void fireEditingStopped() + { + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) + { + if (listeners[i] == CellEditorListener.class) + { + ((CellEditorListener) listeners[i + 1]) + .editingStopped(new ChangeEvent(this)); + } + } + } + + /** + * Notify all listeners that have registered interest for notification on + * this event type. + */ + protected final void fireEditingCanceled() + { + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) + { + if (listeners[i] == CellEditorListener.class) + { + ((CellEditorListener) listeners[i + 1]) + .editingCanceled(new ChangeEvent(this)); + } + } + } +} diff --git a/src/de/javagl/treetable/AbstractTreeTableModel.java b/src/de/javagl/treetable/AbstractTreeTableModel.java new file mode 100644 index 000000000..312071118 --- /dev/null +++ b/src/de/javagl/treetable/AbstractTreeTableModel.java @@ -0,0 +1 @@ +/* * www.javagl.de - JTreeTable * * Copyright (c) 2016 Marco Hutter - http://www.javagl.de * * This library is based on the code from the article "Creating TreeTables" * by Sun Microsystems (now known as Oracle). * * The original copyright header: * * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of Sun Microsystems nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package de.javagl.treetable; import javax.swing.tree.*; import javax.swing.event.*; /** * An abstract implementation of the TreeTableModel interface, handling the list * of listeners.
*
* Note: In order to actually display the JTree in one column of the * JTreeTable, implementors of this class have to return the * TreeTableModel.class as the respective column class * in their implementation of the {@link #getColumnClass(int)} method: *

 * public Class<?> getColumnClass(int column)
 * {
 *     if (column == columnThatShouldContainTheTree)
 *     {
 *         return TreeTableModel.class;
 *     }
 *     // Return other types as desired:
 *     return Object.class;
 * }
 * 
*/ public abstract class AbstractTreeTableModel implements TreeTableModel { /** * The root node of the tree */ protected Object root; /** * The TreeModelListeners that will be notified about modifications */ private final EventListenerList listenerList = new EventListenerList(); /** * Default constructor * * @param root The root node of the tree */ protected AbstractTreeTableModel(Object root) { this.root = root; } @Override public Object getRoot() { return root; } @Override public boolean isLeaf(Object node) { return getChildCount(node) == 0; } @Override public void valueForPathChanged(TreePath path, Object newValue) { // Empty default implementation } @Override public int getIndexOfChild(Object parent, Object child) { for (int i = 0; i < getChildCount(parent); i++) { if (getChild(parent, i).equals(child)) { return i; } } return -1; } @Override public void addTreeModelListener(TreeModelListener l) { listenerList.add(TreeModelListener.class, l); } @Override public void removeTreeModelListener(TreeModelListener l) { listenerList.remove(TreeModelListener.class, l); } /** * Notify all listeners that have registered interest for notification on * this event type. The event instance is lazily created using the * parameters passed into the fire method. * * @param source The source of the event * @param path The tree path * @param childIndices The child indices * @param children The children */ protected final void fireTreeNodesChanged( Object source, Object[] path, int[] childIndices, Object[] children) { Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeModelListener.class) { if (e == null) { e = new TreeModelEvent( source, path, childIndices, children); } ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e); } } } /** * Notify all listeners that have registered interest for notification on * this event type. The event instance is lazily created using the * parameters passed into the fire method. * * @param source The source of the event * @param path The tree path * @param childIndices The child indices * @param children The children */ protected final void fireTreeNodesInserted( Object source, Object[] path, int[] childIndices, Object[] children) { Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeModelListener.class) { if (e == null) { e = new TreeModelEvent( source, path, childIndices, children); } ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e); } } } /** * Notify all listeners that have registered interest for notification on * this event type. The event instance is lazily created using the * parameters passed into the fire method. * * @param source The source of the event * @param path The tree path * @param childIndices The child indices * @param children The children */ protected final void fireTreeNodesRemoved( Object source, Object[] path, int[] childIndices, Object[] children) { Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeModelListener.class) { if (e == null) { e = new TreeModelEvent( source, path, childIndices, children); } ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e); } } } /** * Notify all listeners that have registered interest for notification on * this event type. The event instance is lazily created using the * parameters passed into the fire method. * * @param source The source of the event * @param path The tree path * @param childIndices The child indices * @param children The children */ protected final void fireTreeStructureChanged( Object source, Object[] path, int[] childIndices, Object[] children) { Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeModelListener.class) { if (e == null) { e = new TreeModelEvent( source, path, childIndices, children); } ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e); } } } @Override public boolean isCellEditable(Object node, int column) { return getColumnClass(column) == TreeTableModel.class; } @Override public void setValueAt(Object aValue, Object node, int column) { // Empty default implementation } } \ No newline at end of file diff --git a/src/de/javagl/treetable/JTreeTable.java b/src/de/javagl/treetable/JTreeTable.java new file mode 100644 index 000000000..fcded3c3e --- /dev/null +++ b/src/de/javagl/treetable/JTreeTable.java @@ -0,0 +1,461 @@ +/* + * www.javagl.de - JTreeTable + * + * Copyright (c) 2016 Marco Hutter - http://www.javagl.de + * + * This library is based on the code from the article "Creating TreeTables" + * by Sun Microsystems (now known as Oracle). + * + * The original copyright header: + * + * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Sun Microsystems nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package de.javagl.treetable; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.event.MouseEvent; +import java.util.EventObject; + +import javax.swing.JTable; +import javax.swing.JTree; +import javax.swing.ListSelectionModel; +import javax.swing.LookAndFeel; +import javax.swing.UIManager; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeSelectionModel; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +/** + * A JTreeTable is a table that uses a JTree as a renderer (and editor) for + * the cells in a particular column. It is backed by a {@link TreeTableModel} + * that provides the tree structure as well as the table data. + */ +public class JTreeTable extends JTable +{ + /** + * Serial UID + */ + private static final long serialVersionUID = 6063660027266471485L; + /** + * A special JTree that is used as a TableCellRenderer + */ + private final TreeTableCellRenderer tree; + + /** + * Creates a new JTreeTable that is backed by the given + * {@link TreeTableModel} + * + * @param treeTableModel The {@link TreeTableModel} + */ + public JTreeTable(TreeTableModel treeTableModel) + { + // Create the tree. It will be used as a renderer and editor. + tree = new TreeTableCellRenderer(treeTableModel); + + // Install a tableModel representing the visible rows in the tree. + super.setModel(new TreeTableModelAdapter(treeTableModel, tree)); + + // Force the JTable and JTree to share their row selection models. + ListToTreeSelectionModelWrapper selectionWrapper = + new ListToTreeSelectionModelWrapper(); + tree.setSelectionModel(selectionWrapper); + setSelectionModel(selectionWrapper.getListSelectionModel()); + + // Install the tree editor renderer and editor. + setDefaultRenderer(TreeTableModel.class, tree); + setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor()); + + // No grid. + setShowGrid(false); + + // No intercell spacing + setIntercellSpacing(new Dimension(0, 0)); + + // And update the height of the trees row to match that of + // the table. + if (tree.getRowHeight() < 1) + { + // Metal looks better like this. + setRowHeight(18); + } + } + + /** + * Overridden to message super and forward the method to the tree. Since the + * tree is not actually in the component hierarchy it will never receive + * this unless we forward it in this manner. + */ + @Override + public void updateUI() + { + super.updateUI(); + if (tree != null) + { + tree.updateUI(); + } + // Use the tree's default foreground and background colors in the + // table. + LookAndFeel.installColorsAndFont(this, + "Tree.background", "Tree.foreground", "Tree.font"); + } + + /* + * Workaround for BasicTableUI anomaly. Make sure the UI never tries to + * paint the editor. The UI currently uses different techniques to paint the + * renderers and editors and overriding setBounds() below is not the right + * thing to do for an editor. Returning -1 for the editing row in this case, + * ensures the editor is never painted. + */ + @Override + public int getEditingRow() + { + return (getColumnClass(editingColumn) == TreeTableModel.class) ? + -1 : editingRow; + } + + /** + * Overridden to pass the new rowHeight to the tree. + */ + @Override + public void setRowHeight(int rowHeight) + { + super.setRowHeight(rowHeight); + if (tree != null && tree.getRowHeight() != rowHeight) + { + tree.setRowHeight(getRowHeight()); + } + } + + /** + * Returns the tree that is being shared between the model. + * + * @return The tree + */ + public JTree getTree() + { + return tree; + } + + /** + * A TreeCellRenderer that displays a JTree. + */ + public class TreeTableCellRenderer extends JTree implements + TableCellRenderer + { + /** + * Serial UID + */ + private static final long serialVersionUID = -3458046584741784015L; + + /** + * Last table/tree row asked to renderer. + */ + private int visibleRow; + + /** + * Creates a new TreeTableCellRenderer that displays the JTree + * according to the given tree model + * + * @param model The tree model + */ + TreeTableCellRenderer(TreeModel model) + { + super(model); + } + + /** + * updateUI is overridden to set the colors of the Tree's renderer to + * match that of the table. + */ + @Override + public void updateUI() + { + super.updateUI(); + // Make the tree's cell renderer use the table's cell selection + // colors. + TreeCellRenderer tcr = getCellRenderer(); + if (tcr instanceof DefaultTreeCellRenderer) + { + DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr); + // For 1.1 uncomment this, 1.2 has a bug that will cause an + // exception to be thrown if the border selection color is + // null. + // dtcr.setBorderSelectionColor(null); + dtcr.setTextSelectionColor(UIManager + .getColor("Table.selectionForeground")); + dtcr.setBackgroundSelectionColor(UIManager + .getColor("Table.selectionBackground")); + } + } + + /** + * Sets the row height of the tree, and forwards the row height to the + * table. + */ + @Override + public void setRowHeight(int rowHeight) + { + if (rowHeight > 0) + { + super.setRowHeight(rowHeight); + if (JTreeTable.this.getRowHeight() != rowHeight) + { + JTreeTable.this.setRowHeight(getRowHeight()); + } + } + } + + /** + * This is overridden to set the height to match that of the JTable. + */ + @Override + public void setBounds(int x, int y, int w, int h) + { + super.setBounds(x, 0, w, JTreeTable.this.getHeight()); + } + + /** + * Sublcassed to translate the graphics such that the last visible row + * will be drawn at 0,0. + */ + @Override + public void paint(Graphics g) + { + g.translate(0, -visibleRow * getRowHeight()); + super.paint(g); + } + + /** + * TreeCellRenderer method. Overridden to update the visible row. + */ + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, boolean isSelected, boolean hasFocus, int row, + int column) + { + if (isSelected) + setBackground(table.getSelectionBackground()); + else + setBackground(table.getBackground()); + + visibleRow = row; + return this; + } + } + + /** + * TreeTableCellEditor implementation. Component returned is the JTree. + */ + public class TreeTableCellEditor extends AbstractCellEditor implements + TableCellEditor + { + @Override + public Component getTableCellEditorComponent(JTable table, + Object value, boolean isSelected, int r, int c) + { + return tree; + } + + /** + * Overridden to return false, and if the event is a mouse event it is + * forwarded to the tree. + *

+ * The behavior for this is debatable, and should really be offered as a + * property. By returning false, all keyboard actions are implemented in + * terms of the table. By returning true, the tree would get a chance to + * do something with the keyboard events. For the most part this is ok. + * But for certain keys, such as left/right, the tree will + * expand/collapse where as the table focus should really move to a + * different column. Page up/down should also be implemented in terms of + * the table. By returning false this also has the added benefit that + * clicking outside of the bounds of the tree node, but still in the + * tree column will select the row, whereas if this returned true that + * wouldn't be the case. + *

+ * By returning false we are also enforcing the policy that the tree + * will never be editable (at least by a key sequence). + */ + @Override + public boolean isCellEditable(EventObject e) + { + if (!(e instanceof MouseEvent)) + { + return false; + } + MouseEvent me = (MouseEvent) e; + for (int counter = getColumnCount() - 1; counter >= 0; counter--) + { + if (getColumnClass(counter) == TreeTableModel.class) + { + MouseEvent newME = + new MouseEvent(tree, me.getID(), me.getWhen(), + me.getModifiers(), me.getX() + - getCellRect(0, counter, true).x, + me.getY(), me.getClickCount(), + me.isPopupTrigger(), me.getButton()); + tree.dispatchEvent(newME); + break; + } + } + return false; + } + } + + /** + * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel to + * listen for changes in the ListSelectionModel it maintains. Once a change + * in the ListSelectionModel happens, the paths are updated in the + * DefaultTreeSelectionModel. + */ + class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel + { + /** + * Serial UID + */ + private static final long serialVersionUID = -3150534152238745922L; + + /** + * Set to true when we are updating the ListSelectionModel. + */ + protected boolean updatingListSelectionModel; + + /** + * Default constructor + */ + ListToTreeSelectionModelWrapper() + { + getListSelectionModel().addListSelectionListener( + new ListSelectionListener() + { + @Override + public void valueChanged(ListSelectionEvent e) + { + updateSelectedPathsFromSelectedRows(); + } + }); + } + + /** + * Returns the list selection model. ListToTreeSelectionModelWrapper + * listens for changes to this model and updates the selected paths + * accordingly. + * + * @return The list selection model + */ + ListSelectionModel getListSelectionModel() + { + return listSelectionModel; + } + + /** + * This is overridden to set updatingListSelectionModel and + * message super. This is the only place DefaultTreeSelectionModel + * alters the ListSelectionModel. + */ + @Override + public void resetRowSelection() + { + if (!updatingListSelectionModel) + { + updatingListSelectionModel = true; + try + { + super.resetRowSelection(); + } + finally + { + updatingListSelectionModel = false; + } + } + // Notice how we don't message super if + // updatingListSelectionModel is true. If + // updatingListSelectionModel is true, it implies the + // ListSelectionModel has already been updated and the + // paths are the only thing that needs to be updated. + } + + /** + * If updatingListSelectionModel is false, this will reset + * the selected paths from the selected rows in the list selection + * model. + */ + protected void updateSelectedPathsFromSelectedRows() + { + if (!updatingListSelectionModel) + { + updatingListSelectionModel = true; + try + { + // This is way expensive, ListSelectionModel needs an + // enumerator for iterating. + int min = listSelectionModel.getMinSelectionIndex(); + int max = listSelectionModel.getMaxSelectionIndex(); + + clearSelection(); + if (min != -1 && max != -1) + { + for (int counter = min; counter <= max; counter++) + { + if (listSelectionModel.isSelectedIndex(counter)) + { + TreePath selPath = tree.getPathForRow(counter); + + if (selPath != null) + { + addSelectionPath(selPath); + } + } + } + } + } + finally + { + updatingListSelectionModel = false; + } + } + } + + } + + //JPEXS + public void setTreeTableModel(TreeTableModel model) { + tree.setModel(model); + setModel(new TreeTableModelAdapter(model, tree)); + repaint(); + } +} diff --git a/src/de/javagl/treetable/TreeTableModel.java b/src/de/javagl/treetable/TreeTableModel.java new file mode 100644 index 000000000..7d8ed26bf --- /dev/null +++ b/src/de/javagl/treetable/TreeTableModel.java @@ -0,0 +1,125 @@ +/* + * www.javagl.de - JTreeTable + * + * Copyright (c) 2016 Marco Hutter - http://www.javagl.de + * + * This library is based on the code from the article "Creating TreeTables" + * by Sun Microsystems (now known as Oracle). + * + * The original copyright header: + * + * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Sun Microsystems nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package de.javagl.treetable; + +import javax.swing.tree.TreeModel; + +/** + * TreeTableModel is the model used by a JTreeTable. It extends TreeModel + * to add methods for getting information about the set of columns each + * node in the TreeTableModel may have. Each column, like a column in + * a TableModel, has a name and a type associated with it. Each node in + * the TreeTableModel can return a value for each of the columns and + * set that value if isCellEditable() returns true.
+ *
+ * Note: In order to actually display the JTree in one column of the + * JTreeTable, implementors of this class have to return the + * TreeTableModel.class as the respective column class + * in their implementation of the {@link #getColumnClass(int)} method: + *


+ * public Class<?> getColumnClass(int column)
+ * {
+ *     if (column == columnThatShouldContainTheTree)
+ *     {
+ *         return TreeTableModel.class;
+ *     }
+ *     // Return other types as desired:
+ *     return Object.class;
+ * }
+ * 
+ * + */ +public interface TreeTableModel extends TreeModel +{ + /** + * Returns the number of available columns. + * + * @return The number of columns + */ + int getColumnCount(); + + /** + * Returns the name for the specified column + * + * @param column The column + * @return The column name + */ + String getColumnName(int column); + + /** + * Returns the type for the specified column.
+ *
+ * Note: For the column that should display the JTree, this + * method must return TreeTableModel.class. + * + * @param column The column + * @return The column name + */ + Class getColumnClass(int column); + + /** + * Returns the value to be displayed for the given node in the + * specified column + * + * @param node The node + * @param column The column + * @return The object that should be displayed + */ + Object getValueAt(Object node, int column); + + /** + * Indicates whether the the value for the given node in the + * specified column is editable + * + * @param node The node + * @param column The column + * @return Whether the specified value is editable + */ + boolean isCellEditable(Object node, int column); + + /** + * Sets the value for the given node in the specified column + * + * @param value The value to set + * @param node The node + * @param column The column + */ + void setValueAt(Object value, Object node, int column); +} diff --git a/src/de/javagl/treetable/TreeTableModelAdapter.java b/src/de/javagl/treetable/TreeTableModelAdapter.java new file mode 100644 index 000000000..26fe82d1e --- /dev/null +++ b/src/de/javagl/treetable/TreeTableModelAdapter.java @@ -0,0 +1,203 @@ +/* + * www.javagl.de - JTreeTable + * + * Copyright (c) 2016 Marco Hutter - http://www.javagl.de + * + * This library is based on the code from the article "Creating TreeTables" + * by Sun Microsystems (now known as Oracle). + * + * The original copyright header: + * + * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Sun Microsystems nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package de.javagl.treetable; + +import javax.swing.JTree; +import javax.swing.SwingUtilities; +import javax.swing.table.AbstractTableModel; +import javax.swing.tree.TreePath; +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeExpansionListener; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; + +/** + * This is a wrapper class takes a TreeTableModel and implements the table model + * interface. The implementation is trivial, with all of the event dispatching + * support provided by the superclass: the AbstractTableModel. + */ +class TreeTableModelAdapter extends AbstractTableModel +{ + /** + * Serial UID + */ + private static final long serialVersionUID = 8489726674002235447L; + + /** + * The tree that backs this adapter. When the tree is expanded or + * collapsed, the table model is updated accordingly + */ + private final JTree tree; + + /** + * The backing {@link TreeTableModel} + */ + private final TreeTableModel treeTableModel; + + /** + * Default constructor + * @param treeTableModel The backing {@link TreeTableModel} + * @param tree The tree + */ + TreeTableModelAdapter(TreeTableModel treeTableModel, JTree tree) + { + this.tree = tree; + this.treeTableModel = treeTableModel; + + tree.addTreeExpansionListener(new TreeExpansionListener() + { + // Don't use fireTableRowsInserted() here; the selection model + // would get updated twice. + @Override + public void treeExpanded(TreeExpansionEvent event) + { + fireTableDataChanged(); + } + + @Override + public void treeCollapsed(TreeExpansionEvent event) + { + fireTableDataChanged(); + } + }); + + // Install a TreeModelListener that can update the table when + // tree changes. We use delayedFireTableDataChanged as we can + // not be guaranteed the tree will have finished processing + // the event before us. + treeTableModel.addTreeModelListener(new TreeModelListener() + { + @Override + public void treeNodesChanged(TreeModelEvent e) + { + delayedFireTableDataChanged(); + } + + @Override + public void treeNodesInserted(TreeModelEvent e) + { + delayedFireTableDataChanged(); + } + + @Override + public void treeNodesRemoved(TreeModelEvent e) + { + delayedFireTableDataChanged(); + } + + @Override + public void treeStructureChanged(TreeModelEvent e) + { + delayedFireTableDataChanged(); + } + }); + } + + @Override + public int getColumnCount() + { + return treeTableModel.getColumnCount(); + } + + @Override + public String getColumnName(int column) + { + return treeTableModel.getColumnName(column); + } + + @Override + public Class getColumnClass(int column) + { + return treeTableModel.getColumnClass(column); + } + + @Override + public int getRowCount() + { + return tree.getRowCount(); + } + + @Override + public Object getValueAt(int row, int column) + { + return treeTableModel.getValueAt(nodeForRow(row), column); + } + + @Override + public boolean isCellEditable(int row, int column) + { + return treeTableModel.isCellEditable(nodeForRow(row), column); + } + + @Override + public void setValueAt(Object value, int row, int column) + { + treeTableModel.setValueAt(value, nodeForRow(row), column); + } + + + /** + * Returns the tree node that is located in the specified row + * + * @param row The row + * @return The tree node + */ + private Object nodeForRow(int row) + { + TreePath treePath = tree.getPathForRow(row); + return treePath.getLastPathComponent(); + } + + /** + * Invokes fireTableDataChanged after all the pending events have been + * processed. SwingUtilities.invokeLater is used to handle this. + */ + private void delayedFireTableDataChanged() + { + SwingUtilities.invokeLater(new Runnable() + { + @Override + public void run() + { + fireTableDataChanged(); + } + }); + } +} diff --git a/src/de/javagl/treetable/package-info.java b/src/de/javagl/treetable/package-info.java new file mode 100644 index 000000000..35724f64a --- /dev/null +++ b/src/de/javagl/treetable/package-info.java @@ -0,0 +1,9 @@ +/** + * A JTreeTable based on the example code from the article "Creating TreeTables" + * by Sun Microsystems.
+ *
+ * See http://java.sun.com/products/jfc/tsc/articles/treetable2/index.html + * (only available via the web archive link + * https://web.archive.org/web/20120626135631/http://java.sun.com/products/jfc/tsc/articles/treetable2/index.html now) + */ +package de.javagl.treetable; \ No newline at end of file