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

org.jppf.ui.treetable.JTreeTable Maven / Gradle / Ivy

The newest version!
/*
 * JPPF.
 * Copyright (C) 2005-2019 JPPF Team.
 * http://www.jppf.org
 *
 * Licensed 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.
 */
/*
 * @(#)JTreeTable.java  1.2 98/10/27
 *
 * Copyright 1997, 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 org.jppf.ui.treetable;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.tree.*;

import org.slf4j.*;

/**
 * This example shows how to create a simple JTreeTable component, by using a JTree as a renderer (and editor) for the
 * cells in a particular column in the JTable.
 *
 * @version 1.2 10/27/98
 *
 * @author Philip Milne
 * @author Scott Violet
 */
public class JTreeTable extends JTable {
  /**
   * Logger for this class.
   */
  private static Logger log = LoggerFactory.getLogger(JTreeTable.class);
  /**
   * A subclass of JTree.
   */
  protected TreeTableCellRenderer tree;

  /**
   * Initialize this tree table with the specified model.
   * @param treeTableModel this tree table's model.
   */
  public JTreeTable(final TreeTableModel treeTableModel) {
    super();
    // 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.
    final ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper();
    tree.setSelectionModel(selectionWrapper);
    selectionWrapper.setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
    setSelectionModel(selectionWrapper.getListSelectionModel());
    selectionWrapper.getListSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    // 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.
    // Metal looks better like this.
    if (tree.getRowHeight() < 1) setRowHeight(18);
    //setTableHeader(new TreeTableHeader());
  }

  /**
   * 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.
   * {@inheritDoc}
   */
  @Override
  public int getEditingRow() {
    return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 : editingRow;
  }

  /**
   * Overridden to pass the new rowHeight to the tree.
   * {@inheritDoc}
   */
  @Override
  public void setRowHeight(final 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 a {@link JTree} instance.
   */
  public JTree getTree() {
    return tree;
  }

  /**
   * Set the selection mode of this tree table.
   * @param selectionMode - one of ListSelectionModel.SINGLE_SELECTION, ListSelectionModel.SINGLE_INTERVAL_SELECTION, ListSelectionModel.MULTIPLE_INTERVAL_SELECTION.
   */
  @Override
  public void setSelectionMode(final int selectionMode) {
    super.setSelectionMode(selectionMode);
    int treeMode;
    if (selectionMode == ListSelectionModel.SINGLE_SELECTION) treeMode = TreeSelectionModel.SINGLE_TREE_SELECTION;
    else if (selectionMode == ListSelectionModel.SINGLE_INTERVAL_SELECTION) treeMode = TreeSelectionModel.CONTIGUOUS_TREE_SELECTION;
    else treeMode = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;
    tree.getSelectionModel().setSelectionMode(treeMode);
  }

  /**
   * A TreeCellRenderer that displays a JTree.
   */
  public class TreeTableCellRenderer extends JTree implements TableCellRenderer {
    /**
     * Last table/tree row asked to renderer.
     */
    protected int visibleRow;

    /**
     * Initialize this renderer with the specified tree model.
     * @param model a {@link TreeModel} instance.
     */
    public TreeTableCellRenderer(final TreeModel model) {
      super(model);
      setUI(new CustomTreeUI(JTreeTable.this));
      setOpaque(false);
    }

    /**
     * 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.
      final TreeCellRenderer tcr = getCellRenderer();
      if (tcr instanceof DefaultTreeCellRenderer) {
        final 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.
     * {@inheritDoc}
     */
    @Override
    public void setRowHeight(final 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.
     * {@inheritDoc}
     */
    @Override
    public void setBounds(final int x, final int y, final int w, final int h) {
      super.setBounds(x, 0, w, JTreeTable.this.getHeight());
    }

    /**
     * Subclassed to translate the graphics such that the last visible row will be drawn at 0,0.
     * {@inheritDoc}
     */
    @Override
    public void paint(final Graphics g) {
      g.translate(0, -visibleRow * getRowHeight());
      try {
        super.paint(g);
      } catch(final Exception e) {
        log.debug(e.getMessage(), e);
      }
    }

    /**
     * TreeCellRenderer method. Overridden to update the visible row.
     * {@inheritDoc}
     */
    @Override
    public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final 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(final JTable table, final Object value, final boolean isSelected, final int r, final 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). * {@inheritDoc} */ @Override public boolean isCellEditable(final EventObject e) { if (e instanceof MouseEvent) { for (int counter = getColumnCount() - 1; counter >= 0; counter--) { if (getColumnClass(counter) == TreeTableModel.class) { final MouseEvent me = (MouseEvent) e; if (me.isControlDown()) return false; final int x = me.getX() - getCellRect(0, counter, true).x; final int row = JTreeTable.this.rowAtPoint(me.getPoint()); final int y = me.getY() - (row * JTreeTable.this.getIntercellSpacing().height); final MouseEvent newME = new MouseEvent(tree, me.getID(), me.getWhen(), me.getModifiers(), x, y, me.getClickCount(), me.isPopupTrigger()); 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 { /** * Set to true when we are updating the ListSelectionModel. */ protected final AtomicBoolean updatingListSelectionModel = new AtomicBoolean(false); /** * */ protected final AtomicBoolean updatingTreeSelectionModel = new AtomicBoolean(false); /** * Initialize this selection model. */ public ListToTreeSelectionModelWrapper() { super(); getListSelectionModel().addListSelectionListener(createListSelectionListener()); getTree().getSelectionModel().addTreeSelectionListener(new TreeSelectionHandler()); } /** * Returns the list selection model. ListToTreeSelectionModelWrapper listens for changes to this model and updates * the selected paths accordingly. * @return a {@link ListSelectionModel} instance. */ 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.compareAndSet(false, true)) { try { if (selection != null) super.resetRowSelection(); } finally { updatingListSelectionModel.set(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. } /** * Creates and returns an instance of ListSelectionHandler. * @return a {@link ListSelectionListener} instance. */ protected ListSelectionListener createListSelectionListener() { return new ListSelectionHandler(); } /** * If updatingListSelectionModel is false, this will reset the selected paths from the selected rows in the list selection model. */ protected void updateSelectedPathsFromSelectedRows() { if (updatingListSelectionModel.compareAndSet(false, true)) { try { // This is way expensive, ListSelectionModel needs an enumerator for iterating. final int min = listSelectionModel.getMinSelectionIndex(); final int max = listSelectionModel.getMaxSelectionIndex(); clearSelection(); if (min != -1 && max != -1) { for (int counter = min; counter <= max; counter++) { if (listSelectionModel.isSelectedIndex(counter)) { final TreePath selPath = tree.getPathForRow(counter); if (selPath != null) addSelectionPath(selPath); } } } } finally { updatingListSelectionModel.set(false); } } } /** * If updatingListSelectionModel is false, this will reset the selected paths from the selected rows in * the list selection model. */ protected void updateSelectedTableRows() { if (!updatingListSelectionModel.get() && updatingTreeSelectionModel.compareAndSet(false, true)) { try { final TreeSelectionModel treeSelectionModel = getTree().getSelectionModel(); final int[] rows = treeSelectionModel.getSelectionRows(); if ((rows == null) || (rows.length == 0)) listSelectionModel.clearSelection(); else { final Set selectionSet = new HashSet<>(); for (final int r: rows) selectionSet.add(r); for (int i=0; i





© 2015 - 2025 Weber Informatics LLC | Privacy Policy