package org.expeditee.gio.gesture; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; 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 org.expeditee.actions.Actions; import org.expeditee.actions.Misc; import org.expeditee.actions.Navigation; import org.expeditee.core.Colour; import org.expeditee.core.Point; import org.expeditee.core.bounds.AxisAlignedBoxBounds; import org.expeditee.gio.ClipboardManager.ClipboardData; import org.expeditee.gio.EcosystemManager; import org.expeditee.gio.gesture.Gesture.GestureType; import org.expeditee.gio.gesture.data.ChangeColourGestureData; import org.expeditee.gio.gesture.data.ClickGestureData; import org.expeditee.gio.gesture.data.CreateItemGestureData; import org.expeditee.gio.gesture.data.CreateItemGestureData.ItemType; import org.expeditee.gio.gesture.data.DeleteGestureData; import org.expeditee.gio.gesture.data.FormatGestureData; import org.expeditee.gio.gesture.data.InsertStringGestureData; import org.expeditee.gio.gesture.data.ItemSpecificGestureData; import org.expeditee.gio.gesture.data.JustifyGestureData; import org.expeditee.gio.gesture.data.LinkGestureData; import org.expeditee.gio.gesture.data.NavigateFrameGestureData; import org.expeditee.gio.gesture.data.NavigateTextGestureData; import org.expeditee.gio.gesture.data.PanGestureData; import org.expeditee.gio.gesture.data.PickUpGestureData; import org.expeditee.gio.gesture.data.RefreshGestureData; import org.expeditee.gio.gesture.data.ScaleGestureData; import org.expeditee.gio.gesture.data.SelectAreaGestureData; import org.expeditee.gio.gesture.data.UndoGestureData; import org.expeditee.gio.gesture.data.ZoomGestureData; import org.expeditee.gio.input.KBMInputEvent.Key; import org.expeditee.gio.input.StandardInputEventListeners; import org.expeditee.gui.AttributeUtils; import org.expeditee.gui.AttributeValuePair; import org.expeditee.gui.Browser; import org.expeditee.gui.ColorUtils; import org.expeditee.gui.DisplayController; import org.expeditee.gui.Frame; import org.expeditee.gui.FrameCreator; import org.expeditee.gui.FrameGraphics; import org.expeditee.gui.FrameIO; import org.expeditee.gui.FrameUtils; import org.expeditee.gui.FreeItems; import org.expeditee.gui.ItemsList; import org.expeditee.gui.MessageBay; import org.expeditee.gui.Vector; 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.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.MagneticConstraint.MagneticConstraints; import org.expeditee.items.widgets.ButtonWidget; import org.expeditee.items.widgets.Widget; 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.settings.templates.TemplateSettings; import org.expeditee.stats.SessionStats; public class StandardGestureActions implements GestureListener { /** The gesture types offered by core Expeditee. */ //@formatter:off public enum StandardGestureType { ACTION, // Make action, remove action CALCULATE, CHANGE_COLOUR, // F3 CLICK, COPY, CREATE_ITEM, CUT, DELETE, DROP_DOWN, // F0 (ESC) (Positions the cursor below the current text item. Similar to enter in traditional editors.) EXTRACT_ATTRIBUTES, EXTRUDE, FORMAT, INSERT_DATE, // F5 INSERT_STRING, JUSTIFY, LINK, // Create link, remove link, follow link MAKE_CIRCLE, MOVE_CURSOR, NAVIGATE_FRAME, // Forward/back/next/previous frame NAVIGATE_TEXT, NEW_FRAMESET, // F6 NEXT_ITEM, PAN, PASTE, PICK_UP, PLACE, REFRESH, // F12 ROTATE_DISPLAY_MODE, // F9 ROTATE_FONT_FAMILY, // F8 ROTATE_FONT_STYLE, // F7 SAVE, // F11 SCALE, // F1 & F2 SELECT_AREA, SPLIT_TEXT, TOGGLE_ANNOTATION, // F4 TOGGLE_ARROWHEAD, TOGGLE_BOLD, TOGGLE_ITALICS, TOGGLE_ITEMS_MARK, // Show/hide the little circle indicating the item has a link and/or action TOGGLE_XRAY_MODE, // F10 CYCLE_SURROGATE_MODE, // Shift + F10 UNDO, ZOOM, ACTIVATE_BUTTON, // Enter while over Item with _acceptsKeyboardEnter set to true REPARSE // Ctrl + Shift + R } //@formatter:on private static StandardGestureActions _instance = null; public static StandardGestureActions getInstance() { if (_instance == null) { _instance = new StandardGestureActions(); } return _instance; } private HashMap _gestureTypes; private HashMap _actions; private StandardGestureActions() { _gestureTypes = new HashMap(); initialiseGestureTypes(); _actions = new HashMap(); initialiseActions(); } @Override public void preGesture(final Gesture gesture) { final GestureAction action = getGestureAction(gesture.getType()); if (action == null) { return; } action.prepare(gesture); } @Override public void onGesture(final Gesture gesture) { final GestureAction action = getGestureAction(gesture.getType()); if (action == null) { return; } action.exec(gesture); } @Override public void postGesture(final Gesture gesture) { final GestureAction action = getGestureAction(gesture.getType()); if (action == null) { return; } action.finalise(gesture); } private void setGestureAction(GestureType type, GestureAction action) { if (type == null) { return; } _actions.put(type, action); } private GestureAction getGestureAction(GestureType type) { if (type == null) { return null; } return _actions.get(type); } /** * Sets up the standard gesture actions. */ private void initialiseActions() { // Set the ACTION action setGestureAction(gestureType(StandardGestureType.ACTION), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item current = data.getCurrentItem(); // If its not linked then link it to its self if (current instanceof Text) { if (!current.hasAction()) { String text = ((Text) current).getText().trim(); // first trim the annotation if (text.startsWith("@")) { text = text.substring(1).trim(); } // then trim the action String lowerCaseText = text.toLowerCase(); if (lowerCaseText.startsWith("a:")) { text = text.substring("a:".length()).trim(); } else if (lowerCaseText.startsWith("action:")) { text = text.substring("action:".length()).trim(); } current.setAction(text); } else { // If its linked remove the link current.setActions(null); } } } }); // Set the CALCULATE action setGestureAction(gestureType(StandardGestureType.CALCULATE), new GestureAction() { @Override public void exec(Gesture gesture) { Item on = ((ItemSpecificGestureData) gesture.getData()).getCurrentItem(); if (on != null) { calculateItem(on); DisplayController.requestRefresh(true); } } }); // Set the CHANGE_COLOUR action setGestureAction(gestureType(StandardGestureType.CHANGE_COLOUR), new GestureAction() { @Override public void exec(Gesture gesture) { ChangeColourGestureData data = (ChangeColourGestureData) gesture.getData(); Item currentItem = data.getCurrentItem(); Collection currentItems = data.getCurrentItems(); Collection enclosure = data.getEnclosure(); boolean shouldSetTransparent = data.shouldSetTransparent(); boolean shouldSetSecondaryColour = data.shouldSetSecondaryColour(); if (currentItem == null && currentItems != null && currentItems.size() > 0) { Collection connected = data.getEnclosure().iterator().next().getAllConnected(); if (connected.size() > 0) { for (Item d : enclosure) { if (shouldSetSecondaryColour) { SetGradientColor(d, shouldSetTransparent); } else { SetFillColor(d, shouldSetTransparent); } break; } } } else if (currentItem != null) { SetColor(currentItem, shouldSetTransparent, shouldSetSecondaryColour); } } }); // Set the CLICK action setGestureAction(gestureType(StandardGestureType.CLICK), new GestureAction() { @Override public void exec(Gesture gesture) { ClickGestureData data = (ClickGestureData) gesture.getData(); click(data.getCurrentItem(), data.getCurrentItems(), data.getPosition()); } }); // Set the COPY action setGestureAction(gestureType(StandardGestureType.COPY), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item current = data.getCurrentItem(); if (FreeItems.hasItemsAttachedToCursor()) { ItemSelection.copyClone(); } else if (current instanceof Text) { copyItemToClipboard(current); } } }); // Set the CREATE_ITEM action setGestureAction(gestureType(StandardGestureType.CREATE_ITEM), new GestureAction() { @Override public void exec(Gesture gesture) { CreateItemGestureData data = (CreateItemGestureData) gesture.getData(); if (data.getItemType() == ItemType.LINE) { newLineAction(data.getPosition(), data.getCurrentItem()); } else if (data.getItemType() == ItemType.BOX) { createRectangleAction(); } // TODO: Complete. cts16 } }); // Set the CUT action setGestureAction(gestureType(StandardGestureType.CUT), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSelection.cut(); } }); // Set the DELETE action setGestureAction(gestureType(StandardGestureType.DELETE), new GestureAction() { @Override public void exec(Gesture gesture) { DeleteGestureData data = (DeleteGestureData) gesture.getData(); if (FreeItems.getInstance().isEmpty() && data.getCurrentItem() == null && data.getCurrentItems() == null && data.getEnclosure() == null) { final Gesture undoGesture = data.getAlternateMode() ? new Gesture(StandardGestureActions.getInstance().gestureType(StandardGestureType.UNDO), new UndoGestureData(true)) : new Gesture(StandardGestureActions.getInstance().gestureType(StandardGestureType.UNDO), new UndoGestureData(false)); getGestureAction(gestureType(StandardGestureType.UNDO)).exec(undoGesture); } else { delete(data.getCurrentItem(), data.getCurrentItems(), data.getEnclosure(), data.getAlternateMode()); } } }); // Set the DROP_DOWN action setGestureAction(gestureType(StandardGestureType.DROP_DOWN), new GestureAction() { @Override public void exec(Gesture gesture) { // Get the enclosed items Item on = FrameUtils.getCurrentItem(); if (on == null) { Collection enclosed = FrameUtils.getCurrentItems(on); Collection lineEnds; Item firstConnected; if (enclosed != null && enclosed.size() > 0) { // ensure only one dot\line is present in the list lineEnds = FrameUtils.getEnclosingLineEnds(); firstConnected = lineEnds.iterator().next(); // Get the last text item and drop from in Item lastText = null; for (Item i : enclosed) { if (i instanceof Text) { lastText = i; } } // Drop from the item if there was a text item in the box if (lastText != null) { Drop(lastText, false); } else { // Move to the top of the box AxisAlignedBoxBounds rect = firstConnected.getBoundingBox(); int newX = rect.getMinX() + Text.MARGIN_LEFT; int newY = Text.MARGIN_LEFT + rect.getMinY() + DisplayController.getCurrentFrame().getItemTemplate().getBoundsHeight(); moveCursorAndFreeItems(newX, newY); // TODO can resetOffset be put inside moveCursorAndFreeItems resetOffset(); } return; } } Drop(on, false); } }); // Set the EXTRACT_ATTRIBUTES action setGestureAction(gestureType(StandardGestureType.EXTRACT_ATTRIBUTES), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); extractAttributes(data.getCurrentItem()); } }); // Set the FORMAT action setGestureAction(gestureType(StandardGestureType.FORMAT), new GestureAction() { @Override public void exec(Gesture gesture) { FormatGestureData data = (FormatGestureData) gesture.getData(); if (data.getHorizontal()) { Actions.LegacyPerformActionCatchErrors(data.getFrame(), null, "HFormat"); } else { Actions.LegacyPerformActionCatchErrors(data.getFrame(), null, "Format"); } } }); // Set the INSERT_DATE action setGestureAction(gestureType(StandardGestureType.INSERT_DATE), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item currentItem = data.getCurrentItem(); Collection currentItems = data.getCurrentItems(); if (currentItem == null && currentItems != null && currentItems.size() > 0) { Text.AddDate(data.getEnclosure().iterator().next()); } else { Text.AddDate(currentItem); } } }); // Set the INSERT_STRING action setGestureAction(gestureType(StandardGestureType.INSERT_STRING), new GestureAction() { @Override public void exec(Gesture gesture) { InsertStringGestureData data = (InsertStringGestureData) gesture.getData(); Item currentItem = data.getCurrentItem(); char[] charArray = data.getString().toCharArray(); if (currentItem != null) { // If we are over a item taht accepts keyboard enter and the only // character being entered is the enter key then we do not insert it. boolean keyboardEnter = data.getCurrentItem().acceptsKeyboardEnter(); if (keyboardEnter && charArray.length == 1 && charArray[0] == (char) 10) { return; } } for (char c : charArray) { processChar(c, data.isShiftDown()); } } }); // Set the JUSTIFY action setGestureAction(gestureType(StandardGestureType.JUSTIFY), new GestureAction() { @Override public void exec(Gesture gesture) { JustifyGestureData data = (JustifyGestureData) gesture.getData(); Text textCurrent = data.getCurrentTextItem(); if (textCurrent == null) { for (Text t : data.getFrame().getBodyTextItems(false)) { if (data.getResetWidth()) { t.setWidth(null); } t.justify(true); } } else { if (data.getResetWidth()) { textCurrent.setWidth(null); } textCurrent.justify(true); } } }); // Set the REPARSE FRAME BODY action setGestureAction(gestureType(StandardGestureType.REPARSE), new GestureAction() { @Override public void exec(Gesture gesture) { Frame currentFrame = DisplayController.getCurrentFrame(); if (Browser.DEBUG) { ItemsList body = currentFrame.getBody(false); ItemsList primaryBody = currentFrame.getPrimaryBody(); ItemsList surrogateBody = currentFrame.getSurrogateBody(); MessageBay.displayMessage("Body:" + body.toString()); MessageBay.displayMessage("Primaries:" + primaryBody.toString()); MessageBay.displayMessage("Surrogates:" + surrogateBody.toString()); } currentFrame.parse(); if (Browser.DEBUG) { ItemsList body = currentFrame.getBody(false); ItemsList primaryBody = currentFrame.getPrimaryBody(); ItemsList surrogateBody = currentFrame.getSurrogateBody(); MessageBay.displayMessage("Body:" + body.toString()); MessageBay.displayMessage("Primaries:" + primaryBody.toString()); MessageBay.displayMessage("Surrogates:" + surrogateBody.toString()); } } }); // Set the LINK action setGestureAction(gestureType(StandardGestureType.LINK), new GestureAction() { @Override public void exec(Gesture gesture) { LinkGestureData data = (LinkGestureData) gesture.getData(); Item current = data.getCurrentItem(); boolean follow = data.getFollow(); // If its not linked then link it to itself if (current instanceof Text && current.getLink() == null) { String text = ((Text) current).getText(); // Ignore the annotation if there is one if (text.charAt(0) == '@') { text = text.substring(1); } if (FrameIO.isValidFrameName(text)) { current.setLink(text); } else if (FrameIO.isValidFramesetName(text)) { current.setLink(text + '1'); } } else if (current != null && !follow) { // If its linked remove the link current.setLink(null); } if (current != null && current.getLink() != null && follow) { Navigation.Goto(current.getAbsoluteLink()); return; } } }); // Set the MAKE_CIRCLE action setGestureAction(gestureType(StandardGestureType.MAKE_CIRCLE), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item current = data.getCurrentItem(); Text item = null; // Check if its a line to be turned into a circle if (current instanceof Dot && current.getLines().size() == 1) { item = Item.replaceDot(current, '@'); } else if (current instanceof Line && current.getAllConnected().size() == 3) { Item end = ((Line) current).getEndItem(); if (end instanceof Dot) { item = Item.replaceDot(end, '@'); } else if (end instanceof Text) { item = (Text) end; } } if (item != null) { item.setText("@c"); DisplayController.setCursorPosition(item.getX(), item.getY()); FrameUtils.setLastEdited(null); Refresh(); } } }); // Set the MOVE_CURSOR action setGestureAction(gestureType(StandardGestureType.MOVE_CURSOR), new GestureAction() { @Override public void exec(Gesture gesture) { if (gesture.isRobotic()) { EcosystemManager.getInputManager().setCursorPosition(gesture.getData().getPosition()); } else { mouseMoved(gesture.getData().getPosition()); } } }); // Set the NAVIGATE_FRAME action setGestureAction(gestureType(StandardGestureType.NAVIGATE_FRAME), new GestureAction() { @Override public void exec(Gesture gesture) { NavigateFrameGestureData data = (NavigateFrameGestureData) gesture.getData(); switch (data.getNavigateTo()) { case BACK_FRAME: DisplayController.Back(); break; case EARLIEST_FRAME: while (DisplayController.Back()) { ; } break; case FIRST_FRAME: Navigation.ZeroFrame(); Navigation.NextFrame(); break; case FORWARD_FRAME: DisplayController.Forward(); break; case LAST_FRAME: Navigation.LastFrame(); break; case LATEST_FRAME: while (DisplayController.Forward()) { ; } break; case NEXT_FRAME: Navigation.NextFrame(false); break; case PREVIOUS_FRAME: Navigation.PreviousFrame(false); break; case SPECIFIC_FRAME: Navigation.Goto(data.getCurrentFrameset() + data.getCurrentFrameNumber()); break; case ZERO_FRAME: Navigation.ZeroFrame(); break; default: break; } } }); // Set the NAVIGATE_TEXT action setGestureAction(gestureType(StandardGestureType.NAVIGATE_TEXT), new GestureAction() { @Override public void exec(Gesture gesture) { NavigateTextGestureData data = (NavigateTextGestureData) gesture.getData(); switch (data.getNavigateTo()) { case ABOVE_LINE: move(Text.UP, data.getSelecting(), false); break; case BELOW_LINE: move(Text.DOWN, data.getSelecting(), false); break; case LINE_END: move(Text.LINE_END, data.getSelecting(), false); break; case LINE_HOME: move(Text.LINE_HOME, data.getSelecting(), false); break; case NEXT_CHARACTER: move(Text.RIGHT, data.getSelecting(), false); break; case NEXT_TEXT_ITEM: NextTextItem(data.getCurrentItem(), true); break; case NEXT_WORD: DisplayController.setTextCursor((Text) data.getCurrentItem(), Text.RIGHT, false, data.getSelecting(), true, true); break; case PARAGRAPH_END: move(Text.END, data.getSelecting(), true); break; case PARAGRAPH_HOME: move(Text.HOME, data.getSelecting(), true); break; case PREVIOUS_CHARACTER: move(Text.LEFT, data.getSelecting(), false); break; case PREVIOUS_TEXT_ITEM: NextTextItem(data.getCurrentItem(), false); break; case PREVIOUS_WORD: DisplayController.setTextCursor((Text) data.getCurrentItem(), Text.LEFT, false, data.getSelecting(), true, true); break; default: break; } } }); // Set the NEW_FRAMESET action setGestureAction(gestureType(StandardGestureType.NEW_FRAMESET), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); if (data.getCurrentItem() != null) { CreateFrameset(data.getCurrentItem()); } } }); // Set the NEXT_ITEM action setGestureAction(gestureType(StandardGestureType.NEXT_ITEM), new GestureAction() { @Override public void exec(Gesture gesture) { // GestureData data = (GestureData) gesture.getData(); // TODO: Complete. cts16 } }); // Set the PAN action setGestureAction(gestureType(StandardGestureType.PAN), new GestureAction() { @Override public void exec(Gesture gesture) { PanGestureData data = (PanGestureData) gesture.getData(); Misc.pan(DisplayController.getCurrentFrame(), data.getPanDelta().getX(), data.getPanDelta().getY()); } }); // Set the PASTE action setGestureAction(gestureType(StandardGestureType.PASTE), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSelection.paste(); } }); // Set the PICK_UP action setGestureAction(gestureType(StandardGestureType.PICK_UP), new GestureAction() { @Override public void exec(Gesture gesture) { PickUpGestureData data = (PickUpGestureData) gesture.getData(); if (!data.wasDragged()) { if (data.getCurrentItem() != null) { handlePickup(data.getCurrentItem(), data.getPosition(), data.getCopy(), data.getExtrude()); } else if (data.getCurrentItems() != null) { handlePickup(data.getCurrentItems(), data.getEnclosure(), data.getPosition(), data.getCopy()); } } else { Item item = data.getCurrentItem(); if (item instanceof Text) { Text text = (Text) item; text.setSelectionStart(data.getDraggedFrom()); text.setSelectionEnd(data.getPosition()); text.setSelectionColour(data.getCopy() ? Text.RANGE_COPY_COLOUR : Text.RANGE_CUT_COLOUR); DisplayController.setTextCursor(text, Text.NONE, false, false, false, false); if (data.getFinishedDragging()) { pickupRange(text, data.getPosition(), data.getCopy(), data.getInheritAttributes()); } else { DisplayController.requestRefresh(true); } } else if (item instanceof Picture && data.getCopy()) { Picture picture = (Picture) item; picture.setStartCrop(data.getDraggedFrom()); picture.setEndCrop(data.getPosition()); picture.setShowCrop(true); picture.setHighlightMode(Item.HighlightMode.None); picture.setHighlightColor(Item.DEPRESSED_HIGHLIGHT); if (data.getFinishedDragging()) { if (picture.isCropTooSmall()) { return; } Picture cropped = picture.copy(); cropped.setParent(null); // move the cropped image to the cursor int width = cropped.getWidth(); int height = cropped.getHeight(); if (cropped.getSource().getX() + width < data.getPosition().getX()) { cropped.getSource().setX(data.getPosition().getX() - width); } if (cropped.getSource().getY() + height < data.getPosition().getY()) { cropped.getSource().setY(data.getPosition().getY() - height); } pickup(cropped); // MIKE put the code below up here picture.clearCropping(); FrameGraphics.changeHighlightMode(picture, HighlightMode.None); } DisplayController.requestRefresh(true); } } } }); // Set the PLACE action setGestureAction(gestureType(StandardGestureType.PLACE), new GestureAction() { private boolean shouldClearFreeItems = false; @Override public void exec(final Gesture gesture) { final PickUpGestureData data = (PickUpGestureData) gesture.getData(); if (data.getCopy()) { shouldClearFreeItems = false; copyPlace(data.getCurrentItem()); } else { if (doMerging(data.getCurrentItem())) { shouldClearFreeItems = true; deleteItemsAction(data.getCurrentItem()); } else { shouldClearFreeItems = true; anchorFreeItemsAction(data.getCurrentItems()); } } } @Override public void finalise(final Gesture gesture) { if (shouldClearFreeItems) { FreeItems.getInstance().clear(); } } }); // Set the REFRESH action setGestureAction(gestureType(StandardGestureType.REFRESH), new GestureAction() { @Override public void exec(Gesture gesture) { RefreshGestureData data = (RefreshGestureData) gesture.getData(); if (data.shouldReloadFrameFirst()) { MessageBay.displayMessage("Loaded in data from external edit made my another user."); DisplayController.Reload(DisplayController.TwinFramesSide.LEFT); } Refresh(data.shouldRefreshFrameSize()); } }); // Set the ROTATE_DISPLAY_MODE action setGestureAction(gestureType(StandardGestureType.ROTATE_DISPLAY_MODE), new GestureAction() { @Override public void exec(Gesture gesture) { DisplayController.rotateAudienceModes(); } }); // Set the ROTATE_FONT_FAMILY action setGestureAction(gestureType(StandardGestureType.ROTATE_FONT_FAMILY), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item currentItem = data.getCurrentItem(); Collection currentItems = data.getCurrentItems(); if (currentItem == null && currentItems != null && currentItems.size() > 0) { ToggleFontFamily(data.getEnclosure().iterator().next()); } else if (currentItem != null) { ToggleFontFamily(currentItem); } } }); // Set the ROTATE_FONT_STYLE action setGestureAction(gestureType(StandardGestureType.ROTATE_FONT_STYLE), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item currentItem = data.getCurrentItem(); Collection currentItems = data.getCurrentItems(); if (currentItem == null && currentItems != null && currentItems.size() > 0) { ToggleFontStyle(data.getEnclosure().iterator().next()); } else if (currentItem != null) { ToggleFontStyle(currentItem); } } }); // Set the SAVE action setGestureAction(gestureType(StandardGestureType.SAVE), new GestureAction() { @Override public void exec(Gesture gesture) { Save(); } }); // Set the SCALE action setGestureAction(gestureType(StandardGestureType.SCALE), new GestureAction() { @Override public void exec(Gesture gesture) { ScaleGestureData data = (ScaleGestureData) gesture.getData(); boolean scaleAroundCursor = data.getPosition() != null; int scaleFactor = data.getScaleFactor(); Item currentItem = data.getCurrentItem(); Collection currentItems = data.getCurrentItems(); if (currentItem == null && currentItems != null && currentItems.size() > 0) { SetSize(data.getEnclosure().iterator().next(), scaleFactor, false, true, scaleAroundCursor); } else if (currentItem != null) { SetSize(currentItem, scaleFactor, true, false, scaleAroundCursor); if (currentItem instanceof Text) { DisplayController.setTextCursor((Text) currentItem, Text.NONE, true, false, false, true); } } } }); // Set the SELECT_AREA action setGestureAction(gestureType(StandardGestureType.SELECT_AREA), new GestureAction() { @Override public void exec(Gesture gesture) { SelectAreaGestureData data = (SelectAreaGestureData) gesture.getData(); Item item = data.getCurrentItem(); if (item != null && item instanceof Text) { Text text = (Text) item; text.setSelectionStart(data.getDraggedFrom()); text.setSelectionEnd(data.getPosition()); text.setSelectionColour(Text.RANGE_SELECT_COLOUR); DisplayController.setTextCursor(text, Text.NONE, false, false, false, false); DisplayController.requestRefresh(true); } } }); // Set the SPLIT_TEXT action setGestureAction(gestureType(StandardGestureType.SPLIT_TEXT), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Text text = data.getCurrentTextItem(); if (text == null) { return; } List textLines = text.getTextList(); if (textLines.size() <= 1) { return; } // remove all except the first line of text from the item being split text.setText(textLines.get(0)); int y = text.getY(); Frame parent = text.getParent(); for (int i = 1; i < textLines.size(); i++) { Text newText = text.copy(); newText.setText(textLines.get(i)); y += newText.getBoundsHeight(); newText.setY(y); // update the items ID to prevent conflicts with the current frame if (parent != null) { newText.setID(parent.getNextItemID()); parent.addItem(newText); } } } }); // Set the TOGGLE_ANNOTATION action setGestureAction(gestureType(StandardGestureType.TOGGLE_ANNOTATION), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item currentItem = data.getCurrentItem(); Collection currentItems = data.getCurrentItems(); if (currentItem == null && currentItems != null && currentItems.size() > 0) { ToggleAnnotation(data.getEnclosure().iterator().next()); } else if (currentItem != null) { ToggleAnnotation(currentItem); } } }); // Set the TOGGLE_ARROWHEAD action setGestureAction(gestureType(StandardGestureType.TOGGLE_ARROWHEAD), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item item = data.getCurrentItem(); if (item instanceof Line) { ((Line) item).toggleArrow(); DisplayController.requestRefresh(true); } } }); // Set the TOGGLE_BOLD action setGestureAction(gestureType(StandardGestureType.TOGGLE_BOLD), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item current = data.getCurrentItem(); if (current instanceof Text) { ((Text) current).toggleBold(); } } }); // Set the TOGGLE_ITALICS action setGestureAction(gestureType(StandardGestureType.TOGGLE_ITALICS), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item current = data.getCurrentItem(); if (current instanceof Text) { ((Text) current).toggleItalics(); } } }); // Set the TOGGLE_ITEMS_MARK action setGestureAction(gestureType(StandardGestureType.TOGGLE_ITEMS_MARK), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item current = data.getCurrentItem(); if (current == null) { return; } if (current != null && !current.hasPermission(UserAppliedPermission.full)) { MessageBay.displayMessage("Insufficient permission toggle the items mark"); return; } boolean newValue = !(current.getLinkMark() || current.getActionMark()); current.setLinkMark(newValue); current.setActionMark(newValue); } }); // Set the TOGGLE_XRAY_MODE action setGestureAction(gestureType(StandardGestureType.TOGGLE_XRAY_MODE), new GestureAction() { @Override public void exec(Gesture gesture) { DisplayController.ToggleXRayMode(); } }); // Set the UNDO action setGestureAction(gestureType(StandardGestureType.UNDO), new GestureAction() { @Override public void exec(Gesture gesture) { UndoGestureData data = (UndoGestureData) gesture.getData(); if (data.getRedo()) { DisplayController.getCurrentFrame().redo(); } else { DisplayController.getCurrentFrame().undo(); } } }); // Set the ZOOM action setGestureAction(gestureType(StandardGestureType.ZOOM), new GestureAction() { @Override public void exec(Gesture gesture) { ZoomGestureData data = (ZoomGestureData) gesture.getData(); zoomFrameIfEnabled(DisplayController.getCurrentFrame(), data.getScaleFactor(), data.getPosition()); } }); // Set the ACTIVATE BUTTON action setGestureAction(gestureType(StandardGestureType.ACTIVATE_BUTTON), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); Item currentItem = data.getCurrentItem(); if (currentItem != null && currentItem.acceptsKeyboardEnter()) { getGestureAction(Gesture.GestureType.get("CLICK")).exec(gesture); } } }); setGestureAction(gestureType(StandardGestureType.CYCLE_SURROGATE_MODE), new GestureAction() { @Override public void exec(Gesture gesture) { if (((UndoGestureData) gesture.getData()).getRedo()) { DisplayController.ResetSurrogateMode(); } else { DisplayController.ToggleSurrogateMode(); } } }); } /** Initialises the set of gesture types. */ private void initialiseGestureTypes() { for (StandardGestureType type : StandardGestureType.values()) { GestureType gestureType; if ((gestureType = GestureType.register(type.toString())) == null) { gestureType = GestureType.get(type.toString()); } _gestureTypes.put(type, gestureType); } } /** Gets the gesture type associated with the given standard type. */ public GestureType gestureType(StandardGestureType type) { if (type == null) { return null; } return _gestureTypes.get(type); } /* * TODO: EVERYTHING BELOW HERE IS ONLY TEMPORARILY SITUATED IN THIS CLASS * * The following methods were mostly copy-pasted here from the old * FrameKeyboardActions and FrameMouseActions classes (to enable quick * change-over to the new input system). Really they should be relocated to more * suitable homes based on their function. */ /** * 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); } 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()); } // TODO: Review if these are needed as they ugly up the code. cts16 static int _offX, _offY; /** * Picks up a group of items on a frame. * * @param toGrab * The items to pick up. */ public static void pickup(Collection toGrab) { if (toGrab == null || toGrab.size() == 0) { return; } boolean bReparse = false; boolean bRecalculate = false; Frame currentFrame = DisplayController.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 = DisplayController.getMouseX() - i.getX() + i.getOffset().getX(); _offY = DisplayController.getMouseY() - i.getY() + i.getOffset().getY(); // 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(), EcosystemManager.getInputManager().getCursorPosition()); ItemUtils.EnclosedCheck(toGrab); // otherwise, just use the first item } else if (toGrab.size() == 1) { Item soleItem = toGrab.iterator().next(); _offX = DisplayController.getMouseX() - soleItem.getX() + soleItem.getOffset().getX(); _offY = DisplayController.getMouseY() - soleItem.getY() + soleItem.getOffset().getY(); // Now call move so that if we are on a message in the message box // It doesn't appear up the top of the screen!! move(toGrab, EcosystemManager.getInputManager().getCursorPosition()); } else { MessageBay.displayMessage("Insufficient permission to pickup the items"); } if (bReparse) { FrameUtils.Parse(currentFrame, false, false, false); } else { currentFrame.notifyObservers(bRecalculate); } DisplayController.requestRefresh(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 (FreeItems.rubberBanding()) { DisplayController.setCursor(Item.HIDDEN_CURSOR); return; } // This is to make sure the TEXT_CURSOR doesn't get inadvertently turned off! Item on = FrameUtils.getCurrentItem(); if (on != null && on instanceof Text) { return; } DisplayController.setCursor(Item.DEFAULT_CURSOR); } private static Text _toRemove = null; public static void processChar(char ch, boolean isShiftDown) { Navigation.ResetLastAddToBack(); Item on = FrameUtils.getCurrentItem(); // permission check if (on != null && !on.hasPermission(UserAppliedPermission.full)) { boolean canTabToNext = !isShiftDown && ch == '\t' && on instanceof Text && ((Text) on).getTabNext() != null; boolean canTabToPrevious = isShiftDown && ch == '\t' && on instanceof Text && ((Text) on).getTabPrevious() != null; if (!canTabToNext && !canTabToPrevious) { MessageBay.displayMessage("Insufficient permission to edit this item"); return; } } // Certain keys are handled by MagneticConstraints. // If the shift key is down the 'inverted' version of that key is used. if (isShiftDown && MagneticConstraints.getInstance().keyHit(-ch, on)) { return; } else if (MagneticConstraints.getInstance().keyHit(ch, on)) { return; } if (_toRemove != null && on != _toRemove) { assert (_toRemove.getLength() == 0); // This line is to protect mistaken removal of items if there is a bug... if (_toRemove.getLength() == 0) { DisplayController.getCurrentFrame().removeItem(_toRemove); } } _toRemove = null; // ignore delete and backspace if in free space if ((on == null || !(on instanceof Text)) && (ch == '\b' || ch == '\t' || ch == Text.DELETE_CHARACTER)) { return; } SessionStats.TypedChar(ch); // check for dot's being replaced with text if (on != null && on instanceof Dot && !(on instanceof WidgetCorner)) { if (ch == '\b' || ch == Text.DELETE_CHARACTER) { return; } Item.replaceDot(on, ch); return; } // only text can interact with keyboard events if (on != null && !(on instanceof Text)) { on = null; } // DisplayIO.UpdateTitle(); Text text = (Text) on; // if this text is empty but has not been removed (such as from ESC-pushdown) if (text != null && text.isEmpty() && (ch == '\b' || ch == Text.DELETE_CHARACTER)) { if (text.getLines().size() > 0) { Text.replaceText(text); } else { DisplayController.setCursor(Item.DEFAULT_CURSOR); } return; } // if the user is in free space, create a new text item // MikeSays: Why do we have to check is highlighted... doing so causes // problems if you type characters too fast, they turn into multiple text // items. ie. JK together on the Linux laptop. if (on == null /* || !on.isHighlighted() */) { // DisplayIO.UpdateTitle(); text = Text.createText(ch); text.justify(false); FrameUtils.setLastEdited(text); DisplayController.setTextCursor(text, Text.NONE); DisplayController.requestRefresh(true); return; } else { FrameUtils.setLastEdited(text); } DisplayController.setTextCursor(text, Text.NONE); insertCharacterAction(text, isShiftDown, ch); // This repaint is needed for WINDOWS only?!?!? Mike is not sure why! // if (ch == Text.DELETE_CHARACTER) DisplayController.requestRefresh(true); // a change has occured to the Frame text.getParent().setChanged(true); // check that the Text item still exists (hasn't been deleted\backspaced // away) if (text.isEmpty() && text.getMinWidth() == null) { _toRemove = text; if (text.hasAction()) { text.setActionMark(true); } else if (text.getLink() != null) { text.setLinkMark(true); } else if (text.getLines().size() > 0) { Item.replaceText(text); } else { // DisplayIO.getCurrentFrame().removeItem(text); DisplayController.setCursor(Item.DEFAULT_CURSOR); } } } public static Text insertCharacterAction(Text text, boolean isShiftDown, final char c) { final float oldY = DisplayController.getFloatMouseY(); final float oldX = DisplayController.getFloatMouseX(); // System.err.println("insertCharacterAction: Prior to inserting character mouse // at: " + oldX + "," + oldY); Point newMouse = null; if (c == '\t') { if (isShiftDown) { Text tabPrevious = text.getTabPrevious(); if (tabPrevious == null) { newMouse = text.removeTab(c, oldX, oldY); } else { StandardGestureActions.moveCursorAndFreeItems(tabPrevious.getX(), tabPrevious.getY()); return text; } } else { Text tabNext = text.getTabNext(); if (tabNext == null) { newMouse = text.insertTab(c, oldX, oldY); } else { StandardGestureActions.moveCursorAndFreeItems(tabNext.getX(), tabNext.getY()); return text; } } } else { newMouse = text.insertChar(c, oldX, DisplayController.getFloatMouseY()); } // check if the user hit enter and the cursor is now on another text item if (oldY < newMouse.getY()) { AxisAlignedBoxBounds rect = text.getBoundingBox(); Item justBelow = FrameUtils.onItem(DisplayController.getCurrentFrame(), text.getX() + 10, rect.getMinY() + rect.getHeight() + 1, false); // Dont drop unless if (justBelow != null && justBelow instanceof Text && justBelow != text) { // Drop all the items below it down! // Get the list of items that must be dropped List column = DisplayController.getCurrentFrame().getColumn(text); FrameUtils.Align(column, false, 0); } } DisplayController.setCursorPosition(newMouse.getX(), newMouse.getY(), false); return text; } /** * Moves the items to the current mouse position (plus the current offset) * * @param toMove */ public static void move(Collection toMove, Point to) { move(toMove, to, false); } static void move(Collection toMove, Point to, boolean cursor) { Item firstDot = toMove.iterator().next(); int deltax = (to.getX() - (cursor ? 0 : _offX)) - firstDot.getX(); int deltay = (to.getY() - (cursor ? 0 : _offY)) - firstDot.getY(); // Cache the position of all items before moving any as constraints // may move the items before we get to them, causing jumping HashMap preMovePositions = new HashMap(); for (Item move : toMove) { preMovePositions.put(move, move.getPosition()); } for (Item move : toMove) { Point pos = preMovePositions.get(move); move.setPosition(pos.getX() + deltax, pos.getY() + deltay); if (!cursor && move instanceof Text) { ((Text) move).setAlpha(FrameUtils.hasCurrentItem() && FreeItems.getInstance().size() > 0 ? 60 : -1); } } DisplayController.requestRefresh(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) { DisplayController.getCurrentFrame().removeAllItems(enclosure); } AxisAlignedBoxBounds rect = i.getEnclosedBox(); long width = Math.round(rect.getWidth()); if (isVector) { NumberFormat nf = Vector.getNumberFormatter(); linker.setText(linker.getText() + ": " + nf.format((width / DisplayController.getFramePaintAreaWidth()))); } else { linker.setText(linker.getText() + ": " + width); } linker.setPosition(new Point(rect.getMinX(), rect.getMinY())); linker.setThickness(i.getThickness()); linker.setBorderColor(i.getColor()); break; } } if (!isVector) { deleteItems(enclosure, false); } } } boolean mouseMoved; linker.getParent().setChanged(true); Frame next = FrameIO.CreateNewFrame(linker, null); 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 DisplayController.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; } public static void resetOffset() { if (FreeItems.hasItemsAttachedToCursor()) { _offX = DisplayController.getMouseX() - FreeItems.getInstance().get(0).getX() + FreeItems.getInstance().get(0).getOffset().getX(); _offY = DisplayController.getMouseY() - FreeItems.getInstance().get(0).getY() + FreeItems.getInstance().get(0).getOffset().getY(); } } /** * Forces a re-parse and repaint of the current Frame. */ public static void Refresh() { Refresh(false); } /** * Forces a re-parse and repaint of the current Frame. */ public static void Refresh(boolean refreshFrameSize) { Frame currentFrame = DisplayController.getCurrentFrame(); if (refreshFrameSize) { currentFrame.refreshSize(); } // Refresh widgets that use its self as a data source currentFrame.notifyObservers(true); if (FrameIO.isProfileFrame(currentFrame)) { // TODO ensure that users can not delete the first frame in a frameset... // TODO handle the case when users manually delete the first frame in a frameset // from the filesystem Frame profile = FrameIO.LoadFrame(currentFrame.getFramesetName() + "1"); assert (profile != null); FrameUtils.Parse(currentFrame); FrameUtils.ParseProfile(profile); } else { FrameUtils.Parse(currentFrame); } // Need to update the cursor for when text items change to @b pictures // etc and the text cursor is showing updateCursor(); refreshHighlights(); DisplayController.requestRefresh(false); } /** * Saves the current frame. */ public static void Save() { Frame current = DisplayController.getCurrentFrame(); current.change(); FrameIO.SaveFrame(current, true, true); MessageBay.displayMessage("Current frame has been saved."); } public static final String DEFAULT_NEW_ITEM_TEXT = ""; public static String getAutoBullet(String s) { return getBullet(s, true); } private static String getBullet(String s, boolean nextBullet) { String newItemText = DEFAULT_NEW_ITEM_TEXT; if (s == null) { return newItemText; } /* * Item i = ItemUtils.FindTag(DisplayIO.getCurrentFrame().getItems(), * "@NoAutoBullets"); if (i != null) return newItemText; */ // Separate the space at the start of the text item String preceedingSpace = ""; for (int i = 0; i < s.length(); i++) { if (!Character.isSpaceChar(s.charAt(i))) { preceedingSpace = s.substring(0, i); s = s.substring(i); break; } } // figure out the type of the text item // This allows us to do auto bulleting if (s != null && s.length() > 1) { // First check for text beginning with * @ # etc // These are simple auto bullets if (!Character.isLetterOrDigit(s.charAt(0)) && !Character.isSpaceChar(s.charAt(0))) { if (Text.isBulletChar(s.charAt(0))) { int nonSpaceIndex = 1; // Find the end of the bullet and space after the bullet while (nonSpaceIndex < s.length() && s.charAt(nonSpaceIndex) == ' ') { nonSpaceIndex++; } // we must have a special char followed by >= 1 space if (nonSpaceIndex > 1) { newItemText = s.substring(0, nonSpaceIndex); } } // Auto numbering and lettering } else { if (Character.isDigit(s.charAt(0))) { newItemText = getAutoNumber(s, nextBullet); // Auto lettering } else if (Character.isLetter(s.charAt(0))) { newItemText = getAutoLetter(s, nextBullet); } } } return preceedingSpace + newItemText; } /** * Gets the string to be used to start the next auto lettered text item. * * @param s * the previous text items * @return the initial text for the new text item */ private static String getAutoLetter(String s, boolean nextBullet) { String newItemText = DEFAULT_NEW_ITEM_TEXT; int nonLetterIndex = 1; if (isAutoNumberOrLetterChar(s.charAt(nonLetterIndex))) { // Now search for the next non space character int nonSpaceIndex = nonLetterIndex + 1; while (nonSpaceIndex < s.length() && s.charAt(nonSpaceIndex) == ' ') { nonSpaceIndex++; } // If there was a space then we have reached the end of our auto // text if (nonSpaceIndex > nonLetterIndex + 1) { if (nextBullet) { newItemText = nextLetterSequence(s.substring(0, nonLetterIndex)) + s.substring(nonLetterIndex, nonSpaceIndex); } else { newItemText = s.substring(0, nonSpaceIndex); } } } return newItemText; } /** * Gets the string to be used to start the next auto numbered text item. * * @param s * the previous text item * @return the beginning of the next auto numbered text item */ private static String getAutoNumber(String s, boolean nextBullet) { String newItemText = DEFAULT_NEW_ITEM_TEXT; int nonDigitIndex = 1; while (Character.isDigit(s.charAt(nonDigitIndex))) { nonDigitIndex++; if (nonDigitIndex + 1 >= s.length()) { return DEFAULT_NEW_ITEM_TEXT; } } if (isAutoNumberOrLetterChar(s.charAt(nonDigitIndex))) { // we must have a number followed one non letter // then one or more spaces int nonSpaceIndex = nonDigitIndex + 1; while (nonSpaceIndex < s.length() && s.charAt(nonSpaceIndex) == ' ') { nonSpaceIndex++; } if (nonSpaceIndex > nonDigitIndex + 1) { if (nextBullet) { newItemText = (Integer.parseInt(s.substring(0, nonDigitIndex)) + 1) + s.substring(nonDigitIndex, nonSpaceIndex); } else { newItemText = s.substring(0, nonSpaceIndex); } } } return newItemText; } private static boolean isAutoNumberOrLetterChar(char c) { return c == ':' || c == '-' || c == '.' || c == ')' || c == '>'; } /** * Gets the next letter sequence for a given string to be used in auto * lettering. * * @param s * a sequence of letters * @return the next sequence of letters */ static private String nextLetterSequence(String s) { if (s.length() > 1) { return s; } if (s.equals("z")) { return "a"; } return (char) (s.charAt(0) + 1) + ""; } public static String getBullet(String s) { return getBullet(s, false); } /** * true if lastItem only has highlighting removed when a new item is * highlighted. */ private static boolean _lastHoldsHighlight = false; public static void setHighlightHold(boolean hold) { _lastHoldsHighlight = hold; } 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; /** the current context of the cursor. */ private static int _context = 0; /** keeps track of the last highlighted Item. */ private static Item _lastHighlightedItem = null; private static boolean _forceArrowCursor = true; public static 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; } } else { _context = CONTEXT_FREESPACE; } // 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) { line.showVirtualSpot(lineEnd, DisplayController.getMouseX(), DisplayController.getMouseY()); } 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 // Stops interfering with the highlighting while cropping a picture if (on instanceof Picture) { Picture p = (Picture) on; if (!p.isBeingCropped()) { on = FrameGraphics.Highlight(on.getEditTarget()); } } else { on = FrameGraphics.Highlight(on.getEditTarget()); } } // if the last item highlighted is still highlighted, clear it if (_lastHoldsHighlight) { _lastHoldsHighlight = false; for (Item i : DisplayController.getCurrentFrame().getSortedItems()) { 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 (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) { 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.hasItemsAttachedToCursor() && DisplayController.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 static boolean ContainsOneOf(Collection enclosure, Collection freeItems) { if (freeItems == null) { return false; } for (Item i : freeItems) { if (enclosure.contains(i)) { return true; } } return false; } 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().getSortedItems()); DisplayController.requestRefresh(true); } } 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) { // Widget components in this loop. 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.remove(i); } } 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 = DisplayController.getCurrentFrame(); if (bReparse) { FrameUtils.Parse(currentFrame, false, false, false); } else { currentFrame.notifyObservers(bRecalculate); } DisplayController.requestRefresh(true); } 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(DisplayController.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 = DisplayController.getCurrentFrame(); currentFrame.addToUndoDelete(new ItemsList(itemList), false); itemList.clear(); if (bReparse) { FrameUtils.Parse(currentFrame, false, 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; } } public static Item getlastHighlightedItem() { return _lastHighlightedItem; } /** * event called when mouse exits window (can't use MouseListener callback since * that callback doesn't correctly receive all mouse exit events) *** Above * comment no longer applies! *** TODO: Rename. cts16 */ public static void mouseExitedWindow() { if (FreeItems.hasItemsAttachedToCursor()) { 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 static void setForceArrow(boolean val) { _forceArrowCursor = val; } protected static void zoomFrame(Frame frame, double scaleFactor, int x, int y) { if (frame == null) { return; } Collection items = frame.getVisibleItems(); for (Item item : items) { if (item instanceof Text && item.getSize() <= Text.MINIMUM_FONT_SIZE && scaleFactor < 1) { return; } } for (Vector v : frame.getVectors()) { v.Source.scale((float) scaleFactor, x, y); } for (Item item : items) { // This line is only needed for circles!! // Need to really fix up the way this works!! if (item.hasEnclosures()) { continue; } if (!item.hasPermission(UserAppliedPermission.full)) { continue; } item.invalidateAll(); if (!(item instanceof Line)) { item.scale((float) scaleFactor, x, y); } } for (Item item : items) { if (!item.hasPermission(UserAppliedPermission.full)) { continue; } // if (!(item instanceof Line)) item.invalidateBounds(); if (item instanceof Line) { ((Line) item).refreshStroke(item.getThickness()); } item.invalidateAll(); } } public static boolean zoomFrameIfEnabled(Frame frame, double scaleFactor, Point centreOfZoom) { boolean zoom_active = ExperimentalFeatures.FrameZoom.get(); if (zoom_active) { zoomFrame(DisplayController.getCurrentFrame(), scaleFactor, centreOfZoom.getX(), centreOfZoom.getY()); DisplayController.getCurrentFrame().refreshSize(); Refresh(); } else { String frameZoomingDisabledMessage = "Frame Zooming currently disabled. " + "Access Settings->Experimental->FrameZoom and set to 'true' to enable this"; MessageBay.displayMessageOnce(frameZoomingDisabledMessage); } return zoom_active; } /** * Performs the dropping action: If the cursor is in free space then: the cursor * is repositioned below the last non-annotation text item. If the cursor is on * an item, and has items attached then: the cursor is positioned below the * pointed to item, and the items below are 'pushed down' to make room. * * @param toDropFrom * The Item being pointed at by the mouse, may be null to indicate * the cursor is in free space. */ public static boolean Drop(Item toDropFrom, boolean bPasting) { try { FrameUtils.setLastEdited(null); String newItemText = DEFAULT_NEW_ITEM_TEXT; // if a line is being rubber-banded, this is a no-op if (Frame.rubberbandingLine()) { return false; } // if the cursor is in free space then the drop will happen from the // last non annotation text item on the frame if (toDropFrom == null) { toDropFrom = DisplayController.getCurrentFrame().getLastNonAnnotationTextItem(); } // if no item was found, return if (toDropFrom == null) { MessageBay.errorMessage("No item could be found to drop from"); return false; } if (!(toDropFrom instanceof Text)) { MessageBay.displayMessage("Only text items can be dropped from"); return false; } // Get the list of items that must be dropped List column = DisplayController.getCurrentFrame().getColumn(toDropFrom); if (column == null) { MessageBay.errorMessage("No column found to align items to"); return false; } Item title = DisplayController.getCurrentFrame().getTitleItem(); // We won't do auto-bulleting when dropping from titles if (!bPasting && toDropFrom != title) { newItemText = getAutoBullet(((Text) toDropFrom).getFirstLine()); } Text dummyItem = null; if (!bPasting && FreeItems.textOnlyAttachedToCursor()) { dummyItem = (Text) FreeItems.getItemAttachedToCursor(); String autoBullet = getAutoBullet(dummyItem.getText()); if (autoBullet.length() > 0) { newItemText = ""; } dummyItem.setText(newItemText + dummyItem.getText()); } dummyItem = Text.createText(); if (FreeItems.textOnlyAttachedToCursor()) { Text t = (Text) FreeItems.getItemAttachedToCursor(); dummyItem.setSize(t.getSize()); int lines = t.getTextList().size(); for (int i = 0; i < lines; i++) { newItemText += '\n'; } } dummyItem.setText(newItemText); // If the only item on the frame is the title and the frame name // goto the zero frame and drop to the @start if there is one // or a fixed amount if there is not if (column.size() == 0) { Frame current = DisplayController.getCurrentFrame(); // Item itemTemplate = current.getItemTemplate(); int xPos = title.getX() + FrameCreator.INDENT_FROM_TITLE; int yPos = FrameCreator.getYStart(title); // Check for @start on the zero frame Frame zero = FrameIO.LoadFrame(current.getFramesetName() + '0'); Text start = zero.getAnnotation("start"); if (start != null) { xPos = start.getX(); yPos = start.getY(); } dummyItem.setPosition(xPos, yPos); // DisplayIO.setCursorPosition(xPos, yPos); checkMovingCursor(dummyItem); } else { int yPos = column.get(0).getY() + 1; int xPos = column.get(0).getX(); // Either position the new item below the title or just above // the first item below the title if (toDropFrom == title && column.get(0) != title) { // If dropping from the title position just above top item yPos = column.get(0).getY() - 1; Frame current = DisplayController.getCurrentFrame(); // Check for @start on the zero frame Frame zero = FrameIO.LoadFrame(current.getFramesetName() + '0'); Text start = zero.getAnnotation("start"); if (start != null) { yPos = Math.min(yPos, start.getY()); } } dummyItem.setPosition(xPos, yPos); column.add(dummyItem); FrameUtils.Align(column, false, 0); // Check if it will be outside the frame area if (dummyItem.getY() < 0 || dummyItem.getY() > DisplayController.getFramePaintAreaHeight()) { // Check for the 'next' tag! Frame current = DisplayController.getCurrentFrame(); Item next = current.getAnnotation("next"); Item prev = current.getAnnotation("previous"); // Check for an unlinked next tag if ((next != null && !next.hasLink()) || (prev != null && prev.hasLink())) { Frame firstFrame = current; if (next != null) { next.delete(); } FrameCreator frameCreator = new FrameCreator(null); // Add the next button next = frameCreator.addNextButton(current, null); // Create the new frame linked to the next tag boolean mouseMoved = tdfc(next); Frame moreFrame = DisplayController.getCurrentFrame(); // Add previous button to the new frame frameCreator.addPreviousButton(moreFrame, firstFrame.getName()); Item first = current.getAnnotation("first"); if (first != null) { frameCreator.addFirstButton(moreFrame, first.getLink()); } else { frameCreator.addFirstButton(moreFrame, firstFrame.getName()); } // Add the @next if we are pasting // if (bPasting) { // Item copy = next.copy(); // copy.setLink(null); // moreFrame.addItem(copy); // } moreFrame.setTitle(firstFrame.getTitleItem().getText()); // need to move the mouse to the top of the frame if // there wasnt an @start on it if (!mouseMoved) { Item moreTitle = moreFrame.getTitleItem(); moreTitle.setOverlayPermission(UserAppliedPermission.full); Drop(moreTitle, bPasting); } // Add the bullet text to the item dummyItem.setPosition(DisplayController.getMouseX(), DisplayController.getMouseY()); } else { MessageBay.warningMessage("Can not create items outside the frame area"); // ensures correct repainting when items don't move DisplayController.setCursorPosition(DisplayController.getMouseX(), DisplayController.getMouseY()); return false; } } if (!FreeItems.textOnlyAttachedToCursor() && !dummyItem.isEmpty()) { DisplayController.getCurrentFrame().addItem(dummyItem); } checkMovingCursor(dummyItem); } if (dummyItem.getText().length() == 0 || FreeItems.hasItemsAttachedToCursor()) { dummyItem.getParentOrCurrentFrame().removeItem(dummyItem); dummyItem.setRightMargin(DisplayController.getFramePaintAreaWidth(), false); } else { dummyItem.setWidth(toDropFrom.getWidth()); } DisplayController.resetCursorOffset(); DisplayController.requestRefresh(true); } catch (RuntimeException e) { // MessageBay.errorMessage(e.getMessage()); e.printStackTrace(); return false; } return true; } /** * @param dummyItem */ public static void moveCursorAndFreeItems(int x, int y) { Point oldMousePosition = DisplayController.getMousePosition(); if (oldMousePosition.getX() == x && oldMousePosition.getY() == y) { return; } DisplayController.setCursorPosition(x, y); Item firstItem = FreeItems.getItemAttachedToCursor(); if (firstItem == null) { return; } int deltaX = firstItem.getX() - x; int deltaY = firstItem.getY() - y; for (Item i : FreeItems.getInstance()) { i.setPosition(i.getX() - deltaX, i.getY() - deltaY); } } /** * @param dummyItem */ private static void checkMovingCursor(Text dummyItem) { // Move the item to the cursor position if (FreeItems.hasItemsAttachedToCursor()) { moveCursorAndFreeItems(dummyItem.getX(), dummyItem.getY()); } else { DisplayController.MoveCursorToEndOfItem(dummyItem); } } private static void calculateItem(Item toCalculate) { if (toCalculate == null) { return; } if (!toCalculate.update()) { toCalculate.setFormula(null); MessageBay.errorMessage("Can not calculate formula [" + toCalculate.getText() + ']'); } } /** * Adjusts the size of the given Item, by the given amount. Note: The amount is * relative and can be positive or negative. * * @param toSet * The Item whose size is to be adjusted * @param diff * The amount to adjust the Item's size by * @param moveCursor * true if the cursor position should be automatically adjusted with * resizing */ public static void SetSize(Item item, int diff, boolean moveCursor, boolean insideEnclosure, boolean isControlDown) { Collection toSize = new HashSet(); Collection widgets = new HashSet(); // the mouse is only moved when the Item is on the frame, not free // boolean moveMouse = false; Item toSet = null; // if the user is not pointing to any item if (item == null) { if (FreeItems.hasItemsAttachedToCursor()) { toSize.addAll(FreeItems.getInstance()); } else { MessageBay.displayMessage("There are no Items selected on the Frame or on the Cursor"); return; } } else { if (item.isFrameName()) { // scale the entire frame if (diff != 0) { double scaleFactor = diff > 0 ? 1.1 : 0.909090909; zoomFrameIfEnabled(DisplayController.getCurrentFrame(), scaleFactor, new Point(0, 0)); } // MessageBay.displayMessage("Can not resize the frame name"); return; } // check permissions if (!item.hasPermission(UserAppliedPermission.full)) { Item editTarget = item.getEditTarget(); if (editTarget != item && editTarget.hasPermission(UserAppliedPermission.full)) { item = editTarget; } else { MessageBay.displayMessage("Insufficient permission to change the size of that item"); return; } } toSet = item; // For resizing enclosures pick up everything that is attached to // items partly in the enclosure // TODO make this only pick up stuff COMPLETELY enclosed... if we // change copying to copy only the stuff completely enclosed if (insideEnclosure) { for (Item i : FrameUtils.getCurrentItems(toSet)) { if (i.hasPermission(UserAppliedPermission.full) && !toSize.contains(i)) { toSize.addAll(i.getAllConnected()); } } } // Enclosed circle centers are resized with the center as origin // Just add the circle center to the list of items to size else if (!toSet.hasEnclosures() && !(toSet instanceof Text) && toSet.isLineEnd()) { toSize.addAll(toSet.getLines()); } else if (toSet instanceof Line) { Line line = (Line) toSet; if (!(toSet instanceof WidgetEdge) || ((WidgetEdge) toSet).getWidgetSource().isWidgetEdgeThicknessAdjustable()) { float current = Math.abs(line.getThickness()); current = Math.max(current + diff, Item.MINIMUM_THICKNESS); line.setThickness(current); DisplayController.requestRefresh(true); return; } } else { toSize.add(toSet); } } // add widgets to notify for (Item i : toSize) { if (i instanceof WidgetEdge) { widgets.add(((WidgetEdge) i).getWidgetSource()); } else if (i instanceof WidgetCorner) { widgets.add(((WidgetCorner) i).getWidgetSource()); } } Point origin = EcosystemManager.getInputManager().getCursorPosition(); // Inside enclosures increase the size of the enclosure double ratio = (100.0 + diff * 2) / 100.0; if (insideEnclosure) { Collection done = new HashSet(); // adjust the size of all the items for (Item i : toSize) { if (done.contains(i)) { continue; } if (i.isLineEnd()) { if (!(i instanceof WidgetCorner) || !((WidgetCorner) i).getWidgetSource().isFixedSize()) // don't // size // fixed // widgets { Collection allConnected = i.getAllConnected(); done.addAll(allConnected); for (Item it : allConnected) { it.translate(origin, ratio); it.setArrowheadLength((float) (it.getArrowheadLength() * ratio)); } i.setThickness((float) (i.getThickness() * ratio)); } } else if (i instanceof XRayable) { XRayable xRay = (XRayable) i; Text source = xRay.getSource(); // Ensure that the source is done before the XRayable if (!done.contains(source)) { scaleText(insideEnclosure, origin, ratio, done, source); } i.translate(origin, ratio); i.setThickness((float) (i.getThickness() * ratio)); done.add(i); } else if (i.hasVector()) { // TODO Improve the effiency of resizing vectors... ie... // dont want to have to reparse all the time assert (i instanceof Text); Text text = (Text) i; AttributeValuePair avp = new AttributeValuePair(text.getText()); double scale = 1F; try { scale = avp.getDoubleValue(); } catch (Exception e) { } scale *= ratio; NumberFormat nf = Vector.getNumberFormatter(); text.setAttributeValue(nf.format(scale)); text.translate(origin, ratio); item.getParent().parse(); } else if (i instanceof Text) { scaleText(insideEnclosure, origin, ratio, done, (Text) i); } } // refresh anchored items if (refreshAnchors(toSize)) { FrameUtils.Parse(DisplayController.getCurrentFrame(), false); } // notify widgets they were resized for (Widget iw : widgets) { iw.onResized(); } DisplayController.requestRefresh(true); return; } // adjust the size of all the items for (Item i : toSize) { // Lines and dots use thickness, not size if (i.hasEnclosures()) { Circle c = (Circle) i.getEnclosures().iterator().next(); c.setSize(c.getSize() * (float) ratio); } else if (i instanceof Line || i instanceof Circle && !insideEnclosure) { float current = Math.abs(i.getThickness()); current = Math.max(current + diff, Item.MINIMUM_THICKNESS); i.setThickness(current); } else if (i instanceof Dot) { Item dot = i; float current = Math.abs(dot.getThickness()); current = Math.max(current + diff, Item.MINIMUM_THICKNESS); dot.setThickness(current); } else if (i.hasVector()) { assert (item instanceof Text); Text text = (Text) item; AttributeValuePair avp = new AttributeValuePair(text.getText()); double scale = 1F; try { scale = avp.getDoubleValue(); } catch (Exception e) { } scale *= ratio; NumberFormat nf = Vector.getNumberFormatter(); text.setAttributeValue(nf.format(scale)); text.translate(origin, ratio); item.getParent().parse(); } else { float oldSize = Math.abs(i.getSize()); float newSize = Math.max(oldSize + diff, Item.MINIMUM_THICKNESS); float resizeRatio = newSize / oldSize; // Set size for Picture also translates i.setSize(newSize); if (i instanceof Text && i.getSize() != oldSize) { if (toSize.size() == 1 && !isControlDown) { moveCursorAndFreeItems(i.getX(), i.getY()); } else { i.translate(origin, resizeRatio); if (i.isLineEnd()) { i.setPosition(i.getPosition()); } } } } } if (toSet != null) { toSet.getParent().setChanged(true); } // refresh anchored items if (refreshAnchors(toSize)) { FrameUtils.Parse(DisplayController.getCurrentFrame(), false); } DisplayController.requestRefresh(true); } /** * @param origin * @param ratio * @param done * @param source */ private static void scaleText(boolean insideEnclosure, Point origin, double ratio, Collection done, Text source) { if (insideEnclosure) { source.setWidth(Math.round((float) (source.getAbsoluteWidth() * ratio))); } source.translate(origin, ratio); source.setSize((float) (source.getSize() * ratio)); done.add(source); } private static boolean refreshAnchors(Collection items) { boolean bReparse = false; for (Item i : items) { Integer anchorLeft = i.getAnchorLeft(); Integer anchorRight = i.getAnchorRight(); Integer anchorTop = i.getAnchorTop(); Integer anchorBottom = i.getAnchorBottom(); if (anchorLeft != null) { i.setAnchorLeft(anchorLeft); if (i.hasVector()) { bReparse = true; } } if (anchorRight != null) { i.setAnchorRight(anchorRight); if (i.hasVector()) { bReparse = true; } } if (anchorTop != null) { i.setAnchorTop(anchorTop); if (i.hasVector()) { bReparse = true; } } if (anchorBottom != null) { i.setAnchorBottom(anchorBottom); if (i.hasVector()) { bReparse = true; } } } return bReparse; } /** * Sets the colour of the current Item based on its current colour. The colours * proceed in the order stored in COLOR_WHEEL. * * @param toSet * The Item whose colour is to be changed */ private static void SetColor(Item item, boolean setTransparent, boolean setBackgroundColor) { // first determine the next color Colour color = null; Frame currentFrame = DisplayController.getCurrentFrame(); if (item == null) { if (FreeItems.hasItemsAttachedToCursor()) { color = FreeItems.getInstance().get(0).getColor(); } else { return; } // change the background color if the user is pointing on the frame name } else if (item == currentFrame.getNameItem()) { // check permissions if (!item.hasPermission(UserAppliedPermission.full)) { MessageBay.displayMessage("Insufficient permission to the frame's background color"); return; } if (setTransparent) { currentFrame.setBackgroundColor(null); } else { currentFrame.toggleBackgroundColor(); } DisplayController.requestRefresh(true); return; } else { // check permissions if (!item.hasPermission(UserAppliedPermission.full)) { Item editTarget = item.getEditTarget(); if (editTarget != item && editTarget.hasPermission(UserAppliedPermission.full)) { item = editTarget; } else { MessageBay.displayMessage("Insufficient permission to change color"); return; } } // Toggling color of circle center changes the circle fill color if (item.hasEnclosures()) { if (setBackgroundColor) { SetGradientColor(item.getEnclosures().iterator().next(), setTransparent); } else { SetFillColor(item.getEnclosures().iterator().next(), setTransparent); } } else if (setBackgroundColor) { color = item.getPaintBackgroundColor(); } else { color = item.getPaintColor(); } } if (setTransparent) { color = null; } else if (setBackgroundColor) { color = ColorUtils.getNextColor(color, TemplateSettings.FillColorWheel.get(), item.getPaintColor()); } else { color = ColorUtils.getNextColor(color, TemplateSettings.ColorWheel.get(), currentFrame.getPaintBackgroundColor()); } if (setBackgroundColor) { if (item == null && FreeItems.hasItemsAttachedToCursor()) { for (Item i : FreeItems.getInstance()) { i.setBackgroundColor(color); } } else { item.setBackgroundColor(color); item.getParent().setChanged(true); } } else { if (item == null && FreeItems.hasItemsAttachedToCursor()) { for (Item i : FreeItems.getInstance()) { i.setColor(color); } } else { item.setColor(color); item.getParent().setChanged(true); } } DisplayController.requestRefresh(true); } private static void SetFillColor(Item item, boolean setTransparent) { if (item == null) { return; } if (!item.hasPermission(UserAppliedPermission.full)) { MessageBay.displayMessage("Insufficient permission to change fill color"); return; } Item toSet = item; Colour color = toSet.getFillColor(); if (setTransparent) { color = null; } else { color = ColorUtils.getNextColor(color, TemplateSettings.FillColorWheel.get(), toSet.getGradientColor()); } // if (color == null) { // MessageBay.displayMessage("FillColor is now transparent"); // } toSet.setFillColor(color); toSet.getParent().setChanged(true); DisplayController.requestRefresh(true); } private static void SetGradientColor(Item item, boolean setTransparent) { if (item == null) { return; } if (!item.hasPermission(UserAppliedPermission.full)) { MessageBay.displayMessage("Insufficient permission to change gradient color"); return; } Item toSet = item; Colour color = toSet.getGradientColor(); if (setTransparent) { color = null; } else { color = ColorUtils.getNextColor(color, TemplateSettings.ColorWheel.get(), toSet.getFillColor()); } // if (color == null) { // MessageBay.displayMessage("FillColor is now transparent"); // } toSet.setGradientColor(color); toSet.getParent().setChanged(true); DisplayController.requestRefresh(true); } /** * Toggles the given Item's annotation status on\off. * * @param toToggle * The Item to toggle */ private static void ToggleAnnotation(Item toToggle) { if (toToggle == null) { MessageBay.displayMessage("There is no Item selected to toggle"); return; } // check permissions if (!toToggle.hasPermission(UserAppliedPermission.full)) { MessageBay.displayMessage("Insufficient permission to toggle that item's annotation"); return; } toToggle.setAnnotation(!toToggle.isAnnotation()); toToggle.getParent().setChanged(true); DisplayController.requestRefresh(true); } /** * Toggles the face style of a text item * * @param toToggle * The Item to toggle */ private static void ToggleFontStyle(Item toToggle) { if (toToggle == null) { MessageBay.displayMessage("There is no Item selected to toggle"); return; } // check permissions if (!toToggle.hasPermission(UserAppliedPermission.full)) { MessageBay.displayMessage("Insufficient permission to toggle that item's annotation"); return; } if (toToggle instanceof Text) { Text text = (Text) toToggle; text.toggleFontStyle(); text.getParent().setChanged(true); DisplayController.requestRefresh(true); } } /** * Toggles the face style of a text item * * @param toToggle * The Item to toggle */ private static void ToggleFontFamily(Item toToggle) { if (toToggle == null) { MessageBay.displayMessage("There is no Item selected to toggle"); return; } // check permissions if (!toToggle.hasPermission(UserAppliedPermission.full)) { MessageBay.displayMessage("Insufficient permission to toggle that item's annotation"); return; } if (toToggle instanceof Text) { Text text = (Text) toToggle; text.toggleFontFamily(); text.getParent().setChanged(true); DisplayController.requestRefresh(true); } } /** * Creates a new Frameset with the name given by the Item * * @param name */ private static void CreateFrameset(Item item) { if (item == null) { MessageBay.displayMessage("There is no selected item to use for the frameset name"); return; } if (!(item instanceof Text)) { MessageBay.displayMessage("Framesets can only be created from text items"); return; } // Don't create frameset if the item is linked if (item.getLink() != null) { MessageBay.displayMessage("A frameset can not be created from a linked item"); return; } // check permissions if (!item.hasPermission(UserAppliedPermission.full)) { MessageBay.displayMessage("Insufficient permission to create a frameset from this item"); return; } Text text = (Text) item; try { String name = text.getFirstLine(); Frame linkTo = null; if (name.toLowerCase().startsWith("group: ")) { String groupName = name.substring(7); // create the new group linkTo = FrameIO.CreateNewGroup(groupName); } else { // create the new frameset linkTo = FrameIO.CreateNewFrameset(name); } DisplayController.setCursor(Item.DEFAULT_CURSOR); text.setLink(linkTo.getName()); text.getParent().setChanged(true); FrameUtils.DisplayFrame(linkTo, true, true); linkTo.moveMouseToDefaultLocation(); // this needs to be done if the user doesn't move the mouse before // doing Tdfc while the cursor is set to the text cursor DisplayController.setCursor(Item.DEFAULT_CURSOR); } catch (Exception e) { MessageBay.errorMessage(e.getMessage()); } } private void move(int direction, boolean isShiftDown, boolean isCtrlDown) { Item on = FrameUtils.getCurrentItem(); if ((on == null) || (on instanceof Picture)) { navigateFrame(direction); return; } if (on instanceof Text) { Text text = (Text) on; // When the user hits the left and right button with mouse // positions over the the frame name navigation occurs if (text.isFrameName()) { navigateFrame(direction); return; } else { FrameUtils.setLastEdited(text); DisplayController.setTextCursor(text, direction, false, isShiftDown, isCtrlDown, true); } } } private void navigateFrame(int direction) { switch (direction) { case Text.RIGHT: case Text.PAGE_UP: Navigation.NextFrame(false); break; case Text.LEFT: case Text.PAGE_DOWN: Navigation.PreviousFrame(false); break; case Text.HOME: case Text.LINE_HOME: Navigation.ZeroFrame(); break; case Text.END: case Text.LINE_END: Navigation.LastFrame(); break; } } /** * Moves the cursor to the next text item on the frame * * @param currentItem * @param direction * move up if direction is negative, down if direction is positive */ public static void NextTextItem(Item currentItem, boolean down) { // Move the cursor to the next text item Frame current = DisplayController.getCurrentFrame(); Text title = current.getTitleItem(); Collection currentItems = FrameUtils.getCurrentTextItems(); List textItems = new ArrayList(); // Move to the next text item in the box if if (currentItems.contains(currentItem)) { textItems.addAll(currentItems); } else { if (title != null) { textItems.add(title); } textItems.addAll(current.getBodyTextItems(true)); } Collections.sort(textItems); if (textItems.size() == 0) { // If there are no text items on the frame its a NoOp if (title == null) { return; } if (title != null) { DisplayController.MoveCursorToEndOfItem(title); } DisplayController.requestRefresh(true); return; } // If the user is mouse wheeling in free space... if (currentItem == null) { // find the nearest item in the correct direction int currY = DisplayController.getMouseY(); for (int i = 0; i < textItems.size(); i++) { Item t = textItems.get(i); if (currY < t.getY()) { if (down) { DisplayController.MoveCursorToEndOfItem(t); } else { if (i == 0) { DisplayController.MoveCursorToEndOfItem(current.getTitleItem()); } else { DisplayController.MoveCursorToEndOfItem(textItems.get(i - 1)); } } DisplayController.requestRefresh(true); return; } } // If we are at the bottom of the screen and the user scrolls down // then scroll backup to the title if (textItems.size() > 0) { DisplayController.MoveCursorToEndOfItem(textItems.get(textItems.size() - 1)); } return; } // Find the current item... then move to the next item int i = textItems.indexOf(currentItem); int nextIndex = i + (down ? 1 : -1); if (nextIndex >= 0 && nextIndex < textItems.size()) { DisplayController.MoveCursorToEndOfItem(textItems.get(nextIndex)); } else { DisplayController.MoveCursorToEndOfItem(currentItem); } return; } private static void copyItemToClipboard(Item on) { if (on == null || !(on instanceof Text)) { return; } Text text = (Text) on; String string = text.copySelectedText(); if (string == null || string.length() == 0) { string = text.getText(); } // add the text of the item to the clipboard ClipboardData clipboardData = ClipboardData.fromString(string); EcosystemManager.getClipboardManager().set(clipboardData); } public static void delete(Item toDelete, Collection deleteItems, Collection deleteEnclosure, boolean alternateMode) { boolean bRecalculate = false; FrameUtils.setLastEdited(null); _offX = _offY = 0; Frame currentFrame = DisplayController.getCurrentFrame(); // check if the user is pointing at the frame's framename if (toDelete != null && toDelete == currentFrame.getNameItem()) { currentFrame.clear(false); DisplayController.requestRefresh(true); return; } // if the user is deleting items attached to the cursor if (FreeItems.hasItemsAttachedToCursor()) { // 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 = DisplayController.getMouseX() - anchored.getX() + anchored.getOffset().getX(); _offY = DisplayController.getMouseY() - anchored.getY() + anchored.getOffset().getY(); } else { // if shift is pressed delete the entire shape attached to the dot if (alternateMode) { 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 (alternateMode) { Collection tmp = deleteEnclosure; if (tmp != null) { items = new ArrayList(); items.addAll(tmp); for (Item i : tmp) { if (i instanceof Dot) { items.addAll(((Dot) i).getLines()); } } } } else { items = deleteItems; } 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 deleted, remove the // other end if all its connecting lines are being // deleted 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(); DisplayController.requestRefresh(true); // otherwise this is an undo command } return; // this is a delete command } else { // Special case if toDelete item is an image: only want to delete if over // non-background pixel color if (toDelete instanceof Picture) { int mouseX = DisplayController.getMouseX(); int mouseY = DisplayController.getMouseY(); Picture clickedOnPicture = (Picture) toDelete; Frame current_frame = DisplayController.getCurrentFrame(); Colour bg_col = current_frame.getBackgroundColor(); if (clickedOnPicture.MouseOverBackgroundPixel(mouseX, mouseY, bg_col)) { return; } // If get to here, then user clicked on an image (non-trival pixel color), // so go ahead and let it be deleted in the usual way } // 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; List canditateFilesToDelete = new ArrayList(); if (toDelete.isLineEnd()) { // delete the entire connected shape if shift is down if (alternateMode) { 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 && alternateMode || 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()); if (toDelete instanceof Picture && alternateMode) { String pathStr = ((Picture) toDelete).getPath(); Path path = Paths.get(pathStr); canditateFilesToDelete.add(path); } } SessionStats.DeletedItems(toUndo); if (parent != null) { parent.addToUndoDelete(new ItemsList(toUndo), !canditateFilesToDelete.isEmpty()); parent.removeAllItems(toUndo); // toDelete.getConnected() deleteUnusedFiles(canditateFilesToDelete); } // reset the mouse cursor updateCursor(); if (parent != null) { // ItemUtils.EnclosedCheck(parent.getItems()); ItemUtils.Justify(parent); } if (toDelete.hasOverlay()) { FrameUtils.Parse(parent, false, false, false); DisplayController.requestRefresh(false); } DisplayController.setCursor(Item.DEFAULT_CURSOR); } currentFrame.notifyObservers(bRecalculate); DisplayController.requestRefresh(true); } private static void deleteUnusedFiles(List canditateFilesToDelete) { for (Path p: canditateFilesToDelete) { try { Path trashPath = Paths.get(FrameIO.TRASH_PATH); trashPath.toFile().mkdirs(); Path destination = trashPath.resolve(p.getFileName()); Files.move(p, destination, StandardCopyOption.ATOMIC_MOVE); } catch (IOException e) { MessageBay.displayMessage("There was a problem moving a file to the trash. File: " + p.getFileName()); } } } public static void mouseEnteredWindow() { // 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); ClipboardData clipboardData = EcosystemManager.getClipboardManager().get(); try { if (clipboardData.data instanceof ExpDataHandler) { // Expeditee data ExpDataHandler expData = (ExpDataHandler) clipboardData.data; if (expData.autoPaste) { List items = new ExpClipReader(DisplayController.getMouseX(), DisplayController.getMouseY()) .read(expData.items); // generate new IDs and pickup 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); */ EcosystemManager.getClipboardManager().set(clipboardData); } } } catch (Exception ex) { ex.printStackTrace(); } } private static boolean oldControlDown = false; private void mouseMoved(Point to) { boolean shiftStateChanged = false; if (oldControlDown != StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL)) { shiftStateChanged = true; oldControlDown = StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL); } float MouseX = to.getX(); float MouseY = to.getY(); // 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().getDistanceTo(to) > 20) // TODO: // Remove // magic // constant. // cts16 { FrameUtils.setLastEdited(null); } // If shift is down then the movement is constrained if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL) && FreeItems.getInstance().size() > 0) { // Check if we are rubber banding a line if (shiftStateChanged && FreeItems.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(to.getX() - otherEnd.getX()); int deltaY = Math.abs(to.getY() - otherEnd.getY()); // Check if its a vertical line if (deltaX < deltaY / 2) { new Constraint(thisEnd, otherEnd, thisEnd.getParentOrCurrentFrame().getNextItemID(), Constraint.VERTICAL); } // Check if its horizontal else if (deltaY <= deltaX / 2) { new Constraint(thisEnd, otherEnd, thisEnd.getParentOrCurrentFrame().getNextItemID(), Constraint.HORIZONTAL); } else { // Add DIAGONAL constraints 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 it's a line-end 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 ((c1 == Constraint.VERTICAL && c2 == Constraint.HORIZONTAL) || (c1 == Constraint.HORIZONTAL && c2 == Constraint.VERTICAL)) { 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)); } } // 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 = Point.distanceBetween(lineEnd1.getPosition(), to); double l2 = Point.distanceBetween(lineEnd2.getPosition(), to); double l3 = Point.distanceBetween(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((to.getX() - lineEnd1.getX()) * ratio) + lineEnd1.getX(); MouseY = Math.round((to.getY() - lineEnd1.getY()) * ratio) + lineEnd1.getY(); } } } } else if (shiftStateChanged && !StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL) && FreeItems.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(); } refreshHighlights(); if (FreeItems.hasCursor()) { move(FreeItems.getCursor(), to, true); } if (FreeItems.hasItemsAttachedToCursor()) { move(FreeItems.getInstance(), to); } if (_forceArrowCursor) { updateCursor(); } _forceArrowCursor = true; } /** * 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; } /** * Performs picking up of a single item, with special handling for lines. */ private void handlePickup(Item item, Point atPosition, boolean copy, boolean extrude) { // check permissions if (copy && item.isLineEnd()) { if (!item.hasPermission(UserAppliedPermission.full)) { MessageBay.displayMessage("Insufficient permission to unreel"); return; } } else if (!item.hasPermission(UserAppliedPermission.full)) { Item editTarget = item.getEditTarget(); if ((editTarget != item && editTarget.hasPermission(UserAppliedPermission.full)) || (copy && editTarget.hasPermission(UserAppliedPermission.copy))) { item = editTarget; } else { MessageBay.displayMessage("Insufficient permission to pick up item"); return; } } // Make a copy of the picked-up item if (copy) { List copies = ItemUtils.UnreelLine(item, false); // Copies will NOT be null if the user right clicked on a point if (copies == null) { Collection originals = item.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 firstCopy = copies.get(0); Item original = originals.iterator().next(); if (original.getLink() == null && original.isFrameTitle()) { // save the frame after copying // i.getParent().setChanged(true); firstCopy.setLink(original.getParentOrCurrentFrame().getName()); } } FrameGraphics.changeHighlightMode(item, HighlightMode.None); if (!extrude) { clearParent(copies); } } ItemUtils.Justify(copies); pickup(copies); // Just pick up the item without copying } else { // BROOK: WIDGET RECTANGLES DONT ALLOW DISCONNECTION if (item instanceof Line && !(item instanceof WidgetEdge)) { // Check if within 20% of the end of the line Line l = (Line) item; Item toDisconnect = l.getEndPointToDisconnect(atPosition.getX(), atPosition.getY()); if (toDisconnect == null) { pickup(item); } else { if (toDisconnect.getHighlightMode() == Item.HighlightMode.Normal) { DisplayController.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(); DisplayController.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 = DisplayController.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); DisplayController.setCursorPosition(toDisconnect.getPosition(), false); pickup(newPoint); ItemUtils.EnclosedCheck(toDisconnect.getParentOrCurrentFrame().getSortedItems()); } } } } else { if (item.isLineEnd()) { DisplayController.setCursorPosition(item.getPosition(), false); } pickup(item); } } } /** * Performs picking up of a single item, with special handling for lines. */ private void handlePickup(Collection items, Collection enclosure, Point atPosition, boolean copy) { List toPickup = new ArrayList(items.size()); if (copy) { List copies = new ArrayList(); // Set the selection mode for the items that were clicked in Collection enclosed = getFullyEnclosedItems(items); 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 : items) { i.setHighlightMode(HighlightMode.None); } } toPickup = copies; } else { for (Item ip : items) { if (ip.hasPermission(UserAppliedPermission.full)) { toPickup.add(ip); } } } pickup(toPickup); } /** * 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 doMerging(Item clicked) { if (clicked == null) { return false; } if(clicked instanceof WidgetCorner) { final WidgetCorner wc = (WidgetCorner) clicked; if(wc.getWidgetSource().ItemsMiddleClickDropped()) { return true; } } // // 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; } if (clicked instanceof Picture) { int mouseX = DisplayController.getMouseX(); int mouseY = DisplayController.getMouseY(); Picture clickedOnPicture = (Picture) clicked; Frame current_frame = DisplayController.getCurrentFrame(); Colour bg_col = current_frame.getBackgroundColor(); if (clickedOnPicture.MouseOverBackgroundPixel(mouseX, mouseY, bg_col)) { // Make 'clicked' null, effectively causing a back() operation return false; } } return true; } private static boolean isRubberBandingCorner() { return getShapeCorner(FreeItems.getInstance()) != null; } public List deleteItemsAction(Item item) { List items = new ArrayList(); Item merger = FreeItems.getItemAttachedToCursor(); assert (merger != null); // check permissions String toDeleteOwner = item.getOwner(); boolean isSameOwner = toDeleteOwner.equals(UserSettings.UserName.get()); boolean isOwnerPermissionChange = isSameOwner && merger.getText().startsWith("Permission:"); if (!item.hasPermission(UserAppliedPermission.full) && !isOwnerPermissionChange) { // Items on the message box have parent == null if (item.getParent() != null) { if (!item.isFrameName()) { Item editTarget = item.getEditTarget(); if (editTarget != item && editTarget.hasPermission(UserAppliedPermission.full)) { item = editTarget; } else { MessageBay.displayMessage("Insufficient permission"); return items; } } } else /* Its in the message area */ { MessageBay.displayMessage("Insufficient permission"); return items; } } Collection left = null; // when anchoring a line end onto a text line end, holding shift // prevents the line ends from being merged if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) { left = FreeItems.getInstance(); } else { left = merge(FreeItems.getInstance(), item); } Collection toDelete = new LinkedList(); toDelete.addAll(FreeItems.getInstance()); toDelete.removeAll(left); anchor(left); FreeItems.getInstance().clear(); DisplayController.getCurrentFrame().removeAllItems(toDelete); updateCursor(); // Make sure the dot goes away when anchoring a line end behind // a text line end if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) { refreshHighlights(); } DisplayController.requestRefresh(true); return items; // otherwise, anchor the items } private static final int RECTANGLE_TO_POINT_THRESHOLD = 20; 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)) { DisplayController.setCursorPosition(mergee.getPosition()); DisplayController.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(), DisplayController.getMouseX(), DisplayController.getMouseY()); // 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) { DisplayController.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, DisplayController.getMouseX(), DisplayController.getMouseY()); if (res != null) { remain.add(res); } } } else { if (mergee.isLineEnd()) { DisplayController.setCursorPosition(mergee.getPosition()); } // Moving the cursor ensures shapes are anchored correctly res = mergee.merge(i, DisplayController.getMouseX(), DisplayController.getMouseY()); if (res != null) { remain.addAll(res.getConnected()); } } } } updateCursor(); mergee.getParent().setChanged(true); ItemUtils.EnclosedCheck(mergee.getParent().getSortedItems()); // Mike: Why does parse frame have to be called?!? FrameUtils.Parse(mergee.getParent()); return remain; } 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 void anchorFreeItemsAction(Collection items) { if (items != null && FreeItems.getInstance().size() == 1) { Item item = FreeItems.getItemAttachedToCursor(); if (item instanceof Text) { Text text = (Text) item; if (AttributeUtils.setAttribute(text, text, 2)) { items.removeAll(FrameUtils.getEnclosingLineEnds().iterator().next().getAllConnected()); for (Item i : items) { 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; } public void copyPlace(Item item) { List copies = null; 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 something other than a lineEnd or a dot } else if (item != null && (!(FreeItems.getItemAttachedToCursor() instanceof Dot) || item instanceof Dot || item.isLineEnd())) { if(item instanceof WidgetCorner) { final WidgetCorner wc = (WidgetCorner) item; wc.getWidgetSource().ItemsRightClickDropped(); return; } // TODO Change the items merge methods so the logic is simplified // check permissions if (!item.hasPermission(UserAppliedPermission.full) && item.getParent().getNameItem() != item) { MessageBay.displayMessage("Insufficient permission to merge items"); return; } if (item instanceof Text || item instanceof Dot || item instanceof XRayable) { if (isRubberBandingCorner()) { copies = mergeGroupAction(item); // line onto something } else if (FreeItems.getInstance().size() == 2 /* && clicked instanceof XRayable */) { copies = mergeTwoItemsAction(item); } else if (FreeItems.getInstance().size() == 1) { copies = mergeSingleItemAction(item); } else { copies = stampAction(); } } else { copies = ItemUtils.UnreelLine(FreeItems.getInstance(), false); 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()) { copies = rubberBandingCornerAction(); } else { if (FreeItems.rubberBanding()) { copies = rubberBandingCopyAction(item); } else { stampItemsOnCursor(true); copies = FreeItems.getInstance(); } } } } /** * 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 List mergeGroupAction(Item item) { List copies = new ArrayList(); // Move the cursor so that the copy is exactly the // same as the shape that was anchored DisplayController.setCursorPosition(item.getPosition()); Item d = getFirstFreeLineEnd(); // get a copy of all enclosed items before merging // lineEnds Collection items = FrameUtils.getItemsEnclosedBy(DisplayController.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(item.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(), item); // anchor the points anchor(remain); FreeItems.getInstance().clear(); pickup(copies); return copies; } private static Item getFirstFreeLineEnd() { for (Item i : FreeItems.getInstance()) { if (i.isLineEnd()) { return i; } } return null; } public List mergeTwoItemsAction(Item item) { List copies = new ArrayList(); copies = ItemUtils.UnreelLine(FreeItems.getInstance(), false); Collection leftOver = merge(FreeItems.getInstance(), item); 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, EcosystemManager.getInputManager().getCursorPosition()); pickup(copies); // point onto point return copies; } public List mergeSingleItemAction(Item item) { List copies = new ArrayList(); copies = copy(FreeItems.getInstance()); Collection remain = merge(copies, item); // ignore items that could not be merged. anchor(remain); return copies; } public List stampAction() { List copies = new ArrayList(); stampItemsOnCursor(true); copies = FreeItems.getInstance(); return copies; } /** * */ 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(), EcosystemManager.getInputManager().getCursorPosition()); for (Item i : copies) { i.setHighlightMode(HighlightMode.None); } anchor(copies); } public List rubberBandingCornerAction() { List copies = new ArrayList(); 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(DisplayController.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 = DisplayController.getMouseX(); double currentY = DisplayController.getMouseY(); Item closest = null; double shortestDistance = Double.MAX_VALUE; for (Item next : toCopy) { if (next instanceof Line) { continue; } double distance = Point.distanceBetween((int) currentX, (int) currentY, next.getX(), next.getY()); if (distance < shortestDistance) { shortestDistance = distance; closest = next; } } // Move the cursor to closest item DisplayController.setCursorPosition(closest.getPosition()); // Pickup copy of the stuff inside the rectangle 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); } } return 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; } public List rubberBandingCopyAction(Item item) { List copies = new ArrayList(); if (item != null) { Collection leftOver = merge(FreeItems.getInstance(), item); anchor(leftOver); } // This is executed when the user is putting down a line // endpoint and unreeling. ie. Normal unreeling copies = ItemUtils.UnreelLine(FreeItems.getInstance(), false); 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, EcosystemManager.getInputManager().getCursorPosition()); pickup(copies); return copies; } public List newLineAction(Point position, Item item) { List copies = new ArrayList(); // If we have permission to copy this item then pick it up if (item != null && item.isLineEnd() && item.hasPermission(UserAppliedPermission.full)) { item.removeAllConstraints(); pickup(item); return copies; } if (item 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 copies; } // 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 (item instanceof Line && item.hasPermission(UserAppliedPermission.full)) { Frame current = DisplayController.getCurrentFrame(); // create the two endpoints Line oldLine = (Line) item; Item newPoint = oldLine.getStartItem().copy(); newPoint.setPosition(position); 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 copies; } Line newLine = createLine(); SessionStats.CreatedItems(newLine.getAllConnected()); return copies; } private static Line createLine() { Frame current = DisplayController.getCurrentFrame(); // create the two endpoints Item end = DisplayController.getCurrentFrame().createDot(); Item start = DisplayController.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; } public List createRectangleAction() { final List copies = new ArrayList(); Item[] d = new Item[4]; // create dots Frame current = DisplayController.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(); return copies; } /** * This method handles all left-click actions */ private void click(Item clicked, Collection clickedIn, Point position) { // Gets the current frame Frame f = DisplayController.getCurrentFrame(); // Checks if the current frame is an overlay if (f.getOverlays() != null && FrameUtils.getCurrentItem() != null) { Item i = FrameUtils.getCurrentItem(); // Checks if the item clicked in the overlay is a Rubbish Bin. If it is, delete // the item attached to the cursor and return. if (i instanceof WidgetCorner) { try { WidgetCorner wc = (WidgetCorner) i; ButtonWidget bw = (ButtonWidget) wc.getWidgetSource(); // Should call a button widgets 'itemheldwhileclicked' method, and process // depending on the widget type - else will return false. if (bw.itemHeldWhileClicked(bw) == true) { return; } } catch (Exception e) { e.printStackTrace(); } } } // 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 = position.getX(); int mouseY = position.getY(); // System.out.println(mouseX + "," + mouseY); for (Item i : DisplayController.getCurrentFrame().getSortedItems()) { // System.out.println(i.getName().toString()); if (i instanceof Text) { if (i.isNear(mouseX, mouseY)) { clicked = i; break; } } } } if (clicked instanceof Text) { Text text = (Text) clicked; /* Don't 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 && clickedIn != null && clickedIn.size() >= 4) { // Check to see if the user clicked into a widgets empty space Widget iw = null; for (Item i : clickedIn) { 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: mustn'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 : clickedIn) { /* * 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 instanceof Picture) { int mouseX = position.getX(); int mouseY = position.getY(); Picture clickedOnPicture = (Picture) clicked; Frame current_frame = DisplayController.getCurrentFrame(); Colour bg_col = current_frame.getBackgroundColor(); if (clickedOnPicture.MouseOverBackgroundPixel(mouseX, mouseY, bg_col)) { // Make 'clicked' null, effectively causing a back() operation clicked = null; } } // This makes it so clicking repeatedly on the frameName doesn't add the // frames to the backup stack. Only the first frame is added to the // backup stack. if (!(clicked != null && clicked.isFrameName())) { Navigation.ResetLastAddToBack(); } 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 (clickedOn.hasAction()) { executeActionAction(clicked); } else if (clickedOn.getLink() != null) { followLinkAction(clicked); // 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()) { respondToFrameNameClickAction(); } TDFCAction(clicked); } } else { backAction(); } } public void executeActionAction(Item item) { item.performActions(); item.setHighlightMode(HighlightMode.None); refreshHighlights(); } public void followLinkAction(Item item) { /* * 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 (!item.isOldTag()) { FrameIO.SaveFrame(DisplayController.getCurrentFrame()); } Navigation.setLastNavigationItem(item); load(item.getAbsoluteLink(), item.getLinkHistory()); // DisplayIO.UpdateTitle(); } private static void load(String toLoad, boolean addToHistory) { if (FrameIO.isValidFrameName(toLoad)) { DisplayController.clearBackedUpFrames(); FrameUtils.DisplayFrame(toLoad, addToHistory, true); } else { MessageBay.errorMessage(toLoad + " is not a valid frame name."); } } public void respondToFrameNameClickAction() { if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL)) { Navigation.PreviousFrame(false); } else { Navigation.NextFrame(false); } } public List TDFCAction(Item item) { // check for TDFC permission if (!item.hasPermission(UserAppliedPermission.createFrames)) { MessageBay.displayMessage("Insufficient permission to TDFC (Top Down Frame Creation) from that item"); return null; } if (item.isOldTag()) { return null; } try { tdfc(item); } catch (RuntimeException e) { e.printStackTrace(); MessageBay.errorMessage("Top Down Frame Creation (TDFC) error: " + e.getMessage()); } return null; } public void backAction() { // if user is not pointing at something, this is a back if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL) || StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) { forward(); } else { back(); } } private static void forward() { DisplayController.Forward(); // repaint things if necessary if (FreeItems.hasItemsAttachedToCursor()) { move(FreeItems.getInstance(), EcosystemManager.getInputManager().getCursorPosition()); } if (FreeItems.hasCursor()) { move(FreeItems.getCursor(), EcosystemManager.getInputManager().getCursorPosition(), true); } } private static void back() { DisplayController.Back(); // repaint things if necessary if (FreeItems.hasItemsAttachedToCursor()) { move(FreeItems.getInstance(), EcosystemManager.getInputManager().getCursorPosition()); } if (FreeItems.hasCursor()) { move(FreeItems.getCursor(), EcosystemManager.getInputManager().getCursorPosition(), true); } } /** * 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.hasItemsAttachedToCursor()) { 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); } } private void pickupRange(Text text, Point position, boolean copy, boolean inheritAttributes) { Text ranged; if (inheritAttributes) { // If shift is down, copy everything (size, color, etc.) except actions, links // and data ranged = text.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 final String copySelectedText = text.copySelectedText(); if (copySelectedText.length() < 1) { return; } ranged = DisplayController.getCurrentFrame().getItemTemplate(copySelectedText.charAt(0)); } // if the user is cutting text from the item if (!copy) { // 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 (!text.hasPermission(UserAppliedPermission.full) || text.isFrameName()) { MessageBay.displayMessage("Insufficient permission to cut text"); text.clearSelection(); DisplayController.requestRefresh(true); return; } // if the entire text is selected and its not a line end then pickup the item boolean entireText = text.getSelectionSize() == text.getLength(); if (entireText && !text.isLineEnd()) { text.clearSelection(); ranged.delete(); handlePickup(text, position, false, false); return; } else { ranged.setText(text.cutSelectedText()); ranged.setWidth(text.getWidth()); // If its the whole text then replace last ranged with a dot if (entireText) { Item dot = Item.replaceText(text); dot.setHighlightMode(HighlightMode.None); } } // if the user is copying text from the item } else { // 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 (!text.hasPermission(UserAppliedPermission.copy)) { MessageBay.displayMessage("Insufficient permission to copy text"); text.clearSelection(); DisplayController.requestRefresh(true); return; } ranged.setText(text.copySelectedText()); } ranged.setParent(null); ranged.setPosition(position); pickup(ranged); text.clearSelection(); text.setHighlightMode(HighlightMode.None); refreshHighlights(); DisplayController.requestRefresh(false); return; } }