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

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

Switch to using a link instead of an action for navigation buttons, also add a setting for whether a link should be added to history (to enable navigation buttons that don't generate history)

File size: 98.7 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 }
873
874 private static boolean doMerging(Item clicked) {
875 if (clicked == null)
876 return false;
877
878 // // Brook: widgets do not merge
879 // if (clicked instanceof WidgetCorner)
880 // return false;
881 //
882 // // Brook: widgets do not merge
883 // if (clicked instanceof WidgetEdge)
884 // return false;
885
886 // System.out.println(FreeItems.getInstance().size());
887 if (isRubberBandingCorner()) {
888 if (clicked.isLineEnd()
889 || clicked.getAllConnected().contains(
890 FreeItems.getItemAttachedToCursor())) {
891 return true;
892 }
893 }
894
895 if (FreeItems.getInstance().size() > 2)
896 return false;
897
898 Item attachedToCursor = FreeItems.getItemAttachedToCursor();
899
900 if (clicked instanceof Text
901 && !(attachedToCursor instanceof Text || attachedToCursor
902 .isLineEnd())) {
903 return false;
904 }
905
906 return true;
907 }
908
909 public static void middleButton() {
910 Item currentItem = FrameUtils.getCurrentItem();
911 getInstance().middleButton(currentItem,
912 FrameUtils.getCurrentItems(currentItem), false);
913 updateCursor();
914 }
915
916 public static void rightButton() {
917 Item currentItem = FrameUtils.getCurrentItem();
918 getInstance().rightButton(currentItem,
919 FrameUtils.getCurrentItems(currentItem));
920 updateCursor();
921 }
922
923 public static void leftButton() {
924 Item currentItem = FrameUtils.getCurrentItem();
925 getInstance().leftButton(currentItem,
926 FrameUtils.getCurrentItems(currentItem), false, false);
927 updateCursor();
928 }
929
930 /**
931 * This method handles all middle-click actions
932 */
933 private void middleButton(Item clicked, Collection<Item> clickedIn,
934 boolean isShiftDown) {
935
936 // If the user clicked into a widgets free space...
937 if (clicked == null && _lastClickedIn != null
938 && _lastClickedIn.size() >= 4) {
939
940 // Check to see if the use clicked into a widgets empty space
941 InteractiveWidget iw = null;
942
943 for (Item i : _lastClickedIn) {
944
945 if (i instanceof WidgetCorner) {
946 iw = ((WidgetCorner) i).getWidgetSource();
947 break;
948 } else if (i instanceof WidgetEdge) {
949 iw = ((WidgetEdge) i).getWidgetSource();
950 break;
951 }
952 }
953
954 if (iw != null) {
955
956 // Handle dropping items on widgets
957 if(iw.ItemsMiddleClickDropped()) {
958 return;
959 }
960 }
961 }
962 // if the cursor has Items attached
963 if (FreeItems.itemsAttachedToCursor()) {
964 // if the user is pointing at something, merge the items (if
965 // possible)
966 if (doMerging(clicked)) {
967 // check permissions
968 if (!clicked.hasPermission(UserAppliedPermission.full)) {
969 //Items on the message box have parent == null
970 if (clicked.getParent() != null) {
971 if (!clicked.isFrameName()) {
972 Item editTarget = clicked.getEditTarget();
973 if (editTarget != clicked
974 && editTarget
975 .hasPermission(UserAppliedPermission.full)) {
976 clicked = editTarget;
977 } else {
978 MessageBay
979 .displayMessage("Insufficient permission");
980 return;
981 }
982 }
983
984 } else /*Its in the message area*/ {
985 MessageBay.displayMessage("Insufficient permission");
986 return;
987 }
988 }
989 Item merger = FreeItems.getItemAttachedToCursor();
990 assert (merger != null);
991 Collection<Item> left = null;
992 // when anchoring a line end onto a text line end, holding shift
993 // prevents the line ends from being merged
994 if (isShiftDown) {
995 left = FreeItems.getInstance();
996 } else {
997 left = merge(FreeItems.getInstance(), clicked);
998 }
999 Collection<Item> toDelete = new LinkedList<Item>();
1000 toDelete.addAll(FreeItems.getInstance());
1001 toDelete.removeAll(left);
1002 anchor(left);
1003 FreeItems.getInstance().clear();
1004 DisplayIO.getCurrentFrame().removeAllItems(toDelete);
1005 updateCursor();
1006 // Make sure the dot goes away when anchoring a line end behind
1007 // a text line end
1008 if (isShiftDown) {
1009 refreshHighlights();
1010 }
1011 FrameGraphics.requestRefresh(true);
1012 return;
1013 // otherwise, anchor the items
1014 } else {
1015 if (clickedIn != null && FreeItems.getInstance().size() == 1) {
1016 Item item = FreeItems.getItemAttachedToCursor();
1017 if (item instanceof Text) {
1018 Text text = (Text) item;
1019 if (AttributeUtils.setAttribute(text, text, 2)) {
1020 clickedIn.removeAll(FrameUtils
1021 .getEnclosingLineEnds().iterator().next()
1022 .getAllConnected());
1023 for (Item i : clickedIn) {
1024 AttributeUtils.setAttribute(i, text);
1025 }
1026 FreeItems.getInstance().clear();
1027 }
1028 }
1029 }
1030
1031 // if a line is being rubber-banded, check for auto
1032 // straightening
1033 anchor(FreeItems.getInstance());
1034 FreeItems.getInstance().clear();
1035 updateCursor();
1036 _offX = _offY = 0;
1037 return;
1038 }
1039 // otherwise if the user is pointing at something, pick it up unless shift is down
1040 } else if (clicked != null && !isShiftDown) {
1041
1042 // check permissions
1043 if (!clicked.hasPermission(UserAppliedPermission.full)) {
1044 Item editTarget = clicked.getEditTarget();
1045 if (editTarget != clicked
1046 && editTarget.hasPermission(UserAppliedPermission.full)) {
1047 clicked = editTarget;
1048 } else {
1049 MessageBay
1050 .displayMessage("Insufficient permission to pick up item");
1051 return;
1052 }
1053 }
1054
1055 // BROOK: WIDGET RECTANGLES DONT ALLOW DISCONNECTION
1056 if (clicked instanceof Line && !(clicked instanceof WidgetEdge)) {
1057 // Check if within 20% of the end of the line
1058 Line l = (Line) clicked;
1059 Item toDisconnect = l.getEndPointToDisconnect(_lastMouseClick
1060 .getX(), _lastMouseClick.getY());
1061
1062 if (toDisconnect == null) {
1063 pickup(clicked);
1064 } else {
1065 if (toDisconnect.getHighlightMode() == Item.HighlightMode.Normal) {
1066 DisplayIO.setCursorPosition(toDisconnect.getPosition(),
1067 false);
1068 pickup(toDisconnect);
1069 } else {
1070 List<Line> lines = toDisconnect.getLines();
1071 // This is to remove constraints from single lines
1072 // with constraints...
1073 // ie. partially deleted rectangles
1074 if (lines.size() == 1) {
1075 toDisconnect.removeAllConstraints();
1076
1077 DisplayIO.setCursorPosition(toDisconnect
1078 .getPosition(), false);
1079 // This is to ensure the selected mode will be set
1080 // to Normal rather than disconnect when the line is
1081 // anchored
1082 toDisconnect
1083 .setHighlightMode(Item.HighlightMode.Normal);
1084 pickup(toDisconnect);
1085 } else {
1086 // If we are then detatch the line and pick up its
1087 // end point...
1088 Frame currentFrame = DisplayIO.getCurrentFrame();
1089 Item newPoint = null;
1090
1091 // If the point we are disconnecting is text...
1092 // Then we want to leave the text behind
1093 // And disconnect a point
1094 if (toDisconnect instanceof Text) {
1095 newPoint = new Dot(toDisconnect.getX(),
1096 toDisconnect.getY(), -1);
1097 Item.DuplicateItem(toDisconnect, newPoint);
1098 } else {
1099 newPoint = toDisconnect.copy();
1100 }
1101
1102 currentFrame.addItem(newPoint);
1103 // remove the current item from the connected
1104 // list for this item
1105 l.replaceLineEnd(toDisconnect, newPoint);
1106 // remove unneeded constrains
1107 newPoint.removeAllConstraints();
1108
1109 // Set the new points mode to normal before picking
1110 // it up so it will be restored correctly when
1111 // anchored
1112 newPoint
1113 .setHighlightMode(Item.HighlightMode.Normal);
1114 toDisconnect
1115 .setHighlightMode(Item.HighlightMode.None);
1116 DisplayIO.setCursorPosition(toDisconnect
1117 .getPosition(), false);
1118 pickup(newPoint);
1119 ItemUtils.EnclosedCheck(toDisconnect
1120 .getParentOrCurrentFrame().getItems());
1121 }
1122 }
1123 }
1124 } else {
1125 if (clicked.isLineEnd()) {
1126 DisplayIO.setCursorPosition(clicked.getPosition(), false);
1127 }
1128 pickup(clicked);
1129 }
1130 // if we're inside a shape, pick it up unless shift is down
1131 } else if (clickedIn != null && !isShiftDown) {
1132 ArrayList<Item> toPickup = new ArrayList<Item>(clickedIn.size());
1133 for (Item ip : clickedIn)
1134 if (ip.hasPermission(UserAppliedPermission.full))
1135 toPickup.add(ip);
1136 pickup(toPickup);
1137 // otherwise the user is creating a line
1138 } else {
1139 Item on = FrameUtils.onItem(DisplayIO.getCurrentFrame(), Math
1140 .round(MouseX), Math.round(MouseY), true);
1141 // If we have permission to copy this item then pick it up
1142 if (on != null && on.isLineEnd()
1143 && on.hasPermission(UserAppliedPermission.full)) {
1144 on.removeAllConstraints();
1145 pickup(on);
1146 return;
1147 }
1148
1149 if (on instanceof WidgetEdge) {
1150 // Dont allow the user to break widget edges.
1151 // Note: had to return here because random dots would
1152 // appear otherwise... cannot understand code below
1153 // with create line.
1154 return;
1155 }
1156
1157 // if its on a line then split the line and put a point on it and
1158 // pick that point up. Only if it is not a widget line
1159 if (on instanceof Line && on.hasPermission(UserAppliedPermission.full)) {
1160 Frame current = DisplayIO.getCurrentFrame();
1161 // create the two endpoints
1162 Line oldLine = (Line) on;
1163 Item newPoint = oldLine.getStartItem().copy();
1164 newPoint.setPosition(MouseX, MouseY);
1165
1166 Item end = oldLine.getEndItem();
1167 // create the Line
1168 Line newLine = new Line(newPoint, end, current.getNextItemID());
1169 oldLine.replaceLineEnd(end, newPoint);
1170 newPoint.removeAllConstraints();
1171 pickup(newPoint);
1172 // Update the stats
1173 Collection<Item> created = new LinkedList<Item>();
1174 created.add(newPoint);
1175 created.add(newLine);
1176 SessionStats.CreatedItems(newLine.getAllConnected());
1177 return;
1178 }
1179 Line newLine = createLine();
1180 SessionStats.CreatedItems(newLine.getAllConnected());
1181 return;
1182 }
1183 SessionStats.MovedItems(FreeItems.getInstance());
1184 }
1185
1186 private static Item getFirstFreeLineEnd() {
1187 for (Item i : FreeItems.getInstance())
1188 if (i.isLineEnd())
1189 return i;
1190 return null;
1191 }
1192
1193 private static boolean isRubberBandingCorner() {
1194 return getShapeCorner(FreeItems.getInstance()) != null;
1195 }
1196
1197 /**
1198 * Gets the rectangle corner from the list of items that are part of a
1199 * rectangle.
1200 *
1201 * @param partialRectangle
1202 * a corner and its two connecting lines.
1203 * @return the rectangle corner or null if the list of items is not part of
1204 * a rectangle.
1205 */
1206 private static Item getShapeCorner(List<Item> partialRectangle) {
1207 if (partialRectangle.size() < 3)
1208 return null;
1209 Item lineEnd = null;
1210 // only one lineEnd will be present for rectangles
1211 // All other items must be lines
1212 for (Item i : partialRectangle) {
1213 if (i.isLineEnd()) {
1214 if (lineEnd == null) {
1215 lineEnd = i;
1216 } else {
1217 return null;
1218 }
1219 } else if (!(i instanceof Line)) {
1220 return null;
1221 }
1222 }
1223 // if this is at least the corner of two connected lines
1224 if (lineEnd != null && lineEnd.getAllConnected().size() >= 5)
1225 return lineEnd;
1226
1227 return null;
1228 }
1229
1230 /**
1231 * This method handles all right-click action
1232 */
1233 private void rightButton(Item clicked, Collection<Item> clickedIn) {
1234
1235 // If the user clicked into a widgets free space...
1236 if (clicked == null && _lastClickedIn != null
1237 && _lastClickedIn.size() >= 4) {
1238
1239 // Check to see if the use clicked into a widgets empty space
1240 InteractiveWidget iw = null;
1241
1242 for (Item i : _lastClickedIn) {
1243
1244 if (i instanceof WidgetCorner) {
1245 iw = ((WidgetCorner) i).getWidgetSource();
1246 break;
1247 } else if (i instanceof WidgetEdge) {
1248 iw = ((WidgetEdge) i).getWidgetSource();
1249 break;
1250 }
1251 }
1252
1253 if (iw != null) {
1254
1255 // Handle dropping items on widgets
1256 if(iw.ItemsRightClickDropped()) {
1257 return;
1258 }
1259 }
1260 }
1261
1262 // if the cursor has Items attached, then anchor a copy of them
1263
1264 List<Item> copies = null;
1265 if (FreeItems.itemsAttachedToCursor()) {
1266 if (FreeItems.getInstance().size() == 1
1267 && FreeItems.getItemAttachedToCursor().isAutoStamp()) {
1268 // Dont stamp if the user is painting... because we dont want to
1269 // save any of the items created!
1270 return;
1271 // if the user is clicking on something, merge the items
1272 // unless it is a point onto somethin other than a lineEnd or a
1273 // dot
1274 } else if (clicked != null
1275 // TODO Change the items merge methods so the logic is simplified
1276 && (!(FreeItems.getItemAttachedToCursor() instanceof Dot)
1277 || clicked instanceof Dot || clicked.isLineEnd())) {
1278 // check permissions
1279 if (!clicked.hasPermission(UserAppliedPermission.full)
1280 && clicked.getParent().getNameItem() != clicked) {
1281 MessageBay
1282 .displayMessage("Insufficient permission to merge items");
1283 return;
1284 }
1285 if (clicked instanceof Text || clicked instanceof Dot
1286 || clicked instanceof XRayable) {
1287 if (isRubberBandingCorner()) {
1288 // Move the cursor so that the copy is exactly the
1289 // same as the shape that was anchored
1290 DisplayIO.setCursorPosition(clicked.getPosition());
1291 Item d = getFirstFreeLineEnd();
1292 // get a copy of all enclosed items before merging
1293 // lineEnds
1294 Collection<Item> items = FrameUtils.getItemsEnclosedBy(
1295 DisplayIO.getCurrentFrame(), d
1296 .getEnclosedShape());
1297 // If its not an enclosed shape then pick up the
1298 // connected shape
1299 if (items == null || items.size() == 0) {
1300 items = d.getAllConnected();
1301 } else {
1302 // For some reason the item that was clicked ends up
1303 // in the enclosure and needs to be removed
1304 items.removeAll(clicked.getConnected());
1305 // the item that was the origin of the enclosed
1306 // shape used to create the enclosure does not get
1307 // returned from getItemsEnclosedBy to the enclosure
1308 // so it must be added
1309 items.addAll(d.getConnected());
1310 }
1311
1312 Collection<Item> toCopy = new LinkedHashSet<Item>();
1313
1314 for (Item ip : items) {
1315 if (ip.hasPermission(UserAppliedPermission.copy))
1316 toCopy.add(ip);
1317 }
1318 copies = copy(toCopy);
1319 // Now do the merging
1320 Collection<Item> remain = merge(
1321 FreeItems.getInstance(), clicked);
1322 // anchor the points
1323 anchor(remain);
1324 FreeItems.getInstance().clear();
1325 pickup(copies);
1326 // line onto something
1327 } else if (FreeItems.getInstance().size() == 2
1328 /* && clicked instanceof XRayable */) {
1329 copies = ItemUtils.UnreelLine(FreeItems.getInstance(),
1330 _controlDown);
1331 Collection<Item> leftOver = merge(FreeItems
1332 .getInstance(), clicked);
1333 anchor(leftOver);
1334 if (copies == null)
1335 copies = copy(FreeItems.getInstance());
1336 FreeItems.getInstance().clear();
1337 for (Item i : copies)
1338 i.setOffset(0, 0);
1339 // need to move to prevent cursor dislocation
1340 move(copies);
1341 pickup(copies);
1342 // point onto point
1343 } else if (FreeItems.getInstance().size() == 1) {
1344 copies = copy(FreeItems.getInstance());
1345 Collection<Item> remain = merge(copies, clicked);
1346
1347 // ignore items that could not be merged.
1348 anchor(remain);
1349 } else {
1350 stampItemsOnCursor(true);
1351 copies = FreeItems.getInstance();
1352 }
1353 } else {
1354 copies = ItemUtils.UnreelLine(FreeItems.getInstance(),
1355 _controlDown);
1356 if (copies == null)
1357 copies = copy(FreeItems.getInstance());
1358 for (Item i : copies) {
1359 i.setOffset(0, 0);
1360 }
1361 anchor(FreeItems.getInstance());
1362 FreeItems.getInstance().clear();
1363 pickup(copies);
1364 }
1365 // otherwise, anchor the items
1366 } else {
1367 // check if this is anchoring a rectangle
1368 if (isRubberBandingCorner()) {
1369 Item d = getFirstFreeLineEnd();
1370 // anchor the points
1371 anchor(FreeItems.getInstance());
1372 FreeItems.getInstance().clear();
1373 updateCursor();
1374 // pick up a copy of all enclosed items
1375 Collection<Item> enclosedItems = FrameUtils
1376 .getItemsEnclosedBy(DisplayIO.getCurrentFrame(), d
1377 .getEnclosedShape());
1378 if (enclosedItems != null) {
1379 enclosedItems.removeAll(d.getAllConnected());
1380 Collection<Item> toCopy = getFullyEnclosedItems(enclosedItems);
1381
1382 if (toCopy.size() > 0) {
1383 // Find the closest item to the mouse cursor
1384 double currentX = DisplayIO.getMouseX();
1385 double currentY = FrameMouseActions.getY();
1386 Item closest = null;
1387 double shortestDistance = Double.MAX_VALUE;
1388 for (Item next : toCopy) {
1389 if (next instanceof Line)
1390 continue;
1391 double distance = Point.distance(currentX,
1392 currentY, next.getX(), next.getY());
1393 if (distance < shortestDistance) {
1394 shortestDistance = distance;
1395 closest = next;
1396 }
1397 }
1398 // Move the cursor to closest item
1399 DisplayIO.setCursorPosition(closest.getPosition());
1400 // Pickup copy of the stuff inside the rectange
1401 copies = copy(toCopy);
1402 pickup(copies);
1403 // Remove the rectangle
1404 d.getParentOrCurrentFrame().removeAllItems(
1405 d.getAllConnected());
1406 } else {
1407 // Pick up a copy of the rectangle
1408 copies = copy(d.getAllConnected());
1409 pickup(copies);
1410 }
1411 }
1412 } else {
1413 if (rubberBanding()) {
1414 if (clicked != null) {
1415 Collection<Item> leftOver = merge(FreeItems
1416 .getInstance(), clicked);
1417 anchor(leftOver);
1418 }
1419 // This is executed when the user is putting down a line
1420 // endpoint and unreeling. ie. Normal unreeling
1421 copies = ItemUtils.UnreelLine(FreeItems.getInstance(),
1422 _controlDown);
1423
1424 if (copies == null)
1425 copies = copy(FreeItems.getInstance());
1426 anchor(FreeItems.getInstance());
1427 for (Item i : copies)
1428 i.setOffset(0, 0);
1429 // need to move to prevent cursor dislocation
1430 move(copies);
1431 pickup(copies);
1432 } else if (_extrude) {
1433 List<Item> originals = new ArrayList<Item>();
1434 // remove any lines that dont have both endpoints
1435 // floating
1436 for (Item i : FreeItems.getInstance()) {
1437 if (i.isFloating())
1438 originals.add(i);
1439 }
1440 if (copies == null)
1441 copies = ItemUtils.CopyItems(originals, _extrude);
1442 for (Item i : copies)
1443 i.setOffset(0, 0);
1444 anchor(FreeItems.getInstance());
1445 // Move isnt working right for extruding!!
1446 // move(copies);
1447 pickup(copies);
1448 } else {
1449 stampItemsOnCursor(true);
1450 copies = FreeItems.getInstance();
1451 }
1452 }
1453 }
1454 } else {
1455 // if the user is pointing at something and shift isn't down, make a copy
1456 if (clicked != null && !isShiftDown()) {
1457 // check permissions
1458 if (clicked.isLineEnd()) {
1459 if (!clicked.hasPermission(UserAppliedPermission.full)) {
1460 MessageBay
1461 .displayMessage("Insufficient permission to unreel");
1462 return;
1463 }
1464 } else if (!clicked.hasPermission(UserAppliedPermission.copy)) {
1465 Item editTarget = clicked.getEditTarget();
1466 if (editTarget != clicked
1467 && editTarget.hasPermission(UserAppliedPermission.copy)) {
1468 clicked = editTarget;
1469 } else {
1470 MessageBay
1471 .displayMessage("Insufficient permission to copy");
1472 return;
1473 }
1474 }
1475
1476 copies = ItemUtils.UnreelLine(clicked, _controlDown);
1477 // Copies will NOT be null if the user right clicked on a point
1478 if (copies == null) {
1479 Collection<Item> originals = clicked.getConnected();
1480 copies = ItemUtils.CopyItems(originals, _extrude);
1481 // if this is the title of the frame, link it to the frame
1482 if (originals.size() == 1 && copies.size() == 1) {
1483 Item copy = copies.get(0);
1484 Item original = originals.iterator().next();
1485 if (original.getLink() == null
1486 && original.isFrameTitle()) {
1487 // save the frame after copying
1488 // i.getParent().setChanged(true);
1489 copy.setLink(original.getParentOrCurrentFrame()
1490 .getName());
1491 }
1492 }
1493
1494 FrameGraphics.changeHighlightMode(clicked,
1495 HighlightMode.None);
1496
1497 if (!_extrude)
1498 clearParent(copies);
1499 }
1500
1501 pickup(copies);
1502 } else {
1503 // if user is pointing in a closed shape and shift isn't down, make a copy of the items inside
1504 if (clickedIn != null && !isShiftDown()) {
1505 // Set the selection mode for the items that were clicked in
1506 Collection<Item> enclosed = getFullyEnclosedItems(clickedIn);
1507 if (enclosed.size() == 0) {
1508 MessageBay
1509 .displayMessage("Insufficient permission to copy items");
1510 } else {
1511 copies = copy(enclosed);
1512 clearParent(copies);
1513 pickup(copies);
1514 for (Item i : clickedIn) {
1515 i.setHighlightMode(HighlightMode.None);
1516 }
1517 }
1518 // otherwise, create a rectangle
1519 } else {
1520 Item on = FrameUtils.onItem(DisplayIO.getCurrentFrame(),
1521 MouseX, MouseY, true);
1522 // if its on a line then create a line from that line
1523 if (on instanceof Line && on.hasPermission(UserAppliedPermission.full)) {
1524
1525 Line onLine = (Line) on;
1526 Line newLine = onLine.copy();
1527 Item end = newLine.getEndItem();
1528 Item start = newLine.getStartItem();
1529 end.setPosition(MouseX, MouseY);
1530 start.setPosition(MouseX, MouseY);
1531 onLine.autoArrowheadLength();
1532 // anchor the start
1533 anchor(start);
1534 // attach the line to the cursor
1535 pickup(end);
1536
1537 List<Item> toMerge = new LinkedList<Item>();
1538 toMerge.add(newLine.getStartItem());
1539 toMerge.add(newLine);
1540
1541 // Make sure the highlighting is shown when the end is
1542 // anchored
1543 end.setHighlightMode(Item.HighlightMode.Normal);
1544 merge(toMerge, on);
1545 // anchor(left);
1546 // FreeItems.getInstance().clear();
1547 FrameGraphics.Repaint();
1548 updateCursor();
1549 return;
1550 }
1551
1552 copies = new ArrayList<Item>();
1553 Item[] d = new Item[RECTANGLE_CORNERS];
1554 // create dots
1555 Frame current = DisplayIO.getCurrentFrame();
1556 for (int i = 0; i < d.length; i++) {
1557 d[i] = current.createDot();
1558 copies.add(d[i]);
1559 }
1560
1561 current.nextDot();
1562
1563 // create lines
1564 copies.add(new Line(d[0], d[1], current.getNextItemID()));
1565 copies.add(new Line(d[1], d[2], current.getNextItemID()));
1566 copies.add(new Line(d[2], d[3], current.getNextItemID()));
1567 copies.add(new Line(d[3], d[0], current.getNextItemID()));
1568
1569 new Constraint(d[0], d[1], current.getNextItemID(),
1570 Constraint.HORIZONTAL);
1571 new Constraint(d[2], d[3], current.getNextItemID(),
1572 Constraint.HORIZONTAL);
1573 new Constraint(d[1], d[2], current.getNextItemID(),
1574 Constraint.VERTICAL);
1575 new Constraint(d[3], d[0], current.getNextItemID(),
1576 Constraint.VERTICAL);
1577
1578 anchor(new ArrayList<Item>(copies));
1579 pickup(d[3]);
1580 d[3].setHighlightMode(HighlightMode.Normal);
1581
1582 SessionStats.CreatedItems(copies);
1583 copies.clear();
1584 }
1585 }
1586 }
1587 getInstance().refreshHighlights();
1588 SessionStats.CopiedItems(copies);
1589 updateCursor();
1590 FrameGraphics.Repaint();
1591 }
1592
1593 /**
1594 *
1595 */
1596 private static void stampItemsOnCursor(boolean save) {
1597 List<Item> copies = copy(FreeItems.getInstance());
1598 // MIKE: what does the below 2 lines do?
1599 for (Item i : copies) {
1600 i.setOffset(0, 0);
1601 i.setSave(save);
1602 }
1603 // The below code has a little problem withflicker when stamp
1604 // and dragging
1605 move(FreeItems.getInstance());
1606 for (Item i : copies) {
1607 i.setHighlightMode(HighlightMode.None);
1608 }
1609 anchor(copies);
1610 }
1611
1612 /**
1613 * @param enclosedItems
1614 * @return
1615 */
1616 private static Collection<Item> getFullyEnclosedItems(
1617 Collection<Item> enclosure) {
1618 // copy the enclosedItems because the list will be modified
1619 Collection<Item> enclosedItems = new LinkedHashSet<Item>(enclosure);
1620 Collection<Item> toCopy = new LinkedHashSet<Item>(enclosedItems.size());
1621
1622 while (enclosedItems.size() > 0) {
1623 Item i = enclosedItems.iterator().next();
1624 if (i.hasPermission(UserAppliedPermission.copy)) {
1625 Collection<Item> items = i.getAllConnected();
1626 // Only copy if the entire shape is enclosed
1627 if (enclosedItems.containsAll(items)) {
1628 toCopy.addAll(items);
1629 }
1630 enclosedItems.removeAll(items);
1631 } else {
1632 enclosedItems.remove(i);
1633 }
1634 }
1635 return toCopy;
1636 }
1637
1638 /**
1639 * Marks the items as not belonging to any specific frame. When picking up
1640 * items the parent will be automatically cleared for items on the current
1641 * frame but not for overlay items. This method ensures that overlay items
1642 * will also be cleared. This is useful when picking up copies of items from
1643 * an overlay (with the right mouse button) to ensure that the copy will be
1644 * anchored on the current frame rather than the overlay. When items are
1645 * picked up with the middle button clearParent should NOT be called.
1646 *
1647 * @param items
1648 * to have their parent cleared
1649 */
1650 private static void clearParent(List<Item> items) {
1651 for (Item i : items) {
1652 // The next line is only necessary for circles...
1653 // Need to clean up/refactory some of this stuff
1654 i.getParentOrCurrentFrame().removeItem(i);
1655 i.setParent(null);
1656 }
1657 }
1658
1659 public void mouseEntered(MouseEvent e) {
1660 }
1661
1662 public void mouseExited(MouseEvent e) {
1663 }
1664
1665 private boolean _overFrame;
1666 private int panStartX, panStartY;
1667 private boolean _isPanOp;
1668 public void mouseDragged(MouseEvent e) {
1669 _lastMouseDragged = e;
1670 // System.out.println("MouseDragged");
1671
1672 // Stop the longDepress mouse timer if the user drags above a threshold
1673 if (_MouseTimer.isRunning()) {
1674 if (Math.abs(e.getX() - _lastMouseClick.getX())
1675 + Math.abs(e.getY() - _lastMouseClick.getY()) > 10)
1676 _MouseTimer.stop();
1677 }
1678
1679 if (_autoStamp) {
1680 stampItemsOnCursor(false);
1681 }
1682
1683 /*
1684 * Have the free items follow the cursor if the user clicks in freespace
1685 * then moves.
1686 */
1687 if (FreeItems.getInstance().size() > 0 && _lastClickedOn == null) {
1688 mouseMoved(e);
1689 return;
1690 }
1691
1692 // panning the frame when dragging the mouse while shift-leftclicking
1693 if(ExperimentalFeatures.MousePan.get() && _overFrame && e.isShiftDown() &&
1694 (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0 &&
1695 (_isPanOp || (Math.max(Math.abs(panStartX - e.getX()), Math.abs(panStartY - e.getY())) > 5))) {
1696 int dX = (int) (e.getX() - MouseX);
1697 int dY = (int) (e.getY() - MouseY);
1698 Misc.pan(DisplayIO.getCurrentFrame(), dX, dY);
1699 MouseX = e.getX();
1700 MouseY = e.getY();
1701 _isPanOp = true;
1702 }
1703
1704 // check if user is dragging across a text item
1705 if (_lastRanged != null) {
1706 // System.out.println(MouseY - e.getY());
1707
1708 MouseX = e.getX();
1709 MouseY = e.getY();
1710
1711 int distance = _lastRanged.getY() - FrameMouseActions.getY();
1712 if (distance <= 0)
1713 distance = FrameMouseActions.getY() - _lastRanged.getY()
1714 - _lastRanged.getBoundsHeight();
1715
1716 if (distance > UserSettings.NoOpThreshold.get()) {
1717 _lastRanged.clearSelectionEnd();
1718 _isNoOp = true;
1719 } else {
1720 // update the ranged section
1721 _lastRanged.setSelectionEnd(DisplayIO.getMouseX(),
1722 FrameMouseActions.getY());
1723 _isNoOp = false;
1724 }
1725
1726 DisplayIO.setTextCursor(_lastRanged, Text.NONE, false, e
1727 .isShiftDown(), e.isControlDown(), false);
1728 FrameGraphics.Repaint();
1729 return;
1730 }
1731
1732 // if the user is dragging across a picture
1733 if (_lastCropped != null) {
1734 // If shift is down then the distance moved is the same in the x and
1735 // y
1736 MouseX = e.getX();
1737 MouseY = e.getY();
1738
1739 if (e.isControlDown()) {
1740 int deltaX = Math.abs(e.getX() - _lastMouseClick.getX());
1741 int deltaY = Math.abs(e.getY() - _lastMouseClick.getY());
1742 if (deltaX > deltaY) {
1743 MouseY = _lastMouseClick.getY() + deltaX
1744 * (e.getY() > _lastMouseClick.getY() ? 1 : -1);
1745 } else {
1746 MouseX = _lastMouseClick.getX() + deltaY
1747 * (e.getX() > _lastMouseClick.getX() ? 1 : -1);
1748 }
1749 }
1750 // update the ranged section
1751 _lastCropped.setEndCrop(DisplayIO.getMouseX(), FrameMouseActions
1752 .getY());
1753
1754 FrameGraphics.Repaint();
1755 return;
1756 }
1757
1758 /*
1759 * This is the context of a user clicking in freespace an dragging onto
1760 * the edge of a line
1761 */
1762 if ((_mouseDown == MouseEvent.BUTTON2 || _mouseDown == MouseEvent.BUTTON3)
1763 && _lastClickedOn == null && _lastClickedIn == null) {
1764 Item on = FrameUtils.onItem(DisplayIO.getCurrentFrame(), e.getX(),
1765 e.getY(), true);
1766
1767 if (FreeItems.getInstance().size() == 0) {
1768 // if the user can spot-weld, show the virtual spot
1769 if (on instanceof Line) {
1770 Line line = (Line) on;
1771 line.showVirtualSpot(e.getX(), e.getY());
1772 }
1773 if (on != null && on.isLineEnd()) {
1774 _lastHighlightedItem = on;
1775 on.setHighlightMode(Item.HighlightMode.Normal);
1776 } else if (_lastHighlightedItem != null) {
1777 _lastHighlightedItem
1778 .setHighlightMode(Item.HighlightMode.None);
1779 _lastHighlightedItem = null;
1780 }
1781 }
1782 }
1783
1784 // Use the below calculation for better speed. If it causes problems
1785 // switch back to the Euclidean distance calculation
1786 if (Math.abs(MouseX - e.getX()) > UserSettings.NoOpThreshold.get()
1787 || Math.abs(MouseY - e.getY()) > UserSettings.NoOpThreshold.get())
1788 _isNoOp = true;
1789
1790 FrameGraphics.Repaint();
1791 }
1792
1793 private static MouseEvent _lastMouseMoved = null;
1794
1795 private static Integer LastRobotX = null;
1796
1797 private static Integer LastRobotY = null;
1798
1799 // For some reason... sometimes the mouse move gets lost when moving the
1800 // mouse really quickly after clicking...
1801 // Use this timer to make sure it gets reset eventually if the Robot
1802 // generated event never arrives.
1803 private static Timer _RobotTimer = new Timer(200, new ActionListener() {
1804 public void actionPerformed(ActionEvent ae) {
1805 _RobotTimer.stop();
1806 LastRobotX = null;
1807 LastRobotY = null;
1808 // System.out.println("RobotTimer");
1809 }
1810 });
1811
1812 private static Timer _autoStampTimer = new Timer(200, new ActionListener() {
1813 public void actionPerformed(ActionEvent ae) {
1814 stampItemsOnCursor(false);
1815 }
1816 });
1817
1818 private static boolean _controlDown;
1819
1820 private static boolean _shiftDown;
1821
1822 public static boolean isControlDown() {
1823 return _controlDown;
1824 }
1825
1826 public static boolean isShiftDown() {
1827 return _shiftDown;
1828 }
1829
1830 public static void setLastRobotMove(float x, float y) {
1831 // Make sure the system is in the right state while waiting for the
1832 // Robots event to arrive.
1833 MouseX = x;
1834 MouseY = y;
1835 // System.out.println("MouseMoved: " + MouseX + "," + MouseY + " " +
1836 // System.currentTimeMillis());
1837 LastRobotX = Math.round(x);
1838 LastRobotY = Math.round(y);
1839 _RobotTimer.start();
1840 }
1841
1842 public static boolean isWaitingForRobot() {
1843 return LastRobotX != null;
1844 }
1845
1846 /**
1847 * Updates the stored mouse position and highlights any items as necessary.
1848 */
1849 public void mouseMoved(MouseEvent e) {
1850 mouseMoved(e, false);
1851 }
1852
1853 private void mouseMoved(MouseEvent e, boolean shiftStateChanged) {
1854 // System.out.println("mouseMoved");
1855 // System.out.println(_context);
1856 if (_context == CONTEXT_FREESPACE)
1857 FrameKeyboardActions.resetEnclosedItems();
1858 // System.out.println(e.getX() + "," + e.getY() + " " + e.getWhen());
1859 if (LastRobotX != null) {
1860 // Wait until the last Robot mouse move event arrives before
1861 // processing other events
1862 if (/* FreeItems.getInstance().size() == 0 || */
1863 (LastRobotX == e.getX() && LastRobotY == e.getY())) {
1864 LastRobotX = null;
1865 LastRobotY = null;
1866 _RobotTimer.stop();
1867 } else {
1868 // System.out.println("Ignored: " +
1869 // FreeItems.getInstance().size());
1870 return;
1871 }
1872 }
1873
1874 MouseX = e.getX();
1875 MouseY = e.getY();
1876
1877 Help.updateStatus();
1878
1879 // System.out.println(MouseX + "," + MouseY);
1880
1881 // Moving the mouse a certain distance removes the last edited text if
1882 // it is empty
1883 Text lastEdited = FrameUtils.getLastEdited();
1884 if (lastEdited != null && lastEdited.getText().length() == 0
1885 && lastEdited.getPosition().distance(e.getPoint()) > 20) {
1886 FrameUtils.setLastEdited(null);
1887 }
1888
1889 // If shift is down then the movement is constrained
1890 if (_controlDown && FreeItems.getInstance().size() > 0) {
1891 // Check if we are rubber banding a line
1892 if (shiftStateChanged && rubberBanding()) {
1893 // Get the line end that is being rubber banded
1894 Item thisEnd = FreeItems.getInstance().get(0).isLineEnd() ? FreeItems
1895 .getInstance().get(0)
1896 : FreeItems.getInstance().get(1);
1897 Line line = (Line) (FreeItems.getInstance().get(0).isLineEnd() ? FreeItems
1898 .getInstance().get(1)
1899 : FreeItems.getInstance().get(0));
1900 Item otherEnd = line.getOppositeEnd(thisEnd);
1901 int deltaX = Math.abs(e.getX() - otherEnd.getX());
1902 int deltaY = Math.abs(e.getY() - otherEnd.getY());
1903 // Check if its a vertical line
1904 if (deltaX < deltaY / 2) {
1905 // otherEnd.setX(thisEnd.getX());
1906 // MouseX = otherEnd.getX();
1907 if (shiftStateChanged) {
1908 new Constraint(thisEnd, otherEnd, thisEnd
1909 .getParentOrCurrentFrame().getNextItemID(),
1910 Constraint.VERTICAL);
1911 }
1912 }
1913 // Check if its horizontal
1914 else if (deltaY <= deltaX / 2) {
1915 // MouseY = otherEnd.getY();
1916 // otherEnd.setY(thisEnd.getY());
1917 if (shiftStateChanged) {
1918 new Constraint(thisEnd, otherEnd, thisEnd
1919 .getParentOrCurrentFrame().getNextItemID(),
1920 Constraint.HORIZONTAL);
1921 }
1922 } else {
1923 // Add DIAGONAL constraints
1924 // if (deltaX > deltaY) {
1925 // otherEnd.setY(thisEnd.getY() + deltaX
1926 // * (e.getY() < otherEnd.getY() ? 1 : -1));
1927 // } else {
1928 // otherEnd.setX(thisEnd.getX() + deltaY
1929 // * (e.getX() < otherEnd.getX() ? 1 : -1));
1930 // }
1931 if (shiftStateChanged) {
1932 int constraint = Constraint.DIAGONAL_NEG;
1933 // Check if the slope is positive
1934 if ((thisEnd.getY() - otherEnd.getY())
1935 / (double) (thisEnd.getX() - otherEnd.getX()) > 0.0) {
1936 constraint = Constraint.DIAGONAL_POS;
1937 }
1938
1939 new Constraint(thisEnd, otherEnd, thisEnd
1940 .getParentOrCurrentFrame().getNextItemID(),
1941 constraint);
1942 }
1943 }
1944 }// If its a lineend attached to two lines lengthen the shorter
1945 // so it is the same length as the longer line
1946 else if (FreeItems.getInstance().size() == 3) {
1947 // check if we are rubber banding the corner of a shape
1948 Item thisEnd = getShapeCorner(FreeItems.getInstance());
1949 if (thisEnd != null) {
1950 Line line1 = thisEnd.getLines().get(0);
1951 Line line2 = thisEnd.getLines().get(1);
1952 // Check if the two lines are constrained and hence it is a
1953 // rectangle
1954 Integer c1 = line1.getPossibleConstraint();
1955 Integer c2 = line2.getPossibleConstraint();
1956
1957 if (c1 != null && c2 != null) {
1958 // This is the case of a constrained rectangle
1959 if ((c2 == Constraint.VERTICAL || c2 == Constraint.HORIZONTAL)
1960 && (c1 == Constraint.VERTICAL || c1 == Constraint.HORIZONTAL)
1961 && (c1 != c2)) {
1962 Line vLine = line2;
1963 Line hLine = line1;
1964 if (c1 == Constraint.VERTICAL) {
1965 vLine = line1;
1966 hLine = line2;
1967 }
1968 Item hOtherEnd = hLine.getOppositeEnd(thisEnd);
1969 Item vOtherEnd = vLine.getOppositeEnd(thisEnd);
1970
1971 double vLength = Math
1972 .abs(vOtherEnd.getY() - MouseY);
1973 double hLength = Math
1974 .abs(hOtherEnd.getX() - MouseX);
1975
1976 if (vLength > hLength) {
1977 MouseX = Math.round(hOtherEnd.getX() + vLength
1978 * (MouseX > hOtherEnd.getX() ? 1 : -1));
1979 } else /* if (hLength > vLength) */{
1980 MouseY = Math.round(vOtherEnd.getY() + hLength
1981 * (MouseY > vOtherEnd.getY() ? 1 : -1));
1982 }
1983 }
1984 // } else if (c2 != null) {
1985 //
1986 // } // Other wise it is a not constrained shape so
1987 // constrain
1988 // the two lines lengths to be equal
1989 } else {
1990 Item lineEnd1 = line1.getOppositeEnd(thisEnd);
1991 Item lineEnd2 = line2.getOppositeEnd(thisEnd);
1992 double l1 = Line.getLength(lineEnd1.getPosition(), e
1993 .getPoint());
1994 double l2 = Line.getLength(lineEnd2.getPosition(), e
1995 .getPoint());
1996 double l3 = Line.getLength(lineEnd1.getPosition(),
1997 lineEnd2.getPosition());
1998 // l1 needs to be the shorter end
1999 if (l1 > l2) {
2000 Item temp = lineEnd1;
2001 lineEnd1 = lineEnd2;
2002 lineEnd2 = temp;
2003 double tempL = l1;
2004 l1 = l2;
2005 l2 = tempL;
2006 }
2007 // Now use the cosine rule to calculate the angle
2008 // between l1 and l3
2009 double cosTheta = (l1 * l1 + l3 * l3 - l2 * l2)
2010 / (2 * l1 * l3);
2011 // now calculate the new length for the lines using cos
2012 // rule
2013 double l_new = l3 / (2 * cosTheta);
2014 double ratio = l_new / l1;
2015 MouseX = Math.round((e.getX() - lineEnd1.getX())
2016 * ratio)
2017 + lineEnd1.getX();
2018 MouseY = Math.round((e.getY() - lineEnd1.getY())
2019 * ratio)
2020 + lineEnd1.getY();
2021
2022 }
2023 }
2024 }
2025 } else if (shiftStateChanged && !_controlDown && rubberBanding()) {
2026 // Get the line end that is being rubber banded
2027 Item thisEnd = FreeItems.getInstance().get(0).isLineEnd() ? FreeItems
2028 .getInstance().get(0)
2029 : FreeItems.getInstance().get(1);
2030 thisEnd.removeAllConstraints();
2031 }
2032
2033 if (_lastMouseMoved == null)
2034 _lastMouseMoved = e;
2035
2036 _lastMouseMoved = e;
2037
2038 refreshHighlights();
2039
2040 if (FreeItems.hasCursor()) {
2041 move(FreeItems.getCursor(), true);
2042 }
2043
2044 if (FreeItems.itemsAttachedToCursor()) {
2045 move(FreeItems.getInstance());
2046 // System.out.println(FreeItems.getInstance().size());
2047 }
2048
2049 if (_forceArrowCursor)
2050 updateCursor();
2051
2052 _forceArrowCursor = true;
2053 }
2054
2055 public void refreshHighlights() {
2056 // ByMike: Get the item the mouse is hovering over
2057 Item click = FrameUtils.getCurrentItem();
2058 Item on = null;
2059 // System.out.println(click);
2060 if (click != null) {
2061 on = click;
2062 // set the context
2063 if (on instanceof Line)
2064 _context = CONTEXT_AT_LINE;
2065 else if (on instanceof Dot)
2066 _context = CONTEXT_AT_DOT;
2067 else if (on instanceof Text) {
2068 _context = CONTEXT_AT_TEXT;
2069 }
2070 if (FreeItems.getInstance().size() > 0)
2071 _alpha = 60;
2072 else
2073 _alpha = -1;
2074 } else {
2075 _context = CONTEXT_FREESPACE;
2076 _alpha = -1;
2077 }
2078
2079 // if the user is pointing at an item, highlight it
2080 if (on != null && !FreeItems.getInstance().contains(on)) {
2081 // if the user can spot-weld, show the virtual spot
2082 if (FreeItems.getInstance().size() == 2 && on instanceof Line) {
2083 Line line = (Line) on;
2084 Item freeItem0 = FreeItems.getInstance().get(0);
2085 Item freeItem1 = FreeItems.getInstance().get(1);
2086 Item lineEnd = freeItem0.isLineEnd() ? freeItem0 : (freeItem1
2087 .isLineEnd() ? freeItem1 : null);
2088 if (lineEnd != null) {
2089 if (_mouseDown == 0)
2090 line.showVirtualSpot(lineEnd, DisplayIO.getMouseX(),
2091 FrameMouseActions.getY());
2092 } else
2093 // The user is pointing at another point or text item
2094 // etc
2095 FrameGraphics.changeHighlightMode(on,
2096 Item.HighlightMode.Normal);
2097 } else {
2098 // FrameGraphics.ChangeSelectionMode(on,
2099 // Item.SelectedMode.Connected);
2100 // TODO: The method below is for the most part redundant
2101 on = FrameGraphics.Highlight(on.getEditTarget());
2102 }
2103 // if the last item highlighted is still highlighted, clear it
2104 if (_lastHoldsHighlight) {
2105 _lastHoldsHighlight = false;
2106 for (Item i : DisplayIO.getCurrentFrame().getItems())
2107 if (i.isHighlighted() && i != on)
2108 FrameGraphics.changeHighlightMode(i,
2109 Item.HighlightMode.None);
2110 }
2111
2112 // if the user is not pointing at an item, check for enclosure
2113 // highlighting
2114 } else if (on == null) {
2115 Collection<Item> enclosure = FrameUtils.getEnclosingLineEnds();
2116 if (enclosure != null && enclosure.size() > 0) {
2117 Item firstLineEnd = enclosure.iterator().next();
2118 HighlightMode hm;
2119 if(isShiftDown()) {
2120 hm = HighlightMode.Connected;
2121 } else {
2122 hm = HighlightMode.Enclosed;
2123 }
2124 if (firstLineEnd.getLines().size() > 1 &&
2125 // check that the enclosure is not part of a point being
2126 // dragged in space
2127 !ContainsOneOf(enclosure, FreeItems.getInstance())) {
2128 on = firstLineEnd.getLines().get(0);
2129 // System.out.println(on == null ? "Null" :
2130 // on.toString());
2131 FrameGraphics.changeHighlightMode(on, hm);
2132 } else if (firstLineEnd instanceof XRayable) {
2133 on = firstLineEnd;
2134 FrameGraphics.changeHighlightMode(firstLineEnd, hm);
2135 }
2136 _context = CONTEXT_AT_ENCLOSURE;
2137 } else if (_lastHighlightedItem != null) {
2138 // System.out.println("LastHighlightedItem");
2139 _lastHoldsHighlight = false;
2140 }
2141 }
2142
2143 // disable cursor changes when the cursor has items attached
2144 if (FreeItems.itemsAttachedToCursor()
2145 && DisplayIO.getCursor() != Item.TEXT_CURSOR)
2146 _forceArrowCursor = false;
2147
2148 // setLastHighlightedItem(on);
2149
2150 if (_lastHighlightedItem != null && _lastHighlightedItem != on
2151 && !_lastHoldsHighlight) {
2152 // Turn off the highlighting only if
2153 // the last highlighted item is not connected to the currentItem
2154 // Otherwise we get flickering in transition from connected to
2155 // normal mode while moving the cursor along a line.
2156 if (on == null
2157 || (!on.getAllConnected().contains(_lastHighlightedItem))) {
2158 FrameGraphics.changeHighlightMode(_lastHighlightedItem,
2159 Item.HighlightMode.None);
2160 }
2161 }
2162
2163 _lastHighlightedItem = on;
2164
2165 }
2166
2167 private boolean ContainsOneOf(Collection<Item> enclosure,
2168 Collection<Item> freeItems) {
2169 if (freeItems == null)
2170 return false;
2171 for (Item i : freeItems) {
2172 if (enclosure.contains(i))
2173 return true;
2174 }
2175 return false;
2176 }
2177
2178 /**
2179 * Checks if lines are being rubber banded.
2180 *
2181 * @return true if the user is rubberBanding one or more lines
2182 */
2183 private static boolean rubberBanding() {
2184 if (FreeItems.getInstance().size() != 2) {
2185 return false;
2186 }
2187
2188 // if rubber-banding, there will be 1 lineend and the rest will be lines
2189 boolean foundLineEnd = false;
2190 for (Item i : FreeItems.getInstance()) {
2191 if (i.isLineEnd()) {
2192 if (foundLineEnd) {
2193 return false;
2194 }
2195 foundLineEnd = true;
2196 } else if (!(i instanceof Line) || !i.isVisible()) {
2197 return false;
2198 }
2199 }
2200 return true;
2201 }
2202
2203 /**
2204 * Updates the current mouse cursor to whatever it should be. i.e. Hidden
2205 * when rubber-banding lines, otherwise default (arrow)
2206 */
2207 public static void updateCursor() {
2208 if (rubberBanding()) {
2209 DisplayIO.setCursor(Item.HIDDEN_CURSOR);
2210 return;
2211 }
2212 // This is to make sure the TEXT_CURSOR doesnt get inadvertantly turned
2213 // off!
2214 Item on = FrameUtils.getCurrentItem();
2215 if (on != null && on instanceof Text) {
2216 return;
2217 }
2218 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2219 }
2220
2221 public static void setHighlightHold(boolean hold) {
2222 _lastHoldsHighlight = hold;
2223 }
2224
2225 public static void resetOffset() {
2226 if (FreeItems.itemsAttachedToCursor()) {
2227 _offX = DisplayIO.getMouseX()
2228 - FreeItems.getInstance().get(0).getX()
2229 + FreeItems.getInstance().get(0).getOffset().x;
2230 _offY = getY() - FreeItems.getInstance().get(0).getY()
2231 + FreeItems.getInstance().get(0).getOffset().y;
2232 }
2233 }
2234
2235 /**
2236 * Moves the items to the current mouse position (plus the current offset)
2237 *
2238 * @param toMove
2239 */
2240 static void move(Collection<Item> toMove) {
2241 move(toMove, false);
2242 }
2243
2244 static void move(Collection<Item> toMove, boolean cursor) {
2245
2246 // Gets the origin of the first item to move
2247 int xPos = (DisplayIO.getMouseX() - (cursor ? 0 : _offX));
2248
2249 Item firstDot = toMove.iterator().next();
2250
2251 int deltax = firstDot.getX() - xPos;
2252 int deltay = firstDot.getY() - (getY() - (cursor ? 0 : _offY));
2253
2254 for (Item move : toMove) {
2255 move.setPosition(move.getX() - deltax, move.getY() - deltay);
2256
2257 if (!cursor && move instanceof Text) {
2258 ((Text) move).setAlpha(_alpha);
2259 }
2260 }
2261
2262 FrameGraphics.requestRefresh(true);
2263 // FrameGraphics.refresh(true);
2264 }
2265
2266 private static void load(String toLoad, boolean addToHistory) {
2267 if (FrameIO.isValidFrameName(toLoad)) {
2268 DisplayIO.clearBackedUpFrames();
2269 FrameUtils.DisplayFrame(toLoad, addToHistory, true);
2270 } else {
2271 MessageBay.errorMessage(toLoad + " is not a valid frame name.");
2272 }
2273 }
2274
2275 private static void back() {
2276 DisplayIO.Back();
2277
2278 // repaint things if necessary
2279 if (FreeItems.itemsAttachedToCursor())
2280 move(FreeItems.getInstance());
2281
2282 if (FreeItems.hasCursor())
2283 move(FreeItems.getCursor(), true);
2284 }
2285
2286 private static void forward() {
2287 DisplayIO.Forward();
2288
2289 // repaint things if necessary
2290 if (FreeItems.itemsAttachedToCursor())
2291 move(FreeItems.getInstance());
2292
2293 if (FreeItems.hasCursor())
2294 move(FreeItems.getCursor(), true);
2295 }
2296
2297 /**
2298 * Returns true if the mouse moved during TDFC. This will happen if there is
2299 * a start annotation item on the frame.
2300 *
2301 * @param linker
2302 * @return
2303 */
2304 public static boolean tdfc(Item linker) throws RuntimeException {
2305 // if this is a non-usable item
2306 if (linker.getID() < 0)
2307 return false;
2308
2309 // Check if its an image that can be resized to fit a box
2310 // around it
2311 String text = linker.getText();
2312 boolean isVector = text.equals("@v") || text.equals("@av");
2313 boolean isFrameImage = text.equals("@f");
2314 boolean isBitmap = false; // text.equals("@b");
2315
2316 if (isVector || isFrameImage || isBitmap) {
2317 Collection<Item> enclosure = FrameUtils.getEnclosingLineEnds(linker
2318 .getPosition());
2319 if (enclosure != null) {
2320 for (Item i : enclosure) {
2321 if (i.isLineEnd() && i.isEnclosed()) {
2322 if (!isVector)
2323 DisplayIO.getCurrentFrame().removeAllItems(
2324 enclosure);
2325 Rectangle rect = i.getEnclosedRectangle();
2326 long width = Math.round(rect.getWidth());
2327 if (isVector) {
2328 NumberFormat nf = Vector.getNumberFormatter();
2329 linker.setText(linker.getText()
2330 + ": "
2331 + nf.format((width / FrameGraphics
2332 .getMaxFrameSize().getWidth())));
2333 } else {
2334 linker.setText(linker.getText() + ": " + width);
2335 }
2336
2337 linker.setPosition(new Point(rect.x, rect.y));
2338 linker.setThickness(i.getThickness());
2339 linker.setBorderColor(i.getColor());
2340 break;
2341 }
2342 }
2343 if (!isVector)
2344 FrameMouseActions.deleteItems(enclosure, false);
2345 }
2346 }
2347
2348 boolean mouseMoved;
2349
2350 linker.getParent().setChanged(true);
2351
2352 Frame next = FrameIO.CreateNewFrame(linker);
2353
2354 linker.setLink("" + next.getNumber());
2355
2356 for (Item i : next.getTextItems()) {
2357 // Set the link for @Parent annotation item if one
2358 if (ItemUtils.startsWithTag(i, ItemUtils.TAG_PARENT)
2359 && i.getLink() == null) {
2360 Frame parent = linker.getParentOrCurrentFrame();
2361 i.setLink(parent.getName());
2362 } else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_BACKUP, false)) {
2363 // Delink backup tag if it is on the frame
2364 i.setLink(null);
2365 }
2366 }
2367
2368 FrameUtils.DisplayFrame(next, true, true);
2369 FrameUtils.setTdfcItem(linker);
2370
2371 mouseMoved = next.moveMouseToDefaultLocation();
2372 // this needs to be done if the user doesnt move the mouse before doing
2373 // tdfc while the cursor is set to the text cursor
2374 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2375 // This needs to be done in case there was a @start on the frame which
2376 // triggers changed to be set to true when it should stay as false
2377 next.setChanged(false);
2378 return mouseMoved;
2379 }
2380
2381 /**
2382 * Creates a new Text item and fills it with particular attributes extracted
2383 * from the given Item. Note: Users always have permission to extract
2384 * attributes, so it is not checked.
2385 *
2386 * @param toExtract
2387 * Item containing the Item to extract the attributes from.
2388 */
2389 private static void extractAttributes(Item toExtract) {
2390 if (toExtract == null || toExtract == null)
2391 return;
2392
2393 if (FreeItems.itemsAttachedToCursor())
2394 return;
2395
2396 Item attribs;
2397 Item item = toExtract;
2398 // Extract the frames attributes when the user clicks on the frame name
2399 FrameGraphics.changeHighlightMode(item, HighlightMode.None);
2400 if (item.isFrameName())
2401 attribs = AttributeUtils.extractAttributes(item.getParent());
2402 else {
2403 attribs = AttributeUtils.extractAttributes(item);
2404 }
2405
2406 if (attribs == null)
2407 MessageBay
2408 .displayMessage("All attributes of that item are default values.");
2409 else {
2410 // Give the attribute text item the color of the item for which
2411 // attributes are being extracted.
2412 // attribs.setColor(item.getColor());
2413 pickup(attribs);
2414 }
2415 }
2416
2417 public static void delete(Item toDelete) {
2418 boolean bRecalculate = false;
2419
2420 FrameUtils.setLastEdited(null);
2421 _offX = _offY = 0;
2422
2423 Frame currentFrame = DisplayIO.getCurrentFrame();
2424 // check if the user is pointing at the frame's framename
2425 if (toDelete != null && toDelete == currentFrame.getNameItem()) {
2426 currentFrame.clear(false);
2427 FrameGraphics.Repaint();
2428 return;
2429 }
2430
2431 // if the user is deleting items attached to the cursor
2432 if (FreeItems.itemsAttachedToCursor()) {
2433 // if this is an item-swap
2434 if (FreeItems.getInstance().size() == 1
2435 && FreeItems.getInstance().get(0) instanceof Text
2436 && toDelete != null && toDelete instanceof Text) {
2437
2438 // check permissions
2439 if (!toDelete.hasPermission(UserAppliedPermission.full)) {
2440 MessageBay
2441 .displayMessage("Insufficient permission to swap Item text");
2442 return;
2443 }
2444 Text anchored = (Text) toDelete;
2445 Text free = (Text) FreeItems.getInstance().get(0);
2446 SessionStats.DeletedItem(free);
2447 // List<String> temp = anchored.getText();
2448 anchored.setText(free.getText());
2449 anchored.setFormula(free.getFormula());
2450
2451 // free.setTextList(temp);
2452 FreeItems.getInstance().clear();
2453
2454 anchored.getParent().setChanged(true);
2455
2456 bRecalculate |= free.recalculateWhenChanged();
2457 bRecalculate |= anchored.recalculateWhenChanged();
2458
2459 // update the offset since the text has changed
2460 _offX = DisplayIO.getMouseX() - anchored.getX()
2461 + anchored.getOffset().x;
2462 _offY = getY() - anchored.getY() + anchored.getOffset().y;
2463 } else {
2464 // if shift is pressed delete the entire shape attached to the dot
2465 if(isShiftDown()) {
2466 List<Item> tmp = new ArrayList<Item>(FreeItems.getInstance());
2467 for(Item i : tmp) {
2468 // remove entire rectangles instead of just the corner
2469 if(i instanceof Dot) {
2470 FreeItems.getInstance().addAll(i.getAllConnected());
2471 for(Item j : i.getAllConnected()) {
2472 if(j instanceof Dot) {
2473 FreeItems.getInstance().addAll(j.getLines());
2474 }
2475 }
2476 }
2477 }
2478 }
2479 deleteItems(FreeItems.getInstance());
2480 }
2481 // reset the mouse cursor
2482 updateCursor();
2483 // the user is not pointing at an item
2484 } else if (toDelete == null) {
2485
2486 // if the user is pointing inside a closed shape, delete it
2487
2488 Collection<Item> items = null;
2489 // if shift is down, only delete the enclosing shape (ignore the items inside)
2490 if(isShiftDown()) {
2491 Collection<Item> tmp = FrameUtils.getEnclosingLineEnds();
2492 if(tmp != null) {
2493 items = new ArrayList<Item>();
2494 items.addAll(tmp);
2495 for(Item i : tmp) {
2496 if(i instanceof Dot) {
2497 items.addAll(((Dot)i).getLines());
2498 }
2499 }
2500 }
2501 } else {
2502 items = FrameUtils.getCurrentItems(null);
2503 }
2504
2505 if (items != null) {
2506 Collection<Item> toRemove = new LinkedHashSet<Item>(items
2507 .size());
2508 for (Item ip : items) {
2509 if (ip.hasPermission(UserAppliedPermission.full)) {
2510 // Only include lines if one of their enpoints are also
2511 // being removed
2512 if (ip instanceof Line) {
2513 Line l = (Line) ip;
2514 Item end = l.getEndItem();
2515 Item start = l.getStartItem();
2516
2517 // If one end of a line is being delted, remove the
2518 // other end if all its connecting lines are being
2519 // delted
2520 if (items.contains(end)) {
2521 if (!items.contains(start)
2522 && items.containsAll(start.getLines())) {
2523 toRemove.add(start);
2524 }
2525 } else if (items.contains(start)) {
2526 if (items.containsAll(end.getLines())) {
2527 toRemove.add(end);
2528 }
2529 } else {
2530 continue;
2531 }
2532 }
2533 toRemove.add(ip);
2534 }
2535 }
2536
2537 deleteItems(toRemove);
2538
2539 // reset the mouse cursor
2540 updateCursor();
2541 FrameGraphics.Repaint();
2542
2543 // otherwise this is an undo command
2544 } else {
2545 DisplayIO.getCurrentFrame().undo();
2546 }
2547 return;
2548 // this is a delete command
2549 } else {
2550 // check permissions
2551 if (!toDelete.hasPermission(UserAppliedPermission.full)) {
2552 Item editTarget = toDelete.getEditTarget();
2553 if (editTarget != toDelete
2554 && editTarget.hasPermission(UserAppliedPermission.full)) {
2555 toDelete = editTarget;
2556 } else {
2557 MessageBay
2558 .displayMessage("Insufficient permission to delete item");
2559 return;
2560 }
2561 }
2562
2563 Frame parent = toDelete.getParent();
2564 if (parent != null) {
2565 parent.setChanged(true);
2566 }
2567 Collection<Item> toUndo = null;
2568 if (toDelete.isLineEnd()) {
2569 // delete the entire connected shape if shift is down
2570 if(isShiftDown()) {
2571 List<Item> tmp = new ArrayList<Item>();
2572 tmp.add(toDelete);
2573 // remove entire shape instead of just the corner
2574 tmp.addAll(toDelete.getAllConnected());
2575 for(Item j : toDelete.getAllConnected()) {
2576 if(j instanceof Dot) {
2577 tmp.addAll(j.getLines());
2578 }
2579 }
2580 deleteItems(tmp);
2581 return;
2582 } else {
2583 toUndo = deleteLineEnd(toDelete);
2584 }
2585 // delete the entire connected shape if shift is down, unless we're hovering the end of the line
2586 } else if (toDelete instanceof WidgetEdge) { // must notify
2587 // widgets that they
2588 // are being deleted
2589 ((WidgetEdge) toDelete).getWidgetSource().onDelete();
2590 toUndo = toDelete.getConnected();
2591 } else if (toDelete instanceof Line && isShiftDown() ||
2592 toDelete.getHighlightMode() == Item.HighlightMode.Disconnect) {
2593 Line line = (Line) toDelete;
2594 Item start = line.getStartItem();
2595 Item end = line.getEndItem();
2596 Collection<Item> delete = new LinkedList<Item>();
2597 delete.add(toDelete);
2598 if (end.getLines().size() == 1) {
2599 delete.add(end);
2600 } else {
2601 end.removeLine(line);
2602 }
2603 if (start.getLines().size() == 1) {
2604 delete.add(start);
2605 } else {
2606 start.removeLine(line);
2607 }
2608 toUndo = delete;
2609 } else {
2610 bRecalculate |= toDelete.recalculateWhenChanged();
2611 toUndo = toDelete.getConnected(); // copy(toDelete.getConnected());
2612 }
2613 SessionStats.DeletedItems(toUndo);
2614 if (parent != null) {
2615 parent.addAllToUndo(toUndo);
2616 parent.removeAllItems(toUndo); // toDelete.getConnected()
2617 }
2618 // reset the mouse cursor
2619 updateCursor();
2620 if (parent != null)
2621 ItemUtils.EnclosedCheck(parent.getItems());
2622 if (toDelete.hasOverlay()) {
2623 FrameUtils.Parse(parent, false, false);
2624 FrameGraphics.requestRefresh(false);
2625 }
2626
2627 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2628
2629 }
2630
2631 currentFrame.notifyObservers(bRecalculate);
2632
2633 FrameGraphics.Repaint();
2634 }
2635
2636 public static void deleteItems(Collection<Item> itemList) {
2637 deleteItems(itemList, true);
2638 }
2639
2640 public static void deleteItems(Collection<Item> itemList, boolean addToUndo) {
2641 boolean bReparse = false;
2642 boolean bRecalculate = false;
2643
2644 SessionStats.DeletedItems(itemList);
2645 List<Frame> modifiedFrames = new LinkedList<Frame>();
2646 // Get a list of all the modified frames
2647 for (Item i : itemList) {
2648 Frame parent = i.getParent();
2649 if (parent != null)
2650 modifiedFrames.add(parent);
2651 i.setHighlightMode(HighlightMode.None);
2652 bReparse |= i.hasOverlay();
2653 bRecalculate |= i.recalculateWhenChanged();
2654 }
2655 // If they are all free items then add the current frame
2656 if (modifiedFrames.size() == 0) {
2657 modifiedFrames.add(DisplayIO.getCurrentFrame());
2658 }
2659
2660 Collection<Item> toUndo = new LinkedHashSet<Item>();
2661 // disconnect any connected items
2662 for (Item i : itemList) {
2663
2664 // Only delete the current item if have not already deleted.
2665 // This is especially important for heavy duty widgets - so they
2666 // do not have to expire several times per delete.
2667 if (toUndo.contains(i))
2668 continue;
2669
2670 // Make sure text items attached to cursor are reset back to the
2671 // transparency they should have.
2672 if (i instanceof Text) {
2673 ((Text) i).setAlpha(-1);
2674 }
2675
2676 if (i.getLines().size() > 0) {
2677
2678 Collection<Item> toDelete = deleteLineEnd(i);
2679 if (addToUndo) {
2680 // add the copied items to the undo stack
2681 for (Item itemToUndo : toDelete) {
2682
2683 if (!toUndo.contains(itemToUndo))
2684 toUndo.add(itemToUndo);
2685
2686 }
2687 }
2688 } else if (!toUndo.contains(i)) {
2689 if (addToUndo)
2690 toUndo.add(i); // Why was is this a copy
2691 }
2692 }
2693
2694 for (Frame f : modifiedFrames) {
2695 f.removeAllItems(itemList);
2696 ItemUtils.EnclosedCheck(f.getItems());
2697 }
2698 // TODO: How should undelete deal with undo when items are removed from
2699 // the current frame as well as the overlay frame
2700 Frame currentFrame = DisplayIO.getCurrentFrame();
2701 currentFrame.addAllToUndo(itemList);
2702 itemList.clear();
2703 if (bReparse) {
2704 FrameUtils.Parse(currentFrame, false, false);
2705 /*
2706 * TODO check if I need to recalculate even if reparse occurs, here
2707 * and in anchor, pickup etc
2708 */
2709 } else {
2710 currentFrame.notifyObservers(bRecalculate);
2711 }
2712
2713 }
2714
2715 private static Collection<Item> deleteLineEnd(Item lineEnd) {
2716
2717 if (lineEnd instanceof WidgetCorner) { // Brook
2718
2719 WidgetCorner wc = (WidgetCorner) lineEnd;
2720 Frame parent = wc.getWidgetSource().getParentFrame();
2721
2722 // Remove from the parent frame
2723 if (parent != null) {
2724 parent.removeAllItems(wc.getWidgetSource().getItems());
2725 }
2726
2727 wc.getWidgetSource().onDelete(); // Changes the widgets
2728 // corner/edges ID's...
2729
2730 return wc.getWidgetSource().getItems();
2731
2732 } else {
2733
2734 // // create a backup copy of the dot and its lines
2735 // List<Item> copy = copy(lineEnd.getConnected());
2736 //
2737 // // Remove lines from their anchored dots
2738 // // note: the line is kept so that it can be properly restored
2739 // for (Item ic : copy) {
2740 // if (ic instanceof Line) {
2741 // Line line = (Line) ic;
2742 // // Remove the line from the item that is not the copy of the
2743 // // line end being deletedF
2744 // if (!copy.contains(line.getStartItem()))
2745 // line.getStartItem().removeLine(line);
2746 // if (!copy.contains(line.getEndItem()))
2747 // line.getEndItem().removeLine(line);
2748 // }
2749 // }
2750
2751 Collection<Item> copy = lineEnd.getConnected();
2752
2753 // remove all lines being deleted
2754 for (Item ic : lineEnd.getConnected()) {
2755 if (ic instanceof Line
2756 && ((Line) ic).getOppositeEnd(lineEnd) != null) {
2757 Line line = (Line) ic;
2758
2759 // Invalidate the line to make sure we dont get any ghost
2760 // arrowheads.
2761 ic.invalidateAll();
2762
2763 Item d = line.getOppositeEnd(lineEnd);
2764 d.removeLine(line);
2765
2766 // if the dot was only part of one line, it can be
2767 // removed
2768 if (d.getLines().size() == 0) {
2769 if (d.getParent() != null)
2770 d.getParent().removeItem(d);
2771 if (!copy.contains(d))
2772 copy.add(d);
2773 }
2774
2775 if (lineEnd.getParent() != null)
2776 lineEnd.getParent().removeItem(ic);
2777 }
2778 }
2779 return copy;
2780 }
2781 }
2782
2783 private static void removeAllLinesExcept(Item from, Item but) {
2784 List<Line> lines = new LinkedList<Line>();
2785 lines.addAll(from.getLines());
2786 for (Line line : lines)
2787 if (line.getOppositeEnd(from) != but)
2788 from.removeLine(line);
2789
2790 List<Constraint> consts = new LinkedList<Constraint>();
2791 consts.addAll(from.getConstraints());
2792 for (Constraint c : consts)
2793 if (c.getOppositeEnd(from) != but)
2794 from.removeConstraint(c);
2795 }
2796
2797 public static Collection<Item> merge(List<Item> merger, Item mergee) {
2798 assert (mergee != null);
2799 if (mergee.isFrameName()) {
2800 return mergee.getParent().merge(merger);
2801 }
2802
2803 // if(mergee instanceof XRayable)
2804 // return merger;
2805
2806 // check for rectangle merging
2807 if (merger.size() == 3 && mergee.getLines().size() == 2) {
2808 Item corner = getShapeCorner(merger);
2809 // if this is a corner of a shape
2810 if (corner != null) {
2811 Collection<Item> allConnected = corner.getAllConnected();
2812 // Check if we are collapsing a rectangle
2813 if (allConnected.size() == 8 && allConnected.contains(mergee)) {
2814 DisplayIO.setCursorPosition(mergee.getPosition());
2815 DisplayIO.getCurrentFrame().removeAllItems(allConnected);
2816
2817 // find the point opposite corner...
2818 Item opposite = null;
2819 List<Line> lines = corner.getLines();
2820 for (Line l : lines) {
2821 allConnected.remove(l.getOppositeEnd(corner));
2822 }
2823 allConnected.remove(corner);
2824 for (Item i : allConnected) {
2825 if (i.isLineEnd()) {
2826 opposite = i;
2827 break;
2828 }
2829 }
2830 assert (opposite != null);
2831
2832 // check if the rectangle is small enough that it should be
2833 // collapsed to a single point
2834 int x1 = Math.abs(opposite.getX() - mergee.getX());
2835 int x2 = Math.abs(opposite.getY() - mergee.getY());
2836 int distance = (int) Math.sqrt(Math.pow(x1, 2)
2837 + Math.pow(x2, 2));
2838
2839 if (distance < RECTANGLE_TO_POINT_THRESHOLD) {
2840 mergee.removeAllConstraints();
2841 mergee.removeAllLines();
2842 mergee.setThickness(4 * mergee.getThickness());
2843 return mergee.getAllConnected();
2844 } else {
2845 removeAllLinesExcept(mergee, opposite);
2846 removeAllLinesExcept(opposite, mergee);
2847
2848 return mergee.getAllConnected();
2849 }
2850 }
2851 }
2852 }
2853
2854 List<Item> remain = new ArrayList<Item>();
2855 Item res = null;
2856
2857 for (Item i : merger) {
2858 if (!i.isVisible())
2859 continue;
2860 // check for link merging
2861 if (i instanceof Text
2862 && FrameIO.isValidFrameName((((Text) i).getFirstLine()))
2863 && FrameIO.canAccessFrame((((Text) i).getFirstLine()))) {
2864 // check that we can actually access the frame this link
2865 // corresponds to
2866 mergee.setLink(((Text) i).getFirstLine());
2867 } else {
2868 // check for attribute merging
2869 if (i instanceof Text && !i.isLineEnd()) {
2870 Text txt = (Text) i;
2871
2872 // if this is not an attribute merge
2873 if (!AttributeUtils.setAttribute(mergee, txt)) {
2874 // set mouse position for text merges
2875 if (mergee instanceof Text) {
2876 ((Text) mergee).insertText(txt.getText(), DisplayIO
2877 .getMouseX(), FrameMouseActions.getY());
2878 //Delete the item which had its text merged
2879 txt.delete();
2880 return remain;
2881 } else if (mergee instanceof WidgetCorner) {
2882 if (merger.size() == 1 && txt.getLink() != null) {
2883 // If the text item is linked then use that
2884 ((WidgetCorner) mergee).setLink(txt
2885 .getAbsoluteLink(), txt);
2886 } else {
2887 remain.addAll(merger);
2888 }
2889 return remain;
2890 } else if (mergee instanceof WidgetEdge) {
2891 if (merger.size() == 1 && txt.getLink() != null) {
2892 // If the text item is linked then use that
2893 ((WidgetEdge) mergee).setLink(txt
2894 .getAbsoluteLink(), txt);
2895 } else {
2896 remain.addAll(merger);
2897 }
2898 return remain;
2899 } else if (mergee instanceof Dot) {
2900 DisplayIO.setCursorPosition(mergee.getPosition());
2901 txt.setPosition(mergee.getPosition());
2902 txt.setThickness(mergee.getThickness());
2903 Frame parent = mergee.getParent();
2904 parent.removeItem(mergee);
2905 anchor(txt);
2906 // change the end points of the lines to the text
2907 // item
2908 while (mergee.getLines().size() > 0) {
2909 Line l = mergee.getLines().get(0);
2910 l.replaceLineEnd(mergee, txt);
2911 }
2912 break;
2913 }
2914
2915 // TODO tidy this up...
2916 // Dot override doesnt use the x and y coords
2917 // Text does... but could be removed
2918 res = mergee.merge(i, DisplayIO.getMouseX(), getY());
2919 if (res != null) {
2920 remain.add(res);
2921 }
2922 }
2923 } else {
2924 if (mergee.isLineEnd()) {
2925 DisplayIO.setCursorPosition(mergee.getPosition());
2926 }
2927 // Moving the cursor ensures shapes are anchored correctly
2928 res = mergee.merge(i, DisplayIO.getMouseX(), getY());
2929 if (res != null)
2930 remain.addAll(res.getConnected());
2931
2932 }
2933 }
2934 }
2935 updateCursor();
2936
2937 mergee.getParent().setChanged(true);
2938
2939 ItemUtils.EnclosedCheck(mergee.getParent().getItems());
2940 // Mike: Why does parse frame have to be called?!?
2941 FrameUtils.Parse(mergee.getParent());
2942
2943 return remain;
2944 }
2945
2946 /**
2947 * Picks up an item on a frame.
2948 *
2949 * @param toGrab
2950 * item to be picked up
2951 * @param removeItem
2952 * true if the item should be removed from the frame
2953 */
2954 public static void pickup(Item toGrab) {
2955 if (toGrab.isFrameName())
2956 return;
2957
2958 if (!toGrab.hasPermission(UserAppliedPermission.full)) {
2959 if (toGrab.getEditTarget() != toGrab) {
2960 pickup(toGrab.getEditTarget());
2961 } else {
2962 MessageBay
2963 .displayMessage("Insufficient permission pickup the item");
2964 }
2965 return;
2966 }
2967
2968 if (toGrab instanceof Circle)
2969 toGrab.setHighlightMode(HighlightMode.Connected);
2970 // Dont set the highlight mode if a vector is being picked up
2971 else if (toGrab.isVisible()) {
2972 toGrab.setHighlightMode(HighlightMode.Normal);
2973 }
2974
2975 // Brook: If the widget corner is being picked up. Instead refer to
2976 // picking up the edge for fixed-sized widgets so it is not so confusing
2977 if (toGrab instanceof WidgetCorner) {
2978 WidgetCorner wc = (WidgetCorner) toGrab;
2979 if (wc.getWidgetSource().isFixedSize()) {
2980 for (Item i : toGrab.getConnected()) {
2981 if (i instanceof WidgetEdge) {
2982 toGrab = i;
2983 break;
2984 }
2985 }
2986 }
2987 }
2988 pickup(toGrab.getConnected());
2989 }
2990
2991 public static void pickup(Collection<Item> toGrab) {
2992 if (toGrab == null || toGrab.size() == 0)
2993 return;
2994
2995 boolean bReparse = false;
2996 boolean bRecalculate = false;
2997
2998 Frame currentFrame = DisplayIO.getCurrentFrame();
2999 String currentFrameName = currentFrame.getName();
3000 Iterator<Item> iter = toGrab.iterator();
3001 while (iter.hasNext()) {
3002 Item i = iter.next();
3003 if (!i.hasPermission(UserAppliedPermission.full)) {
3004 iter.remove();
3005 continue;
3006 }
3007 if (i.equals(_lastHighlightedItem))
3008 _lastHighlightedItem = null;
3009
3010 bRecalculate |= i.recalculateWhenChanged();
3011 // i.setSelectedMode(SelectedMode.None);
3012 // Check if it has a relative link if so make it absolute
3013 i.setAbsoluteLink();
3014 // parent may be null
3015 if (i.getParent() != null) {
3016 i.getParent().removeItem(i);
3017 if (currentFrameName.equals(i.getParent().getName()))
3018 i.setParent(null);
3019 }
3020 FreeItems.getInstance().add(i);
3021 i.setFloating(true);
3022 // If its a vector pick up a copy of the stuff on the vector frame
3023 if (i.hasVector()) {
3024 bReparse = true;
3025
3026 Frame overlayFrame = FrameIO.LoadFrame(i.getAbsoluteLink());
3027 Collection<Item> copies = ItemUtils.CopyItems(overlayFrame
3028 .getNonAnnotationItems(false), i.getVector());
3029 for (Item copy : copies) {
3030 FreeItems.getInstance().add(copy);
3031 copy.setEditTarget(i);
3032 copy.setFloating(true);
3033 copy.setParent(null);
3034 // copy.setHighlightMode(HighlightMode.Connected);
3035 }
3036 }
3037 }
3038 currentFrame.change();
3039
3040 _lastHighlightedItem = null;
3041 updateCursor();
3042
3043 // if there are multiple items in the list, determine which to use for
3044 // offset calculations
3045 if (toGrab.size() > 1) {
3046 for (Item i : toGrab) {
3047 // MIKE: Movement goes haywire if these are removed because Line
3048 // class returns 0 for getX
3049 if (!(i instanceof Line) && !(i instanceof XRayable)) {
3050 _offX = DisplayIO.getMouseX() - i.getX() + i.getOffset().x;
3051 _offY = getY() - i.getY() + i.getOffset().y;
3052
3053 // make the offset item the first item in the list (so
3054 // move method knows which item to use)
3055 FreeItems.getInstance().set(
3056 FreeItems.getInstance().indexOf(i),
3057 FreeItems.getInstance().get(0));
3058 FreeItems.getInstance().set(0, i);
3059 break;
3060 }
3061 }
3062
3063 move(FreeItems.getInstance());
3064 ItemUtils.EnclosedCheck(toGrab);
3065 // otherwise, just use the first item
3066 } else if (toGrab.size() == 1) {
3067 Item soleItem = toGrab.iterator().next();
3068 _offX = DisplayIO.getMouseX() - soleItem.getX()
3069 + soleItem.getOffset().x;
3070 _offY = getY() - soleItem.getY() + soleItem.getOffset().y;
3071 // Now call move so that if we are on a message in the message box
3072 // It doesnt appear up the top of the scree!!
3073 move(toGrab);
3074 } else {
3075 MessageBay
3076 .displayMessage("Insufficient permission to pickup the items");
3077 }
3078 if (bReparse)
3079 FrameUtils.Parse(currentFrame, false, false);
3080 else
3081 currentFrame.notifyObservers(bRecalculate);
3082
3083 FrameGraphics.Repaint();
3084 }
3085
3086 private static Line createLine() {
3087 Frame current = DisplayIO.getCurrentFrame();
3088 // create the two endpoints
3089 Item end = DisplayIO.getCurrentFrame().createDot();
3090 Item start = DisplayIO.getCurrentFrame().createDot();
3091
3092 // create the Line
3093 Line line = new Line(start, end, current.getNextItemID());
3094 line.autoArrowheadLength();
3095
3096 // anchor the start
3097 anchor(start);
3098
3099 // attach the line to the cursor
3100 pickup(end);
3101 _lastHighlightedItem = null;
3102
3103 // TODO figure out how to get the end to highlight
3104 // end.setSelectedMode(SelectedMode.Normal);
3105 // end.setSelectedMode(SelectedMode.None);
3106
3107 return line;
3108 }
3109
3110 /**
3111 * Returns a list of copies of the list passed in
3112 *
3113 * @param toCopy
3114 * The list of items to copy
3115 * @return A List of copied Items
3116 */
3117 private static List<Item> copy(Collection<Item> toCopy) {
3118 return ItemUtils.CopyItems(toCopy);
3119 }
3120
3121 public static void anchor(Item toAnchor, boolean checkEnclosure) {
3122 // Only anchor items we have full permission over... ie dont anchor
3123 // vector items
3124 if (!toAnchor.hasPermission(UserAppliedPermission.full))
3125 return;
3126
3127 toAnchor.anchor();
3128
3129 if (checkEnclosure) {
3130 ItemUtils.EnclosedCheck(toAnchor.getParentOrCurrentFrame()
3131 .getItems());
3132 FrameGraphics.Repaint();
3133 }
3134 }
3135
3136 public static void anchor(Item toAnchor) {
3137 anchor(toAnchor, true);
3138 }
3139
3140 public static void anchor(Collection<Item> toAnchor) {
3141 boolean bReparse = false;
3142 boolean bRecalculate = false;
3143 // Need to make sure we check enclosure for overlays etc
3144 Set<Frame> checkEnclosure = new HashSet<Frame>();
3145
3146 // Create a clone of toAnchor since in the proccess of anchoring items
3147 // they can change the state of the toAnchor collection and thus create
3148 // concurrent modification exceptions.
3149 // This is especially needed for widgets being removed when anchored:
3150 // since they
3151 // currently are composed of 8 items this is vital. In the new revision
3152 // of
3153 // widgets being implemented as a single item this this can be
3154 // depreciated
3155 // however it may be useful for other applications.
3156 Collection<Item> toAnchorCopy = new ArrayList<Item>(toAnchor);
3157
3158 for (Item i : toAnchorCopy) {
3159 if (toAnchor.contains(i)) { // since to anchor could change while
3160 // anchoring
3161 // if (!i.hasVector())
3162 anchor(i, false);
3163 checkEnclosure.add(i.getParentOrCurrentFrame());
3164 bReparse |= i.hasOverlay();
3165 bRecalculate |= i.recalculateWhenChanged();
3166 }
3167 }
3168
3169 toAnchor.clear();
3170 // Check enclosure for all the frames of the items that were anchored
3171 for (Frame f : checkEnclosure) {
3172 ItemUtils.EnclosedCheck(f.getItems());
3173 }
3174
3175 Frame currentFrame = DisplayIO.getCurrentFrame();
3176 if (bReparse)
3177 FrameUtils.Parse(currentFrame, false, false);
3178 else {
3179 currentFrame.notifyObservers(bRecalculate);
3180 }
3181 FrameGraphics.Repaint();
3182 }
3183
3184 /*
3185 * private boolean mouseMovedRecently() { Date now = new Date();
3186 *
3187 * return now.getTime() - _lastMouseMovement.getTime() < 150; }
3188 */
3189
3190 public void mouseWheelMoved(MouseWheelEvent arg0) {
3191 Navigation.ResetLastAddToBack();
3192
3193 int clicks = arg0.getClickCount();
3194
3195 if (FreeItems.getInstance().size() == 2) {
3196 if ((FreeItems.getInstance().get(0).isLineEnd() && FreeItems
3197 .getInstance().get(1) instanceof Line)
3198 || (FreeItems.getInstance().get(1).isLineEnd() && FreeItems
3199 .getInstance().get(0) instanceof Line)) {
3200
3201 Line line;
3202 if (FreeItems.getInstance().get(0) instanceof Line)
3203 line = (Line) FreeItems.getInstance().get(0);
3204 else
3205 line = (Line) FreeItems.getInstance().get(1);
3206
3207 // User must do multiple clicks to toggle the line
3208 if (clicks == MOUSE_WHEEL_THRESHOLD) {
3209 if (arg0.isShiftDown())
3210 line.toggleArrowHeadRatio(arg0.getWheelRotation());
3211 else
3212 line.toggleArrowHeadLength(arg0.getWheelRotation());
3213 // line.getParent().change();
3214 FrameGraphics.Repaint();
3215 }
3216 }
3217 } else if (arg0.getWheelRotation() != 0 && arg0.isShiftDown()) {
3218
3219 FunctionKey rotationType = FunctionKey.SizeUp;
3220 if (arg0.getWheelRotation() > 0) {
3221 rotationType = FunctionKey.SizeDown;
3222 }
3223
3224 Item ip = FrameUtils.getCurrentItem();
3225 if (ip != null && clicks > 1) {
3226 float size = ip.getSize();
3227 if (ip instanceof Dot || ip instanceof Line
3228 || ip instanceof Circle) {
3229 size = ip.getThickness();
3230 }
3231 // base the number of clicks on the size of the object
3232 clicks = (int) Math.ceil(size / 20.0 * clicks);
3233 }
3234 FrameKeyboardActions.functionKey(rotationType, clicks, arg0
3235 .isShiftDown(), arg0.isControlDown());
3236
3237 } else if (clicks == MOUSE_WHEEL_THRESHOLD) {
3238 Item item = FrameUtils.getCurrentItem();
3239
3240 // if the user is not pointing to any item
3241 if (item == null) {
3242 FrameKeyboardActions.NextTextItem(null,
3243 arg0.getWheelRotation() > 0);
3244 return;
3245 }
3246
3247 if (item instanceof Line || item instanceof Circle) {
3248 // check permissions
3249 if (!item.hasPermission(UserAppliedPermission.full)) {
3250 MessageBay
3251 .displayMessage("Insufficient permission to edit the Line");
3252 return;
3253 }
3254 item.toggleDashed(arg0.getWheelRotation());
3255 item.getParent().change();
3256 FrameGraphics.Repaint();
3257 return;
3258 } else if (item instanceof Text) {
3259 FrameKeyboardActions.NextTextItem(item,
3260 arg0.getWheelRotation() > 0);
3261 }
3262 }
3263 }
3264
3265 /**
3266 *
3267 * @return the integer value for the last mouse button clicked.
3268 */
3269 public static int getLastMouseButton() {
3270 if (_lastMouseClick == null)
3271 return MouseEvent.NOBUTTON;
3272
3273 return _lastMouseClick.getButton();
3274 }
3275
3276 public static boolean isDelete(int modifiersEx) {
3277
3278 int onMask = MouseEvent.BUTTON3_DOWN_MASK
3279 | MouseEvent.BUTTON2_DOWN_MASK;
3280 return (modifiersEx & onMask) == onMask;
3281 }
3282
3283 public static boolean isGetAttributes(int modifiersEx) {
3284 int onMask = MouseEvent.BUTTON3_DOWN_MASK
3285 | MouseEvent.BUTTON1_DOWN_MASK;
3286 return (modifiersEx & onMask) == onMask;
3287 }
3288
3289 public static boolean isTwoClickNoOp(int modifiersEx) {
3290 int onMask = MouseEvent.BUTTON2_DOWN_MASK
3291 | MouseEvent.BUTTON1_DOWN_MASK;
3292 return (modifiersEx & onMask) == onMask;
3293 }
3294
3295 public static boolean wasDeleteClicked() {
3296 if (_lastMouseClick == null)
3297 return false;
3298 return isDelete(_lastMouseClickModifiers);
3299 }
3300
3301 public static void control(KeyEvent ke) {
3302 for (Item i : FreeItems.getInstance()) {
3303 i.invalidateCommonTrait(ItemAppearence.PreMoved);
3304 }
3305
3306 for (Item i : FreeItems.getCursor()) {
3307 i.invalidateCommonTrait(ItemAppearence.PreMoved);
3308 }
3309
3310 _controlDown = ke.isControlDown();
3311
3312 if (_controlDown) {
3313 // TODO why are these two lines needed?!?!
3314 // _offX = 0;
3315 // _offY = 0;
3316 } else {
3317 resetOffset();
3318 }
3319
3320 if (_mouseDown > 0 && _lastMouseDragged != null) {
3321 MouseEvent me = _lastMouseDragged;
3322 _lastMouseDragged = new MouseEvent(ke.getComponent(),
3323 MouseEvent.NOBUTTON, ke.getWhen(), ke.getModifiers(), me
3324 .getX(), me.getY(), 0, false);
3325 _instance.mouseDragged(_lastMouseDragged);
3326 } else if (_lastMouseMoved != null) {
3327 MouseEvent me = _lastMouseMoved;
3328 _lastMouseMoved = new MouseEvent(ke.getComponent(),
3329 MouseEvent.NOBUTTON, ke.getWhen(), ke.getModifiers(), me
3330 .getX(), me.getY(), 0, false);
3331 _instance.mouseMoved(_lastMouseMoved, true);
3332 }
3333 updateCursor();
3334
3335 Help.updateStatus();
3336
3337 for (Item i : FreeItems.getInstance()) {
3338 i.invalidateCommonTrait(ItemAppearence.PostMoved);
3339 }
3340
3341 for (Item i : FreeItems.getCursor()) {
3342 i.invalidateCommonTrait(ItemAppearence.PostMoved);
3343 }
3344 // TODO: Check why the new constrained line is not repainted immediately
3345 FrameGraphics.requestRefresh(true);
3346 FrameGraphics.refresh(true);
3347 }
3348
3349 public static int getX() {
3350 return Math.round(MouseX);
3351 }
3352
3353 public static int getY() {
3354 return Math.round(MouseY);
3355 }
3356
3357 public static Item getlastHighlightedItem() {
3358 return _lastHighlightedItem;
3359 }
3360
3361 public static Point getPosition() {
3362 return new Point(getX(), getY());
3363 }
3364
3365 public static Point getFreeItemsOffset() {
3366 return new Point(_offX, _offY);
3367 }
3368
3369 public static void shift(KeyEvent e) {
3370 _shiftDown = e.isShiftDown();
3371 Help.updateStatus();
3372 getInstance().refreshHighlights();
3373 }
3374}
Note: See TracBrowser for help on using the repository browser.