Java tutorial
/** BlackBoard breadboard designer Written and maintained by Matthias Pueski Copyright (c) 2010-2011 Matthias Pueski 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.pmedv.blackboard.components; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.image.BufferedImage; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.ToolTipManager; import javax.swing.undo.UndoManager; import javax.swing.undo.UndoableEdit; import net.infonode.docking.View; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdesktop.jxlayer.JXLayer; import org.pbjar.jxlayer.plaf.ext.TransformUI; import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel; import org.pmedv.blackboard.BoardUtil; import org.pmedv.blackboard.EditorUtils; import org.pmedv.blackboard.ShapeStyle; import org.pmedv.blackboard.ShapeUtil; import org.pmedv.blackboard.app.EditorMode; import org.pmedv.blackboard.app.FileState; import org.pmedv.blackboard.app.SelectionState; import org.pmedv.blackboard.commands.AddItemCommand; import org.pmedv.blackboard.commands.AddResistorCommand; import org.pmedv.blackboard.commands.AddSymbolToLibraryCommand; import org.pmedv.blackboard.commands.AddTextCommand; import org.pmedv.blackboard.commands.BreakSymbolCommand; import org.pmedv.blackboard.commands.BrowsePartsCommand; import org.pmedv.blackboard.commands.ConvertToPartCommand; import org.pmedv.blackboard.commands.ConvertToSymbolCommand; import org.pmedv.blackboard.commands.CopyCommand; import org.pmedv.blackboard.commands.DeleteCommand; import org.pmedv.blackboard.commands.DuplicateCommand; import org.pmedv.blackboard.commands.EditPropertiesCommand; import org.pmedv.blackboard.commands.ExportImageCommand; import org.pmedv.blackboard.commands.FlipHorizontalCommand; import org.pmedv.blackboard.commands.FlipVerticalCommand; import org.pmedv.blackboard.commands.MoveItemEdit; import org.pmedv.blackboard.commands.MoveLineEdit; import org.pmedv.blackboard.commands.MoveMultipleItemsEdit; import org.pmedv.blackboard.commands.MoveToLayerCommand; import org.pmedv.blackboard.commands.PasteCommand; import org.pmedv.blackboard.commands.RedoCommand; import org.pmedv.blackboard.commands.RotateCCWCommand; import org.pmedv.blackboard.commands.RotateCWCommand; import org.pmedv.blackboard.commands.SaveBoardCommand; import org.pmedv.blackboard.commands.SetColorCommand; import org.pmedv.blackboard.commands.SetDrawModeCommand; import org.pmedv.blackboard.commands.SetSelectModeCommand; import org.pmedv.blackboard.commands.SimulateCircuitCommand; import org.pmedv.blackboard.commands.ToggleGridCommand; import org.pmedv.blackboard.commands.ToggleMirrorCommand; import org.pmedv.blackboard.commands.ToggleSnapToGridCommand; import org.pmedv.blackboard.commands.UndoCommand; import org.pmedv.blackboard.components.WireConnection.WireConnectionType; import org.pmedv.blackboard.events.EditorChangedEvent; import org.pmedv.blackboard.events.EditorChangedEvent.EventType; import org.pmedv.blackboard.events.EditorChangedListener; import org.pmedv.blackboard.models.BoardEditorModel; import org.pmedv.blackboard.models.BoardEditorModel.BoardType; import org.pmedv.blackboard.panels.ShapePropertiesPanel; import org.pmedv.blackboard.provider.SimulatorProvider; import org.pmedv.blackboard.spice.SpiceSimulator; import org.pmedv.blackboard.tools.BoardGen; import org.pmedv.blackboard.tools.ConnectionUtils; import org.pmedv.core.context.AppContext; import org.pmedv.core.gui.ApplicationWindow; import org.pmedv.core.preferences.Preferences; import org.pmedv.core.services.ResourceService; import org.springframework.context.ApplicationContext; /** * <p> * This is the main editor component of blackboard. * <p> * This component was never meant to be so big and complicated. Since * it only started as a small "just for fun" project. * Therefore some parts of this components are a little bit hacky ond obscure at the moment, sorry. * <p> * At the moment we should not extend its functionality anymore and * leave the editor just as it is for version 1.0 and only fix pending bugs if possible. * </p> * <p> * For a later version a big rewrite is planned which should take concern * of the following aspects: * </p> * <p> * <ul> * <li> * The editor should have a real state machine to take track of the current editor * state, like selection behaviour, moving, rotating copying and deleting elements. * </li> * <li> * The keyboard movement of parts does not really work at the moment. * </li> * <li> * Elements should be rotateable freely instead of 90 degree steps. * </li> * <li> * The labels of any element should be moveable in some way. * </li> * <li> * There should be a real print preview and page setup for the selected * board. It should be taken care of the real dimensions of the board * in order to be able to print a well sized board. * </li> * <li> * The mechanism to remember the last position of any component is a big mess * </li> * <li>We should not distinguish between a single selected item and a list of selected * items.</li> * </ul> * </p> * <p> * Maybe if anything of the above has been well rewritten, Blackboard could be extended such * that real PCB layouts are possible (a PSpice integration would be also very nice ;) ). * </p> * * @author Matthias Pueski * */ @SuppressWarnings({ "unused", "unchecked" }) public class BoardEditor extends JPanel implements MouseMotionListener { private JXLayer<?> zoomLayer; private static final long serialVersionUID = -6034450048001248857L; private static final Log log = LogFactory.getLog(BoardEditor.class); // blank background color private static final Color BLANK = new Color(1.0f, 1.0f, 1.0f, 0.0f); // raster size private int raster = 16; // some convenience objects private ApplicationContext ctx = AppContext.getContext(); private ApplicationWindow win = ctx.getBean(ApplicationWindow.class); private ResourceService resources = ctx.getBean(ResourceService.class); private Palette palette = ctx.getBean(Palette.class); // current mode private EditorMode editorMode; // popup for the editor private JPopupMenu popupMenu; // we need an addlinecommand private AddItemCommand addItemCommand; private DeleteCommand deleteCommand; private SaveBoardCommand saveBoardCommand; // is the part being edited? private boolean editPart = false; // is the drag inside any handle? private boolean draggingStartHandle = false; private boolean draggingEndHandle = false; private boolean draggingNorthWestEdge = false; private boolean draggingNorthEastEdge = false; private boolean draggingSouthWestEdge = false; private boolean draggingSouthEastEdge = false; // grid issues boolean snapToGrid = true; boolean gridVisible = false; // are the symbols magnetic? private boolean magnetic = false; // which button is being pressed? private boolean button1Pressed = false; private boolean button2Pressed = false; private boolean button3Pressed = false; // where did the drag of the mouse happen? private int dragStartX; private int dragStartY; // where was the dragging stopped? private int dragStopX; private int dragStopY; // start of the line in case of a paint event private int lineStartX; private int lineStartY; // stop of the line being painted private int lineStopX; private int lineStopY; // where did the context click happen? private int contextClickX = 0; private int contextClickY = 0; // the current selected and moving item private Item currentMovingItem; // the selected item private Item selectedItem; // background of the board private Image backgroundImage; private String backgroundImageName; // did the user press control? protected boolean ctrlPressed = false; // list holding all current selected items private ArrayList<Item> selectedItems = new ArrayList<Item>(); // state of the current selection private SelectionState state; // the state of the current file private FileState fileState; private File currentFile; // the border indicating the current selection range protected Rectangle selectionBorder = new Rectangle(); // the model of the board private BoardEditorModel model; // docking view private View view; private ArrayList<EditorChangedListener> listeners; private final float[] zoomLevels = { 0.1f, 0.25f, 0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f, 4.0f, 6.0f, 8.0f }; private int currentZoomIndex = 4; private final ShapePropertiesPanel shapesPanel = ctx.getBean(ShapePropertiesPanel.class); private final Line currentDrawingLine = new Line(0, 0, 0, 0, 0); private final Font miniFont = new Font("SansSerif", Font.PLAIN, 7); private List<Line> connectedLines = null; private UndoManager undoManager; private Item mouseOverItem = null; private Pin mouseOverPin = null; private Line mouseOverLine = null; private boolean drawing = false; private boolean skip = false; private final StringBuffer tooltipBuffer = new StringBuffer(); private final List<Line> magenticLines = new LinkedList<Line>(); private static final BasicStroke DEFAULT_STROKE = new BasicStroke(1.0f); private Pin selectedPin = null; /** * Creates a new board editor based on an existing model * * @param model */ public BoardEditor(final BoardEditorModel model) { super(null); setOpaque(false); listeners = new ArrayList<EditorChangedListener>(); this.model = model; model.setCurrentLayer(model.getLayer(BoardEditorModel.TOP_LAYER)); if (model.getType() != null && !model.getType().equals(BoardType.SCHEMATICS)) { if (model.getType().equals(BoardType.CUSTOM)) { setBackgroundImage(model.getBackgroundImage()); } else setBackgroundImage( BoardGen.generateBoard(model.getWidth(), model.getHeight(), model.getType(), false)); } // set initial states state = SelectionState.NOTHING_SELECTED; editorMode = EditorMode.SELECT; // init commands saveBoardCommand = ctx.getBean(SaveBoardCommand.class); deleteCommand = ctx.getBean(DeleteCommand.class); addItemCommand = new AddItemCommand(); // setup the editor view Dimension size = new Dimension(model.getWidth(), model.getHeight()); setSize(size); setBounds(new Rectangle(size)); setPreferredSize(size); initListeners(); initActions(); EditorUtils.initToolbar(); hookContextMenu(); updateStatusBar(); setTransferHandler(new BoardEditorSymbolTransferHandler()); EditorUtils.configureDropTarget(this, raster); undoManager = new UndoManager(); win.getViewLabel().setText(resources.getResourceByKey("BoardDesignerPerspective.topView")); addMouseMotionListener(this); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { cancelDrawing(); } }; KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); registerKeyboardAction(actionListener, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); } /** * @return the undoManager */ public UndoManager getUndoManager() { return undoManager; } public void cancelDrawing() { skip = true; handleReleaseDrawEvent(null); lineStopX = 0; lineStopY = 0; refresh(); } /** * Setup the listeners */ private void initListeners() { addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent e) { handleMouseDragged(e); } @Override public void mouseMoved(MouseEvent e) { Point p = e.getPoint(); p = BoardUtil.mirrorTransform(p, zoomLayer, e); if (editorMode.equals(EditorMode.CHECK_CONNECTIONS)) { mouseOverLine = EditorUtils.findMouseOverLine(e, BoardEditor.this); } else { mouseOverPin = EditorUtils.findPin(e.getX(), e.getY(), BoardEditor.this); } win.getCustomLabel().setText("x : " + e.getX() + " y : " + e.getY()); if (mouseOverPin != null) { tooltipBuffer.delete(0, tooltipBuffer.length()); tooltipBuffer.append(mouseOverPin.getNum()); if (mouseOverPin.getName() != null) { tooltipBuffer.append(" " + mouseOverPin.getName()); } BoardEditor.this.setToolTipText(tooltipBuffer.toString()); ToolTipManager.sharedInstance() .mouseMoved(new MouseEvent(BoardEditor.this, 0, 0, 0, (int) p.getX(), (int) p.getY(), // X-Y of the mouse for the tool tip 0, false)); } else if (mouseOverLine != null) { tooltipBuffer.delete(0, tooltipBuffer.length()); tooltipBuffer.append("net : " + mouseOverLine.getNetIndex()); BoardEditor.this.setToolTipText(tooltipBuffer.toString()); ToolTipManager.sharedInstance() .mouseMoved(new MouseEvent(BoardEditor.this, 0, 0, 0, (int) p.getX(), (int) p.getY(), // X-Y of the mouse for the tool tip 0, false)); } else { BoardEditor.this.setToolTipText(null); } } }); addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { handleMousePressed(e); } @Override public void mouseReleased(MouseEvent e) { handleMouseReleased(e); } }); addMouseWheelListener(new MouseWheelListener() { @Override public void mouseWheelMoved(MouseWheelEvent e) { Boolean invertMouse = (Boolean) Preferences.values .get("org.pmedv.blackboard.BoardDesignerPerspective.invertMouse"); if (invertMouse) { if (currentZoomIndex - e.getWheelRotation() < zoomLevels.length && currentZoomIndex - e.getWheelRotation() > 0) { currentZoomIndex -= e.getWheelRotation(); } } else { if (currentZoomIndex + e.getWheelRotation() < zoomLevels.length && currentZoomIndex + e.getWheelRotation() > 0) { currentZoomIndex += e.getWheelRotation(); } } JXLayer<?> layer = getZoomLayer(); TransformUI ui = (TransformUI) (Object) layer.getUI(); DefaultTransformModel model = (DefaultTransformModel) ui.getModel(); model.setScale(zoomLevels[currentZoomIndex]); ctx.getBean(ApplicationWindow.class).getZoomCombo().setSelectedItem(zoomLevels[currentZoomIndex]); } }); addMouseMotionListener(AppContext.getContext().getBean(AddTextCommand.class)); } /** * Wire needed actions with the keyboard */ @SuppressWarnings("serial") private void initActions() { // keyboard associations ApplicationWindow window = AppContext.getBean(ApplicationWindow.class); // // window.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "upAction"); // window.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "downAction"); // window.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "leftAction"); // window.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "rightAction"); // // window.getRootPane().getActionMap().put("upAction", new AbstractAction() { // public void actionPerformed(ActionEvent event) { // moveItemByKey("up"); // } // }); // window.getRootPane().getActionMap().put("downAction", new AbstractAction() { // public void actionPerformed(ActionEvent event) { // moveItemByKey("down"); // } // }); // window.getRootPane().getActionMap().put("leftAction", new AbstractAction() { // public void actionPerformed(ActionEvent event) { // moveItemByKey("left"); // } // }); // window.getRootPane().getActionMap().put("rightAction", new AbstractAction() { // public void actionPerformed(ActionEvent event) { // moveItemByKey("right"); // } // }); registerKeyboardAction(new ActionListener() { public void actionPerformed(ActionEvent event) { moveItemByKey("up"); } }, KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW); registerKeyboardAction(new ActionListener() { public void actionPerformed(ActionEvent event) { moveItemByKey("down"); } }, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW); registerKeyboardAction(new ActionListener() { public void actionPerformed(ActionEvent event) { moveItemByKey("left"); } }, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW); registerKeyboardAction(new ActionListener() { public void actionPerformed(ActionEvent event) { moveItemByKey("right"); } }, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW); } private void moveItemByKey(String direction) { if (selectedItem != null) { updateStatusBar(); } if (selectedItems.size() > 0) { for (Item i : selectedItems) { } updateStatusBar(); } } private void handleMouseDragged(MouseEvent e) { if (editorMode.equals(EditorMode.SELECT) || editorMode.equals(EditorMode.MOVE)) handleDragEvent(e); else if (editorMode.equals(EditorMode.DRAW_LINE) || editorMode.equals(EditorMode.DRAW_MEASURE) || editorMode.equals(EditorMode.DRAW_ELLIPSE) || editorMode.equals(EditorMode.DRAW_RECTANGLE)) { handleDrawEvent(e); } refresh(); } private void handleMousePressed(MouseEvent e) { if (e.isPopupTrigger()) { handleContextClick(e); } if (e.isControlDown()) { ctrlPressed = true; } else { ctrlPressed = false; } if (e.getButton() == 1) { if (!button1Pressed) { button1Pressed = true; if (editorMode.equals(EditorMode.SELECT)) { handleClickSelectionEvent(e); } else if (editorMode.equals(EditorMode.MOVE)) { dragStartX = e.getX(); dragStartY = e.getY(); dragStopX = e.getX(); dragStopY = e.getY(); } else if (editorMode.equals(EditorMode.DRAW_LINE) || editorMode.equals(EditorMode.DRAW_MEASURE) || editorMode.equals(EditorMode.DRAW_RECTANGLE) || editorMode.equals(EditorMode.DRAW_ELLIPSE)) { if (!model.getCurrentLayer().getName().equalsIgnoreCase("Board")) { if (!drawing) { handleClickDrawEvent(e); } else { handleReleaseDrawEvent(e); } } } else if (editorMode.equals(EditorMode.CHECK_CONNECTIONS)) { handleCheckConnectionEvent(e); } } } else if (e.getButton() == 2) { button2Pressed = true; } else if (e.getButton() == 3) { button3Pressed = true; } if (e.getClickCount() == 2 && e.isShiftDown()) { if (selectedItem != null || getSelectedItems().size() > 0) { AppContext.getContext().getBean(EditPropertiesCommand.class).execute(null); } } refresh(); } /** * <p> * Connection check mode : If any click occurs in this mode, check if * the click happened on any line. If that happened, determine all lines that * are connected with that line and add them to the list off connected lines. * </p> * <p> * If the list of connected lines contains any line while the board is being painted, * they are painted in some bright manner in order to view them easily. * * @param e */ private void handleCheckConnectionEvent(MouseEvent e) { LinkedList<Line> lines = new LinkedList<Line>(); Line selectedLine = null; // First collect all lines. for (Layer layer : model.getLayers()) { for (Item item : layer.getItems()) { if (item instanceof Line) { Line line = (Line) item; lines.add(line); } } } // Then check which line has been selected for (Layer layer : model.getLayers()) { for (Item item : layer.getItems()) { if (item instanceof Line) { Line line = (Line) item; if (line.containsPoint(e.getX(), e.getY())) { selectedLine = line; } } } } // And finally determine the connections to that line if (selectedLine != null && lines.size() > 1) { connectedLines = ConnectionUtils.getConnectedLines(selectedLine, lines); selectedItems.clear(); // for convenience, we select all connected lines selectedItems.addAll(connectedLines); updateEditCommands(); } if (selectedLine == null) { connectedLines.clear(); } } private void handleMouseReleased(MouseEvent e) { ArrayList<UndoableEdit> edits = new ArrayList<UndoableEdit>(); for (Item item : selectedItems) { Line currentLine = null; if (item instanceof Line) { currentLine = (Line) item; } // just add edit if item or line has been moved if (item.getOldXLoc() > 0 || (currentLine != null && !currentLine.getOldstart().equals(currentLine.getStart()))) { if (item instanceof Line) { Line l = (Line) item; MoveLineEdit ml = new MoveLineEdit(l, l.getOldstart().x, l.getOldstart().y, l.getStart().x, l.getStart().y, l.getOldEnd().x, l.getOldEnd().y, l.getEnd().x, l.getEnd().y, true); edits.add(ml); for (WireConnection wc : l.getWireConnections()) { Line l1 = wc.getLine(); ml = new MoveLineEdit(l1, l1.getOldstart().x, l1.getOldstart().y, l1.getStart().x, l1.getStart().y, l1.getOldEnd().x, l1.getOldEnd().y, l1.getEnd().x, l1.getEnd().y, true); edits.add(ml); l1.setOldstart(new Point(l1.getStart())); l1.setOldEnd(new Point(l1.getEnd())); } } else { MoveItemEdit me = new MoveItemEdit(item, item.getOldXLoc(), item.getOldYLoc(), item.getXLoc(), item.getYLoc(), true); if (item instanceof Part) { Part part = (Part) item; for (WireConnection wc : part.getWireConnections()) { Line l1 = wc.getLine(); MoveLineEdit m = new MoveLineEdit(l1, l1.getOldstart().x, l1.getOldstart().y, l1.getStart().x, l1.getStart().y, l1.getOldEnd().x, l1.getOldEnd().y, l1.getEnd().x, l1.getEnd().y, true); edits.add(m); l1.setOldstart(new Point(l1.getStart())); l1.setOldEnd(new Point(l1.getEnd())); } edits.add(me); } } setFileState(FileState.DIRTY); } } if (edits.size() > 0) { MoveMultipleItemsEdit mme = new MoveMultipleItemsEdit(edits); if (!undoManager.addEdit(mme)) { log.error("could not add edit to undo manager"); } else { log.info("added edit : " + this.getClass()); } } if (editorMode.equals(EditorMode.SELECT)) { if (selectedItem != null) state = SelectionState.SINGLE_ITEM_SELECTED; else state = SelectionState.NOTHING_SELECTED; } if (e.isPopupTrigger()) { handleContextClick(e); } if (e.getButton() == 1) { button1Pressed = false; if (editorMode.equals(EditorMode.SELECT)) { // handleClickSelectionEvent(e); handleReleaseSelectionEvent(e, edits); } } else if (e.getButton() == 2) { button2Pressed = false; } else if (e.getButton() == 3) { button3Pressed = false; } refresh(); } private void handleDrawEvent(MouseEvent e) { lineStopX = e.getX(); lineStopY = e.getY(); if (snapToGrid) { lineStopX = BoardUtil.snap(lineStopX, raster); lineStopY = BoardUtil.snap(lineStopY, raster); } selectedPin = EditorUtils.findPin(lineStopX, lineStopY, this); } private void handleDragEvent(MouseEvent e) { int deltaX = e.getX() - dragStartX; int deltaY = e.getY() - dragStartY; if (snapToGrid) { deltaX = BoardUtil.snap(deltaX, raster); deltaY = BoardUtil.snap(deltaY, raster); } if (currentMovingItem != null) { // if (editorMode.equals(EditorMode.MOVE)) { setFileState(FileState.DIRTY); if (currentMovingItem instanceof Line && currentMovingItem == selectedItem) { Line l = (Line) currentMovingItem; moveLine(deltaX, deltaY, l, false); setFileState(FileState.DIRTY); } else if ((currentMovingItem instanceof Part || currentMovingItem instanceof Box || currentMovingItem instanceof Ellipse) && currentMovingItem == selectedItem) { moveOrResizeItem(deltaX, deltaY, currentMovingItem); setFileState(FileState.DIRTY); } //} } else { if (e.getX() <= model.getWidth()) dragStopX = e.getX(); else dragStopX = model.getWidth() - 1; if (e.getY() <= model.getHeight()) dragStopY = e.getY(); else dragStopY = model.getHeight() - 1; if (state.equals(SelectionState.DRAGGING_NEW_SELECTION)) { for (Layer layer : model.getLayers()) { if (!layer.isVisible() || layer.isLocked()) { continue; } for (Item item : layer.getItems()) { if (ShapeUtil.collides(selectionBorder, item.getBoundingBox())) { if (!selectedItems.contains(item)) selectedItems.add(item); } else { if (selectedItems.contains(item)) selectedItems.remove(item); } } } } // one or many items selected, move them else { if (selectedItems.size() > 1) { // if (editorMode.equals(EditorMode.MOVE)) { for (Item item : selectedItems) { if (item instanceof Line) { Line l = (Line) item; moveLine(deltaX, deltaY, l, false); } else { moveOrResizeItem(deltaX, deltaY, item); } setFileState(FileState.DIRTY); } // } } else { getCurrentMovingItem(e); } } } updateEditCommands(); } /** * Determines the item currently being moved, this is in any case * the same as the selected item, if theres no selected item, we * do not have a moving item. * * @param e */ private void getCurrentMovingItem(MouseEvent e) { for (Layer layer : model.getLayers()) { for (Item item : layer.getItems()) { if (item instanceof Line) { Line line = (Line) item; if (line.containsPoint(e.getX(), e.getY()) || line.isInsideEndHandle(e.getX(), e.getY()) || line.isInsideStartHandle(e.getX(), e.getY())) { if (button1Pressed) { if (item == selectedItem) { currentMovingItem = item; break; } } } } else { if (item.isInside(e.getX(), e.getY()) || item.isInsideNorthEastHandle(e.getX(), e.getY()) || item.isInsideNorthWestHandle(e.getX(), e.getY()) || item.isInsideSouthEastHandle(e.getX(), e.getY()) || item.isInsideSouthWestHandle(e.getX(), e.getY())) { if (button1Pressed) { if (item == selectedItem) { currentMovingItem = item; break; } } } } } if (currentMovingItem != null) { break; } } } private void moveLine(int deltaX, int deltaY, Line line, boolean subItem) { int newLineStartX = (int) line.getOldstart().getX() + deltaX; int newLineStartY = (int) line.getOldstart().getY() + deltaY; int newLineStopX = (int) line.getOldEnd().getX() + deltaX; int newLineStopY = (int) line.getOldEnd().getY() + deltaY; // prevent subitems from being distorted and line elements resized if multiple lines are selected if (draggingStartHandle && !subItem && selectedItems.size() == 0) { if (magnetic) { for (WireConnection wc : line.getWireConnections()) { Line otherLine = wc.getLine(); int _newLineStartX = (int) otherLine.getOldstart().getX() + deltaX; int _newLineStartY = (int) otherLine.getOldstart().getY() + deltaY; int _newLineStopX = (int) otherLine.getOldEnd().getX() + deltaX; int _newLineStopY = (int) otherLine.getOldEnd().getY() + deltaY; if (wc.getType().equals(WireConnectionType.START_START)) { otherLine.getStart().setLocation(_newLineStartX, _newLineStartY); } else if (wc.getType().equals(WireConnectionType.END_START)) { otherLine.getEnd().setLocation(_newLineStopX, _newLineStopY); } } } line.getStart().setLocation(newLineStartX, newLineStartY); } else if (draggingEndHandle && !subItem && selectedItems.size() == 0) { if (magnetic) { for (WireConnection wc : line.getWireConnections()) { Line otherLine = wc.getLine(); int _newLineStartX = (int) otherLine.getOldstart().getX() + deltaX; int _newLineStartY = (int) otherLine.getOldstart().getY() + deltaY; int _newLineStopX = (int) otherLine.getOldEnd().getX() + deltaX; int _newLineStopY = (int) otherLine.getOldEnd().getY() + deltaY; if (wc.getType().equals(WireConnectionType.START_END)) { otherLine.getStart().setLocation(_newLineStartX, _newLineStartY); } else if (wc.getType().equals(WireConnectionType.END_END)) { otherLine.getEnd().setLocation(_newLineStopX, _newLineStopY); } } } line.getEnd().setLocation(newLineStopX, newLineStopY); } else { if (magnetic) { for (WireConnection wc : line.getWireConnections()) { Line otherLine = wc.getLine(); int _newLineStartX = (int) otherLine.getOldstart().getX() + deltaX; int _newLineStartY = (int) otherLine.getOldstart().getY() + deltaY; int _newLineStopX = (int) otherLine.getOldEnd().getX() + deltaX; int _newLineStopY = (int) otherLine.getOldEnd().getY() + deltaY; if (wc.getType().equals(WireConnectionType.START_START)) { otherLine.getStart().setLocation(_newLineStartX, _newLineStartY); } else if (wc.getType().equals(WireConnectionType.START_END)) { otherLine.getStart().setLocation(_newLineStartX, _newLineStartY); } else if (wc.getType().equals(WireConnectionType.END_END)) { otherLine.getEnd().setLocation(_newLineStopX, _newLineStopY); } else if (wc.getType().equals(WireConnectionType.END_START)) { otherLine.getEnd().setLocation(_newLineStopX, _newLineStopY); } } } line.getStart().setLocation(newLineStartX, newLineStartY); line.getEnd().setLocation(newLineStopX, newLineStopY); } } /** * Moves or resizes an {@link Item} based on given delta * * @param deltaX the delta in x-direction * @param deltaY the delta in y-direction * * @param item the item to be moved or resized */ private void moveOrResizeItem(int deltaX, int deltaY, Item item) { int xLoc = item.getOldXLoc() + deltaX; int yLoc = item.getOldYLoc() + deltaY; int width = item.getOldWidth() + deltaX; int height = item.getOldHeight() + deltaY; if (width <= 8) width = 8; if (height <= 8) height = 8; if (draggingNorthWestEdge && item.isResizable()) { item.setWidth(item.getOldWidth() - deltaX); item.setHeight(item.getOldHeight() - deltaY); item.setYLoc(yLoc); item.setXLoc(xLoc); } else if (draggingNorthEastEdge && item.isResizable()) { item.setWidth(width); item.setHeight(item.getOldHeight() - deltaY); item.setYLoc(yLoc); } else if (draggingSouthWestEdge && item.isResizable()) { item.setHeight(height); item.setWidth(item.getOldWidth() - deltaX); item.setXLoc(xLoc); } else if (draggingSouthEastEdge && item.isResizable()) { item.setWidth(width); item.setHeight(height); } else { item.setXLoc(xLoc); item.setYLoc(yLoc); if (item instanceof Part) { Part part = (Part) item; if (magnetic) { for (WireConnection wc : part.getWireConnections()) { Line otherLine = wc.getLine(); int newLineStartX = (int) otherLine.getOldstart().getX() + deltaX; int newLineStartY = (int) otherLine.getOldstart().getY() + deltaY; int newLineStopX = (int) otherLine.getOldEnd().getX() + deltaX; int newLineStopY = (int) otherLine.getOldEnd().getY() + deltaY; if (wc.getType().equals(WireConnectionType.START)) { otherLine.getStart().setLocation(newLineStartX, newLineStartY); } else { otherLine.getEnd().setLocation(newLineStopX, newLineStopY); } } } } if (item instanceof Symbol) { Symbol symbol = (Symbol) item; for (Item subItem : symbol.getItems()) { if (subItem instanceof Line) { Line line = (Line) subItem; moveLine(deltaX, deltaY, line, true); } else { // restore position xLoc = subItem.getOldXLoc() + deltaX; yLoc = subItem.getOldYLoc() + deltaY; subItem.setXLoc(xLoc); subItem.setYLoc(yLoc); } } } } } /** * Click happened, change state of selected item or select another one * * @param e */ private void handleClickSelectionEvent(MouseEvent e) { if (ctrlPressed) { if (selectedItem != null) { selectedItems.add(selectedItem); selectedItem = null; } Item selection = selectItem(e); if (selection != null) { selectedItems.add(selection); } refresh(); } else { if (selectedItem != null) { // only one item selected changeSelectedItemState(e); } else { Item selection = null; selection = selectItem(e); if (selection != null) { if (!selectedItems.contains(selection)) { selectedItems.clear(); selectedItem = selection; changeSelectedItemState(e); } } if (selection == null) { selectedItems.clear(); selectedItem = null; clearSelectionBorder(); state = SelectionState.DRAGGING_NEW_SELECTION; } } } if (selectedItem == null && selectedItems.size() == 0) { clearSelectionBorder(); } updateRelatedUI(); dragStartX = e.getX(); dragStartY = e.getY(); dragStopX = e.getX(); dragStopY = e.getY(); updateEditCommands(); } /** * Change the state of selected items based on an already selected item. * If the click happens on the same item the state might be changed, otherwise * another item is selected or the selection is cleared, if no item was hit. * * @param e */ private void changeSelectedItemState(MouseEvent e) { if (selectedItem instanceof Line) { Line line = (Line) selectedItem; if (line.isInsideStartHandle(e.getX(), e.getY())) { draggingStartHandle = true; draggingEndHandle = false; } else if (line.isInsideEndHandle(e.getX(), e.getY())) { draggingStartHandle = false; draggingEndHandle = true; } // Click did not happen on same line, select another else { draggingStartHandle = false; draggingEndHandle = false; selectedItem = selectItem(e); refresh(); } line.setOldstart(new Point(line.getStart())); line.setOldEnd(new Point(line.getEnd())); } else if (selectedItem instanceof Part || selectedItem instanceof Box || selectedItem instanceof Ellipse || selectedItem instanceof Symbol) { draggingNorthWestEdge = selectedItem.isInsideNorthWestHandle(e.getX(), e.getY()); draggingNorthEastEdge = selectedItem.isInsideNorthEastHandle(e.getX(), e.getY()); draggingSouthWestEdge = selectedItem.isInsideSouthWestHandle(e.getX(), e.getY()); draggingSouthEastEdge = selectedItem.isInsideSouthEastHandle(e.getX(), e.getY()); selectedItem = selectItem(e); refresh(); BoardUtil.syncItemState(model); } } private Item selectItem(MouseEvent e) { Item currentItem = null; log.info("Select item."); ArrayList<Item> possibleSelectedItems = new ArrayList<Item>(); // first collect all items below clicked point for (Layer layer : model.getLayers()) { if (!layer.isVisible() || layer.isLocked()) continue; for (Item item : layer.getItems()) { if (item instanceof Line) { Line line = (Line) item; if (line.containsPoint(e.getX(), e.getY())) { possibleSelectedItems.add(line); } } else { if (item.isInside(e.getX(), e.getY()) || item.isInsideNorthEastHandle(e.getX(), e.getY()) || item.isInsideNorthWestHandle(e.getX(), e.getY()) || item.isInsideSouthEastHandle(e.getX(), e.getY()) || item.isInsideSouthWestHandle(e.getX(), e.getY())) { possibleSelectedItems.add(item); } } } } // No item has been hit if (possibleSelectedItems.isEmpty()) { return null; } Collections.sort(possibleSelectedItems); Collections.reverse(possibleSelectedItems); // find item with max z-order int z = Integer.MIN_VALUE; // already an item selected, select the next one below // if the selected one is the at the bottom, select the topmost one if (selectedItem != null) { for (Item item : possibleSelectedItems) { if (item.getIndex() < selectedItem.getIndex()) { currentItem = item; break; } } // there was no item below, find the topmost one if (currentItem == null) { for (Item item : possibleSelectedItems) { if (item.getIndex() > z) { currentItem = item; break; } } } } // no item selected, find the one which is on top else { for (Item item : possibleSelectedItems) { if (item.getIndex() > z) { currentItem = item; } } } if (currentItem instanceof Line) { Line line = (Line) currentItem; // click happened on line line.setOldstart(new Point(line.getStart())); line.setOldEnd(new Point(line.getEnd())); } else if (currentItem instanceof Part) { currentItem.setOldXLoc(currentItem.getXLoc()); currentItem.setOldYLoc(currentItem.getYLoc()); if (currentItem instanceof Symbol) { Symbol symbol = (Symbol) currentItem; for (Item subItem : symbol.getItems()) { // restore position if (subItem instanceof Line) { Line line = (Line) subItem; line.setOldstart(new Point(line.getStart())); line.setOldEnd(new Point(line.getEnd())); } else { subItem.setOldXLoc(subItem.getXLoc()); subItem.setOldYLoc(subItem.getYLoc()); subItem.setOldWidth(subItem.getWidth()); subItem.setOldHeight(subItem.getHeight()); } } } } if (magnetic) { if (currentItem != null) { if (currentItem instanceof Line) { Line line = (Line) currentItem; BoardUtil.findConnectionsToLine(line, model); } else { BoardUtil.findConnectionsToItem(currentItem, model); } } } return currentItem; } /** * Updates the UI element state based on the selected item */ private void updateRelatedUI() { if (selectedItem == null) { ctx.getBean(ShapePropertiesPanel.class).getObjectField() .setText(resources.getResourceByKey("ShapePropertiesPanel.items.none")); ctx.getBean(ShapePropertiesPanel.class).getRotationSpinner().setEnabled(false); ctx.getBean(ShapePropertiesPanel.class).getStartAngleSpinner().setEnabled(false); } else { ctx.getBean(ShapePropertiesPanel.class).getObjectField() .setText(selectedItem.getClass().getSimpleName()); if (selectedItem instanceof Ellipse) { Ellipse el = (Ellipse) selectedItem; ctx.getBean(ShapePropertiesPanel.class).getRotationSpinner().setEnabled(true); ctx.getBean(ShapePropertiesPanel.class).getStartAngleSpinner().setEnabled(true); ctx.getBean(ShapePropertiesPanel.class).getRotationSpinner() .setValue(Integer.valueOf(el.getRotation())); ctx.getBean(ShapePropertiesPanel.class).getStartAngleSpinner() .setValue(Integer.valueOf(el.getStartAngle())); } else { ctx.getBean(ShapePropertiesPanel.class).getRotationSpinner().setEnabled(false); ctx.getBean(ShapePropertiesPanel.class).getStartAngleSpinner().setEnabled(false); } if (selectedItem instanceof Shape) { Shape shape = (Shape) selectedItem; ctx.getBean(ShapePropertiesPanel.class).getStyleCombo().setSelectedItem(shape.getStyle()); ctx.getBean(ShapePropertiesPanel.class).getThicknessCombo().setSelectedItem(shape.getStroke()); } if (selectedItem instanceof Line) { Line line = (Line) selectedItem; ctx.getBean(ShapePropertiesPanel.class).getStartLineCombo().setSelectedItem(line.getStartType()); ctx.getBean(ShapePropertiesPanel.class).getEndLineCombo().setSelectedItem(line.getEndType()); } } } public void clearSelectionBorder() { selectionBorder.setLocation(0, 0); selectionBorder.setSize(0, 0); } private void handleReleaseSelectionEvent(MouseEvent e, ArrayList<UndoableEdit> edits) { if (currentMovingItem != null) { if (currentMovingItem instanceof Line) { Line line = (Line) currentMovingItem; MoveLineEdit ml = new MoveLineEdit(line, line.getOldstart().x, line.getOldstart().y, line.getStart().x, line.getStart().y, line.getOldEnd().x, line.getOldEnd().y, line.getEnd().x, line.getEnd().y, false); for (WireConnection wc : line.getWireConnections()) { Line l1 = wc.getLine(); MoveLineEdit m = new MoveLineEdit(l1, l1.getOldstart().x, l1.getOldstart().y, l1.getStart().x, l1.getStart().y, l1.getOldEnd().x, l1.getOldEnd().y, l1.getEnd().x, l1.getEnd().y, true); edits.add(m); l1.setOldstart(new Point(l1.getStart())); l1.setOldEnd(new Point(l1.getEnd())); } if (edits.size() > 0) { edits.add(ml); MoveMultipleItemsEdit mme = new MoveMultipleItemsEdit(edits); if (!undoManager.addEdit(mme)) { log.error("could not add edit to undo manager"); } else { log.info("added edit : " + this.getClass()); } } else { if (!undoManager.addEdit(ml)) { log.error("could not add edit to undo manager"); } else { log.info("added edit : " + this.getClass()); } } ctx.getBean(RedoCommand.class).setEnabled(undoManager.canRedo()); ctx.getBean(UndoCommand.class).setEnabled(undoManager.canUndo()); line.setOldstart(new Point(line.getStart())); line.setOldEnd(new Point(line.getEnd())); } else { MoveItemEdit me = new MoveItemEdit(currentMovingItem, currentMovingItem.getOldXLoc(), currentMovingItem.getOldYLoc(), currentMovingItem.getXLoc(), currentMovingItem.getYLoc(), false); if (currentMovingItem instanceof Part) { Part part = (Part) currentMovingItem; for (WireConnection wc : part.getWireConnections()) { Line l1 = wc.getLine(); MoveLineEdit m = new MoveLineEdit(l1, l1.getOldstart().x, l1.getOldstart().y, l1.getStart().x, l1.getStart().y, l1.getOldEnd().x, l1.getOldEnd().y, l1.getEnd().x, l1.getEnd().y, true); edits.add(m); l1.setOldstart(new Point(l1.getStart())); l1.setOldEnd(new Point(l1.getEnd())); } if (edits.size() > 0) { edits.add(me); MoveMultipleItemsEdit mme = new MoveMultipleItemsEdit(edits); if (!undoManager.addEdit(mme)) { log.error("could not add edit to undo manager"); } else { log.info("added edit : " + this.getClass()); } } else { if (!undoManager.addEdit(me)) { log.error("could not add edit to undo manager"); } else { log.info("added edit : " + this.getClass()); } } } ctx.getBean(RedoCommand.class).setEnabled(undoManager.canRedo()); ctx.getBean(UndoCommand.class).setEnabled(undoManager.canUndo()); } currentMovingItem = null; } else if (selectedItems.size() > 0) { BoardUtil.syncItemState(selectedItems); ctx.getBean(ShapePropertiesPanel.class).getObjectField() .setText(selectedItems.size() + " " + resources.getResourceByKey("ShapePropertiesPanel.items")); if (edits.size() > 0) { MoveMultipleItemsEdit mme = new MoveMultipleItemsEdit(edits); if (!undoManager.addEdit(mme)) { log.error("could not add edit to undo manager"); } else { log.info("added edit : " + this.getClass()); } ctx.getBean(RedoCommand.class).setEnabled(undoManager.canRedo()); ctx.getBean(UndoCommand.class).setEnabled(undoManager.canUndo()); } } BoardUtil.syncItemState(model); } private void handleClickDrawEvent(MouseEvent e) { if (e == null) return; drawing = true; // if we are drawing a continous line, make sure the // next line segment starts at the previous segments end Boolean drawContinuous = (Boolean) Preferences.values .get("org.pmedv.blackboard.BoardDesignerPerspective.dawContinuousLines"); if (drawContinuous && lineStopX > 0 && lineStopY > 0) { lineStartX = lineStopX; lineStartY = lineStopY; return; } // start of a new line, mouse coordinates are starting point else { lineStartX = e.getX(); lineStartY = e.getY(); } // snap the point to the grid if (snapToGrid) { lineStartX = BoardUtil.snap(lineStartX, raster); lineStartY = BoardUtil.snap(lineStartY, raster); } } private void handleReleaseDrawEvent(MouseEvent e) { drawing = false; if (!skip) { if (lineStartX > 0 && lineStartY > 0 && lineStopX > 0 && lineStopY > 0) { if (editorMode.equals(EditorMode.DRAW_LINE)) { Line line = new Line(lineStartX, lineStartY, lineStopX, lineStopY, model.getCurrentLayer().getItems().size() - 1); line.setOldstart(new Point(line.getStart())); line.setOldEnd(new Point(line.getEnd())); addItemCommand.setItem(line); addItemCommand.execute(null); } else if (editorMode.equals(EditorMode.DRAW_MEASURE)) { Measure measure = new Measure(lineStartX, lineStartY, lineStopX, lineStopY, model.getCurrentLayer().getItems().size() - 1); measure.setOldstart(new Point(measure.getStart())); measure.setOldEnd(new Point(measure.getEnd())); addItemCommand.setItem(measure); addItemCommand.execute(null); } else if (editorMode.equals(EditorMode.DRAW_RECTANGLE) || editorMode.equals(EditorMode.DRAW_ELLIPSE)) { Item newItem; if (editorMode.equals(EditorMode.DRAW_RECTANGLE)) { newItem = new Box(); } else { newItem = new Ellipse(); } newItem.setXLoc(lineStartX); newItem.setYLoc(lineStartY); newItem.setOldXLoc(newItem.getXLoc()); newItem.setOldYLoc(newItem.getYLoc()); newItem.setHeight(lineStopY - lineStartY); newItem.setWidth(lineStopX - lineStartX); newItem.setOldWidth(newItem.getWidth()); newItem.setOldHeight(newItem.getHeight()); Boolean useLayerColor = (Boolean) Preferences.values .get("org.pmedv.blackboard.BoardDesignerPerspective.useLayerColor"); if (useLayerColor) newItem.setColor(model.getCurrentLayer().getColor()); else newItem.setColor(palette.getCurrentColor()); addItemCommand.setItem(newItem); addItemCommand.execute(null); } } } skip = false; lineStartX = 0; lineStartY = 0; Boolean drawContinuous = (Boolean) Preferences.values .get("org.pmedv.blackboard.BoardDesignerPerspective.dawContinuousLines"); if (!drawContinuous) { lineStopX = 0; lineStopY = 0; } // draw continuously if desired if (drawContinuous && selectedPin == null && editorMode.equals(EditorMode.DRAW_LINE)) { handleClickDrawEvent(e); } else { lineStopX = 0; lineStopY = 0; lineStartX = 0; lineStartY = 0; } } @Override protected void paintComponent(Graphics g) { Boolean useLayerColor = (Boolean) Preferences.values .get("org.pmedv.blackboard.BoardDesignerPerspective.useLayerColor"); // clear all // g.clearRect(0, 0, getWidth(), getHeight()); g.setColor(BLANK); g.fillRect(0, 0, getWidth(), getHeight()); // some nice anti aliasing... Graphics2D g2 = (Graphics2D) g; RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.setRenderingHints(rh); // TODO : Should this be done here? // Sort layers by z-index g2.setColor(Color.LIGHT_GRAY); g2.setStroke(BoardUtil.stroke_1_0f); Collections.sort(model.getLayers()); for (int i = model.getLayers().size() - 1; i >= 0; i--) { // Sort items by z-index Collections.sort(model.getLayers().get(i).getItems()); drawLayer(model.getLayers().get(i), g2); } // draw selection border if (state.equals(SelectionState.DRAGGING_NEW_SELECTION) && button1Pressed) { g2.setColor(Color.GREEN); g2.setStroke(BoardUtil.stroke_1_0f); if (dragStopX < dragStartX && dragStopY > dragStartY) { selectionBorder.setSize(dragStartX - dragStopX, dragStopY - dragStartY); selectionBorder.setLocation(dragStopX, dragStartY); } else if (dragStopY < dragStartY && dragStopX > dragStartX) { selectionBorder.setSize(dragStopX - dragStartX, dragStartY - dragStopY); selectionBorder.setLocation(dragStartX, dragStopY); } else if (dragStopX < dragStartX && dragStopY < dragStartY) { selectionBorder.setSize(dragStartX - dragStopX, dragStartY - dragStopY); selectionBorder.setLocation(dragStopX, dragStopY); } else { selectionBorder.setSize(dragStopX - dragStartX, dragStopY - dragStartY); selectionBorder.setLocation(dragStartX, dragStartY); } g2.draw(selectionBorder); } // display shape currently being drawed if (lineStartX > 0 && lineStartY > 0 && lineStopX > 0 && lineStopY > 0) { if (useLayerColor) g2.setColor(model.getCurrentLayer().getColor()); else g2.setColor(palette.getCurrentColor()); g2.setStroke((BasicStroke) shapesPanel.getThicknessCombo().getSelectedItem()); // draw new line if (editorMode.equals(EditorMode.DRAW_LINE)) { if (useLayerColor) currentDrawingLine.setColor(model.getCurrentLayer().getColor()); else currentDrawingLine.setColor(palette.getCurrentColor()); currentDrawingLine.setStartType((LineEdgeType) shapesPanel.getStartLineCombo().getSelectedItem()); currentDrawingLine.setEndType((LineEdgeType) shapesPanel.getEndLineCombo().getSelectedItem()); currentDrawingLine.setStroke((BasicStroke) shapesPanel.getThicknessCombo().getSelectedItem()); currentDrawingLine.getStart().setLocation(lineStartX, lineStartY); currentDrawingLine.getEnd().setLocation(lineStopX, lineStopY); currentDrawingLine.draw(g2); } else if (editorMode.equals(EditorMode.DRAW_MEASURE)) { currentDrawingLine.setStroke(Measure.DEFAULT_STROKE); currentDrawingLine.getStart().setLocation(lineStartX, lineStartY); currentDrawingLine.getEnd().setLocation(lineStopX, lineStopY); currentDrawingLine.draw(g2); } // draw new box or ellipse else if (editorMode.equals(EditorMode.DRAW_RECTANGLE) || editorMode.equals(EditorMode.DRAW_ELLIPSE)) { int xLoc = lineStartX; int yLoc = lineStartY; int width = lineStopX - lineStartX; int height = lineStopY - lineStartY; ShapeStyle style = (ShapeStyle) shapesPanel.getStyleCombo().getSelectedItem(); if (style == null || style.equals(ShapeStyle.FILLED)) { if (editorMode.equals(EditorMode.DRAW_RECTANGLE)) { g2.fillRect(xLoc, yLoc, width, height); } else { g2.fillOval(xLoc, yLoc, width, height); } } else if (style.equals(ShapeStyle.OUTLINED)) { g2.setStroke((BasicStroke) shapesPanel.getThicknessCombo().getSelectedItem()); if (editorMode.equals(EditorMode.DRAW_RECTANGLE)) { g2.drawRect(xLoc, yLoc, width, height); } else { g2.drawOval(xLoc, yLoc, width, height); } } } } // draw selection handles if (selectedItem != null) { g2.setStroke(BoardUtil.stroke_1_0f); g2.setColor(Color.GREEN); selectedItem.drawHandles(g2, 8); } // draw border if (zoomLayer != null) { TransformUI ui = (TransformUI) (Object) zoomLayer.getUI(); DefaultTransformModel xmodel = (DefaultTransformModel) ui.getModel(); if (xmodel.isMirror()) { g2.setColor(Color.RED); } else { g2.setColor(Color.GREEN); } } else { g2.setColor(Color.GREEN); } g2.setStroke(DEFAULT_STROKE); Rectangle border = new Rectangle(0, 0, model.getWidth() - 1, model.getHeight() - 1); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); g2.draw(border); if (model.getType().equals(BoardType.STRIPES) || model.getType().equals(BoardType.HOLES)) { g2.setColor(Color.BLACK); g2.setFont(miniFont); int index = 1; for (int x = 12; x < model.getWidth() - 16; x += 16) { g2.drawString(String.valueOf(index++), x, 8); } index = 1; for (int y = 18; y < model.getHeight(); y += 16) { g2.drawString(String.valueOf(index++), 3, y); } } if (editorMode.equals(EditorMode.CHECK_CONNECTIONS)) { if (connectedLines != null) { for (Line line : connectedLines) { line.drawFat(g2); } } } if (drawing) { g2.setColor(Color.BLUE); g2.setStroke(DEFAULT_STROKE); if (selectedPin != null) { g2.drawRect(lineStopX - 8, lineStopY - 8, 16, 16); } } super.paintComponents(g2); } /** * Draws the selected item to the graphics context * * @param layer the layer to be drawn * @param g2d the graphics context */ private void drawLayer(Layer layer, Graphics2D g2d) { if (!layer.isVisible()) return; DefaultTransformModel xmodel = null; if (zoomLayer != null) { TransformUI ui = (TransformUI) (Object) zoomLayer.getUI(); xmodel = (DefaultTransformModel) ui.getModel(); } if (layer.getName().equalsIgnoreCase("board")) { if (backgroundImage != null) { g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, layer.getOpacity())); g2d.drawImage(backgroundImage, 0, 0, this); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); } else { g2d.setColor(Color.WHITE); g2d.fillRect(0, 0, getWidth(), getHeight()); } g2d.setColor(Color.LIGHT_GRAY); g2d.setStroke(BoardUtil.stroke_1_0f); Boolean dotGrid = (Boolean) Preferences.values .get("org.pmedv.blackboard.BoardDesignerPerspective.dotGrid"); // draw grid if (gridVisible) { BoardUtil.drawGrid(g2d, raster, model.getWidth(), model.getHeight(), dotGrid.booleanValue()); } return; } if (layer.getName().equalsIgnoreCase("ratsnest")) { if (layer.getImage() != null) { g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, layer.getOpacity())); g2d.drawImage(layer.getImage(), 0, 0, this); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); } return; } for (Item item : layer.getItems()) { if (xmodel != null) item.setMirror(xmodel.isMirror()); item.setOpacity(layer.getOpacity()); if (item instanceof Line) { Line line = (Line) item; if (line.getStroke() != null) g2d.setStroke(line.getStroke()); else g2d.setStroke(BoardUtil.stroke_3_0f); g2d.setColor(item.getColor()); } item.draw(g2d); if (selectedItems.contains(item)) { g2d.setColor(Color.GREEN); g2d.setStroke(BoardUtil.stroke_1_0f); item.drawHandles(g2d, 8); } } } /** * Setup the context menu * * TODO : Should be externalized into an XML based configuration file. */ private void hookContextMenu() { popupMenu = new JPopupMenu(); popupMenu.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.GRAY, 1), BorderFactory.createEmptyBorder(5, 5, 5, 5))); popupMenu.add(ctx.getBean(SetSelectModeCommand.class)); popupMenu.add(ctx.getBean(SetDrawModeCommand.class)); // popupMenu.add(ctx.getBean(SetMoveModeCommand.class)); popupMenu.add(ctx.getBean(SetColorCommand.class)); popupMenu.addSeparator(); popupMenu.add(ctx.getBean(ToggleSnapToGridCommand.class)); popupMenu.add(ctx.getBean(ToggleGridCommand.class)); popupMenu.add(ctx.getBean(ToggleMirrorCommand.class)); popupMenu.addSeparator(); popupMenu.add(ctx.getBean(BrowsePartsCommand.class)); popupMenu.add(ctx.getBean(AddResistorCommand.class)); popupMenu.add(ctx.getBean(ExportImageCommand.class)); popupMenu.add(ctx.getBean(AddTextCommand.class)); popupMenu.addSeparator(); popupMenu.add(ctx.getBean(CopyCommand.class)); popupMenu.add(ctx.getBean(PasteCommand.class)); popupMenu.addSeparator(); popupMenu.add(ctx.getBean(UndoCommand.class)); popupMenu.add(ctx.getBean(RedoCommand.class)); popupMenu.addSeparator(); popupMenu.add(deleteCommand); popupMenu.addSeparator(); popupMenu.add(ctx.getBean(RotateCWCommand.class)); popupMenu.add(ctx.getBean(RotateCCWCommand.class)); popupMenu.add(ctx.getBean(FlipHorizontalCommand.class)); popupMenu.add(ctx.getBean(FlipVerticalCommand.class)); popupMenu.addSeparator(); popupMenu.add(ctx.getBean(MoveToLayerCommand.class)); popupMenu.add(ctx.getBean(ConvertToPartCommand.class)); popupMenu.add(ctx.getBean(ConvertToSymbolCommand.class)); popupMenu.add(ctx.getBean(BreakSymbolCommand.class)); popupMenu.add(ctx.getBean(AddSymbolToLibraryCommand.class)); popupMenu.addSeparator(); // popupMenu.add(ctx.getBean(EditPartCommand.class)); popupMenu.add(ctx.getBean(EditPropertiesCommand.class)); popupMenu.addSeparator(); final SimulateCircuitCommand simulateCommand = ctx.getBean(SimulateCircuitCommand.class); final JMenu simulatorMenu = new JMenu(resources.getResourceByKey("SimulateCircuitCommand.name")); simulatorMenu.setIcon(resources.getIcon("icon.simulate")); final SimulatorProvider provider = AppContext.getContext().getBean(SimulatorProvider.class); for (final SpiceSimulator simulator : provider.getElements()) { final JMenuItem item = new JMenuItem(simulator.getName()); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { simulateCommand.setSimulator(simulator); simulateCommand.execute(null); } }); simulatorMenu.add(item); } popupMenu.add(simulatorMenu); } /** * Occurs usually if the right mouse button has been pressed * * @param event */ private void handleContextClick(MouseEvent event) { Point point = event.getPoint(); point = BoardUtil.mirrorTransform(point, zoomLayer, event); contextClickX = (int) point.x; contextClickY = (int) point.y; deleteCommand.setEnabled(selectedItem != null || selectedItems.size() > 0); popupMenu.show(zoomLayer, point.x, point.y); } /** * @return the snapToGrid */ public boolean isSnapToGrid() { return snapToGrid; } /** * @param snapToGrid the snapToGrid to set */ public void setSnapToGrid(boolean snapToGrid) { this.snapToGrid = snapToGrid; } /** * @return the selectedItem */ public Item getSelectedItem() { return selectedItem; } public void setSelectedItem(Item item) { selectedItem = item; } /** * @return the gridVisible */ public boolean isGridVisible() { return gridVisible; } /** * @param gridVisible the gridVisible to set */ public void setGridVisible(boolean gridVisible) { this.gridVisible = gridVisible; } /** * @return the selectedItems */ public ArrayList<Item> getSelectedItems() { return selectedItems; } /** * @param selectedItems the selectedItems to set */ public void setSelectedItems(ArrayList<Item> selectedItems) { this.selectedItems = selectedItems; } /** * @return the fileState */ public FileState getFileState() { return fileState; } /** * @param fileState the fileState to set */ public void setFileState(FileState fileState) { if (view != null) { if (fileState.equals(FileState.DIRTY)) { saveBoardCommand.setEnabled(true); if (currentFile != null) view.getViewProperties().setTitle(currentFile.getName() + "*"); else { if (!view.getViewProperties().getTitle().contains("*")) view.getViewProperties().setTitle(view.getViewProperties().getTitle() + "*"); } } else { saveBoardCommand.setEnabled(false); if (currentFile != null) view.getViewProperties().setTitle(currentFile.getName()); else view.getViewProperties().setTitle("untitled"); } } this.fileState = fileState; } /** * @return the currentFile */ public File getCurrentFile() { return currentFile; } /** * @param currentFile the currentFile to set */ public void setCurrentFile(File currentFile) { this.currentFile = currentFile; } /** * @return the view */ public View getView() { return view; } /** * @param view the view to set */ public void setView(View view) { this.view = view; } /** * @return the backgroundImage */ public Image getBackgroundImage() { return backgroundImage; } /** * @param backgroundImage the backgroundImage to set */ public void setBackgroundImage(Image backgroundImage) { this.backgroundImage = backgroundImage; } /** * @return the backgroundImageName */ public String getBackgroundImageName() { return backgroundImageName; } /** * @param backgroundImageName the backgroundImageName to set */ public void setBackgroundImageName(String backgroundImageName) { this.backgroundImageName = backgroundImageName; } public void updateStatusBar() { log.debug("editor update"); StringBuffer text = new StringBuffer(); if (snapToGrid) text.append(resources.getResourceByKey("mode.snap")); else text.append(resources.getResourceByKey("mode.nosnap")); text.append(" | "); if (editorMode.equals(EditorMode.SELECT)) text.append(resources.getResourceByKey("mode.select")); else if (editorMode.equals(EditorMode.DRAW_LINE)) text.append(resources.getResourceByKey("mode.line")); else if (editorMode.equals(EditorMode.DRAW_RECTANGLE)) text.append(resources.getResourceByKey("mode.box")); else if (editorMode.equals(EditorMode.DRAW_ELLIPSE)) text.append(resources.getResourceByKey("mode.ellipse")); else if (editorMode.equals(EditorMode.CHECK_CONNECTIONS)) text.append(resources.getResourceByKey("mode.check")); else if (editorMode.equals(EditorMode.MOVE)) text.append(resources.getResourceByKey("mode.move")); text.append(" | "); if (magnetic) text.append(resources.getResourceByKey("mode.magnetic")); BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); Graphics g = image.getGraphics(); g.setColor(palette.getCurrentColor()); g.fillRect(0, 0, 16, 16); win.getStatusLabel().setText(text.toString()); win.getStatusLabel().setIcon(new ImageIcon(image)); if (saveBoardCommand != null && fileState != null) saveBoardCommand.setEnabled(fileState.equals(FileState.DIRTY)); refresh(); } /** * @return the model */ public BoardEditorModel getModel() { return model; } /** * @param model the model to set */ public void setModel(BoardEditorModel model) { this.model = model; } public void setEditorMode(EditorMode mode) { this.editorMode = mode; } public EditorMode getEditorMode() { return this.editorMode; } /** * @return the contextClickX */ public int getContextClickX() { return contextClickX; } /** * @return the contextClickY */ public int getContextClickY() { return contextClickY; } public void refresh() { invalidate(); repaint(); } public int getRaster() { return raster; } public void setRaster(int raster) { this.raster = raster; } public JXLayer<?> getZoomLayer() { return zoomLayer; } public void setZoomLayer(JXLayer<?> zoomLayer) { this.zoomLayer = zoomLayer; } public void updateEditCommands() { boolean isSingleItemSelected = selectedItem != null && !editPart; ctx.getBean(RotateCWCommand.class).setEnabled(isSingleItemSelected && !(selectedItem instanceof Line)); ctx.getBean(RotateCCWCommand.class).setEnabled(isSingleItemSelected && !(selectedItem instanceof Line)); ctx.getBean(DeleteCommand.class).setEnabled(isSingleItemSelected || selectedItems.size() > 0); ctx.getBean(CopyCommand.class).setEnabled(isSingleItemSelected || selectedItems.size() > 0); ctx.getBean(DuplicateCommand.class).setEnabled(isSingleItemSelected || selectedItems.size() > 0); ctx.getBean(MoveToLayerCommand.class).setEnabled(isSingleItemSelected || selectedItems.size() > 0); ctx.getBean(ConvertToPartCommand.class).setEnabled( (isSingleItemSelected || selectedItems.size() > 0) && !(selectedItem instanceof Symbol)); ctx.getBean(PasteCommand.class).setEnabled(AppContext.getClipboard().size() > 0); boolean canConvert = true; for (Item item : selectedItems) { if ((!(item instanceof Shape) && !(item instanceof TextPart)) || item instanceof Resistor || item instanceof Diode) { canConvert = false; break; } } ctx.getBean(ConvertToSymbolCommand.class).setEnabled(canConvert && selectedItems.size() > 0); ctx.getBean(BreakSymbolCommand.class).setEnabled(isSingleItemSelected && selectedItem instanceof Symbol); ctx.getBean(AddSymbolToLibraryCommand.class) .setEnabled(isSingleItemSelected && selectedItem instanceof Symbol); ctx.getBean(FlipHorizontalCommand.class).setEnabled(isSingleItemSelected && selectedItem instanceof Symbol); ctx.getBean(FlipVerticalCommand.class).setEnabled(isSingleItemSelected && selectedItem instanceof Symbol); } public void addEditorChangeListener(EditorChangedListener listener) { listeners.add(listener); } public void notifyListeners(EventType type) { BoardEditor editor = null; if (type == EventType.EDITOR_CHANGED) editor = this; for (EditorChangedListener ecl : listeners) { ecl.editorChanged(new EditorChangedEvent(editor, type)); } } public boolean isMagnetic() { return magnetic; } public void setMagnetic(boolean magnetic) { this.magnetic = magnetic; } @Override public void mouseDragged(MouseEvent e) { } @Override public void mouseMoved(MouseEvent e) { if (drawing) { handleDrawEvent(e); refresh(); } } }