package net.sourceforge.fidocadj.circuit.controllers; import java.io.*; import java.util.Vector; import net.sourceforge.fidocadj.circuit.model.*; import net.sourceforge.fidocadj.geom.*; import net.sourceforge.fidocadj.layers.*; import net.sourceforge.fidocadj.primitives.*; import net.sourceforge.fidocadj.graphic.*; /** ElementsEdtActions: contains a controller for adding/modifying elements to a drawing model. In the jargon of this file "editing primitive" means the one which is currently being entered if an editing action is in place. For example, if the user wants to introduce a new macro, it will be the new macro which is shown in green, following the mouse pointer. <pre> This file is part of FidoCadJ. FidoCadJ 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. FidoCadJ 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 FidoCadJ. If not, @see <a href=http://www.gnu.org/licenses/>http://www.gnu.org/licenses/</a>. Copyright 2014-2015 by Davide Bucci </pre> @author Davide Bucci */ public class ElementsEdtActions { protected final DrawingModel dmp; protected final UndoActions ua; protected final EditorActions edt; final SelectionActions sa; final AddElements ae; // The current layer being edited public int currentLayer; // Array used to keep track of the insertion of elements which require // more than one click (logical coordinates). Index begins at 1 to // clickNumber. public int[] xpoly; public int[] ypoly; // used when entering a macro public String macroKey; // Nuber of clicks done when entering an object. public int clickNumber; // The primitive being edited public transient GraphicPrimitive primEdit; // editing action being done public int actionSelected; // Track wether an editing action is being made. public boolean successiveMove; // TO IMPROVE: this must be synchronized with the value in PrimitivePolygon // Maximum number of polygon vertices public static final int NPOLY=256; // Selection states public static final int NONE = 0; public static final int SELECTION = 1; public static final int ZOOM = 2; public static final int HAND = 3; public static final int LINE = 4; public static final int TEXT = 5; public static final int BEZIER = 6; public static final int POLYGON = 7; public static final int ELLIPSE = 8; public static final int RECTANGLE = 9; public static final int CONNECTION = 10; public static final int PCB_LINE = 11; public static final int PCB_PAD = 12; public static final int MACRO = 13; public static final int COMPLEXCURVE = 14; protected PrimitivesParInterface primitivesParListener; /** Standard constructor: provide the database class. @param pp the Model containing the database. @param s the selection controller. @param u the Undo controller, to ease undo operations. @param e the Basic editing controller, for handling selection operations. */ public ElementsEdtActions (DrawingModel pp, SelectionActions s, UndoActions u, EditorActions e) { dmp=pp; ua=u; ae=new AddElements(dmp,ua); edt=e; sa=s; xpoly = new int[NPOLY]; ypoly = new int[NPOLY]; currentLayer=0; primEdit = null; primitivesParListener=null; actionSelected = SELECTION; } /** Sets the action mode. @param a the wanted editing mode. */ public void setActionSelected(int a) { if (a!=actionSelected) clickNumber=0; actionSelected = a; } /** Get the current {@link AddElements} controller. @return the current controller. */ public AddElements getAddElements() { return ae; } /** Set the listener for showing popups and editing actions which are platform-dependent. @param l the listener to be employed. */ public void setPrimitivesParListener(PrimitivesParInterface l) { primitivesParListener=l; } /** Determine wether the current primitive being added is a macro. @return true if the current primitive (i.e. the one who is in green under the mouse cursor) is a macro. */ public boolean isEnteringMacro() { return primEdit instanceof PrimitiveMacro; } /** Chooses the entering state. @param s the new state to be set. @param macro the current macro key if a applicable, which means that a macro is being entered. */ public void setState(int s, String macro) { actionSelected=s; clickNumber=0; successiveMove=false; macroKey=macro; } /** Rotate the macro being edited around its first control point (90 degrees clockwise rotation). */ public void rotateMacro() { if(primEdit instanceof PrimitiveMacro) { primEdit.rotatePrimitive(false, primEdit.getFirstPoint().x, primEdit.getFirstPoint().y); } } /** Mirror the macro being edited around the x coordinate of the first control point. */ public void mirrorMacro() { if(primEdit instanceof PrimitiveMacro) { primEdit.mirrorPrimitive(primEdit.getFirstPoint().x); } } /** Here we analyze and handle the mouse click. The behaviour is different depending on which selection state we are. @param cs the current coordinate mapping @param x the x coordinate of the click (in screen coordinates) @param y the y coordinate of the click (in screen coordinates) @param button3 true if the alternate button has been pressed @param toggle if true, circle the selection state or activate alternate input method (i.e. ellipses are forced to be circles, rectangles squares and so on...) @param doubleClick true if a double click has to be processed @return true if a repaint is needed. */ public boolean handleClick(MapCoordinates cs, int x, int y, boolean button3, boolean toggle, boolean doubleClick) { String cmd; int i; GraphicPrimitive g; boolean repaint=false; if(clickNumber>NPOLY-1) clickNumber=NPOLY-1; //*************** coordinatesListener.changeInfos(""); // We need to differentiate this case since when we are entering a // macro, primEdit already contains some useful hints about the // orientation and the mirroring, so we need to keep it. if (actionSelected !=MACRO) primEdit = null; // Right-click in certain cases shows the parameters dialog. if(button3 && actionSelected!=NONE && actionSelected!=SELECTION && actionSelected!=ZOOM && actionSelected!=TEXT && primitivesParListener!=null) { primitivesParListener.selectAndSetProperties(x,y); return false; } switch(actionSelected) { // No action: ignore case NONE: clickNumber = 0; break; // Selection state case SELECTION: clickNumber = 0; // Double click shows the Parameters dialog. if(doubleClick&&primitivesParListener!=null) { primitivesParListener.setPropertiesForPrimitive(); break; } else if(button3 && primitivesParListener!=null) { // Show a pop up menu if the user does a right-click primitivesParListener.showPopUpMenu(x,y); } else { // Select elements edt.handleSelection(cs, x, y, toggle); } break; // Zoom state case ZOOM: if(primitivesParListener!=null) primitivesParListener.changeZoomByStep(!button3, x,y,1.5); break; // Put a connection (easy: just one click is needed) case CONNECTION: ae.addConnection(cs.unmapXsnap(x),cs.unmapXsnap(y), currentLayer); repaint=true; break; // Put a PCB pad (easy: just one click is needed) case PCB_PAD: // Add a PCB pad primitive at the given point ae.addPCBPad(cs.unmapXsnap(x), cs.unmapYsnap(y), currentLayer); repaint=true; break; // Add a line: two clicks needed case LINE: if (doubleClick) { clickNumber=0; } else { successiveMove=false; clickNumber=ae.addLine(cs.unmapXsnap(x), cs.unmapYsnap(y), xpoly, ypoly, currentLayer, ++clickNumber, button3); repaint=true; } break; // Add a text line: just one click is needed case TEXT: if (doubleClick && primitivesParListener!=null) { primitivesParListener.selectAndSetProperties(x,y); break; } PrimitiveAdvText newtext = new PrimitiveAdvText(cs.unmapXsnap(x), cs.unmapYsnap(y), 3,4,dmp.getTextFont(),0,0, "String", currentLayer); sa.setSelectionAll(false); dmp.addPrimitive(newtext, true, ua); newtext.setSelected(true); repaint=true; if(primitivesParListener!=null) primitivesParListener.setPropertiesForPrimitive(); break; // Add a Bézier polygonal curve: we need four clicks. case BEZIER: repaint=true; if(button3) { clickNumber = 0; } else { if(doubleClick) successiveMove=false; clickNumber=ae.addBezier(cs.unmapXsnap(x), cs.unmapYsnap(y), xpoly, ypoly, currentLayer, ++clickNumber); } break; // Insert a polygon: continue until double click. case POLYGON: // a polygon definition is ended with a double click if (doubleClick) { PrimitivePolygon poly=new PrimitivePolygon(false, currentLayer,0, dmp.getTextFont(), dmp.getTextFontSize()); for(i=1; i<=clickNumber; ++i) poly.addPoint(xpoly[i],ypoly[i]); dmp.addPrimitive(poly, true,ua); clickNumber = 0; repaint=true; break; } else { ++ clickNumber; successiveMove=false; // clickNumber == 0 means that no polygon is being drawn // prevent that we exceed the number of allowed points if (clickNumber==NPOLY) return false; xpoly[clickNumber] = cs.unmapXsnap(x); ypoly[clickNumber] = cs.unmapYsnap(y); } break; // Insert a complex curve: continue until double click. case COMPLEXCURVE: // a polygon definition is ended with a double click if (doubleClick) { PrimitiveComplexCurve compc=new PrimitiveComplexCurve(false, false, currentLayer, false, false, 0, 3, 2, 0, dmp.getTextFont(), dmp.getTextFontSize()); for(i=1; i<=clickNumber; ++i) compc.addPoint(xpoly[i],ypoly[i]); dmp.addPrimitive(compc, true,ua); clickNumber = 0; repaint=true; } else { ++ clickNumber; successiveMove=false; // prevent that we exceed the number of allowed points if (clickNumber==NPOLY) return false; // clickNumber == 0 means that no polygon is being drawn xpoly[clickNumber] = cs.unmapXsnap(x); ypoly[clickNumber] = cs.unmapYsnap(y); } break; // Enter an ellipse: two clicks needed case ELLIPSE: // If control is hold, trace a circle successiveMove=false; clickNumber=ae.addEllipse(cs.unmapXsnap(x), cs.unmapYsnap(y), xpoly, ypoly, currentLayer, ++clickNumber, toggle&&clickNumber>0); repaint=true; break; // Enter a rectangle: two clicks needed case RECTANGLE: // If control is hold, trace a square successiveMove=false; clickNumber=ae.addRectangle(cs.unmapXsnap(x), cs.unmapYsnap(y), xpoly, ypoly, currentLayer, ++clickNumber, toggle&&clickNumber>0); repaint=true; break; // Insert a PCB line: two clicks needed. case PCB_LINE: if (doubleClick) { clickNumber = 0; break; } successiveMove=false; clickNumber = ae.addPCBLine(cs.unmapXsnap(x), cs.unmapYsnap(y), xpoly, ypoly, currentLayer, ++clickNumber, button3, ae.getPCB_thickness()); repaint=true; break; // Enter a macro: just one click is needed. case MACRO: successiveMove=false; primEdit=ae.addMacro(cs.unmapXsnap(x), cs.unmapYsnap(y), sa, primEdit, macroKey); repaint=true; break; default: break; } return repaint; } /** Draws the current editing primitive. @param g the graphic context on which to draw. @param cs the current coordinate mapping system. */ public void drawPrimEdit(GraphicsInterface g, MapCoordinates cs) { int x, y; if(primEdit!=null) { primEdit.draw(g, cs, StandardLayers.createEditingLayerArray()); } } /** Shows the clicks done by the user. @param g the graphic context where one should write. @param cs the current coordinate mapping. */ public void showClicks(GraphicsInterface g, MapCoordinates cs) { int x, y; g.setColor(g.getColor().red()); // The data here begins at index 1, due to the internal construction. int mult=(int)Math.round(g.getScreenDensity()/112); g.applyStroke(2.0f*mult,0); for(int i=1; i<=clickNumber; ++i) { x = cs.mapXi(xpoly[i], ypoly[i], false); y = cs.mapYi(xpoly[i], ypoly[i], false); g.drawLine(x-15*mult, y, x+15*mult, y); g.drawLine(x, y-15*mult, x, y+15*mult); } } /** Get the current editing action (see the constants defined in this class) @return the current editing action. */ public int getSelectionState() { return actionSelected; } /** Set the current editing primitive. @param gp the current editing primitive. */ public void setPrimEdit(GraphicPrimitive gp) { primEdit=gp; } /** Get the current editing primitive. @return the current editing primitive. */ public GraphicPrimitive getPrimEdit() { return primEdit; } }