/** * FrameMouseActions.java * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org * * 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 . */ package org.expeditee.gui; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.swing.Timer; import org.expeditee.actions.Actions; import org.expeditee.actions.Misc; import org.expeditee.actions.Navigation; import org.expeditee.gui.indirect.mouse.IndirectMouseActions; import org.expeditee.gui.indirect.mouse.MouseAction; import org.expeditee.gui.indirect.mouse.MouseAction.MouseInfo; import org.expeditee.io.ExpClipReader; import org.expeditee.io.ItemSelection; import org.expeditee.io.ItemSelection.ExpDataHandler; import org.expeditee.items.Circle; import org.expeditee.items.Constraint; import org.expeditee.items.Dot; import org.expeditee.items.Item; import org.expeditee.items.Item.HighlightMode; import org.expeditee.items.ItemAppearence; import org.expeditee.items.ItemUtils; import org.expeditee.items.Line; import org.expeditee.items.Picture; import org.expeditee.items.Text; import org.expeditee.items.UserAppliedPermission; import org.expeditee.items.XRayable; import org.expeditee.items.widgets.InteractiveWidget; import org.expeditee.items.widgets.WidgetCorner; import org.expeditee.items.widgets.WidgetEdge; import org.expeditee.settings.UserSettings; import org.expeditee.settings.experimental.ExperimentalFeatures; import org.expeditee.stats.SessionStats; public class FrameMouseActions implements MouseListener, MouseMotionListener, MouseWheelListener { private static int _lastMouseClickModifiers = 0; private static MouseEvent _lastMouseDragged; private boolean _autoStamp = false; private FrameMouseActions() { IndirectMouseActions.getInstance().setBackAction(new MouseAction() { @Override public void exec(MouseInfo info) { //if user is not pointing at something,this is a back if (info.isControlDown || info.isShiftDown) forward(); else back(); } }); } private static FrameMouseActions _instance = null; public static FrameMouseActions getInstance() { if (_instance == null) _instance = new FrameMouseActions(); return _instance; } private static final int RECTANGLE_CORNERS = 4; // TODO say where/how used private static final int MOUSE_WHEEL_THRESHOLD = 2; private static final int MINIMUM_RANGE_DEPRESS_TIME = 250; private static final int RECTANGLE_TO_POINT_THRESHOLD = 20; private static Date _lastMouseClickDate = new Date(); public static final int LITTLE_MOUSE_PAUSE = 500; public static final int ZERO_MOUSE_PAUSE = 0; public static final int BIG_MOUSE_PAUSE = 750; public static final int CONTEXT_FREESPACE = 0; public static final int CONTEXT_AT_TEXT = 1; public static final int CONTEXT_AT_LINE = 2; public static final int CONTEXT_AT_DOT = 3; public static final int CONTEXT_AT_ENCLOSURE = 4; public static int _alpha = -1; /** * The last known mouse X coordinate */ public static float MouseX; /** * The last known mouse Y coordinate. Relative to the top of the * application. */ public static float MouseY; // Distance of mouse cursor from the origin of the item that was picked up // The are used in the move method to calculate the distance moved by the // cursor private static int _offX; private static int _offY; // Keeps track of mouse button events when a delete occurs private static boolean _isDelete = false; // Keeps track of mouse button events when the user extracts attributes // occurs private static boolean _isAttribute = false; /** * A flag to indicate that the last mouseUp event was part of a two button * click sequence hence the next mouse up should be ignored* */ private static boolean _wasDouble = false; private static boolean _isNoOp = false; private static boolean _extrude = false; // keeps track of the last highlighted Item private static Item _lastHighlightedItem = null; // keeps track of the item being 'ranged out' if there is one. private static Text _lastRanged = null; // keeps track of the picture being cropped if there is one private static Picture _lastCropped = null; // true if lastItem only has highlighting removed when a new item is // highlighted private static boolean _lastHoldsHighlight = false; private static boolean _forceArrowCursor = true; // the current context of the cursor private static int _context = 0; public static void setForceArrow(boolean val) { _forceArrowCursor = val; } public static int getContext() { return _context; } static int _mouseDown = 0; private static MouseEvent _lastMouseClick = null; private static Item _lastClickedOn = null; private static Collection _lastClickedIn = null; private static boolean _pulseOn = false; private static final int PULSE_AMOUNT = 2; private static Timer _MouseTimer = new Timer(LITTLE_MOUSE_PAUSE, new ActionListener() { public void actionPerformed(ActionEvent ae) { // check if we are in free space if (_lastClickedOn == null && FreeItems.getInstance().size() == 0) { // System.out.println("SuperBack!"); _MouseTimer.setDelay(ZERO_MOUSE_PAUSE); back(); } else { if (FrameUtils.getCurrentItem() == null) { // Check if we are toggling arrowhead if (FreeItems.getInstance().size() <= 2) { for (Item i : FreeItems.getInstance()) { if (i instanceof Line) { ((Line) i).toggleArrow(); } } FrameGraphics.Repaint(); } } _MouseTimer.stop(); } } }); private static void setPulse(boolean pulseOn) { if (_pulseOn == pulseOn) { return; } int amount = PULSE_AMOUNT; if (!pulseOn) { amount *= -1; } _pulseOn = pulseOn; if (_lastClickedOn != null) { for (Item i : _lastClickedOn.getAllConnected()) { if (i instanceof Line) { Line line = (Line) i; line.setThickness(line.getThickness() + amount); } } } FrameGraphics.Repaint(); } private static Timer _ExtrudeMouseTimer = new Timer(BIG_MOUSE_PAUSE, new ActionListener() { public void actionPerformed(ActionEvent ae) { setPulse(true); _extrude = true; _ExtrudeMouseTimer.stop(); } }); public void mouseClicked(MouseEvent e) { } /** * Each Item on the Frame is checked to determine if the mouse x,y * coordinates are on the Item (or within the Shape surrounding it). If the * coordinates are on the Item then the Item is checked for a link, if it * has a link the link is followed, if not, nothing is done. */ public void mousePressed(MouseEvent e) { ProccessMousePressedEvent(e, e.getModifiersEx()); } private void ProccessMousePressedEvent(MouseEvent e, int modifiersEx) { // System.out.println("MousePressed " + e.getX() + "," + e.getY() + " " // + e.getWhen()); // TODO WHY DID I NOT COMMENT THIS LINE!! MIKE SAYS if (LastRobotX != null) { _RobotTimer.stop(); LastRobotX = null; LastRobotY = null; mouseMoved(e); } if(ExperimentalFeatures.MousePan.get()) { // don't pan if we're not over the frame _overFrame = FrameUtils.getCurrentItem() == null; _isPanOp = false; // update panning position values so position doesn't jump panStartX = e.getX(); panStartY = e.getY(); MouseX = panStartX; MouseY = panStartY; } // System.out.println(modifiersEx); if (_mouseDown == 0) _lastMouseClickDate = new Date(); int buttonPressed = e.getButton(); _mouseDown += buttonPressed; _lastClickedOn = FrameUtils.getCurrentItem(); // load any frame if necessary Item on = _lastClickedOn; _lastClickedIn = FrameUtils.getCurrentItems(on); // if (_lastClickedIn != null){ // System.out.println(_lastClickedIn.size());} /* * This makes it so clicking repeatedly on the frameName doesnt add the * frames to the backup stack. Only the first frame is added to the * backup stack. */ if (on == null || buttonPressed != MouseEvent.BUTTON1 || !on.isFrameName()) { Navigation.ResetLastAddToBack(); } SessionStats.MouseClicked(e.getButton()); if (buttonPressed == MouseEvent.BUTTON1) { SessionStats.AddFrameEvent("Ld"); _extrude = false; } else if (buttonPressed == MouseEvent.BUTTON2) { SessionStats.AddFrameEvent("Md"); _extrude = false; } else if (buttonPressed == MouseEvent.BUTTON3) { SessionStats.AddFrameEvent("Rd"); // Check if the user picked up a paint brush if (FreeItems.getInstance().size() == 1 && FreeItems.getItemAttachedToCursor().isAutoStamp()) { int delay = (int) (FreeItems.getItemAttachedToCursor() .getAutoStamp() * 1000); if (delay < 10) { _autoStamp = true; } else { _autoStampTimer.setDelay(delay); _autoStampTimer.start(); } } } // Mike says... // For somereason the modifiers for e are different from modifiersEx // The SwingUtilities.convertMouseEvent method changes the modifiers _lastMouseClick = e; _lastMouseClickModifiers = modifiersEx; /* * Only start the timer when in free space when the user double clicks * to do super back TODO change this so that there are separate timers * for super back and the other Long depress actions if that is what is * wanted. */ if (_lastClickedOn == null && FreeItems.getInstance().size() == 0) { // System.out.println(e.getClickCount()); if (e.getClickCount() >= 2) { _MouseTimer.start(); } } else if (_lastClickedOn != null && FreeItems.getInstance().size() == 0 && e.getButton() == MouseEvent.BUTTON3) { _ExtrudeMouseTimer.start(); } else { _MouseTimer.start(); } // pre-cache the frame if it is linked // If pre-caching is done, it must be done in the background // if (on != null && on.getLink() != null && on.isLinkValid()) { // FrameIO.Precache(on.getAbsoluteLink()); // } // check for delete command if (isDelete(modifiersEx)) { _isDelete = true; // _lastRanged = null; _lastCropped = null; _wasDouble = false; // check for attributes command } else if (isGetAttributes(modifiersEx)) { _isAttribute = true; _wasDouble = false; } else if (isTwoClickNoOp(modifiersEx)) { _isAttribute = false; _wasDouble = false; _isDelete = false; _isNoOp = true; } else _isDelete = false; // This must happen before the previous code // This is when the user is anchoring something if (buttonPressed != MouseEvent.BUTTON1 && (_context == CONTEXT_FREESPACE || _context == CONTEXT_AT_ENCLOSURE) && FreeItems.itemsAttachedToCursor()) { FrameGraphics.changeHighlightMode(_lastHighlightedItem, Item.HighlightMode.None); _lastHighlightedItem = FreeItems.getItemAttachedToCursor(); for (Item i : FreeItems.getInstance()) { i.setHighlightColor(Item.DEPRESSED_HIGHLIGHT); } FrameGraphics.Repaint(); // this is when the user is picking something up } else if (_lastHighlightedItem != null) { if (!(_lastHighlightedItem instanceof Line)) { _lastHighlightedItem .setHighlightColor(Item.DEPRESSED_HIGHLIGHT); } else { for (Item i : _lastHighlightedItem.getAllConnected()) { i.setHighlightColor(Item.DEPRESSED_HIGHLIGHT); } } FrameGraphics.Repaint(); } // if the user is ranging text if (on != null && on instanceof Text && !_isDelete) { _lastRanged = (Text) on; // set start-drag point _lastRanged.setSelectionStart(DisplayIO.getMouseX(), FrameMouseActions.getY()); } /* * Want to repaint the text with deleteRange color as soon as the second * button is pressed */ if (_lastRanged != null) { _lastRanged.invalidateAll(); FrameGraphics.requestRefresh(true); } if (on != null && on instanceof Picture && e.getButton() == MouseEvent.BUTTON3 && !_isDelete) { _lastCropped = (Picture) on; // set start crop point _lastCropped.setStartCrop(DisplayIO.getMouseX(), FrameMouseActions .getY()); _lastCropped.setShowCrop(true); } } // This is where all the processing happens public void mouseReleased(MouseEvent e) { // System.out.println("Released " + e.getX() + "," + e.getY() + " " + // e.getWhen()); FrameUtils.ResponseTimer.restart(); _autoStampTimer.stop(); _autoStamp = false; // Auto-hide popups when user clicks into expeditee world // If the user clicks into empty space and a popup-is showing, then // the user porbably wants to click away the popup - therefore ignore // the event boolean shouldConsume = PopupManager.getInstance() .shouldConsumeBackClick(); PopupManager.getInstance().hideAutohidePopups(); if (shouldConsume && e.getButton() == MouseEvent.BUTTON1) { return; // consume back click event } // _lastMovedDistance = new Point(e.getX() - _lastMouseClick.getX(), e // .getY() // - _lastMouseClick.getY()); _mouseDown -= e.getButton(); updateCursor(); // System.out.println(e.getX() + ", " + e.getY()); Text lastRanged = _lastRanged; _lastRanged = null; // Dont do ranging if the user moves really quickly... // They are probably trying to pick something up in this case if (lastRanged != null) { long depressTime = (new Date()).getTime() - _lastMouseClickDate.getTime(); // double changeInDistance = // e.getPoint().distance(_currentMouseClick.getPoint()); // double speed = changeInDistance * 1000 / changeInTime; // System.out.println(depressTime); if (depressTime < MINIMUM_RANGE_DEPRESS_TIME || lastRanged.getSelectionSize() <= 0) {// Text.MINIMUM_RANGED_CHARS) // { lastRanged.clearSelection(); lastRanged = null; } } _ExtrudeMouseTimer.stop(); _MouseTimer.stop(); setPulse(false); // if the last action was a delete, then ignore the next mouseup if (_wasDouble) { _wasDouble = false; return; } // This code must come after the _wasDouble code... // Otherwise get Stopping Agent method after doing the left+right format // shortcut if (Actions.isAgentRunning()) { Actions.stopAgent(); return; } /* * if (_isNoOp) { if (e.getButton() != MouseEvent.NOBUTTON) { _isNoOp = * false; _wasDouble = true; // lastRanged.clearSelection(); * FrameGraphics.Repaint(); return; } } */ // get whatever the user was pointing at Item clickedOn = _lastClickedOn; Collection clickedIn = _lastClickedIn; MouseX = e.getX(); MouseY = e.getY(); Item releasedOn = FrameUtils.getCurrentItem(); Collection releasedIn = FrameUtils.getCurrentItems(releasedOn); // Only a no op if user releases in free space! if (_isPanOp || (_isNoOp && (releasedOn == null && releasedIn == null))) { if (_isDelete) { _isDelete = false; _wasDouble = true; } _isNoOp = false; if (_lastHighlightedItem != null) FrameGraphics.changeHighlightMode(_lastHighlightedItem, Item.HighlightMode.None); if (FreeItems.itemsAttachedToCursor()) { move(FreeItems.getInstance()); } if (FreeItems.hasCursor()) { move(FreeItems.getCursor(), true); } if(!_isPanOp) { MessageBay.displayMessage("Action cancelled, mouse moved more than " + UserSettings.NoOpThreshold.get() + " pixels."); } FrameGraphics.Repaint(); return; } else { _isNoOp = false; } // if this is a delete command if (_isDelete) { if (lastRanged != null) { Item i = FreeItems.getItemAttachedToCursor(); if (i != null && i instanceof Text) { lastRanged.replaceSelectedText(((Text) i).getText()); FreeItems.getInstance().clear(); } else lastRanged.cutSelectedText(); lastRanged.clearSelection(); FrameGraphics.Repaint(); } else { delete(clickedOn); } _wasDouble = true; _isDelete = false; return; } // if this is an attribute extraction command if (_isAttribute) { if (clickedOn == null) { Frame current = DisplayIO.getCurrentFrame(); if (isControlDown()) { Actions.PerformActionCatchErrors(current, null, "HFormat"); } if (!isControlDown() || isShiftDown()) { Actions.PerformActionCatchErrors(current, null, "Format"); } } else { extractAttributes(clickedOn); } // if the user dragged and displayed some cropping with left and // right button is a no op for now // but later could make this the shrinkTo context if (_lastCropped != null) { _lastCropped.clearCropping(); _lastCropped = null; } _wasDouble = true; _isAttribute = false; return; } // if the user is ranging-out text if (lastRanged != null && e.getButton() != MouseEvent.BUTTON1) { Text ranged; if (isShiftDown()) { // If shift is down, copy everything (size, color, etc.) except actions, links and data ranged = lastRanged.copy(); ranged.setActions(null); ranged.setData((List) null); ranged.setLink(null); } else { // If shift isn't down, don't copy any attributes, but base the new text item on the appropriate template ranged = DisplayIO.getCurrentFrame().getItemTemplate(lastRanged.copySelectedText().charAt(0)); } // if the user is cutting text from the item if (e.getButton() == MouseEvent.BUTTON2) { // Check if the user is trying to range an item for which they // do not have permission to do so... or it is the frame name if (!lastRanged.hasPermission(UserAppliedPermission.full) || lastRanged.isFrameName()) { MessageBay .displayMessage("Insufficient permission to cut text"); lastRanged.clearSelection(); FrameGraphics.Repaint(); return; } // if the entire text is selected and its not a line end then // pickup the item boolean entireText = lastRanged.getSelectionSize() == lastRanged .getLength(); if (entireText && !lastRanged.isLineEnd()) { lastRanged.clearSelection(); ranged.delete(); middleButton(clickedOn, clickedIn, e.isShiftDown()); return; } else { ranged.setText(lastRanged.cutSelectedText()); ranged.setWidth(lastRanged.getWidth()); // If its the whole text then replace last ranged with a dot if (entireText) { Item dot = FrameKeyboardActions.replaceText(lastRanged); dot.setHighlightMode(HighlightMode.None); } } // if the user is copying text from the item } else if (e.getButton() == MouseEvent.BUTTON3) { // Check if the user is trying to range an item for which they // do not have permission to do so... or it is the frame name if (!lastRanged.hasPermission(UserAppliedPermission.copy)) { MessageBay .displayMessage("Insufficient permission to copy text"); lastRanged.clearSelection(); FrameGraphics.Repaint(); return; } ranged.setText(lastRanged.copySelectedText()); } ranged.setParent(null); ranged.setPosition(DisplayIO.getMouseX(), FrameMouseActions.getY()); pickup(ranged); lastRanged.clearSelection(); lastRanged.setHighlightMode(HighlightMode.None); refreshHighlights(); FrameGraphics.refresh(false); return; } // if the user is cropping an image if (clickedOn != null && clickedOn == _lastCropped) { if (_lastCropped.isCropTooSmall()) { _lastCropped = null; // FrameGraphics // .WarningMessage("Crop cancelled because it was below the // minimum size"); } else { Picture cropped = _lastCropped.copy(); cropped.setParent(null); // move the cropped image to the cursor int width = cropped.getWidth(); int height = cropped.getHeight(); if(cropped.getSource().getX() + width < MouseX) { cropped.getSource().setX(MouseX - width); } if(cropped.getSource().getY() + height < MouseY) { cropped.getSource().setY(MouseY - height); } pickup(cropped); // MIKE put the code below up here _lastCropped.clearCropping(); FrameGraphics.changeHighlightMode(_lastCropped, HighlightMode.None); _lastCropped = null; FrameGraphics.Repaint(); return; } } assert (_lastCropped == null); // if the user has cropped an image, either the above happend or this is // a no-op MIKE says WHEN DO WE NEED THE CODE BELOW // if (_lastCropped != null && !_lastCropped.isCropTooSmall()) { // _lastCropped.clearCropping(); // _lastCropped = null; // FrameGraphics.Repaint(); // return; // } // if the user is left-clicking if (e.getButton() == MouseEvent.BUTTON1) { SessionStats.AddFrameEvent("Lu"); leftButton(clickedOn, clickedIn, e.isShiftDown(), e.isControlDown()); return; } if (e.getButton() == MouseEvent.BUTTON2) { SessionStats.AddFrameEvent("Mu"); middleButton(clickedOn, clickedIn, e.isShiftDown()); return; } if (e.getButton() == MouseEvent.BUTTON3) { SessionStats.AddFrameEvent("Ru"); rightButton(clickedOn, clickedIn); return; } // error, we should have returned by now System.out.println("Error: mouseReleased should have returned by now. " + e); } /** * This method handles all left-click actions */ private void leftButton(Item clicked, Collection clickedIn, boolean isShiftDown, boolean isControlDown) { // if the user is pointing at something then either follow the link or // do TDFC if (clicked == null) { // Check if the user is nearby another item... int mouseX = DisplayIO.getMouseX(); int mouseY = FrameMouseActions.getY(); // System.out.println(mouseX + "," + mouseY); for (Item i : DisplayIO.getCurrentFrame().getItems()) { if (i instanceof Text) { if (i.isNear(mouseX, mouseY)) { clicked = i; break; } } } } if (clicked instanceof Text) { Text text = (Text) clicked; /* Dont follow link when just highlighting text with the left button */ if (text.getText().length() == 0) clicked = null; else if (text.getSelectionSize() > 0) { return; } } // If the user clicked into a widgets free space... if (clicked == null && _lastClickedIn != null && _lastClickedIn.size() >= 4) { // Check to see if the use clicked into a widgets empty space InteractiveWidget iw = null; for (Item i : _lastClickedIn) { if (i instanceof WidgetCorner) { iw = ((WidgetCorner) i).getWidgetSource(); break; } else if (i instanceof WidgetEdge) { iw = ((WidgetEdge) i).getWidgetSource(); break; } } if (iw != null) { // Handle dropping items on widgets if(iw.ItemsLeftClickDropped()) { return; } // Note: musten't directly use source for handling the link // because all link operations will by-pass the widgets special // handling with links... Item widgetLink = iw.getItems().get(0); assert (widgetLink != null); clicked = widgetLink; } else { for (Item i : _lastClickedIn) { /* * Find the first linked item or the first unlinked Dot This * code assumes that items are are ordered from top to * bottom. TODO make sure the list will always be ordered * correctly!! */ if (i.hasLink() || i instanceof Dot) { clicked = i; break; } } } } if (clicked != null) { // check item permissions boolean hasLinkOrAction = clicked.hasLink() || clicked.hasAction(); if ((hasLinkOrAction && !clicked .hasPermission(UserAppliedPermission.followLinks)) || (!hasLinkOrAction && !clicked .hasPermission(UserAppliedPermission.createFrames))) { Item editTarget = clicked.getEditTarget(); if (editTarget != clicked) { if (editTarget.hasPermission(UserAppliedPermission.followLinks)) { clicked = editTarget; } else { MessageBay .displayMessage("Insufficient permission to perform action on item"); return; } } } Item clickedOn = clicked; // actions take priority if (_lastMouseClick != null && !_lastMouseClick.isControlDown() && clickedOn.hasAction()) { clickedOn.performActions(); clickedOn.setHighlightMode(HighlightMode.None); getInstance().refreshHighlights(); return; } else if (clickedOn.getLink() != null) { /* * Dont save the frame if we are moving to an old version of * this frame because everytime we save with the old tag... the * frame is backed up */ if (!clickedOn.isOldTag()) FrameIO.SaveFrame(DisplayIO.getCurrentFrame()); Navigation.setLastNavigationItem(clickedOn); load(clickedOn.getAbsoluteLink(), clickedOn.getLinkHistory()); // DisplayIO.UpdateTitle(); return; // no link is found, perform TDFC } else { /* * if the user is clicking on the frame name then move to the * next or previous frame regardless of whether or not the frame * is protected */ if (clickedOn.isFrameName()) { if (isControlDown) Navigation.PreviousFrame(false); else Navigation.NextFrame(false); return; } // check for TDFC permission if (!clicked.hasPermission(UserAppliedPermission.createFrames)) { MessageBay .displayMessage("Insufficient permission to TDFC (Top Down Frame Creation) from that item"); return; } if (clickedOn.isOldTag()) return; try { tdfc(clickedOn); } catch (RuntimeException e) { e.printStackTrace(); MessageBay.errorMessage("Top Down Frame Creation (TDFC) error: " + e.getMessage()); } return; } } else { IndirectMouseActions.getInstance().getBackAction().exec(new MouseInfo(clicked, clickedIn, isShiftDown, isControlDown)); // if user is not pointing at something,this is a back // if (isShiftDown || isControlDown) // forward(); // else // back(); } } private static boolean doMerging(Item clicked) { if (clicked == null) return false; // // Brook: widgets do not merge // if (clicked instanceof WidgetCorner) // return false; // // // Brook: widgets do not merge // if (clicked instanceof WidgetEdge) // return false; // System.out.println(FreeItems.getInstance().size()); if (isRubberBandingCorner()) { if (clicked.isLineEnd() || clicked.getAllConnected().contains( FreeItems.getItemAttachedToCursor())) { return true; } } if (FreeItems.getInstance().size() > 2) return false; Item attachedToCursor = FreeItems.getItemAttachedToCursor(); if (clicked instanceof Text && !(attachedToCursor instanceof Text || attachedToCursor .isLineEnd())) { return false; } return true; } public static void middleButton() { Item currentItem = FrameUtils.getCurrentItem(); getInstance().middleButton(currentItem, FrameUtils.getCurrentItems(currentItem), false); updateCursor(); } public static void rightButton() { Item currentItem = FrameUtils.getCurrentItem(); getInstance().rightButton(currentItem, FrameUtils.getCurrentItems(currentItem)); updateCursor(); } public static void leftButton() { Item currentItem = FrameUtils.getCurrentItem(); getInstance().leftButton(currentItem, FrameUtils.getCurrentItems(currentItem), false, false); updateCursor(); } /** * This method handles all middle-click actions */ private void middleButton(Item clicked, Collection clickedIn, boolean isShiftDown) { // If the user clicked into a widgets free space... if (clicked == null && _lastClickedIn != null && _lastClickedIn.size() >= 4) { // Check to see if the use clicked into a widgets empty space InteractiveWidget iw = null; for (Item i : _lastClickedIn) { if (i instanceof WidgetCorner) { iw = ((WidgetCorner) i).getWidgetSource(); break; } else if (i instanceof WidgetEdge) { iw = ((WidgetEdge) i).getWidgetSource(); break; } } if (iw != null) { // Handle dropping items on widgets if(iw.ItemsMiddleClickDropped()) { return; } } } // if the cursor has Items attached if (FreeItems.itemsAttachedToCursor()) { // if the user is pointing at something, merge the items (if // possible) if (doMerging(clicked)) { // check permissions if (!clicked.hasPermission(UserAppliedPermission.full)) { //Items on the message box have parent == null if (clicked.getParent() != null) { if (!clicked.isFrameName()) { Item editTarget = clicked.getEditTarget(); if (editTarget != clicked && editTarget .hasPermission(UserAppliedPermission.full)) { clicked = editTarget; } else { MessageBay .displayMessage("Insufficient permission"); return; } } } else /*Its in the message area*/ { MessageBay.displayMessage("Insufficient permission"); return; } } Item merger = FreeItems.getItemAttachedToCursor(); assert (merger != null); Collection left = null; // when anchoring a line end onto a text line end, holding shift // prevents the line ends from being merged if (isShiftDown) { left = FreeItems.getInstance(); } else { left = merge(FreeItems.getInstance(), clicked); } Collection toDelete = new LinkedList(); toDelete.addAll(FreeItems.getInstance()); toDelete.removeAll(left); anchor(left); FreeItems.getInstance().clear(); DisplayIO.getCurrentFrame().removeAllItems(toDelete); updateCursor(); // Make sure the dot goes away when anchoring a line end behind // a text line end if (isShiftDown) { refreshHighlights(); } FrameGraphics.requestRefresh(true); return; // otherwise, anchor the items } else { if (clickedIn != null && FreeItems.getInstance().size() == 1) { Item item = FreeItems.getItemAttachedToCursor(); if (item instanceof Text) { Text text = (Text) item; if (AttributeUtils.setAttribute(text, text, 2)) { clickedIn.removeAll(FrameUtils .getEnclosingLineEnds().iterator().next() .getAllConnected()); for (Item i : clickedIn) { AttributeUtils.setAttribute(i, text); } FreeItems.getInstance().clear(); } } } // if a line is being rubber-banded, check for auto // straightening anchor(FreeItems.getInstance()); FreeItems.getInstance().clear(); updateCursor(); _offX = _offY = 0; return; } // otherwise if the user is pointing at something, pick it up unless shift is down } else if (clicked != null && !isShiftDown) { // check permissions if (!clicked.hasPermission(UserAppliedPermission.full)) { Item editTarget = clicked.getEditTarget(); if (editTarget != clicked && editTarget.hasPermission(UserAppliedPermission.full)) { clicked = editTarget; } else { MessageBay .displayMessage("Insufficient permission to pick up item"); return; } } // BROOK: WIDGET RECTANGLES DONT ALLOW DISCONNECTION if (clicked instanceof Line && !(clicked instanceof WidgetEdge)) { // Check if within 20% of the end of the line Line l = (Line) clicked; Item toDisconnect = l.getEndPointToDisconnect(_lastMouseClick .getX(), _lastMouseClick.getY()); if (toDisconnect == null) { pickup(clicked); } else { if (toDisconnect.getHighlightMode() == Item.HighlightMode.Normal) { DisplayIO.setCursorPosition(toDisconnect.getPosition(), false); pickup(toDisconnect); } else { List lines = toDisconnect.getLines(); // This is to remove constraints from single lines // with constraints... // ie. partially deleted rectangles if (lines.size() == 1) { toDisconnect.removeAllConstraints(); DisplayIO.setCursorPosition(toDisconnect .getPosition(), false); // This is to ensure the selected mode will be set // to Normal rather than disconnect when the line is // anchored toDisconnect .setHighlightMode(Item.HighlightMode.Normal); pickup(toDisconnect); } else { // If we are then detatch the line and pick up its // end point... Frame currentFrame = DisplayIO.getCurrentFrame(); Item newPoint = null; // If the point we are disconnecting is text... // Then we want to leave the text behind // And disconnect a point if (toDisconnect instanceof Text) { newPoint = new Dot(toDisconnect.getX(), toDisconnect.getY(), -1); Item.DuplicateItem(toDisconnect, newPoint); } else { newPoint = toDisconnect.copy(); } currentFrame.addItem(newPoint); // remove the current item from the connected // list for this item l.replaceLineEnd(toDisconnect, newPoint); // remove unneeded constrains newPoint.removeAllConstraints(); // Set the new points mode to normal before picking // it up so it will be restored correctly when // anchored newPoint .setHighlightMode(Item.HighlightMode.Normal); toDisconnect .setHighlightMode(Item.HighlightMode.None); DisplayIO.setCursorPosition(toDisconnect .getPosition(), false); pickup(newPoint); ItemUtils.EnclosedCheck(toDisconnect .getParentOrCurrentFrame().getItems()); } } } } else { if (clicked.isLineEnd()) { DisplayIO.setCursorPosition(clicked.getPosition(), false); } pickup(clicked); } // if we're inside a shape, pick it up unless shift is down } else if (clickedIn != null && !isShiftDown) { ArrayList toPickup = new ArrayList(clickedIn.size()); for (Item ip : clickedIn) if (ip.hasPermission(UserAppliedPermission.full)) toPickup.add(ip); pickup(toPickup); // otherwise the user is creating a line } else { Item on = FrameUtils.onItem(DisplayIO.getCurrentFrame(), Math .round(MouseX), Math.round(MouseY), true); // If we have permission to copy this item then pick it up if (on != null && on.isLineEnd() && on.hasPermission(UserAppliedPermission.full)) { on.removeAllConstraints(); pickup(on); return; } if (on instanceof WidgetEdge) { // Don't allow the user to break widget edges. // Note: had to return here because random dots would // appear otherwise... cannot understand code below // with create line. return; } // if its on a line then split the line and put a point on it and // pick that point up. Only if it is not a widget line if (on instanceof Line && on.hasPermission(UserAppliedPermission.full)) { Frame current = DisplayIO.getCurrentFrame(); // create the two endpoints Line oldLine = (Line) on; Item newPoint = oldLine.getStartItem().copy(); newPoint.setPosition(MouseX, MouseY); Item end = oldLine.getEndItem(); // create the Line Line newLine = new Line(newPoint, end, current.getNextItemID()); oldLine.replaceLineEnd(end, newPoint); newPoint.removeAllConstraints(); pickup(newPoint); // Update the stats Collection created = new LinkedList(); created.add(newPoint); created.add(newLine); SessionStats.CreatedItems(newLine.getAllConnected()); return; } Line newLine = createLine(); SessionStats.CreatedItems(newLine.getAllConnected()); return; } SessionStats.MovedItems(FreeItems.getInstance()); } private static Item getFirstFreeLineEnd() { for (Item i : FreeItems.getInstance()) if (i.isLineEnd()) return i; return null; } private static boolean isRubberBandingCorner() { return getShapeCorner(FreeItems.getInstance()) != null; } /** * Gets the rectangle corner from the list of items that are part of a * rectangle. * * @param partialRectangle * a corner and its two connecting lines. * @return the rectangle corner or null if the list of items is not part of * a rectangle. */ private static Item getShapeCorner(List partialRectangle) { if (partialRectangle.size() < 3) return null; Item lineEnd = null; // only one lineEnd will be present for rectangles // All other items must be lines for (Item i : partialRectangle) { if (i.isLineEnd()) { if (lineEnd == null) { lineEnd = i; } else { return null; } } else if (!(i instanceof Line)) { return null; } } // if this is at least the corner of two connected lines if (lineEnd != null && lineEnd.getAllConnected().size() >= 5) return lineEnd; return null; } /** * This method handles all right-click action */ private void rightButton(Item clicked, Collection clickedIn) { // If the user clicked into a widgets free space... if (clicked == null && _lastClickedIn != null && _lastClickedIn.size() >= 4) { // Check to see if the use clicked into a widgets empty space InteractiveWidget iw = null; for (Item i : _lastClickedIn) { if (i instanceof WidgetCorner) { iw = ((WidgetCorner) i).getWidgetSource(); break; } else if (i instanceof WidgetEdge) { iw = ((WidgetEdge) i).getWidgetSource(); break; } } if (iw != null) { // Handle dropping items on widgets if(iw.ItemsRightClickDropped()) { return; } } } // if the cursor has Items attached, then anchor a copy of them List copies = null; if (FreeItems.itemsAttachedToCursor()) { if (FreeItems.getInstance().size() == 1 && FreeItems.getItemAttachedToCursor().isAutoStamp()) { // Dont stamp if the user is painting... because we dont want to // save any of the items created! return; // if the user is clicking on something, merge the items // unless it is a point onto somethin other than a lineEnd or a // dot } else if (clicked != null // TODO Change the items merge methods so the logic is simplified && (!(FreeItems.getItemAttachedToCursor() instanceof Dot) || clicked instanceof Dot || clicked.isLineEnd())) { // check permissions if (!clicked.hasPermission(UserAppliedPermission.full) && clicked.getParent().getNameItem() != clicked) { MessageBay .displayMessage("Insufficient permission to merge items"); return; } if (clicked instanceof Text || clicked instanceof Dot || clicked instanceof XRayable) { if (isRubberBandingCorner()) { // Move the cursor so that the copy is exactly the // same as the shape that was anchored DisplayIO.setCursorPosition(clicked.getPosition()); Item d = getFirstFreeLineEnd(); // get a copy of all enclosed items before merging // lineEnds Collection items = FrameUtils.getItemsEnclosedBy( DisplayIO.getCurrentFrame(), d .getEnclosedShape()); // If its not an enclosed shape then pick up the // connected shape if (items == null || items.size() == 0) { items = d.getAllConnected(); } else { // For some reason the item that was clicked ends up // in the enclosure and needs to be removed items.removeAll(clicked.getConnected()); // the item that was the origin of the enclosed // shape used to create the enclosure does not get // returned from getItemsEnclosedBy to the enclosure // so it must be added items.addAll(d.getConnected()); } Collection toCopy = new LinkedHashSet(); for (Item ip : items) { if (ip.hasPermission(UserAppliedPermission.copy)) toCopy.add(ip); } copies = copy(toCopy); // Now do the merging Collection remain = merge( FreeItems.getInstance(), clicked); // anchor the points anchor(remain); FreeItems.getInstance().clear(); pickup(copies); // line onto something } else if (FreeItems.getInstance().size() == 2 /* && clicked instanceof XRayable */) { copies = ItemUtils.UnreelLine(FreeItems.getInstance(), _controlDown); Collection leftOver = merge(FreeItems .getInstance(), clicked); anchor(leftOver); if (copies == null) copies = copy(FreeItems.getInstance()); FreeItems.getInstance().clear(); for (Item i : copies) i.setOffset(0, 0); // need to move to prevent cursor dislocation move(copies); pickup(copies); // point onto point } else if (FreeItems.getInstance().size() == 1) { copies = copy(FreeItems.getInstance()); Collection remain = merge(copies, clicked); // ignore items that could not be merged. anchor(remain); } else { stampItemsOnCursor(true); copies = FreeItems.getInstance(); } } else { copies = ItemUtils.UnreelLine(FreeItems.getInstance(), _controlDown); if (copies == null) copies = copy(FreeItems.getInstance()); for (Item i : copies) { i.setOffset(0, 0); } anchor(FreeItems.getInstance()); FreeItems.getInstance().clear(); pickup(copies); } // otherwise, anchor the items } else { // check if this is anchoring a rectangle if (isRubberBandingCorner()) { Item d = getFirstFreeLineEnd(); // anchor the points anchor(FreeItems.getInstance()); FreeItems.getInstance().clear(); updateCursor(); // pick up a copy of all enclosed items Collection enclosedItems = FrameUtils .getItemsEnclosedBy(DisplayIO.getCurrentFrame(), d .getEnclosedShape()); if (enclosedItems != null) { enclosedItems.removeAll(d.getAllConnected()); Collection toCopy = getFullyEnclosedItems(enclosedItems); if (toCopy.size() > 0) { // Find the closest item to the mouse cursor double currentX = DisplayIO.getMouseX(); double currentY = FrameMouseActions.getY(); Item closest = null; double shortestDistance = Double.MAX_VALUE; for (Item next : toCopy) { if (next instanceof Line) continue; double distance = Point.distance(currentX, currentY, next.getX(), next.getY()); if (distance < shortestDistance) { shortestDistance = distance; closest = next; } } // Move the cursor to closest item DisplayIO.setCursorPosition(closest.getPosition()); // Pickup copy of the stuff inside the rectange copies = copy(toCopy); pickup(copies); // Remove the rectangle d.getParentOrCurrentFrame().removeAllItems( d.getAllConnected()); } else { // Pick up a copy of the rectangle copies = copy(d.getAllConnected()); pickup(copies); } } } else { if (rubberBanding()) { if (clicked != null) { Collection leftOver = merge(FreeItems .getInstance(), clicked); anchor(leftOver); } // This is executed when the user is putting down a line // endpoint and unreeling. ie. Normal unreeling copies = ItemUtils.UnreelLine(FreeItems.getInstance(), _controlDown); if (copies == null) copies = copy(FreeItems.getInstance()); anchor(FreeItems.getInstance()); for (Item i : copies) i.setOffset(0, 0); // need to move to prevent cursor dislocation move(copies); pickup(copies); } else if (_extrude) { List originals = new ArrayList(); // remove any lines that dont have both endpoints // floating for (Item i : FreeItems.getInstance()) { if (i.isFloating()) originals.add(i); } if (copies == null) copies = ItemUtils.CopyItems(originals, _extrude); for (Item i : copies) i.setOffset(0, 0); anchor(FreeItems.getInstance()); // Move isnt working right for extruding!! // move(copies); pickup(copies); } else { stampItemsOnCursor(true); copies = FreeItems.getInstance(); } } } } else { // if the user is pointing at something and shift isn't down, make a copy if (clicked != null && !isShiftDown()) { // check permissions if (clicked.isLineEnd()) { if (!clicked.hasPermission(UserAppliedPermission.full)) { MessageBay .displayMessage("Insufficient permission to unreel"); return; } } else if (!clicked.hasPermission(UserAppliedPermission.copy)) { Item editTarget = clicked.getEditTarget(); if (editTarget != clicked && editTarget.hasPermission(UserAppliedPermission.copy)) { clicked = editTarget; } else { MessageBay .displayMessage("Insufficient permission to copy"); return; } } copies = ItemUtils.UnreelLine(clicked, _controlDown); // Copies will NOT be null if the user right clicked on a point if (copies == null) { Collection originals = clicked.getConnected(); copies = ItemUtils.CopyItems(originals, _extrude); // if this is the title of the frame, link it to the frame if (originals.size() == 1 && copies.size() == 1) { Item copy = copies.get(0); Item original = originals.iterator().next(); if (original.getLink() == null && original.isFrameTitle()) { // save the frame after copying // i.getParent().setChanged(true); copy.setLink(original.getParentOrCurrentFrame() .getName()); } } FrameGraphics.changeHighlightMode(clicked, HighlightMode.None); if (!_extrude) clearParent(copies); } ItemUtils.Justify(copies); pickup(copies); } else { // if user is pointing in a closed shape and shift isn't down, make a copy of the items inside if (clickedIn != null && !isShiftDown()) { // Set the selection mode for the items that were clicked in Collection enclosed = getFullyEnclosedItems(clickedIn); if (enclosed.size() == 0) { MessageBay .displayMessage("Insufficient permission to copy items"); } else { copies = copy(enclosed); clearParent(copies); ItemUtils.Justify(copies); pickup(copies); for (Item i : clickedIn) { i.setHighlightMode(HighlightMode.None); } } // otherwise, create a rectangle } else { Item on = FrameUtils.onItem(DisplayIO.getCurrentFrame(), MouseX, MouseY, true); // if its on a line then create a line from that line if (on instanceof Line && on.hasPermission(UserAppliedPermission.full)) { Line onLine = (Line) on; Line newLine = onLine.copy(); Item end = newLine.getEndItem(); Item start = newLine.getStartItem(); end.setPosition(MouseX, MouseY); start.setPosition(MouseX, MouseY); onLine.autoArrowheadLength(); // anchor the start anchor(start); // attach the line to the cursor pickup(end); List toMerge = new LinkedList(); toMerge.add(newLine.getStartItem()); toMerge.add(newLine); // Make sure the highlighting is shown when the end is // anchored end.setHighlightMode(Item.HighlightMode.Normal); merge(toMerge, on); // anchor(left); // FreeItems.getInstance().clear(); FrameGraphics.Repaint(); updateCursor(); return; } copies = new ArrayList(); Item[] d = new Item[RECTANGLE_CORNERS]; // create dots Frame current = DisplayIO.getCurrentFrame(); for (int i = 0; i < d.length; i++) { d[i] = current.createDot(); copies.add(d[i]); } current.nextDot(); // create lines copies.add(new Line(d[0], d[1], current.getNextItemID())); copies.add(new Line(d[1], d[2], current.getNextItemID())); copies.add(new Line(d[2], d[3], current.getNextItemID())); copies.add(new Line(d[3], d[0], current.getNextItemID())); new Constraint(d[0], d[1], current.getNextItemID(), Constraint.HORIZONTAL); new Constraint(d[2], d[3], current.getNextItemID(), Constraint.HORIZONTAL); new Constraint(d[1], d[2], current.getNextItemID(), Constraint.VERTICAL); new Constraint(d[3], d[0], current.getNextItemID(), Constraint.VERTICAL); anchor(new ArrayList(copies)); pickup(d[3]); d[3].setHighlightMode(HighlightMode.Normal); SessionStats.CreatedItems(copies); copies.clear(); } } } getInstance().refreshHighlights(); SessionStats.CopiedItems(copies); updateCursor(); FrameGraphics.Repaint(); } /** * */ private static void stampItemsOnCursor(boolean save) { List copies = copy(FreeItems.getInstance()); // MIKE: what does the below 2 lines do? for (Item i : copies) { i.setOffset(0, 0); i.setSave(save); } // The below code has a little problem withflicker when stamp // and dragging move(FreeItems.getInstance()); for (Item i : copies) { i.setHighlightMode(HighlightMode.None); } anchor(copies); } /** * @param enclosedItems * @return */ private static Collection getFullyEnclosedItems( Collection enclosure) { // copy the enclosedItems because the list will be modified Collection enclosedItems = new LinkedHashSet(enclosure); Collection toCopy = new LinkedHashSet(enclosedItems.size()); while (enclosedItems.size() > 0) { Item i = enclosedItems.iterator().next(); if (i.hasPermission(UserAppliedPermission.copy)) { Collection items = i.getAllConnected(); // Only copy if the entire shape is enclosed if (enclosedItems.containsAll(items)) { toCopy.addAll(items); } enclosedItems.removeAll(items); } else { enclosedItems.remove(i); } } return toCopy; } /** * Marks the items as not belonging to any specific frame. When picking up * items the parent will be automatically cleared for items on the current * frame but not for overlay items. This method ensures that overlay items * will also be cleared. This is useful when picking up copies of items from * an overlay (with the right mouse button) to ensure that the copy will be * anchored on the current frame rather than the overlay. When items are * picked up with the middle button clearParent should NOT be called. * * @param items * to have their parent cleared */ private static void clearParent(List items) { for (Item i : items) { // The next line is only necessary for circles... // Need to clean up/refactory some of this stuff i.getParentOrCurrentFrame().removeItem(i); i.setParent(null); } } private static boolean inWindow = false; /** * event called when mouse exits window * (can't use MouseListener callback since that callback doesn't * correctly receive all mouse exit events) */ public static void mouseExitedWindow(MouseEvent e) { inWindow = false; // System.out.println("Left window"); if(FreeItems.itemsAttachedToCursor()) { boolean cut = true; for(Item i : FreeItems.getInstance()) { for(Item j : i.getAllConnected()) { if(!FreeItems.getInstance().contains(j)) { cut = false; break; } } } if(cut) { ItemSelection.cut(); } } } public void mouseEntered(MouseEvent e) { // check if we are entering the window from outside of the window, or if we were just over a widget if(!inWindow) { inWindow = true; // if there is expeditee data on the clipboard that has not yet been autoPasted, autoPaste it Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard(); Transferable content = c.getContents(null); try { if(content.isDataFlavorSupported(ItemSelection.expDataFlavor)) { // Expeditee data ExpDataHandler expData = (ExpDataHandler)content.getTransferData(ItemSelection.expDataFlavor); if(expData.autoPaste) { List items = new ExpClipReader(FrameMouseActions.getX(), FrameMouseActions.getY()).read(expData.items); // generate new IDs and pickup FrameMouseActions.pickup(ItemUtils.CopyItems(items)); // update the clipboard contents so they won't be autoPasted again expData.autoPaste = false; String stringData = ""; Image imageData = null; if(content.isDataFlavorSupported(DataFlavor.stringFlavor)) { stringData = (String) content.getTransferData(DataFlavor.stringFlavor); } if(content.isDataFlavorSupported(DataFlavor.imageFlavor)) { imageData = (Image) content.getTransferData(DataFlavor.imageFlavor); } c.setContents(new ItemSelection(stringData, imageData, expData), null); } } } catch(Exception ex) { ex.printStackTrace(); } } } public void mouseExited(MouseEvent e) { } private boolean _overFrame; private int panStartX, panStartY; private boolean _isPanOp; public void mouseDragged(MouseEvent e) { _lastMouseDragged = e; // System.out.println("MouseDragged"); // Stop the longDepress mouse timer if the user drags above a threshold if (_MouseTimer.isRunning()) { if (Math.abs(e.getX() - _lastMouseClick.getX()) + Math.abs(e.getY() - _lastMouseClick.getY()) > 10) _MouseTimer.stop(); } if (_autoStamp) { stampItemsOnCursor(false); } /* * Have the free items follow the cursor if the user clicks in freespace * then moves. */ if (FreeItems.getInstance().size() > 0 && _lastClickedOn == null) { mouseMoved(e); return; } // panning the frame when dragging the mouse while shift-leftclicking if(ExperimentalFeatures.MousePan.get() && _overFrame && e.isShiftDown() && (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0 && (_isPanOp || (Math.max(Math.abs(panStartX - e.getX()), Math.abs(panStartY - e.getY())) > 5))) { int dX = (int) (e.getX() - MouseX); int dY = (int) (e.getY() - MouseY); Misc.pan(DisplayIO.getCurrentFrame(), dX, dY); MouseX = e.getX(); MouseY = e.getY(); _isPanOp = true; } // check if user is dragging across a text item if (_lastRanged != null) { // System.out.println(MouseY - e.getY()); MouseX = e.getX(); MouseY = e.getY(); int distance = _lastRanged.getY() - FrameMouseActions.getY(); if (distance <= 0) distance = FrameMouseActions.getY() - _lastRanged.getY() - _lastRanged.getBoundsHeight(); if (distance > UserSettings.NoOpThreshold.get()) { _lastRanged.clearSelectionEnd(); _isNoOp = true; } else { // update the ranged section _lastRanged.setSelectionEnd(DisplayIO.getMouseX(), FrameMouseActions.getY()); _isNoOp = false; } DisplayIO.setTextCursor(_lastRanged, Text.NONE, false, e .isShiftDown(), e.isControlDown(), false); FrameGraphics.Repaint(); return; } // if the user is dragging across a picture if (_lastCropped != null) { // If shift is down then the distance moved is the same in the x and // y MouseX = e.getX(); MouseY = e.getY(); if (e.isControlDown()) { int deltaX = Math.abs(e.getX() - _lastMouseClick.getX()); int deltaY = Math.abs(e.getY() - _lastMouseClick.getY()); if (deltaX > deltaY) { MouseY = _lastMouseClick.getY() + deltaX * (e.getY() > _lastMouseClick.getY() ? 1 : -1); } else { MouseX = _lastMouseClick.getX() + deltaY * (e.getX() > _lastMouseClick.getX() ? 1 : -1); } } // update the ranged section _lastCropped.setEndCrop(DisplayIO.getMouseX(), FrameMouseActions .getY()); FrameGraphics.Repaint(); return; } /* * This is the context of a user clicking in freespace an dragging onto * the edge of a line */ if ((_mouseDown == MouseEvent.BUTTON2 || _mouseDown == MouseEvent.BUTTON3) && _lastClickedOn == null && _lastClickedIn == null) { Item on = FrameUtils.onItem(DisplayIO.getCurrentFrame(), e.getX(), e.getY(), true); if (FreeItems.getInstance().size() == 0) { // if the user can spot-weld, show the virtual spot if (on instanceof Line) { Line line = (Line) on; line.showVirtualSpot(e.getX(), e.getY()); } if (on != null && on.isLineEnd()) { _lastHighlightedItem = on; on.setHighlightMode(Item.HighlightMode.Normal); } else if (_lastHighlightedItem != null) { _lastHighlightedItem .setHighlightMode(Item.HighlightMode.None); _lastHighlightedItem = null; } } } // Use the below calculation for better speed. If it causes problems // switch back to the Euclidean distance calculation if (Math.abs(MouseX - e.getX()) > UserSettings.NoOpThreshold.get() || Math.abs(MouseY - e.getY()) > UserSettings.NoOpThreshold.get()) _isNoOp = true; FrameGraphics.Repaint(); } private static MouseEvent _lastMouseMoved = null; private static Integer LastRobotX = null; private static Integer LastRobotY = null; // For some reason... sometimes the mouse move gets lost when moving the // mouse really quickly after clicking... // Use this timer to make sure it gets reset eventually if the Robot // generated event never arrives. private static Timer _RobotTimer = new Timer(200, new ActionListener() { public void actionPerformed(ActionEvent ae) { _RobotTimer.stop(); LastRobotX = null; LastRobotY = null; // System.out.println("RobotTimer"); } }); private static Timer _autoStampTimer = new Timer(200, new ActionListener() { public void actionPerformed(ActionEvent ae) { stampItemsOnCursor(false); } }); private static boolean _controlDown; private static boolean _shiftDown; private static OnNewFrameAction _onFrameAction = null; public static void setTDFCAction(OnNewFrameAction action) { _onFrameAction = action; } public static boolean isControlDown() { return _controlDown; } public static boolean isShiftDown() { return _shiftDown; } public static void setLastRobotMove(float x, float y) { // Make sure the system is in the right state while waiting for the // Robots event to arrive. MouseX = x; MouseY = y; // System.out.println("MouseMoved: " + MouseX + "," + MouseY + " " + // System.currentTimeMillis()); LastRobotX = Math.round(x); LastRobotY = Math.round(y); _RobotTimer.start(); } public static boolean isWaitingForRobot() { return LastRobotX != null; } /** * Updates the stored mouse position and highlights any items as necessary. */ public void mouseMoved(MouseEvent e) { mouseMoved(e, false); } private void mouseMoved(MouseEvent e, boolean shiftStateChanged) { // System.out.println("mouseMoved"); // System.out.println(_context); if (_context == CONTEXT_FREESPACE) FrameKeyboardActions.resetEnclosedItems(); // System.out.println(e.getX() + "," + e.getY() + " " + e.getWhen()); if (LastRobotX != null) { // Wait until the last Robot mouse move event arrives before // processing other events if (/* FreeItems.getInstance().size() == 0 || */ (LastRobotX == e.getX() && LastRobotY == e.getY())) { LastRobotX = null; LastRobotY = null; _RobotTimer.stop(); } else { // System.out.println("Ignored: " + // FreeItems.getInstance().size()); return; } } MouseX = e.getX(); MouseY = e.getY(); // System.out.println(MouseX + "," + MouseY); // Moving the mouse a certain distance removes the last edited text if // it is empty Text lastEdited = FrameUtils.getLastEdited(); if (lastEdited != null && lastEdited.getText().length() == 0 && lastEdited.getPosition().distance(e.getPoint()) > 20) { FrameUtils.setLastEdited(null); } // If shift is down then the movement is constrained if (_controlDown && FreeItems.getInstance().size() > 0) { // Check if we are rubber banding a line if (shiftStateChanged && rubberBanding()) { // Get the line end that is being rubber banded Item thisEnd = FreeItems.getInstance().get(0).isLineEnd() ? FreeItems .getInstance().get(0) : FreeItems.getInstance().get(1); Line line = (Line) (FreeItems.getInstance().get(0).isLineEnd() ? FreeItems .getInstance().get(1) : FreeItems.getInstance().get(0)); Item otherEnd = line.getOppositeEnd(thisEnd); int deltaX = Math.abs(e.getX() - otherEnd.getX()); int deltaY = Math.abs(e.getY() - otherEnd.getY()); // Check if its a vertical line if (deltaX < deltaY / 2) { // otherEnd.setX(thisEnd.getX()); // MouseX = otherEnd.getX(); if (shiftStateChanged) { new Constraint(thisEnd, otherEnd, thisEnd .getParentOrCurrentFrame().getNextItemID(), Constraint.VERTICAL); } } // Check if its horizontal else if (deltaY <= deltaX / 2) { // MouseY = otherEnd.getY(); // otherEnd.setY(thisEnd.getY()); if (shiftStateChanged) { new Constraint(thisEnd, otherEnd, thisEnd .getParentOrCurrentFrame().getNextItemID(), Constraint.HORIZONTAL); } } else { // Add DIAGONAL constraints // if (deltaX > deltaY) { // otherEnd.setY(thisEnd.getY() + deltaX // * (e.getY() < otherEnd.getY() ? 1 : -1)); // } else { // otherEnd.setX(thisEnd.getX() + deltaY // * (e.getX() < otherEnd.getX() ? 1 : -1)); // } if (shiftStateChanged) { int constraint = Constraint.DIAGONAL_NEG; // Check if the slope is positive if ((thisEnd.getY() - otherEnd.getY()) / (double) (thisEnd.getX() - otherEnd.getX()) > 0.0) { constraint = Constraint.DIAGONAL_POS; } new Constraint(thisEnd, otherEnd, thisEnd .getParentOrCurrentFrame().getNextItemID(), constraint); } } }// If its a lineend attached to two lines lengthen the shorter // so it is the same length as the longer line else if (FreeItems.getInstance().size() == 3) { // check if we are rubber banding the corner of a shape Item thisEnd = getShapeCorner(FreeItems.getInstance()); if (thisEnd != null) { Line line1 = thisEnd.getLines().get(0); Line line2 = thisEnd.getLines().get(1); // Check if the two lines are constrained and hence it is a // rectangle Integer c1 = line1.getPossibleConstraint(); Integer c2 = line2.getPossibleConstraint(); if (c1 != null && c2 != null) { // This is the case of a constrained rectangle if ((c2 == Constraint.VERTICAL || c2 == Constraint.HORIZONTAL) && (c1 == Constraint.VERTICAL || c1 == Constraint.HORIZONTAL) && (c1 != c2)) { Line vLine = line2; Line hLine = line1; if (c1 == Constraint.VERTICAL) { vLine = line1; hLine = line2; } Item hOtherEnd = hLine.getOppositeEnd(thisEnd); Item vOtherEnd = vLine.getOppositeEnd(thisEnd); double vLength = Math .abs(vOtherEnd.getY() - MouseY); double hLength = Math .abs(hOtherEnd.getX() - MouseX); if (vLength > hLength) { MouseX = Math.round(hOtherEnd.getX() + vLength * (MouseX > hOtherEnd.getX() ? 1 : -1)); } else /* if (hLength > vLength) */{ MouseY = Math.round(vOtherEnd.getY() + hLength * (MouseY > vOtherEnd.getY() ? 1 : -1)); } } // } else if (c2 != null) { // // } // Other wise it is a not constrained shape so // constrain // the two lines lengths to be equal } else { Item lineEnd1 = line1.getOppositeEnd(thisEnd); Item lineEnd2 = line2.getOppositeEnd(thisEnd); double l1 = Line.getLength(lineEnd1.getPosition(), e .getPoint()); double l2 = Line.getLength(lineEnd2.getPosition(), e .getPoint()); double l3 = Line.getLength(lineEnd1.getPosition(), lineEnd2.getPosition()); // l1 needs to be the shorter end if (l1 > l2) { Item temp = lineEnd1; lineEnd1 = lineEnd2; lineEnd2 = temp; double tempL = l1; l1 = l2; l2 = tempL; } // Now use the cosine rule to calculate the angle // between l1 and l3 double cosTheta = (l1 * l1 + l3 * l3 - l2 * l2) / (2 * l1 * l3); // now calculate the new length for the lines using cos // rule double l_new = l3 / (2 * cosTheta); double ratio = l_new / l1; MouseX = Math.round((e.getX() - lineEnd1.getX()) * ratio) + lineEnd1.getX(); MouseY = Math.round((e.getY() - lineEnd1.getY()) * ratio) + lineEnd1.getY(); } } } } else if (shiftStateChanged && !_controlDown && rubberBanding()) { // Get the line end that is being rubber banded Item thisEnd = FreeItems.getInstance().get(0).isLineEnd() ? FreeItems .getInstance().get(0) : FreeItems.getInstance().get(1); thisEnd.removeAllConstraints(); } if (_lastMouseMoved == null) _lastMouseMoved = e; _lastMouseMoved = e; refreshHighlights(); if (FreeItems.hasCursor()) { move(FreeItems.getCursor(), true); } if (FreeItems.itemsAttachedToCursor()) { move(FreeItems.getInstance()); // System.out.println(FreeItems.getInstance().size()); } if (_forceArrowCursor) updateCursor(); _forceArrowCursor = true; } public void refreshHighlights() { // ByMike: Get the item the mouse is hovering over Item click = FrameUtils.getCurrentItem(); Item on = null; // System.out.println(click); if (click != null) { on = click; // set the context if (on instanceof Line) _context = CONTEXT_AT_LINE; else if (on instanceof Dot) _context = CONTEXT_AT_DOT; else if (on instanceof Text) { _context = CONTEXT_AT_TEXT; } if (FreeItems.getInstance().size() > 0) _alpha = 60; else _alpha = -1; } else { _context = CONTEXT_FREESPACE; _alpha = -1; } // if the user is pointing at an item, highlight it if (on != null && !FreeItems.getInstance().contains(on)) { // if the user can spot-weld, show the virtual spot if (FreeItems.getInstance().size() == 2 && on instanceof Line) { Line line = (Line) on; Item freeItem0 = FreeItems.getInstance().get(0); Item freeItem1 = FreeItems.getInstance().get(1); Item lineEnd = freeItem0.isLineEnd() ? freeItem0 : (freeItem1 .isLineEnd() ? freeItem1 : null); if (lineEnd != null) { if (_mouseDown == 0) line.showVirtualSpot(lineEnd, DisplayIO.getMouseX(), FrameMouseActions.getY()); } else // The user is pointing at another point or text item // etc FrameGraphics.changeHighlightMode(on, Item.HighlightMode.Normal); } else { // FrameGraphics.ChangeSelectionMode(on, // Item.SelectedMode.Connected); // TODO: The method below is for the most part redundant on = FrameGraphics.Highlight(on.getEditTarget()); } // if the last item highlighted is still highlighted, clear it if (_lastHoldsHighlight) { _lastHoldsHighlight = false; for (Item i : DisplayIO.getCurrentFrame().getItems()) if (i.isHighlighted() && i != on) FrameGraphics.changeHighlightMode(i, Item.HighlightMode.None); } // if the user is not pointing at an item, check for enclosure // highlighting } else if (on == null) { Collection enclosure = FrameUtils.getEnclosingLineEnds(); if (enclosure != null && enclosure.size() > 0) { Item firstLineEnd = enclosure.iterator().next(); HighlightMode hm; if(isShiftDown()) { hm = HighlightMode.Connected; } else { hm = HighlightMode.Enclosed; } if (firstLineEnd.getLines().size() > 1 && // check that the enclosure is not part of a point being // dragged in space !ContainsOneOf(enclosure, FreeItems.getInstance())) { on = firstLineEnd.getLines().get(0); // System.out.println(on == null ? "Null" : // on.toString()); FrameGraphics.changeHighlightMode(on, hm); } else if (firstLineEnd instanceof XRayable) { on = firstLineEnd; FrameGraphics.changeHighlightMode(firstLineEnd, hm); } _context = CONTEXT_AT_ENCLOSURE; } else if (_lastHighlightedItem != null) { // System.out.println("LastHighlightedItem"); _lastHoldsHighlight = false; } } // disable cursor changes when the cursor has items attached if (FreeItems.itemsAttachedToCursor() && DisplayIO.getCursor() != Item.TEXT_CURSOR) _forceArrowCursor = false; // setLastHighlightedItem(on); if (_lastHighlightedItem != null && _lastHighlightedItem != on && !_lastHoldsHighlight) { // Turn off the highlighting only if // the last highlighted item is not connected to the currentItem // Otherwise we get flickering in transition from connected to // normal mode while moving the cursor along a line. if (on == null || (!on.getAllConnected().contains(_lastHighlightedItem))) { FrameGraphics.changeHighlightMode(_lastHighlightedItem, Item.HighlightMode.None); } } _lastHighlightedItem = on; } private boolean ContainsOneOf(Collection enclosure, Collection freeItems) { if (freeItems == null) return false; for (Item i : freeItems) { if (enclosure.contains(i)) return true; } return false; } /** * Checks if lines are being rubber banded. * * @return true if the user is rubberBanding one or more lines */ private static boolean rubberBanding() { if (FreeItems.getInstance().size() != 2) { return false; } // if rubber-banding, there will be 1 lineend and the rest will be lines boolean foundLineEnd = false; for (Item i : FreeItems.getInstance()) { if (i.isLineEnd()) { if (foundLineEnd) { return false; } foundLineEnd = true; } else if (!(i instanceof Line) || !i.isVisible()) { return false; } } return true; } /** * Updates the current mouse cursor to whatever it should be. i.e. Hidden * when rubber-banding lines, otherwise default (arrow) */ public static void updateCursor() { if (rubberBanding()) { DisplayIO.setCursor(Item.HIDDEN_CURSOR); return; } // This is to make sure the TEXT_CURSOR doesnt get inadvertantly turned // off! Item on = FrameUtils.getCurrentItem(); if (on != null && on instanceof Text) { return; } DisplayIO.setCursor(Item.DEFAULT_CURSOR); } public static void setHighlightHold(boolean hold) { _lastHoldsHighlight = hold; } public static void resetOffset() { if (FreeItems.itemsAttachedToCursor()) { _offX = DisplayIO.getMouseX() - FreeItems.getInstance().get(0).getX() + FreeItems.getInstance().get(0).getOffset().x; _offY = getY() - FreeItems.getInstance().get(0).getY() + FreeItems.getInstance().get(0).getOffset().y; } } /** * Moves the items to the current mouse position (plus the current offset) * * @param toMove */ static void move(Collection toMove) { move(toMove, false); } static void move(Collection toMove, boolean cursor) { // Gets the origin of the first item to move int xPos = (DisplayIO.getMouseX() - (cursor ? 0 : _offX)); Item firstDot = toMove.iterator().next(); int deltax = firstDot.getX() - xPos; int deltay = firstDot.getY() - (getY() - (cursor ? 0 : _offY)); for (Item move : toMove) { move.setPosition(move.getX() - deltax, move.getY() - deltay); if (!cursor && move instanceof Text) { ((Text) move).setAlpha(_alpha); } } FrameGraphics.requestRefresh(true); // FrameGraphics.refresh(true); } private static void load(String toLoad, boolean addToHistory) { if (FrameIO.isValidFrameName(toLoad)) { DisplayIO.clearBackedUpFrames(); FrameUtils.DisplayFrame(toLoad, addToHistory, true); } else { MessageBay.errorMessage(toLoad + " is not a valid frame name."); } } private static void back() { DisplayIO.Back(); // repaint things if necessary if (FreeItems.itemsAttachedToCursor()) move(FreeItems.getInstance()); if (FreeItems.hasCursor()) move(FreeItems.getCursor(), true); } private static void forward() { DisplayIO.Forward(); // repaint things if necessary if (FreeItems.itemsAttachedToCursor()) move(FreeItems.getInstance()); if (FreeItems.hasCursor()) move(FreeItems.getCursor(), true); } /** * Returns true if the mouse moved during TDFC. This will happen if there is * a start annotation item on the frame. * * @param linker * @return */ public static boolean tdfc(Item linker) throws RuntimeException { // if this is a non-usable item if (linker.getID() < 0) return false; // Check if its an image that can be resized to fit a box // around it String text = linker.getText(); boolean isVector = text.equals("@v") || text.equals("@av"); boolean isFrameImage = text.equals("@f"); boolean isBitmap = false; // text.equals("@b"); if (isVector || isFrameImage || isBitmap) { Collection enclosure = FrameUtils.getEnclosingLineEnds(linker .getPosition()); if (enclosure != null) { for (Item i : enclosure) { if (i.isLineEnd() && i.isEnclosed()) { if (!isVector) DisplayIO.getCurrentFrame().removeAllItems( enclosure); Rectangle rect = i.getEnclosedRectangle(); long width = Math.round(rect.getWidth()); if (isVector) { NumberFormat nf = Vector.getNumberFormatter(); linker.setText(linker.getText() + ": " + nf.format((width / FrameGraphics .getMaxFrameSize().getWidth()))); } else { linker.setText(linker.getText() + ": " + width); } linker.setPosition(new Point(rect.x, rect.y)); linker.setThickness(i.getThickness()); linker.setBorderColor(i.getColor()); break; } } if (!isVector) FrameMouseActions.deleteItems(enclosure, false); } } boolean mouseMoved; linker.getParent().setChanged(true); Frame next = FrameIO.CreateNewFrame(linker, _onFrameAction); linker.setLink("" + next.getNumber()); for (Item i : next.getTextItems()) { // Set the link for @Parent annotation item if one if (ItemUtils.startsWithTag(i, ItemUtils.TAG_PARENT) && i.getLink() == null) { Frame parent = linker.getParentOrCurrentFrame(); i.setLink(parent.getName()); } else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_BACKUP, false)) { // Delink backup tag if it is on the frame i.setLink(null); } } FrameUtils.DisplayFrame(next, true, true); FrameUtils.setTdfcItem(linker); mouseMoved = next.moveMouseToDefaultLocation(); // this needs to be done if the user doesnt move the mouse before doing // tdfc while the cursor is set to the text cursor DisplayIO.setCursor(Item.DEFAULT_CURSOR); // This needs to be done in case there was a @start on the frame which // triggers changed to be set to true when it should stay as false next.setChanged(false); return mouseMoved; } /** * Creates a new Text item and fills it with particular attributes extracted * from the given Item. Note: Users always have permission to extract * attributes, so it is not checked. * * @param toExtract * Item containing the Item to extract the attributes from. */ private static void extractAttributes(Item toExtract) { if (toExtract == null || toExtract == null) return; if (FreeItems.itemsAttachedToCursor()) return; Item attribs; Item item = toExtract; // Extract the frames attributes when the user clicks on the frame name FrameGraphics.changeHighlightMode(item, HighlightMode.None); if (item.isFrameName()) attribs = AttributeUtils.extractAttributes(item.getParent()); else { attribs = AttributeUtils.extractAttributes(item); } if (attribs == null) MessageBay .displayMessage("All attributes of that item are default values."); else { // Give the attribute text item the color of the item for which // attributes are being extracted. // attribs.setColor(item.getColor()); pickup(attribs); } } public static void delete(Item toDelete) { boolean bRecalculate = false; FrameUtils.setLastEdited(null); _offX = _offY = 0; Frame currentFrame = DisplayIO.getCurrentFrame(); // check if the user is pointing at the frame's framename if (toDelete != null && toDelete == currentFrame.getNameItem()) { currentFrame.clear(false); FrameGraphics.Repaint(); return; } // if the user is deleting items attached to the cursor if (FreeItems.itemsAttachedToCursor()) { // if this is an item-swap if (FreeItems.getInstance().size() == 1 && FreeItems.getInstance().get(0) instanceof Text && toDelete != null && toDelete instanceof Text) { // check permissions if (!toDelete.hasPermission(UserAppliedPermission.full)) { MessageBay .displayMessage("Insufficient permission to swap Item text"); return; } Text anchored = (Text) toDelete; Text free = (Text) FreeItems.getInstance().get(0); SessionStats.DeletedItem(free); // List temp = anchored.getText(); anchored.setText(free.getText()); anchored.setFormula(free.getFormula()); // free.setTextList(temp); FreeItems.getInstance().clear(); anchored.getParent().setChanged(true); bRecalculate |= free.recalculateWhenChanged(); bRecalculate |= anchored.recalculateWhenChanged(); // update the offset since the text has changed _offX = DisplayIO.getMouseX() - anchored.getX() + anchored.getOffset().x; _offY = getY() - anchored.getY() + anchored.getOffset().y; } else { // if shift is pressed delete the entire shape attached to the dot if(isShiftDown()) { List tmp = new ArrayList(FreeItems.getInstance()); for(Item i : tmp) { // remove entire rectangles instead of just the corner if(i instanceof Dot) { FreeItems.getInstance().addAll(i.getAllConnected()); for(Item j : i.getAllConnected()) { if(j instanceof Dot) { FreeItems.getInstance().addAll(j.getLines()); } } } } } deleteItems(FreeItems.getInstance()); } // reset the mouse cursor updateCursor(); // the user is not pointing at an item } else if (toDelete == null) { // if the user is pointing inside a closed shape, delete it Collection items = null; // if shift is down, only delete the enclosing shape (ignore the items inside) if(isShiftDown()) { Collection tmp = FrameUtils.getEnclosingLineEnds(); if(tmp != null) { items = new ArrayList(); items.addAll(tmp); for(Item i : tmp) { if(i instanceof Dot) { items.addAll(((Dot)i).getLines()); } } } } else { items = FrameUtils.getCurrentItems(null); } if (items != null) { Collection toRemove = new LinkedHashSet(items .size()); for (Item ip : items) { if (ip.hasPermission(UserAppliedPermission.full)) { // Only include lines if one of their enpoints are also // being removed if (ip instanceof Line) { Line l = (Line) ip; Item end = l.getEndItem(); Item start = l.getStartItem(); // If one end of a line is being delted, remove the // other end if all its connecting lines are being // delted if (items.contains(end)) { if (!items.contains(start) && items.containsAll(start.getLines())) { toRemove.add(start); } } else if (items.contains(start)) { if (items.containsAll(end.getLines())) { toRemove.add(end); } } else { continue; } } toRemove.add(ip); } } deleteItems(toRemove); // reset the mouse cursor updateCursor(); FrameGraphics.Repaint(); // otherwise this is an undo command } else { if(isControlDown()) { DisplayIO.getCurrentFrame().redo(); } else { DisplayIO.getCurrentFrame().undo(); } } return; // this is a delete command } else { // check permissions if (!toDelete.hasPermission(UserAppliedPermission.full)) { Item editTarget = toDelete.getEditTarget(); if (editTarget != toDelete && editTarget.hasPermission(UserAppliedPermission.full)) { toDelete = editTarget; } else { MessageBay .displayMessage("Insufficient permission to delete item"); return; } } Frame parent = toDelete.getParent(); if (parent != null) { parent.setChanged(true); } Collection toUndo = null; if (toDelete.isLineEnd()) { // delete the entire connected shape if shift is down if(isShiftDown()) { List tmp = new ArrayList(); tmp.add(toDelete); // remove entire shape instead of just the corner tmp.addAll(toDelete.getAllConnected()); for(Item j : toDelete.getAllConnected()) { if(j instanceof Dot) { tmp.addAll(j.getLines()); } } deleteItems(tmp); return; } else { toUndo = deleteLineEnd(toDelete); } // delete the entire connected shape if shift is down, unless we're hovering the end of the line } else if (toDelete instanceof WidgetEdge) { // must notify // widgets that they // are being deleted ((WidgetEdge) toDelete).getWidgetSource().onDelete(); toUndo = toDelete.getConnected(); } else if (toDelete instanceof Line && isShiftDown() || toDelete.getHighlightMode() == Item.HighlightMode.Disconnect) { Line line = (Line) toDelete; Item start = line.getStartItem(); Item end = line.getEndItem(); Collection delete = new LinkedList(); delete.add(toDelete); if (end.getLines().size() == 1) { delete.add(end); } else { end.removeLine(line); } if (start.getLines().size() == 1) { delete.add(start); } else { start.removeLine(line); } toUndo = delete; } else { bRecalculate |= toDelete.recalculateWhenChanged(); toUndo = toDelete.getConnected(); // copy(toDelete.getConnected()); } SessionStats.DeletedItems(toUndo); if (parent != null) { parent.addToUndoDelete(toUndo); parent.removeAllItems(toUndo); // toDelete.getConnected() } // reset the mouse cursor updateCursor(); if (parent != null) // ItemUtils.EnclosedCheck(parent.getItems()); ItemUtils.Justify(parent); if (toDelete.hasOverlay()) { FrameUtils.Parse(parent, false, false); FrameGraphics.requestRefresh(false); } DisplayIO.setCursor(Item.DEFAULT_CURSOR); } currentFrame.notifyObservers(bRecalculate); FrameGraphics.Repaint(); } public static void deleteItems(Collection itemList) { deleteItems(itemList, true); } public static void deleteItems(Collection itemList, boolean addToUndo) { boolean bReparse = false; boolean bRecalculate = false; SessionStats.DeletedItems(itemList); List modifiedFrames = new LinkedList(); // Get a list of all the modified frames for (Item i : itemList) { Frame parent = i.getParent(); if (parent != null) modifiedFrames.add(parent); i.setHighlightMode(HighlightMode.None); bReparse |= i.hasOverlay(); bRecalculate |= i.recalculateWhenChanged(); } // If they are all free items then add the current frame if (modifiedFrames.size() == 0) { modifiedFrames.add(DisplayIO.getCurrentFrame()); } Collection toUndo = new LinkedHashSet(); // disconnect any connected items for (Item i : itemList) { // Only delete the current item if have not already deleted. // This is especially important for heavy duty widgets - so they // do not have to expire several times per delete. if (toUndo.contains(i)) continue; // Make sure text items attached to cursor are reset back to the // transparency they should have. if (i instanceof Text) { ((Text) i).setAlpha(-1); } if (i.getLines().size() > 0) { Collection toDelete = deleteLineEnd(i); if (addToUndo) { // add the copied items to the undo stack for (Item itemToUndo : toDelete) { if (!toUndo.contains(itemToUndo)) toUndo.add(itemToUndo); } } } else if (!toUndo.contains(i)) { if (addToUndo) toUndo.add(i); // Why was is this a copy } } for (Frame f : modifiedFrames) { f.removeAllItems(itemList); // ItemUtils.EnclosedCheck(f.getItems()); ItemUtils.Justify(f); } // TODO: How should undelete deal with undo when items are removed from // the current frame as well as the overlay frame Frame currentFrame = DisplayIO.getCurrentFrame(); currentFrame.addToUndoDelete(itemList); itemList.clear(); if (bReparse) { FrameUtils.Parse(currentFrame, false, false); /* * TODO check if I need to recalculate even if reparse occurs, here * and in anchor, pickup etc */ } else { currentFrame.notifyObservers(bRecalculate); } } private static Collection deleteLineEnd(Item lineEnd) { if (lineEnd instanceof WidgetCorner) { // Brook WidgetCorner wc = (WidgetCorner) lineEnd; Frame parent = wc.getWidgetSource().getParentFrame(); // Remove from the parent frame if (parent != null) { parent.removeAllItems(wc.getWidgetSource().getItems()); } wc.getWidgetSource().onDelete(); // Changes the widgets // corner/edges ID's... return wc.getWidgetSource().getItems(); } else { // // create a backup copy of the dot and its lines // List copy = copy(lineEnd.getConnected()); // // // Remove lines from their anchored dots // // note: the line is kept so that it can be properly restored // for (Item ic : copy) { // if (ic instanceof Line) { // Line line = (Line) ic; // // Remove the line from the item that is not the copy of the // // line end being deletedF // if (!copy.contains(line.getStartItem())) // line.getStartItem().removeLine(line); // if (!copy.contains(line.getEndItem())) // line.getEndItem().removeLine(line); // } // } Collection copy = lineEnd.getConnected(); // remove all lines being deleted for (Item ic : lineEnd.getConnected()) { if (ic instanceof Line && ((Line) ic).getOppositeEnd(lineEnd) != null) { Line line = (Line) ic; // Invalidate the line to make sure we dont get any ghost // arrowheads. ic.invalidateAll(); Item d = line.getOppositeEnd(lineEnd); d.removeLine(line); // if the dot was only part of one line, it can be // removed if (d.getLines().size() == 0) { if (d.getParent() != null) d.getParent().removeItem(d); if (!copy.contains(d)) copy.add(d); } if (lineEnd.getParent() != null) lineEnd.getParent().removeItem(ic); } } return copy; } } private static void removeAllLinesExcept(Item from, Item but) { List lines = new LinkedList(); lines.addAll(from.getLines()); for (Line line : lines) if (line.getOppositeEnd(from) != but) from.removeLine(line); List consts = new LinkedList(); consts.addAll(from.getConstraints()); for (Constraint c : consts) if (c.getOppositeEnd(from) != but) from.removeConstraint(c); } public static Collection merge(List merger, Item mergee) { assert (mergee != null); if (mergee.isFrameName()) { return mergee.getParent().merge(merger); } // if(mergee instanceof XRayable) // return merger; // check for rectangle merging if (merger.size() == 3 && mergee.getLines().size() == 2) { Item corner = getShapeCorner(merger); // if this is a corner of a shape if (corner != null) { Collection allConnected = corner.getAllConnected(); // Check if we are collapsing a rectangle if (allConnected.size() == 8 && allConnected.contains(mergee)) { DisplayIO.setCursorPosition(mergee.getPosition()); DisplayIO.getCurrentFrame().removeAllItems(allConnected); // find the point opposite corner... Item opposite = null; List lines = corner.getLines(); for (Line l : lines) { allConnected.remove(l.getOppositeEnd(corner)); } allConnected.remove(corner); for (Item i : allConnected) { if (i.isLineEnd()) { opposite = i; break; } } assert (opposite != null); // check if the rectangle is small enough that it should be // collapsed to a single point int x1 = Math.abs(opposite.getX() - mergee.getX()); int x2 = Math.abs(opposite.getY() - mergee.getY()); int distance = (int) Math.sqrt(Math.pow(x1, 2) + Math.pow(x2, 2)); if (distance < RECTANGLE_TO_POINT_THRESHOLD) { mergee.removeAllConstraints(); mergee.removeAllLines(); mergee.setThickness(4 * mergee.getThickness()); return mergee.getAllConnected(); } else { removeAllLinesExcept(mergee, opposite); removeAllLinesExcept(opposite, mergee); return mergee.getAllConnected(); } } } } List remain = new ArrayList(); Item res = null; for (Item i : merger) { if (!i.isVisible()) continue; // check for link merging if (i instanceof Text && FrameIO.isValidFrameName((((Text) i).getFirstLine())) && FrameIO.canAccessFrame((((Text) i).getFirstLine()))) { // check that we can actually access the frame this link // corresponds to mergee.setLink(((Text) i).getFirstLine()); } else { // check for attribute merging if (i instanceof Text && !i.isLineEnd()) { Text txt = (Text) i; // if this is not an attribute merge if (!AttributeUtils.setAttribute(mergee, txt)) { // set mouse position for text merges if (mergee instanceof Text) { ((Text) mergee).insertText(txt.getText(), DisplayIO .getMouseX(), FrameMouseActions.getY()); //Delete the item which had its text merged txt.delete(); return remain; } else if (mergee instanceof WidgetCorner) { if (merger.size() == 1 && txt.getLink() != null) { // If the text item is linked then use that ((WidgetCorner) mergee).setLink(txt .getAbsoluteLink(), txt); } else { remain.addAll(merger); } return remain; } else if (mergee instanceof WidgetEdge) { if (merger.size() == 1 && txt.getLink() != null) { // If the text item is linked then use that ((WidgetEdge) mergee).setLink(txt .getAbsoluteLink(), txt); } else { remain.addAll(merger); } return remain; } else if (mergee instanceof Dot) { DisplayIO.setCursorPosition(mergee.getPosition()); txt.setPosition(mergee.getPosition()); txt.setThickness(mergee.getThickness()); Frame parent = mergee.getParent(); parent.removeItem(mergee); anchor(txt); // change the end points of the lines to the text // item while (mergee.getLines().size() > 0) { Line l = mergee.getLines().get(0); l.replaceLineEnd(mergee, txt); } break; } // TODO tidy this up... // Dot override doesnt use the x and y coords // Text does... but could be removed res = mergee.merge(i, DisplayIO.getMouseX(), getY()); if (res != null) { remain.add(res); } } } else { if (mergee.isLineEnd()) { DisplayIO.setCursorPosition(mergee.getPosition()); } // Moving the cursor ensures shapes are anchored correctly res = mergee.merge(i, DisplayIO.getMouseX(), getY()); if (res != null) remain.addAll(res.getConnected()); } } } updateCursor(); mergee.getParent().setChanged(true); ItemUtils.EnclosedCheck(mergee.getParent().getItems()); // Mike: Why does parse frame have to be called?!? FrameUtils.Parse(mergee.getParent()); return remain; } /** * Picks up an item on a frame. * * @param toGrab * item to be picked up * @param removeItem * true if the item should be removed from the frame */ public static void pickup(Item toGrab) { if (toGrab.isFrameName()) return; if (!toGrab.hasPermission(UserAppliedPermission.full)) { if (toGrab.getEditTarget() != toGrab) { pickup(toGrab.getEditTarget()); } else { MessageBay .displayMessage("Insufficient permission pickup the item"); } return; } if (toGrab instanceof Circle) toGrab.setHighlightMode(HighlightMode.Connected); // Dont set the highlight mode if a vector is being picked up else if (toGrab.isVisible()) { toGrab.setHighlightMode(HighlightMode.Normal); } // Brook: If the widget corner is being picked up. Instead refer to // picking up the edge for fixed-sized widgets so it is not so confusing if (toGrab instanceof WidgetCorner) { WidgetCorner wc = (WidgetCorner) toGrab; if (wc.getWidgetSource().isFixedSize()) { for (Item i : toGrab.getConnected()) { if (i instanceof WidgetEdge) { toGrab = i; break; } } } } pickup(toGrab.getConnected()); } public static void pickup(Collection toGrab) { if (toGrab == null || toGrab.size() == 0) return; boolean bReparse = false; boolean bRecalculate = false; Frame currentFrame = DisplayIO.getCurrentFrame(); String currentFrameName = currentFrame.getName(); Iterator iter = toGrab.iterator(); while (iter.hasNext()) { Item i = iter.next(); if (!i.hasPermission(UserAppliedPermission.full)) { iter.remove(); continue; } if (i.equals(_lastHighlightedItem)) _lastHighlightedItem = null; bRecalculate |= i.recalculateWhenChanged(); // i.setSelectedMode(SelectedMode.None); // Check if it has a relative link if so make it absolute i.setAbsoluteLink(); // parent may be null if (i.getParent() != null) { i.getParent().removeItem(i); if (currentFrameName.equals(i.getParent().getName())) i.setParent(null); } FreeItems.getInstance().add(i); i.setFloating(true); // If its a vector pick up a copy of the stuff on the vector frame if (i.hasVector()) { bReparse = true; Frame overlayFrame = FrameIO.LoadFrame(i.getAbsoluteLink()); Collection copies = ItemUtils.CopyItems(overlayFrame .getNonAnnotationItems(false), i.getVector()); for (Item copy : copies) { FreeItems.getInstance().add(copy); copy.setEditTarget(i); copy.setFloating(true); copy.setParent(null); // copy.setHighlightMode(HighlightMode.Connected); } } } currentFrame.change(); _lastHighlightedItem = null; updateCursor(); // if there are multiple items in the list, determine which to use for // offset calculations if (toGrab.size() > 1) { for (Item i : toGrab) { // MIKE: Movement goes haywire if these are removed because Line // class returns 0 for getX if (!(i instanceof Line) && !(i instanceof XRayable)) { _offX = DisplayIO.getMouseX() - i.getX() + i.getOffset().x; _offY = getY() - i.getY() + i.getOffset().y; // make the offset item the first item in the list (so // move method knows which item to use) FreeItems.getInstance().set( FreeItems.getInstance().indexOf(i), FreeItems.getInstance().get(0)); FreeItems.getInstance().set(0, i); break; } } move(FreeItems.getInstance()); ItemUtils.EnclosedCheck(toGrab); // otherwise, just use the first item } else if (toGrab.size() == 1) { Item soleItem = toGrab.iterator().next(); _offX = DisplayIO.getMouseX() - soleItem.getX() + soleItem.getOffset().x; _offY = getY() - soleItem.getY() + soleItem.getOffset().y; // Now call move so that if we are on a message in the message box // It doesnt appear up the top of the scree!! move(toGrab); } else { MessageBay .displayMessage("Insufficient permission to pickup the items"); } if (bReparse) FrameUtils.Parse(currentFrame, false, false); else currentFrame.notifyObservers(bRecalculate); FrameGraphics.Repaint(); } private static Line createLine() { Frame current = DisplayIO.getCurrentFrame(); // create the two endpoints Item end = DisplayIO.getCurrentFrame().createDot(); Item start = DisplayIO.getCurrentFrame().createDot(); // create the Line Line line = new Line(start, end, current.getNextItemID()); line.autoArrowheadLength(); // anchor the start anchor(start); // attach the line to the cursor pickup(end); _lastHighlightedItem = null; // TODO figure out how to get the end to highlight // end.setSelectedMode(SelectedMode.Normal); // end.setSelectedMode(SelectedMode.None); return line; } /** * Returns a list of copies of the list passed in * * @param toCopy * The list of items to copy * @return A List of copied Items */ private static List copy(Collection toCopy) { return ItemUtils.CopyItems(toCopy); } public static void anchor(Item toAnchor, boolean checkEnclosure) { // Only anchor items we have full permission over... i.e. don't anchor vector items if (!toAnchor.hasPermission(UserAppliedPermission.full)) return; toAnchor.anchor(); if (checkEnclosure) { ItemUtils.EnclosedCheck(toAnchor.getParentOrCurrentFrame() .getItems()); FrameGraphics.Repaint(); } } public static void anchor(Item toAnchor) { anchor(toAnchor, true); } public static void anchor(Collection toAnchor) { boolean bReparse = false; boolean bRecalculate = false; // Need to make sure we check enclosure for overlays etc Set checkEnclosure = new HashSet(); // Create a clone of toAnchor since in the proccess of anchoring items // they can change the state of the toAnchor collection and thus create // concurrent modification exceptions. // This is especially needed for widgets being removed when anchored: // since they // currently are composed of 8 items this is vital. In the new revision // of // widgets being implemented as a single item this this can be // depreciated // however it may be useful for other applications. Collection toAnchorCopy = new ArrayList(toAnchor); for (Item i : toAnchorCopy) { if (toAnchor.contains(i)) { // since to anchor could change while // anchoring // if (!i.hasVector()) anchor(i, false); checkEnclosure.add(i.getParentOrCurrentFrame()); bReparse |= i.hasOverlay(); bRecalculate |= i.recalculateWhenChanged(); } } toAnchor.clear(); // Check enclosure for all the frames of the items that were anchored for (Frame f : checkEnclosure) { // ItemUtils.EnclosedCheck(f.getItems()); ItemUtils.Justify(f); } Frame currentFrame = DisplayIO.getCurrentFrame(); if (bReparse) FrameUtils.Parse(currentFrame, false, false); else { currentFrame.notifyObservers(bRecalculate); } FrameGraphics.Repaint(); } /* * private boolean mouseMovedRecently() { Date now = new Date(); * * return now.getTime() - _lastMouseMovement.getTime() < 150; } */ public void mouseWheelMoved(MouseWheelEvent arg0) { Navigation.ResetLastAddToBack(); int clicks = Math.abs(arg0.getWheelRotation()); if (FreeItems.getInstance().size() == 2) { if ((FreeItems.getInstance().get(0).isLineEnd() && FreeItems .getInstance().get(1) instanceof Line) || (FreeItems.getInstance().get(1).isLineEnd() && FreeItems .getInstance().get(0) instanceof Line)) { Line line; if (FreeItems.getInstance().get(0) instanceof Line) line = (Line) FreeItems.getInstance().get(0); else line = (Line) FreeItems.getInstance().get(1); // User must do multiple clicks to toggle the line if (clicks >= MOUSE_WHEEL_THRESHOLD) { if (arg0.isShiftDown()) line.toggleArrowHeadRatio(arg0.getWheelRotation()); else line.toggleArrowHeadLength(arg0.getWheelRotation()); // line.getParent().change(); FrameGraphics.Repaint(); } } } else if (arg0.getWheelRotation() != 0 && arg0.isShiftDown()) { FunctionKey rotationType = FunctionKey.SizeUp; if (arg0.getWheelRotation() > 0) { rotationType = FunctionKey.SizeDown; } FrameKeyboardActions.functionKey(rotationType, 1, arg0 .isShiftDown(), arg0.isControlDown()); } else if (clicks >= MOUSE_WHEEL_THRESHOLD) { Item item = FrameUtils.getCurrentItem(); // if the user is not pointing to any item if (item == null) { FrameKeyboardActions.NextTextItem(null, arg0.getWheelRotation() > 0); return; } if (item instanceof Line || item instanceof Circle) { // check permissions if (!item.hasPermission(UserAppliedPermission.full)) { MessageBay .displayMessage("Insufficient permission to edit the Line"); return; } item.toggleDashed(arg0.getWheelRotation()); item.getParent().change(); FrameGraphics.Repaint(); return; } else if (item instanceof Text) { FrameKeyboardActions.NextTextItem(item, arg0.getWheelRotation() > 0); } } } /** * * @return the integer value for the last mouse button clicked. */ public static int getLastMouseButton() { if (_lastMouseClick == null) return MouseEvent.NOBUTTON; return _lastMouseClick.getButton(); } public static boolean isDelete(int modifiersEx) { int onMask = MouseEvent.BUTTON3_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK; return (modifiersEx & onMask) == onMask; } public static boolean isGetAttributes(int modifiersEx) { int onMask = MouseEvent.BUTTON3_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK; return (modifiersEx & onMask) == onMask; } public static boolean isTwoClickNoOp(int modifiersEx) { int onMask = MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK; return (modifiersEx & onMask) == onMask; } public static boolean wasDeleteClicked() { if (_lastMouseClick == null) return false; return isDelete(_lastMouseClickModifiers); } public static void control(KeyEvent ke) { for (Item i : FreeItems.getInstance()) { i.invalidateCommonTrait(ItemAppearence.PreMoved); } for (Item i : FreeItems.getCursor()) { i.invalidateCommonTrait(ItemAppearence.PreMoved); } _controlDown = ke.isControlDown(); if (_controlDown) { // TODO why are these two lines needed?!?! // _offX = 0; // _offY = 0; } else { resetOffset(); } if (_mouseDown > 0 && _lastMouseDragged != null) { MouseEvent me = _lastMouseDragged; _lastMouseDragged = new MouseEvent(ke.getComponent(), MouseEvent.NOBUTTON, ke.getWhen(), ke.getModifiers(), me .getX(), me.getY(), 0, false); _instance.mouseDragged(_lastMouseDragged); } else if (_lastMouseMoved != null) { MouseEvent me = _lastMouseMoved; _lastMouseMoved = new MouseEvent(ke.getComponent(), MouseEvent.NOBUTTON, ke.getWhen(), ke.getModifiers(), me .getX(), me.getY(), 0, false); _instance.mouseMoved(_lastMouseMoved, true); } updateCursor(); Help.updateStatus(); for (Item i : FreeItems.getInstance()) { i.invalidateCommonTrait(ItemAppearence.PostMoved); } for (Item i : FreeItems.getCursor()) { i.invalidateCommonTrait(ItemAppearence.PostMoved); } // TODO: Check why the new constrained line is not repainted immediately FrameGraphics.requestRefresh(true); FrameGraphics.refresh(true); } public static int getX() { return Math.round(MouseX); } public static int getY() { return Math.round(MouseY); } public static Item getlastHighlightedItem() { return _lastHighlightedItem; } public static Point getPosition() { return new Point(getX(), getY()); } public static Point getFreeItemsOffset() { return new Point(_offX, _offY); } public static void shift(KeyEvent e) { _shiftDown = e.isShiftDown(); Help.updateStatus(); getInstance().refreshHighlights(); } }