Package org.pentaho.reporting.designer.core.editor.report

Source Code of org.pentaho.reporting.designer.core.editor.report.AbstractRenderComponent$AsyncChangeNotifier

/*!
* 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;

import java.applet.Applet;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Window;
import java.awt.dnd.DropTarget;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.ImageObserver;
import java.awt.print.PageFormat;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.designer.core.Messages;
import org.pentaho.reporting.designer.core.ReportDesignerBoot;
import org.pentaho.reporting.designer.core.ReportDesignerContext;
import org.pentaho.reporting.designer.core.editor.ContextMenuUtility;
import org.pentaho.reporting.designer.core.editor.ReportDocumentContext;
import org.pentaho.reporting.designer.core.editor.ZoomModel;
import org.pentaho.reporting.designer.core.editor.ZoomModelListener;
import org.pentaho.reporting.designer.core.editor.report.drag.CompoundDragOperation;
import org.pentaho.reporting.designer.core.editor.report.drag.MouseDragOperation;
import org.pentaho.reporting.designer.core.editor.report.drag.MoveDragOperation;
import org.pentaho.reporting.designer.core.editor.report.drag.ResizeBottomDragOperation;
import org.pentaho.reporting.designer.core.editor.report.drag.ResizeLeftDragOperation;
import org.pentaho.reporting.designer.core.editor.report.drag.ResizeRightDragOperation;
import org.pentaho.reporting.designer.core.editor.report.drag.ResizeTopDragOperation;
import org.pentaho.reporting.designer.core.editor.report.layouting.AbstractElementRenderer;
import org.pentaho.reporting.designer.core.editor.report.layouting.ElementRenderer;
import org.pentaho.reporting.designer.core.editor.report.snapping.EmptySnapModel;
import org.pentaho.reporting.designer.core.editor.report.snapping.FullSnapModel;
import org.pentaho.reporting.designer.core.editor.report.snapping.SnapToPositionModel;
import org.pentaho.reporting.designer.core.model.CachedLayoutData;
import org.pentaho.reporting.designer.core.model.HorizontalPositionsModel;
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.model.selection.DocumentContextSelectionModel;
import org.pentaho.reporting.designer.core.model.selection.ReportSelectionEvent;
import org.pentaho.reporting.designer.core.model.selection.ReportSelectionListener;
import org.pentaho.reporting.designer.core.settings.SettingsListener;
import org.pentaho.reporting.designer.core.settings.WorkspaceSettings;
import org.pentaho.reporting.designer.core.util.BreakPositionsList;
import org.pentaho.reporting.designer.core.util.FpsCalculator;
import org.pentaho.reporting.designer.core.util.exceptions.UncaughtExceptionsModel;
import org.pentaho.reporting.designer.core.util.undo.AttributeEditUndoEntry;
import org.pentaho.reporting.designer.core.util.undo.CompoundUndoEntry;
import org.pentaho.reporting.designer.core.util.undo.MassElementStyleUndoEntry;
import org.pentaho.reporting.designer.core.util.undo.MassElementStyleUndoEntryBuilder;
import org.pentaho.reporting.designer.core.util.undo.UndoEntry;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.Band;
import org.pentaho.reporting.engine.classic.core.Element;
import org.pentaho.reporting.engine.classic.core.PageDefinition;
import org.pentaho.reporting.engine.classic.core.ReportElement;
import org.pentaho.reporting.engine.classic.core.RootLevelBand;
import org.pentaho.reporting.engine.classic.core.Section;
import org.pentaho.reporting.engine.classic.core.designtime.AttributeExpressionChange;
import org.pentaho.reporting.engine.classic.core.designtime.DataFactoryChange;
import org.pentaho.reporting.engine.classic.core.designtime.StyleExpressionChange;
import org.pentaho.reporting.engine.classic.core.designtime.SubReportParameterChange;
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.style.ElementStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.ResolverStyleSheet;
import org.pentaho.reporting.engine.classic.core.style.resolver.SimpleStyleResolver;
import org.pentaho.reporting.engine.classic.core.util.PageFormatFactory;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictBounds;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.designtime.swing.ColorUtility;

/**
* Base class to handle rendering & dnd events of elements rendered inside sub-reports
*
* @author Thomas Morgner
*/
public abstract class AbstractRenderComponent extends JComponent
    implements ReportElementEditorContext, CellEditorListener
{
  protected class AsyncChangeNotifier implements Runnable
  {
    public void run()
    {
      final AbstractElementRenderer elementRenderer = (AbstractElementRenderer) getElementRenderer();
      elementRenderer.fireChangeEvent();
    }
  }

  protected class RequestFocusHandler extends MouseAdapter implements PropertyChangeListener
  {
    /**
     * Invoked when the mouse has been clicked on a component.
     */
    public void mouseClicked(final MouseEvent e)
    {
      requestFocusInWindow();
      setFocused(true);
      SwingUtilities.invokeLater(new AsyncChangeNotifier());
    }

    /**
     * This method gets called when a bound property is changed.
     *
     * @param evt A PropertyChangeEvent object describing the event source and the property that has changed.
     */

    public void propertyChange(final PropertyChangeEvent evt)
    {
      final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
      final boolean oldFocused = isFocused();
      final boolean focused = (owner == AbstractRenderComponent.this);
      if (oldFocused != focused)
      {
        setFocused(focused);
        repaintConditionally();
      }
      SwingUtilities.invokeLater(new AsyncChangeNotifier());
    }
  }

  protected class RootBandChangeHandler implements SettingsListener, ReportModelListener
  {
    protected RootBandChangeHandler()
    {
      updateGridSettings();
    }

    public void nodeChanged(final ReportModelEvent event)
    {
      final Object element = event.getElement();
      if (element instanceof ReportElement == false)
      {
        return;
      }

      final Object parameter = event.getParameter();
      if (parameter instanceof DataFactoryChange ||
          parameter instanceof SubReportParameterChange ||
          parameter instanceof AttributeExpressionChange ||
          parameter instanceof StyleExpressionChange)
      {
        // filter out known change events that cannot alter the layout.
        // this saves us a few CPU cycles here and there
        return;
      }

      getElementRenderer().invalidateLayout();
      revalidate();
      repaintConditionally();
    }

    public void settingsChanged()
    {
      updateGridSettings();

      // this is cheap, just repaint and we will be happy
      repaintConditionally();

    }
  }

  protected class MouseSelectionHandler extends MouseAdapter implements MouseMotionListener
  {
    private Point2D normalizedSelectionRectangleOrigin;
    private Point selectionRectangleOrigin;
    private Point selectionRectangleTarget;
    private boolean clearSelectionOnDrag;
    private HashSet<Element> newlySelectedElements;

    protected MouseSelectionHandler()
    {
      newlySelectedElements = new HashSet<Element>();
    }


    /**
     * Invoked when a mouse button has been released on a component.
     */
    public void mouseReleased(final MouseEvent e)
    {
      getDesignerContext().setSelectionWaiting(false);
      normalizedSelectionRectangleOrigin = null;
      selectionRectangleOrigin = null;
      selectionRectangleTarget = null;
      newlySelectedElements.clear();
      repaintConditionally();
    }

    /**
     * Invoked when a mouse button is pressed on a component and then dragged. <code>MOUSE_DRAGGED</code> events will
     * continue to be delivered to the component where the drag originated until the mouse button is released
     * (regardless of whether the mouse position is within the bounds of the component).
     * <p/>
     * Due to platform-dependent Drag&Drop implementations, <code>MOUSE_DRAGGED</code> events may not be delivered
     * during a native Drag&Drop operation.
     */
    public void mouseDragged(final MouseEvent e)
    {
      if (getDesignerContext().isSelectionWaiting() == false)
      {
        return;
      }

      // Check to see if this mouse handler should handle this mouse event (Mouse Operation Handler vs the Mouse Selection Handler)
      if (isMouseOperationInProgress())
      {
        return;
      }
      if (normalizedSelectionRectangleOrigin == null)
      {
        return;
      }

      final Point2D normalizedSelectionRectangleTarget = normalize(e.getPoint());
      normalizedSelectionRectangleTarget.setLocation(Math.max(0, normalizedSelectionRectangleTarget.getX()), Math.max(0, normalizedSelectionRectangleTarget
          .getY()));
      final ElementRenderer rendererRoot = getElementRenderer();
      final ReportDocumentContext renderContext = getRenderContext();

      if (clearSelectionOnDrag)
      {
        final DocumentContextSelectionModel selectionModel = renderContext.getSelectionModel();
        selectionModel.clearSelection();
        clearSelectionOnDrag = false;
      }

      selectionRectangleTarget = e.getPoint();

      // Calculate the bounding box for the selection
      final double y1 = Math.min(normalizedSelectionRectangleOrigin.getY(), normalizedSelectionRectangleTarget.getY());
      final double x1 = Math.min(normalizedSelectionRectangleOrigin.getX(), normalizedSelectionRectangleTarget.getX());
      final double y2 = Math.max(normalizedSelectionRectangleOrigin.getY(), normalizedSelectionRectangleTarget.getY());
      final double x2 = Math.max(normalizedSelectionRectangleOrigin.getX(), normalizedSelectionRectangleTarget.getX());

      final Element[] allNodes = rendererRoot.getElementsAt(x1, y1, x2 - x1, y2 - y1);
      final DocumentContextSelectionModel selectionModel = renderContext.getSelectionModel();

      // Convert between points to micro-points (1 point X 100K is a micro-point)
      final StrictBounds rect1 = StrictGeomUtility.createBounds(x1, y1, x2 - x1, y2 - y1);
      final StrictBounds rect2 = new StrictBounds();

      for (int i = allNodes.length - 1; i >= 0; i -= 1)
      {
        final Element element = allNodes[i];
        if (element instanceof RootLevelBand)
        {
          continue;
        }

        final CachedLayoutData data = ModelUtility.getCachedLayoutData(element);
        rect2.setRect(data.getX(), data.getY(), data.getWidth(), data.getHeight());

        // Checking if the bounding box intersects an element
        if (StrictBounds.intersects(rect1, rect2))
        {
          if (selectionModel.add(element))
          {
            newlySelectedElements.add(element);
          }
        }
      }

      // second step, check which previously added elements are no longer selected by the rectangle.
      for (Iterator<Element> visualReportElementIterator = newlySelectedElements.iterator(); visualReportElementIterator.hasNext(); )
      {
        final Element element = visualReportElementIterator.next();
        final CachedLayoutData data = ModelUtility.getCachedLayoutData(element);
        rect2.setRect(data.getX(), data.getY(), data.getWidth(), data.getHeight());
        if (StrictBounds.intersects(rect1, rect2) == false)
        {
          selectionModel.remove(element);
          visualReportElementIterator.remove();
        }

      }

      repaintConditionally();
    }

    public Point getSelectionRectangleOrigin()
    {
      return selectionRectangleOrigin;
    }

    public Point getSelectionRectangleTarget()
    {
      return selectionRectangleTarget;
    }

    /**
     * Invoked when the mouse cursor has been moved onto a component but no buttons have been pushed.
     */
    public void mouseMoved(final MouseEvent e)
    {
    }


    /**
     * Invoked when a mouse button has been pressed on a component.
     */
    public void mousePressed(final MouseEvent e)
    {
      if (isMouseOperationPossible())
      {
        return;
      }

      if (getDesignerContext().isSelectionWaiting() == false)
      {
        return;
      }

      newlySelectedElements.clear();
      normalizedSelectionRectangleOrigin = normalize(e.getPoint());
      normalizedSelectionRectangleOrigin.setLocation(Math.max(0,
          normalizedSelectionRectangleOrigin.getX()), Math.max(0, normalizedSelectionRectangleOrigin.getY()));

      selectionRectangleOrigin = e.getPoint();

      if (e.isShiftDown() == false)
      {
        clearSelectionOnDrag = true;
      }

      final ReportDocumentContext renderContext = getRenderContext();
      final ElementRenderer rendererRoot = getElementRenderer();

      final DocumentContextSelectionModel selectionModel = renderContext.getSelectionModel();
      final Element[] allNodes = rendererRoot.getElementsAt
          (normalizedSelectionRectangleOrigin.getX(), normalizedSelectionRectangleOrigin.getY());
      for (int i = allNodes.length - 1; i >= 0; i -= 1)
      {
        final Element element = allNodes[i];
        if (element instanceof RootLevelBand)
        {
          continue;
        }

        if (!e.isShiftDown())
        {
          if (!selectionModel.isSelected(element))
          {
            selectionModel.clearSelection();
            selectionModel.add(element);
            return;
          }
        }
      }
    }

    /**
     * Invoked when the mouse has been clicked on a component.
     */
    public void mouseClicked(final MouseEvent e)
    {
      final Point2D point = normalize(e.getPoint());
      if (point.getX() < 0 || point.getY() < 0)
      {
        return; // we do not handle that one ..
      }

      final DocumentContextSelectionModel selectionModel = getRenderContext().getSelectionModel();
      final ElementRenderer rendererRoot = getElementRenderer();

      // Sorted list of all elements that intersect the point we are seeking
      final Element[] allNodes = rendererRoot.getElementsAt(point.getX(), point.getY());
      for (int i = allNodes.length - 1; i >= 0; i -= 1)
      {
        // Check if element is null due to structural helper node like (SectionRenderBox)
        final Element element = allNodes[i];
        if (element instanceof RootLevelBand)
        {
          continue;
        }

        if (e.isShiftDown())
        {
          toggleSelection(selectionModel, element);

        }
        else
        {
          if (!selectionModel.isSelected(element))
          {
            selectionModel.clearSelection();
            selectionModel.add(element);
          }
        }

        return;
      }

      if (e.isShiftDown() == false)
      {
        selectionModel.clearSelection();
      }

      final Element element = rendererRoot.getElement();
      if (element instanceof RootLevelBand)
      {
        if (e.isShiftDown())
        {
          toggleSelection(selectionModel, element);

        }
        else
        {
          if (!selectionModel.isSelected(element))
          {
            selectionModel.clearSelection();
            selectionModel.add(element);
          }
        }
      }
    }

    private void toggleSelection(final DocumentContextSelectionModel selectionModel, final Element element)
    {
      // toggle selection ..
      if (selectionModel.isSelected(element))
      {
        selectionModel.remove(element);
      }
      else
      {
        selectionModel.add(element);
      }
    }
  }

  protected class SelectionModelListener implements ReportSelectionListener
  {
    protected SelectionModelListener()
    {
    }

    public void selectionAdded(final ReportSelectionEvent event)
    {
      final Object element = event.getElement();
      if (element instanceof Element == false)
      {
        return;
      }

      final Element velement = (Element) element;
      ReportElement parentSearch = velement;
      final Section rootBand = getElementRenderer().getElement();
      final ZoomModel zoomModel = getRenderContext().getZoomModel();
      while (parentSearch != null)
      {
        if (parentSearch == rootBand)
        {
          final SelectionOverlayInformation renderer = new SelectionOverlayInformation(velement);
          renderer.validate(zoomModel.getZoomAsPercentage());
          velement.setAttribute(ReportDesignerBoot.DESIGNER_NAMESPACE, ReportDesignerBoot.SELECTION_OVERLAY_INFORMATION, renderer, false);
          repaintConditionally();
          return;
        }
        parentSearch = parentSearch.getParentSection();
      }
    }

    public void selectionRemoved(final ReportSelectionEvent event)
    {
      final Object element = event.getElement();
      if (element instanceof Element)
      {
        final Element e = (Element) element;
        e.setAttribute(ReportDesignerBoot.DESIGNER_NAMESPACE, ReportDesignerBoot.SELECTION_OVERLAY_INFORMATION, null, false);
      }
      repaintConditionally();
    }

    public void leadSelectionChanged(final ReportSelectionEvent event)
    {
      if (event.getModel().getSelectionCount() != 1)
      {
        return;
      }
      final Object raw = event.getElement();
      if (raw instanceof Element == false)
      {
        return;
      }

      Element e = (Element) raw;
      while (e != null && e instanceof RootLevelBand == false)
      {
        e = e.getParent();
      }

      if (e == getRootBand())
      {
        setFocused(true);
        repaintConditionally();
        SwingUtilities.invokeLater(new AsyncChangeNotifier());
      }
      else
      {
        setFocused(false);
        repaintConditionally();
        SwingUtilities.invokeLater(new AsyncChangeNotifier());
      }
    }
  }

  protected static final class RepaintHandler implements LinealModelListener, ZoomModelListener, ChangeListener
  {
    private AbstractRenderComponent component;

    private RepaintHandler(final AbstractRenderComponent component)
    {
      this.component = component;
    }

    public void stateChanged(final ChangeEvent e)
    {
      // this is cheap, just repaint and we will be happy
      component.revalidate();
      component.repaintConditionally();
    }

    public void modelChanged(final LinealModelEvent event)
    {
      component.revalidate();
      component.repaintConditionally();
    }

    public void zoomFactorChanged()
    {
      component.revalidate();
      component.repaintConditionally();
      component.stopCellEditing();
    }

  }

  protected class SettingsUpdateHandler implements SettingsListener
  {
    protected SettingsUpdateHandler()
    {
    }

    public void settingsChanged()
    {
      updateGridSettings();

      revalidate();
      repaintConditionally();
    }
  }

  protected class KeyboardElementMoveHandler extends KeyAdapter
  {

    public KeyboardElementMoveHandler()
    {
    }

    public void keyReleased(final KeyEvent e)
    {
      if (e.isShiftDown() == false && getDesignerContext().isSelectionWaiting())
      {
        if (currentIndicator == SelectionOverlayInformation.InRangeIndicator.NOT_IN_RANGE)
        {
          setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
        else if (currentIndicator == SelectionOverlayInformation.InRangeIndicator.MOVE)
        {
          setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
        }
        getDesignerContext().setSelectionWaiting(false);
      }
    }

    public void keyPressed(final KeyEvent keyEvent)
    {
      // move all selected components 1px
      List<Element> selectedElements = getRenderContext().getSelectionModel().getSelectedElementsOfType(Element.class);
      if (selectedElements.isEmpty())
      {
        return;
      }

      // if any element's X or Y is == 0, then do not move anything
      // PRD-1442
      final int keyCode = keyEvent.getKeyCode();
      if (keyCode != KeyEvent.VK_UP && keyCode != KeyEvent.VK_DOWN &&
          keyCode != KeyEvent.VK_LEFT && keyCode != KeyEvent.VK_RIGHT)
      {
        return;
      }

      if (keyEvent.isShiftDown() || keyEvent.isAltDown() || keyEvent.isControlDown())
      {
        return;
      }

      keyEvent.consume();

      for (final Element element: selectedElements)
      {
        if (element instanceof RootLevelBand)
        {
          continue;
        }
        final double elementX = element.getStyle().getDoubleStyleProperty(ElementStyleKeys.POS_X, 0);
        final double elementY = element.getStyle().getDoubleStyleProperty(ElementStyleKeys.POS_Y, 0);
        // check if we can't move, one of the elements in the group is already at the minimum position
        if (keyCode == KeyEvent.VK_UP && elementY == 0)
        {
          return;
        }
        else if (keyCode == KeyEvent.VK_LEFT && elementX == 0)
        {
          return;
        }
      }

      final MassElementStyleUndoEntryBuilder builder = new MassElementStyleUndoEntryBuilder(selectedElements);
      final MoveDragOperation mop = new MoveDragOperation(selectedElements, new Point(), EmptySnapModel.INSTANCE, EmptySnapModel.INSTANCE);

      if (keyCode == KeyEvent.VK_UP)
      {
        mop.update(new Point(0, -1), 1);
      }
      else if (keyCode == KeyEvent.VK_DOWN)
      {
        mop.update(new Point(0, 1), 1);
      }
      else if (keyCode == KeyEvent.VK_LEFT)
      {
        mop.update(new Point(-1, 0), 1);
      }
      else
      {
        mop.update(new Point(1, 0), 1);
      }
      final MassElementStyleUndoEntry massElementStyleUndoEntry = builder.finish();
      getRenderContext().getUndo().addChange(Messages.getString("AbstractRenderComponent.MoveUndoName"), massElementStyleUndoEntry);
      mop.finish();
    }
  }

  /**
   * When you double-click on an element, you can edit it inside
   * the canvas editor area.
   */
  protected class MouseEditorActionHandler extends MouseAdapter
  {
    private MouseEditorActionHandler()
    {
    }

    /**
     * Invoked when the mouse has been clicked on a component.
     */
    public void mouseClicked(final MouseEvent e)
    {
      if (stopCellEditing() == false)
      {
        return;
      }

      if (e.isPopupTrigger())
      {
        final Point2D point = normalize(e.getPoint());
        if (point.getX() < 0 || point.getY() < 0)
        {
          return; // we do not handle that one ..
        }

        showElementPopup(e, point);
        return;
      }

      // ReportElementInlineEditor ...
      if (e.getClickCount() >= 2 && (e.getButton() == MouseEvent.BUTTON1))
      {
        final Point2D point = normalize(e.getPoint());
        if (point.getX() < 0 || point.getY() < 0)
        {
          return; // we do not handle that one ..
        }

        final Element element = getElementForLocation(point, true);
        if (element == null)
        {
          return;
        }

        final String typeName = element.getElementTypeName();
        ReportElementEditor elementEditor = ReportElementEditorRegistry.getInstance().getPlugin(typeName);
        if (elementEditor == null)
        {
          elementEditor = ReportElementEditorRegistry.getInstance().getPlugin(null);
          if (elementEditor == null)
          {
            return;
          }
        }

        final ReportElementInlineEditor inlineEditor = elementEditor.createInlineEditor();
        if (inlineEditor == null)
        {
          return;
        }

        installEditor(inlineEditor, element);
      }
    }

    /**
     * Invoked when a mouse button has been released on a component.
     */
    public void mouseReleased(final MouseEvent e)
    {
      if (stopCellEditing() == false)
      {
        return;
      }

      if (e.isPopupTrigger())
      {
        final Point2D point = normalize(e.getPoint());
        if (point.getX() < 0 || point.getY() < 0)
        {
          return; // we do not handle that one ..
        }

        showElementPopup(e, point);
      }
    }

    /**
     * Invoked when a mouse button has been pressed on a component.
     */
    public void mousePressed(final MouseEvent e)
    {
      if (stopCellEditing() == false)
      {
        return;
      }

      if (e.isPopupTrigger())
      {
        final Point2D point = normalize(e.getPoint());
        if (point.getX() < 0 || point.getY() < 0)
        {
          return; // we do not handle that one ..
        }

        showElementPopup(e, point);
      }
    }

    protected void showElementPopup(final MouseEvent e, final Point2D normalizedPoint)
    {
      Element element = getElementForLocation(normalizedPoint, true);
      if (element == null)
      {
        element = (Element) findRootBandForPosition(normalizedPoint);
      }
      if (element == null)
      {
        return;
      }

      final JPopupMenu pop = ContextMenuUtility.getMenu(getDesignerContext(), element);
      if (pop == null)
      {
        return;
      }
      pop.show(AbstractRenderComponent.this, e.getX(), e.getY());
    }
  }

  private class MouseOperationHandler extends MouseAdapter implements MouseMotionListener
  {
    private SelectionOverlayInformation currentRenderer;
    private Point2D lastPoint;

    private MouseOperationHandler()
    {
    }

    /**
     * Invoked when a mouse button is pressed on a component and then dragged. <code>MOUSE_DRAGGED</code> events will continue to be delivered to the component
     * where the drag originated until the mouse button is released (regardless of whether the mouse position is within the bounds of the component).
     * <p/>
     * Due to platform-dependent Drag&Drop implementations, <code>MOUSE_DRAGGED</code> events may not be delivered during a native Drag&Drop operation.
     */
    public void mouseDragged(final MouseEvent e)
    {
      if (lastPoint == null)
      {
        return;
      }

      final Point2D normalizedPoint = normalize(e.getPoint());
      updateElements(normalizedPoint, e.isAltDown(), e.isControlDown());
    }

    /**
     * Invoked when the mouse cursor has been moved onto a component but no buttons have been pushed.
     */
    public void mouseMoved(final MouseEvent e)
    {
      final Point point1 = e.getPoint();
      updateCursor(point1);
    }

    private void updateCursor(final Point rawPoint)
    {
      final boolean selectionMode = getDesignerContext().isSelectionWaiting();
      if (selectionMode)
      {
        setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        currentIndicator = SelectionOverlayInformation.InRangeIndicator.NOT_IN_RANGE;
        return;
      }

      currentIndicator = SelectionOverlayInformation.InRangeIndicator.NOT_IN_RANGE;
      final Point2D normalizedPoint = normalize(rawPoint);
      if (currentRenderer != null)
      {
        currentIndicator = currentRenderer.getMouseInRangeIndicator(normalizedPoint);
        if (currentIndicator == SelectionOverlayInformation.InRangeIndicator.NOT_IN_RANGE)
        {
          currentRenderer = null;
          currentIndicator = SelectionOverlayInformation.InRangeIndicator.NOT_IN_RANGE;
        }
        if (currentIndicator == SelectionOverlayInformation.InRangeIndicator.MOVE)
        {
          currentRenderer = null;
          currentIndicator = SelectionOverlayInformation.InRangeIndicator.NOT_IN_RANGE;
        }
      }

      for (final Element e : getRenderContext().getSelectionModel().getSelectedElementsOfType(Element.class))
      {
        final Object o = e.getAttribute(ReportDesignerBoot.DESIGNER_NAMESPACE, "selection-overlay-information"); // NON-NLS
        if (o instanceof SelectionOverlayInformation == false)
        {
          continue;
        }

        if (isLocalElement(e) == false)
        {
          continue;
        }

        final SelectionOverlayInformation renderer = (SelectionOverlayInformation) o;
        final SelectionOverlayInformation.InRangeIndicator indicator = renderer.getMouseInRangeIndicator(normalizedPoint);
        if (indicator == SelectionOverlayInformation.InRangeIndicator.NOT_IN_RANGE)
        {
          continue;
        }

        // a resize-handle wins over a ordinary move selection
        if (currentIndicator == SelectionOverlayInformation.InRangeIndicator.MOVE
            || currentIndicator == SelectionOverlayInformation.InRangeIndicator.NOT_IN_RANGE)
        {
          currentIndicator = indicator;
          currentRenderer = renderer;
        }
        else
        {
          break;
        }
      }

      updateCursorForIndicator();
    }

    /**
     * Invoked when a mouse button has been pressed on a component.
     */
    public void mousePressed(final MouseEvent e)
    {
      lastPoint = normalize(e.getPoint());
      updateCursor(e.getPoint());
      initializeDragOperation(lastPoint, currentIndicator);
    }

    /**
     * Invoked when a mouse button has been released on a component.
     */
    public void mouseReleased(final MouseEvent e)
    {
      if (lastPoint == null)
      {
        return;
      }

      if (lastPoint.equals(normalize(e.getPoint())) == false)
      {
        // only fire a drag operation if we moved the mouse
        finishDragOperation();
      }
    }

    /**
     * Invoked when the mouse enters a component.
     */
    public void mouseEntered(final MouseEvent e)
    {
      updateCursor(e.getPoint());
    }

    /**
     * Invoked when the mouse has been clicked on a component.
     */
    public void mouseClicked(final MouseEvent e)
    {
      updateCursor(e.getPoint());
    }
  }

  protected class CellEditorRemover implements PropertyChangeListener
  {
    private KeyboardFocusManager focusManager;

    public CellEditorRemover(final KeyboardFocusManager fm)
    {
      this.focusManager = fm;
    }

    public void propertyChange(final PropertyChangeEvent ev)
    {
      if (!isEditing() || isTerminateEditOnFocusLost() == false)
      {
        return;
      }

      Component c = focusManager.getPermanentFocusOwner();
      while (c != null)
      {
        if (c == AbstractRenderComponent.this)
        {
          // focus remains inside the table
          return;
        }
        else if ((c instanceof Window) || (c instanceof Applet && c.getParent() == null))
        {
          if (c == SwingUtilities.getRoot(AbstractRenderComponent.this))
          {
            if (!getCellEditor().stopCellEditing())
            {
              getCellEditor().cancelCellEditing();
            }
          }
          break;
        }
        c = c.getParent();
      }
    }
  }

  private class DragAbortReportModelListener implements ReportModelListener
  {
    private DragAbortReportModelListener()
    {
    }

    public void nodeChanged(final ReportModelEvent event)
    {
      if (event.isNodeAddedEvent() || event.isNodeDeleteEvent())
      {
        finishDragOperation();
      }
    }
  }

  private class SelectionStateHandler implements PropertyChangeListener
  {
    /**
     * This method gets called when a bound property is changed.
     *
     * @param evt A PropertyChangeEvent object describing the event source
     *            and the property that has changed.
     */
    public void propertyChange(final PropertyChangeEvent evt)
    {
      if (getDesignerContext().isSelectionWaiting())
      {
        setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
      }
      else
      {
        updateCursorForIndicator();
      }
    }
  }

  protected class SelectionRectangleOverlayRenderer implements OverlayRenderer
  {
    public SelectionRectangleOverlayRenderer()
    {
    }

    public void validate(final ReportDocumentContext context, final double zoomFactor, final Point2D sectionOffset)
    {
    }

    public void draw(final Graphics2D graphics, final Rectangle2D bounds, final ImageObserver obs)
    {
      paintSelectionRectangle(graphics);
    }
  }

  // 50 fps max
  private static final long REPAINT_INTERVAL = 1000 / 50;
  private static final Log logger = LogFactory.getLog(AbstractRenderComponent.class);
  private static final BasicStroke SELECTION_STROKE = new BasicStroke(0.5f);

  private CellEditorRemover editorRemover;
  private RepaintHandler repaintHandler;
  private SettingsUpdateHandler settingsUpdateHandler;
  private ReportDesignerContext designerContext;
  private ReportDocumentContext renderContext;
  private boolean showLeftBorder;
  private boolean showTopBorder;
  private double gridSize;
  private int gridDivisions;
  private boolean terminateEditOnFocusLost;
  private Component editorComponent;
  private ReportElementInlineEditor inlineEditor;
  private MouseDragOperation operation;
  private MassElementStyleUndoEntryBuilder undoEntryBuilder;
  private FullSnapModel horizontalSnapModel;
  private FullSnapModel verticalSnapModel;
  private LinealModel verticalLinealModel;
  private LinealModel horizontalLinealModel;
  private HorizontalPositionsModel horizontalPositionsModel;
  private boolean focused;
  private SelectionOverlayInformation.InRangeIndicator currentIndicator;
  private SelectionStateHandler selectionStateHandler;
  private ArrayList<Object> oldValues = new ArrayList<Object>();
  private MouseSelectionHandler selectionHandler;
  private RequestFocusHandler focusHandler;
  private SelectionModelListener selectionModelListener;
  private RootBandChangeHandler changeHandler;
  private SimpleStyleResolver styleResolver;
  private ResolverStyleSheet resolvedStyle;
  private FpsCalculator fpsCalculator;
  private boolean paintingImmediately = false;


  protected AbstractRenderComponent(final ReportDesignerContext designerContext,
                                    final ReportDocumentContext renderContext)
  {
    if (renderContext == null)
    {
      throw new NullPointerException();
    }
    if (designerContext == null)
    {
      throw new NullPointerException();
    }

    setDoubleBuffered(true);
    setOpaque(false);
    setFocusable(true);
    setFocusCycleRoot(true);
    setFocusTraversalKeysEnabled(false);
    setLayout(null);

    this.fpsCalculator = new FpsCalculator();
    this.showLeftBorder = true;
    this.showTopBorder = false;
    this.repaintHandler = new RepaintHandler(this);
    this.designerContext = designerContext;
    this.renderContext = renderContext;
    this.settingsUpdateHandler = new SettingsUpdateHandler();
    this.horizontalSnapModel = new FullSnapModel();
    this.verticalSnapModel = new FullSnapModel();
    this.terminateEditOnFocusLost = true;

    gridSize = WorkspaceSettings.getInstance().getGridSize();
    gridDivisions = WorkspaceSettings.getInstance().getGridDivisions();

    WorkspaceSettings.getInstance().addSettingsListener(settingsUpdateHandler);

    new DropTarget(this, new BandDndHandler(this));

    renderContext.getZoomModel().addZoomModelListener(repaintHandler);
    renderContext.getReportDefinition().addReportModelListener(new DragAbortReportModelListener());

    addMouseListener(new MouseEditorActionHandler());
    addKeyListener(new KeyboardElementMoveHandler());

    selectionStateHandler = new SelectionStateHandler();
    designerContext.addPropertyChangeListener(ReportDesignerContext.SELECTION_WAITING_PROPERTY, selectionStateHandler);

    focusHandler = new RequestFocusHandler();
    addMouseListener(focusHandler);
    KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("permanentFocusOwner", focusHandler); // NON-NLS

    this.selectionHandler = new MouseSelectionHandler();
    addMouseListener(selectionHandler);
    addMouseMotionListener(selectionHandler);

    this.changeHandler = new RootBandChangeHandler();
    this.selectionModelListener = new SelectionModelListener();

    renderContext.getSelectionModel().addReportSelectionListener(selectionModelListener);

    new DropTarget(this, new BandDndHandler(this));

    installMouseOperationHandler();

    styleResolver = new SimpleStyleResolver(true);
    resolvedStyle = new ResolverStyleSheet();

    renderContext.getReportDefinition().addReportModelListener(changeHandler);
  }

  /**
   * Abstract method to retrieve the element renderer
   *
   * @return ElementRenderer
   */
  protected abstract ElementRenderer getElementRenderer();

  /**
   * Abstract method to return the default element
   *
   * @return Element
   */
  public abstract Element getDefaultElement();

  public Band getRootBand()
  {
    return (Band) getElementRenderer().getElement();
  }

  public boolean isTerminateEditOnFocusLost()
  {
    return terminateEditOnFocusLost;
  }

  public void setTerminateEditOnFocusLost(final boolean terminateEditOnFocusLost)
  {
    this.terminateEditOnFocusLost = terminateEditOnFocusLost;
  }

  protected abstract boolean isLocalElement(ReportElement e);

  protected void installMouseOperationHandler()
  {
    // must be added *after* the selection handler
    final MouseOperationHandler operationHandler = new MouseOperationHandler();
    addMouseListener(operationHandler);
    addMouseMotionListener(operationHandler);
  }

  protected boolean isFocused()
  {
    return focused;
  }

  protected void setFocused(final boolean focused)
  {
    this.focused = focused;
  }

  public boolean isShowLeftBorder()
  {
    return showLeftBorder;
  }

  public void setShowLeftBorder(final boolean showLeftBorder)
  {
    this.showLeftBorder = showLeftBorder;
  }

  public boolean isShowTopBorder()
  {
    return showTopBorder;
  }

  public void setShowTopBorder(final boolean showTopBorder)
  {
    this.showTopBorder = showTopBorder;
  }

  protected double getLeftBorder()
  {
    if (renderContext == null)
    {
      return 0;
    }
    if (showLeftBorder == false)
    {
      return 0;
    }
    final PageDefinition pageDefinition = renderContext.getContextRoot().getPageDefinition();
    final PageFormat pageFormat = pageDefinition.getPageFormat(0);
    final PageFormatFactory pageFormatFactory = PageFormatFactory.getInstance();
    return pageFormatFactory.getLeftBorder(pageFormat.getPaper());
  }

  protected double getTopBorder()
  {
    if (renderContext == null)
    {
      return 0;
    }
    if (showTopBorder == false)
    {
      return 0;
    }
    final PageDefinition pageDefinition = renderContext.getContextRoot().getPageDefinition();
    final PageFormat pageFormat = pageDefinition.getPageFormat(0);
    final PageFormatFactory pageFormatFactory = PageFormatFactory.getInstance();
    return pageFormatFactory.getTopBorder(pageFormat.getPaper());
  }

  public Point2D normalize(final Point2D e)
  {
    final double topBorder = getTopBorder();
    final double leftBorder = getLeftBorder();

    final float scaleFactor = getRenderContext().getZoomModel().getZoomAsPercentage();
    final double x = (e.getX() / scaleFactor) - leftBorder;
    final double y = (e.getY() / scaleFactor) - topBorder;

    final Point2D o = getOffset();
    o.setLocation(x, y + o.getY());
    return o;
  }

  protected Point2D getOffset()
  {
    final StrictBounds bounds = getElementRenderer().getRootElementBounds();
    return new Point2D.Double(StrictGeomUtility.toExternalValue(bounds.getX()),
        StrictGeomUtility.toExternalValue(bounds.getY()));
  }

  public ReportDocumentContext getRenderContext()
  {
    return renderContext;
  }

  public ReportDesignerContext getDesignerContext()
  {
    return designerContext;
  }

  protected void paintComponent(final Graphics g)
  {
    if (fpsCalculator.isActive())
    {
      fpsCalculator.tick();
    }

    final Graphics2D g2 = (Graphics2D) g.create();

    g2.setColor(new Color(224, 224, 224));
    g2.fillRect(0, 0, getWidth(), getHeight());


    final int leftBorder = (int) getLeftBorder();
    final int topBorder = (int) getTopBorder();
    final float scaleFactor = getRenderContext().getZoomModel().getZoomAsPercentage();

    // draw the page area ..
    final PageDefinition pageDefinition = getRenderContext().getContextRoot().getPageDefinition();
    final Rectangle2D.Double area = new Rectangle2D.Double(0, 0, pageDefinition.getWidth() * scaleFactor, getHeight());
    g2.translate(leftBorder * scaleFactor, topBorder * scaleFactor);
    g2.clip(area);
    g2.setColor(Color.WHITE);
    g2.fill(area);

    // draw the grid (unscaled, but translated)
    final Point2D offset = getOffset();
    if (offset.getX() != 0)
    {
      // The blackout area is for inline sub-reports and is the area where the subreport is not interested in
      // (so we can clip out).  The blackout area is only visible in the sub-report.
      final Rectangle2D.Double blackoutArea = new Rectangle2D.Double(0, 0, offset.getX() * scaleFactor, getHeight());
      g2.setColor(Color.LIGHT_GRAY);
      g2.fill(blackoutArea);
    }
    paintGrid(g2);
    paintElementAlignment(g2);
    g2.dispose();

    final Graphics2D logicalPageAreaG2 = (Graphics2D) g.create();
    // draw the renderable content ...
    logicalPageAreaG2.translate(leftBorder * scaleFactor, topBorder * scaleFactor);
    logicalPageAreaG2.clip(area);
    logicalPageAreaG2.scale(scaleFactor, scaleFactor);

    try
    {
      final ElementRenderer rendererRoot = getElementRenderer();
      if (rendererRoot != null)
      {
        if (rendererRoot.draw(logicalPageAreaG2) == false)
        {
          rendererRoot.handleError(designerContext, renderContext);

          logicalPageAreaG2.scale(1f / scaleFactor, 1f / scaleFactor);
          logicalPageAreaG2.setPaint(Color.WHITE);
          logicalPageAreaG2.fill(area);
        }
      }
    }
    catch (Exception e)
    {
      // ignore for now..
      UncaughtExceptionsModel.getInstance().addException(e);
    }

    logicalPageAreaG2.dispose();

    final OverlayRenderer[] renderers = new OverlayRenderer[4];
    renderers[0] = new OverlappingElementOverlayRenderer(getDefaultElement()); // displays the red border for warning
    renderers[1] = new SelectionOverlayRenderer(getDefaultElement());
    renderers[2] = new GuidelineOverlayRenderer(horizontalLinealModel, verticalLinealModel);
    renderers[3] = new SelectionRectangleOverlayRenderer();   // blue box when you shift and drag the region to select multiple elements

    for (int i = 0; i < renderers.length; i++)
    {
      final OverlayRenderer renderer = renderers[i];
      final Graphics2D selectionG2 = (Graphics2D) g.create();

      renderer.validate(getRenderContext(), scaleFactor, offset);
      renderer.draw(selectionG2, new Rectangle2D.Double(getLeftBorder(), getTopBorder(), getWidth(), getHeight()), this);
      selectionG2.dispose();
    }
  }

  protected void paintSelectionRectangle(final Graphics2D g2)
  {
    final Point origin = selectionHandler.getSelectionRectangleOrigin();
    final Point target = selectionHandler.getSelectionRectangleTarget();

    if (origin == null || target == null)
    {
      return;
    }

    g2.setColor(Color.BLUE);
    g2.setStroke(SELECTION_STROKE);

    final double y1 = Math.min(origin.getY(), target.getY());
    final double x1 = Math.min(origin.getX(), target.getX());
    final double y2 = Math.max(origin.getY(), target.getY());
    final double x2 = Math.max(origin.getX(), target.getX());

    g2.draw(new Rectangle2D.Double(x1, y1, x2 - x1, y2 - y1));
  }

  protected void paintGrid(final Graphics2D g2d)
  {
    if (WorkspaceSettings.getInstance().isShowGrid())
    {
      final float scaleFactor = getRenderContext().getZoomModel().getZoomAsPercentage();
      final double gridSize = getGridSize() * scaleFactor;
      if (gridSize < 1)
      {
        return;
      }

      final int gridDivisions = Math.max(1, getGridDivisions());

      final Color primaryColor = WorkspaceSettings.getInstance().getGridColor();
      final Color secondaryColor = ColorUtility.convertToBrighter(primaryColor);
      // draw vertical lines
      g2d.setStroke(new BasicStroke(.1f));
      int horizontalLineCount = 0;
      final Line2D.Double line = new Line2D.Double();
      final double gridHeight = getHeight();
      final double gridWidth = getWidth();
      for (double w = gridSize; w < gridWidth; w += gridSize)
      {
        if (horizontalLineCount % gridDivisions == gridDivisions - 1)
        {
          g2d.setColor(primaryColor);
        }
        else
        {
          g2d.setColor(secondaryColor);
        }
        horizontalLineCount++;
        line.setLine(w, 0, w, gridHeight);
        g2d.draw(line);
      }

      // draw horizontal lines
      int verticalLineCount = 0;
      for (double h = gridSize; h < gridHeight; h += gridSize)
      {
        if (verticalLineCount % gridDivisions == gridDivisions - 1)
        {
          g2d.setColor(primaryColor);
        }
        else
        {
          g2d.setColor(secondaryColor);
        }
        verticalLineCount++;
        line.setLine(0, h, gridWidth, h);
        g2d.draw(line);
      }
    }
  }

  protected void paintElementAlignment(final Graphics2D g2d)
  {
    if (WorkspaceSettings.getInstance().isShowElementAlignmentHints())
    {
      final float scaleFactor = getRenderContext().getZoomModel().getZoomAsPercentage();
      g2d.setColor(WorkspaceSettings.getInstance().getAlignmentHintColor());
      g2d.setStroke(new BasicStroke(.2f));

      final double gridHeight = getHeight();
      final double gridWidth = getWidth();
      final long[] hPositions;
      if (getHorizontalPositionsModel() == null)
      {
        final BreakPositionsList horizontalPositions = getHorizontalEdgePositions();
        hPositions = horizontalPositions.getKeys();
      }
      else
      {
        hPositions = getHorizontalPositionsModel().getBreaks();
      }
      final Line2D.Double line = new Line2D.Double();
      for (int i = 0; i < hPositions.length; i++)
      {
        final double position = StrictGeomUtility.toExternalValue(hPositions[i]);
        final double x = position * scaleFactor;
        line.setLine(x, 0, x, gridHeight);
        g2d.draw(line);
      }

      final Point2D offset = getOffset();
      final BreakPositionsList verticalPositions = getVerticalEdgePositions();
      final long[] vPositions = verticalPositions.getKeys();
      for (int i = 0; i < vPositions.length; i++)
      {
        final double position = StrictGeomUtility.toExternalValue(vPositions[i]) - offset.getY();
        final double y2 = position * scaleFactor;
        line.setLine(0, y2, gridWidth, y2);
        g2d.draw(line);
      }
    }

  }

  protected void updateGridSettings()
  {
    gridSize = WorkspaceSettings.getInstance().getGridSize();
    gridDivisions = WorkspaceSettings.getInstance().getGridDivisions();
  }

  public double getGridSize()
  {
    return gridSize;
  }

  public int getGridDivisions()
  {
    return gridDivisions;
  }


  public Element getElementForLocation(final Point2D point, final boolean onlySelected)
  {
    final ElementRenderer rendererRoot = getElementRenderer();
    final Element[] allNodes = rendererRoot.getElementsAt(point.getX(), point.getY());
    for (int i = allNodes.length - 1; i >= 0; i -= 1)
    {
      final Element element = allNodes[i];
      if (ModelUtility.isHideInLayoutGui(element) == true)
      {
        continue;
      }

      styleResolver.resolve(element, resolvedStyle);
      if (resolvedStyle.getBooleanStyleProperty(ElementStyleKeys.VISIBLE) == false)
      {
        continue;
      }

      if (onlySelected == false || getRenderContext().getSelectionModel().isSelected(element))
      {
        return element;
      }
    }
    return null;
  }

  protected RootLevelBand findRootBandForPosition(final Point2D point)
  {
    if (getElementRenderer() == null)
    {
      return null;
    }

    final Element[] elementsAt = getElementRenderer().getElementsAt(point.getX(), point.getY());
    for (int i = elementsAt.length - 1; i >= 0; i -= 1)
    {
      final Element element = elementsAt[i];
      if (element instanceof RootLevelBand)
      {
        return (RootLevelBand) element;
      }
    }

    final Section section = getElementRenderer().getElement();
    if (section instanceof RootLevelBand)
    {
      return (RootLevelBand) section;
    }
    return null;
  }

  public void dispose()
  {
    WorkspaceSettings.getInstance().removeSettingsListener(settingsUpdateHandler);

    if (this.verticalLinealModel != null)
    {
      this.verticalLinealModel.removeLinealModelListener(repaintHandler);
    }
    if (this.horizontalLinealModel != null)
    {
      this.horizontalLinealModel.removeLinealModelListener(repaintHandler);
    }
    if (getElementRenderer() != null)
    {
      getElementRenderer().removeChangeListener(repaintHandler);
    }

    designerContext.removePropertyChangeListener(ReportDesignerContext.SELECTION_WAITING_PROPERTY, selectionStateHandler);

    KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener("permanentFocusOwner", focusHandler); // NON-NLS

    final ReportDocumentContext renderContext = getRenderContext();
    renderContext.getReportDefinition().removeReportModelListener(changeHandler);
    renderContext.getSelectionModel().removeReportSelectionListener(selectionModelListener);

    getElementRenderer().dispose();
  }

  protected void removeEditor()
  {
    if (editorRemover != null)
    {
      KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener
          ("permanentFocusOwner", editorRemover); // NON-NLS
      editorRemover = null;
    }
    if (editorComponent == null)
    {
      inlineEditor = null;
      return;
    }

    remove(editorComponent);
    inlineEditor.removeCellEditorListener(this);

    editorComponent = null;
    inlineEditor = null;
  }

  protected ReportElementInlineEditor getCellEditor()
  {
    return inlineEditor;
  }

  protected boolean installEditor(final ReportElementInlineEditor inlineEditor, final Element element)
  {
    if (inlineEditor == null)
    {
      throw new NullPointerException();
    }

    this.inlineEditor = inlineEditor;

    final CachedLayoutData data = ModelUtility.getCachedLayoutData(element);
    if (data == null)
    {
      removeEditor();
      return false;
    }
    final Component editorComponent = inlineEditor.getElementCellEditorComponent(this, element);
    if (editorComponent == null)
    {
      removeEditor();
      return false;
    }

    if (editorRemover == null)
    {
      final KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
      editorRemover = new CellEditorRemover(fm);
      fm.addPropertyChangeListener("permanentFocusOwner", editorRemover); // NON-NLS
    }

    this.editorComponent = editorComponent;

    final float zoomFactor = getRenderContext().getZoomModel().getZoomAsPercentage();

    final Rectangle2D bounds = getElementRenderer().getBounds();
    final int x = (int) ((getLeftBorder() + StrictGeomUtility.toExternalValue(data.getX())) * zoomFactor);
    final int y = (int) ((getTopBorder() + (StrictGeomUtility.toExternalValue(data.getY()) - bounds.getY()) * zoomFactor));
    final int width = (int) (StrictGeomUtility.toExternalValue(data.getWidth()) * zoomFactor);
    final int height = (int) (StrictGeomUtility.toExternalValue(data.getHeight()) * zoomFactor);
    editorComponent.setBounds(x, y, width, height);
    add(editorComponent);
    editorComponent.validate();
    inlineEditor.addCellEditorListener(this);

    List<Element> selectedElements = getRenderContext().getSelectionModel().getSelectedElementsOfType(Element.class);
    final Element[] visualElements = selectedElements.toArray(new Element[selectedElements.size()]);
    if (visualElements.length > 0)
    {
      oldValues = new ArrayList<Object>();
      for (int i = 0; i < visualElements.length; i++)
      {
        final Object attribute = visualElements[i].getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE);
        oldValues.add(attribute);
      }
    }

    return true;
  }

  protected boolean isEditing()
  {
    return inlineEditor != null;
  }

  public void editingStopped(final ChangeEvent e)
  {
    List<Element> selectedElements = getRenderContext().getSelectionModel().getSelectedElementsOfType(Element.class);
    final Element[] visualElements = selectedElements.toArray(new Element[selectedElements.size()]);
    if (visualElements.length > 0)
    {
      final ArrayList<UndoEntry> undos = new ArrayList<UndoEntry>();
      for (int i = 0; i < visualElements.length; i++)
      {
        final Object attribute = visualElements[i].getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE);
        undos.add(new AttributeEditUndoEntry(visualElements[i].getObjectID(), AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE, oldValues.get(i),
            attribute));
      }
      getRenderContext().getUndo().addChange(Messages.getString("AbstractRenderComponent.InlineEditUndoName"),
          new CompoundUndoEntry((UndoEntry[]) undos.toArray(new UndoEntry[undos.size()])));
    }

    removeEditor();
  }

  public void editingCanceled(final ChangeEvent e)
  {
    operation = null;
    undoEntryBuilder = null;
    removeEditor();
  }

  public JComponent getRepresentationContainer()
  {
    return this;
  }

  public LinealModel getVerticalLinealModel()
  {
    return verticalLinealModel;
  }

  public LinealModel getHorizontalLinealModel()
  {
    return horizontalLinealModel;
  }

  public HorizontalPositionsModel getHorizontalPositionsModel()
  {
    return horizontalPositionsModel;
  }

  protected void updateElements(final Point2D normalizedPoint, final boolean snapToGrid, final boolean snapToElements)
  {
    if (operation != null)
    {
      horizontalSnapModel.setEnableElements(snapToElements || WorkspaceSettings.getInstance().isSnapToElements());
      horizontalSnapModel.setEnableGrid(snapToGrid || WorkspaceSettings.getInstance().isSnapToGrid());
      horizontalSnapModel.setEnableGuides(true);
      verticalSnapModel.setEnableElements(snapToElements || WorkspaceSettings.getInstance().isSnapToElements());
      verticalSnapModel.setEnableGrid(snapToGrid || WorkspaceSettings.getInstance().isSnapToGrid());
      verticalSnapModel.setEnableGuides(true);
      operation.update(normalizedPoint, getRenderContext().getZoomModel().getZoomAsPercentage());
    }
  }

  /**
   * Returns the break positions for inner-band drag-operations (snap to element).
   *
   * @return the edge positions of all elements.
   */
  protected BreakPositionsList getHorizontalEdgePositions()
  {
    return getElementRenderer().getHorizontalEdgePositions();
  }

  /**
   * Returns the break positions for inner-band drag-operations (snap to element).
   *
   * @return the edge positions of all elements.
   */
  protected BreakPositionsList getVerticalEdgePositions()
  {
    return getElementRenderer().getVerticalEdgePositions();
  }

  protected Element[] filterLocalElements(final Element[] originalElements)
  {
    final ArrayList<Element> result = new ArrayList<Element>(originalElements.length);
    for (int i = 0; i < originalElements.length; i++)
    {
      final Element element = originalElements[i];
      if (isLocalElement(element) == false)
      {
        continue;
      }
      result.add(element);
    }
    return result.toArray(new Element[result.size()]);
  }

  protected void initializeDragOperation(final Point2D originPoint,
                                         final SelectionOverlayInformation.InRangeIndicator currentIndicator)
  {
    fpsCalculator.reset();
    fpsCalculator.setActive(true);

    List<Element> visualElements = getRenderContext().getSelectionModel().getSelectedElementsOfType(Element.class);
    if (visualElements.isEmpty())
    {
      return;
    }

    horizontalSnapModel.getGridModel().setGridSize(StrictGeomUtility.toInternalValue(getGridSize()));
    verticalSnapModel.getGridModel().setGridSize(StrictGeomUtility.toInternalValue(getGridSize()));
    horizontalSnapModel.setEnableGrid(WorkspaceSettings.getInstance().isSnapToGrid());
    verticalSnapModel.setEnableGrid(WorkspaceSettings.getInstance().isSnapToGrid());

    final SnapToPositionModel horizontalGuildesPositions = horizontalSnapModel.getGuidesModel();
    horizontalGuildesPositions.clear();
    final GuideLine[] hlines = horizontalLinealModel.getGuideLines();
    for (int i = 0; i < hlines.length; i++)
    {
      final GuideLine guideLine = hlines[i];
      if (guideLine.isActive())
      {
        horizontalGuildesPositions.add(StrictGeomUtility.toInternalValue(guideLine.getPosition()), null);
      }
    }

    final SnapToPositionModel verticalGuidesPositions = verticalSnapModel.getGuidesModel();
    verticalGuidesPositions.clear();
    final GuideLine[] vlines = verticalLinealModel.getGuideLines();
    for (int i = 0; i < vlines.length; i++)
    {
      final GuideLine guideLine = vlines[i];
      if (guideLine.isActive())
      {
        verticalGuidesPositions.add(StrictGeomUtility.toInternalValue(guideLine.getPosition()), null);
      }
    }

    final SnapToPositionModel hElementModel = horizontalSnapModel.getElementModel();
    hElementModel.clear();
    final BreakPositionsList horizontalPositions = getHorizontalEdgePositions();
    final long[] horizontalKeys;
    if (horizontalPositionsModel == null)
    {
      horizontalKeys = horizontalPositions.getKeys();
    }
    else
    {
      horizontalKeys = horizontalPositionsModel.getBreaks();
    }

    for (int i = 0; i < horizontalKeys.length; i++)
    {
      final long key = horizontalKeys[i];
      hElementModel.add(key, horizontalPositions.getOwner(key));
    }

    final SnapToPositionModel vElementModel = verticalSnapModel.getElementModel();
    vElementModel.clear();
    final BreakPositionsList verticalPositions = getVerticalEdgePositions();
    final long[] verticalKeys = verticalPositions.getKeys();
    for (int i = 0; i < verticalKeys.length; i++)
    {
      final long key = verticalKeys[i];
      vElementModel.add(key, verticalPositions.getOwner(key));
    }

    switch (currentIndicator)
    {
      case MOVE:
        operation = new MoveDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel);
        break;
      case BOTTOM_CENTER:
        operation = new ResizeBottomDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel);
        break;
      case MIDDLE_RIGHT:
        operation = new ResizeRightDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel);
        break;
      case MIDDLE_LEFT:
        operation = new ResizeLeftDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel);
        break;
      case TOP_CENTER:
        operation = new ResizeTopDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel);
        break;
      case BOTTOM_LEFT:
      {
        final CompoundDragOperation op = new CompoundDragOperation();
        op.add(new ResizeLeftDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel));
        op.add(new ResizeBottomDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel));
        operation = op;
        break;
      }
      case BOTTOM_RIGHT:
      {
        final CompoundDragOperation op = new CompoundDragOperation();
        op.add(new ResizeRightDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel));
        op.add(new ResizeBottomDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel));
        operation = op;
        break;
      }
      case TOP_LEFT:
      {
        final CompoundDragOperation op = new CompoundDragOperation();
        op.add(new ResizeLeftDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel));
        op.add(new ResizeTopDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel));
        operation = op;
        break;
      }
      case TOP_RIGHT:
      {
        final CompoundDragOperation op = new CompoundDragOperation();
        op.add(new ResizeRightDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel));
        op.add(new ResizeTopDragOperation(visualElements, originPoint, horizontalSnapModel, verticalSnapModel));
        operation = op;
        break;
      }
      default:
    }

    if (operation != null)
    {
      undoEntryBuilder = new MassElementStyleUndoEntryBuilder(visualElements);
    }
  }

  protected void finishDragOperation()
  {
    if (operation != null)
    {
      operation.finish();

      final MassElementStyleUndoEntry undoEntry = undoEntryBuilder.finish();
      getRenderContext().getUndo().addChange(Messages.getString("AbstractRenderComponent.ResizeUndoName"), undoEntry);
    }

    operation = null;
    undoEntryBuilder = null;
    repaintConditionally();

    fpsCalculator.setActive(false);
    logger.debug ("MoveOperation-performance: " + fpsCalculator.getFps());
  }

  public void repaintConditionally()
  {
    if (!paintingImmediately && operation != null)
    {
      // guard against paintImmediately being called again if we're
      // already in the middle of repainting.
      paintingImmediately = true;
      paintImmediately(0, 0, getWidth(), getHeight());
      paintingImmediately = false;
    }
    else
    {
      repaint(REPAINT_INTERVAL);
    }
  }

  protected boolean isMouseOperationInProgress()
  {
    return operation != null;
  }

  protected boolean isMouseOperationPossible()
  {
    return currentIndicator != null && currentIndicator != SelectionOverlayInformation.InRangeIndicator.NOT_IN_RANGE;
  }

  protected void installLineals(final LinealModel horizontalLinealModel,
                                final HorizontalPositionsModel horizontalPositionsModel)
  {
    final LinealModel verticalLinealModel;
    final ElementRenderer elementRenderer = getElementRenderer();
    if (elementRenderer != null)
    {
      verticalLinealModel = elementRenderer.getVerticalLinealModel();
    }
    else
    {
      verticalLinealModel = null;
    }

    if (this.verticalLinealModel != null)
    {
      this.verticalLinealModel.removeLinealModelListener(repaintHandler);
    }
    if (this.horizontalLinealModel != null)
    {
      this.horizontalLinealModel.removeLinealModelListener(repaintHandler);
    }

    this.horizontalPositionsModel = horizontalPositionsModel;
    this.verticalLinealModel = verticalLinealModel;
    this.horizontalLinealModel = horizontalLinealModel;

    if (this.verticalLinealModel != null)
    {
      this.verticalLinealModel.addLinealModelListener(repaintHandler);
    }
    if (this.horizontalLinealModel != null)
    {
      this.horizontalLinealModel.addLinealModelListener(repaintHandler);
    }

    if (elementRenderer != null)
    {
      elementRenderer.removeChangeListener(repaintHandler);
      elementRenderer.addChangeListener(repaintHandler);
    }
  }

  public Dimension getMinimumSize()
  {
    return getPreferredSize();
  }

  public Dimension getPreferredSize()
  {
    final ElementRenderer rendererRoot = getElementRenderer();
    if (rendererRoot == null)
    {
      return new Dimension(0, 0);
    }

    final float zoom = getRenderContext().getZoomModel().getZoomAsPercentage();
    try
    {
      final Rectangle2D bounds = rendererRoot.getBounds();
      final int leftBorder;
      if (isShowLeftBorder())
      {
        leftBorder = (int) getLeftBorder();
      }
      else
      {
        leftBorder = 0;
      }

      final int topBorder;
      if (isShowTopBorder())
      {
        topBorder = (int) getTopBorder();
      }
      else
      {
        topBorder = 0;
      }

      final int width = (int) (zoom * (leftBorder + bounds.getWidth()));
      final int height = (int) (zoom * (topBorder + bounds.getHeight()));
      return new Dimension(width, height);
    }
    catch (Exception e)
    {
      UncaughtExceptionsModel.getInstance().addException(e);
      return new Dimension(0, (int) (zoom * rendererRoot.getVisualHeight()));
    }
  }

  public void removeNotify()
  {
    KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener
        ("permanentFocusOwner", editorRemover); // NON-NLS
    editorRemover = null;
    super.removeNotify();
  }

  protected boolean stopCellEditing()
  {
    if (isEditing() == false)
    {
      return true;
    }
    final ReportElementInlineEditor elementInlineEditor = getCellEditor();
    if (elementInlineEditor == null)
    {
      return true;
    }
    return elementInlineEditor.stopCellEditing();
  }

  protected void updateCursorForIndicator()
  {
    if (currentIndicator == null)
    {
      setCursor(Cursor.getDefaultCursor());
      return;
    }
    switch (currentIndicator)
    {
      case NOT_IN_RANGE:
        setCursor(Cursor.getDefaultCursor());
        break;
      case MOVE:
        setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
        break;
      case BOTTOM_CENTER:
        setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
        break;
      case BOTTOM_LEFT:
        setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
        break;
      case BOTTOM_RIGHT:
        setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
        break;
      case MIDDLE_LEFT:
        setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
        break;
      case MIDDLE_RIGHT:
        setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
        break;
      case TOP_LEFT:
        setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
        break;
      case TOP_CENTER:
        setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
        break;
      case TOP_RIGHT:
        setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
        break;
    }
  }
}
TOP

Related Classes of org.pentaho.reporting.designer.core.editor.report.AbstractRenderComponent$AsyncChangeNotifier

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.