components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/tree/TreeTable.java
changeset 827 0944d8c0158b
equal deleted inserted replaced
826:c6aad84d2493 827:0944d8c0158b
       
     1 /*
       
     2  * CDDL HEADER START
       
     3  *
       
     4  * The contents of this file are subject to the terms of the
       
     5  * Common Development and Distribution License (the "License").
       
     6  * You may not use this file except in compliance with the License.
       
     7  *
       
     8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
       
     9  * or http://www.opensolaris.org/os/licensing.
       
    10  * See the License for the specific language governing permissions
       
    11  * and limitations under the License.
       
    12  *
       
    13  * When distributing Covered Code, include this CDDL HEADER in each
       
    14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
       
    15  * If applicable, add the following below this CDDL HEADER, with the
       
    16  * fields enclosed by brackets "[]" replaced with your own identifying
       
    17  * information: Portions Copyright [yyyy] [name of copyright owner]
       
    18  *
       
    19  * CDDL HEADER END
       
    20  */
       
    21 
       
    22 /*
       
    23  * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
       
    24  */
       
    25 
       
    26 package com.oracle.solaris.vp.util.swing.tree;
       
    27 
       
    28 import java.awt.*;
       
    29 import java.awt.event.MouseEvent;
       
    30 import java.util.*;
       
    31 import java.util.List;
       
    32 import javax.swing.*;
       
    33 import javax.swing.event.*;
       
    34 import javax.swing.table.*;
       
    35 import com.oracle.solaris.vp.util.swing.ExtTable;
       
    36 
       
    37 @SuppressWarnings({"serial"})
       
    38 public class TreeTable extends ExtTable {
       
    39     //
       
    40     // Instance data
       
    41     //
       
    42 
       
    43     protected class RenderPanel extends JPanel {
       
    44 	//
       
    45 	// Instance data
       
    46 	//
       
    47 
       
    48 	private TreeLines lines;
       
    49 
       
    50 	//
       
    51 	// Constructors
       
    52 	//
       
    53 
       
    54 	public RenderPanel(TreeLines lines) {
       
    55 	    super(new BorderLayout());
       
    56 	    this.lines = lines;
       
    57 	    setBackground(UIManager.getColor("Table.selectionBackground"));
       
    58 	    setOpaque(false);
       
    59 	}
       
    60 
       
    61 	//
       
    62 	// Component methods
       
    63 	//
       
    64 
       
    65 	@Override
       
    66 	public boolean isEnabled() {
       
    67 	    try {
       
    68 		return getComponent(1).isEnabled();
       
    69 	    } catch (IndexOutOfBoundsException e) {
       
    70 		return false;
       
    71 	    }
       
    72 	}
       
    73 
       
    74 	@Override
       
    75 	public void setEnabled(boolean enabled) {
       
    76 	    try {
       
    77 		getComponent(1).setEnabled(enabled);
       
    78 	    } catch (IndexOutOfBoundsException ignore) {
       
    79 	    }
       
    80 	}
       
    81 
       
    82 	//
       
    83 	// RenderPanel methods
       
    84 	//
       
    85 
       
    86 	public TreeLines getTreeLines() {
       
    87 	    return lines;
       
    88 	}
       
    89 
       
    90 	public void setComponent(Component c) {
       
    91 	    removeAll();
       
    92 	    add(lines, BorderLayout.WEST);
       
    93 	    add(c, BorderLayout.CENTER);
       
    94 	}
       
    95     }
       
    96 
       
    97     //
       
    98     // Static data
       
    99     //
       
   100 
       
   101     private RowFilter<TreeTableModelAdapter, Integer> ROOT_VISIBLE_FILTER =
       
   102 	new RowFilter<TreeTableModelAdapter, Integer>() {
       
   103 	    @Override
       
   104 	    public boolean include(RowFilter.Entry<
       
   105 		? extends TreeTableModelAdapter, ? extends Integer> entry) {
       
   106 
       
   107 		if (isRootVisible()) {
       
   108 		    return true;
       
   109 		}
       
   110 
       
   111 		int index = entry.getIdentifier();
       
   112 		ModelRowData row = entry.getModel().getRow(index);
       
   113 
       
   114 		return row.getLevel() != 0;
       
   115 	    }
       
   116 	};
       
   117 
       
   118     private RowFilter<TreeTableModelAdapter, Integer>
       
   119 	ANCESTOR_COLLAPSED_FILTER =
       
   120 	new RowFilter<TreeTableModelAdapter, Integer>() {
       
   121 	    @Override
       
   122 	    public boolean include(RowFilter.Entry<
       
   123 		? extends TreeTableModelAdapter, ? extends Integer> entry) {
       
   124 
       
   125 		TreeTableModelAdapter adapter = entry.getModel();
       
   126 		int index = entry.getIdentifier();
       
   127 		ModelRowData row = adapter.getRow(index);
       
   128 
       
   129 		ModelRowData parent = adapter.getParent(row);
       
   130 		while (parent != null) {
       
   131 		    if (!parent.isExpanded()) {
       
   132 			return false;
       
   133 		    }
       
   134 		    parent = adapter.getParent(parent);
       
   135 		}
       
   136 
       
   137 		return true;
       
   138 	    }
       
   139 	};
       
   140 
       
   141     //
       
   142     // Instance data
       
   143     //
       
   144 
       
   145     private List<ViewRowData> rowData = new ArrayList<ViewRowData>();
       
   146 
       
   147     private TreeLines renderTreeLines = new TreeLines(this);
       
   148     private RenderPanel renderPanel = new RenderPanel(renderTreeLines);
       
   149 
       
   150     private TreeLines editorTreeLines = new TreeLines(this);
       
   151     private RenderPanel editPanel = new RenderPanel(editorTreeLines);
       
   152 
       
   153     private boolean rootVisible = true;
       
   154     private boolean turnerPressed;
       
   155 
       
   156     private List<RowFilter<TreeTableModelAdapter, Integer>> filters =
       
   157 	new ArrayList<RowFilter<TreeTableModelAdapter, Integer>>();
       
   158 
       
   159     //
       
   160     // Constructors
       
   161     //
       
   162 
       
   163     public TreeTable(TreeTableModel model) {
       
   164 	super(new TreeTableModelAdapter(model));
       
   165 
       
   166 	// Set up sorting
       
   167 	setAutoCreateRowSorter(false);
       
   168 
       
   169 	final TableRowSorter<TreeTableModelAdapter> sorter =
       
   170 	    new TableRowSorter<TreeTableModelAdapter>(getModel());
       
   171 
       
   172 	// By default, sorting is disabled
       
   173 	for (int i = 0, n = model.getColumnCount(); i < n; i++) {
       
   174 	    sorter.setSortable(i, false);
       
   175 	}
       
   176 
       
   177 	getColumnModel().addColumnModelListener(
       
   178 	    new TableColumnModelListener() {
       
   179 		@Override
       
   180 		public void columnAdded(TableColumnModelEvent e) {
       
   181 		    sorter.setSortable(e.getToIndex(), false);
       
   182 		}
       
   183 
       
   184 		@Override
       
   185 		public void columnMarginChanged(ChangeEvent e) {
       
   186 		}
       
   187 
       
   188 		@Override
       
   189 		public void columnMoved(TableColumnModelEvent e) {
       
   190 		}
       
   191 
       
   192 		@Override
       
   193 		public void columnRemoved(TableColumnModelEvent e) {
       
   194 		}
       
   195 
       
   196 		@Override
       
   197 		public void columnSelectionChanged(ListSelectionEvent e) {
       
   198 		}
       
   199 	    });
       
   200 
       
   201 	sorter.setRowFilter(
       
   202 	    new RowFilter<TreeTableModelAdapter, Integer>() {
       
   203 		@Override
       
   204 		public boolean include(
       
   205 		    RowFilter.Entry<? extends TreeTableModelAdapter,
       
   206 		    ? extends Integer> entry) {
       
   207 
       
   208 		    synchronized (filters) {
       
   209 			for (RowFilter<TreeTableModelAdapter, Integer> filter :
       
   210 			    filters) {
       
   211 
       
   212 			    if (!filter.include(entry)) {
       
   213 				return false;
       
   214 			    }
       
   215 			}
       
   216 			return true;
       
   217 		    }
       
   218 		}
       
   219 	    });
       
   220 	addRowFilter(ROOT_VISIBLE_FILTER);
       
   221 	addRowFilter(ANCESTOR_COLLAPSED_FILTER);
       
   222 
       
   223 	setRowSorter(sorter);
       
   224     }
       
   225 
       
   226     //
       
   227     // Component methods
       
   228     //
       
   229 
       
   230     @Override
       
   231     protected void processMouseEvent(MouseEvent e) {
       
   232 	if (e.getButton() == 1) {
       
   233 	    switch (e.getID()) {
       
   234 		case MouseEvent.MOUSE_PRESSED:
       
   235 		    Point p = e.getPoint();
       
   236 		    int vRow = rowAtPoint(p);
       
   237 		    if (vRow != -1) {
       
   238 			int vCol = columnAtPoint(p);
       
   239 			int mCol = convertColumnIndexToModel(vCol);
       
   240 			TreeTableModelAdapter adapter = getModel();
       
   241 			if (adapter.getTreeTableModel().isTreeColumn(mCol)) {
       
   242 
       
   243 			    Rectangle cellRect = getCellRect(vRow, vCol, false);
       
   244 			    Point relPoint = new Point(
       
   245 				p.x - cellRect.x, p.y - cellRect.y);
       
   246 
       
   247 			    ViewRowData data = getRowData(vRow);
       
   248 			    renderTreeLines.setRowData(data);
       
   249 
       
   250 			    if (renderTreeLines.isOverTurner(relPoint)) {
       
   251 //				TableCellEditor editor =
       
   252 //				    getTable().getCellEditor();
       
   253 //				if (editor != null) {
       
   254 //				    editor.stopCellEditing();
       
   255 //				}
       
   256 
       
   257 				ModelRowData row = data.getModelRow();
       
   258 				row.setExpanded(!row.isExpanded());
       
   259 				((TableRowSorter)getRowSorter()).sort();
       
   260 				turnerPressed = true;
       
   261 				e.consume();
       
   262 				return;
       
   263 			    }
       
   264 			}
       
   265 		    }
       
   266 		    break;
       
   267 
       
   268 		case MouseEvent.MOUSE_RELEASED:
       
   269 		    turnerPressed = false;
       
   270 		    break;
       
   271 	    }
       
   272 	}
       
   273 
       
   274 	super.processMouseEvent(e);
       
   275     }
       
   276 
       
   277     @Override
       
   278     protected void processMouseMotionEvent(MouseEvent e) {
       
   279 	switch (e.getID()) {
       
   280 	    case MouseEvent.MOUSE_DRAGGED:
       
   281 		if (turnerPressed) {
       
   282 		    e.consume();
       
   283 		    return;
       
   284 		}
       
   285 		break;
       
   286 	}
       
   287 
       
   288 	super.processMouseMotionEvent(e);
       
   289     }
       
   290 
       
   291     //
       
   292     // JComponent methods
       
   293     //
       
   294 
       
   295     @Override
       
   296     protected void paintComponent(Graphics g) {
       
   297 	// This recalculation really should only be done when 1) a sort occurs,
       
   298 	// 2) the model changes, or 3) the visible rows change.  Lacking hooks
       
   299 	// into the latter, do it every time.
       
   300 	synchronized (rowData) {
       
   301 	    Rectangle clip = getVisibleRect();
       
   302 	    int rHeight = getRowHeight();
       
   303 	    int fVRow = clip.y / rHeight;
       
   304 	    int lVRow = (clip.y + clip.height) / rHeight;
       
   305 	    lVRow = Math.min(lVRow, getRowCount() - 1);
       
   306 
       
   307 	    int nVRows = lVRow - fVRow + 1;
       
   308 
       
   309 	    rowData.clear();
       
   310 
       
   311 	    TreeTableModelAdapter adapter = getModel();
       
   312 	    List<Boolean> showLines = null;
       
   313 
       
   314 	    ViewRowData data = null;
       
   315 	    for (int vRow = lVRow; vRow >= fVRow; vRow--) {
       
   316 		data = createRowData(adapter, vRow,
       
   317 		    data == null ? null : data.getShowLines());
       
   318 
       
   319 		rowData.add(data);
       
   320 	    }
       
   321 
       
   322 	    Collections.reverse(rowData);
       
   323 	}
       
   324 
       
   325 	super.paintComponent(g);
       
   326     }
       
   327 
       
   328     //
       
   329     // JTable methods
       
   330     //
       
   331 
       
   332     @Override
       
   333     public TreeTableModelAdapter getModel() {
       
   334 	return (TreeTableModelAdapter)super.getModel();
       
   335     }
       
   336 
       
   337     @Override
       
   338     public Component prepareEditor(TableCellEditor editor, int vRow, int vCol) {
       
   339 	Component comp = super.prepareEditor(editor, vRow, vCol);
       
   340 	return prepare(editPanel, comp, vRow, vCol);
       
   341     }
       
   342 
       
   343     @Override
       
   344     public Component prepareRenderer(TableCellRenderer renderer, int vRow,
       
   345 	int vCol) {
       
   346 
       
   347 	Component comp = super.prepareRenderer(renderer, vRow, vCol);
       
   348 	return prepare(renderPanel, comp, vRow, vCol);
       
   349     }
       
   350 
       
   351     //
       
   352     // TreeTable methods
       
   353     //
       
   354 
       
   355     public void addRowFilter(RowFilter<TreeTableModelAdapter, Integer> filter) {
       
   356 	synchronized (filters) {
       
   357 	    filters.add(filter);
       
   358 	}
       
   359     }
       
   360 
       
   361     public TreeLines getEditorTreeLines() {
       
   362 	return editorTreeLines;
       
   363     }
       
   364 
       
   365     public TreeLines getRenderTreeLines() {
       
   366 	return renderTreeLines;
       
   367     }
       
   368 
       
   369     public boolean isRootVisible() {
       
   370 	return rootVisible;
       
   371     }
       
   372 
       
   373     public void removeRowFilter(
       
   374 	RowFilter<TreeTableModelAdapter, Integer> filter) {
       
   375 
       
   376 	synchronized (filters) {
       
   377 	    filters.remove(filter);
       
   378 	}
       
   379     }
       
   380 
       
   381     public void setRootVisible(boolean rootVisible) {
       
   382 	this.rootVisible = rootVisible;
       
   383 	((TableRowSorter)getRowSorter()).sort();
       
   384 	repaint();
       
   385     }
       
   386 
       
   387     //
       
   388     // Private methods
       
   389     //
       
   390 
       
   391     private ViewRowData createRowData(TreeTableModelAdapter adapter, int vRow,
       
   392 	List<Boolean> templateShowLines) {
       
   393 
       
   394 	int mRow = convertRowIndexToModel(vRow);
       
   395 	ModelRowData row = adapter.getRow(mRow);
       
   396 	int level = row.getLevel();
       
   397 
       
   398 	// Ensure sufficient size
       
   399 	List<Boolean> showLines = new ArrayList<Boolean>();
       
   400 	for (int i = 0; i < level; i++) {
       
   401 	    Boolean value = null;
       
   402 	    try {
       
   403 		value = templateShowLines.get(i);
       
   404 	    } catch (IndexOutOfBoundsException ignore) {
       
   405 	    } catch (NullPointerException ignore) {
       
   406 	    }
       
   407 	    showLines.add(value);
       
   408 	}
       
   409 	showLines.add(null);
       
   410 
       
   411 	ViewRowData data = new ViewRowData(vRow, row, showLines);
       
   412 
       
   413 	int nRows = getRowCount();
       
   414 	for (vRow++; level >= 0 && showLines.get(level) == null; vRow++) {
       
   415 	    int curLevel = -1;
       
   416 	    if (vRow < nRows) {
       
   417 		mRow = convertRowIndexToModel(vRow);
       
   418 		row = adapter.getRow(mRow);
       
   419 		curLevel = row.getLevel();
       
   420 	    }
       
   421 
       
   422 	    if (curLevel == level) {
       
   423 		showLines.set(level, true);
       
   424 		level--;
       
   425 	    } else
       
   426 
       
   427 	    if (curLevel < level) {
       
   428 		for (int i = level; i > curLevel; i--) {
       
   429 		    showLines.set(level, false);
       
   430 		    level--;
       
   431 		}
       
   432 
       
   433 		if (level != -1) {
       
   434 		    showLines.set(level, true);
       
   435 		    level--;
       
   436 		}
       
   437 	    }
       
   438 	}
       
   439 
       
   440 	return data;
       
   441     }
       
   442 
       
   443     private ViewRowData getRowData(int vRow) throws IndexOutOfBoundsException {
       
   444 	synchronized (rowData) {
       
   445 	    int offset = rowData.get(0).getRow();
       
   446 	    return rowData.get(vRow - offset);
       
   447 	}
       
   448     }
       
   449 
       
   450     private Component prepare(RenderPanel panel, Component comp, int vRow,
       
   451 	int vCol) {
       
   452 
       
   453 	TreeTableModelAdapter model = getModel();
       
   454 	int mCol = convertColumnIndexToModel(vCol);
       
   455 
       
   456 	if (model.getTreeTableModel().isTreeColumn(mCol)) {
       
   457 	    try {
       
   458 		ViewRowData data = getRowData(vRow);
       
   459 		panel.getTreeLines().setRowData(data);
       
   460 		panel.setComponent(comp);
       
   461 
       
   462 		if (isCellSelected(vRow, vCol)) {
       
   463 		    panel.setOpaque(true);
       
   464 		    panel.getTreeLines().setForeground(
       
   465 			UIManager.getColor("Table.selectionForeground"));
       
   466 		} else {
       
   467 		    panel.setOpaque(false);
       
   468 		    panel.getTreeLines().setForeground(null);
       
   469 		}
       
   470 
       
   471 		comp = panel;
       
   472 	    } catch (IndexOutOfBoundsException ignore) {
       
   473 	    }
       
   474 	}
       
   475 
       
   476 	return comp;
       
   477     }
       
   478 }