/*!
* 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;
import org.pentaho.reporting.designer.core.ReportDesignerContext;
import org.pentaho.reporting.designer.core.actions.DesignerContextAction;
import org.pentaho.reporting.designer.core.actions.ToggleStateAction;
import org.pentaho.reporting.designer.core.actions.elements.SelectCrosstabBandAction;
import org.pentaho.reporting.designer.core.actions.elements.format.BoldAction;
import org.pentaho.reporting.designer.core.actions.elements.format.EditHyperlinkAction;
import org.pentaho.reporting.designer.core.actions.elements.format.FontColorSelectorComponent;
import org.pentaho.reporting.designer.core.actions.elements.format.FontFamilySelectorComponent;
import org.pentaho.reporting.designer.core.actions.elements.format.FontSizeSelectorComponent;
import org.pentaho.reporting.designer.core.actions.elements.format.ItalicsAction;
import org.pentaho.reporting.designer.core.actions.elements.format.TextAlignmentCenterAction;
import org.pentaho.reporting.designer.core.actions.elements.format.TextAlignmentJustifyAction;
import org.pentaho.reporting.designer.core.actions.elements.format.TextAlignmentLeftAction;
import org.pentaho.reporting.designer.core.actions.elements.format.TextAlignmentRightAction;
import org.pentaho.reporting.designer.core.actions.elements.format.UnderlineAction;
import org.pentaho.reporting.designer.core.actions.global.ShowPreviewPaneAction;
import org.pentaho.reporting.designer.core.editor.preview.ReportPreviewComponent;
import org.pentaho.reporting.designer.core.editor.report.AbstractRenderComponent;
import org.pentaho.reporting.designer.core.editor.report.CrosstabRenderComponent;
import org.pentaho.reporting.designer.core.editor.report.ReportRenderEvent;
import org.pentaho.reporting.designer.core.editor.report.ReportRenderListener;
import org.pentaho.reporting.designer.core.editor.report.ResizeRootBandComponent;
import org.pentaho.reporting.designer.core.editor.report.RootBandRenderComponent;
import org.pentaho.reporting.designer.core.editor.report.RootBandRenderingModel;
import org.pentaho.reporting.designer.core.editor.report.layouting.CrosstabRenderer;
import org.pentaho.reporting.designer.core.editor.report.layouting.ElementRenderer;
import org.pentaho.reporting.designer.core.editor.report.layouting.RootBandRenderer;
import org.pentaho.reporting.designer.core.editor.report.lineal.AllVerticalLinealsComponent;
import org.pentaho.reporting.designer.core.editor.report.lineal.HorizontalLinealComponent;
import org.pentaho.reporting.designer.core.model.HorizontalPositionsModel;
import org.pentaho.reporting.designer.core.util.ActionToggleButton;
import org.pentaho.reporting.designer.core.util.CanvasImageLoader;
import org.pentaho.reporting.designer.core.util.Unit;
import org.pentaho.reporting.engine.classic.core.AbstractReportDefinition;
import org.pentaho.reporting.engine.classic.core.CrosstabElement;
import org.pentaho.reporting.engine.classic.core.ReportDefinition;
import org.pentaho.reporting.engine.classic.core.event.ReportModelEvent;
import org.pentaho.reporting.engine.classic.core.event.ReportModelListener;
import org.pentaho.reporting.libraries.designtime.swing.ToolbarButton;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
/**
* A component holding the report. It contains the lineals, zoom controller in the upper left corner and as a viewport,
* it contains the report-layout component.
*
* @author Thomas Morgner
*/
public class ReportRendererComponent extends JComponent {
private static class RightImageBorder extends JComponent {
public RightImageBorder() {
setOpaque( false );
setBackground( new Color( 0, 0, 0, 0 ) );
setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
final ImageIcon bottomRightBorder = CanvasImageLoader.getInstance().getRightCornerShadowImage();
setMinimumSize( new Dimension( bottomRightBorder.getIconWidth(), 0 ) );
setPreferredSize( new Dimension( bottomRightBorder.getIconWidth(), 0 ) );
}
protected void paintComponent( final Graphics g ) {
super.paintComponent( g );
// g.clearRect(0, 0, getWidth(), getHeight());
final ImageIcon rightBorder = CanvasImageLoader.getInstance().getRightShadowImage();
g.drawImage( rightBorder.getImage(), 0, 0, rightBorder.getIconWidth(), getHeight(), this );
}
}
private static class BottomImageBorder extends JComponent {
private ImageIcon bottomBorder;
private ImageIcon bottomRightBorder;
public BottomImageBorder() {
setOpaque( false );
setBackground( new Color( 0, 0, 0, 0 ) );
setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
bottomBorder = CanvasImageLoader.getInstance().getBottomShadowImage();
bottomRightBorder = CanvasImageLoader.getInstance().getRightCornerShadowImage();
setMinimumSize( new Dimension( 0, Math.max( bottomBorder.getIconHeight(), bottomRightBorder.getIconHeight() ) ) );
setPreferredSize(
new Dimension( 0, Math.max( bottomBorder.getIconHeight(), bottomRightBorder.getIconHeight() ) ) );
}
protected void paintComponent( final Graphics g ) {
super.paintComponent( g );
final int cornerWidth = bottomRightBorder.getIconWidth();
g.drawImage( bottomBorder.getImage(), 0, 0, getWidth() - cornerWidth, bottomBorder.getIconHeight(), this );
g.drawImage( bottomRightBorder.getImage(), getWidth() - cornerWidth, 0, cornerWidth,
bottomRightBorder.getIconHeight(), this );
}
}
private static class ImagePanel extends JComponent {
private Image img;
public ImagePanel( final Image img ) {
this.img = img;
final Dimension size = new Dimension( img.getWidth( null ), img.getHeight( null ) );
setPreferredSize( size );
setMinimumSize( size );
setMaximumSize( size );
setSize( size );
setLayout( null );
}
public void paintComponent( final Graphics g ) {
g.setColor( getBackground() );
g.fillRect( 0, 0, getWidth(), getHeight() );
g.drawImage( img, 0, 0, null );
}
}
private static class LayoutScrollable extends JPanel implements Scrollable {
private LayoutScrollable() {
setLayout( new BorderLayout() );
}
/**
* Returns the preferred size of the viewport for a view component. For example, the preferred size of a
* <code>JList</code> component is the size required to accommodate all of the cells in its list. However, the value
* of <code>preferredScrollableViewportSize</code> is the size required for <code>JList.getVisibleRowCount</code>
* rows. A component without any properties that would affect the viewport size should just return
* <code>getPreferredSize</code> here.
*
* @return the preferredSize of a <code>JViewport</code> whose view is this <code>Scrollable</code>
* @see JViewport#getPreferredSize
*/
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
/**
* Components that display logical rows or columns should compute the scroll increment that will completely expose
* one new row or column, depending on the value of orientation. Ideally, components should handle a partially
* exposed row or column by returning the distance required to completely expose the item.
* <p/>
* Scrolling containers, like JScrollPane, will use this method each time the user requests a unit scroll.
*
* @param visibleRect The view area visible within the viewport
* @param orientation Either SwingConstants.VERTICAL or SwingConstants.HORIZONTAL.
* @param direction Less than zero to scroll up/left, greater than zero for down/right.
* @return The "unit" increment for scrolling in the specified direction. This value should always be positive.
* @see JScrollBar#setUnitIncrement
*/
public int getScrollableUnitIncrement( final Rectangle visibleRect, final int orientation, final int direction ) {
return 20;
}
/**
* Components that display logical rows or columns should compute the scroll increment that will completely expose
* one block of rows or columns, depending on the value of orientation.
* <p/>
* Scrolling containers, like JScrollPane, will use this method each time the user requests a block scroll.
*
* @param visibleRect The view area visible within the viewport
* @param orientation Either SwingConstants.VERTICAL or SwingConstants.HORIZONTAL.
* @param direction Less than zero to scroll up/left, greater than zero for down/right.
* @return The "block" increment for scrolling in the specified direction. This value should always be positive.
* @see JScrollBar#setBlockIncrement
*/
public int getScrollableBlockIncrement( final Rectangle visibleRect, final int orientation, final int direction ) {
return 100;
}
/**
* Return true if a viewport should always force the width of this <code>Scrollable</code> to match the width of the
* viewport. For example a normal text view that supported line wrapping would return true here, since it would be
* undesirable for wrapped lines to disappear beyond the right edge of the viewport. Note that returning true for a
* Scrollable whose ancestor is a JScrollPane effectively disables horizontal scrolling.
* <p/>
* Scrolling containers, like JViewport, will use this method each time they are validated.
*
* @return True if a viewport should force the Scrollables width to match its own.
*/
public boolean getScrollableTracksViewportWidth() {
return false;
}
/**
* Return true if a viewport should always force the height of this Scrollable to match the height of the viewport.
* For example a columnar text view that flowed text in left to right columns could effectively disable vertical
* scrolling by returning true here.
* <p/>
* Scrolling containers, like JViewport, will use this method each time they are validated.
*
* @return True if a viewport should force the Scrollables height to match its own.
*/
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
private class RootBandModelUpdateHandler implements ChangeListener {
/**
* Invoked when the target of the listener has changed its state.
*
* @param e a ChangeEvent object
*/
public void stateChanged( final ChangeEvent e ) {
registerReport();
}
}
private class LayoutUpdateHandler implements ReportRenderListener {
private LayoutUpdateHandler() {
}
public void layoutChanged( final ReportRenderEvent event ) {
final RootBandRenderingModel renderingModel = getRenderingModel();
final AbstractReportDefinition report = getReport();
final HorizontalPositionsModel horizontalPositionsModel = getHorizontalPositionsModel();
final ElementRenderer[] allRenderers = renderingModel.getAllRenderers();
final long age = report.getChangeTracker();
boolean change = false;
synchronized( horizontalPositionsModel ) {
// update the horizontal positions ...
for ( int i = 0; i < allRenderers.length; i++ ) {
final ElementRenderer renderer = allRenderers[ i ];
final long[] keys = renderer.getHorizontalEdgePositionKeys();
if ( horizontalPositionsModel.add( keys, age ) ) {
change = true;
}
}
if ( horizontalPositionsModel.clear( age ) ) {
change = true;
}
}
if ( change ) {
// and then repaint ..
horizontalPositionsModel.fireChangeEvent();
for ( int i = 0; i < rootBandRenderers.size(); i++ ) {
final AbstractRenderComponent component = rootBandRenderers.get( i );
component.repaint();
}
}
}
}
private class ReportPreviewChangeHandler implements ReportModelListener {
public void nodeChanged( final ReportModelEvent event ) {
if ( !designVisible ) {
if ( designerContext.getActiveContext() != renderContext ) {
showDesign();
} else {
previewComponent.updatePreview( renderContext );
}
}
}
}
@SuppressWarnings( "deprecation" )
private static class NoKeysScrollPane extends JScrollPane {
protected void processKeyEvent( final KeyEvent e ) {
}
public boolean keyDown( final Event evt, final int key ) {
return false;
}
public boolean keyUp( final Event evt, final int key ) {
return false;
}
protected boolean processKeyBinding( final KeyStroke ks,
final KeyEvent e,
final int condition,
final boolean pressed ) {
return false;
}
}
private ReportDocumentContext renderContext;
private RootBandRenderingModel renderingModel;
private HorizontalLinealComponent horizontalLinealComponent;
private JPanel layoutRendererComponent;
private ArrayList<AbstractRenderComponent> rootBandRenderers;
private ReportDesignerContext designerContext;
private boolean designVisible;
private HorizontalPositionsModel horizontalPositionsModel;
private CardLayout cardLayout;
private ReportPreviewComponent previewComponent;
private JComponent designView;
private JComponent previewView;
public ReportRendererComponent( final ReportDesignerContext designerContext,
final ReportRenderContext renderContext ) {
if ( renderContext == null ) {
throw new NullPointerException();
}
if ( designerContext == null ) {
throw new NullPointerException();
}
this.designVisible = true;
this.designerContext = designerContext;
this.rootBandRenderers = new ArrayList<AbstractRenderComponent>();
this.renderContext = renderContext;
this.renderingModel = new RootBandRenderingModel( renderContext );
this.renderingModel.addChangeListener( new RootBandModelUpdateHandler() );
this.renderingModel.addReportRenderListener( new LayoutUpdateHandler() );
this.horizontalPositionsModel = HorizontalPositionsModel.getHorizontalPositionsModel( renderContext );
horizontalLinealComponent = new HorizontalLinealComponent( renderContext, false );
layoutRendererComponent = new JPanel();
layoutRendererComponent.setLayout( new BoxLayout( layoutRendererComponent, BoxLayout.Y_AXIS ) );
layoutRendererComponent.setBackground( new Color( 0, 0, 0, 0 ) );
layoutRendererComponent.setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
layoutRendererComponent.setOpaque( false );
cardLayout = new CardLayout();
previewComponent = new ReportPreviewComponent( designerContext );
getReport().addReportModelListener( new ReportPreviewChangeHandler() );
previewView = new ImagePanel( CanvasImageLoader.getInstance().getBackgroundImage().getImage() );
previewView.setLayout( new BorderLayout() );
previewView.add( previewComponent, BorderLayout.CENTER );
final ZoomModel zoomModel = renderContext.getZoomModel();
final JComponent zoomController = new ZoomController( zoomModel );
final AllVerticalLinealsComponent verticalLinealsComponent = new AllVerticalLinealsComponent( renderingModel );
verticalLinealsComponent.setOpaque( false );
verticalLinealsComponent.setBackground( new Color( 0, 0, 0, 0 ) );
verticalLinealsComponent.setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
final LayoutScrollable viewPortComponent = new LayoutScrollable();
viewPortComponent.add( layoutRendererComponent, BorderLayout.NORTH );
viewPortComponent.setBackground( new Color( 0, 0, 0, 0 ) );
viewPortComponent.setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
viewPortComponent.setOpaque( false );
// effectively disable all key events on scroller
// PRD-1441
final JScrollPane reportScrollPane = new NoKeysScrollPane();
reportScrollPane.setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
reportScrollPane.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED );
reportScrollPane.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
reportScrollPane.setColumnHeaderView( horizontalLinealComponent );
reportScrollPane.setCorner( JScrollPane.UPPER_LEFT_CORNER, zoomController );
reportScrollPane.setRowHeaderView( verticalLinealsComponent );
reportScrollPane.setViewportView( viewPortComponent );
reportScrollPane.setFocusTraversalKeysEnabled( false );
reportScrollPane.setBackground( new Color( 0, 0, 0, 0 ) );
reportScrollPane.setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
reportScrollPane.setOpaque( false );
final JViewport viewport = reportScrollPane.getViewport();
viewport.setOpaque( false );
viewPortComponent.setBackground( new Color( 0, 0, 0, 0 ) );
viewPortComponent.setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
reportScrollPane.getRowHeader().setOpaque( false );
( (JComponent) reportScrollPane.getRowHeader().getView() ).setOpaque( false );
reportScrollPane.getColumnHeader().setOpaque( false );
( (JComponent) reportScrollPane.getColumnHeader().getView() ).setOpaque( false );
designView = new ImagePanel( CanvasImageLoader.getInstance().getBackgroundImage().getImage() );
designView.setLayout( new BorderLayout() );
designView.setOpaque( true );
designView.add( createToolbar(), BorderLayout.NORTH );
designView.add( reportScrollPane, BorderLayout.CENTER );
add( designView, "design" ); // NON-NLS
add( previewView, "preview" ); // NON-NLS
setLayout( cardLayout );
showDesign();
registerReport();
}
public boolean isDesignVisible() {
return designVisible;
}
public void showPreview() {
designVisible = false;
previewView.setVisible( true );
previewComponent.updatePreview( renderContext );
designView.setVisible( false );
cardLayout.last( this );
repaint();
}
public void showDesign() {
designVisible = true;
previewView.setVisible( false );
previewComponent.updatePreview( null );
designView.setVisible( true );
cardLayout.first( this );
repaint();
}
public HorizontalPositionsModel getHorizontalPositionsModel() {
return horizontalPositionsModel;
}
private JToolBar createToolbar() {
final ShowPreviewPaneAction previewAction = new ShowPreviewPaneAction();
previewAction.setReportDesignerContext( designerContext );
final EditHyperlinkAction hyperlinkAction = new EditHyperlinkAction();
hyperlinkAction.setReportDesignerContext( designerContext );
final FontFamilySelectorComponent familySelectorComponent = new FontFamilySelectorComponent();
familySelectorComponent.setReportDesignerContext( designerContext );
final FontSizeSelectorComponent sizeSelectorComponent = new FontSizeSelectorComponent();
sizeSelectorComponent.setReportDesignerContext( designerContext );
final FontColorSelectorComponent colorSelectorComponent = new FontColorSelectorComponent();
colorSelectorComponent.setReportDesignerContext( designerContext );
final JToolBar toolBar = new JToolBar();
toolBar.setFloatable( false );
toolBar.setOpaque( true );
toolBar.add( new ToolbarButton( previewAction ) );
toolBar.add( new JToolBar.Separator() );
toolBar.add( familySelectorComponent );
toolBar.add( sizeSelectorComponent );
toolBar.add( new JToolBar.Separator() );
toolBar.add( createButton( new BoldAction() ) );
toolBar.add( createButton( new ItalicsAction() ) );
toolBar.add( createButton( new UnderlineAction() ) );
toolBar.add( new JToolBar.Separator() );
toolBar.add( colorSelectorComponent );
toolBar.add( new JToolBar.Separator() );
toolBar.add( createButton( new TextAlignmentLeftAction() ) );
toolBar.add( createButton( new TextAlignmentCenterAction() ) );
toolBar.add( createButton( new TextAlignmentRightAction() ) );
toolBar.add( createButton( new TextAlignmentJustifyAction() ) );
toolBar.add( new JToolBar.Separator() );
toolBar.add( new ToolbarButton( hyperlinkAction ) );
// Add special crosstab band selection icon
if ( getRenderContext().getReportDefinition() instanceof CrosstabElement ) {
final SelectCrosstabBandAction selectCrosstabBandAction = new SelectCrosstabBandAction();
selectCrosstabBandAction.setReportDesignerContext( designerContext );
toolBar.add( new ToolbarButton( selectCrosstabBandAction ) );
toolBar.add( new JToolBar.Separator() );
}
return toolBar;
}
private JToggleButton createButton( final DesignerContextAction action ) {
final ActionToggleButton button = new ActionToggleButton();
action.addPropertyChangeListener( new ActionSelectedHandler( button ) );
action.setReportDesignerContext( designerContext );
button.putClientProperty( "hideActionText", Boolean.TRUE ); // NON-NLS
button.setFocusable( false );
button.setAction( action );
return button;
}
public RootBandRenderingModel getRenderingModel() {
return renderingModel;
}
public AbstractReportDefinition getReport() {
return renderContext.getReportDefinition();
}
public void dispose() {
for ( int i = 0; i < rootBandRenderers.size(); i++ ) {
final AbstractRenderComponent o = rootBandRenderers.get( i );
o.dispose();
}
rootBandRenderers.clear();
previewComponent.dispose();
}
protected void registerReport() {
layoutRendererComponent.removeAll();
for ( int i = 0; i < rootBandRenderers.size(); i++ ) {
final AbstractRenderComponent o = rootBandRenderers.get( i );
o.dispose();
}
rootBandRenderers.clear();
final ElementRenderer[] allRenderers = renderingModel.getAllRenderers();
for ( int i = 0; i < allRenderers.length; i++ ) {
final ElementRenderer allRenderer = allRenderers[ i ];
final AbstractRenderComponent renderComponent;
if ( allRenderer instanceof RootBandRenderer ) {
final RootBandRenderer rootRenderer = (RootBandRenderer) allRenderer;
final ReportDocumentContext context = rootRenderer.getReportRenderContext();
final ReportDefinition reportDefinition = context.getReportDefinition();
// Increase crosstab canvas height during a drag-n-drop operation of a new crosstab
if ( reportDefinition instanceof CrosstabElement ) {
rootRenderer.setVisualHeight( Unit.INCH.getDotsPerUnit() * 1.5 * 2 );
}
final RootBandRenderComponent bandComponent =
new RootBandRenderComponent( designerContext, renderContext, false );
bandComponent.setShowTopBorder( false );
bandComponent.setShowLeftBorder( false );
bandComponent
.installRenderer( rootRenderer, horizontalLinealComponent.getLinealModel(), horizontalPositionsModel );
renderComponent = bandComponent;
} else if ( allRenderer instanceof CrosstabRenderer ) {
final CrosstabRenderer rootRenderer = (CrosstabRenderer) allRenderer;
rootRenderer.setVisualHeight( Unit.INCH.getDotsPerUnit() * 1.5 * 2 );
final CrosstabRenderComponent bandComponent = new CrosstabRenderComponent( designerContext, renderContext );
bandComponent.setShowTopBorder( false );
bandComponent.setShowLeftBorder( false );
bandComponent
.installRenderer( rootRenderer, horizontalLinealComponent.getLinealModel(), horizontalPositionsModel );
renderComponent = bandComponent;
} else {
renderComponent = null;
}
if ( renderComponent != null ) {
final JPanel renderWrapper = new JPanel( new GridBagLayout() );
renderWrapper.setOpaque( false );
renderWrapper.setBackground( new Color( 0, 0, 0, 0 ) );
renderWrapper.setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.BOTH;
renderWrapper.add( renderComponent, gbc );
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.BOTH;
renderWrapper.add( new RightImageBorder(), gbc );
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 1;
gbc.fill = GridBagConstraints.BOTH;
renderWrapper.add( new ResizeRootBandComponent( true, allRenderer, renderContext ), gbc );
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 1;
gbc.fill = GridBagConstraints.BOTH;
renderWrapper.add( new RightImageBorder(), gbc );
layoutRendererComponent.add( renderWrapper );
rootBandRenderers.add( renderComponent );
}
}
layoutRendererComponent.add( new BottomImageBorder() );
revalidate();
repaint();
}
public ReportDocumentContext getRenderContext() {
return renderContext;
}
private class ActionSelectedHandler implements PropertyChangeListener {
private JToggleButton button;
public ActionSelectedHandler( final JToggleButton aButton ) {
this.button = aButton;
}
public void propertyChange( final PropertyChangeEvent event ) {
final ToggleStateAction theAction = (ToggleStateAction) event.getSource();
this.button.setSelected( theAction.isSelected() );
}
}
}