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

Last change on this file since 681 was 681, checked in by jts21, 10 years ago

Add correct status messages for cases where there are items on the cursor

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