All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.swing.outline.DefaultOutlineModel Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.swing.outline;

import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.event.TreeModelListener;
import javax.swing.table.TableModel;
import javax.swing.tree.AbstractLayoutCache;
import javax.swing.tree.FixedHeightLayoutCache;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.VariableHeightLayoutCache;

/** Proxies a standard TreeModel and TableModel, translating events between
 * the two.  Note that the constructor is not public;  the TableModel that is
 * proxied is the OutlineModel's own.  To make use of this class, implement
 * RowModel - that is a mini-table model in which the TreeModel is responsible
 * for defining the set of rows; it is passed an object from the tree, which
 * it may use to generate values for the other columns.  Pass that and the
 * TreeModel you want to use to createOutlineModel.
 * 

* A note on TableModelEvents produced by this model: There is a slight * impedance mismatch between TableModelEvent and TreeModelEvent. When the * tree changes, it is necessary to fire TableModelEvents to update the display. * However, TreeModelEvents support changes to discontiguous segments of the * model (i.e. "child nodes 3, 4 and 9 were deleted"). TableModelEvents * have no such concept - they operate on contiguous ranges of rows. Therefore, * one incoming TreeModelEvent may result in more than one TableModelEvent being * fired. Discontiguous TreeModelEvents will be broken into their contiguous * segments, which will be fired sequentially (in the case of removals, in * reverse order). So, the example above would generate two TableModelEvents, * the first indicating that row 9 was removed, and the second indicating that * rows 3 and 4 were removed. *

* Clients which need to know whether the TableModelEvent they have just * received is one of a group (perhaps they update some data structure, and * should not do so until the table's state is fully synchronized with that * of the tree model) may call areMoreEventsPending(). *

* In the case of TreeModelEvents which add items to an unexpanded tree node, * a simple value change TableModelEvent will be fired for the row in question * on the tree column index. *

* Note also that if the model is large-model, removal events may only indicate * those indices which were visible at the time of removal, because less data * is retained about the position of nodes which are not displayed. In this * case, the only issue is the accuracy of the scrollbar in the model; in * practice this is a non-issue, since it is based on the Outline's row count, * which will be accurate. *

* A note to subclassers, if we even leave this class non-final: If you do * not use ProxyTableModel and RowMapper (which probably means you are doing * something wrong), do not fire structural changes from the TableModel. * This class is designed such that the TreeModel is entirely in control of the * count and contents of the rows of the table. It and only it may fire * structural changes. *

* Note that this class enforces access only on the event dispatch thread * with assertions. All events fired by the underlying table and tree model * must be fired on the event dispatch thread. * * @author Tim Boudreau */ public class DefaultOutlineModel implements OutlineModel { private TreeModel treeModel; private TableModel tableModel; private AbstractLayoutCache layout; private TreePathSupport treePathSupport; private EventBroadcaster broadcaster; private String nodesColumnLabel = "Nodes"; //Some constants we use to have a single method handle all translated //event firing private static final int NODES_CHANGED = 0; private static final int NODES_INSERTED = 1; private static final int NODES_REMOVED = 2; private static final int STRUCTURE_CHANGED = 3; /** Create a small model OutlineModel using the supplied tree model and row model * @param treeModel The tree model that is the data model for the expandable * tree column of an Outline * @param rowModel The row model which will supply values for each row based * on the tree node in that row in the tree model */ public static OutlineModel createOutlineModel(TreeModel treeModel, RowModel rowModel) { return createOutlineModel (treeModel, rowModel, false, null); } /** Create an OutlineModel using the supplied tree model and row model, * specifying if it is a large-model tree * @param treeModel The tree model * @param rowModel The row model * @param isLargeModel true if it's a large model tree, false otherwise. */ public static OutlineModel createOutlineModel(TreeModel treeModel, RowModel rowModel, boolean isLargeModel) { return createOutlineModel (treeModel, rowModel, isLargeModel, null); } /** Create an OutlineModel using the supplied tree model and row model, * specifying if it is a large-model tree * @param treeModel The tree model * @param rowModel The row model * @param isLargeModel true if it's a large model tree, false otherwise. * @param nodesColumnLabel Label of the node's column */ public static OutlineModel createOutlineModel(TreeModel treeModel, RowModel rowModel, boolean isLargeModel, String nodesColumnLabel) { return new DefaultOutlineModel (treeModel, rowModel, isLargeModel, nodesColumnLabel); } /** Create a new instance of DefaultOutlineModel using the supplied tree model and row model, * specifying if it is a large-model tree * @param treeModel The tree model * @param rowModel The row model * @param largeModel true if it's a large model tree, false otherwise. * @param nodesColumnLabel Label of the node's column */ protected DefaultOutlineModel(TreeModel treeModel, RowModel rowModel, boolean largeModel, String nodesColumnLabel) { this( treeModel, new ProxyTableModel(rowModel), largeModel, nodesColumnLabel ); } /** Creates a new instance of DefaultOutlineModel. Note * Do not fire table structure changes from the wrapped TableModel (value * changes are okay). Changes that affect the number of rows must come * from the TreeModel. * @param treeModel The tree model * @param tableModel The table model * @param largeModel true if it's a large model tree, false otherwise. * @param nodesColumnLabel Label of the node's column */ protected DefaultOutlineModel(TreeModel treeModel, TableModel tableModel, boolean largeModel, String nodesColumnLabel) { this.treeModel = treeModel; this.tableModel = tableModel; if (nodesColumnLabel != null) { this.nodesColumnLabel = nodesColumnLabel; } layout = largeModel ? (AbstractLayoutCache) new FixedHeightLayoutCache() : (AbstractLayoutCache) new VariableHeightLayoutCache(); broadcaster = new EventBroadcaster (this); layout.setRootVisible(true); layout.setModel(this); treePathSupport = new TreePathSupport(this, layout); treePathSupport.addTreeExpansionListener(broadcaster); treePathSupport.addTreeWillExpandListener(broadcaster); treeModel.addTreeModelListener(broadcaster); tableModel.addTableModelListener(broadcaster); if (tableModel instanceof ProxyTableModel) { ((ProxyTableModel) tableModel).setOutlineModel(this); } } @Override public final TreePathSupport getTreePathSupport() { return treePathSupport; } @Override public final AbstractLayoutCache getLayout() { return layout; } /** Flag which is set to true while multiple TableModelEvents generated * from a single TreeModelEvent are being fired, so clients can avoid * any model queries until all pending changes have been fired. The * main thing to avoid is any mid-process repaints, which can only happen * if the response to an event will be to call paintImmediately(). *

* This value is guaranteed to be true for the first group of * related events, and false if tested in response to the final event. * * @return true if more events are pending, false otherwise. */ public boolean areMoreEventsPending() { return broadcaster.areMoreEventsPending(); } /** Accessor for EventBroadcaster */ TreeModel getTreeModel() { return treeModel; } /** Accessor for EventBroadcaster */ TableModel getTableModel() { return tableModel; } @Override public final Object getChild(Object parent, int index) { return treeModel.getChild (parent, index); } @Override public final int getChildCount(Object parent) { return treeModel.getChildCount (parent); } /** Delegates to the RowMapper for > 0 columns; column 0 always * returns Object.class */ @Override public final Class getColumnClass(int columnIndex) { if (columnIndex == 0) { return Object.class; } else { return tableModel.getColumnClass(columnIndex-1); } } @Override public final int getColumnCount() { return tableModel.getColumnCount()+1; } @Override public String getColumnName(int columnIndex) { if (columnIndex == 0) { return nodesColumnLabel; } else { return tableModel.getColumnName(columnIndex-1); } } /** * Change the label of the 'tree' column. * @param label New label for tree column. */ public void setNodesColumnLabel( String label ) { this.nodesColumnLabel = label; broadcaster.fireTableChange( new TableModelEvent( this, -1, -1, 0, TableModelEvent.HEADER_ROW ) ); } @Override public final int getIndexOfChild(Object parent, Object child) { return treeModel.getIndexOfChild(parent, child); } @Override public final Object getRoot() { return treeModel.getRoot(); } @Override public final int getRowCount() { return layout.getRowCount(); } @Override public final Object getValueAt(int rowIndex, int columnIndex) { Object result; if (columnIndex == 0) { //XXX need a column ID - columnIndex = 0 depends on the column model TreePath path = getLayout().getPathForRow(rowIndex); if (path != null) { result = path.getLastPathComponent(); } else { result = null; } } else { result = (tableModel.getValueAt(rowIndex, columnIndex -1)); } return result; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex == 0) { return false; //XXX support editing of node names } else { return tableModel.isCellEditable(rowIndex, columnIndex-1); } } @Override public final boolean isLeaf(Object node) { return null != node && treeModel.isLeaf(node); } // Delegates to the EventBroadcaster for this model @Override public final synchronized void addTableModelListener(TableModelListener l) { broadcaster.addTableModelListener (l); } // Delegates to the EventBroadcaster for this model @Override public final synchronized void addTreeModelListener(TreeModelListener l) { broadcaster.addTreeModelListener (l); } // Delegates to the EventBroadcaster for this model @Override public final synchronized void removeTableModelListener(TableModelListener l) { broadcaster.removeTableModelListener(l); } // Delegates to the EventBroadcaster for this model @Override public final synchronized void removeTreeModelListener(TreeModelListener l) { broadcaster.removeTreeModelListener(l); } /** Delegates to the RowModel (or TableModel) for non-0 columns */ @Override public final void setValueAt(Object aValue, int rowIndex, int columnIndex) { if (columnIndex != 0) { tableModel.setValueAt (aValue, rowIndex, columnIndex-1); } else { setTreeValueAt(aValue, rowIndex); } } /** * Sets the value of a 'tree' cell at given row number. * The default implementation does nothing. * * @param aValue * @param rowIndex */ protected void setTreeValueAt(Object aValue, int rowIndex) { //do nothing } @Override public final void valueForPathChanged(javax.swing.tree.TreePath path, Object newValue) { //if the model is correctly implemented, this will trigger a change //event treeModel.valueForPathChanged(path, newValue); } @Override public boolean isLargeModel() { return layout instanceof FixedHeightLayoutCache; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy