text: package pipe.controllers;
  
  import pipe.constants.GUIConstants;
  import pipe.controllers.application.PipeApplicationController;
  import pipe.gui.PetriNetTab;
  import pipe.historyActions.MultipleEdit;
  import pipe.historyActions.component.AddPetriNetObject;
  import pipe.utilities.gui.GuiUtils;
  import uk.ac.imperial.pipe.exceptions.PetriNetComponentException;
  import uk.ac.imperial.pipe.models.petrinet.*;
  import uk.ac.imperial.pipe.naming.MultipleNamer;
  import uk.ac.imperial.pipe.naming.PetriNetComponentNamer;
  import uk.ac.imperial.pipe.visitor.PasteVisitor;
  import uk.ac.imperial.pipe.visitor.component.PetriNetComponentVisitor;
  
  import javax.swing.event.UndoableEditEvent;
  import javax.swing.event.UndoableEditListener;
  import javax.swing.undo.UndoableEdit;
  
  import java.awt.*;
  import java.awt.event.KeyEvent;
  import java.awt.event.MouseEvent;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.LinkedList;
  import java.util.List;
  
  
  /**
   * Class to handle copy and paste functionality
   */
  @SuppressWarnings("serial")
  public class CopyPasteManager extends javax.swing.JComponent
          implements java.awt.event.MouseListener, java.awt.event.MouseMotionListener, java.awt.event.KeyListener {
  
      /**
       * Colour of rectangle displayed when pasting
       */
      private static final Paint PASTE_COLOR = new Color(155, 155, 155, 100);
  
      /**
       * Colour of rectangle outline displayed when pasting
       */
      private static final Color PASTE_COLOR_OUTLINE = new Color(155, 0, 0, 0);
  
      /**
       * Rectangle displayed which marks the outline of the objects to paste
       */
      private final Rectangle pasteRectangle = new Rectangle(-1, -1);
  
      /**
       * Petri net tab where pasting takes place
       */
      private final PetriNetTab petriNetTab;
  
      /**
       * Petri net pasting objects from/to
       */
      private final PetriNet petriNet;
  
      /**
       * Main PIPE application controller
       */
      private final PipeApplicationController applicationController;
  
      /**
       * Origin of the selected components to paste (top left corner)
       */
      private final Point rectangleOrigin = new Point();
  
      /**
       * Listener for undoable events being created
       */
      private final UndoableEditListener listener;
  
      /**
       * pasteInProgres is true when pasteRectangle is visible (user is doing a
       * paste but still hasn't chosen the position where elements will be pasted).
       */
      private boolean pasteInProgress = false;
  
      /**
       * Components to paste when paste is clicked.
       * These are set when copied
       */
      private Collection<PetriNetComponent> pasteComponents = new ArrayList<>();
  
  
      /**
       * Constructor
       *
       * @param listener              undoable event listener, used to register undo events to
       * @param petriNetTab           current Petri net tab
       * @param net                   underlying Petri net displayed on the Petri net tab
       * @param applicationController main application controller
       */
      public CopyPasteManager(UndoableEditListener listener, PetriNetTab petriNetTab, PetriNet net,
                              PipeApplicationController applicationController) {
          this.petriNetTab = petriNetTab;
          petriNet = net;
          this.applicationController = applicationController;
          addMouseListener(this);
          addMouseMotionListener(this);
          addKeyListener(this);
          this.listener = listener;
  
      }
  
      /**
       * Creates new components for the petri net to copy when pasted
       *
       * @param selectedComponents components to copy
       */
      public void copy(Collection<PetriNetComponent> selectedComponents) {
          pasteComponents.clear();
          pasteComponents.addAll(selectedComponents);
          LocationVisitor locationVisitor = new LocationVisitor();
  
          for (PetriNetComponent component : selectedComponents) {
              try {
                  component.accept(locationVisitor);
              } catch (PetriNetComponentException e) {
                  GuiUtils.displayErrorMessage(null, e.getMessage());
              }
          }
          Location location = locationVisitor.location;
          pasteRectangle.setRect(location.left, location.top, location.right - location.left,
                  location.bottom - location.top);
          rectangleOrigin.setLocation(location.left, location.top);
      }
  
      /**
       * Shows the paste rectangle on screen
       */
      public void showPasteRectangle() {
          if (!pasteInProgress) {
              petriNetTab.add(this);
              requestFocusInWindow();
              //            if (zoom != petriNetTab.getZoom()) {
              //                updateSize(pasteRectangle, zoom, petriNetTab.getZoom());
              //                zoom = petriNetTab.getZoom();
              //            }
  
              petriNetTab.setLayer(this, GUIConstants.SELECTION_LAYER_OFFSET);
              repaint();
              pasteInProgress = true;
              updateBounds();
          }
      }
  
      /**
       * Update the bounds which this object can be displayed at
       */
      private void updateBounds() {
          if (pasteInProgress) {
              PetriNetTab activeTab = applicationController.getActiveTab();
              setBounds(0, 0, activeTab.getWidth(), activeTab.getHeight());
          }
      }
  
      /**
       * @return if it is possible to perform a paste action
       */
      public boolean pasteEnabled() {
          return !pasteComponents.isEmpty();
      }
  
      /**
       * Paints the paste rectangle onto the screen
       *
       * @param g paint graphics
       */
      @Override
      public void paintComponent(Graphics g) {
          super.paintComponent(g);
          Graphics2D g2d = (Graphics2D) g;
          g2d.setPaint(PASTE_COLOR);
          g2d.fill(pasteRectangle);
          g2d.setXORMode(PASTE_COLOR_OUTLINE);
          g2d.draw(pasteRectangle);
      }
  
      /**
       * Dragging the mouse on the screen updates the location of the
       * paste rectangle
       *
       * @param e mouse drag event
       */
      @Override
      public void mouseDragged(MouseEvent e) {
          if (pasteInProgress) {
              updateRect(e.getPoint());
          }
      }
  
      /**
       * Changes the rectangles location to point
       *
       * @param point new top left point for rectangle
       */
      private void updateRect(Point point) {
          pasteRectangle.setLocation(point);
          repaint();
          updateBounds();
      }
  
      /**
       * Moving the mouse on the screen updates the location of the
       * paste rectangle
       *
       * @param e mouse move event
       */
      @Override
      public void mouseMoved(MouseEvent e) {
          if (pasteInProgress) {
              updateRect(e.getPoint());
          }
      }
  
      /**
       * Noop action on click
       *
       * @param e mouse click event 
       */
      @Override
      public void mouseClicked(MouseEvent e) {
          //Not needed
      }
  
      /**
       * Performs the paste action
       *
       * @param e mouse pressed event 
       */
      @Override
      public void mousePressed(MouseEvent e) {
          petriNetTab.updatePreferredSize();
          petriNetTab.setLayer(this, GUIConstants.LOWEST_LAYER_OFFSET);
          repaint();
          if (pasteInProgress) {
              paste(petriNetTab);
          }
      }
  
      /**
       * Noop action
       *
       * @param e mouse released event 
       */
      @Override
      public void mouseReleased(MouseEvent e) {
          // Not needed
      }
  
      /**
       * Noop action
       *
       * @param e mouse entered event 
       */
      @Override
      public void mouseEntered(MouseEvent e) {
          // Not needed
      }
  
      /**
       * Noop action
       *
       * @param e mouse exited event 
       */
      @Override
      public void mouseExited(MouseEvent e) {
          // Not needed
      }
  
      /**
       * Paste pastes the new objects into the petriNet specified in consturction.
       * <p>
       * It first pastes the connectables, and then other components. This ordering is important
       * and will ensure that arcs are created with the right components.
       * </p>
       * @param petriNetTab petri net tab to paste items to
       */
      private void paste(PetriNetTab petriNetTab) {
          pasteInProgress = false;
          petriNetTab.remove(this);
  
          if (pasteComponents.isEmpty()) {
              return;
          }
  
          int despX = pasteRectangle.x - rectangleOrigin.x;
          int despY = pasteRectangle.y - rectangleOrigin.y;
  
          MultipleNamer multipleNamer = new PetriNetComponentNamer(petriNet);
          PasteVisitor pasteVisitor = new PasteVisitor(petriNet, pasteComponents, multipleNamer, despX, despY);
  
          try {
              for (Connectable component : getConnectablesToPaste()) {
                  component.accept(pasteVisitor);
              }
              for (PetriNetComponent component : getNonConnectablesToPaste()) {
                  component.accept(pasteVisitor);
              }
          } catch (PetriNetComponentException e) {
  
              GuiUtils.displayErrorMessage(null, e.getMessage());
          }
  
          createPasteHistoryItem(pasteVisitor.getCreatedComponents());
      }
  
      /**
       * @return a collection of the connectable items to paste
       */
      private Collection<Connectable> getConnectablesToPaste() {
          final Collection<Connectable> connectables = new LinkedList<>();
          PetriNetComponentVisitor connectableVisitor = new PlaceTransitionVisitor() {
              @Override
              public void visit(Place place) {
                  connectables.add(place);
              }
  
              @Override
              public void visit(Transition transition) {
                  connectables.add(transition);
              }
          };
  
          for (PetriNetComponent component : pasteComponents) {
              try {
                  component.accept(connectableVisitor);
              } catch (PetriNetComponentException e) {
                  GuiUtils.displayErrorMessage(null, e.getMessage());
              }
          }
          return connectables;
      }
  
      /**
       * @return Petri net components that do not inherit from Connectable
       */
      private Collection<PetriNetComponent> getNonConnectablesToPaste() {
          final Collection<PetriNetComponent> components = new LinkedList<>();
          PetriNetComponentVisitor componentVisitor = new NonConnectableVisitor() {
              @Override
              public void visit(Token token) {
                  components.add(token);
              }
  
              @Override
              public void visit(Annotation annotation) {
                  components.add(annotation);
              }
  
              @Override
              public void visit(InboundArc inboundArc) {
                  components.add(inboundArc);
              }
  
              @Override
              public void visit(OutboundArc outboundArc) {
                  components.add(outboundArc);
              }
          };
  
          for (PetriNetComponent component : pasteComponents) {
              try {
                  component.accept(componentVisitor);
              } catch (PetriNetComponentException e) {
                  GuiUtils.displayErrorMessage(null, e.getMessage());
              }
          }
          return components;
      }
  
      /**
       * Creates a history item for the new components added to the petrinet
       *
       * @param createdComponents new components that have been created
       */
      private void createPasteHistoryItem(Iterable<PetriNetComponent> createdComponents) {
          List<UndoableEdit> undoableEditList = new LinkedList<>();
          for (PetriNetComponent component : createdComponents) {
              AddPetriNetObject addAction = new AddPetriNetObject(component, petriNet);
              undoableEditList.add(addAction);
          }
  
          listener.undoableEditHappened(new UndoableEditEvent(this, new MultipleEdit(undoableEditList)));
      }
  
      /**
       * Noop action
       *
       * @param e key typed event 
       */
      @Override
      public void keyTyped(KeyEvent e) {
          // Not needed
      }
  
      /**
       * Noop action
       *
       * @param e key pressed event 
       */
      @Override
      public void keyPressed(KeyEvent e) {
          // Not needed
      }
  
      /**
       * Noop action
       *
       * @param e key released event 
       */
      @Override
      public void keyReleased(KeyEvent e) {
          if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
              cancelPaste();
          }
      }
  
      /**
       * Cancel the paste. This will stop the paste rectangle being displayed but will
       * keep the copied items selected for future pastes
       */
      public void cancelPaste() {
          PetriNetTab tab = applicationController.getActiveTab();
          cancelPaste(tab);
      }
  
      /**
       * Cancel the paste. This will stop the paste rectangle being displayed but will
       * keep the copied items selected for future pastes
       *
       * @param view tab on which the paste is taking place
       */
      void cancelPaste(PetriNetTab view) {
          pasteInProgress = false;
          view.repaint();
          view.remove(this);
      }
  
      /**
       * Used for creating anonymous classes that only visit
       * Places and Transition
       */
      private interface PlaceTransitionVisitor extends PlaceVisitor, TransitionVisitor {
  
      }
  
  
      /**
       * Used for creating anonymous classes that visit non connectable classes
       */
      private interface NonConnectableVisitor extends AnnotationVisitor, ArcVisitor, TokenVisitor {
  
      }
  
      /**
       * Private class used to set the bounds of a selection rectangle
       * Needed to create a class so that the visitor can change the values
       */
      private static class Location {
          /**
           * Bottom location
           */
          private double bottom = 0;
  
          /**
           * Right of the rectangle
           */
          private double right = 0;
  
          /**
           * Top of the rectangle
           */
          private double top = Double.MAX_VALUE;
  
          /**
           * Left of the rectangle
           */
          private double left = Double.MAX_VALUE;
      }
  
      /**
       * Used to set the bounds of the rectagle displayed when copy pasting
       */
      private static class LocationVisitor implements PlaceTransitionVisitor {
          /**
           * Location of the rectangle
           */
          private final Location location = new Location();
  
          /**
           * Adjusts the bounds to include the position of the place
           * @param place
           */
          @Override
          public void visit(Place place) {
              adjustLocation(place);
          }
  
          /**
           * Changes the bounds of the rectangle to include the connectable
           * @param connectable being bounded 
           * @param <T> type of the connectable
           */
          private <T extends Connectable> void adjustLocation(T connectable) {
              if (connectable.getX() < location.left) {
                  location.left = connectable.getX();
              }
              if (connectable.getX() + connectable.getWidth() > location.right) {
                  location.right = connectable.getX() + connectable.getWidth();
              }
              if (connectable.getY() < location.top) {
                  location.top = connectable.getY();
              }
              if (connectable.getY() + connectable.getHeight() > location.bottom) {
                  location.bottom = connectable.getY() + connectable.getHeight();
              }
          }
  
          /**
           * Adjusts the bounds of the rectangle to include the position of the transition
           * @param transition to be included in the bounds 
           */
          @Override
          public void visit(Transition transition) {
              adjustLocation(transition);
          }
  
      }
  
  
  }
  