/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.designer.core.editor.structuretree; import org.pentaho.reporting.designer.core.ReportDesignerContext; import org.pentaho.reporting.designer.core.editor.ReportDocumentContext; import org.pentaho.reporting.designer.core.model.selection.DocumentContextSelectionModel; import org.pentaho.reporting.designer.core.model.selection.ReportSelectionEvent; import org.pentaho.reporting.designer.core.model.selection.ReportSelectionListener; import javax.swing.*; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.event.TreeWillExpandListener; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.ExpandVetoException; import javax.swing.tree.TreePath; import java.util.TreeSet; public abstract class AbstractReportTree extends JTree { public static enum RenderType { REPORT, DATA } protected class RestoreStateTask implements Runnable { protected RestoreStateTask() { } /** * When an object implementing interface <code>Runnable</code> is used to create a thread, starting the thread * causes the object's <code>run</code> method to be called in that separately executing thread. * <p/> * The general contract of the method <code>run</code> is that it may take any action whatsoever. * * @see Thread#run() */ public void run() { if ( getRenderContext() == null ) { return; } for ( final Integer index : getExpandedNodes() ) { expandRow( index.intValue() ); } } } private class ReportTreeExpansionListener implements TreeWillExpandListener { public void treeWillExpand( final TreeExpansionEvent event ) throws ExpandVetoException { addExpandedNode( getRowForPath( event.getPath() ) ); } public void treeWillCollapse( final TreeExpansionEvent event ) throws ExpandVetoException { removeExpandedNode( getRowForPath( event.getPath() ) ); } } private class TreeSelectionHandler implements TreeSelectionListener { /** * Called whenever the value of the selection changes. * * @param e the event that characterizes the change. */ public void valueChanged( final TreeSelectionEvent e ) { addExpandedNode( getRowForPath( e.getPath() ) ); if ( updateFromExternalSource ) { return; } updateFromInternalSource = true; try { final ReportDocumentContext renderContext = getRenderContext(); if ( renderContext == null ) { return; } final TreePath[] treePaths = getSelectionPaths(); if ( treePaths == null ) { selectionModel.clearSelection(); renderContext.getSelectionModel().clearSelection(); return; } final DocumentContextSelectionModel selectionModel = renderContext.getSelectionModel(); final Object[] data = new Object[ treePaths.length ]; for ( int i = 0; i < treePaths.length; i++ ) { final TreePath path = treePaths[ i ]; data[ i ] = path.getLastPathComponent(); } selectionModel.setSelectedElements( data ); } finally { updateFromInternalSource = false; } } } private class ReportSelectionHandler implements ReportSelectionListener { public void selectionAdded( final ReportSelectionEvent event ) { if ( isUpdateFromInternalSource() ) { return; } try { setUpdateFromExternalSource( true ); final TreePath path = getPathForNode( event.getElement() ); if ( path != null ) { addSelectionPath( path ); } } finally { setUpdateFromExternalSource( false ); } } public void selectionRemoved( final ReportSelectionEvent event ) { if ( isUpdateFromInternalSource() ) { return; } try { setUpdateFromExternalSource( true ); final TreePath path = getPathForNode( event.getElement() ); if ( path != null ) { removeSelectionPath( path ); } } finally { setUpdateFromExternalSource( false ); } } public void leadSelectionChanged( final ReportSelectionEvent event ) { } } protected static final DefaultTreeModel EMPTY_MODEL = new DefaultTreeModel( null, false ); private ReportDesignerContext reportDesignerContext; private boolean updateFromInternalSource; private boolean updateFromExternalSource; private ReportSelectionHandler selectionHandler; public AbstractReportTree() { super( EMPTY_MODEL ); selectionHandler = new ReportSelectionHandler(); addTreeWillExpandListener( new ReportTreeExpansionListener() ); getSelectionModel().addTreeSelectionListener( new TreeSelectionHandler() ); } protected ReportSelectionHandler getSelectionHandler() { return selectionHandler; } protected abstract TreePath getPathForNode( Object node ); public abstract void setRenderContext( final ReportDocumentContext renderContext ); protected abstract ReportDocumentContext getRenderContext(); public ReportDesignerContext getReportDesignerContext() { return reportDesignerContext; } public void setReportDesignerContext( final ReportDesignerContext reportDesignerContext ) { this.reportDesignerContext = reportDesignerContext; } protected TreeSet<Integer> getExpandedNodes() { final ReportDocumentContext renderContext = getRenderContext(); if ( renderContext == null ) { // dummy operation.. return new TreeSet<Integer>(); } final Object property = renderContext.getProperties().get( "::layout-report-tree:expanded-nodes" ); if ( property instanceof TreeSet ) { return (TreeSet<Integer>) property; } final TreeSet<Integer> retval = new TreeSet<Integer>(); renderContext.getProperties().put( "::layout-report-tree:expanded-nodes", retval ); return retval; } protected void addExpandedNode( final int row ) { getExpandedNodes().add( row ); } protected void removeExpandedNode( final int row ) { getExpandedNodes().remove( row ); } protected void restoreState() { SwingUtilities.invokeLater( new RestoreStateTask() ); } protected boolean isUpdateFromInternalSource() { return updateFromInternalSource; } protected void setUpdateFromInternalSource( final boolean updateFromInternalSource ) { this.updateFromInternalSource = updateFromInternalSource; } protected boolean isUpdateFromExternalSource() { return updateFromExternalSource; } protected void setUpdateFromExternalSource( final boolean updateFromExternalSource ) { this.updateFromExternalSource = updateFromExternalSource; } protected void invalidateLayoutCache() { // this bit of magic invalidates the layout cache setCellRenderer( new StructureTreeCellRenderer() ); } }