/******************************************************************************* * Copyright (c) 2016 Weasis Team and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.core.ui.editor.image; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import javax.swing.JComponent; import org.weasis.core.api.gui.model.ViewModel; import org.weasis.core.api.gui.model.ViewModelChangeListener; import org.weasis.core.api.gui.util.GeomUtil; import org.weasis.core.ui.model.GraphicModel; import org.weasis.core.ui.model.graphic.Graphic; import org.weasis.core.ui.model.imp.XmlGraphicModel; import org.weasis.core.ui.model.layer.GraphicModelChangeListener; import org.weasis.core.ui.model.utils.imp.DefaultGraphicLabel; import org.weasis.core.ui.model.utils.imp.DefaultViewModel; /** * The Class GraphicsPane. * * @author Nicolas Roduit */ public class GraphicsPane extends JComponent implements Canvas { private static final long serialVersionUID = -7830146632397526267L; protected GraphicModel graphicManager; protected ViewModel viewModel; protected final LayerModelHandler layerModelHandler; protected final ViewModelHandler viewModelHandler; protected final DrawingsKeyListeners drawingsKeyListeners = new DrawingsKeyListeners(); protected final HashMap<String, Object> actionsInView = new HashMap<>(); protected final AffineTransform affineTransform = new AffineTransform(); protected final AffineTransform inverseTransform = new AffineTransform(); protected final PropertyChangeListener graphicsChangeHandler = new PropertyChangeHandler(); public GraphicsPane(ViewModel viewModel) { setOpaque(false); this.layerModelHandler = new LayerModelHandler(); this.graphicManager = new XmlGraphicModel(); this.graphicManager.addChangeListener(layerModelHandler); this.viewModelHandler = new ViewModelHandler(); this.viewModel = Optional.ofNullable(viewModel).orElseGet(DefaultViewModel::new); this.viewModel.addViewModelChangeListener(viewModelHandler); } @Override public void setGraphicManager(GraphicModel graphicManager) { Objects.requireNonNull(graphicManager); GraphicModel graphicManagerOld = this.graphicManager; if (!Objects.equals(graphicManager, graphicManagerOld)) { graphicManagerOld.removeChangeListener(layerModelHandler); graphicManagerOld.removeGraphicChangeHandler(graphicsChangeHandler); graphicManagerOld.deleteNonSerializableGraphics(); this.graphicManager = graphicManager; this.graphicManager.addGraphicChangeHandler(graphicsChangeHandler); if (this instanceof ViewCanvas) { this.graphicManager.updateLabels(Boolean.TRUE, (ViewCanvas) this); } this.graphicManager.addChangeListener(layerModelHandler); firePropertyChange("graphicManager", graphicManagerOld, this.graphicManager); //$NON-NLS-1$ } } @Override public GraphicModel getGraphicManager() { return graphicManager; } @Override public AffineTransform getAffineTransform() { return affineTransform; } @Override public AffineTransform getInverseTransform() { return inverseTransform; } @Override public PropertyChangeListener getGraphicsChangeHandler() { return graphicsChangeHandler; } @Override public void disposeView() { Optional.ofNullable(viewModel).ifPresent(model -> model.removeViewModelChangeListener(viewModelHandler)); // Unregister listener graphicManager.removeChangeListener(layerModelHandler); graphicManager.removeGraphicChangeHandler(graphicsChangeHandler); } /** * Gets the view model. * * @return the view model, never null */ @Override public ViewModel getViewModel() { return viewModel; } /** * Sets the view model. * * @param viewModel * the view model, never null */ @Override public void setViewModel(ViewModel viewModel) { ViewModel viewModelOld = this.viewModel; if (viewModelOld != viewModel) { if (viewModelOld != null) { viewModelOld.removeViewModelChangeListener(viewModelHandler); } this.viewModel = viewModel; if (this.viewModel != null) { this.viewModel.addViewModelChangeListener(viewModelHandler); } firePropertyChange("viewModel", viewModelOld, this.viewModel); //$NON-NLS-1$ } } @Override public Object getActionValue(String action) { if (action == null) { return null; } return actionsInView.get(action); } @Override public Map<String, Object> getActionsInView() { return actionsInView; } @Override public JComponent getJComponent() { return this; } @Override public void zoom(Double viewScale) { double modelOffsetXOld = viewModel.getModelOffsetX(); double modelOffsetYOld = viewModel.getModelOffsetY(); double viewScaleOld = viewModel.getViewScale(); double viewportWidth = getWidth() - 1.0; double viewportHeight = getHeight() - 1.0; double centerX = modelOffsetXOld + 0.5 * viewportWidth / viewScaleOld; double centerY = modelOffsetYOld + 0.5 * viewportHeight / viewScaleOld; zoom(centerX, centerY, viewScale); } public void zoom(double centerX, double centerY, double viewScale) { Double vScale = cropViewScale(viewScale); double viewportWidth = getWidth() - 1.0; double viewportHeight = getHeight() - 1.0; double modelOffsetX = centerX - 0.5 * viewportWidth / vScale; double modelOffsetY = centerY - 0.5 * viewportHeight / vScale; getViewModel().setModelOffset(modelOffsetX, modelOffsetY, vScale); } public void zoom(Rectangle2D zoomRect) { double viewportWidth = getWidth() - 1.0; double viewportHeight = getHeight() - 1.0; zoom(zoomRect.getCenterX(), zoomRect.getCenterY(), Math.min(viewportWidth / zoomRect.getWidth(), viewportHeight / zoomRect.getHeight())); } @Override public double getBestFitViewScale() { double viewportWidth = getWidth() - 1.0; double viewportHeight = getHeight() - 1.0; final Rectangle2D modelArea = viewModel.getModelArea(); return cropViewScale(Math.min(viewportWidth / modelArea.getWidth(), viewportHeight / modelArea.getHeight())); } @Override public double viewToModelX(Double viewX) { return viewModel.getModelOffsetX() + viewToModelLength(viewX); } @Override public double viewToModelY(Double viewY) { return viewModel.getModelOffsetY() + viewToModelLength(viewY); } @Override public double viewToModelLength(Double viewLength) { return viewLength / viewModel.getViewScale(); } @Override public double modelToViewX(Double modelX) { return modelToViewLength(modelX - viewModel.getModelOffsetX()); } @Override public double modelToViewY(Double modelY) { return modelToViewLength(modelY - viewModel.getModelOffsetY()); } @Override public double modelToViewLength(Double modelLength) { return modelLength * viewModel.getViewScale(); } @Override public Point2D getImageCoordinatesFromMouse(Integer x, Integer y) { double viewScale = getViewModel().getViewScale(); Point2D p2 = new Point2D.Double(x + getViewModel().getModelOffsetX() * viewScale, y + getViewModel().getModelOffsetY() * viewScale); inverseTransform.transform(p2, p2); return p2; } @Override public Point getMouseCoordinatesFromImage(Double x, Double y) { Point2D p2 = new Point2D.Double(x, y); affineTransform.transform(p2, p2); double viewScale = getViewModel().getViewScale(); return new Point((int) Math.floor(p2.getX() - getViewModel().getModelOffsetX() * viewScale + 0.5), (int) Math.floor(p2.getY() - getViewModel().getModelOffsetY() * viewScale + 0.5)); } @Override protected void paintComponent(Graphics g) { // honor the opaque property if (isOpaque()) { g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); } } // ///////////////////////////////////////////////////////////////////////////////////// // Helpers private double cropViewScale(double viewScale) { return DefaultViewModel.cropViewScale(viewScale, viewModel.getViewScaleMin(), viewModel.getViewScaleMax()); } public static void repaint(Canvas canvas, Rectangle rectangle) { if (rectangle != null) { // Add the offset of the canvas double viewScale = canvas.getViewModel().getViewScale(); int x = (int) (rectangle.x - canvas.getViewModel().getModelOffsetX() * viewScale); int y = (int) (rectangle.y - canvas.getViewModel().getModelOffsetY() * viewScale); canvas.getJComponent().repaint(new Rectangle(x, y, rectangle.width, rectangle.height)); } } // ///////////////////////////////////////////////////////////////////////////////////// // Inner Classes /** * The Class LayerModelHandler. * * @author Nicolas Roduit */ private class LayerModelHandler implements GraphicModelChangeListener { @Override public void handleModelChanged(GraphicModel modelList) { repaint(); } } /** * The Class ViewModelHandler. * * @author Nicolas Roduit */ private class ViewModelHandler implements ViewModelChangeListener { @Override public void handleViewModelChanged(ViewModel viewModel) { repaint(); } } /** * The Class DrawingsKeyListeners. * * @author Nicolas Roduit */ private class DrawingsKeyListeners implements KeyListener { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_DELETE) { graphicManager.deleteSelectedGraphics(GraphicsPane.this, true); } else if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_D) { graphicManager.setSelectedGraphic(null); } else if (e.isControlDown() && e.getKeyCode() == KeyEvent.VK_A) { graphicManager.setSelectedAllGraphics(); } // FIXME arrows is already used with pan! // else if (e.getKeyCode() == KeyEvent.VK_LEFT) { // layerModel.moveSelectedGraphics(-1, 0); // } // else if (e.getKeyCode() == KeyEvent.VK_UP) { // layerModel.moveSelectedGraphics(0, -1); // } // else if (e.getKeyCode() == KeyEvent.VK_RIGHT) { // layerModel.moveSelectedGraphics(1, 0); // } // else if (e.getKeyCode() == KeyEvent.VK_DOWN) { // layerModel.moveSelectedGraphics(0, 1); // } } @Override public void keyReleased(KeyEvent e) { // Do Nothing } @Override public void keyTyped(KeyEvent e) { // DO nothing } } /** * The Class PropertyChangeHandler. * * @author Nicolas Roduit */ class PropertyChangeHandler implements PropertyChangeListener, Serializable { private static final long serialVersionUID = -9094820911680205527L; private PropertyChangeHandler() { } // This method is called when a property is changed (fired from a graphic) @Override public void propertyChange(PropertyChangeEvent propertychangeevent) { Object obj = propertychangeevent.getSource(); String s = propertychangeevent.getPropertyName(); if (obj instanceof Graphic) { Graphic graph = (Graphic) obj; if ("bounds".equals(s)) { //$NON-NLS-1$ graphicBoundsChanged(graph, (Shape) propertychangeevent.getOldValue(), (Shape) propertychangeevent.getNewValue(), getAffineTransform()); } else if ("graphicLabel".equals(s)) { //$NON-NLS-1$ labelBoundsChanged(graph, (DefaultGraphicLabel) propertychangeevent.getOldValue(), (DefaultGraphicLabel) propertychangeevent.getNewValue(), getAffineTransform()); } else if ("remove".equals(s)) { //$NON-NLS-1$ removeGraphic(graph); } else if ("remove.repaint".equals(s)) { //$NON-NLS-1$ removeGraphicAndRepaint(graph); } else if ("toFront".equals(s)) { //$NON-NLS-1$ toFront(graph); } else if ("toBack".equals(s)) { //$NON-NLS-1$ toBack(graph); } } } public void toFront(Graphic graphic) { List<Graphic> list = graphicManager.getModels(); synchronized (list) { for (int i = 0; i < list.size(); i++) { if (list.get(i).equals(graphic)) { Collections.rotate(list.subList(i, list.size()), -1); break; } } } repaint(); } public void toBack(Graphic graphic) { List<Graphic> list = graphicManager.getModels(); synchronized (list) { for (int i = 0; i < list.size(); i++) { if (list.get(i).equals(graphic)) { Collections.rotate(list.subList(0, i + 1), 1); break; } } } repaint(); } public void removeGraphicAndRepaint(Graphic graphic) { removeGraphic(graphic); GraphicsPane.repaint(GraphicsPane.this, graphic.getTransformedBounds(graphic.getShape(), getAffineTransform())); } public void removeGraphic(Graphic graphic) { if (graphicManager != null) { graphicManager.removeGraphic(graphic); } graphic.removePropertyChangeListener(graphicsChangeHandler); } protected Rectangle rectangleUnion(Rectangle rectangle, Rectangle rectangle1) { if (rectangle == null) { return rectangle1; } return rectangle1 == null ? rectangle : rectangle.union(rectangle1); } protected void graphicBoundsChanged(Graphic graphic, Shape oldShape, Shape shape, AffineTransform transform) { if (graphic != null) { if (oldShape == null) { if (shape != null) { Rectangle rect = graphic.getTransformedBounds(shape, transform); GraphicsPane.repaint(GraphicsPane.this, rect); } } else { if (shape == null) { Rectangle rect = graphic.getTransformedBounds(oldShape, transform); GraphicsPane.repaint(GraphicsPane.this, rect); } else { Rectangle rect = rectangleUnion(graphic.getTransformedBounds(oldShape, transform), graphic.getTransformedBounds(shape, transform)); GraphicsPane.repaint(GraphicsPane.this, rect); } } } } protected void labelBoundsChanged(Graphic graphic, DefaultGraphicLabel oldLabel, DefaultGraphicLabel newLabel, AffineTransform transform) { if (graphic != null) { boolean oldNull = oldLabel == null || oldLabel.getLabels() == null; boolean newNull = newLabel == null || newLabel.getLabels() == null; if (oldNull) { if (!newNull) { Rectangle2D rect = graphic.getTransformedBounds(newLabel, transform); GeomUtil.growRectangle(rect, 2); GraphicsPane.repaint(GraphicsPane.this, rect.getBounds()); } } else { if (newNull) { Rectangle2D rect = graphic.getTransformedBounds(oldLabel, transform); GeomUtil.growRectangle(rect, 2); GraphicsPane.repaint(GraphicsPane.this, rect.getBounds()); } else { Rectangle2D newRect = graphic.getTransformedBounds(newLabel, transform); GeomUtil.growRectangle(newRect, 2); Rectangle2D oldRect = graphic.getTransformedBounds(oldLabel, transform); GeomUtil.growRectangle(oldRect, 2); Rectangle rect = rectangleUnion(oldRect.getBounds(), newRect.getBounds()); GraphicsPane.repaint(GraphicsPane.this, rect); } } } } } }