001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: /*
043: * DefaultOutlineModel.java
044: *
045: * Created on January 27, 2004, 6:58 PM
046: */
047:
048: package org.netbeans.swing.outline;
049:
050: import java.util.ArrayList;
051: import java.util.Arrays;
052: import java.util.Enumeration;
053: import java.util.List;
054: import javax.swing.event.TableModelEvent;
055: import javax.swing.event.TableModelListener;
056: import javax.swing.event.TreeExpansionEvent;
057: import javax.swing.event.TreeExpansionListener;
058: import javax.swing.event.TreeModelEvent;
059: import javax.swing.event.TreeModelListener;
060: import javax.swing.event.TreeWillExpandListener;
061: import javax.swing.table.TableModel;
062: import javax.swing.tree.AbstractLayoutCache;
063: import javax.swing.tree.ExpandVetoException;
064: import javax.swing.tree.FixedHeightLayoutCache;
065: import javax.swing.tree.TreeModel;
066: import javax.swing.tree.TreePath;
067: import javax.swing.tree.VariableHeightLayoutCache;
068:
069: /** Proxies a standard TreeModel and TableModel, translating events between
070: * the two. Note that the constructor is not public; the TableModel that is
071: * proxied is the OutlineModel's own. To make use of this class, implement
072: * RowModel - that is a mini-table model in which the TreeModel is responsible
073: * for defining the set of rows; it is passed an object from the tree, which
074: * it may use to generate values for the other columns. Pass that and the
075: * TreeModel you want to use to <code>createOutlineModel</code>.
076: * <p>
077: * A note on TableModelEvents produced by this model: There is a slight
078: * impedance mismatch between TableModelEvent and TreeModelEvent. When the
079: * tree changes, it is necessary to fire TableModelEvents to update the display.
080: * However, TreeModelEvents support changes to discontiguous segments of the
081: * model (i.e. "child nodes 3, 4 and 9 were deleted"). TableModelEvents
082: * have no such concept - they operate on contiguous ranges of rows. Therefore,
083: * one incoming TreeModelEvent may result in more than one TableModelEvent being
084: * fired. Discontiguous TreeModelEvents will be broken into their contiguous
085: * segments, which will be fired sequentially (in the case of removals, in
086: * reverse order). So, the example above would generate two TableModelEvents,
087: * the first indicating that row 9 was removed, and the second indicating that
088: * rows 3 and 4 were removed.
089: * <p>
090: * Clients which need to know whether the TableModelEvent they have just
091: * received is one of a group (perhaps they update some data structure, and
092: * should not do so until the table's state is fully synchronized with that
093: * of the tree model) may call <code>areMoreEventsPending()</code>.
094: * <p>
095: * In the case of TreeModelEvents which add items to an unexpanded tree node,
096: * a simple value change TableModelEvent will be fired for the row in question
097: * on the tree column index.
098: * <p>
099: * Note also that if the model is large-model, removal events may only indicate
100: * those indices which were visible at the time of removal, because less data
101: * is retained about the position of nodes which are not displayed. In this
102: * case, the only issue is the accuracy of the scrollbar in the model; in
103: * practice this is a non-issue, since it is based on the Outline's row count,
104: * which will be accurate.
105: * <p>
106: * A note to subclassers, if we even leave this class non-final: If you do
107: * not use ProxyTableModel and RowMapper (which probably means you are doing
108: * something wrong), <strong>do not fire structural changes from the TableModel</strong>.
109: * This class is designed such that the TreeModel is entirely in control of the
110: * count and contents of the rows of the table. It and only it may fire
111: * structural changes.
112: * <p>
113: * Note that this class enforces access only on the event dispatch thread
114: * with assertions. All events fired by the underlying table and tree model
115: * must be fired on the event dispatch thread.
116: *
117: * @author Tim Boudreau
118: */
119: public class DefaultOutlineModel implements OutlineModel {
120: private TreeModel treeModel;
121: private TableModel tableModel;
122: private AbstractLayoutCache layout;
123: private TreePathSupport treePathSupport;
124: private EventBroadcaster broadcaster;
125: //Some constants we use to have a single method handle all translated
126: //event firing
127: private static final int NODES_CHANGED = 0;
128: private static final int NODES_INSERTED = 1;
129: private static final int NODES_REMOVED = 2;
130: private static final int STRUCTURE_CHANGED = 3;
131:
132: /**
133: * 4/19/2004 - Added ability to set the node column name.
134: * David Botterill
135: */
136:
137: private String nodeColumnName;
138:
139: //XXX deleteme - string version of the avoid constants debug output:
140: private static final String[] types = new String[] {
141: "nodesChanged", "nodesInserted", "nodesRemoved",
142: "structureChanged" };
143:
144: /** Create a small model OutlineModel using the supplied tree model and row model
145: * @param treeModel The tree model that is the data model for the expandable
146: * tree column of an Outline
147: * @param rowModel The row model which will supply values for each row based
148: * on the tree node in that row in the tree model
149: */
150: public static OutlineModel createOutlineModel(TreeModel treeModel,
151: RowModel rowModel) {
152: return createOutlineModel(treeModel, rowModel, false);
153: }
154:
155: /** Create an OutlineModel using the supplied tree model and row model,
156: * specifying if it is a large-model tree */
157: public static OutlineModel createOutlineModel(TreeModel treeModel,
158: RowModel rowModel, boolean isLargeModel) {
159: TableModel tableModel = new ProxyTableModel(rowModel);
160: return new DefaultOutlineModel(treeModel, tableModel,
161: isLargeModel);
162: }
163:
164: /** Creates a new instance of DefaultOutlineModel. <strong><b>Note</b>
165: * Do not fire table structure changes from the wrapped TableModel (value
166: * changes are okay). Changes that affect the number of rows must come
167: * from the TreeModel. */
168: protected DefaultOutlineModel(TreeModel treeModel,
169: TableModel tableModel, boolean largeModel) {
170: this .treeModel = treeModel;
171: this .tableModel = tableModel;
172:
173: layout = largeModel ? (AbstractLayoutCache) new FixedHeightLayoutCache()
174: : (AbstractLayoutCache) new VariableHeightLayoutCache();
175:
176: broadcaster = new EventBroadcaster(this );
177:
178: layout.setRootVisible(true);
179: layout.setModel(this );
180: treePathSupport = new TreePathSupport(this , layout);
181: treePathSupport.addTreeExpansionListener(broadcaster);
182: treePathSupport.addTreeWillExpandListener(broadcaster);
183: treeModel.addTreeModelListener(broadcaster);
184: tableModel.addTableModelListener(broadcaster);
185: if (tableModel instanceof ProxyTableModel) {
186: ((ProxyTableModel) tableModel).setOutlineModel(this );
187: }
188: }
189:
190: public final TreePathSupport getTreePathSupport() {
191: return treePathSupport;
192: }
193:
194: public final AbstractLayoutCache getLayout() {
195: return layout;
196: }
197:
198: public boolean areMoreEventsPending() {
199: return broadcaster.areMoreEventsPending();
200: }
201:
202: /** Accessor for EventBroadcaster */
203: TreeModel getTreeModel() {
204: return treeModel;
205: }
206:
207: /** Accessor for EventBroadcaster */
208: TableModel getTableModel() {
209: return tableModel;
210: }
211:
212: public final Object getChild(Object parent, int index) {
213: return treeModel.getChild(parent, index);
214: }
215:
216: public final int getChildCount(Object parent) {
217: return treeModel.getChildCount(parent);
218: }
219:
220: /** Delegates to the RowMapper for > 0 columns; column 0 always
221: * returns Object.class */
222: public final Class getColumnClass(int columnIndex) {
223: if (columnIndex == 0) {
224: return Object.class;
225: } else {
226: return tableModel.getColumnClass(columnIndex - 1);
227: }
228: }
229:
230: public final int getColumnCount() {
231: return tableModel.getColumnCount() + 1;
232: }
233:
234: /**
235: * Added 4/19/2004 David Botterill
236: */
237: public void setNodeColumnName(String inName) {
238: nodeColumnName = inName;
239: }
240:
241: public String getColumnName(int columnIndex) {
242: /**
243: * Changed 4/19/2004 to allow the node column to be named.
244: * - David Botterill
245: */
246: if (columnIndex == 0) {
247: return null == nodeColumnName ? "Nodes" : nodeColumnName; //XXX
248: } else {
249: return tableModel.getColumnName(columnIndex - 1);
250: }
251: }
252:
253: public final int getIndexOfChild(Object parent, Object child) {
254: return treeModel.getIndexOfChild(parent, child);
255: }
256:
257: public final Object getRoot() {
258: return treeModel.getRoot();
259: }
260:
261: public final int getRowCount() {
262: return layout.getRowCount();
263: }
264:
265: public final Object getValueAt(int rowIndex, int columnIndex) {
266: Object result;
267: if (columnIndex == 0) { //XXX need a column ID - columnIndex = 0 depends on the column model
268: TreePath path = getLayout().getPathForRow(rowIndex);
269: if (path != null) {
270: result = path.getLastPathComponent();
271: } else {
272: result = null;
273: }
274: } else {
275: result = (tableModel.getValueAt(rowIndex, columnIndex - 1));
276: }
277: return result;
278: }
279:
280: public boolean isCellEditable(int rowIndex, int columnIndex) {
281: if (columnIndex == 0) {
282: return false; //XXX support editing of node names
283: } else {
284: return tableModel.isCellEditable(rowIndex, columnIndex - 1);
285: }
286: }
287:
288: public final boolean isLeaf(Object node) {
289: /**
290: * Changed 1/13/2005 - David Botterill
291: * We need to check for a null node here since the DefaultTreeModel does
292: * not and will cause NPE if it's null. This situation occurs when the
293: * DefaultOutlineCellRenderer gets called from the accessibility context.
294: */
295: if (null == node)
296: return true;
297: return treeModel.isLeaf(node);
298: }
299:
300: /** Delegates to the EventBroadcaster for this model */
301: public final synchronized void addTableModelListener(
302: TableModelListener l) {
303: broadcaster.addTableModelListener(l);
304: }
305:
306: /** Delegates to the EventBroadcaster for this model */
307: public final synchronized void addTreeModelListener(
308: TreeModelListener l) {
309: broadcaster.addTreeModelListener(l);
310: }
311:
312: /** Delegates to the EventBroadcaster for this model */
313: public final synchronized void removeTableModelListener(
314: TableModelListener l) {
315: broadcaster.removeTableModelListener(l);
316: }
317:
318: /** Delegates to the EventBroadcaster for this model */
319: public final synchronized void removeTreeModelListener(
320: TreeModelListener l) {
321: broadcaster.removeTreeModelListener(l);
322: }
323:
324: /** Delegates to the RowModel (or TableModel) for non-0 columns */
325: public final void setValueAt(Object aValue, int rowIndex,
326: int columnIndex) {
327: if (columnIndex != 0) {
328: tableModel.setValueAt(aValue, rowIndex, columnIndex - 1);
329: } else {
330: //XXX do something
331: }
332: }
333:
334: public final void valueForPathChanged(
335: javax.swing.tree.TreePath path, Object newValue) {
336: //if the model is correctly implemented, this will trigger a change
337: //event
338: treeModel.valueForPathChanged(path, newValue);
339: }
340:
341: public boolean isLargeModel() {
342: return layout instanceof FixedHeightLayoutCache;
343: }
344:
345: public NodeRowModel getRowNodeModel() {
346: return (ProxyTableModel) tableModel;
347: }
348:
349: }
|