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

Last change on this file since 865 was 865, checked in by davidb, 10 years ago

Tidy up on some comment text

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