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

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

There is a newer version: RELEASE240
Show 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 java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EventObject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JToolTip;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TreeModelEvent;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.tree.AbstractLayoutCache;
import javax.swing.tree.TreePath;
import org.netbeans.swing.etable.ETable;
import org.netbeans.swing.etable.ETableColumn;
import org.netbeans.swing.etable.ETableColumnModel;

/** An Outline, or tree-table component.  Takes an instance of OutlineModel,
 * an interface which merges TreeModel and TableModel.
 * 

* Simplest usage: *

    *
  1. Create a standard tree model for the tree node portion of the outline.
  2. *
  3. Implement RowModel. RowModel is a subset of TableModel - it is passed * the value in column 0 of the Outline and a column index, and returns the * value in the column in question.
  4. *
  5. Pass the TreeModel and the RowModel to DefaultOutlineModel.createModel() *
* This will generate an instance of DefaultOutlineModel which will use the * TreeModel for the rows/tree column content, and use the RowModel to provide * the additional table columns. *

* It is also useful to provide an implementation of RenderDataProvider * to supply icons and affect text display of cells - this covers most of the * needs for which it is necessary to write a custom cell renderer in JTable/JTree. *

* Example usage:
* Assume FileTreeModel is a model which, given a root directory, will * expose the files and folders underneath it. We will implement a * RowModel to expose the file size and date, and a RenderDataProvider which * will use a gray color for uneditable files and expose the full file path as * a tooltip. Assume the class this is implemented in is a * JPanel subclass or other Swing container. *
* XXX todo: clean up formatting & edit for style *

{@code
 * public void initComponents() {
 *   setLayout (new BorderLayout());
 *   TreeModel treeMdl = new FileTreeModel (someDirectory);
 *
 *   OutlineModel mdl = DefaultOutlineModel.createOutlineModel(treeMdl, 
 *       new FileRowModel(), true);
 *   outline = new Outline();
 *   outline.setRenderDataProvider(new FileDataProvider()); 
 *   outline.setRootVisible (true);
 *   outline.setModel (mdl);
 *   add (outline, BorderLayout.CENTER);
 * }
 *  private class FileRowModel implements RowModel {
 *     public Class getColumnClass(int column) {
 *          switch (column) {
 *              case 0 : return Date.class;
 *              case 1 : return Long.class;
 *              default : assert false;
 *          }
 *          return null;
 *      }
 *      
 *      public int getColumnCount() {
 *          return 2;
 *      }
 *      
 *      public String getColumnName(int column) {
 *          return column == 0 ? "Date" : "Size";
 *      }
 *      
 *      public Object getValueFor(Object node, int column) {
 *          File f = (File) node;
 *          switch (column) {
 *              case 0 : return new Date (f.lastModified());
 *              case 1 : return new Long (f.length());
 *              default : assert false;
 *          }
 *          return null;
 *      }
 *      
 *      public boolean isCellEditable(Object node, int column) {
 *          return false;
 *      }
 *      
 *      public void setValueFor(Object node, int column, Object value) {
 *          //do nothing, nothing is editable
 *      }
 *  }
 *  
 *  private class FileDataProvider implements RenderDataProvider {
 *      public java.awt.Color getBackground(Object o) {
 *          return null;
 *      }
 *      
 *      public String getDisplayName(Object o) {
 *          return ((File) o).getName();
 *      }
 *      
 *      public java.awt.Color getForeground(Object o) {
 *          File f = (File) o;
 *          if (!f.isDirectory() && !f.canWrite()) {
 *              return UIManager.getColor ("controlShadow");
 *          }
 *          return null;
 *      }
 *      
 *      public javax.swing.Icon getIcon(Object o) {
 *          return null;
 *      }
 *      
 *      public String getTooltipText(Object o) {
 *          return ((File) o).getAbsolutePath();
 *      }
 *      
 *      public boolean isHtmlDisplayName(Object o) {
 *          return false;
 *      }
 *   }
 * }
* * * @author Tim Boudreau */ public class Outline extends ETable { //XXX plenty of methods missing here - add/remove tree expansion listeners, //better path info/queries, etc. // Tooltips larger than this are screwed up. private static final int MAX_TOOLTIP_LENGTH = 1000; private boolean initialized = false; private Boolean cachedRootVisible = null; private RenderDataProvider renderDataProvider = null; private ComponentListener componentListener = null; private boolean selectionDisabled = false; private boolean rowHeightIsSet = false; private int selectedRow = -1; private int[] lastEditPosition; /** Creates a new instance of Outline */ public Outline() { init(); } public Outline(OutlineModel mdl) { super (mdl); init(); } private void init() { initialized = true; setDefaultRenderer(Object.class, new DefaultOutlineCellRenderer()); ActionMap am = getActionMap(); //make rows expandable with left/rigt arrow keys Action a = am.get("selectNextColumn"); //NOI18N am.put("selectNextColumn", new ExpandAction(true, a)); //NOI18N a = am.get("selectPreviousColumn"); //NOI18N am.put("selectPreviousColumn", new ExpandAction(false, a)); //NOI18N getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (getSelectedRowCount() == 1) { selectedRow = getSelectedRow(); } else { selectedRow = -1; } } }); } /** Always returns the default renderer for Object.class for the tree column */ @Override public TableCellRenderer getCellRenderer(int row, int column) { int c = convertColumnIndexToModel(column); TableCellRenderer result; if (c == 0) { TableColumn tableColumn = getColumnModel().getColumn(column); TableCellRenderer renderer = tableColumn.getCellRenderer(); if (renderer == null) { result = getDefaultRenderer(Object.class); } else { result = renderer; } } else { result = super.getCellRenderer(row, column); } return result; } /** Get the RenderDataProvider which is providing text, icons and tooltips * for items in the tree column. The default property for this value is * null, in which case standard JTable/JTree object -> icon/string * conventions are used */ public RenderDataProvider getRenderDataProvider() { return renderDataProvider; } /** Set the RenderDataProvider which will provide text, icons and tooltips * for items in the tree column. The default is null. If null, * the data displayed will be generated in the standard JTable/JTree way - * calling toString() on objects in the tree model and * using the look and feel's default tree folder and tree leaf icons. */ public void setRenderDataProvider (RenderDataProvider provider) { if (provider != renderDataProvider) { RenderDataProvider old = renderDataProvider; renderDataProvider = provider; firePropertyChange ("renderDataProvider", old, provider); //NOI18N } } /** Get the TreePathSupport object which manages path expansion for this * Outline. */ TreePathSupport getTreePathSupport () { OutlineModel mdl = getOutlineModel(); if (mdl != null) { return mdl.getTreePathSupport(); } else { return null; } } /** Get the layout cache which manages layout data for the Outline. * Under no circumstances directly call the methods on the * layout cache which change the expanded state - such changes will not * be propagated into the table model, and will leave the model and * its layout in inconsistent states. Any calls that affect expanded * state must go through getTreePathSupport(). */ public final AbstractLayoutCache getLayoutCache () { OutlineModel mdl = getOutlineModel(); if (mdl != null) { return mdl.getLayout(); } else { return null; } } boolean isTreeColumnIndex (int column) { int c = convertColumnIndexToModel(column); return c == 0; } public boolean isVisible (TreePath path) { if (getTreePathSupport() != null) { return getTreePathSupport().isVisible(path); } return false; } /** Overridden to pass the fixed row height to the tree layout cache */ @Override public void setRowHeight(int val) { rowHeightIsSet = true; super.setRowHeight(val); if (getLayoutCache() != null) { getLayoutCache().setRowHeight(val); } } /** Set whether or not the root is visible */ public void setRootVisible (boolean val) { if (getOutlineModel() == null) { cachedRootVisible = val ? Boolean.TRUE : Boolean.FALSE; } if (val != isRootVisible()) { getLayoutCache().setRootVisible(val); if( getLayoutCache().getRowCount() > 0 ) { TreePath rootPath = getLayoutCache().getPathForRow(0); if( null != rootPath ) getLayoutCache().treeStructureChanged(new TreeModelEvent(this, rootPath)); } sortAndFilter(); firePropertyChange("rootVisible", !val, val); //NOI18N } } /** Is the tree root visible. Default value is true. */ public boolean isRootVisible() { if (getLayoutCache() == null) { return cachedRootVisible != null ? cachedRootVisible.booleanValue() : true; } else { return getLayoutCache().isRootVisible(); } } @Override public void setRowHeight (int row, int rowHeight) { Logger.getLogger (Outline.class.getName ()).warning ("Not supported yet."); // NOI18N } @Override protected TableColumn createColumn(int modelIndex) { return new OutlineColumn(modelIndex); } private JToolTip toolTip = null; @Override public String getToolTipText(MouseEvent event) { try { // Required to really get the tooltip text: putClientProperty("ComputingTooltip", Boolean.TRUE); toolTip = null; String tipText = null; Point p = event.getPoint(); // Locate the renderer under the event location int hitColumnIndex = columnAtPoint(p); int hitRowIndex = rowAtPoint(p); if ((hitColumnIndex != -1) && (hitRowIndex != -1)) { //Outline tbl = (Outline) table; if (convertColumnIndexToModel(hitColumnIndex) == 0) { // tree column index // For tree column get the tooltip directly from the renderer data provider RenderDataProvider rendata = getRenderDataProvider(); if (rendata != null) { Object value = getValueAt(hitRowIndex, hitColumnIndex); if (value != null) { String toolT = rendata.getTooltipText(value); if (toolT != null && (toolT = toolT.trim ()).length () > 0) { tipText = toolT; } } } } TableCellRenderer renderer = getCellRenderer(hitRowIndex, hitColumnIndex); Component component = prepareRenderer(renderer, hitRowIndex, hitColumnIndex); // Now have to see if the component is a JComponent before // getting the tip if (component instanceof JComponent) { // Convert the event to the renderer's coordinate system Rectangle cellRect = getCellRect(hitRowIndex, hitColumnIndex, false); p.translate(-cellRect.x, -cellRect.y); MouseEvent newEvent = new MouseEvent(component, event.getID(), event.getWhen(), event.getModifiers(), p.x, p.y, event.getXOnScreen(), event.getYOnScreen(), event.getClickCount(), event.isPopupTrigger(), MouseEvent.NOBUTTON); if (tipText == null) { tipText = ((JComponent)component).getToolTipText(newEvent); } toolTip = ((JComponent)component).createToolTip(); } } // No tip from the renderer get our own tip if (tipText == null) tipText = getToolTipText(); if (tipText != null) { tipText = tipText.trim(); if (tipText.length() > MAX_TOOLTIP_LENGTH && !tipText.regionMatches(false, 0, "", 0, 6)) { // Do not cut HTML tooltips tipText = tipText.substring(0, MAX_TOOLTIP_LENGTH) + "..."; } } return tipText; } finally { putClientProperty("ComputingTooltip", Boolean.FALSE); } //return super.getToolTipText(event); } @Override public JToolTip createToolTip() { JToolTip t = toolTip; toolTip = null; if (t != null) { t.addMouseMotionListener(new MouseMotionAdapter() { // #233642 boolean initialized = false; @Override public void mouseMoved(MouseEvent e) { if (!initialized) { initialized = true; // ignore the first event } else { // hide the tooltip if mouse moves over it ToolTipManager.sharedInstance().mousePressed(e); } } }); return t; } else { return super.createToolTip(); } } private transient Map tempSortMap = null; private final transient Object tempSortMapLock = new Object(); /** * Sorts the rows of the tree table. */ @Override protected void sortAndFilter() { TableColumnModel tcm = getColumnModel(); if (tcm instanceof ETableColumnModel) { ETableColumnModel etcm = (ETableColumnModel) tcm; Comparator c = etcm.getComparator(); if (c != null) { TableModel model = getModel(); int noRows = model.getRowCount(); //System.err.println("sortAndFilter: Number of rows = "+noRows); List rows = new ArrayList(); synchronized (tempSortMapLock) { if (tempSortMap != null) { return ; // Sorting right now } Map tsm = new HashMap(); for (int i = 0; i < noRows; i++) { if (acceptByQuickFilter(model, i)) { TreePath tp = getLayoutCache().getPathForRow(i); RowMapping rm = new RowMapping(i, model, this); //System.err.println(" RowMapping("+i+") = "+rm); tsm.put(tp, rm); rows.add(rm); } } tempSortMap = tsm; rows.sort(c); tempSortMap = null; } int [] res = new int[rows.size()]; int [] invRes = new int[noRows]; // carefull - this one is bigger! for (int i = 0; i < res.length; i++) { RowMapping rm = rows.get(i); int rmi = rm.getModelRowIndex(); res[i] = rmi; invRes[rmi] = i; } int[] oldRes = sortingPermutation; int[] oldInvRes = inverseSortingPermutation; //System.err.println(" SETTING PERMUTATION = "+Arrays.toString(res)); //System.err.println(" SETTING INV.PERMUT. = "+Arrays.toString(invRes)); sortingPermutation = res; inverseSortingPermutation = invRes; //adjustSelectedRows(oldRes, oldInvRes, res, invRes); } } } /** * An Outline implementation of table column. */ protected class OutlineColumn extends ETableColumn { /** * Create a new outline column * @param modelIndex The column's model index */ public OutlineColumn(int modelIndex) { super(modelIndex, Outline.this); } @Override protected Comparator getRowComparator(int column, boolean ascending) { return new OutlineRowComparator(column, ascending); } @Override public boolean isHidingAllowed() { return getModelIndex() != 0; } @Override public boolean isSortingAllowed() { return true; } /** * Comparator used for sorting the rows according to value in * a given column. Operates on the RowMapping objects. */ private class OutlineRowComparator extends RowComparator { private boolean ascending = true; public OutlineRowComparator(int column, boolean ascending) { super(column); this.ascending = ascending; } @Override @SuppressWarnings("unchecked") public int compare(RowMapping rm1, RowMapping rm2) { int index1 = rm1.getModelRowIndex(); int index2 = rm2.getModelRowIndex(); if (index1 == index2) { return 0; } TreePath tp1 = getLayoutCache().getPathForRow(index1); TreePath tp2 = getLayoutCache().getPathForRow(index2); if (tp1 == null) { if (tp2 == null) { return 0; } else { return -1; } } else if (tp2 == null) { return 1; } if (tp1.isDescendant(tp2)) { return -1; } if (tp2.isDescendant(tp1)) { return 1; } boolean tp1Changed = false; boolean tp2Changed = false; TreePath parent1 = tp1.getParentPath(); TreePath parent2 = tp2.getParentPath(); if (parent1 != null && parent2 != null && parent1.equals(parent2) && getOutlineModel().isLeaf(tp1.getLastPathComponent()) && getOutlineModel().isLeaf(tp2.getLastPathComponent())) { return ascending ? super.compare(rm1, rm2) : - super.compare(rm1, rm2); } while (tp1.getPathCount() < tp2.getPathCount()) { tp2 = tp2.getParentPath(); tp2Changed = true; } while (tp1.getPathCount() > tp2.getPathCount()) { tp1 = tp1.getParentPath(); tp1Changed = true; } parent1 = tp1.getParentPath(); parent2 = tp2.getParentPath(); while (parent1 != null && parent2 != null && !parent1.equals(parent2)) { tp1 = parent1; tp2 = parent2; parent1 = tp1.getParentPath(); parent2 = tp2.getParentPath(); tp1Changed = true; tp2Changed = true; } if (tp1Changed || tp2Changed) { return compare(tempSortMap.get(tp1), tempSortMap.get(tp2)); } else { return ascending ? super.compare(rm1, rm2) : - super.compare(rm1, rm2); } } } } /** Overridden to throw an exception if the passed model is not an instance * of OutlineModel (with the exception of calls from the * superclass constructor) */ @Override public void setModel (TableModel mdl) { if (initialized && (!(mdl instanceof OutlineModel))) { throw new IllegalArgumentException ( "Table model for an Outline must be an instance of " + "OutlineModel"); //NOI18N } if (mdl instanceof OutlineModel) { AbstractLayoutCache layout = ((OutlineModel) mdl).getLayout(); if (cachedRootVisible != null) { layout.setRootVisible( cachedRootVisible.booleanValue()); } layout.setRowHeight(getRowHeight()); if (((OutlineModel) mdl).isLargeModel()) { addComponentListener (getComponentListener()); layout.setNodeDimensions(new ND()); } else { if (componentListener != null) { removeComponentListener (componentListener); componentListener = null; } } } super.setModel(mdl); } /** Convenience getter for the TableModel as an instance of * OutlineModel. If no OutlineModel has been set, returns null. */ public OutlineModel getOutlineModel() { TableModel mdl = getModel(); if (mdl instanceof OutlineModel) { return (OutlineModel) getModel(); } else { return null; } } /** Expand a tree path */ public void expandPath (TreePath path) { getTreePathSupport().expandPath (path); } public boolean isExpanded (TreePath path) { return getTreePathSupport().isExpanded(path); } /** * Collapse the given tree path. * @param path The tree path to collapse. */ public void collapsePath (TreePath path) { getTreePathSupport().collapsePath (path); } /** * Get the UI bounds of the given tree path. * @param path The tree path to get the bounds for. * @return The bounds. */ public Rectangle getPathBounds(TreePath path) { Insets i = getInsets(); Rectangle bounds = getLayoutCache().getBounds(path, null); if(bounds != null && i != null) { bounds.x += i.left; bounds.y += i.top; } return bounds; } /** * Find the tree path that is closest to the given position. * @param x The X coordinate of the position * @param y The Y coordinate of the position * @return The closest tree path */ public TreePath getClosestPathForLocation(int x, int y) { Insets i = getInsets(); TreePath tp; if (i != null) { tp = getLayoutCache().getPathClosestTo(x - i.left, y - i.top); } else { tp = getLayoutCache().getPathClosestTo(x,y); } int row = getLayoutCache().getRowForPath(tp); // The UI row needs to be converted to the model row. row = convertRowIndexToModel(row); // Now we can get the correct path from the model row: tp = getLayoutCache().getPathForRow(row); return tp; } private KeyStroke lastProcessedKeyStroke; @Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { lastProcessedKeyStroke = ks; try { return super.processKeyBinding(ks, e, condition, pressed); } finally { lastProcessedKeyStroke = null; } } @Override public boolean editCellAt (int row, int column, EventObject e) { //If it was on column 0, it may be a request to expand a tree //node - check for that first. boolean isTreeColumn = isTreeColumnIndex(column); if (isTreeColumn && e instanceof MouseEvent) { MouseEvent me = (MouseEvent) e; TreePath path = getLayoutCache().getPathForRow(convertRowIndexToModel(row)); if (path != null && !getOutlineModel().isLeaf(path.getLastPathComponent())) { int handleWidth = DefaultOutlineCellRenderer.getExpansionHandleWidth(); Insets ins = getInsets(); int nd = path.getPathCount() - (isRootVisible() ? 1 : 2); if (nd < 0) { nd = 0; } int handleStart = ins.left + (nd * DefaultOutlineCellRenderer.getNestingWidth()); int handleEnd = ins.left + handleStart + handleWidth; // Translate 'x' to position of column if non-0: int columnStart = getCellRect(row, column, false).x; handleStart += columnStart; handleEnd += columnStart; TableColumn tableColumn = getColumnModel().getColumn(column); TableCellEditor columnCellEditor = tableColumn.getCellEditor(); if ((me.getX() > ins.left && me.getX() >= handleStart && me.getX() <= handleEnd) || (me.getClickCount() > 1 && columnCellEditor == null)) { boolean expanded = getLayoutCache().isExpanded(path); //me.consume(); - has no effect! //System.err.println(" event consumed."); if (!expanded) { getTreePathSupport().expandPath(path); Object ourObject = path.getLastPathComponent(); int cCount = getOutlineModel().getChildCount(ourObject); if (cCount > 0) { int lastRow = row; for (int i = 0; i < cCount; i++) { Object child = getOutlineModel().getChild(ourObject, i); TreePath childPath = path.pathByAddingChild(child); int childRow = getLayoutCache().getRowForPath(childPath); childRow = convertRowIndexToView(childRow); if (childRow > lastRow) { lastRow = childRow; } } int firstRow = row; Rectangle rectLast = getCellRect(lastRow, 0, true); Rectangle rectFirst = getCellRect(firstRow, 0, true); Rectangle rectFull = new Rectangle( rectFirst.x, rectFirst.y, rectLast.x + rectLast.width - rectFirst.x, rectLast.y + rectLast.height - rectFirst.y); scrollRectToVisible(rectFull); } } else { getTreePathSupport().collapsePath(path); } selectionDisabled = true; return false; } } // It may be a request to check/uncheck a check-box if (checkAt(row, column, me)) { return false; } } else if (isTreeColumn && e instanceof ActionEvent) { if (((ActionEvent) e).getModifiers() == 0 && lastProcessedKeyStroke != null) { if (lastProcessedKeyStroke.getKeyCode() == KeyEvent.VK_SPACE) { if (checkAt(row, column, null)) { return false; } } } } boolean res = false; if (!isTreeColumn || e instanceof MouseEvent && row >= 0 && isEditEvent(row, column, (MouseEvent) e) || // Allow F2 to be used to invoke Rename. e instanceof ActionEvent && row >= 0) { res = super.editCellAt(row, column, e); } if( res && isTreeColumn && row >= 0 && null != getEditorComponent() ) { configureTreeCellEditor(getEditorComponent(), row, column); } if (e == null && !res && isTreeColumn) { // Handle SPACE checkAt(row, column, null); } return res; } private boolean isEditEvent(int row, int column, MouseEvent me) { if (me.getClickCount() > 1) { return true; } boolean noModifiers = me.getModifiersEx() == MouseEvent.BUTTON1_DOWN_MASK; if (lastEditPosition != null && selectedRow == row && noModifiers && lastEditPosition[0] == row && lastEditPosition[1] == column) { int handleWidth = DefaultOutlineCellRenderer.getExpansionHandleWidth(); Insets ins = getInsets(); TreePath path = getLayoutCache().getPathForRow(convertRowIndexToModel(row)); int nd = path.getPathCount() - (isRootVisible() ? 1 : 2); if (nd < 0) { nd = 0; } int handleStart = ins.left + (nd * DefaultOutlineCellRenderer.getNestingWidth()); int handleEnd = ins.left + handleStart + handleWidth; // Translate 'x' to position of column if non-0: int columnStart = getCellRect(row, column, false).x; handleStart += columnStart; handleEnd += columnStart; if (me.getX() >= handleEnd) { lastEditPosition = null; return true; } } lastEditPosition = new int[] { row, column }; return false; } /** * Perform a selection/deselection of a check box on the given row and column, * if a check box exists on the given position. * @param row The row of the check box * @param column The column of the check box * @param me The mouse event that performs the check, or null. * @return true if a {@link CheckRenderDataProvider} is found * on the given row and column, is checkable and enabled and the * mouse event is either null or upon the check-box * location. Returns false otherwise. * @since 1.25 */ protected final boolean checkAt(int row, int column, MouseEvent me) { RenderDataProvider render = getRenderDataProvider(); TableCellRenderer tcr = getDefaultRenderer(Object.class); if (render instanceof CheckRenderDataProvider && tcr instanceof DefaultOutlineCellRenderer) { CheckRenderDataProvider crender = (CheckRenderDataProvider) render; DefaultOutlineCellRenderer ocr = (DefaultOutlineCellRenderer) tcr; Object value = getValueAt(row, column); if (value != null && crender.isCheckable(value) && crender.isCheckEnabled(value)) { boolean chBoxPosition; if (me == null) { chBoxPosition = true; } else { int handleWidth = DefaultOutlineCellRenderer.getExpansionHandleWidth(); int chWidth = ocr.getTheCheckBoxWidth(); Insets ins = getInsets(); TreePath path = getLayoutCache().getPathForRow(convertRowIndexToModel(row)); int nd = path.getPathCount() - (isRootVisible() ? 1 : 2); if (nd < 0) { nd = 0; } int chStart = ins.left + (nd * DefaultOutlineCellRenderer.getNestingWidth()) + handleWidth; int chEnd = chStart + chWidth; //TODO: Translate x/y to position of column if non-0 chBoxPosition = (me.getX() > ins.left && me.getX() >= chStart && me.getX() <= chEnd); } if (chBoxPosition) { Boolean selected = crender.isSelected(value); if (selected == null || Boolean.TRUE.equals(selected)) { crender.setSelected(value, Boolean.FALSE); } else { crender.setSelected(value, Boolean.TRUE); } Rectangle r = getCellRect(row, column, true); repaint (r.x, r.y, r.width, r.height); return true; } } } return false; } /** * Configure the cell editor. * This method allows to override the configuration of cell editor when cell editing is initiated. * * @param editor The editor component * @param row Editor's row * @param column Editor's column */ protected void configureTreeCellEditor( Component editor, int row, int column ) { if( !(editor instanceof JComponent) ) { return; } TreeCellEditorBorder b = new TreeCellEditorBorder(); TreePath path = getLayoutCache().getPathForRow(convertRowIndexToModel(row)); Object o = getValueAt(row, column); RenderDataProvider rdp = getRenderDataProvider(); TableCellRenderer tcr = getDefaultRenderer(Object.class); if (rdp instanceof CheckRenderDataProvider && tcr instanceof DefaultOutlineCellRenderer) { CheckRenderDataProvider crender = (CheckRenderDataProvider) rdp; DefaultOutlineCellRenderer ocr = (DefaultOutlineCellRenderer) tcr; Object value = getValueAt(row, column); if (value != null && crender.isCheckable(value) && crender.isCheckEnabled(value)) { b.checkWidth = ocr.getTheCheckBoxWidth(); b.checkBox = ocr.setUpCheckBox(crender, value, ocr.createCheckBox()); } } b.icon = rdp.getIcon(o); b.nestingDepth = Math.max( 0, path.getPathCount() - (isRootVisible() ? 1 : 2) ); b.isLeaf = getOutlineModel().isLeaf(o); b.isExpanded = getLayoutCache().isExpanded(path); ((JComponent)editor).setBorder(b); } /** Computes row height ... */ @Override public void addNotify () { super.addNotify (); if (!rowHeightIsSet) { calcRowHeight(); } } /** Calculate the height of rows based on the current font. */ private void calcRowHeight() { //Users of themes can set an explicit row height, so check for it Integer i = (Integer) UIManager.get("netbeans.outline.rowHeight"); //NOI18N int rHeight = 20; if (i != null) { rHeight = i.intValue(); } else { //Derive a row height to accomodate the font and expando icon Font f = getFont(); FontMetrics fm = getFontMetrics(f); int h = Math.max (fm.getHeight () + fm.getMaxDescent (), DefaultOutlineCellRenderer.getExpansionHandleHeight ()); rHeight = Math.max (rHeight, h) + 2; // XXX: two pixels for cell's insets } //Set row height. If displayable, this will generate a new call //to paint() setRowHeight(rHeight); } @Override public void tableChanged(TableModelEvent e) { // System.err.println("Table got tableChanged " + e); super.tableChanged(e); // System.err.println("row count is " + getRowCount()); } @Override public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) { if (selectionDisabled) { //selectionDisabled = false; //System.err.println("\nSelection DISABLED."); return ; } super.changeSelection(rowIndex, columnIndex, toggle, extend); } @Override protected void processMouseEvent(MouseEvent e) { switch(e.getID()) { case MouseEvent.MOUSE_PRESSED: selectionDisabled = false; break; case MouseEvent.MOUSE_RELEASED: selectionDisabled = false; break; case MouseEvent.MOUSE_CLICKED: selectionDisabled = false; break; case MouseEvent.MOUSE_ENTERED: selectionDisabled = false; break; case MouseEvent.MOUSE_EXITED: selectionDisabled = false; break; case MouseEvent.MOUSE_MOVED: break; case MouseEvent.MOUSE_DRAGGED: if (selectionDisabled) { //System.err.println("\nDrag DISABLED."); return ; } break; case MouseEvent.MOUSE_WHEEL: break; } super.processMouseEvent(e); } /** Create a component listener to handle size changes if the table model * is large-model */ private ComponentListener getComponentListener() { if (componentListener == null) { componentListener = new SizeManager(); } return componentListener; } private JScrollPane getScrollPane() { JScrollPane result = null; if (getParent() instanceof JViewport) { if (((JViewport) getParent()).getParent() instanceof JScrollPane) { result = (JScrollPane) ((JViewport) getParent()).getParent(); } } return result; } private void change() { revalidate(); repaint(); } private class ND extends AbstractLayoutCache.NodeDimensions { @Override public Rectangle getNodeDimensions(Object value, int row, int depth, boolean expanded, Rectangle bounds) { int wid = Outline.this.getColumnModel().getColumn(0).getPreferredWidth(); bounds.setBounds (0, row * getRowHeight(), wid, getRowHeight()); return bounds; } } /** A component listener. If we're a large model table, we need * to inform the FixedHeightLayoutCache when the size changes, so it * can update its mapping of visible nodes */ private class SizeManager extends ComponentAdapter implements ActionListener { protected Timer timer = null; protected JScrollBar scrollBar = null; @Override public void componentMoved(ComponentEvent e) { if(timer == null) { JScrollPane scrollPane = getScrollPane(); if(scrollPane == null) { change(); } else { scrollBar = scrollPane.getVerticalScrollBar(); if(scrollBar == null || !scrollBar.getValueIsAdjusting()) { // Try the horizontal scrollbar. if((scrollBar = scrollPane.getHorizontalScrollBar()) != null && scrollBar.getValueIsAdjusting()) { startTimer(); } else { change(); } } else { startTimer(); } } } } protected void startTimer() { if(timer == null) { timer = new Timer(200, this); timer.setRepeats(true); } timer.start(); } @Override public void actionPerformed(ActionEvent ae) { if(scrollBar == null || !scrollBar.getValueIsAdjusting()) { if(timer != null) timer.stop(); change(); timer = null; scrollBar = null; } } @Override public void componentHidden(ComponentEvent e) { } @Override public void componentResized(ComponentEvent e) { } @Override public void componentShown(ComponentEvent e) { } } private static class TreeCellEditorBorder implements Border { private Insets insets = new Insets(0,0,0,0); private boolean isLeaf; private boolean isExpanded; private Icon icon; private int nestingDepth; private final int ICON_TEXT_GAP = new JLabel().getIconTextGap(); private int checkWidth; private JCheckBox checkBox; @Override public Insets getBorderInsets(Component c) { insets.left = (nestingDepth * DefaultOutlineCellRenderer.getNestingWidth()) +DefaultOutlineCellRenderer.getExpansionHandleWidth()+1; insets.left += checkWidth + ((icon != null) ? icon.getIconWidth() + ICON_TEXT_GAP : 0); insets.top = 1; insets.right = 1; insets.bottom = 1; return insets; } @Override public boolean isBorderOpaque() { return false; } @Override public void paintBorder(Component c, java.awt.Graphics g, int x, int y, int width, int height) { int iconY; int iconX = nestingDepth * DefaultOutlineCellRenderer.getNestingWidth(); if( !isLeaf ) { Icon expIcon = isExpanded ? DefaultOutlineCellRenderer.getExpandedIcon() : DefaultOutlineCellRenderer.getCollapsedIcon(); if (expIcon.getIconHeight() < height) { iconY = (height / 2) - (expIcon.getIconHeight() / 2); } else { iconY = 0; } expIcon.paintIcon(c, g, iconX, iconY); } iconX += DefaultOutlineCellRenderer.getExpansionHandleWidth() + 1; if (null != checkBox) { java.awt.Graphics chbg = g.create(iconX, y, checkWidth, height); checkBox.paint(chbg); chbg.dispose(); } iconX += checkWidth; if( null != icon ) { if (icon.getIconHeight() < height) { iconY = (height / 2) - (icon.getIconHeight() / 2); } else { iconY = 0; } icon.paintIcon(c, g, iconX, iconY); } } } private class ExpandAction extends AbstractAction { private boolean expand; private Action origAction; public ExpandAction( boolean expand, Action orig ) { this.expand = expand; this.origAction = orig; } @Override public void actionPerformed(ActionEvent e) { if( getSelectedRowCount() == 1 && isTreeColumnIndex (getSelectedColumn()) ) { TreePath selPath = getLayoutCache().getPathForRow(convertRowIndexToModel (getSelectedRow ())); if( null != selPath && !getOutlineModel().isLeaf(selPath.getLastPathComponent()) ) { boolean expanded = getLayoutCache().isExpanded(selPath); if( expanded && !expand ) { collapsePath(selPath); return; } else if( !expanded && expand ) { expandPath(selPath); return; } else if (expanded && expand && getOutlineModel().getChildCount(selPath.getLastPathComponent()) > 0) { int row = getSelectedRow() + 1; if (row < getRowCount()) { selectCell(row, getSelectedColumn()); return ; } } else if (!expanded && !expand) { TreePath parentPath = selPath.getParentPath(); if (parentPath != null) { int row = convertRowIndexToView(getLayoutCache().getRowForPath(parentPath)); selectCell(row, getSelectedColumn()); return ; } } } else if( null != selPath && getOutlineModel().isLeaf(selPath.getLastPathComponent()) ) { if (!expand) { TreePath parentPath = selPath.getParentPath(); if (parentPath != null) { int row = convertRowIndexToView(getLayoutCache().getRowForPath(parentPath)); selectCell(row, getSelectedColumn()); return ; } } } } if( null != origAction ) origAction.actionPerformed(e); } private void selectCell(int row, int col) { changeSelection(row, col, false, false); scrollRectToVisible(getCellRect(row, col, false)); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy