/*!
* 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.report.lineal;
import org.pentaho.reporting.designer.core.Messages;
import org.pentaho.reporting.designer.core.editor.ReportDocumentContext;
import org.pentaho.reporting.designer.core.editor.ReportRenderContext;
import org.pentaho.reporting.designer.core.model.ModelUtility;
import org.pentaho.reporting.designer.core.model.lineal.GuideLine;
import org.pentaho.reporting.designer.core.model.lineal.LinealModel;
import org.pentaho.reporting.designer.core.model.lineal.LinealModelEvent;
import org.pentaho.reporting.designer.core.model.lineal.LinealModelListener;
import org.pentaho.reporting.designer.core.settings.SettingsListener;
import org.pentaho.reporting.designer.core.settings.WorkspaceSettings;
import org.pentaho.reporting.designer.core.util.CanvasImageLoader;
import org.pentaho.reporting.designer.core.util.GuideLineDialog;
import org.pentaho.reporting.designer.core.util.Unit;
import org.pentaho.reporting.designer.core.util.undo.UndoManager;
import org.pentaho.reporting.engine.classic.core.AbstractReportDefinition;
import org.pentaho.reporting.engine.classic.core.PageDefinition;
import org.pentaho.reporting.engine.classic.core.event.ReportModelEvent;
import org.pentaho.reporting.engine.classic.core.event.ReportModelListener;
import org.pentaho.reporting.engine.classic.core.util.PageFormatFactory;
import org.pentaho.reporting.libraries.designtime.swing.ColorUtility;
import org.pentaho.reporting.libraries.designtime.swing.LibSwingUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.text.DecimalFormat;
/**
* A horizontal lineal that is displayed on top of the editor pane. The horizontal lineal model is global for the whole
* report.
*/
public class HorizontalLinealComponent extends JPanel {
private class LinealUpdateHandler implements LinealModelListener {
private LinealUpdateHandler() {
}
public void modelChanged( final LinealModelEvent event ) {
repaint();
}
}
private class DragnDropHandler extends MouseAdapter implements MouseMotionListener {
private int draggedGuideLineIndex;
private DragnDropHandler() {
}
public void mouseDragged( final MouseEvent e ) {
final PageDefinition pageDefinition = getPageDefinition();
if ( pageDefinition == null ) {
return;
}
final LinealModel linealModel = getLinealModel();
if ( draggedGuideLineIndex == -1 || ( ( linealModel.getGuideLineCount() - 1 ) < draggedGuideLineIndex ) ) {
return;
}
if ( draggedGuideLineIndex != getActiveGuideLineIndex() ) {
setActiveGuideLineIndex( draggedGuideLineIndex );
}
final GuideLine dragged = linealModel.getGuideLine( draggedGuideLineIndex );
double start = 0;
if ( isShowLeftBorder() ) {
start = getLeftBorder();
}
final double width = pageDefinition.getWidth();
final float scaleFactor = getZoomAsMicropoints();
final double scaledPosition = ( e.getX() / scaleFactor ) - start;
final double position = Math.min( width, Math.max( (double) 0, scaledPosition ) );
final GuideLine newGuideLine = new GuideLine( position, dragged.isActive() );
final UndoManager undo = getRenderContext().getUndo();
undo.addChange( Messages.getString( "LinealComponent.ChangeGuideUndoName" ),
new UpdateHorizontalGuidelineUndoEntry( draggedGuideLineIndex, newGuideLine, dragged ) );
linealModel.updateGuideLine( draggedGuideLineIndex, newGuideLine );
}
public void mouseMoved( final MouseEvent e ) {
updateGuidelineHighlight( e );
}
public void mouseClicked( final MouseEvent e ) {
final PageDefinition pageDefinition = getPageDefinition();
if ( pageDefinition == null ) {
return;
}
double start = 0;
if ( isShowLeftBorder() ) {
start = getLeftBorder();
}
final int activeGuideIndex = getActiveGuideIndex( e );
if ( activeGuideIndex != -1 ) {
return;
}
final LinealModel linealModel = getLinealModel();
final float scaleFactor = getZoomAsMicropoints();
final double width = pageDefinition.getWidth();
final double scaledPosition = ( e.getX() / scaleFactor ) - start;
final double position = Math.min( width, Math.max( (double) 0, scaledPosition ) );
final GuideLine guideLine = new GuideLine( position, e.getButton() == MouseEvent.BUTTON1 );
final UndoManager undo = getRenderContext().getUndo();
undo.addChange( Messages.getString( "LinealComponent.AddGuideUndoName" ),
new AddHorizontalGuidelinesUndoEntry( guideLine ) );
linealModel.addGuidLine( guideLine );
}
public void mousePressed( final MouseEvent e ) {
if ( e.getButton() == MouseEvent.BUTTON1 ) {
draggedGuideLineIndex = getActiveGuideIndex( e );
if ( draggedGuideLineIndex == -1 ) {
setActiveGuideLineIndex( -1 );
} else {
setActiveGuideLineIndex( draggedGuideLineIndex );
}
}
}
public void mouseReleased( final MouseEvent e ) {
setActiveGuideLineIndex( -1 );
}
}
private class LinealPropertiesAction extends AbstractAction {
private GuideLine guideLine;
private int index;
private LinealPropertiesAction( final GuideLine guideLine, final int index ) {
super( Messages.getString( "LinealComponent.Properties" ) );
this.guideLine = guideLine;
this.index = index;
}
public void actionPerformed( final ActionEvent e ) {
final LinealModel linealModel = getLinealModel();
if ( linealModel == null ) {
return;
}
final GuideLineDialog spinnerDialog;
final Component parent = HorizontalLinealComponent.this;
final Window window = LibSwingUtil.getWindowAncestor( parent );
if ( window instanceof JDialog ) {
spinnerDialog = new GuideLineDialog( (JDialog) window );
} else if ( window instanceof JFrame ) {
spinnerDialog = new GuideLineDialog( (JFrame) window );
} else {
spinnerDialog = new GuideLineDialog();
}
spinnerDialog.setUnit( WorkspaceSettings.getInstance().getUnit() );
spinnerDialog.setPosition( guideLine.getPosition() );
if ( spinnerDialog.showDialog() ) {
final GuideLine newGuideLine = new GuideLine( spinnerDialog.getPosition(), guideLine.isActive() );
linealModel.updateGuideLine( index, newGuideLine );
this.guideLine = newGuideLine;
}
}
}
private class DeactivateGuidelineAction extends AbstractAction {
private final GuideLine guideLine;
private int index;
private DeactivateGuidelineAction( final GuideLine guideLine, final int index ) {
super( Messages.getString( "LinealComponent.Deactivate" ) );
this.guideLine = guideLine;
this.index = index;
}
public void actionPerformed( final ActionEvent e ) {
final GuideLine newGuideLine = new GuideLine( guideLine.getPosition(), false );
final LinealModel linealModel = getLinealModel();
final UndoManager undo = getRenderContext().getUndo();
undo.addChange( Messages.getString( "LinealComponent.DeactivateGuideUndoEntry" ),
new UpdateHorizontalGuidelineUndoEntry( index, newGuideLine, guideLine ) );
linealModel.updateGuideLine( index, newGuideLine );
}
}
private class ActivateGuidelineAction extends AbstractAction {
private final GuideLine oldGuideLine;
private int index;
private ActivateGuidelineAction( final GuideLine guideLine, final int index ) {
super( Messages.getString( "LinealComponent.Activate" ) );
this.oldGuideLine = guideLine;
this.index = index;
}
public void actionPerformed( final ActionEvent e ) {
final GuideLine newGuideLine = new GuideLine( oldGuideLine.getPosition(), true );
final UndoManager undo = getRenderContext().getUndo();
final LinealModel linealModel = getLinealModel();
undo.addChange( Messages.getString( "LinealComponent.ActivateGuideUndoEntry" ),
new UpdateHorizontalGuidelineUndoEntry( index, newGuideLine, oldGuideLine ) );
linealModel.updateGuideLine( index, newGuideLine );
}
}
private class DeleteGuidelineAction extends AbstractAction {
private final GuideLine guideLine;
private DeleteGuidelineAction( final GuideLine guideLine ) {
super( Messages.getString( "LinealComponent.Delete" ) );
this.guideLine = guideLine;
}
public void actionPerformed( final ActionEvent e ) {
final LinealModel linealModel = getLinealModel();
final UndoManager undo = getRenderContext().getUndo();
undo.addChange( Messages.getString( "LinealComponent.DeleteGuideUndoName" ),
new RemoveHorizontalGuidelineUndoEntry( guideLine ) );
linealModel.removeGuideLine( guideLine );
}
}
private class GuideLinePopupHandler extends MouseAdapter {
public void mouseEntered( final MouseEvent e ) {
updateGuidelineHighlight( e );
}
public void mouseExited( final MouseEvent e ) {
if ( getActiveGuidLine() != null ) {
setActiveGuideLineIndex( -1 );
repaint();
}
}
public void mouseClicked( final MouseEvent e ) {
updateGuidelineHighlight( e );
tryShowPopup( e );
}
public void mousePressed( final MouseEvent e ) {
updateGuidelineHighlight( e );
tryShowPopup( e );
}
public void mouseReleased( final MouseEvent e ) {
updateGuidelineHighlight( e );
tryShowPopup( e );
}
}
private class PageFormatUpdateHandler implements ReportModelListener {
private PageFormatUpdateHandler() {
}
public void nodeChanged( final ReportModelEvent event ) {
if ( event.getElement() == event.getReport() ) {
refresh();
}
}
}
private class UnitSettingsListener implements SettingsListener {
private UnitSettingsListener() {
}
public void settingsChanged() {
refresh();
}
}
public static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat( "0.0##" );
public static final DecimalFormat DECIMAL_FORMAT_NUMBERS_ONE_DIGIT = new DecimalFormat( "0.0" );
public static final DecimalFormat DECIMAL_FORMAT_NUMBERS_INTEGER = new DecimalFormat( "0" );
private LinealModel linealModel;
private int activeGuideLineIndex;
private boolean showLeftBorder;
private PageDefinition pageDefinition;
private ReportDocumentContext renderContext;
private HorizontalLinealComponent.LinealUpdateHandler linealModelListener;
public HorizontalLinealComponent( final ReportRenderContext renderContext, final boolean showLeftBorder ) {
if ( renderContext == null ) {
throw new NullPointerException();
}
this.renderContext = renderContext;
this.renderContext.getContextRoot().addReportModelListener( new PageFormatUpdateHandler() );
this.linealModelListener = new LinealUpdateHandler();
this.showLeftBorder = showLeftBorder;
final DragnDropHandler dndHandler = new DragnDropHandler();
addMouseListener( dndHandler );
addMouseMotionListener( dndHandler );
addMouseListener( new GuideLinePopupHandler() );
WorkspaceSettings.getInstance().addSettingsListener( new UnitSettingsListener() );
refresh();
}
public PageDefinition getPageDefinition() {
return pageDefinition;
}
protected void refresh() {
if ( linealModel != null ) {
this.linealModel.removeLinealModelListener( linealModelListener );
}
this.pageDefinition = renderContext.getContextRoot().getPageDefinition();
final AbstractReportDefinition abstractReportDefinition = this.renderContext.getReportDefinition();
this.linealModel = ModelUtility.getHorizontalLinealModel( abstractReportDefinition );
this.linealModel.addLinealModelListener( linealModelListener );
revalidate();
repaint();
}
protected void tryShowPopup( final MouseEvent me ) {
if ( !me.isPopupTrigger() ) {
return;
}
double start = 0;
if ( pageDefinition == null ) {
return;
}
if ( showLeftBorder ) {
start = getLeftBorder();
}
final GuideLine[] guideLines = linealModel.getGuideLines();
for ( int i = 0; i < guideLines.length; i++ ) {
final GuideLine guideLine = guideLines[ i ];
final int x = (int) ( ( guideLine.getPosition() + start ) * getZoomAsMicropoints() );
if ( x <= me.getX() + 2 && x >= me.getX() - 2 ) {
final JPopupMenu popupMenu = createPopupMenu( guideLine, i );
popupMenu.show( HorizontalLinealComponent.this, me.getX(), me.getY() );
break;
}
}
}
private JPopupMenu createPopupMenu( final GuideLine guideLine, final int index ) {
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add( new LinealPropertiesAction( guideLine, index ) );
if ( guideLine.isActive() ) {
popupMenu.add( new DeactivateGuidelineAction( guideLine, index ) );
} else {
popupMenu.add( new ActivateGuidelineAction( guideLine, index ) );
}
popupMenu.add( new DeleteGuidelineAction( guideLine ) );
return popupMenu;
}
protected int getActiveGuideIndex( final MouseEvent e ) {
if ( pageDefinition == null ) {
setToolTipText( null );
return -1;
}
final GuideLine[] lines = linealModel.getGuideLines();
final Unit unit = WorkspaceSettings.getInstance().getUnit();
for ( int i = 0; i < lines.length; i++ ) {
final GuideLine guideLine = lines[ i ];
double start = 0;
if ( showLeftBorder ) {
start = getLeftBorder();
}
final int x = (int) ( ( guideLine.getPosition() + start ) * getZoomAsMicropoints() );
if ( x <= e.getX() + 2 && x >= e.getX() - 2 ) {
final double unitValue = unit.convertFromPoints( guideLine.getPosition() );
setToolTipText( DECIMAL_FORMAT.format( unitValue ) );
return i;
}
}
setToolTipText( null );
return -1;
}
public Dimension getPreferredSize() {
if ( pageDefinition != null ) {
double end = pageDefinition.getWidth();
end += getLeftBorder();
end += getRightBorder();
return new Dimension( (int) ( end * getZoomAsMicropoints() ), 15 );
} else {
return new Dimension( 0, 15 );
}
}
protected void paintComponent( final Graphics g ) {
super.paintComponent( g );
if ( pageDefinition == null ) {
return;
}
final double start = getLeftBorder();
final float scaleFactor = getZoomAsMicropoints();
double end = pageDefinition.getWidth();
end += getLeftBorder();
g.setColor( Color.WHITE );
g.fillRect( 0, 0, (int) ( ( end - start ) * scaleFactor ), getHeight() );
g.setColor( Color.LIGHT_GRAY );
g.drawLine( 0, 0, 0, getHeight() - 1 );
g.drawLine( 0, getHeight() - 1, (int) ( ( end - start ) * scaleFactor ), getHeight() - 1 );
final ImageIcon rightBorder = CanvasImageLoader.getInstance().getRightShadowImage();
g.drawImage( rightBorder.getImage(), (int) ( ( end - start ) * scaleFactor ), 0, rightBorder.getIconWidth(),
getHeight(), null );
drawGuideLines( g );
drawDots( g, start, end );
drawNumbers( g, start, end );
}
protected double getLeftBorder() {
if ( pageDefinition == null ) {
return 0;
}
if ( isShowLeftBorder() == false ) {
return 0;
}
final PageFormat pageFormat = pageDefinition.getPageFormat( 0 );
final PageFormatFactory pageFormatFactory = PageFormatFactory.getInstance();
return pageFormatFactory.getLeftBorder( pageFormat.getPaper() );
}
protected double getRightBorder() {
if ( pageDefinition == null ) {
return 0;
}
final PageFormat pageFormat = pageDefinition.getPageFormat( 0 );
final PageFormatFactory pageFormatFactory = PageFormatFactory.getInstance();
return pageFormatFactory.getRightBorder( pageFormat.getPaper() );
}
private void drawGuideLines( final Graphics g ) {
final GuideLine[] guideLines = linealModel.getGuideLines();
double startOffset = 0;
if ( showLeftBorder ) {
startOffset = getLeftBorder();
}
final Color guideColor = WorkspaceSettings.getInstance().getGuideColor();
final Color guideFill = ColorUtility.convertToGray( guideColor, 0.3f );
final Color guideHighlight = ColorUtility.convertToDarker( guideFill );
final Color disabledGuideColor = ColorUtility.convertToDarker( ColorUtility.convertToGray( guideColor, 0 ) );
final Color disabledGuideFill = ColorUtility.convertToDarker( ColorUtility.convertToGray( guideFill, 0 ) );
final Color disabledHighlightGuide =
ColorUtility.convertToDarker( ColorUtility.convertToGray( guideHighlight, 0 ) );
final float scaleFactor = getZoomAsMicropoints();
final int so = (int) ( startOffset * scaleFactor );
for ( final GuideLine guideLine : guideLines ) {
final int x = (int) ( guideLine.getPosition() * scaleFactor ) + so;
if ( guideLine.isActive() ) {
g.setColor( guideFill );
} else {
g.setColor( disabledGuideFill );
}
g.fillRect( x - 2, 1, 4, 13 );
if ( guideLine.isActive() ) {
g.setColor( guideColor );
} else {
g.setColor( disabledGuideColor );
}
g.drawRect( x - 2, 0, 4, 14 );
}
g.setColor( Color.BLUE );
final GuideLine highlightGuideLine = getActiveGuidLine();
if ( highlightGuideLine != null ) {
final int x = (int) ( highlightGuideLine.getPosition() * scaleFactor ) + so;
if ( highlightGuideLine.isActive() ) {
g.setColor( guideFill );
} else {
g.setColor( disabledGuideFill );
}
g.fillRect( x - 2, 1, 4, 13 );
if ( highlightGuideLine.isActive() ) {
g.setColor( guideHighlight );
} else {
g.setColor( disabledHighlightGuide );
}
g.drawRect( x - 2, 0, 4, 14 );
}
}
private void drawDots( final Graphics g, final double start, final double end ) {
g.setColor( Color.GRAY );
final Unit unit = WorkspaceSettings.getInstance().getUnit();
final float scaleFactor = getZoomAsMicropoints();
final double factorForUnitAndScale = unit.getTickSize( (double) scaleFactor );
final double increment = unit.getDotsPerUnit() * factorForUnitAndScale;
for ( double i = start + increment / 2; i < end; i += increment ) {
final int x = (int) ( i * scaleFactor );
g.drawLine( x, 7, x, 9 );
}
}
private void drawNumbers( final Graphics g, final double start, final double end ) {
final float scaleFactor = getZoomAsMicropoints();
final Unit unit = WorkspaceSettings.getInstance().getUnit();
final double factorForUnitAndScale = unit.getTickSize( (double) scaleFactor );
final double increment = unit.getDotsPerUnit() * factorForUnitAndScale;
DecimalFormat df = DECIMAL_FORMAT_NUMBERS_INTEGER;
if ( factorForUnitAndScale < 1 ) {
df = DECIMAL_FORMAT_NUMBERS_ONE_DIGIT;
}
g.setColor( Color.BLACK );
double number = 0;
for ( double i = start; i < end - increment / 2; i += increment ) {
final int x = (int) ( i * scaleFactor );
if ( number > 0 ) {
final String s = df.format( number );
final Rectangle2D sb = g.getFontMetrics().getStringBounds( s, g );
g.drawString( s, (int) ( ( sb.getX() - sb.getWidth() / 2 ) + x ), (int) -sb.getY() );
}
number += factorForUnitAndScale;
}
}
public LinealModel getLinealModel() {
return linealModel;
}
public float getZoomAsMicropoints() {
return renderContext.getZoomModel().getZoomAsPercentage();
}
protected void updateGuidelineHighlight( final MouseEvent e ) {
final int agIndex = getActiveGuideIndex( e );
final int oldIndex = this.activeGuideLineIndex;
if ( agIndex == -1 ) {
activeGuideLineIndex = -1;
} else {
activeGuideLineIndex = agIndex;
}
if ( activeGuideLineIndex != oldIndex ) {
repaint();
}
}
protected GuideLine getActiveGuidLine() {
final int lineCount = linealModel.getGuideLineCount();
if ( activeGuideLineIndex != -1 && activeGuideLineIndex < lineCount ) {
return linealModel.getGuideLine( activeGuideLineIndex );
}
return null;
}
protected int getActiveGuideLineIndex() {
return activeGuideLineIndex;
}
protected void setActiveGuideLineIndex( final int activeGuideLineIndex ) {
this.activeGuideLineIndex = activeGuideLineIndex;
}
protected ReportDocumentContext getRenderContext() {
return renderContext;
}
protected boolean isShowLeftBorder() {
return showLeftBorder;
}
public void setShowLeftBorder( final boolean showLeftBorder ) {
this.showLeftBorder = showLeftBorder;
refresh();
}
}