source: trunk/src/org/expeditee/gui/FrameMouseActions.java@ 1064

Last change on this file since 1064 was 1064, checked in by davidb, 8 years ago

Addition of new type of frame transition, with off-screen buffering to make use of alpha-values in transition more seamless; Enhancement of display-mode to be full screen; Enahcements to mouse interactions over images -- now if the mouse is over a pixel in the image that is the same colour as the background-color of the frame, it is treated as equivalent to being over the background, rather than the image item. Overall has a natural feel. You now need to click on 'non-trival' parts of an image to get image-related interactive Expteditee behaviour. (note: this is how text already behaves)

File size: 112.7 KB
Line 
1/**
2 * FrameMouseActions.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.gui;
20
21import java.awt.Color;
22import java.awt.Image;
23import java.awt.Point;
24import java.awt.Rectangle;
25import java.awt.Toolkit;
26import java.awt.datatransfer.Clipboard;
27import java.awt.datatransfer.DataFlavor;
28import java.awt.datatransfer.Transferable;
29import java.awt.event.ActionEvent;
30import java.awt.event.ActionListener;
31import java.awt.event.KeyEvent;
32import java.awt.event.MouseEvent;
33import java.awt.event.MouseListener;
34import java.awt.event.MouseMotionListener;
35import java.awt.event.MouseWheelEvent;
36import java.awt.event.MouseWheelListener;
37import java.text.NumberFormat;
38import java.util.ArrayList;
39import java.util.Collection;
40import java.util.Date;
41import java.util.HashSet;
42import java.util.Iterator;
43import java.util.LinkedHashSet;
44import java.util.LinkedList;
45import java.util.List;
46import java.util.Set;
47import java.util.TimerTask;
48
49import javax.swing.Timer;
50
51import org.expeditee.actions.Actions;
52import org.expeditee.actions.Misc;
53import org.expeditee.actions.Navigation;
54import org.expeditee.gui.indirect.mouse.IndirectMouseActions;
55import org.expeditee.gui.indirect.mouse.MouseAction;
56import org.expeditee.gui.indirect.mouse.MouseInfo;
57import org.expeditee.gui.Frame;
58import org.expeditee.io.ExpClipReader;
59import org.expeditee.io.ItemSelection;
60import org.expeditee.io.ItemSelection.ExpDataHandler;
61import org.expeditee.items.Circle;
62import org.expeditee.items.Constraint;
63import org.expeditee.items.Dot;
64import org.expeditee.items.Item;
65import org.expeditee.items.Item.HighlightMode;
66import org.expeditee.items.ItemAppearence;
67import org.expeditee.items.ItemUtils;
68import org.expeditee.items.Line;
69import org.expeditee.items.Picture;
70import org.expeditee.items.Text;
71import org.expeditee.items.UserAppliedPermission;
72import org.expeditee.items.XRayable;
73import org.expeditee.items.widgets.ButtonWidget;
74import org.expeditee.items.widgets.InteractiveWidget;
75import org.expeditee.items.widgets.WidgetCorner;
76import org.expeditee.items.widgets.WidgetEdge;
77import org.expeditee.settings.UserSettings;
78import org.expeditee.settings.experimental.ExperimentalFeatures;
79import org.expeditee.stats.SessionStats;
80
81public class FrameMouseActions implements MouseListener, MouseMotionListener,
82 MouseWheelListener {
83
84 private static int _lastMouseClickModifiers = 0;
85
86 private static MouseEvent _lastMouseDragged;
87
88 private boolean _autoStamp = false;
89
90 private FrameMouseActions() {
91 IndirectMouseActions.getInstance().setBackAction(new MouseAction() {
92 @Override
93 public List<Item> exec(MouseInfo info) {
94 //if user is not pointing at something,this is a back
95 if (info.isControlDown || info.isShiftDown)
96 forward();
97 else
98 back();
99 return null;
100 }
101 });
102 IndirectMouseActions.getInstance().setTDFCAction(new MouseAction() {
103 @Override
104 public List<Item> exec(final MouseInfo info) {
105 // check for TDFC permission
106 if (!info.clicked.hasPermission(UserAppliedPermission.createFrames)) {
107 MessageBay
108 .displayMessage("Insufficient permission to TDFC (Top Down Frame Creation) from that item");
109 return null;
110 }
111
112 if (info.clicked.isOldTag())
113 return null;
114
115 try {
116 tdfc(info.clicked);
117 } catch (RuntimeException e) {
118 e.printStackTrace();
119 MessageBay.errorMessage("Top Down Frame Creation (TDFC) error: " + e.getMessage());
120 }
121 return null;
122 }
123 });
124 IndirectMouseActions.getInstance().setRespondToFrameNameClickAction(new MouseAction() {
125 @Override
126 public List<Item> exec(MouseInfo info) {
127 if (info.isControlDown)
128 Navigation.PreviousFrame(false);
129 else
130 Navigation.NextFrame(false);
131 return null;
132 }
133 });
134 IndirectMouseActions.getInstance().setFollowLinkAction(new MouseAction() {
135 @Override
136 public List<Item> exec(MouseInfo info) {
137 /*
138 * Dont save the frame if we are moving to an old version of
139 * this frame because everytime we save with the old tag... the
140 * frame is backed up
141 */
142 if (!info.clicked.isOldTag())
143 FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
144
145 Navigation.setLastNavigationItem(info.clicked);
146 load(info.clicked.getAbsoluteLink(), info.clicked.getLinkHistory());
147 // DisplayIO.UpdateTitle();
148 return null;
149 }
150 });
151 IndirectMouseActions.getInstance().setExecuteActionAction(new MouseAction() {
152 @Override
153 public List<Item> exec(MouseInfo info) {
154 info.clicked.performActions();
155 info.clicked.setHighlightMode(HighlightMode.None);
156 getInstance().refreshHighlights();
157 return null;
158 }
159 });
160 IndirectMouseActions.getInstance().setCreateRectangleAction(new MouseAction() {
161 @Override
162 public List<Item> exec(MouseInfo info) {
163 final List<Item> copies = new ArrayList<Item>();
164 Item[] d = new Item[RECTANGLE_CORNERS];
165 // create dots
166 Frame current = DisplayIO.getCurrentFrame();
167 for (int i = 0; i < d.length; i++) {
168 d[i] = current.createDot();
169 copies.add(d[i]);
170 }
171
172 current.nextDot();
173
174 // create lines
175 copies.add(new Line(d[0], d[1], current.getNextItemID()));
176 copies.add(new Line(d[1], d[2], current.getNextItemID()));
177 copies.add(new Line(d[2], d[3], current.getNextItemID()));
178 copies.add(new Line(d[3], d[0], current.getNextItemID()));
179
180 new Constraint(d[0], d[1], current.getNextItemID(),
181 Constraint.HORIZONTAL);
182 new Constraint(d[2], d[3], current.getNextItemID(),
183 Constraint.HORIZONTAL);
184 new Constraint(d[1], d[2], current.getNextItemID(),
185 Constraint.VERTICAL);
186 new Constraint(d[3], d[0], current.getNextItemID(),
187 Constraint.VERTICAL);
188
189 anchor(new ArrayList<Item>(copies));
190 pickup(d[3]);
191 d[3].setHighlightMode(HighlightMode.Normal);
192
193 SessionStats.CreatedItems(copies);
194 copies.clear();
195 return copies;
196 }
197 });
198 IndirectMouseActions.getInstance().setExtendLineAction(new MouseAction() {
199 @Override
200 public List<Item> exec(final MouseInfo info) {
201 Line onLine = (Line) info.clicked;
202 Line newLine = onLine.copy();
203 Item end = newLine.getEndItem();
204 Item start = newLine.getStartItem();
205 end.setPosition(MouseX, MouseY);
206 start.setPosition(MouseX, MouseY);
207 onLine.autoArrowheadLength();
208 // anchor the start
209 anchor(start);
210 // attach the line to the cursor
211 pickup(end);
212
213 List<Item> toMerge = new LinkedList<Item>();
214 toMerge.add(newLine.getStartItem());
215 toMerge.add(newLine);
216
217 // Make sure the highlighting is shown when the end is
218 // anchored
219 end.setHighlightMode(Item.HighlightMode.Normal);
220 merge(toMerge, info.clicked);
221 // anchor(left);
222 // FreeItems.getInstance().clear();
223 FrameGraphics.Repaint();
224 updateCursor();
225 return null;
226 }
227 });
228 IndirectMouseActions.getInstance().setMakeGroupCopyAction(new MouseAction() {
229 @Override
230 public List<Item> exec(final MouseInfo info) {
231 List<Item> copies = new ArrayList<Item>();
232 // Set the selection mode for the items that were clicked in
233 Collection<Item> enclosed = getFullyEnclosedItems(info.clickedIn);
234 if (enclosed.size() == 0) {
235 MessageBay
236 .displayMessage("Insufficient permission to copy items");
237 } else {
238 copies = copy(enclosed);
239 clearParent(copies);
240 ItemUtils.Justify(copies);
241 pickup(copies);
242 for (Item i : info.clickedIn) {
243 i.setHighlightMode(HighlightMode.None);
244 }
245 }
246 return copies;
247 }
248 });
249 IndirectMouseActions.getInstance().setMakeCopyAction(new MouseAction() {
250 @Override
251 public List<Item> exec(MouseInfo info) {
252 List<Item> copies = ItemUtils.UnreelLine(info.clicked, _controlDown);
253 // Copies will NOT be null if the user right clicked on a point
254 if (copies == null) {
255 Collection<Item> originals = info.clicked.getConnected();
256 copies = ItemUtils.CopyItems(originals, _extrude);
257 // if this is the title of the frame, link it to the frame
258 if (originals.size() == 1 && copies.size() == 1) {
259 Item copy = copies.get(0);
260 Item original = originals.iterator().next();
261 if (original.getLink() == null
262 && original.isFrameTitle()) {
263 // save the frame after copying
264 // i.getParent().setChanged(true);
265 copy.setLink(original.getParentOrCurrentFrame()
266 .getName());
267 }
268 }
269
270 FrameGraphics.changeHighlightMode(info.clicked,
271 HighlightMode.None);
272
273 if (!_extrude)
274 clearParent(copies);
275 }
276
277 ItemUtils.Justify(copies);
278
279 pickup(copies);
280 return copies;
281 }
282 });
283 IndirectMouseActions.getInstance().setExtrudeAction(new MouseAction() {
284 @Override
285 public List<Item> exec(MouseInfo info) {
286 List<Item> copies = null;
287 List<Item> originals = new ArrayList<Item>();
288 // remove any lines that dont have both endpoints
289 // floating
290 for (Item i : FreeItems.getInstance()) {
291 if (i.isFloating())
292 originals.add(i);
293 }
294 if (copies == null)
295 copies = ItemUtils.CopyItems(originals, _extrude);
296 for (Item i : copies)
297 i.setOffset(0, 0);
298 anchor(FreeItems.getInstance());
299 // Move isnt working right for extruding!!
300 // move(copies);
301 pickup(copies);
302 return copies;
303 }
304 });
305 IndirectMouseActions.getInstance().setRubberBandingCopyAction(new MouseAction() {
306 @Override
307 public List<Item> exec(MouseInfo info) {
308 List<Item> copies = new ArrayList<Item>();
309 if (info.clicked != null) {
310 Collection<Item> leftOver = merge(FreeItems
311 .getInstance(), info.clicked);
312 anchor(leftOver);
313 }
314 // This is executed when the user is putting down a line
315 // endpoint and unreeling. ie. Normal unreeling
316 copies = ItemUtils.UnreelLine(FreeItems.getInstance(),
317 _controlDown);
318
319 if (copies == null)
320 copies = copy(FreeItems.getInstance());
321 anchor(FreeItems.getInstance());
322 for (Item i : copies)
323 i.setOffset(0, 0);
324 // need to move to prevent cursor dislocation
325 move(copies);
326 pickup(copies);
327 return copies;
328 }
329 });
330 IndirectMouseActions.getInstance().setRubberBandingCornerAction(new MouseAction() {
331 @Override
332 public List<Item> exec(final MouseInfo info) {
333 List<Item> copies = new ArrayList<Item>();
334 Item d = getFirstFreeLineEnd();
335 // anchor the points
336 anchor(FreeItems.getInstance());
337 FreeItems.getInstance().clear();
338 updateCursor();
339 // pick up a copy of all enclosed items
340 Collection<Item> enclosedItems = FrameUtils
341 .getItemsEnclosedBy(DisplayIO.getCurrentFrame(), d
342 .getEnclosedShape());
343 if (enclosedItems != null) {
344 enclosedItems.removeAll(d.getAllConnected());
345 Collection<Item> toCopy = getFullyEnclosedItems(enclosedItems);
346
347 if (toCopy.size() > 0) {
348 // Find the closest item to the mouse cursor
349 double currentX = DisplayIO.getMouseX();
350 double currentY = FrameMouseActions.getY();
351 Item closest = null;
352 double shortestDistance = Double.MAX_VALUE;
353 for (Item next : toCopy) {
354 if (next instanceof Line)
355 continue;
356 double distance = Point.distance(currentX,
357 currentY, next.getX(), next.getY());
358 if (distance < shortestDistance) {
359 shortestDistance = distance;
360 closest = next;
361 }
362 }
363 // Move the cursor to closest item
364 DisplayIO.setCursorPosition(closest.getPosition());
365 // Pickup copy of the stuff inside the rectange
366 copies = copy(toCopy);
367 pickup(copies);
368 // Remove the rectangle
369 d.getParentOrCurrentFrame().removeAllItems(
370 d.getAllConnected());
371 } else {
372 // Pick up a copy of the rectangle
373 copies = copy(d.getAllConnected());
374 pickup(copies);
375 }
376 }
377 return copies;
378 }
379 });
380 IndirectMouseActions.getInstance().setStampAction(new MouseAction() {
381 @Override
382 public List<Item> exec(MouseInfo info) {
383 List<Item> copies = new ArrayList<Item>();
384 stampItemsOnCursor(true);
385 copies = FreeItems.getInstance();
386 return copies;
387 }
388 });
389 IndirectMouseActions.getInstance().setMergeSingleItemAction(new MouseAction() {
390 @Override
391 public List<Item> exec(final MouseInfo info) {
392 List<Item> copies = new ArrayList<Item>();
393 copies = copy(FreeItems.getInstance());
394 Collection<Item> remain = merge(copies, info.clicked);
395
396 // ignore items that could not be merged.
397 anchor(remain);
398 return copies;
399 }
400 });
401 IndirectMouseActions.getInstance().setMergeTwoItemsAction(new MouseAction() {
402 @Override
403 public List<Item> exec(MouseInfo info) {
404 List<Item> copies = new ArrayList<Item>();
405 copies = ItemUtils.UnreelLine(FreeItems.getInstance(),
406 _controlDown);
407 Collection<Item> leftOver = merge(FreeItems
408 .getInstance(), info.clicked);
409 anchor(leftOver);
410 if (copies == null)
411 copies = copy(FreeItems.getInstance());
412 FreeItems.getInstance().clear();
413 for (Item i : copies)
414 i.setOffset(0, 0);
415 // need to move to prevent cursor dislocation
416 move(copies);
417 pickup(copies);
418 // point onto point
419 return copies;
420 }
421 });
422 IndirectMouseActions.getInstance().setMergeGroupAction(new MouseAction() {
423 @Override
424 public List<Item> exec(final MouseInfo info) {
425 List<Item> copies = new ArrayList<Item>();
426 // Move the cursor so that the copy is exactly the
427 // same as the shape that was anchored
428 DisplayIO.setCursorPosition(info.clicked.getPosition());
429 Item d = getFirstFreeLineEnd();
430 // get a copy of all enclosed items before merging
431 // lineEnds
432 Collection<Item> items = FrameUtils.getItemsEnclosedBy(
433 DisplayIO.getCurrentFrame(), d
434 .getEnclosedShape());
435 // If its not an enclosed shape then pick up the
436 // connected shape
437 if (items == null || items.size() == 0) {
438 items = d.getAllConnected();
439 } else {
440 // For some reason the item that was clicked ends up
441 // in the enclosure and needs to be removed
442 items.removeAll(info.clicked.getConnected());
443 // the item that was the origin of the enclosed
444 // shape used to create the enclosure does not get
445 // returned from getItemsEnclosedBy to the enclosure
446 // so it must be added
447 items.addAll(d.getConnected());
448 }
449
450 Collection<Item> toCopy = new LinkedHashSet<Item>();
451
452 for (Item ip : items) {
453 if (ip.hasPermission(UserAppliedPermission.copy))
454 toCopy.add(ip);
455 }
456 copies = copy(toCopy);
457 // Now do the merging
458 Collection<Item> remain = merge(
459 FreeItems.getInstance(), info.clicked);
460 // anchor the points
461 anchor(remain);
462 FreeItems.getInstance().clear();
463 pickup(copies);
464 return copies;
465 }
466 });
467 IndirectMouseActions.getInstance().setNewLineAction(new MouseAction() {
468 @Override
469 public List<Item> exec(MouseInfo info) {
470 List<Item> copies = new ArrayList<Item>();
471 Item on = FrameUtils.onItem(DisplayIO.getCurrentFrame(), Math
472 .round(MouseX), Math.round(MouseY), true);
473 // If we have permission to copy this item then pick it up
474 if (on != null && on.isLineEnd()
475 && on.hasPermission(UserAppliedPermission.full)) {
476 on.removeAllConstraints();
477 pickup(on);
478 return copies;
479 }
480
481 if (on instanceof WidgetEdge) {
482 // Don't allow the user to break widget edges.
483 // Note: had to return here because random dots would
484 // appear otherwise... cannot understand code below
485 // with create line.
486 return copies;
487 }
488
489 // if its on a line then split the line and put a point on it and
490 // pick that point up. Only if it is not a widget line
491 if (on instanceof Line && on.hasPermission(UserAppliedPermission.full)) {
492 Frame current = DisplayIO.getCurrentFrame();
493 // create the two endpoints
494 Line oldLine = (Line) info.clicked;
495 Item newPoint = oldLine.getStartItem().copy();
496 newPoint.setPosition(MouseX, MouseY);
497
498 Item end = oldLine.getEndItem();
499 // create the Line
500 Line newLine = new Line(newPoint, end, current.getNextItemID());
501 oldLine.replaceLineEnd(end, newPoint);
502 newPoint.removeAllConstraints();
503 pickup(newPoint);
504 // Update the stats
505 Collection<Item> created = new LinkedList<Item>();
506 created.add(newPoint);
507 created.add(newLine);
508 SessionStats.CreatedItems(newLine.getAllConnected());
509 return copies;
510 }
511 Line newLine = createLine();
512 SessionStats.CreatedItems(newLine.getAllConnected());
513 return copies;
514 }
515 });
516 IndirectMouseActions.getInstance().setGroupPickupAction(new MouseAction() {
517 @Override
518 public List<Item> exec(final MouseInfo info) {
519 List<Item> copies = new ArrayList<Item>();
520 ArrayList<Item> toPickup = new ArrayList<Item>(info.clickedIn.size());
521 for (Item ip : info.clickedIn)
522 if (ip.hasPermission(UserAppliedPermission.full))
523 toPickup.add(ip);
524 pickup(toPickup);
525 // otherwise the user is creating a line
526 return copies;
527 }
528 });
529 IndirectMouseActions.getInstance().setDetachLineAction(new MouseAction() {
530 @Override
531 public List<Item> exec(final MouseInfo info) {
532 List<Item> items = new ArrayList<Item>();
533 // BROOK: WIDGET RECTANGLES DONT ALLOW DISCONNECTION
534 if (info.clicked instanceof Line && !(info.clicked instanceof WidgetEdge)) {
535 // Check if within 20% of the end of the line
536 Line l = (Line) info.clicked;
537 Item toDisconnect = l.getEndPointToDisconnect(_lastMouseClick
538 .getX(), _lastMouseClick.getY());
539
540 if (toDisconnect == null) {
541 pickup(info.clicked);
542 } else {
543 if (toDisconnect.getHighlightMode() == Item.HighlightMode.Normal) {
544 DisplayIO.setCursorPosition(toDisconnect.getPosition(),
545 false);
546 pickup(toDisconnect);
547 } else {
548 List<Line> lines = toDisconnect.getLines();
549 // This is to remove constraints from single lines
550 // with constraints...
551 // ie. partially deleted rectangles
552 if (lines.size() == 1) {
553 toDisconnect.removeAllConstraints();
554
555 DisplayIO.setCursorPosition(toDisconnect
556 .getPosition(), false);
557 // This is to ensure the selected mode will be set
558 // to Normal rather than disconnect when the line is
559 // anchored
560 toDisconnect
561 .setHighlightMode(Item.HighlightMode.Normal);
562 pickup(toDisconnect);
563 } else {
564 // If we are then detatch the line and pick up its
565 // end point...
566 Frame currentFrame = DisplayIO.getCurrentFrame();
567 Item newPoint = null;
568
569 // If the point we are disconnecting is text...
570 // Then we want to leave the text behind
571 // And disconnect a point
572 if (toDisconnect instanceof Text) {
573 newPoint = new Dot(toDisconnect.getX(),
574 toDisconnect.getY(), -1);
575 Item.DuplicateItem(toDisconnect, newPoint);
576 } else {
577 newPoint = toDisconnect.copy();
578 }
579
580 currentFrame.addItem(newPoint);
581 // remove the current item from the connected
582 // list for this item
583 l.replaceLineEnd(toDisconnect, newPoint);
584 // remove unneeded constrains
585 newPoint.removeAllConstraints();
586
587 // Set the new points mode to normal before picking
588 // it up so it will be restored correctly when
589 // anchored
590 newPoint
591 .setHighlightMode(Item.HighlightMode.Normal);
592 toDisconnect
593 .setHighlightMode(Item.HighlightMode.None);
594 DisplayIO.setCursorPosition(toDisconnect
595 .getPosition(), false);
596 pickup(newPoint);
597 ItemUtils.EnclosedCheck(toDisconnect
598 .getParentOrCurrentFrame().getItems());
599 }
600 }
601 }
602 } else {
603 if (info.clicked.isLineEnd()) {
604 DisplayIO.setCursorPosition(info.clicked.getPosition(), false);
605 }
606 pickup(info.clicked);
607 }
608 return items;
609 }
610 });
611 IndirectMouseActions.getInstance().setAnchorFreeItemsAction(new MouseAction() {
612 @Override
613 public List<Item> exec(MouseInfo info) {
614 List<Item> items = new ArrayList<Item>();
615 if (info.clickedIn != null && FreeItems.getInstance().size() == 1) {
616 Item item = FreeItems.getItemAttachedToCursor();
617 if (item instanceof Text) {
618 Text text = (Text) item;
619 if (AttributeUtils.setAttribute(text, text, 2)) {
620 info.clickedIn.removeAll(FrameUtils
621 .getEnclosingLineEnds().iterator().next()
622 .getAllConnected());
623 for (Item i : info.clickedIn) {
624 AttributeUtils.setAttribute(i, text);
625 }
626 FreeItems.getInstance().clear();
627 }
628 }
629 }
630
631 // if a line is being rubber-banded, check for auto
632 // straightening
633 anchor(FreeItems.getInstance());
634 FreeItems.getInstance().clear();
635 updateCursor();
636 _offX = _offY = 0;
637 return items;
638 }
639 });
640 IndirectMouseActions.getInstance().setDeleteItemsAction(new MouseAction() {
641 @Override
642 public List<Item> exec(final MouseInfo info) {
643 List<Item> items = new ArrayList<Item>();
644 // check permissions
645 if (!info.clicked.hasPermission(UserAppliedPermission.full)) {
646 //Items on the message box have parent == null
647 if (info.clicked.getParent() != null) {
648 if (!info.clicked.isFrameName()) {
649 Item editTarget = info.clicked.getEditTarget();
650 if (editTarget != info.clicked
651 && editTarget
652 .hasPermission(UserAppliedPermission.full)) {
653 info.clicked = editTarget;
654 } else {
655 MessageBay
656 .displayMessage("Insufficient permission");
657 return items;
658 }
659 }
660
661 } else /*Its in the message area*/ {
662 MessageBay.displayMessage("Insufficient permission");
663 return items;
664 }
665 }
666 Item merger = FreeItems.getItemAttachedToCursor();
667 assert (merger != null);
668 Collection<Item> left = null;
669 // when anchoring a line end onto a text line end, holding shift
670 // prevents the line ends from being merged
671 if (info.isShiftDown) {
672 left = FreeItems.getInstance();
673 } else {
674 left = merge(FreeItems.getInstance(), info.clicked);
675 }
676 Collection<Item> toDelete = new LinkedList<Item>();
677 toDelete.addAll(FreeItems.getInstance());
678 toDelete.removeAll(left);
679 anchor(left);
680 FreeItems.getInstance().clear();
681 DisplayIO.getCurrentFrame().removeAllItems(toDelete);
682 updateCursor();
683 // Make sure the dot goes away when anchoring a line end behind
684 // a text line end
685 if (info.isShiftDown) {
686 refreshHighlights();
687 }
688 FrameGraphics.requestRefresh(true);
689 return items;
690 // otherwise, anchor the items
691 }
692 });
693 }
694
695 private static FrameMouseActions _instance = null;
696
697 public static FrameMouseActions getInstance() {
698 if (_instance == null)
699 _instance = new FrameMouseActions();
700 return _instance;
701 }
702
703 private static final int RECTANGLE_CORNERS = 4;
704
705 // TODO say where/how used
706 private static final int MOUSE_WHEEL_THRESHOLD = 2;
707
708 private static final int MINIMUM_RANGE_DEPRESS_TIME = 250;
709
710 private static final int RECTANGLE_TO_POINT_THRESHOLD = 20;
711
712 private static Date _lastMouseClickDate = new Date();
713
714 public static final int LITTLE_MOUSE_PAUSE = 500;
715
716 public static final int ZERO_MOUSE_PAUSE = 0;
717
718 public static final int BIG_MOUSE_PAUSE = 750;
719
720 public static final int CONTEXT_FREESPACE = 0;
721
722 public static final int CONTEXT_AT_TEXT = 1;
723
724 public static final int CONTEXT_AT_LINE = 2;
725
726 public static final int CONTEXT_AT_DOT = 3;
727
728 public static final int CONTEXT_AT_ENCLOSURE = 4;
729
730 public static int _alpha = -1;
731
732 /**
733 * The last known mouse X coordinate
734 */
735 public static float MouseX;
736
737 /**
738 * The last known mouse Y coordinate. Relative to the top of the
739 * application.
740 */
741 public static float MouseY;
742
743 // Distance of mouse cursor from the origin of the item that was picked up
744 // The are used in the move method to calculate the distance moved by the
745 // cursor
746 private static int _offX;
747
748 private static int _offY;
749
750 // Keeps track of mouse button events when a delete occurs
751 private static boolean _isDelete = false;
752
753 // Keeps track of mouse button events when the user extracts attributes
754 // occurs
755 private static boolean _isAttribute = false;
756
757 /**
758 * A flag to indicate that the last mouseUp event was part of a two button
759 * click sequence hence the next mouse up should be ignored*
760 */
761 private static boolean _wasDouble = false;
762
763 private static boolean _isNoOp = false;
764
765 private static boolean _extrude = false;
766
767 // keeps track of the last highlighted Item
768 private static Item _lastHighlightedItem = null;
769
770 // keeps track of the item being 'ranged out' if there is one.
771 private static Text _lastRanged = null;
772
773 // keeps track of the picture being cropped if there is one
774 private static Picture _lastCropped = null;
775
776 // true if lastItem only has highlighting removed when a new item is
777 // highlighted
778 private static boolean _lastHoldsHighlight = false;
779
780 private static boolean _forceArrowCursor = true;
781
782 // the current context of the cursor
783 private static int _context = 0;
784
785 public static void setForceArrow(boolean val) {
786 _forceArrowCursor = val;
787 }
788
789 public static int getContext() {
790 return _context;
791 }
792
793 static int _mouseDown = 0;
794
795 private static MouseEvent _lastMouseClick = null;
796
797 private static Item _lastClickedOn = null;
798
799 private static Collection<Item> _lastClickedIn = null;
800
801 private static boolean _pulseOn = false;
802
803 private static final int PULSE_AMOUNT = 2;
804
805 private static Timer _MouseTimer = new Timer(LITTLE_MOUSE_PAUSE,
806 new ActionListener() {
807 public void actionPerformed(ActionEvent ae) {
808 // check if we are in free space
809 if (_lastClickedOn == null
810 && FreeItems.getInstance().size() == 0) {
811 // System.out.println("SuperBack!");
812 _MouseTimer.setDelay(ZERO_MOUSE_PAUSE);
813 back();
814 } else {
815 if (FrameUtils.getCurrentItem() == null) {
816 // Check if we are toggling arrowhead
817 if (FreeItems.getInstance().size() <= 2) {
818 for (Item i : FreeItems.getInstance()) {
819 if (i instanceof Line) {
820 ((Line) i).toggleArrow();
821 }
822 }
823 FrameGraphics.Repaint();
824 }
825 }
826 _MouseTimer.stop();
827 }
828 }
829 });
830
831 private static void setPulse(boolean pulseOn) {
832 if (_pulseOn == pulseOn) {
833 return;
834 }
835 int amount = PULSE_AMOUNT;
836 if (!pulseOn) {
837 amount *= -1;
838 }
839 _pulseOn = pulseOn;
840
841
842 if (_lastClickedOn != null) {
843 for (Item i : _lastClickedOn.getAllConnected()) {
844 if (i instanceof Line) {
845 Line line = (Line) i;
846 line.setThickness(line.getThickness() + amount);
847 }
848 }
849 }
850 FrameGraphics.Repaint();
851 }
852
853 private static Timer _ExtrudeMouseTimer = new Timer(BIG_MOUSE_PAUSE,
854 new ActionListener() {
855 public void actionPerformed(ActionEvent ae) {
856 setPulse(true);
857 _extrude = true;
858 _ExtrudeMouseTimer.stop();
859 }
860 });
861
862 public void mouseClicked(MouseEvent e) {
863 }
864
865 /**
866 * Each Item on the Frame is checked to determine if the mouse x,y
867 * coordinates are on the Item (or within the Shape surrounding it). If the
868 * coordinates are on the Item then the Item is checked for a link, if it
869 * has a link the link is followed, if not, nothing is done.
870 */
871 public void mousePressed(MouseEvent e) {
872 ProccessMousePressedEvent(e, e.getModifiersEx());
873 }
874
875 private void ProccessMousePressedEvent(MouseEvent e, int modifiersEx) {
876 // System.out.println("MousePressed " + e.getX() + "," + e.getY() + " "
877 // + e.getWhen());
878
879 // TODO WHY DID I NOT COMMENT THIS LINE!! MIKE SAYS
880 if (LastRobotX != null) {
881 _RobotTimer.stop();
882 LastRobotX = null;
883 LastRobotY = null;
884 mouseMoved(e);
885 }
886
887 if(ExperimentalFeatures.MousePan.get()) {
888 // don't pan if we're not over the frame
889 _overFrame = FrameUtils.getCurrentItem() == null;
890 _isPanOp = false;
891 // update panning position values so position doesn't jump
892 panStartX = e.getX();
893 panStartY = e.getY();
894 MouseX = panStartX;
895 MouseY = panStartY;
896 }
897
898 // System.out.println(modifiersEx);
899 if (_mouseDown == 0)
900 _lastMouseClickDate = new Date();
901
902 int buttonPressed = e.getButton();
903 _mouseDown += buttonPressed;
904 //when border of bin is clicked we can pick up the widget has been clicked
905 _lastClickedOn = FrameUtils.getCurrentItem();
906 // load any frame if necessary
907 Item on = _lastClickedOn;
908
909 _lastClickedIn = FrameUtils.getCurrentItems(on);
910 // if (_lastClickedIn != null){
911 // System.out.println(_lastClickedIn.size());}
912
913 /*
914 * This makes it so clicking repeatedly on the frameName doesn't add the
915 * frames to the backup stack. Only the first frame is added to the
916 * backup stack.
917 */
918 if (on == null || buttonPressed != MouseEvent.BUTTON1
919 || !on.isFrameName()) {
920 Navigation.ResetLastAddToBack();
921 }
922
923 SessionStats.MouseClicked(e.getButton());
924 if (buttonPressed == MouseEvent.BUTTON1) {
925 SessionStats.AddFrameEvent("Ld");
926 _extrude = false;
927 } else if (buttonPressed == MouseEvent.BUTTON2) {
928 SessionStats.AddFrameEvent("Md");
929 _extrude = false;
930 } else if (buttonPressed == MouseEvent.BUTTON3) {
931 SessionStats.AddFrameEvent("Rd");
932
933 // Check if the user picked up a paint brush
934 if (FreeItems.getInstance().size() == 1
935 && FreeItems.getItemAttachedToCursor().isAutoStamp()) {
936 int delay = (int) (FreeItems.getItemAttachedToCursor()
937 .getAutoStamp() * 1000);
938 if (delay < 10) {
939 _autoStamp = true;
940 } else {
941 _autoStampTimer.setDelay(delay);
942 _autoStampTimer.start();
943 }
944 }
945 }
946
947 // Mike says...
948 // For some reason the modifiers for e are different from modifiersEx
949 // The SwingUtilities.convertMouseEvent method changes the modifiers
950 _lastMouseClick = e;
951 _lastMouseClickModifiers = modifiersEx;
952
953 /*
954 * Only start the timer when in free space when the user double clicks
955 * to do super back TODO change this so that there are separate timers
956 * for super back and the other Long depress actions if that is what is
957 * wanted.
958 */
959 if (_lastClickedOn == null && FreeItems.getInstance().size() == 0) {
960 // System.out.println(e.getClickCount());
961 if (e.getClickCount() >= 2) {
962 _MouseTimer.start();
963 }
964 } else if (_lastClickedOn != null
965 && FreeItems.getInstance().size() == 0
966 && e.getButton() == MouseEvent.BUTTON3) {
967 _ExtrudeMouseTimer.start();
968
969 } else {
970 _MouseTimer.start();
971 }
972
973 // pre-cache the frame if it is linked
974
975 // If pre-caching is done, it must be done in the background
976
977 // if (on != null && on.getLink() != null && on.isLinkValid()) {
978 // FrameIO.Precache(on.getAbsoluteLink());
979 // }
980
981 // check for delete command
982 if (isDelete(modifiersEx)) {
983 _isDelete = true;
984 // _lastRanged = null;
985 _lastCropped = null;
986 _wasDouble = false;
987 // check for attributes command
988 } else if (isGetAttributes(modifiersEx)) {
989 _isAttribute = true;
990 _wasDouble = false;
991 } else if (isTwoClickNoOp(modifiersEx)) {
992 _isAttribute = false;
993 _wasDouble = false;
994 _isDelete = false;
995 _isNoOp = true;
996 } else
997 _isDelete = false;
998
999 // This must happen before the previous code
1000 // This is when the user is anchoring something
1001 if (buttonPressed != MouseEvent.BUTTON1
1002 && (_context == CONTEXT_FREESPACE || _context == CONTEXT_AT_ENCLOSURE)
1003 && FreeItems.itemsAttachedToCursor()) {
1004 FrameGraphics.changeHighlightMode(_lastHighlightedItem,
1005 Item.HighlightMode.None);
1006
1007 _lastHighlightedItem = FreeItems.getItemAttachedToCursor();
1008 for (Item i : FreeItems.getInstance()) {
1009 i.setHighlightColor(Item.DEPRESSED_HIGHLIGHT);
1010 }
1011 FrameGraphics.Repaint();
1012 // this is when the user is picking something up
1013 } else if (_lastHighlightedItem != null) {
1014 if (!(_lastHighlightedItem instanceof Line)) {
1015 _lastHighlightedItem
1016 .setHighlightColor(Item.DEPRESSED_HIGHLIGHT);
1017 } else {
1018 for (Item i : _lastHighlightedItem.getAllConnected()) {
1019 i.setHighlightColor(Item.DEPRESSED_HIGHLIGHT);
1020 }
1021 }
1022 FrameGraphics.Repaint();
1023 }
1024
1025 // if the user is ranging text
1026 if (on != null && on instanceof Text && !_isDelete) {
1027 _lastRanged = (Text) on;
1028 // set start-drag point
1029 _lastRanged.setSelectionStart(DisplayIO.getMouseX(),
1030 FrameMouseActions.getY());
1031 }
1032
1033 /*
1034 * Want to repaint the text with deleteRange color as soon as the second
1035 * button is pressed
1036 */
1037 if (_lastRanged != null) {
1038 _lastRanged.invalidateAll();
1039 FrameGraphics.requestRefresh(true);
1040 }
1041
1042 if (on != null && on instanceof Picture
1043 && e.getButton() == MouseEvent.BUTTON3 && !_isDelete) {
1044 _lastCropped = (Picture) on;
1045 // set start crop point
1046 _lastCropped.setStartCrop(DisplayIO.getMouseX(), FrameMouseActions
1047 .getY());
1048 _lastCropped.setShowCrop(true);
1049 }
1050 }
1051
1052 // This is where all the processing happens
1053 public void mouseReleased(MouseEvent e) {
1054
1055 // System.out.println("Released " + e.getX() + "," + e.getY() + " " +
1056 // e.getWhen());
1057 FrameUtils.ResponseTimer.restart();
1058 _autoStampTimer.stop();
1059 _autoStamp = false;
1060
1061 // Auto-hide popups when user clicks into expeditee world
1062 // If the user clicks into empty space and a popup-is showing, then
1063 // the user probably wants to click away the popup - therefore ignore
1064 // the event
1065 boolean shouldConsume = PopupManager.getInstance()
1066 .shouldConsumeBackClick();
1067 PopupManager.getInstance().hideAutohidePopups();
1068 if (shouldConsume && e.getButton() == MouseEvent.BUTTON1) {
1069 return; // consume back click event
1070 }
1071
1072 // _lastMovedDistance = new Point(e.getX() - _lastMouseClick.getX(), e
1073 // .getY()
1074 // - _lastMouseClick.getY());
1075
1076 _mouseDown -= e.getButton();
1077 updateCursor();
1078
1079 // System.out.println(e.getX() + ", " + e.getY());
1080
1081 Text lastRanged = _lastRanged;
1082 _lastRanged = null;
1083 // Dont do ranging if the user moves really quickly...
1084 // They are probably trying to pick something up in this case
1085 if (lastRanged != null) {
1086 long depressTime = (new Date()).getTime()
1087 - _lastMouseClickDate.getTime();
1088 // double changeInDistance =
1089 // e.getPoint().distance(_currentMouseClick.getPoint());
1090 // double speed = changeInDistance * 1000 / changeInTime;
1091
1092 // System.out.println(depressTime);
1093
1094 if (depressTime < MINIMUM_RANGE_DEPRESS_TIME
1095 || lastRanged.getSelectionSize() <= 0) {// Text.MINIMUM_RANGED_CHARS)
1096 // {
1097 lastRanged.clearSelection();
1098 lastRanged = null;
1099 }
1100 }
1101
1102 _ExtrudeMouseTimer.stop();
1103 _MouseTimer.stop();
1104
1105 setPulse(false);
1106
1107 // if the last action was a delete, then ignore the next mouseup
1108 if (_wasDouble) {
1109 _wasDouble = false;
1110 return;
1111 }
1112
1113 // This code must come after the _wasDouble code...
1114 // Otherwise get Stopping Agent method after doing the left+right format
1115 // shortcut
1116 if (Actions.isAgentRunning()) {
1117 Actions.stopAgent();
1118 return;
1119 }
1120
1121 /*
1122 * if (_isNoOp) { if (e.getButton() != MouseEvent.NOBUTTON) { _isNoOp =
1123 * false; _wasDouble = true; // lastRanged.clearSelection();
1124 * FrameGraphics.Repaint(); return; } }
1125 */
1126
1127 // get whatever the user was pointing at
1128 Item clickedOn = _lastClickedOn;
1129 Collection<Item> clickedIn = _lastClickedIn;
1130
1131 MouseX = e.getX();
1132 MouseY = e.getY();
1133
1134 Item releasedOn = FrameUtils.getCurrentItem();
1135 Collection<Item> releasedIn = FrameUtils.getCurrentItems(releasedOn);
1136
1137 // Only a no op if user releases in free space!
1138 if (_isPanOp || (_isNoOp && (releasedOn == null && releasedIn == null))) {
1139 if (_isDelete) {
1140 _isDelete = false;
1141 _wasDouble = true;
1142 }
1143
1144 _isNoOp = false;
1145
1146 if (_lastHighlightedItem != null)
1147 FrameGraphics.changeHighlightMode(_lastHighlightedItem,
1148 Item.HighlightMode.None);
1149
1150 if (FreeItems.itemsAttachedToCursor()) {
1151 move(FreeItems.getInstance());
1152 }
1153
1154 if (FreeItems.hasCursor()) {
1155 move(FreeItems.getCursor(), true);
1156 }
1157
1158 if(!_isPanOp) {
1159 MessageBay.displayMessage("Action cancelled, mouse moved more than "
1160 + UserSettings.NoOpThreshold.get() + " pixels.");
1161 }
1162 FrameGraphics.Repaint();
1163 return;
1164 } else {
1165 _isNoOp = false;
1166 }
1167
1168 // if this is a delete command
1169 if (_isDelete) {
1170 if (lastRanged != null) {
1171
1172 Item i = FreeItems.getItemAttachedToCursor();
1173 if (i != null && i instanceof Text) {
1174 lastRanged.replaceSelectedText(((Text) i).getText());
1175 FreeItems.getInstance().clear();
1176 } else
1177 lastRanged.cutSelectedText();
1178 lastRanged.clearSelection();
1179 FrameGraphics.Repaint();
1180
1181 } else {
1182 delete(clickedOn);
1183 }
1184 _wasDouble = true;
1185 _isDelete = false;
1186 return;
1187 }
1188
1189 // if this is an attribute extraction command
1190 if (_isAttribute) {
1191 if (clickedOn == null) {
1192 Frame current = DisplayIO.getCurrentFrame();
1193 if (isControlDown()) {
1194 Actions.PerformActionCatchErrors(current, null, "HFormat");
1195 }
1196 if (!isControlDown() || isShiftDown()) {
1197 Actions.PerformActionCatchErrors(current, null, "Format");
1198 }
1199 } else {
1200 extractAttributes(clickedOn);
1201 }
1202 // if the user dragged and displayed some cropping with left and
1203 // right button is a no op for now
1204 // but later could make this the shrinkTo context
1205 if (_lastCropped != null) {
1206 _lastCropped.clearCropping();
1207 _lastCropped = null;
1208 }
1209 _wasDouble = true;
1210 _isAttribute = false;
1211 return;
1212 }
1213
1214 // if the user is ranging-out text
1215 if (lastRanged != null && e.getButton() != MouseEvent.BUTTON1) {
1216
1217 Text ranged;
1218 if (isShiftDown()) {
1219 // If shift is down, copy everything (size, color, etc.) except actions, links and data
1220 ranged = lastRanged.copy();
1221 ranged.setActions(null);
1222 ranged.setData((List<String>) null);
1223 ranged.setLink(null);
1224 } else {
1225 // If shift isn't down, don't copy any attributes, but base the new text item on the appropriate template
1226 ranged = DisplayIO.getCurrentFrame().getItemTemplate(lastRanged.copySelectedText().charAt(0));
1227 }
1228
1229 // if the user is cutting text from the item
1230 if (e.getButton() == MouseEvent.BUTTON2) {
1231 // Check if the user is trying to range an item for which they
1232 // do not have permission to do so... or it is the frame name
1233 if (!lastRanged.hasPermission(UserAppliedPermission.full)
1234 || lastRanged.isFrameName()) {
1235 MessageBay
1236 .displayMessage("Insufficient permission to cut text");
1237 lastRanged.clearSelection();
1238 FrameGraphics.Repaint();
1239 return;
1240 }
1241 // if the entire text is selected and its not a line end then
1242 // pickup the item
1243 boolean entireText = lastRanged.getSelectionSize() == lastRanged
1244 .getLength();
1245 if (entireText && !lastRanged.isLineEnd()) {
1246 lastRanged.clearSelection();
1247 ranged.delete();
1248 middleButton(clickedOn, clickedIn, e.isShiftDown());
1249 return;
1250 } else {
1251 ranged.setText(lastRanged.cutSelectedText());
1252 ranged.setWidth(lastRanged.getWidth());
1253 // If its the whole text then replace last ranged with a dot
1254 if (entireText) {
1255 Item dot = FrameKeyboardActions.replaceText(lastRanged);
1256 dot.setHighlightMode(HighlightMode.None);
1257 }
1258 }
1259 // if the user is copying text from the item
1260 } else if (e.getButton() == MouseEvent.BUTTON3) {
1261 // Check if the user is trying to range an item for which they
1262 // do not have permission to do so... or it is the frame name
1263 if (!lastRanged.hasPermission(UserAppliedPermission.copy)) {
1264 MessageBay
1265 .displayMessage("Insufficient permission to copy text");
1266 lastRanged.clearSelection();
1267 FrameGraphics.Repaint();
1268 return;
1269 }
1270 ranged.setText(lastRanged.copySelectedText());
1271 }
1272
1273 ranged.setParent(null);
1274 ranged.setPosition(DisplayIO.getMouseX(), FrameMouseActions.getY());
1275 pickup(ranged);
1276 lastRanged.clearSelection();
1277 lastRanged.setHighlightMode(HighlightMode.None);
1278 refreshHighlights();
1279 FrameGraphics.refresh(false);
1280 return;
1281 }
1282
1283 // if the user is cropping an image
1284 if (clickedOn != null && clickedOn == _lastCropped) {
1285 if (_lastCropped.isCropTooSmall()) {
1286 _lastCropped = null;
1287 // FrameGraphics
1288 // .WarningMessage("Crop cancelled because it was below the
1289 // minimum size");
1290 } else {
1291 Picture cropped = _lastCropped.copy();
1292 cropped.setParent(null);
1293 // move the cropped image to the cursor
1294 int width = cropped.getWidth();
1295 int height = cropped.getHeight();
1296 if(cropped.getSource().getX() + width < MouseX) {
1297 cropped.getSource().setX(MouseX - width);
1298 }
1299 if(cropped.getSource().getY() + height < MouseY) {
1300 cropped.getSource().setY(MouseY - height);
1301 }
1302 pickup(cropped);
1303 // MIKE put the code below up here
1304 _lastCropped.clearCropping();
1305 FrameGraphics.changeHighlightMode(_lastCropped,
1306 HighlightMode.None);
1307 _lastCropped = null;
1308 FrameGraphics.Repaint();
1309 return;
1310 }
1311 }
1312
1313 assert (_lastCropped == null);
1314 // if the user has cropped an image, either the above happend or this is
1315 // a no-op MIKE says WHEN DO WE NEED THE CODE BELOW
1316 // if (_lastCropped != null && !_lastCropped.isCropTooSmall()) {
1317 // _lastCropped.clearCropping();
1318 // _lastCropped = null;
1319 // FrameGraphics.Repaint();
1320 // return;
1321 // }
1322
1323 // if the user is left-clicking
1324 if (e.getButton() == MouseEvent.BUTTON1) {
1325 SessionStats.AddFrameEvent("Lu");
1326 leftButton(clickedOn, clickedIn, e.isShiftDown(), e.isControlDown());
1327 return;
1328 }
1329
1330 if (e.getButton() == MouseEvent.BUTTON2) {
1331 SessionStats.AddFrameEvent("Mu");
1332 middleButton(clickedOn, clickedIn, e.isShiftDown());
1333 return;
1334 }
1335
1336 if (e.getButton() == MouseEvent.BUTTON3) {
1337 SessionStats.AddFrameEvent("Ru");
1338 rightButton(clickedOn, clickedIn);
1339 return;
1340 }
1341
1342 // error, we should have returned by now
1343 System.out.println("Error: mouseReleased should have returned by now. "
1344 + e);
1345 }
1346
1347 /**
1348 * This method handles all left-click actions
1349 */
1350 private void leftButton(Item clicked, Collection<Item> clickedIn,
1351 boolean isShiftDown, boolean isControlDown) {
1352
1353 //Gets the current frame
1354 Frame f = DisplayIO.getCurrentFrame();
1355
1356 //Checks if the current frame is an overlay
1357 if(f.getOverlays() != null && FrameUtils.getCurrentItem() != null){
1358 Item i = FrameUtils.getCurrentItem();
1359
1360 //Checks if the item clicked in the overlay is a Rubbish Bin. If it is, delete the item attached to the cursor and return.
1361 if(i instanceof WidgetCorner){
1362
1363 try{
1364 WidgetCorner wc = (WidgetCorner)i;
1365 ButtonWidget bw = (ButtonWidget) wc.getWidgetSource();
1366
1367 //Should call a button widgets 'itemheldwhileclicked' method, and process depending on the widget type - else will return false.
1368 if(bw.itemHeldWhileClicked((InteractiveWidget)bw) == true){
1369
1370 return;
1371 }
1372 }
1373 catch (Exception e){
1374
1375 e.printStackTrace();
1376 }
1377 }
1378
1379 Item on = _lastClickedOn;
1380 _lastClickedIn = FrameUtils.getCurrentItems(on);
1381 }
1382
1383
1384 // if the user is pointing at something then either follow the link or
1385 // do TDFC
1386 if (clicked == null) {
1387 // Check if the user is nearby another item...
1388 int mouseX = DisplayIO.getMouseX();
1389 int mouseY = FrameMouseActions.getY();
1390 // System.out.println(mouseX + "," + mouseY);
1391 for (Item i : DisplayIO.getCurrentFrame().getItems()) {
1392 //System.out.println(i.getName().toString());
1393 if (i instanceof Text) {
1394 if (i.isNear(mouseX, mouseY)) {
1395 clicked = i;
1396 break;
1397 }
1398 }
1399 }
1400 }
1401
1402 if (clicked instanceof Text) {
1403 Text text = (Text) clicked;
1404 /* Don't follow link when just highlighting text with the left button */
1405 if (text.getText().length() == 0)
1406 clicked = null;
1407 else if (text.getSelectionSize() > 0) {
1408 return;
1409 }
1410 }
1411
1412 // If the user clicked into a widgets free space...
1413 if (clicked == null && _lastClickedIn != null
1414 && _lastClickedIn.size() >= 4) {
1415
1416 // Check to see if the user clicked into a widgets empty space
1417 InteractiveWidget iw = null;
1418
1419 for (Item i : _lastClickedIn) {
1420
1421 if (i instanceof WidgetCorner) {
1422 iw = ((WidgetCorner) i).getWidgetSource();
1423 break;
1424 } else if (i instanceof WidgetEdge) {
1425 iw = ((WidgetEdge) i).getWidgetSource();
1426 break;
1427 }
1428 }
1429
1430 if (iw != null) {
1431
1432 // Handle dropping items on widgets
1433 if(iw.ItemsLeftClickDropped()) {
1434 return;
1435 }
1436
1437 // Note: mustn't directly use source for handling the link
1438 // because all link operations will by-pass the widgets special
1439 // handling with links...
1440 Item widgetLink = iw.getItems().get(0);
1441 assert (widgetLink != null);
1442 clicked = widgetLink;
1443 } else {
1444 for (Item i : _lastClickedIn) {
1445 /*
1446 * Find the first linked item or the first unlinked Dot This
1447 * code assumes that items are are ordered from top to
1448 * bottom. TODO make sure the list will always be ordered
1449 * correctly!!
1450 */
1451 if (i.hasLink() || i instanceof Dot) {
1452 clicked = i;
1453 break;
1454 }
1455 }
1456 }
1457
1458 }
1459
1460
1461 if (clicked instanceof Picture) {
1462 int mouseX = DisplayIO.getMouseX();
1463 int mouseY = FrameMouseActions.getY();
1464 Picture clickedOnPicture = (Picture)clicked;
1465 Frame current_frame = DisplayIO.getCurrentFrame();
1466 Color bg_col = current_frame.getBackgroundColor();
1467 if (clickedOnPicture.MouseOverBackgroundPixel(mouseX,mouseY,bg_col)) {
1468 // Make 'clicked' null, effectively causing a back() operation
1469 clicked = null;
1470 }
1471 }
1472
1473 if (clicked != null) {
1474 // check item permissions
1475 boolean hasLinkOrAction = clicked.hasLink() || clicked.hasAction();
1476
1477 if ((hasLinkOrAction && !clicked
1478 .hasPermission(UserAppliedPermission.followLinks))
1479 || (!hasLinkOrAction && !clicked
1480 .hasPermission(UserAppliedPermission.createFrames))) {
1481 Item editTarget = clicked.getEditTarget();
1482 if (editTarget != clicked) {
1483 if (editTarget.hasPermission(UserAppliedPermission.followLinks)) {
1484 clicked = editTarget;
1485 } else {
1486 MessageBay
1487 .displayMessage("Insufficient permission to perform action on item");
1488 return;
1489 }
1490 }
1491 }
1492
1493 Item clickedOn = clicked;
1494
1495 // actions take priority
1496 if (_lastMouseClick != null && !_lastMouseClick.isControlDown()
1497 && clickedOn.hasAction()) {
1498 IndirectMouseActions.getInstance().getExecuteActionAction().exec(new MouseInfo(clicked, clickedIn, isShiftDown, isControlDown));
1499 } else if (clickedOn.getLink() != null) {
1500 IndirectMouseActions.getInstance().getFollowLinkAction().exec(new MouseInfo(clicked, clickedIn, isShiftDown, isControlDown));
1501 // no link is found, perform TDFC
1502 } else {
1503 /*
1504 * if the user is clicking on the frame name then move to the
1505 * next or previous frame regardless of whether or not the frame
1506 * is protected
1507 */
1508 if (clickedOn.isFrameName()) {
1509 IndirectMouseActions.getInstance().getRespondToFrameNameClickAction().exec(new MouseInfo(clicked, clickedIn, isShiftDown, isControlDown));
1510 }
1511
1512 IndirectMouseActions.getInstance().getTDFCAction().exec(new MouseInfo(clicked, clickedIn, isShiftDown, isControlDown));
1513 }
1514
1515 } else {
1516
1517 IndirectMouseActions.getInstance().getBackAction().exec(new MouseInfo(clicked, clickedIn, isShiftDown, isControlDown));
1518 }
1519 }
1520
1521 private static boolean doMerging(Item clicked) {
1522 if (clicked == null)
1523 return false;
1524
1525 // // Brook: widgets do not merge
1526 // if (clicked instanceof WidgetCorner)
1527 // return false;
1528 //
1529 // // Brook: widgets do not merge
1530 // if (clicked instanceof WidgetEdge)
1531 // return false;
1532
1533 // System.out.println(FreeItems.getInstance().size());
1534 if (isRubberBandingCorner()) {
1535 if (clicked.isLineEnd()
1536 || clicked.getAllConnected().contains(
1537 FreeItems.getItemAttachedToCursor())) {
1538 return true;
1539 }
1540 }
1541
1542 if (FreeItems.getInstance().size() > 2)
1543 return false;
1544
1545 Item attachedToCursor = FreeItems.getItemAttachedToCursor();
1546
1547 if (clicked instanceof Text
1548 && !(attachedToCursor instanceof Text || attachedToCursor
1549 .isLineEnd())) {
1550 return false;
1551 }
1552
1553 if (clicked instanceof Picture) {
1554 int mouseX = DisplayIO.getMouseX();
1555 int mouseY = FrameMouseActions.getY();
1556 Picture clickedOnPicture = (Picture)clicked;
1557 Frame current_frame = DisplayIO.getCurrentFrame();
1558 Color bg_col = current_frame.getBackgroundColor();
1559 if (clickedOnPicture.MouseOverBackgroundPixel(mouseX,mouseY,bg_col)) {
1560 // Make 'clicked' null, effectively causing a back() operation
1561 return false;
1562 }
1563 }
1564
1565 return true;
1566 }
1567
1568 public static void middleButton() {
1569 Item currentItem = FrameUtils.getCurrentItem();
1570 getInstance().middleButton(currentItem,
1571 FrameUtils.getCurrentItems(currentItem), false);
1572 updateCursor();
1573 }
1574
1575 public static void rightButton() {
1576 Item currentItem = FrameUtils.getCurrentItem();
1577 getInstance().rightButton(currentItem,
1578 FrameUtils.getCurrentItems(currentItem));
1579 updateCursor();
1580 }
1581
1582 public static void leftButton() {
1583 Item currentItem = FrameUtils.getCurrentItem();
1584 getInstance().leftButton(currentItem,
1585 FrameUtils.getCurrentItems(currentItem), false, false);
1586 updateCursor();
1587 }
1588
1589 /**
1590 * This method handles all middle-click actions
1591 */
1592 private void middleButton(Item clicked, Collection<Item> clickedIn,
1593 boolean isShiftDown) {
1594
1595 // If the user clicked into a widgets free space...
1596 if (clicked == null && _lastClickedIn != null
1597 && _lastClickedIn.size() >= 4) {
1598
1599 // Check to see if the use clicked into a widgets empty space
1600 InteractiveWidget iw = null;
1601
1602 for (Item i : _lastClickedIn) {
1603
1604 if (i instanceof WidgetCorner) {
1605 iw = ((WidgetCorner) i).getWidgetSource();
1606 break;
1607 } else if (i instanceof WidgetEdge) {
1608 iw = ((WidgetEdge) i).getWidgetSource();
1609 break;
1610 }
1611 }
1612
1613 if (iw != null) {
1614
1615 // Handle dropping items on widgets
1616 if(iw.ItemsMiddleClickDropped()) {
1617 return;
1618 }
1619 }
1620 }
1621 // if the cursor has Items attached
1622 if (FreeItems.itemsAttachedToCursor()) {
1623 // if the user is pointing at something, merge the items (if
1624 // possible)
1625 if (doMerging(clicked)) {
1626 IndirectMouseActions.getInstance().getDeleteItemsAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1627 } else {
1628 IndirectMouseActions.getInstance().getAnchorFreeItemsAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1629 }
1630 // otherwise if the user is pointing at something, pick it up unless shift is down
1631 } else {
1632 if (clicked instanceof Picture) {
1633 int mouseX = DisplayIO.getMouseX();
1634 int mouseY = FrameMouseActions.getY();
1635 Picture clickedOnPicture = (Picture)clicked;
1636 Frame current_frame = DisplayIO.getCurrentFrame();
1637 Color bg_col = current_frame.getBackgroundColor();
1638 if (clickedOnPicture.MouseOverBackgroundPixel(mouseX,mouseY,bg_col)) {
1639 clicked = null; // Effectively make it as if they haven't clicked on anything
1640 }
1641 }
1642 if (clicked != null && !isShiftDown) {
1643
1644 // check permissions
1645 if (!clicked.hasPermission(UserAppliedPermission.full)) {
1646 Item editTarget = clicked.getEditTarget();
1647 if (editTarget != clicked
1648 && editTarget.hasPermission(UserAppliedPermission.full)) {
1649 clicked = editTarget;
1650 } else {
1651 MessageBay
1652 .displayMessage("Insufficient permission to pick up item");
1653 return;
1654 }
1655 }
1656 IndirectMouseActions.getInstance().getDetachLineAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1657 // if we're inside a shape, pick it up unless shift is down
1658 } else if (clickedIn != null && !isShiftDown) {
1659 IndirectMouseActions.getInstance().getGroupPickupAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1660 } else {
1661 IndirectMouseActions.getInstance().getNewLineAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1662 }
1663 }
1664 SessionStats.MovedItems(FreeItems.getInstance());
1665 }
1666
1667 private static Item getFirstFreeLineEnd() {
1668 for (Item i : FreeItems.getInstance())
1669 if (i.isLineEnd())
1670 return i;
1671 return null;
1672 }
1673
1674 private static boolean isRubberBandingCorner() {
1675 return getShapeCorner(FreeItems.getInstance()) != null;
1676 }
1677
1678 /**
1679 * Gets the rectangle corner from the list of items that are part of a
1680 * rectangle.
1681 *
1682 * @param partialRectangle
1683 * a corner and its two connecting lines.
1684 * @return the rectangle corner or null if the list of items is not part of
1685 * a rectangle.
1686 */
1687 private static Item getShapeCorner(List<Item> partialRectangle) {
1688 if (partialRectangle.size() < 3)
1689 return null;
1690 Item lineEnd = null;
1691 // only one lineEnd will be present for rectangles
1692 // All other items must be lines
1693 for (Item i : partialRectangle) {
1694 if (i.isLineEnd()) {
1695 if (lineEnd == null) {
1696 lineEnd = i;
1697 } else {
1698 return null;
1699 }
1700 } else if (!(i instanceof Line)) {
1701 return null;
1702 }
1703 }
1704 // if this is at least the corner of two connected lines
1705 if (lineEnd != null && lineEnd.getAllConnected().size() >= 5)
1706 return lineEnd;
1707
1708 return null;
1709 }
1710
1711 /**
1712 * This method handles all right-click action
1713 */
1714 private void rightButton(Item clicked, Collection<Item> clickedIn) {
1715
1716 // If the user clicked into a widgets free space...
1717 if (clicked == null && _lastClickedIn != null
1718 && _lastClickedIn.size() >= 4) {
1719
1720 // Check to see if the use clicked into a widgets empty space
1721 InteractiveWidget iw = null;
1722
1723 for (Item i : _lastClickedIn) {
1724
1725 if (i instanceof WidgetCorner) {
1726 iw = ((WidgetCorner) i).getWidgetSource();
1727 break;
1728 } else if (i instanceof WidgetEdge) {
1729 iw = ((WidgetEdge) i).getWidgetSource();
1730 break;
1731 }
1732 }
1733
1734 if (iw != null) {
1735
1736 // Handle dropping items on widgets
1737 if(iw.ItemsRightClickDropped()) {
1738 return;
1739 }
1740 }
1741 }
1742
1743 // if the cursor has Items attached, then anchor a copy of them
1744
1745 List<Item> copies = null;
1746 if (FreeItems.itemsAttachedToCursor()) {
1747 if (FreeItems.getInstance().size() == 1
1748 && FreeItems.getItemAttachedToCursor().isAutoStamp()) {
1749 // Dont stamp if the user is painting... because we dont want to
1750 // save any of the items created!
1751 return;
1752 // if the user is clicking on something, merge the items
1753 // unless it is a point onto something other than a lineEnd or a
1754 // dot
1755 } else if (clicked != null
1756 // TODO Change the items merge methods so the logic is simplified
1757 && (!(FreeItems.getItemAttachedToCursor() instanceof Dot)
1758 || clicked instanceof Dot || clicked.isLineEnd())) {
1759 // check permissions
1760 if (!clicked.hasPermission(UserAppliedPermission.full)
1761 && clicked.getParent().getNameItem() != clicked) {
1762 MessageBay
1763 .displayMessage("Insufficient permission to merge items");
1764 return;
1765 }
1766 if (clicked instanceof Text || clicked instanceof Dot
1767 || clicked instanceof XRayable) {
1768 if (isRubberBandingCorner()) {
1769 copies = IndirectMouseActions.getInstance().getMergeGroupAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1770 // line onto something
1771 } else if (FreeItems.getInstance().size() == 2
1772 /* && clicked instanceof XRayable */) {
1773 copies = IndirectMouseActions.getInstance().getMergeTwoItemsAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1774 } else if (FreeItems.getInstance().size() == 1) {
1775 copies = IndirectMouseActions.getInstance().getMergeSingleItemAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1776 } else {
1777 copies = IndirectMouseActions.getInstance().getStampAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1778 }
1779 } else {
1780 copies = ItemUtils.UnreelLine(FreeItems.getInstance(),
1781 _controlDown);
1782 if (copies == null)
1783 copies = copy(FreeItems.getInstance());
1784 for (Item i : copies) {
1785 i.setOffset(0, 0);
1786 }
1787 anchor(FreeItems.getInstance());
1788 FreeItems.getInstance().clear();
1789 pickup(copies);
1790 }
1791 // otherwise, anchor the items
1792 } else {
1793 // check if this is anchoring a rectangle
1794 if (isRubberBandingCorner()) {
1795 copies = IndirectMouseActions.getInstance().getRubberBandingCornerAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1796 } else {
1797 if (rubberBanding()) {
1798 copies = IndirectMouseActions.getInstance().getRubberBandingCopyAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1799 } else if (_extrude) {
1800 copies = IndirectMouseActions.getInstance().getExtrudeAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1801 } else {
1802 stampItemsOnCursor(true);
1803 copies = FreeItems.getInstance();
1804 }
1805 }
1806 }
1807 } else {
1808
1809 if (clicked instanceof Picture) {
1810 int mouseX = DisplayIO.getMouseX();
1811 int mouseY = FrameMouseActions.getY();
1812 Picture clickedOnPicture = (Picture)clicked;
1813 Frame current_frame = DisplayIO.getCurrentFrame();
1814 Color bg_col = current_frame.getBackgroundColor();
1815 if (clickedOnPicture.MouseOverBackgroundPixel(mouseX,mouseY,bg_col)) {
1816 clicked = null; // Effectively make it as if they haven't clicked on anything
1817 }
1818 }
1819
1820 // if the user is pointing at something and shift isn't down, make a copy
1821 if (clicked != null && !isShiftDown()) {
1822 // check permissions
1823 if (clicked.isLineEnd()) {
1824 if (!clicked.hasPermission(UserAppliedPermission.full)) {
1825 MessageBay
1826 .displayMessage("Insufficient permission to unreel");
1827 return;
1828 }
1829 } else if (!clicked.hasPermission(UserAppliedPermission.copy)) {
1830 Item editTarget = clicked.getEditTarget();
1831 if (editTarget != clicked
1832 && editTarget.hasPermission(UserAppliedPermission.copy)) {
1833 clicked = editTarget;
1834 } else {
1835 MessageBay
1836 .displayMessage("Insufficient permission to copy");
1837 return;
1838 }
1839 }
1840
1841 copies = IndirectMouseActions.getInstance().getMakeCopyAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1842 } else {
1843 // if user is pointing in a closed shape and shift isn't down, make a copy of the items inside
1844 if (clickedIn != null && !isShiftDown()) {
1845 IndirectMouseActions.getInstance().getMakeGroupCopyAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1846 // otherwise, create a rectangle
1847 } else {
1848 Item on = FrameUtils.onItem(DisplayIO.getCurrentFrame(),
1849 MouseX, MouseY, true);
1850 // if its on a line then create a line from that line
1851 if (on instanceof Line && on.hasPermission(UserAppliedPermission.full)) {
1852 IndirectMouseActions.getInstance().getExtendLineAction().exec(new MouseInfo(on, clickedIn, false, false));
1853 }
1854 copies = IndirectMouseActions.getInstance().getCreateRectangleAction().exec(new MouseInfo(clicked, clickedIn, false, false));
1855 }
1856 }
1857 }
1858 getInstance().refreshHighlights();
1859 SessionStats.CopiedItems(copies);
1860 updateCursor();
1861 FrameGraphics.Repaint();
1862 }
1863
1864 /**
1865 *
1866 */
1867 private static void stampItemsOnCursor(boolean save) {
1868 List<Item> copies = copy(FreeItems.getInstance());
1869 // MIKE: what does the below 2 lines do?
1870 for (Item i : copies) {
1871 i.setOffset(0, 0);
1872 i.setSave(save);
1873 }
1874 // The below code has a little problem withflicker when stamp
1875 // and dragging
1876 move(FreeItems.getInstance());
1877 for (Item i : copies) {
1878 i.setHighlightMode(HighlightMode.None);
1879 }
1880 anchor(copies);
1881 }
1882
1883 /**
1884 * @param enclosedItems
1885 * @return
1886 */
1887 private static Collection<Item> getFullyEnclosedItems(
1888 Collection<Item> enclosure) {
1889 // copy the enclosedItems because the list will be modified
1890 Collection<Item> enclosedItems = new LinkedHashSet<Item>(enclosure);
1891 Collection<Item> toCopy = new LinkedHashSet<Item>(enclosedItems.size());
1892
1893 while (enclosedItems.size() > 0) {
1894 Item i = enclosedItems.iterator().next();
1895 if (i.hasPermission(UserAppliedPermission.copy)) {
1896 Collection<Item> items = i.getAllConnected();
1897 // Only copy if the entire shape is enclosed
1898 if (enclosedItems.containsAll(items)) {
1899 toCopy.addAll(items);
1900 }
1901 enclosedItems.removeAll(items);
1902 } else {
1903 enclosedItems.remove(i);
1904 }
1905 }
1906 return toCopy;
1907 }
1908
1909 /**
1910 * Marks the items as not belonging to any specific frame. When picking up
1911 * items the parent will be automatically cleared for items on the current
1912 * frame but not for overlay items. This method ensures that overlay items
1913 * will also be cleared. This is useful when picking up copies of items from
1914 * an overlay (with the right mouse button) to ensure that the copy will be
1915 * anchored on the current frame rather than the overlay. When items are
1916 * picked up with the middle button clearParent should NOT be called.
1917 *
1918 * @param items
1919 * to have their parent cleared
1920 */
1921 private static void clearParent(List<Item> items) {
1922 for (Item i : items) {
1923 // The next line is only necessary for circles...
1924 // Need to clean up/refactory some of this stuff
1925 i.getParentOrCurrentFrame().removeItem(i);
1926 i.setParent(null);
1927 }
1928 }
1929
1930 private static boolean inWindow = false;
1931 /**
1932 * event called when mouse exits window
1933 * (can't use MouseListener callback since that callback doesn't
1934 * correctly receive all mouse exit events)
1935 */
1936 public static void mouseExitedWindow(MouseEvent e) {
1937 inWindow = false;
1938 // System.out.println("Left window");
1939 if(FreeItems.itemsAttachedToCursor()) {
1940 boolean cut = true;
1941 for(Item i : FreeItems.getInstance()) {
1942 for(Item j : i.getAllConnected()) {
1943 if(!FreeItems.getInstance().contains(j)) {
1944 cut = false;
1945 break;
1946 }
1947 }
1948 }
1949 if(cut) {
1950 ItemSelection.cut();
1951 }
1952 }
1953 }
1954
1955 public void mouseEntered(MouseEvent e) {
1956 // check if we are entering the window from outside of the window, or if we were just over a widget
1957 if(!inWindow) {
1958 inWindow = true;
1959 // if there is expeditee data on the clipboard that has not yet been autoPasted, autoPaste it
1960 Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
1961 Transferable content = c.getContents(null);
1962 try {
1963 if(content.isDataFlavorSupported(ItemSelection.expDataFlavor)) { // Expeditee data
1964 ExpDataHandler expData = (ExpDataHandler)content.getTransferData(ItemSelection.expDataFlavor);
1965 if(expData.autoPaste) {
1966 List<Item> items = new ExpClipReader(FrameMouseActions.getX(), FrameMouseActions.getY()).read(expData.items);
1967 // generate new IDs and pickup
1968 FrameMouseActions.pickup(ItemUtils.CopyItems(items));
1969 // update the clipboard contents so they won't be autoPasted again
1970 expData.autoPaste = false;
1971 String stringData = "";
1972 Image imageData = null;
1973 if(content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
1974 stringData = (String) content.getTransferData(DataFlavor.stringFlavor);
1975 }
1976 if(content.isDataFlavorSupported(DataFlavor.imageFlavor)) {
1977 imageData = (Image) content.getTransferData(DataFlavor.imageFlavor);
1978 }
1979 c.setContents(new ItemSelection(stringData, imageData, expData), null);
1980 }
1981 }
1982 } catch(Exception ex) {
1983 ex.printStackTrace();
1984 }
1985 }
1986 }
1987
1988 public void mouseExited(MouseEvent e) {
1989 }
1990
1991 private boolean _overFrame;
1992 private int panStartX, panStartY;
1993 private boolean _isPanOp;
1994 public void mouseDragged(MouseEvent e) {
1995 _lastMouseDragged = e;
1996 // System.out.println("MouseDragged");
1997
1998 // Stop the longDepress mouse timer if the user drags above a threshold
1999 if (_MouseTimer.isRunning()) {
2000 if (Math.abs(e.getX() - _lastMouseClick.getX())
2001 + Math.abs(e.getY() - _lastMouseClick.getY()) > 10)
2002 _MouseTimer.stop();
2003 }
2004
2005 if (_autoStamp) {
2006 stampItemsOnCursor(false);
2007 }
2008
2009 /*
2010 * Have the free items follow the cursor if the user clicks in freespace
2011 * then moves.
2012 */
2013 if (FreeItems.getInstance().size() > 0 && _lastClickedOn == null) {
2014 mouseMoved(e);
2015 return;
2016 }
2017
2018 // panning the frame when dragging the mouse while shift-leftclicking
2019 if(ExperimentalFeatures.MousePan.get() && _overFrame && e.isShiftDown() &&
2020 (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0 &&
2021 (_isPanOp || (Math.max(Math.abs(panStartX - e.getX()), Math.abs(panStartY - e.getY())) > 5))) {
2022 int dX = (int) (e.getX() - MouseX);
2023 int dY = (int) (e.getY() - MouseY);
2024 Misc.pan(DisplayIO.getCurrentFrame(), dX, dY);
2025 MouseX = e.getX();
2026 MouseY = e.getY();
2027 _isPanOp = true;
2028 }
2029
2030 // check if user is dragging across a text item
2031 if (_lastRanged != null) {
2032 // System.out.println(MouseY - e.getY());
2033
2034 MouseX = e.getX();
2035 MouseY = e.getY();
2036
2037 int distance = _lastRanged.getY() - FrameMouseActions.getY();
2038 if (distance <= 0)
2039 distance = FrameMouseActions.getY() - _lastRanged.getY()
2040 - _lastRanged.getBoundsHeight();
2041
2042 if (distance > UserSettings.NoOpThreshold.get()) {
2043 _lastRanged.clearSelectionEnd();
2044 _isNoOp = true;
2045 } else {
2046 // update the ranged section
2047 _lastRanged.setSelectionEnd(DisplayIO.getMouseX(),
2048 FrameMouseActions.getY());
2049 _isNoOp = false;
2050 }
2051
2052 DisplayIO.setTextCursor(_lastRanged, Text.NONE, false, e
2053 .isShiftDown(), e.isControlDown(), false);
2054 FrameGraphics.Repaint();
2055 return;
2056 }
2057
2058 // if the user is dragging across a picture
2059 if (_lastCropped != null) {
2060 // If shift is down then the distance moved is the same in the x and
2061 // y
2062 MouseX = e.getX();
2063 MouseY = e.getY();
2064
2065 if (e.isControlDown()) {
2066 int deltaX = Math.abs(e.getX() - _lastMouseClick.getX());
2067 int deltaY = Math.abs(e.getY() - _lastMouseClick.getY());
2068 if (deltaX > deltaY) {
2069 MouseY = _lastMouseClick.getY() + deltaX
2070 * (e.getY() > _lastMouseClick.getY() ? 1 : -1);
2071 } else {
2072 MouseX = _lastMouseClick.getX() + deltaY
2073 * (e.getX() > _lastMouseClick.getX() ? 1 : -1);
2074 }
2075 }
2076 // update the ranged section
2077 _lastCropped.setEndCrop(DisplayIO.getMouseX(), FrameMouseActions
2078 .getY());
2079
2080 FrameGraphics.Repaint();
2081 return;
2082 }
2083
2084 /*
2085 * This is the context of a user clicking in freespace an dragging onto
2086 * the edge of a line
2087 */
2088 if ((_mouseDown == MouseEvent.BUTTON2 || _mouseDown == MouseEvent.BUTTON3)
2089 && _lastClickedOn == null && _lastClickedIn == null) {
2090 Item on = FrameUtils.onItem(DisplayIO.getCurrentFrame(), e.getX(),
2091 e.getY(), true);
2092
2093 if (FreeItems.getInstance().size() == 0) {
2094 // if the user can spot-weld, show the virtual spot
2095 if (on instanceof Line) {
2096 Line line = (Line) on;
2097 line.showVirtualSpot(e.getX(), e.getY());
2098 }
2099 if (on != null && on.isLineEnd()) {
2100 _lastHighlightedItem = on;
2101 on.setHighlightMode(Item.HighlightMode.Normal);
2102 } else if (_lastHighlightedItem != null) {
2103 _lastHighlightedItem
2104 .setHighlightMode(Item.HighlightMode.None);
2105 _lastHighlightedItem = null;
2106 }
2107 }
2108 }
2109
2110 // Use the below calculation for better speed. If it causes problems
2111 // switch back to the Euclidean distance calculation
2112 if (Math.abs(MouseX - e.getX()) > UserSettings.NoOpThreshold.get()
2113 || Math.abs(MouseY - e.getY()) > UserSettings.NoOpThreshold.get())
2114 _isNoOp = true;
2115
2116 FrameGraphics.Repaint();
2117 }
2118
2119 private static MouseEvent _lastMouseMoved = null;
2120
2121 private static Integer LastRobotX = null;
2122
2123 private static Integer LastRobotY = null;
2124
2125 // For some reason... sometimes the mouse move gets lost when moving the
2126 // mouse really quickly after clicking...
2127 // Use this timer to make sure it gets reset eventually if the Robot
2128 // generated event never arrives.
2129 private static Timer _RobotTimer = new Timer(200, new ActionListener() {
2130 public void actionPerformed(ActionEvent ae) {
2131 _RobotTimer.stop();
2132 LastRobotX = null;
2133 LastRobotY = null;
2134 // System.out.println("RobotTimer");
2135 }
2136 });
2137
2138 private static Timer _autoStampTimer = new Timer(200, new ActionListener() {
2139 public void actionPerformed(ActionEvent ae) {
2140 stampItemsOnCursor(false);
2141 }
2142 });
2143
2144 private static boolean _controlDown;
2145
2146 private static boolean _shiftDown;
2147
2148 private static OnNewFrameAction _onFrameAction = null;
2149
2150 public static void setTDFCAction(OnNewFrameAction action) {
2151 _onFrameAction = action;
2152 }
2153
2154 public static boolean isControlDown() {
2155 return _controlDown;
2156 }
2157
2158 public static boolean isShiftDown() {
2159 return _shiftDown;
2160 }
2161
2162 public static void setLastRobotMove(float x, float y) {
2163 // Make sure the system is in the right state while waiting for the
2164 // Robots event to arrive.
2165 MouseX = x;
2166 MouseY = y;
2167 // System.out.println("MouseMoved: " + MouseX + "," + MouseY + " " +
2168 // System.currentTimeMillis());
2169 LastRobotX = Math.round(x);
2170 LastRobotY = Math.round(y);
2171 _RobotTimer.start();
2172 }
2173
2174 public static boolean isWaitingForRobot() {
2175 return LastRobotX != null;
2176 }
2177
2178 /**
2179 * Updates the stored mouse position and highlights any items as necessary.
2180 */
2181 public void mouseMoved(MouseEvent e) {
2182 mouseMoved(e, false);
2183 }
2184
2185 private void mouseMoved(MouseEvent e, boolean shiftStateChanged) {
2186 // System.out.println("mouseMoved");
2187 // System.out.println(_context);
2188 if (_context == CONTEXT_FREESPACE)
2189 FrameKeyboardActions.resetEnclosedItems();
2190 // System.out.println(e.getX() + "," + e.getY() + " " + e.getWhen());
2191 if (LastRobotX != null) {
2192 // Wait until the last Robot mouse move event arrives before
2193 // processing other events
2194 if (/* FreeItems.getInstance().size() == 0 || */
2195 (LastRobotX == e.getX() && LastRobotY == e.getY())) {
2196 LastRobotX = null;
2197 LastRobotY = null;
2198 _RobotTimer.stop();
2199 } else {
2200 // System.out.println("Ignored: " +
2201 // FreeItems.getInstance().size());
2202 return;
2203 }
2204 }
2205
2206 MouseX = e.getX();
2207 MouseY = e.getY();
2208
2209 // System.out.println(MouseX + "," + MouseY);
2210
2211 // Moving the mouse a certain distance removes the last edited text if
2212 // it is empty
2213 Text lastEdited = FrameUtils.getLastEdited();
2214 if (lastEdited != null && lastEdited.getText().length() == 0
2215 && lastEdited.getPosition().distance(e.getPoint()) > 20) {
2216 FrameUtils.setLastEdited(null);
2217 }
2218
2219 // If shift is down then the movement is constrained
2220 if (_controlDown && FreeItems.getInstance().size() > 0) {
2221 // Check if we are rubber banding a line
2222 if (shiftStateChanged && rubberBanding()) {
2223 // Get the line end that is being rubber banded
2224 Item thisEnd = FreeItems.getInstance().get(0).isLineEnd() ? FreeItems
2225 .getInstance().get(0)
2226 : FreeItems.getInstance().get(1);
2227 Line line = (Line) (FreeItems.getInstance().get(0).isLineEnd() ? FreeItems
2228 .getInstance().get(1)
2229 : FreeItems.getInstance().get(0));
2230 Item otherEnd = line.getOppositeEnd(thisEnd);
2231 int deltaX = Math.abs(e.getX() - otherEnd.getX());
2232 int deltaY = Math.abs(e.getY() - otherEnd.getY());
2233 // Check if its a vertical line
2234 if (deltaX < deltaY / 2) {
2235 // otherEnd.setX(thisEnd.getX());
2236 // MouseX = otherEnd.getX();
2237 if (shiftStateChanged) {
2238 new Constraint(thisEnd, otherEnd, thisEnd
2239 .getParentOrCurrentFrame().getNextItemID(),
2240 Constraint.VERTICAL);
2241 }
2242 }
2243 // Check if its horizontal
2244 else if (deltaY <= deltaX / 2) {
2245 // MouseY = otherEnd.getY();
2246 // otherEnd.setY(thisEnd.getY());
2247 if (shiftStateChanged) {
2248 new Constraint(thisEnd, otherEnd, thisEnd
2249 .getParentOrCurrentFrame().getNextItemID(),
2250 Constraint.HORIZONTAL);
2251 }
2252 } else {
2253 // Add DIAGONAL constraints
2254 // if (deltaX > deltaY) {
2255 // otherEnd.setY(thisEnd.getY() + deltaX
2256 // * (e.getY() < otherEnd.getY() ? 1 : -1));
2257 // } else {
2258 // otherEnd.setX(thisEnd.getX() + deltaY
2259 // * (e.getX() < otherEnd.getX() ? 1 : -1));
2260 // }
2261 if (shiftStateChanged) {
2262 int constraint = Constraint.DIAGONAL_NEG;
2263 // Check if the slope is positive
2264 if ((thisEnd.getY() - otherEnd.getY())
2265 / (double) (thisEnd.getX() - otherEnd.getX()) > 0.0) {
2266 constraint = Constraint.DIAGONAL_POS;
2267 }
2268
2269 new Constraint(thisEnd, otherEnd, thisEnd
2270 .getParentOrCurrentFrame().getNextItemID(),
2271 constraint);
2272 }
2273 }
2274 }// If its a lineend attached to two lines lengthen the shorter
2275 // so it is the same length as the longer line
2276 else if (FreeItems.getInstance().size() == 3) {
2277 // check if we are rubber banding the corner of a shape
2278 Item thisEnd = getShapeCorner(FreeItems.getInstance());
2279 if (thisEnd != null) {
2280 Line line1 = thisEnd.getLines().get(0);
2281 Line line2 = thisEnd.getLines().get(1);
2282 // Check if the two lines are constrained and hence it is a
2283 // rectangle
2284 Integer c1 = line1.getPossibleConstraint();
2285 Integer c2 = line2.getPossibleConstraint();
2286
2287 if (c1 != null && c2 != null) {
2288 // This is the case of a constrained rectangle
2289 if ((c2 == Constraint.VERTICAL || c2 == Constraint.HORIZONTAL)
2290 && (c1 == Constraint.VERTICAL || c1 == Constraint.HORIZONTAL)
2291 && (c1 != c2)) {
2292 Line vLine = line2;
2293 Line hLine = line1;
2294 if (c1 == Constraint.VERTICAL) {
2295 vLine = line1;
2296 hLine = line2;
2297 }
2298 Item hOtherEnd = hLine.getOppositeEnd(thisEnd);
2299 Item vOtherEnd = vLine.getOppositeEnd(thisEnd);
2300
2301 double vLength = Math
2302 .abs(vOtherEnd.getY() - MouseY);
2303 double hLength = Math
2304 .abs(hOtherEnd.getX() - MouseX);
2305
2306 if (vLength > hLength) {
2307 MouseX = Math.round(hOtherEnd.getX() + vLength
2308 * (MouseX > hOtherEnd.getX() ? 1 : -1));
2309 } else /* if (hLength > vLength) */{
2310 MouseY = Math.round(vOtherEnd.getY() + hLength
2311 * (MouseY > vOtherEnd.getY() ? 1 : -1));
2312 }
2313 }
2314 // } else if (c2 != null) {
2315 //
2316 // } // Other wise it is a not constrained shape so
2317 // constrain
2318 // the two lines lengths to be equal
2319 } else {
2320 Item lineEnd1 = line1.getOppositeEnd(thisEnd);
2321 Item lineEnd2 = line2.getOppositeEnd(thisEnd);
2322 double l1 = Line.getLength(lineEnd1.getPosition(), e
2323 .getPoint());
2324 double l2 = Line.getLength(lineEnd2.getPosition(), e
2325 .getPoint());
2326 double l3 = Line.getLength(lineEnd1.getPosition(),
2327 lineEnd2.getPosition());
2328 // l1 needs to be the shorter end
2329 if (l1 > l2) {
2330 Item temp = lineEnd1;
2331 lineEnd1 = lineEnd2;
2332 lineEnd2 = temp;
2333 double tempL = l1;
2334 l1 = l2;
2335 l2 = tempL;
2336 }
2337 // Now use the cosine rule to calculate the angle
2338 // between l1 and l3
2339 double cosTheta = (l1 * l1 + l3 * l3 - l2 * l2)
2340 / (2 * l1 * l3);
2341 // now calculate the new length for the lines using cos
2342 // rule
2343 double l_new = l3 / (2 * cosTheta);
2344 double ratio = l_new / l1;
2345 MouseX = Math.round((e.getX() - lineEnd1.getX())
2346 * ratio)
2347 + lineEnd1.getX();
2348 MouseY = Math.round((e.getY() - lineEnd1.getY())
2349 * ratio)
2350 + lineEnd1.getY();
2351
2352 }
2353 }
2354 }
2355 } else if (shiftStateChanged && !_controlDown && rubberBanding()) {
2356 // Get the line end that is being rubber banded
2357 Item thisEnd = FreeItems.getInstance().get(0).isLineEnd() ? FreeItems
2358 .getInstance().get(0)
2359 : FreeItems.getInstance().get(1);
2360 thisEnd.removeAllConstraints();
2361 }
2362
2363 if (_lastMouseMoved == null)
2364 _lastMouseMoved = e;
2365
2366 _lastMouseMoved = e;
2367
2368 refreshHighlights();
2369
2370 if (FreeItems.hasCursor()) {
2371 move(FreeItems.getCursor(), true);
2372 }
2373
2374 if (FreeItems.itemsAttachedToCursor()) {
2375 move(FreeItems.getInstance());
2376 // System.out.println(FreeItems.getInstance().size());
2377 }
2378
2379 if (_forceArrowCursor)
2380 updateCursor();
2381
2382 _forceArrowCursor = true;
2383 }
2384
2385
2386 public void refreshHighlights() {
2387 // ByMike: Get the item the mouse is hovering over
2388 Item click = FrameUtils.getCurrentItem();
2389 Item on = null;
2390 // System.out.println(click);
2391 if (click != null) {
2392 on = click;
2393 // set the context
2394 if (on instanceof Line)
2395 _context = CONTEXT_AT_LINE;
2396 else if (on instanceof Dot)
2397 _context = CONTEXT_AT_DOT;
2398 else if (on instanceof Text) {
2399 _context = CONTEXT_AT_TEXT;
2400 }
2401 if (FreeItems.getInstance().size() > 0)
2402 _alpha = 60;
2403 else
2404 _alpha = -1;
2405 } else {
2406 _context = CONTEXT_FREESPACE;
2407 _alpha = -1;
2408 }
2409
2410 // if the user is pointing at an item, highlight it
2411 if (on != null && !FreeItems.getInstance().contains(on)) {
2412 // if the user can spot-weld, show the virtual spot
2413 if (FreeItems.getInstance().size() == 2 && on instanceof Line) {
2414 Line line = (Line) on;
2415 Item freeItem0 = FreeItems.getInstance().get(0);
2416 Item freeItem1 = FreeItems.getInstance().get(1);
2417 Item lineEnd = freeItem0.isLineEnd() ? freeItem0 : (freeItem1
2418 .isLineEnd() ? freeItem1 : null);
2419 if (lineEnd != null) {
2420 if (_mouseDown == 0)
2421 line.showVirtualSpot(lineEnd, DisplayIO.getMouseX(),
2422 FrameMouseActions.getY());
2423 } else
2424 // The user is pointing at another point or text item
2425 // etc
2426 FrameGraphics.changeHighlightMode(on,
2427 Item.HighlightMode.Normal);
2428 } else {
2429 // FrameGraphics.ChangeSelectionMode(on,
2430 // Item.SelectedMode.Connected);
2431 // TODO: The method below is for the most part redundant
2432
2433 on = FrameGraphics.Highlight(on.getEditTarget());
2434 }
2435 // if the last item highlighted is still highlighted, clear it
2436 if (_lastHoldsHighlight) {
2437 _lastHoldsHighlight = false;
2438 for (Item i : DisplayIO.getCurrentFrame().getItems())
2439 if (i.isHighlighted() && i != on)
2440 FrameGraphics.changeHighlightMode(i,
2441 Item.HighlightMode.None);
2442 }
2443
2444 // if the user is not pointing at an item, check for enclosure
2445 // highlighting
2446 } else if (on == null) {
2447 Collection<Item> enclosure = FrameUtils.getEnclosingLineEnds();
2448 if (enclosure != null && enclosure.size() > 0) {
2449 Item firstLineEnd = enclosure.iterator().next();
2450 HighlightMode hm;
2451 if(isShiftDown()) {
2452 hm = HighlightMode.Connected;
2453 } else {
2454 hm = HighlightMode.Enclosed;
2455 }
2456 if (firstLineEnd.getLines().size() > 1 &&
2457 // check that the enclosure is not part of a point being
2458 // dragged in space
2459 !ContainsOneOf(enclosure, FreeItems.getInstance())) {
2460 on = firstLineEnd.getLines().get(0);
2461 // System.out.println(on == null ? "Null" :
2462 // on.toString());
2463 FrameGraphics.changeHighlightMode(on, hm);
2464 } else if (firstLineEnd instanceof XRayable) {
2465 on = firstLineEnd;
2466 FrameGraphics.changeHighlightMode(firstLineEnd, hm);
2467 }
2468 _context = CONTEXT_AT_ENCLOSURE;
2469 } else if (_lastHighlightedItem != null) {
2470 // System.out.println("LastHighlightedItem");
2471 _lastHoldsHighlight = false;
2472 }
2473 }
2474
2475 // disable cursor changes when the cursor has items attached
2476 if (FreeItems.itemsAttachedToCursor()
2477 && DisplayIO.getCursor() != Item.TEXT_CURSOR)
2478 _forceArrowCursor = false;
2479
2480 // setLastHighlightedItem(on);
2481
2482 if (_lastHighlightedItem != null && _lastHighlightedItem != on
2483 && !_lastHoldsHighlight) {
2484 // Turn off the highlighting only if
2485 // the last highlighted item is not connected to the currentItem
2486 // Otherwise we get flickering in transition from connected to
2487 // normal mode while moving the cursor along a line.
2488 if (on == null
2489 || (!on.getAllConnected().contains(_lastHighlightedItem))) {
2490 FrameGraphics.changeHighlightMode(_lastHighlightedItem,
2491 Item.HighlightMode.None);
2492 }
2493 }
2494
2495 _lastHighlightedItem = on;
2496
2497 }
2498
2499 private boolean ContainsOneOf(Collection<Item> enclosure,
2500 Collection<Item> freeItems) {
2501 if (freeItems == null)
2502 return false;
2503 for (Item i : freeItems) {
2504 if (enclosure.contains(i))
2505 return true;
2506 }
2507 return false;
2508 }
2509
2510 /**
2511 * Checks if lines are being rubber banded.
2512 *
2513 * @return true if the user is rubberBanding one or more lines
2514 */
2515 private static boolean rubberBanding() {
2516 if (FreeItems.getInstance().size() != 2) {
2517 return false;
2518 }
2519
2520 // if rubber-banding, there will be 1 lineend and the rest will be lines
2521 boolean foundLineEnd = false;
2522 for (Item i : FreeItems.getInstance()) {
2523 if (i.isLineEnd()) {
2524 if (foundLineEnd) {
2525 return false;
2526 }
2527 foundLineEnd = true;
2528 } else if (!(i instanceof Line) || !i.isVisible()) {
2529 return false;
2530 }
2531 }
2532 return true;
2533 }
2534
2535 /**
2536 * Updates the current mouse cursor to whatever it should be. i.e. Hidden
2537 * when rubber-banding lines, otherwise default (arrow)
2538 */
2539 public static void updateCursor() {
2540 if (rubberBanding()) {
2541 DisplayIO.setCursor(Item.HIDDEN_CURSOR);
2542 return;
2543 }
2544 // This is to make sure the TEXT_CURSOR doesnt get inadvertantly turned
2545 // off!
2546 Item on = FrameUtils.getCurrentItem();
2547 if (on != null && on instanceof Text) {
2548 return;
2549 }
2550 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2551 }
2552
2553 public static void setHighlightHold(boolean hold) {
2554 _lastHoldsHighlight = hold;
2555 }
2556
2557 public static void resetOffset() {
2558 if (FreeItems.itemsAttachedToCursor()) {
2559 _offX = DisplayIO.getMouseX()
2560 - FreeItems.getInstance().get(0).getX()
2561 + FreeItems.getInstance().get(0).getOffset().x;
2562 _offY = getY() - FreeItems.getInstance().get(0).getY()
2563 + FreeItems.getInstance().get(0).getOffset().y;
2564 }
2565 }
2566
2567 /**
2568 * Moves the items to the current mouse position (plus the current offset)
2569 *
2570 * @param toMove
2571 */
2572 static void move(Collection<Item> toMove) {
2573 move(toMove, false);
2574 }
2575
2576 static void move(Collection<Item> toMove, boolean cursor) {
2577
2578 // Gets the origin of the first item to move
2579 int xPos = (DisplayIO.getMouseX() - (cursor ? 0 : _offX));
2580
2581 Item firstDot = toMove.iterator().next();
2582
2583 int deltax = firstDot.getX() - xPos;
2584 int deltay = firstDot.getY() - (getY() - (cursor ? 0 : _offY));
2585
2586 for (Item move : toMove) {
2587 move.setPosition(move.getX() - deltax, move.getY() - deltay);
2588
2589 if (!cursor && move instanceof Text) {
2590 ((Text) move).setAlpha(_alpha);
2591 }
2592 }
2593
2594 FrameGraphics.requestRefresh(true);
2595 // FrameGraphics.refresh(true);
2596 }
2597
2598 private static void load(String toLoad, boolean addToHistory) {
2599 if (FrameIO.isValidFrameName(toLoad)) {
2600 DisplayIO.clearBackedUpFrames();
2601 FrameUtils.DisplayFrame(toLoad, addToHistory, true);
2602 } else {
2603 MessageBay.errorMessage(toLoad + " is not a valid frame name.");
2604 }
2605 }
2606
2607 private static void back() {
2608 DisplayIO.Back();
2609
2610 // repaint things if necessary
2611 if (FreeItems.itemsAttachedToCursor())
2612 move(FreeItems.getInstance());
2613
2614 if (FreeItems.hasCursor())
2615 move(FreeItems.getCursor(), true);
2616 }
2617
2618 private static void forward() {
2619 DisplayIO.Forward();
2620
2621 // repaint things if necessary
2622 if (FreeItems.itemsAttachedToCursor())
2623 move(FreeItems.getInstance());
2624
2625 if (FreeItems.hasCursor())
2626 move(FreeItems.getCursor(), true);
2627 }
2628
2629 /**
2630 * Returns true if the mouse moved during TDFC. This will happen if there is
2631 * a start annotation item on the frame.
2632 *
2633 * @param linker
2634 * @return
2635 */
2636 public static boolean tdfc(Item linker) throws RuntimeException {
2637 // if this is a non-usable item
2638 if (linker.getID() < 0)
2639 return false;
2640
2641 // Check if its an image that can be resized to fit a box
2642 // around it
2643 String text = linker.getText();
2644 boolean isVector = text.equals("@v") || text.equals("@av");
2645 boolean isFrameImage = text.equals("@f");
2646 boolean isBitmap = false; // text.equals("@b");
2647
2648 if (isVector || isFrameImage || isBitmap) {
2649 Collection<Item> enclosure = FrameUtils.getEnclosingLineEnds(linker
2650 .getPosition());
2651 if (enclosure != null) {
2652 for (Item i : enclosure) {
2653 if (i.isLineEnd() && i.isEnclosed()) {
2654 if (!isVector)
2655 DisplayIO.getCurrentFrame().removeAllItems(
2656 enclosure);
2657 Rectangle rect = i.getEnclosedRectangle();
2658 long width = Math.round(rect.getWidth());
2659 if (isVector) {
2660 NumberFormat nf = Vector.getNumberFormatter();
2661 linker.setText(linker.getText()
2662 + ": "
2663 + nf.format((width / FrameGraphics
2664 .getMaxFrameSize().getWidth())));
2665 } else {
2666 linker.setText(linker.getText() + ": " + width);
2667 }
2668
2669 linker.setPosition(new Point(rect.x, rect.y));
2670 linker.setThickness(i.getThickness());
2671 linker.setBorderColor(i.getColor());
2672 break;
2673 }
2674 }
2675 if (!isVector)
2676 FrameMouseActions.deleteItems(enclosure, false);
2677 }
2678 }
2679
2680 boolean mouseMoved;
2681
2682 linker.getParent().setChanged(true);
2683
2684 Frame next = FrameIO.CreateNewFrame(linker, _onFrameAction);
2685
2686 linker.setLink("" + next.getNumber());
2687
2688 for (Item i : next.getTextItems()) {
2689 // Set the link for @Parent annotation item if one
2690 if (ItemUtils.startsWithTag(i, ItemUtils.TAG_PARENT)
2691 && i.getLink() == null) {
2692 Frame parent = linker.getParentOrCurrentFrame();
2693 i.setLink(parent.getName());
2694 } else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_BACKUP, false)) {
2695 // Delink backup tag if it is on the frame
2696 i.setLink(null);
2697 }
2698 }
2699
2700 FrameUtils.DisplayFrame(next, true, true);
2701 FrameUtils.setTdfcItem(linker);
2702
2703 mouseMoved = next.moveMouseToDefaultLocation();
2704 // this needs to be done if the user doesnt move the mouse before doing
2705 // tdfc while the cursor is set to the text cursor
2706 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2707 // This needs to be done in case there was a @start on the frame which
2708 // triggers changed to be set to true when it should stay as false
2709 next.setChanged(false);
2710 return mouseMoved;
2711 }
2712
2713 /**
2714 * Creates a new Text item and fills it with particular attributes extracted
2715 * from the given Item. Note: Users always have permission to extract
2716 * attributes, so it is not checked.
2717 *
2718 * @param toExtract
2719 * Item containing the Item to extract the attributes from.
2720 */
2721 private static void extractAttributes(Item toExtract) {
2722 if (toExtract == null || toExtract == null)
2723 return;
2724
2725 if (FreeItems.itemsAttachedToCursor())
2726 return;
2727
2728 Item attribs;
2729 Item item = toExtract;
2730 // Extract the frames attributes when the user clicks on the frame name
2731 FrameGraphics.changeHighlightMode(item, HighlightMode.None);
2732 if (item.isFrameName())
2733 attribs = AttributeUtils.extractAttributes(item.getParent());
2734 else {
2735 attribs = AttributeUtils.extractAttributes(item);
2736 }
2737
2738 if (attribs == null)
2739 MessageBay
2740 .displayMessage("All attributes of that item are default values.");
2741 else {
2742 // Give the attribute text item the color of the item for which
2743 // attributes are being extracted.
2744 // attribs.setColor(item.getColor());
2745 pickup(attribs);
2746 }
2747 }
2748
2749 public static void delete(Item toDelete) {
2750 boolean bRecalculate = false;
2751
2752 FrameUtils.setLastEdited(null);
2753 _offX = _offY = 0;
2754
2755 Frame currentFrame = DisplayIO.getCurrentFrame();
2756 // check if the user is pointing at the frame's framename
2757 if (toDelete != null && toDelete == currentFrame.getNameItem()) {
2758 currentFrame.clear(false);
2759 FrameGraphics.Repaint();
2760 return;
2761 }
2762
2763 // if the user is deleting items attached to the cursor
2764 if (FreeItems.itemsAttachedToCursor()) {
2765 // if this is an item-swap
2766 if (FreeItems.getInstance().size() == 1
2767 && FreeItems.getInstance().get(0) instanceof Text
2768 && toDelete != null && toDelete instanceof Text) {
2769
2770 // check permissions
2771 if (!toDelete.hasPermission(UserAppliedPermission.full)) {
2772 MessageBay
2773 .displayMessage("Insufficient permission to swap Item text");
2774 return;
2775 }
2776 Text anchored = (Text) toDelete;
2777 Text free = (Text) FreeItems.getInstance().get(0);
2778 SessionStats.DeletedItem(free);
2779 // List<String> temp = anchored.getText();
2780 anchored.setText(free.getText());
2781 anchored.setFormula(free.getFormula());
2782
2783 // free.setTextList(temp);
2784 FreeItems.getInstance().clear();
2785
2786 anchored.getParent().setChanged(true);
2787
2788 bRecalculate |= free.recalculateWhenChanged();
2789 bRecalculate |= anchored.recalculateWhenChanged();
2790
2791 // update the offset since the text has changed
2792 _offX = DisplayIO.getMouseX() - anchored.getX()
2793 + anchored.getOffset().x;
2794 _offY = getY() - anchored.getY() + anchored.getOffset().y;
2795 } else {
2796 // if shift is pressed delete the entire shape attached to the dot
2797 if(isShiftDown()) {
2798 List<Item> tmp = new ArrayList<Item>(FreeItems.getInstance());
2799 for(Item i : tmp) {
2800 // remove entire rectangles instead of just the corner
2801 if(i instanceof Dot) {
2802 FreeItems.getInstance().addAll(i.getAllConnected());
2803 for(Item j : i.getAllConnected()) {
2804 if(j instanceof Dot) {
2805 FreeItems.getInstance().addAll(j.getLines());
2806 }
2807 }
2808 }
2809 }
2810 }
2811 deleteItems(FreeItems.getInstance());
2812 }
2813 // reset the mouse cursor
2814 updateCursor();
2815 // the user is not pointing at an item
2816 } else if (toDelete == null) {
2817
2818 // if the user is pointing inside a closed shape, delete it
2819
2820 Collection<Item> items = null;
2821 // if shift is down, only delete the enclosing shape (ignore the items inside)
2822 if(isShiftDown()) {
2823 Collection<Item> tmp = FrameUtils.getEnclosingLineEnds();
2824 if(tmp != null) {
2825 items = new ArrayList<Item>();
2826 items.addAll(tmp);
2827 for(Item i : tmp) {
2828 if(i instanceof Dot) {
2829 items.addAll(((Dot)i).getLines());
2830 }
2831 }
2832 }
2833 } else {
2834 items = FrameUtils.getCurrentItems(null);
2835 }
2836
2837 if (items != null) {
2838 Collection<Item> toRemove = new LinkedHashSet<Item>(items
2839 .size());
2840 for (Item ip : items) {
2841 if (ip.hasPermission(UserAppliedPermission.full)) {
2842 // Only include lines if one of their enpoints are also
2843 // being removed
2844 if (ip instanceof Line) {
2845 Line l = (Line) ip;
2846 Item end = l.getEndItem();
2847 Item start = l.getStartItem();
2848
2849 // If one end of a line is being deleted, remove the
2850 // other end if all its connecting lines are being
2851 // deleted
2852 if (items.contains(end)) {
2853 if (!items.contains(start)
2854 && items.containsAll(start.getLines())) {
2855 toRemove.add(start);
2856 }
2857 } else if (items.contains(start)) {
2858 if (items.containsAll(end.getLines())) {
2859 toRemove.add(end);
2860 }
2861 } else {
2862 continue;
2863 }
2864 }
2865 toRemove.add(ip);
2866 }
2867 }
2868
2869 deleteItems(toRemove);
2870
2871 // reset the mouse cursor
2872 updateCursor();
2873 FrameGraphics.Repaint();
2874
2875 // otherwise this is an undo command
2876 } else {
2877 if(isControlDown()) {
2878 DisplayIO.getCurrentFrame().redo();
2879 } else {
2880 DisplayIO.getCurrentFrame().undo();
2881 }
2882 }
2883 return;
2884 // this is a delete command
2885 } else {
2886
2887 // Special case if toDelete item is an image: only want to delete if over non-background pixel color
2888 if (toDelete instanceof Picture) {
2889 int mouseX = DisplayIO.getMouseX();
2890 int mouseY = FrameMouseActions.getY();
2891 Picture clickedOnPicture = (Picture)toDelete;
2892 Frame current_frame = DisplayIO.getCurrentFrame();
2893 Color bg_col = current_frame.getBackgroundColor();
2894 if (clickedOnPicture.MouseOverBackgroundPixel(mouseX,mouseY,bg_col)) {
2895 // behave as if Redo/Undo request, then return
2896 if(isControlDown()) {
2897 DisplayIO.getCurrentFrame().redo();
2898 } else {
2899 DisplayIO.getCurrentFrame().undo();
2900 }
2901 return;
2902 }
2903 // If get to here, then user clicked on an image (non-trival pixel color),
2904 // so go ahead and let it be deleted in the usual way
2905 }
2906
2907
2908 // check permissions
2909 if (!toDelete.hasPermission(UserAppliedPermission.full)) {
2910 Item editTarget = toDelete.getEditTarget();
2911 if (editTarget != toDelete
2912 && editTarget.hasPermission(UserAppliedPermission.full)) {
2913 toDelete = editTarget;
2914 } else {
2915 MessageBay
2916 .displayMessage("Insufficient permission to delete item");
2917 return;
2918 }
2919 }
2920
2921 Frame parent = toDelete.getParent();
2922 if (parent != null) {
2923 parent.setChanged(true);
2924 }
2925 Collection<Item> toUndo = null;
2926 if (toDelete.isLineEnd()) {
2927 // delete the entire connected shape if shift is down
2928 if(isShiftDown()) {
2929 List<Item> tmp = new ArrayList<Item>();
2930 tmp.add(toDelete);
2931 // remove entire shape instead of just the corner
2932 tmp.addAll(toDelete.getAllConnected());
2933 for(Item j : toDelete.getAllConnected()) {
2934 if(j instanceof Dot) {
2935 tmp.addAll(j.getLines());
2936 }
2937 }
2938 deleteItems(tmp);
2939 return;
2940 } else {
2941 toUndo = deleteLineEnd(toDelete);
2942 }
2943 // delete the entire connected shape if shift is down, unless we're hovering the end of the line
2944 } else if (toDelete instanceof WidgetEdge) { // must notify
2945 // widgets that they
2946 // are being deleted
2947 ((WidgetEdge) toDelete).getWidgetSource().onDelete();
2948 toUndo = toDelete.getConnected();
2949 } else if (toDelete instanceof Line && isShiftDown() ||
2950 toDelete.getHighlightMode() == Item.HighlightMode.Disconnect) {
2951 Line line = (Line) toDelete;
2952 Item start = line.getStartItem();
2953 Item end = line.getEndItem();
2954 Collection<Item> delete = new LinkedList<Item>();
2955 delete.add(toDelete);
2956 if (end.getLines().size() == 1) {
2957 delete.add(end);
2958 } else {
2959 end.removeLine(line);
2960 }
2961 if (start.getLines().size() == 1) {
2962 delete.add(start);
2963 } else {
2964 start.removeLine(line);
2965 }
2966 toUndo = delete;
2967 } else {
2968 bRecalculate |= toDelete.recalculateWhenChanged();
2969 toUndo = toDelete.getConnected(); // copy(toDelete.getConnected());
2970 }
2971 SessionStats.DeletedItems(toUndo);
2972 if (parent != null) {
2973 parent.addToUndoDelete(toUndo);
2974 parent.removeAllItems(toUndo); // toDelete.getConnected()
2975 }
2976 // reset the mouse cursor
2977 updateCursor();
2978 if (parent != null)
2979 // ItemUtils.EnclosedCheck(parent.getItems());
2980 ItemUtils.Justify(parent);
2981 if (toDelete.hasOverlay()) {
2982 FrameUtils.Parse(parent, false, false);
2983 FrameGraphics.requestRefresh(false);
2984 }
2985
2986 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2987
2988 }
2989
2990 currentFrame.notifyObservers(bRecalculate);
2991
2992 FrameGraphics.Repaint();
2993 }
2994
2995 public static void deleteItems(Collection<Item> itemList) {
2996 deleteItems(itemList, true);
2997 }
2998
2999 public static void deleteItems(Collection<Item> itemList, boolean addToUndo) {
3000 boolean bReparse = false;
3001 boolean bRecalculate = false;
3002
3003 SessionStats.DeletedItems(itemList);
3004 List<Frame> modifiedFrames = new LinkedList<Frame>();
3005 // Get a list of all the modified frames
3006 for (Item i : itemList) {
3007 Frame parent = i.getParent();
3008 if (parent != null)
3009 modifiedFrames.add(parent);
3010 i.setHighlightMode(HighlightMode.None);
3011 bReparse |= i.hasOverlay();
3012 bRecalculate |= i.recalculateWhenChanged();
3013 }
3014 // If they are all free items then add the current frame
3015 if (modifiedFrames.size() == 0) {
3016 modifiedFrames.add(DisplayIO.getCurrentFrame());
3017 }
3018
3019 Collection<Item> toUndo = new LinkedHashSet<Item>();
3020 // disconnect any connected items
3021 for (Item i : itemList) {
3022
3023 // Only delete the current item if have not already deleted.
3024 // This is especially important for heavy duty widgets - so they
3025 // do not have to expire several times per delete.
3026 if (toUndo.contains(i))
3027 continue;
3028
3029 // Make sure text items attached to cursor are reset back to the
3030 // transparency they should have.
3031 if (i instanceof Text) {
3032 ((Text) i).setAlpha(-1);
3033 }
3034
3035 if (i.getLines().size() > 0) {
3036
3037 Collection<Item> toDelete = deleteLineEnd(i);
3038 if (addToUndo) {
3039 // add the copied items to the undo stack
3040 for (Item itemToUndo : toDelete) {
3041
3042 if (!toUndo.contains(itemToUndo))
3043 toUndo.add(itemToUndo);
3044
3045 }
3046 }
3047 } else if (!toUndo.contains(i)) {
3048 if (addToUndo)
3049 toUndo.add(i); // Why was is this a copy
3050 }
3051 }
3052
3053 for (Frame f : modifiedFrames) {
3054 f.removeAllItems(itemList);
3055 // ItemUtils.EnclosedCheck(f.getItems());
3056 ItemUtils.Justify(f);
3057 }
3058 // TODO: How should undelete deal with undo when items are removed from
3059 // the current frame as well as the overlay frame
3060 Frame currentFrame = DisplayIO.getCurrentFrame();
3061 currentFrame.addToUndoDelete(itemList);
3062 itemList.clear();
3063 if (bReparse) {
3064 FrameUtils.Parse(currentFrame, false, false);
3065 /*
3066 * TODO check if I need to recalculate even if reparse occurs, here
3067 * and in anchor, pickup etc
3068 */
3069 } else {
3070 currentFrame.notifyObservers(bRecalculate);
3071 }
3072
3073 }
3074
3075 private static Collection<Item> deleteLineEnd(Item lineEnd) {
3076
3077 if (lineEnd instanceof WidgetCorner) { // Brook
3078
3079 WidgetCorner wc = (WidgetCorner) lineEnd;
3080 Frame parent = wc.getWidgetSource().getParentFrame();
3081
3082 // Remove from the parent frame
3083 if (parent != null) {
3084 parent.removeAllItems(wc.getWidgetSource().getItems());
3085 }
3086
3087 wc.getWidgetSource().onDelete(); // Changes the widgets
3088 // corner/edges ID's...
3089
3090 return wc.getWidgetSource().getItems();
3091
3092 } else {
3093
3094 // // create a backup copy of the dot and its lines
3095 // List<Item> copy = copy(lineEnd.getConnected());
3096 //
3097 // // Remove lines from their anchored dots
3098 // // note: the line is kept so that it can be properly restored
3099 // for (Item ic : copy) {
3100 // if (ic instanceof Line) {
3101 // Line line = (Line) ic;
3102 // // Remove the line from the item that is not the copy of the
3103 // // line end being deletedF
3104 // if (!copy.contains(line.getStartItem()))
3105 // line.getStartItem().removeLine(line);
3106 // if (!copy.contains(line.getEndItem()))
3107 // line.getEndItem().removeLine(line);
3108 // }
3109 // }
3110
3111 Collection<Item> copy = lineEnd.getConnected();
3112
3113 // remove all lines being deleted
3114 for (Item ic : lineEnd.getConnected()) {
3115 if (ic instanceof Line
3116 && ((Line) ic).getOppositeEnd(lineEnd) != null) {
3117 Line line = (Line) ic;
3118
3119 // Invalidate the line to make sure we dont get any ghost
3120 // arrowheads.
3121 ic.invalidateAll();
3122
3123 Item d = line.getOppositeEnd(lineEnd);
3124 d.removeLine(line);
3125
3126 // if the dot was only part of one line, it can be
3127 // removed
3128 if (d.getLines().size() == 0) {
3129 if (d.getParent() != null)
3130 d.getParent().removeItem(d);
3131 if (!copy.contains(d))
3132 copy.add(d);
3133 }
3134
3135 if (lineEnd.getParent() != null)
3136 lineEnd.getParent().removeItem(ic);
3137 }
3138 }
3139 return copy;
3140 }
3141 }
3142
3143 private static void removeAllLinesExcept(Item from, Item but) {
3144 List<Line> lines = new LinkedList<Line>();
3145 lines.addAll(from.getLines());
3146 for (Line line : lines)
3147 if (line.getOppositeEnd(from) != but)
3148 from.removeLine(line);
3149
3150 List<Constraint> consts = new LinkedList<Constraint>();
3151 consts.addAll(from.getConstraints());
3152 for (Constraint c : consts)
3153 if (c.getOppositeEnd(from) != but)
3154 from.removeConstraint(c);
3155 }
3156
3157 public static Collection<Item> merge(List<Item> merger, Item mergee) {
3158 assert (mergee != null);
3159 if (mergee.isFrameName()) {
3160 return mergee.getParent().merge(merger);
3161 }
3162
3163 // if(mergee instanceof XRayable)
3164 // return merger;
3165
3166 // check for rectangle merging
3167 if (merger.size() == 3 && mergee.getLines().size() == 2) {
3168 Item corner = getShapeCorner(merger);
3169 // if this is a corner of a shape
3170 if (corner != null) {
3171 Collection<Item> allConnected = corner.getAllConnected();
3172 // Check if we are collapsing a rectangle
3173 if (allConnected.size() == 8 && allConnected.contains(mergee)) {
3174 DisplayIO.setCursorPosition(mergee.getPosition());
3175 DisplayIO.getCurrentFrame().removeAllItems(allConnected);
3176
3177 // find the point opposite corner...
3178 Item opposite = null;
3179 List<Line> lines = corner.getLines();
3180 for (Line l : lines) {
3181 allConnected.remove(l.getOppositeEnd(corner));
3182 }
3183 allConnected.remove(corner);
3184 for (Item i : allConnected) {
3185 if (i.isLineEnd()) {
3186 opposite = i;
3187 break;
3188 }
3189 }
3190 assert (opposite != null);
3191
3192 // check if the rectangle is small enough that it should be
3193 // collapsed to a single point
3194 int x1 = Math.abs(opposite.getX() - mergee.getX());
3195 int x2 = Math.abs(opposite.getY() - mergee.getY());
3196 int distance = (int) Math.sqrt(Math.pow(x1, 2)
3197 + Math.pow(x2, 2));
3198
3199 if (distance < RECTANGLE_TO_POINT_THRESHOLD) {
3200 mergee.removeAllConstraints();
3201 mergee.removeAllLines();
3202 mergee.setThickness(4 * mergee.getThickness());
3203 return mergee.getAllConnected();
3204 } else {
3205 removeAllLinesExcept(mergee, opposite);
3206 removeAllLinesExcept(opposite, mergee);
3207
3208 return mergee.getAllConnected();
3209 }
3210 }
3211 }
3212 }
3213
3214 List<Item> remain = new ArrayList<Item>();
3215 Item res = null;
3216
3217 for (Item i : merger) {
3218 if (!i.isVisible())
3219 continue;
3220 // check for link merging
3221 if (i instanceof Text
3222 && FrameIO.isValidFrameName((((Text) i).getFirstLine()))
3223 && FrameIO.canAccessFrame((((Text) i).getFirstLine()))) {
3224 // check that we can actually access the frame this link
3225 // corresponds to
3226 mergee.setLink(((Text) i).getFirstLine());
3227 } else {
3228 // check for attribute merging
3229 if (i instanceof Text && !i.isLineEnd()) {
3230 Text txt = (Text) i;
3231
3232 // if this is not an attribute merge
3233 if (!AttributeUtils.setAttribute(mergee, txt)) {
3234 // set mouse position for text merges
3235 if (mergee instanceof Text) {
3236 ((Text) mergee).insertText(txt.getText(), DisplayIO
3237 .getMouseX(), FrameMouseActions.getY());
3238 //Delete the item which had its text merged
3239 txt.delete();
3240 return remain;
3241 } else if (mergee instanceof WidgetCorner) {
3242 if (merger.size() == 1 && txt.getLink() != null) {
3243 // If the text item is linked then use that
3244 ((WidgetCorner) mergee).setLink(txt
3245 .getAbsoluteLink(), txt);
3246 } else {
3247 remain.addAll(merger);
3248 }
3249 return remain;
3250 } else if (mergee instanceof WidgetEdge) {
3251 if (merger.size() == 1 && txt.getLink() != null) {
3252 // If the text item is linked then use that
3253 ((WidgetEdge) mergee).setLink(txt
3254 .getAbsoluteLink(), txt);
3255 } else {
3256 remain.addAll(merger);
3257 }
3258 return remain;
3259 } else if (mergee instanceof Dot) {
3260 DisplayIO.setCursorPosition(mergee.getPosition());
3261 txt.setPosition(mergee.getPosition());
3262 txt.setThickness(mergee.getThickness());
3263 Frame parent = mergee.getParent();
3264 parent.removeItem(mergee);
3265 anchor(txt);
3266 // change the end points of the lines to the text
3267 // item
3268 while (mergee.getLines().size() > 0) {
3269 Line l = mergee.getLines().get(0);
3270 l.replaceLineEnd(mergee, txt);
3271 }
3272 break;
3273 }
3274
3275 // TODO tidy this up...
3276 // Dot override doesnt use the x and y coords
3277 // Text does... but could be removed
3278 res = mergee.merge(i, DisplayIO.getMouseX(), getY());
3279 if (res != null) {
3280 remain.add(res);
3281 }
3282 }
3283 } else {
3284 if (mergee.isLineEnd()) {
3285 DisplayIO.setCursorPosition(mergee.getPosition());
3286 }
3287 // Moving the cursor ensures shapes are anchored correctly
3288 res = mergee.merge(i, DisplayIO.getMouseX(), getY());
3289 if (res != null)
3290 remain.addAll(res.getConnected());
3291
3292 }
3293 }
3294 }
3295 updateCursor();
3296
3297 mergee.getParent().setChanged(true);
3298
3299 ItemUtils.EnclosedCheck(mergee.getParent().getItems());
3300 // Mike: Why does parse frame have to be called?!?
3301 FrameUtils.Parse(mergee.getParent());
3302
3303 return remain;
3304 }
3305
3306 /**
3307 * Picks up an item on a frame.
3308 *
3309 * @param toGrab
3310 * item to be picked up
3311 * @param removeItem
3312 * true if the item should be removed from the frame
3313 */
3314 public static void pickup(Item toGrab) {
3315 if (toGrab.isFrameName())
3316 return;
3317
3318 if (!toGrab.hasPermission(UserAppliedPermission.full)) {
3319 if (toGrab.getEditTarget() != toGrab) {
3320 pickup(toGrab.getEditTarget());
3321 } else {
3322 MessageBay
3323 .displayMessage("Insufficient permission pickup the item");
3324 }
3325 return;
3326 }
3327
3328 if (toGrab instanceof Circle)
3329 toGrab.setHighlightMode(HighlightMode.Connected);
3330 // Dont set the highlight mode if a vector is being picked up
3331 else if (toGrab.isVisible()) {
3332 toGrab.setHighlightMode(HighlightMode.Normal);
3333 }
3334
3335 // Brook: If the widget corner is being picked up. Instead refer to
3336 // picking up the edge for fixed-sized widgets so it is not so confusing
3337 if (toGrab instanceof WidgetCorner) {
3338 WidgetCorner wc = (WidgetCorner) toGrab;
3339 if (wc.getWidgetSource().isFixedSize()) {
3340 for (Item i : toGrab.getConnected()) {
3341 if (i instanceof WidgetEdge) {
3342 toGrab = i;
3343 break;
3344 }
3345 }
3346 }
3347 }
3348 pickup(toGrab.getConnected());
3349 }
3350
3351 public static void pickup(Collection<Item> toGrab) {
3352 if (toGrab == null || toGrab.size() == 0)
3353 return;
3354
3355 boolean bReparse = false;
3356 boolean bRecalculate = false;
3357
3358 Frame currentFrame = DisplayIO.getCurrentFrame();
3359 String currentFrameName = currentFrame.getName();
3360 Iterator<Item> iter = toGrab.iterator();
3361 while (iter.hasNext()) {
3362 Item i = iter.next();
3363 if (!i.hasPermission(UserAppliedPermission.full)) {
3364 iter.remove();
3365 continue;
3366 }
3367 if (i.equals(_lastHighlightedItem))
3368 _lastHighlightedItem = null;
3369
3370 bRecalculate |= i.recalculateWhenChanged();
3371 // i.setSelectedMode(SelectedMode.None);
3372 // Check if it has a relative link if so make it absolute
3373 i.setAbsoluteLink();
3374 // parent may be null
3375 if (i.getParent() != null) {
3376 i.getParent().removeItem(i);
3377 if (currentFrameName.equals(i.getParent().getName()))
3378 i.setParent(null);
3379 }
3380 FreeItems.getInstance().add(i);
3381 i.setFloating(true);
3382 // If its a vector pick up a copy of the stuff on the vector frame
3383 if (i.hasVector()) {
3384 bReparse = true;
3385
3386 Frame overlayFrame = FrameIO.LoadFrame(i.getAbsoluteLink());
3387 Collection<Item> copies = ItemUtils.CopyItems(overlayFrame
3388 .getNonAnnotationItems(false), i.getVector());
3389 for (Item copy : copies) {
3390 FreeItems.getInstance().add(copy);
3391 copy.setEditTarget(i);
3392 copy.setFloating(true);
3393 copy.setParent(null);
3394 // copy.setHighlightMode(HighlightMode.Connected);
3395 }
3396 }
3397 }
3398 currentFrame.change();
3399
3400 _lastHighlightedItem = null;
3401 updateCursor();
3402
3403 // if there are multiple items in the list, determine which to use for
3404 // offset calculations
3405 if (toGrab.size() > 1) {
3406 for (Item i : toGrab) {
3407 // MIKE: Movement goes haywire if these are removed because Line
3408 // class returns 0 for getX
3409 if (!(i instanceof Line) && !(i instanceof XRayable)) {
3410 _offX = DisplayIO.getMouseX() - i.getX() + i.getOffset().x;
3411 _offY = getY() - i.getY() + i.getOffset().y;
3412
3413 // make the offset item the first item in the list (so
3414 // move method knows which item to use)
3415 FreeItems.getInstance().set(
3416 FreeItems.getInstance().indexOf(i),
3417 FreeItems.getInstance().get(0));
3418 FreeItems.getInstance().set(0, i);
3419 break;
3420 }
3421 }
3422
3423 move(FreeItems.getInstance());
3424 ItemUtils.EnclosedCheck(toGrab);
3425 // otherwise, just use the first item
3426 } else if (toGrab.size() == 1) {
3427 Item soleItem = toGrab.iterator().next();
3428 _offX = DisplayIO.getMouseX() - soleItem.getX()
3429 + soleItem.getOffset().x;
3430 _offY = getY() - soleItem.getY() + soleItem.getOffset().y;
3431 // Now call move so that if we are on a message in the message box
3432 // It doesnt appear up the top of the scree!!
3433 move(toGrab);
3434 } else {
3435 MessageBay
3436 .displayMessage("Insufficient permission to pickup the items");
3437 }
3438 if (bReparse)
3439 FrameUtils.Parse(currentFrame, false, false);
3440 else
3441 currentFrame.notifyObservers(bRecalculate);
3442
3443 FrameGraphics.Repaint();
3444 }
3445
3446 private static Line createLine() {
3447 Frame current = DisplayIO.getCurrentFrame();
3448 // create the two endpoints
3449 Item end = DisplayIO.getCurrentFrame().createDot();
3450 Item start = DisplayIO.getCurrentFrame().createDot();
3451
3452 // create the Line
3453 Line line = new Line(start, end, current.getNextItemID());
3454 line.autoArrowheadLength();
3455
3456 // anchor the start
3457 anchor(start);
3458
3459 // attach the line to the cursor
3460 pickup(end);
3461 _lastHighlightedItem = null;
3462
3463 // TODO figure out how to get the end to highlight
3464 // end.setSelectedMode(SelectedMode.Normal);
3465 // end.setSelectedMode(SelectedMode.None);
3466
3467 return line;
3468 }
3469
3470 /**
3471 * Returns a list of copies of the list passed in
3472 *
3473 * @param toCopy
3474 * The list of items to copy
3475 * @return A List of copied Items
3476 */
3477 private static List<Item> copy(Collection<Item> toCopy) {
3478 return ItemUtils.CopyItems(toCopy);
3479 }
3480
3481 public static void anchor(Item toAnchor, boolean checkEnclosure) {
3482 // Only anchor items we have full permission over... i.e. don't anchor vector items
3483 if (!toAnchor.hasPermission(UserAppliedPermission.full))
3484 return;
3485
3486 toAnchor.anchor();
3487
3488 if (checkEnclosure) {
3489 ItemUtils.EnclosedCheck(toAnchor.getParentOrCurrentFrame()
3490 .getItems());
3491 FrameGraphics.Repaint();
3492 }
3493 }
3494
3495 public static void anchor(Item toAnchor) {
3496 anchor(toAnchor, true);
3497 }
3498
3499 public static void anchor(Collection<Item> toAnchor) {
3500 boolean bReparse = false;
3501 boolean bRecalculate = false;
3502 // Need to make sure we check enclosure for overlays etc
3503 Set<Frame> checkEnclosure = new HashSet<Frame>();
3504
3505 // Create a clone of toAnchor since in the proccess of anchoring items
3506 // they can change the state of the toAnchor collection and thus create
3507 // concurrent modification exceptions.
3508 // This is especially needed for widgets being removed when anchored:
3509 // since they
3510 // currently are composed of 8 items this is vital. In the new revision
3511 // of
3512 // widgets being implemented as a single item this this can be
3513 // depreciated
3514 // however it may be useful for other applications.
3515 Collection<Item> toAnchorCopy = new ArrayList<Item>(toAnchor);
3516
3517 for (Item i : toAnchorCopy) {
3518 if (toAnchor.contains(i)) { // since to anchor could change while
3519 // anchoring
3520 // if (!i.hasVector())
3521 anchor(i, false);
3522 checkEnclosure.add(i.getParentOrCurrentFrame());
3523 bReparse |= i.hasOverlay();
3524 bRecalculate |= i.recalculateWhenChanged();
3525 }
3526 }
3527
3528 toAnchor.clear();
3529 // Check enclosure for all the frames of the items that were anchored
3530 for (Frame f : checkEnclosure) {
3531 // ItemUtils.EnclosedCheck(f.getItems());
3532 ItemUtils.Justify(f);
3533 }
3534
3535 Frame currentFrame = DisplayIO.getCurrentFrame();
3536 if (bReparse)
3537 FrameUtils.Parse(currentFrame, false, false);
3538 else {
3539 currentFrame.notifyObservers(bRecalculate);
3540 }
3541 FrameGraphics.Repaint();
3542 }
3543
3544 /*
3545 * private boolean mouseMovedRecently() { Date now = new Date();
3546 *
3547 * return now.getTime() - _lastMouseMovement.getTime() < 150; }
3548 */
3549
3550 public void mouseWheelMoved(MouseWheelEvent arg0) {
3551 Navigation.ResetLastAddToBack();
3552
3553 int clicks = Math.abs(arg0.getWheelRotation());
3554
3555 if (FreeItems.getInstance().size() == 2) {
3556 if ((FreeItems.getInstance().get(0).isLineEnd() && FreeItems
3557 .getInstance().get(1) instanceof Line)
3558 || (FreeItems.getInstance().get(1).isLineEnd() && FreeItems
3559 .getInstance().get(0) instanceof Line)) {
3560
3561 Line line;
3562 if (FreeItems.getInstance().get(0) instanceof Line)
3563 line = (Line) FreeItems.getInstance().get(0);
3564 else
3565 line = (Line) FreeItems.getInstance().get(1);
3566
3567 // User must do multiple clicks to toggle the line
3568 if (clicks >= MOUSE_WHEEL_THRESHOLD) {
3569 if (arg0.isShiftDown())
3570 line.toggleArrowHeadRatio(arg0.getWheelRotation());
3571 else
3572 line.toggleArrowHeadLength(arg0.getWheelRotation());
3573 // line.getParent().change();
3574 FrameGraphics.Repaint();
3575 }
3576 }
3577 } else if (arg0.getWheelRotation() != 0 && arg0.isShiftDown()) {
3578
3579 FunctionKey rotationType = FunctionKey.SizeUp;
3580 if (arg0.getWheelRotation() > 0) {
3581 rotationType = FunctionKey.SizeDown;
3582 }
3583
3584 FrameKeyboardActions.functionKey(rotationType, 1, arg0
3585 .isShiftDown(), arg0.isControlDown());
3586
3587 } else if (clicks >= MOUSE_WHEEL_THRESHOLD) {
3588 Item item = FrameUtils.getCurrentItem();
3589
3590 // if the user is not pointing to any item
3591 if (item == null) {
3592 FrameKeyboardActions.NextTextItem(null,
3593 arg0.getWheelRotation() > 0);
3594 return;
3595 }
3596
3597 if (item instanceof Line || item instanceof Circle) {
3598 // check permissions
3599 if (!item.hasPermission(UserAppliedPermission.full)) {
3600 MessageBay
3601 .displayMessage("Insufficient permission to edit the Line");
3602 return;
3603 }
3604 item.toggleDashed(arg0.getWheelRotation());
3605 item.getParent().change();
3606 FrameGraphics.Repaint();
3607 return;
3608 } else if (item instanceof Text) {
3609 FrameKeyboardActions.NextTextItem(item,
3610 arg0.getWheelRotation() > 0);
3611 }
3612 }
3613 }
3614
3615 /**
3616 *
3617 * @return the integer value for the last mouse button clicked.
3618 */
3619 public static int getLastMouseButton() {
3620 if (_lastMouseClick == null)
3621 return MouseEvent.NOBUTTON;
3622
3623 return _lastMouseClick.getButton();
3624 }
3625
3626 public static boolean isDelete(int modifiersEx) {
3627
3628 int onMask = MouseEvent.BUTTON3_DOWN_MASK
3629 | MouseEvent.BUTTON2_DOWN_MASK;
3630 return (modifiersEx & onMask) == onMask;
3631 }
3632
3633 public static boolean isGetAttributes(int modifiersEx) {
3634 int onMask = MouseEvent.BUTTON3_DOWN_MASK
3635 | MouseEvent.BUTTON1_DOWN_MASK;
3636 return (modifiersEx & onMask) == onMask;
3637 }
3638
3639 public static boolean isTwoClickNoOp(int modifiersEx) {
3640 int onMask = MouseEvent.BUTTON2_DOWN_MASK
3641 | MouseEvent.BUTTON1_DOWN_MASK;
3642 return (modifiersEx & onMask) == onMask;
3643 }
3644
3645 public static boolean wasDeleteClicked() {
3646 if (_lastMouseClick == null)
3647 return false;
3648 return isDelete(_lastMouseClickModifiers);
3649 }
3650
3651 public static void control(KeyEvent ke) {
3652 for (Item i : FreeItems.getInstance()) {
3653 i.invalidateCommonTrait(ItemAppearence.PreMoved);
3654 }
3655
3656 for (Item i : FreeItems.getCursor()) {
3657 i.invalidateCommonTrait(ItemAppearence.PreMoved);
3658 }
3659
3660 _controlDown = ke.isControlDown();
3661
3662 if (_controlDown) {
3663 // TODO why are these two lines needed?!?!
3664 // _offX = 0;
3665 // _offY = 0;
3666 } else {
3667 resetOffset();
3668 }
3669
3670 if (_mouseDown > 0 && _lastMouseDragged != null) {
3671 MouseEvent me = _lastMouseDragged;
3672 _lastMouseDragged = new MouseEvent(ke.getComponent(),
3673 MouseEvent.NOBUTTON, ke.getWhen(), ke.getModifiers(), me
3674 .getX(), me.getY(), 0, false);
3675 _instance.mouseDragged(_lastMouseDragged);
3676 } else if (_lastMouseMoved != null) {
3677 MouseEvent me = _lastMouseMoved;
3678 _lastMouseMoved = new MouseEvent(ke.getComponent(),
3679 MouseEvent.NOBUTTON, ke.getWhen(), ke.getModifiers(), me
3680 .getX(), me.getY(), 0, false);
3681 _instance.mouseMoved(_lastMouseMoved, true);
3682 }
3683 updateCursor();
3684
3685 Help.updateStatus();
3686
3687 for (Item i : FreeItems.getInstance()) {
3688 i.invalidateCommonTrait(ItemAppearence.PostMoved);
3689 }
3690
3691 for (Item i : FreeItems.getCursor()) {
3692 i.invalidateCommonTrait(ItemAppearence.PostMoved);
3693 }
3694 // TODO: Check why the new constrained line is not repainted immediately
3695 FrameGraphics.requestRefresh(true);
3696 FrameGraphics.refresh(true);
3697 }
3698
3699 public static int getX() {
3700 return Math.round(MouseX);
3701 }
3702
3703 public static int getY() {
3704 return Math.round(MouseY);
3705 }
3706
3707 public static Item getlastHighlightedItem() {
3708 return _lastHighlightedItem;
3709 }
3710
3711 public static Point getPosition() {
3712 return new Point(getX(), getY());
3713 }
3714
3715 public static Point getFreeItemsOffset() {
3716 return new Point(_offX, _offY);
3717 }
3718
3719 public static void shift(KeyEvent e) {
3720 _shiftDown = e.isShiftDown();
3721 Help.updateStatus();
3722 getInstance().refreshHighlights();
3723 }
3724}
Note: See TracBrowser for help on using the repository browser.