/* * funCKit - functional Circuit Kit * Copyright (C) 2013 Lukas Elsner <open@mindrunner.de> * Copyright (C) 2013 Peter Dahlberg <catdog2@tuxzone.org> * Copyright (C) 2013 Julian Stier <mail@julian-stier.de> * Copyright (C) 2013 Sebastian Vetter <mail@b4sti.eu> * Copyright (C) 2013 Thomas Poxrucker <poxrucker_t@web.de> * Copyright (C) 2013 Alexander Treml <alex.treml@directbox.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.sep2011.funckit.controller; import de.sep2011.funckit.Application; import de.sep2011.funckit.Application.OperatingSystem; import de.sep2011.funckit.controller.listener.edit.CopyActionListener; import de.sep2011.funckit.controller.listener.edit.PasteActionListener; import de.sep2011.funckit.model.graphmodel.Brick; import de.sep2011.funckit.model.graphmodel.Circuit; import de.sep2011.funckit.model.graphmodel.Component; import de.sep2011.funckit.model.graphmodel.ComponentType; import de.sep2011.funckit.model.graphmodel.Element; import de.sep2011.funckit.model.sessionmodel.EditPanelModel; import de.sep2011.funckit.model.sessionmodel.Project; import de.sep2011.funckit.model.sessionmodel.Settings; import de.sep2011.funckit.util.Log; import de.sep2011.funckit.view.EditPanel; import javax.swing.Timer; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.util.Deque; import java.util.LinkedList; import java.util.Set; import static java.lang.Math.abs; import static javax.swing.SwingUtilities.isRightMouseButton; /** * Implements all methods of tool-interface with empty body to make concrete * tool implementations more lightweight, as they mostly need only few action * methods for their implementation. */ public abstract class AbstractTool implements Tool { private final static double ZOOM_BREAKPOINT_ELEMENT_ACTIVE = 0.2; private long spaceReleaseTimeCode = 0; private final Timer spaceReleaseTimer = new Timer(1, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { controller.getSessionModel().restoreTool(); } }); /** * Current mediating controller object with access to model and view * objects. */ Controller controller; /** * Creates a new AbstractTool */ protected AbstractTool() { spaceReleaseTimer.setRepeats(false); } /** * {@inheritDoc} */ @Override public void keyTyped(KeyEvent keyEvent, EditPanelModel editPanelModel) { } @Override public Cursor getToolDefaultCursor() { return Cursor.getDefaultCursor(); } /** * {@inheritDoc} */ @Override public void keyPressed(KeyEvent keyEvent, EditPanelModel editPanelModel) { if (keyEvent.getKeyCode() == KeyEvent.VK_SHIFT) { controller.getSessionModel().saveTool(); controller.getSessionModel().setTool(new SelectTool(controller)); // controllGotPressed = true; } else if (keyEvent.getKeyCode() == KeyEvent.VK_SPACE) { long timecode = keyEvent.getWhen(); spaceReleaseTimer.stop(); if (timecode == spaceReleaseTimeCode) { // ignore auto repeat } else { controller.getSessionModel().saveTool(); controller.getSessionModel().setTool(new DragViewportTool(controller)); } } else if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) { this.cancelCurrentAction(editPanelModel); } else if (keyEvent.getKeyCode() == KeyEvent.VK_T && isPlatformCtrlOrBlumenkohlDown(keyEvent)) { Circuit c = editPanelModel.getCircuit(); Project p = controller.getSessionModel().getCurrentProject(); if (p != null) { p.addEditPanelModel(new EditPanelModel(c, new LinkedList<Component>(editPanelModel .getComponentStack()))); } } else if (keyEvent.getKeyCode() == KeyEvent.VK_W && isPlatformCtrlOrBlumenkohlDown(keyEvent)) { Project p = controller.getSessionModel().getCurrentProject(); p.removeEditPanelModel(editPanelModel); } else if (keyEvent.getKeyCode() == KeyEvent.VK_M && isPlatformCtrlOrBlumenkohlDown(keyEvent)) { Settings settings = controller.getSessionModel().getSettings(); if (settings.getBoolean(Settings.MMMode)) { /* Reset environment to normal. */ settings.set(Settings.MMMode, false); controller.getSessionModel().setCurrentCursor(new Cursor(Cursor.DEFAULT_CURSOR)); editPanelModel.setCursor(getToolDefaultCursor()); } else { /* Define mickey mouse environment. */ settings.set(Settings.MMMode, true); controller.getSessionModel().setCurrentCursor(MM_CURSOR); editPanelModel.setCursor(MM_CURSOR); } } else if (keyEvent.getKeyCode() == KeyEvent.VK_I && isPlatformCtrlOrBlumenkohlDown(keyEvent)) { Settings settings = controller.getSessionModel().getSettings(); boolean current = settings.getBoolean(Settings.SHOW_TOOLTIPS); settings.set(Settings.SHOW_TOOLTIPS, !current); } } @Override public void keyReleased(KeyEvent keyEvent, EditPanelModel editPanelModel) { if (keyEvent.getKeyCode() == KeyEvent.VK_SHIFT) { controller.getSessionModel().restoreTool(); // controllGotPressed = false; } else if (keyEvent.getKeyCode() == KeyEvent.VK_SPACE) { spaceReleaseTimeCode = keyEvent.getWhen(); spaceReleaseTimer.restart(); // spaceGotPressed = false; } } /** * Opens the context menu of the {@link EditPanel} at the position of the * event. * * @param event * the mouse event */ protected void openContextMenu(MouseEvent event) { // Makes contextmenu if (event.getComponent() instanceof EditPanel) { EditPanel ep = (EditPanel) event.getComponent(); ep.showContextMenu(event.getX(), event.getY()); } } @Override public void mouseClicked(MouseEvent e, EditPanelModel editPanelModel) { Settings settings = controller.getSessionModel().getSettings(); if (isRightMouseButton(e)) { this.cancelCurrentAction(editPanelModel); // position clicked on in the model Point clickPointInModel = calculateInversePoint(e.getPoint(), editPanelModel.getTransformation()); Circuit c = editPanelModel.getCircuit(); Set<Element> selected = editPanelModel.getSelectedElements(); // brick we clicked on Brick clickedBrick = c.getBrickAtPosition(clickPointInModel); // if we did not click on a brick look for a near wire, else take // the brick Element clickedElement = (clickedBrick == null ? c.getWireAtPosition(clickPointInModel, settings.getInt(Settings.WIRE_SCATTER_FACTOR)) : clickedBrick); // clear selection if clicked on empty space if (clickedElement == null) { selected.clear(); editPanelModel.setSelectedElements(selected); } // single click on an element with right mouse button else { if (!selected.contains(clickedElement)) { if (!(e.isControlDown())) { selected.clear(); } selected.add(clickedElement); } // update selected elements editPanelModel.setSelectedElements(selected); } openContextMenu(e); // middle taste mouse } if (e.getButton() == 2) { CopyActionListener.fillCopyBuffer(controller.getSessionModel()); PasteActionListener.paste(controller, e.getPoint()); } // check for double click on a component to open it in a new tab if (e.getClickCount() == 2) { // position clicked on in the model Point clickPointInModel = calculateInversePoint(e.getPoint(), editPanelModel.getTransformation()); Circuit c = editPanelModel.getCircuit(); // brick we clicked on Brick brick = c.getBrickAtPosition(clickPointInModel); if (brick instanceof Component) { Component component = (Component) brick; ComponentType type = component.getType(); Circuit circuit = type.getCircuit(); Deque<Component> oldStack = editPanelModel.getComponentStack(); Deque<Component> stack = new LinkedList<Component>(oldStack); stack.push(component); EditPanelModel panelModel = new EditPanelModel(circuit, stack); // open new tab controller.getSessionModel().getCurrentProject().addEditPanelModel(panelModel); // select the new tab controller.getSessionModel().getCurrentProject() .setSelectedEditPanelModel(panelModel); } } } @Override public void mousePressed(MouseEvent mouseEvent, EditPanelModel editPanelModel) { editPanelModel.setActiveBrick(null); } @Override public void mouseReleased(MouseEvent mouseEvent, EditPanelModel editPanelModel) { } @Override public void mouseEntered(MouseEvent mouseEvent, EditPanelModel editPanelModel) { } @Override public void mouseExited(MouseEvent mouseEvent, EditPanelModel editPanelModel) { } @Override public void mouseDragged(MouseEvent mouseEvent, EditPanelModel editPanelModel) { } @Override public void mouseMoved(MouseEvent mouseEvent, EditPanelModel editPanelModel) { double zoomLevel = editPanelModel.getTransformation().getScaleX(); if (zoomLevel > ZOOM_BREAKPOINT_ELEMENT_ACTIVE) { Point position = calculateInversePoint(mouseEvent.getPoint(), editPanelModel.getTransformation()); Brick activeBrick = editPanelModel.getCircuit().getBrickAtPosition(position); editPanelModel.setActiveBrick(activeBrick); } else { editPanelModel.setActiveBrick(null); } } @Override public void mouseWheelMoved(MouseWheelEvent mouseWheelEvent, EditPanelModel editPanelModel) { if (mouseWheelEvent.getWheelRotation() < 0 && isPlatformCtrlOrBlumenkohlDown(mouseWheelEvent)) { // center on mouse position Point position = mouseWheelEvent.getPoint(); editPanelModel.setAutoNotify(false); editPanelModel.setCenter(position.x, position.y); editPanelModel.setAutoNotify(true); // actual zoom editPanelModel.zoom(1.1); } else if (isPlatformCtrlOrBlumenkohlDown(mouseWheelEvent)) { // center on mouse position Point position = mouseWheelEvent.getPoint(); editPanelModel.setAutoNotify(false); editPanelModel.setCenter(position.x, position.y); editPanelModel.setAutoNotify(true); // actual zoom editPanelModel.zoom(0.9); }// scrolling vertical else /* if(mouseWheelEvent.getWheelRotation() != 0) */{ int scroll = mouseWheelEvent.getWheelRotation(); double scrollSpeed = controller.getSessionModel().getSettings() .getDouble(Settings.SCROLL_SPEED); int scrollDiff = (int) (scroll * scrollSpeed); Point scrolledPoint = mouseWheelEvent.isAltDown() ? (new Point(scrollDiff, 0)) : (new Point(0, scrollDiff)); AffineTransform transformation = editPanelModel.getTransformation(); Point nullPointInModel = calculateInversePoint(new Point(), transformation); Point scrolledPointInModel = calculateInversePoint(scrolledPoint, transformation); editPanelModel.translate(scrolledPointInModel.x - nullPointInModel.x, scrolledPointInModel.y - nullPointInModel.y); } if (editPanelModel.getToolMode() == EditPanelModel.ToolMode.SELECT_RECT_MODE) { editPanelModel.setSelectionEnd(calculateInversePoint(mouseWheelEvent.getPoint(), editPanelModel.getTransformation())); } } public static boolean isPlatformCtrlOrBlumenkohlDown(InputEvent event) { return (Application.OS == OperatingSystem.OSX ? event.isMetaDown() : event.isControlDown()); } /** * Calculates the inverse point using the given {@link AffineTransform}. * * @param p * point to transform * @param at * the transformation object * @return the inverse transformed point */ public static Point calculateInversePoint(Point p, AffineTransform at) { Point inv = new Point(); try { at.inverseTransform(p, inv); } catch (NoninvertibleTransformException e1) { Log.gl().warn(e1.toString()); } return inv; } /** * Clears the ghosts. * * @param editPanelModel * the editpanelmodel * @return the empty ghost set */ protected Set<Element> clearGhosts(EditPanelModel editPanelModel) { Project project = controller.getSessionModel().getCurrentProject(); Set<Element> ghosts = editPanelModel.getGhosts(); ghosts.clear(); if (project != null) { project.setErrorGhosts(null); } editPanelModel.setGhosts(ghosts); return ghosts; } /** * move a Element so that its leftTop Point will be its center Point. If * GridLock is on move it appropriate * * @param elem * element to move * @param gridlock * true to lock to grif * @return the Element for convenience */ protected Element moveElementForPlacement(Element elem, boolean gridlock) { Point pos = elem.getPosition(); Dimension dim = elem.getDimension(); pos.x -= dim.width / 2; pos.y -= dim.height / 2; elem.setPosition(pos); if (gridlock) { elem.setPosition(lockPointOnGrid(pos)); } return elem; } /** * Creates a new point which is locked on the grid based on the given point. * * @param point * the point to convert to lock position * @return the converted point */ protected Point lockPointOnGrid(Point point) { Point result = new Point(point); int gridSize = controller.getSessionModel().getSettings().getInt(Settings.GRID_SIZE); int deltaX = point.x >= 0 ? point.x % gridSize : gridSize - abs(point.x % gridSize); int deltaY = point.y >= 0 ? point.y % gridSize : gridSize - abs(point.y % gridSize); if (deltaX < gridSize / 2) { result.x -= deltaX; } else { result.x += gridSize - deltaX; } if (deltaY < gridSize / 2) { result.y -= deltaY; } else { result.y += gridSize - deltaY; } return result; } /** * Helper Method to drag the viewport. * * @param e * the mouse event * @param editPanelModel * the EditPanelModel */ protected static void dragViewport(MouseEvent e, EditPanelModel editPanelModel) { if (editPanelModel.getDragStartPoint() != null) { AffineTransform transformation = editPanelModel.getTransformation(); Point dragMousePressedPoint = editPanelModel.getDragStartPoint(); Point dragMouseNewPoint = e.getPoint(); Point transformedNewPoint = calculateInversePoint(dragMouseNewPoint, transformation); Point transformedDragStartPoint = calculateInversePoint(dragMousePressedPoint, transformation); editPanelModel.setDragStartPoint(dragMouseNewPoint); editPanelModel.translate(transformedDragStartPoint.x - transformedNewPoint.x, transformedDragStartPoint.y - transformedNewPoint.y); } } /** * Cancels the current action. * * @param editPanelModel * the panel model */ protected void cancelCurrentAction(EditPanelModel editPanelModel) { } }