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

Last change on this file since 730 was 730, checked in by bln4, 10 years ago

Additions made:
The ability to register an action on TDFC. default: No additional actions are taken.
The abiility to provide a title. default: With no title set whenever expeditee sets its title it will prepend a empty string. set: it will prepend what you want you set the title too.
An additional 'getter' type method for XGroupItem that takes a y-coord and gives the span that covers that position.

File size: 98.8 KB
Line 
1package org.expeditee.gui;
2
3import java.awt.Point;
4import java.awt.Rectangle;
5import java.awt.event.ActionEvent;
6import java.awt.event.ActionListener;
7import java.awt.event.KeyEvent;
8import java.awt.event.MouseEvent;
9import java.awt.event.MouseListener;
10import java.awt.event.MouseMotionListener;
11import java.awt.event.MouseWheelEvent;
12import java.awt.event.MouseWheelListener;
13import java.text.NumberFormat;
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.Date;
18import java.util.HashSet;
19import java.util.Iterator;
20import java.util.LinkedHashSet;
21import java.util.LinkedList;
22import java.util.List;
23import java.util.Set;
24
25import javax.swing.Timer;
26
27import org.expeditee.actions.Actions;
28import org.expeditee.actions.Misc;
29import org.expeditee.actions.Navigation;
30import org.expeditee.items.Circle;
31import org.expeditee.items.Constraint;
32import org.expeditee.items.Dot;
33import org.expeditee.items.Item;
34import org.expeditee.items.Item.HighlightMode;
35import org.expeditee.items.ItemAppearence;
36import org.expeditee.items.ItemUtils;
37import org.expeditee.items.Line;
38import org.expeditee.items.Picture;
39import org.expeditee.items.Text;
40import org.expeditee.items.UserAppliedPermission;
41import org.expeditee.items.XRayable;
42import org.expeditee.items.widgets.InteractiveWidget;
43import org.expeditee.items.widgets.WidgetCorner;
44import org.expeditee.items.widgets.WidgetEdge;
45import org.expeditee.settings.UserSettings;
46import org.expeditee.settings.experimental.ExperimentalFeatures;
47import org.expeditee.stats.SessionStats;
48
49public class FrameMouseActions implements MouseListener, MouseMotionListener,
50 MouseWheelListener {
51
52 private static int _lastMouseClickModifiers = 0;
53
54 private static MouseEvent _lastMouseDragged;
55
56 private boolean _autoStamp = false;
57
58 private FrameMouseActions() {
59 }
60
61 private static FrameMouseActions _instance = null;
62
63 public static FrameMouseActions getInstance() {
64 if (_instance == null)
65 _instance = new FrameMouseActions();
66 return _instance;
67 }
68
69 private static final int RECTANGLE_CORNERS = 4;
70
71 // TODO say where/how used
72 private static final int MOUSE_WHEEL_THRESHOLD = 3;
73
74 private static final int MINIMUM_RANGE_DEPRESS_TIME = 250;
75
76 private static final int RECTANGLE_TO_POINT_THRESHOLD = 20;
77
78 private static Date _lastMouseClickDate = new Date();
79
80 public static final int LITTLE_MOUSE_PAUSE = 500;
81
82 public static final int ZERO_MOUSE_PAUSE = 0;
83
84 public static final int BIG_MOUSE_PAUSE = 750;
85
86 public static final int CONTEXT_FREESPACE = 0;
87
88 public static final int CONTEXT_AT_TEXT = 1;
89
90 public static final int CONTEXT_AT_LINE = 2;
91
92 public static final int CONTEXT_AT_DOT = 3;
93
94 public static final int CONTEXT_AT_ENCLOSURE = 4;
95
96 public static int _alpha = -1;
97
98 /**
99 * The last known mouse X coordinate
100 */
101 public static float MouseX;
102
103 /**
104 * The last known mouse Y coordinate. Relative to the top of the
105 * application.
106 */
107 public static float MouseY;
108
109 // Distance of mouse cursor from the origin of the item that was picked up
110 // The are used in the move method to calculate the distance moved by the
111 // cursor
112 private static int _offX;
113
114 private static int _offY;
115
116 // Keeps track of mouse button events when a delete occurs
117 private static boolean _isDelete = false;
118
119 // Keeps track of mouse button events when the user extracts attributes
120 // occurs
121 private static boolean _isAttribute = false;
122
123 /**
124 * A flag to indicate that the last mouseUp event was part of a two button
125 * click sequence hence the next mouse up should be ignored*
126 */
127 private static boolean _wasDouble = false;
128
129 private static boolean _isNoOp = false;
130
131 private static boolean _extrude = false;
132
133 // keeps track of the last highlighted Item
134 private static Item _lastHighlightedItem = null;
135
136 // keeps track of the item being 'ranged out' if there is one.
137 private static Text _lastRanged = null;
138
139 // keeps track of the picture being cropped if there is one
140 private static Picture _lastCropped = null;
141
142 // true if lastItem only has highlighting removed when a new item is
143 // highlighted
144 private static boolean _lastHoldsHighlight = false;
145
146 private static boolean _forceArrowCursor = true;
147
148 // the current context of the cursor
149 private static int _context = 0;
150
151 public static void setForceArrow(boolean val) {
152 _forceArrowCursor = val;
153 }
154
155 public static int getContext() {
156 return _context;
157 }
158
159 static int _mouseDown = 0;
160
161 private static MouseEvent _lastMouseClick = null;
162
163 private static Item _lastClickedOn = null;
164
165 private static Collection<Item> _lastClickedIn = null;
166
167 private static boolean _pulseOn = false;
168
169 private static final int PULSE_AMOUNT = 2;
170
171 private static Timer _MouseTimer = new Timer(LITTLE_MOUSE_PAUSE,
172 new ActionListener() {
173 public void actionPerformed(ActionEvent ae) {
174 // check if we are in free space
175 if (_lastClickedOn == null
176 && FreeItems.getInstance().size() == 0) {
177 // System.out.println("SuperBack!");
178 _MouseTimer.setDelay(ZERO_MOUSE_PAUSE);
179 back();
180 } else {
181 if (FrameUtils.getCurrentItem() == null) {
182 // Check if we are toggling arrowhead
183 if (FreeItems.getInstance().size() <= 2) {
184 for (Item i : FreeItems.getInstance()) {
185 if (i instanceof Line) {
186 ((Line) i).toggleArrow();
187 }
188 }
189 FrameGraphics.Repaint();
190 }
191 }
192 _MouseTimer.stop();
193 }
194 }
195 });
196
197 private static void setPulse(boolean pulseOn) {
198 if (_pulseOn == pulseOn) {
199 return;
200 }
201 int amount = PULSE_AMOUNT;
202 if (!pulseOn) {
203 amount *= -1;
204 }
205 _pulseOn = pulseOn;
206
207
208 if (_lastClickedOn != null) {
209 for (Item i : _lastClickedOn.getAllConnected()) {
210 if (i instanceof Line) {
211 Line line = (Line) i;
212 line.setThickness(line.getThickness() + amount);
213 }
214 }
215 }
216 FrameGraphics.Repaint();
217 }
218
219 private static Timer _ExtrudeMouseTimer = new Timer(BIG_MOUSE_PAUSE,
220 new ActionListener() {
221 public void actionPerformed(ActionEvent ae) {
222 setPulse(true);
223 _extrude = true;
224 _ExtrudeMouseTimer.stop();
225 }
226 });
227
228 public void mouseClicked(MouseEvent e) {
229 }
230
231 /**
232 * Each Item on the Frame is checked to determine if the mouse x,y
233 * coordinates are on the Item (or within the Shape surrounding it). If the
234 * coordinates are on the Item then the Item is checked for a link, if it
235 * has a link the link is followed, if not, nothing is done.
236 */
237 public void mousePressed(MouseEvent e) {
238 ProccessMousePressedEvent(e, e.getModifiersEx());
239 }
240
241 public void ProccessMousePressedEvent(MouseEvent e, int modifiersEx) {
242 // System.out.println("MousePressed " + e.getX() + "," + e.getY() + " "
243 // + e.getWhen());
244
245 // TODO WHY DID I NOT COMMENT THIS LINE!! MIKE SAYS
246 if (LastRobotX != null) {
247 _RobotTimer.stop();
248 LastRobotX = null;
249 LastRobotY = null;
250 mouseMoved(e);
251 }
252
253 if(ExperimentalFeatures.MousePan.get()) {
254 // don't pan if we're not over the frame
255 _overFrame = FrameUtils.getCurrentItem() == null;
256 _isPanOp = false;
257 // update panning position values so position doesn't jump
258 panStartX = e.getX();
259 panStartY = e.getY();
260 MouseX = panStartX;
261 MouseY = panStartY;
262 }
263
264 // System.out.println(modifiersEx);
265 if (_mouseDown == 0)
266 _lastMouseClickDate = new Date();
267
268 int buttonPressed = e.getButton();
269 _mouseDown += buttonPressed;
270 _lastClickedOn = FrameUtils.getCurrentItem();
271 // load any frame if necessary
272 Item on = _lastClickedOn;
273
274 _lastClickedIn = FrameUtils.getCurrentItems(on);
275 // if (_lastClickedIn != null){
276 // System.out.println(_lastClickedIn.size());}
277
278 /*
279 * This makes it so clicking repeatedly on the frameName doesnt add the
280 * frames to the backup stack. Only the first frame is added to the
281 * backup stack.
282 */
283 if (on == null || buttonPressed != MouseEvent.BUTTON1
284 || !on.isFrameName()) {
285 Navigation.ResetLastAddToBack();
286 }
287
288 SessionStats.MouseClicked(e.getButton());
289 if (buttonPressed == MouseEvent.BUTTON1) {
290 SessionStats.AddFrameEvent("Ld");
291 _extrude = false;
292 } else if (buttonPressed == MouseEvent.BUTTON2) {
293 SessionStats.AddFrameEvent("Md");
294 _extrude = false;
295 } else if (buttonPressed == MouseEvent.BUTTON3) {
296 SessionStats.AddFrameEvent("Rd");
297
298 // Check if the user picked up a paint brush
299 if (FreeItems.getInstance().size() == 1
300 && FreeItems.getItemAttachedToCursor().isAutoStamp()) {
301 int delay = (int) (FreeItems.getItemAttachedToCursor()
302 .getAutoStamp() * 1000);
303 if (delay < 10) {
304 _autoStamp = true;
305 } else {
306 _autoStampTimer.setDelay(delay);
307 _autoStampTimer.start();
308 }
309 }
310 }
311
312 // Mike says...
313 // For somereason the modifiers for e are different from modifiersEx
314 // The SwingUtilities.convertMouseEvent method changes the modifiers
315 _lastMouseClick = e;
316 _lastMouseClickModifiers = modifiersEx;
317
318 /*
319 * Only start the timer when in free space when the user double clicks
320 * to do super back TODO change this so that there are separate timers
321 * for super back and the other Long depress actions if that is what is
322 * wanted.
323 */
324 if (_lastClickedOn == null && FreeItems.getInstance().size() == 0) {
325 // System.out.println(e.getClickCount());
326 if (e.getClickCount() >= 2) {
327 _MouseTimer.start();
328 }
329 } else if (_lastClickedOn != null
330 && FreeItems.getInstance().size() == 0
331 && e.getButton() == MouseEvent.BUTTON3) {
332 _ExtrudeMouseTimer.start();
333
334 } else {
335 _MouseTimer.start();
336 }
337
338 // pre-cache the frame if it is linked
339
340 // If pre-caching is done, it must be done in the background
341
342 // if (on != null && on.getLink() != null && on.isLinkValid()) {
343 // FrameIO.Precache(on.getAbsoluteLink());
344 // }
345
346 // check for delete command
347 if (isDelete(modifiersEx)) {
348 _isDelete = true;
349 // _lastRanged = null;
350 _lastCropped = null;
351 _wasDouble = false;
352 // check for attributes command
353 } else if (isGetAttributes(modifiersEx)) {
354 _isAttribute = true;
355 _wasDouble = false;
356 } else if (isTwoClickNoOp(modifiersEx)) {
357 _isAttribute = false;
358 _wasDouble = false;
359 _isDelete = false;
360 _isNoOp = true;
361 } else
362 _isDelete = false;
363
364 // This must happen before the previous code
365 // This is when the user is anchoring something
366 if (buttonPressed != MouseEvent.BUTTON1
367 && (_context == CONTEXT_FREESPACE || _context == CONTEXT_AT_ENCLOSURE)
368 && FreeItems.itemsAttachedToCursor()) {
369 FrameGraphics.changeHighlightMode(_lastHighlightedItem,
370 Item.HighlightMode.None);
371
372 _lastHighlightedItem = FreeItems.getItemAttachedToCursor();
373 for (Item i : FreeItems.getInstance()) {
374 i.setHighlightColor(Item.DEPRESSED_HIGHLIGHT);
375 }
376 FrameGraphics.Repaint();
377 // this is when the user is picking something up
378 } else if (_lastHighlightedItem != null) {
379 if (!(_lastHighlightedItem instanceof Line)) {
380 _lastHighlightedItem
381 .setHighlightColor(Item.DEPRESSED_HIGHLIGHT);
382 } else {
383 for (Item i : _lastHighlightedItem.getAllConnected()) {
384 i.setHighlightColor(Item.DEPRESSED_HIGHLIGHT);
385 }
386 }
387 FrameGraphics.Repaint();
388 }
389
390 // if the user is ranging text
391 if (on != null && on instanceof Text && !_isDelete) {
392 _lastRanged = (Text) on;
393 // set start-drag point
394 _lastRanged.setSelectionStart(DisplayIO.getMouseX(),
395 FrameMouseActions.getY());
396 }
397
398 /*
399 * Want to repaint the text with deleteRange color as soon as the second
400 * button is pressed
401 */
402 if (_lastRanged != null) {
403 _lastRanged.invalidateAll();
404 FrameGraphics.requestRefresh(true);
405 }
406
407 if (on != null && on instanceof Picture
408 && e.getButton() == MouseEvent.BUTTON3 && !_isDelete) {
409 _lastCropped = (Picture) on;
410 // set start crop point
411 _lastCropped.setStartCrop(DisplayIO.getMouseX(), FrameMouseActions
412 .getY());
413 _lastCropped.setShowCrop(true);
414 }
415 }
416
417 // This is where all the processing happens
418 public void mouseReleased(MouseEvent e) {
419
420 // System.out.println("Released " + e.getX() + "," + e.getY() + " " +
421 // e.getWhen());
422 FrameUtils.ResponseTimer.restart();
423 _autoStampTimer.stop();
424 _autoStamp = false;
425
426 // Auto-hide popups when user clicks into expeditee world
427 // If the user clicks into empty space and a popup-is showing, then
428 // the user porbably wants to click away the popup - therefore ignore
429 // the event
430 boolean shouldConsume = PopupManager.getInstance()
431 .shouldConsumeBackClick();
432 PopupManager.getInstance().hideAutohidePopups();
433 if (shouldConsume && e.getButton() == MouseEvent.BUTTON1) {
434 return; // consume back click event
435 }
436
437 // _lastMovedDistance = new Point(e.getX() - _lastMouseClick.getX(), e
438 // .getY()
439 // - _lastMouseClick.getY());
440
441 _mouseDown -= e.getButton();
442 updateCursor();
443
444 // System.out.println(e.getX() + ", " + e.getY());
445
446 Text lastRanged = _lastRanged;
447 _lastRanged = null;
448 // Dont do ranging if the user moves really quickly...
449 // They are probably trying to pick something up in this case
450 if (lastRanged != null) {
451 long depressTime = (new Date()).getTime()
452 - _lastMouseClickDate.getTime();
453 // double changeInDistance =
454 // e.getPoint().distance(_currentMouseClick.getPoint());
455 // double speed = changeInDistance * 1000 / changeInTime;
456
457 // System.out.println(depressTime);
458
459 if (depressTime < MINIMUM_RANGE_DEPRESS_TIME
460 || lastRanged.getSelectionSize() <= 0) {// Text.MINIMUM_RANGED_CHARS)
461 // {
462 lastRanged.clearSelection();
463 lastRanged = null;
464 }
465 }
466
467 _ExtrudeMouseTimer.stop();
468 _MouseTimer.stop();
469
470 setPulse(false);
471
472 // if the last action was a delete, then ignore the next mouseup
473 if (_wasDouble) {
474 _wasDouble = false;
475 return;
476 }
477
478 // This code must come after the _wasDouble code...
479 // Otherwise get Stopping Agent method after doing the left+right format
480 // shortcut
481 if (Actions.isAgentRunning()) {
482 Actions.stopAgent();
483 return;
484 }
485
486 /*
487 * if (_isNoOp) { if (e.getButton() != MouseEvent.NOBUTTON) { _isNoOp =
488 * false; _wasDouble = true; // lastRanged.clearSelection();
489 * FrameGraphics.Repaint(); return; } }
490 */
491
492 // get whatever the user was pointing at
493 Item clickedOn = _lastClickedOn;
494 Collection<Item> clickedIn = _lastClickedIn;
495
496 MouseX = e.getX();
497 MouseY = e.getY();
498
499 Item releasedOn = FrameUtils.getCurrentItem();
500 Collection<Item> releasedIn = FrameUtils.getCurrentItems(releasedOn);
501
502 // Only a no op if user releases in free space!
503 if (_isPanOp || (_isNoOp && (releasedOn == null && releasedIn == null))) {
504 if (_isDelete) {
505 _isDelete = false;
506 _wasDouble = true;
507 }
508
509 _isNoOp = false;
510
511 if (_lastHighlightedItem != null)
512 FrameGraphics.changeHighlightMode(_lastHighlightedItem,
513 Item.HighlightMode.None);
514
515 if (FreeItems.itemsAttachedToCursor()) {
516 move(FreeItems.getInstance());
517 }
518
519 if (FreeItems.hasCursor()) {
520 move(FreeItems.getCursor(), true);
521 }
522
523 if(!_isPanOp) {
524 MessageBay.displayMessage("Action cancelled, mouse moved more than "
525 + UserSettings.NoOpThreshold.get() + " pixels.");
526 }
527 FrameGraphics.Repaint();
528 return;
529 } else {
530 _isNoOp = false;
531 }
532
533 // if this is a delete command
534 if (_isDelete) {
535 if (lastRanged != null) {
536
537 Item i = FreeItems.getItemAttachedToCursor();
538 if (i != null && i instanceof Text) {
539 lastRanged.replaceSelectedText(((Text) i).getText());
540 FreeItems.getInstance().clear();
541 } else
542 lastRanged.cutSelectedText();
543 lastRanged.clearSelection();
544 FrameGraphics.Repaint();
545
546 } else {
547 delete(clickedOn);
548 }
549 _wasDouble = true;
550 _isDelete = false;
551 return;
552 }
553
554 // if this is an attribute extraction command
555 if (_isAttribute) {
556 if (clickedOn == null) {
557 Frame current = DisplayIO.getCurrentFrame();
558 if (isControlDown()) {
559 Actions.PerformActionCatchErrors(current, null, "HFormat");
560 }
561 if (!isControlDown() || isShiftDown()) {
562 Actions.PerformActionCatchErrors(current, null, "Format");
563 }
564 } else {
565 extractAttributes(clickedOn);
566 }
567 // if the user dragged and displayed some cropping with left and
568 // right button is a no op for now
569 // but later could make this the shrinkTo context
570 if (_lastCropped != null) {
571 _lastCropped.clearCropping();
572 _lastCropped = null;
573 }
574 _wasDouble = true;
575 _isAttribute = false;
576 return;
577 }
578
579 // if the user is ranging-out text
580 if (lastRanged != null && e.getButton() != MouseEvent.BUTTON1) {
581
582 Text ranged = DisplayIO.getCurrentFrame().createNewText();
583 ranged.setColor(lastRanged.getColor());
584 ranged.setBackgroundColor(lastRanged.getBackgroundColor());
585 ranged.setFont(ranged.getFont());
586 ranged.setWidth(lastRanged.getAbsoluteWidth() * -1);
587
588 // if the user is cutting text from the item
589 if (e.getButton() == MouseEvent.BUTTON2) {
590 // Check if the user is trying to range an item for which they
591 // do not have permission to do so... or it is the frame name
592 if (!lastRanged.hasPermission(UserAppliedPermission.full)
593 || lastRanged.isFrameName()) {
594 MessageBay
595 .displayMessage("Insufficient permission to cut text");
596 lastRanged.clearSelection();
597 FrameGraphics.Repaint();
598 return;
599 }
600 // if the entire text is selected and its not a line end then
601 // pickup the item
602 boolean entireText = lastRanged.getSelectionSize() == lastRanged
603 .getLength();
604 if (entireText && !lastRanged.isLineEnd()) {
605 lastRanged.clearSelection();
606 ranged.delete();
607 middleButton(clickedOn, clickedIn, e.isShiftDown());
608 return;
609 } else {
610 ranged.setText(lastRanged.cutSelectedText());
611 ranged.setWidth(lastRanged.getWidth());
612 // If its the whole text then replace last ranged with a dot
613 if (entireText) {
614 Item dot = FrameKeyboardActions.replaceText(lastRanged);
615 dot.setHighlightMode(HighlightMode.None);
616 }
617 }
618 // if the user is copying text from the item
619 } else if (e.getButton() == MouseEvent.BUTTON3) {
620 // Check if the user is trying to range an item for which they
621 // do not have permission to do so... or it is the frame name
622 if (!lastRanged.hasPermission(UserAppliedPermission.copy)) {
623 MessageBay
624 .displayMessage("Insufficient permission to copy text");
625 lastRanged.clearSelection();
626 FrameGraphics.Repaint();
627 return;
628 }
629 ranged.setText(lastRanged.copySelectedText());
630 }
631
632 ranged.setParent(null);
633 ranged.setPosition(DisplayIO.getMouseX(), FrameMouseActions.getY());
634 pickup(ranged);
635 lastRanged.clearSelection();
636 lastRanged.setHighlightMode(HighlightMode.None);
637 refreshHighlights();
638 FrameGraphics.refresh(false);
639 return;
640 }
641
642 // if the user is cropping an image
643 if (clickedOn != null && clickedOn == _lastCropped) {
644 if (_lastCropped.isCropTooSmall()) {
645 _lastCropped = null;
646 // FrameGraphics
647 // .WarningMessage("Crop cancelled because it was below the
648 // minimum size");
649 } else {
650 Picture cropped = _lastCropped.copy();
651 cropped.setParent(null);
652 // move the cropped image to the cursor
653 int width = cropped.getWidth();
654 int height = cropped.getHeight();
655 if(cropped.getSource().getX() + width < MouseX) {
656 cropped.getSource().setX(MouseX - width);
657 }
658 if(cropped.getSource().getY() + height < MouseY) {
659 cropped.getSource().setY(MouseY - height);
660 }
661 pickup(cropped);
662 // MIKE put the code below up here
663 _lastCropped.clearCropping();
664 FrameGraphics.changeHighlightMode(_lastCropped,
665 HighlightMode.None);
666 _lastCropped = null;
667 FrameGraphics.Repaint();
668 return;
669 }
670 }
671
672 assert (_lastCropped == null);
673 // if the user has cropped an image, either the above happend or this is
674 // a no-op MIKE says WHEN DO WE NEED THE CODE BELOW
675 // if (_lastCropped != null && !_lastCropped.isCropTooSmall()) {
676 // _lastCropped.clearCropping();
677 // _lastCropped = null;
678 // FrameGraphics.Repaint();
679 // return;
680 // }
681
682 // if the user is left-clicking
683 if (e.getButton() == MouseEvent.BUTTON1) {
684 SessionStats.AddFrameEvent("Lu");
685 leftButton(clickedOn, clickedIn, e.isShiftDown(), e.isControlDown());
686 return;
687 }
688
689 if (e.getButton() == MouseEvent.BUTTON2) {
690 SessionStats.AddFrameEvent("Mu");
691 middleButton(clickedOn, clickedIn, e.isShiftDown());
692 return;
693 }
694
695 if (e.getButton() == MouseEvent.BUTTON3) {
696 SessionStats.AddFrameEvent("Ru");
697 rightButton(clickedOn, clickedIn);
698 return;
699 }
700
701 // error, we should have returned by now
702 System.out.println("Error: mouseReleased should have returned by now. "
703 + e);
704 }
705
706 /**
707 * This method handles all left-click actions
708 */
709 private void leftButton(Item clicked, Collection<Item> clickedIn,
710 boolean isShiftDown, boolean isControlDown) {
711
712 // if the user is pointing at something then either follow the link or
713 // do TDFC
714 if (clicked == null) {
715 // Check if the user is nearby another item...
716 int mouseX = DisplayIO.getMouseX();
717 int mouseY = FrameMouseActions.getY();
718 // System.out.println(mouseX + "," + mouseY);
719 for (Item i : DisplayIO.getCurrentFrame().getItems()) {
720 if (i instanceof Text) {
721 if (i.isNear(mouseX, mouseY)) {
722 clicked = i;
723 break;
724 }
725 }
726 }
727 }
728
729 if (clicked instanceof Text) {
730 Text text = (Text) clicked;
731 /* Dont follow link when just highlighting text with the left button */
732 if (text.getText().length() == 0)
733 clicked = null;
734 else if (text.getSelectionSize() > 0) {
735 return;
736 }
737 }
738
739 // If the user clicked into a widgets free space...
740 if (clicked == null && _lastClickedIn != null
741 && _lastClickedIn.size() >= 4) {
742
743 // Check to see if the use clicked into a widgets empty space
744 InteractiveWidget iw = null;
745
746 for (Item i : _lastClickedIn) {
747
748 if (i instanceof WidgetCorner) {
749 iw = ((WidgetCorner) i).getWidgetSource();
750 break;
751 } else if (i instanceof WidgetEdge) {
752 iw = ((WidgetEdge) i).getWidgetSource();
753 break;
754 }
755 }
756
757 if (iw != null) {
758
759 // Handle dropping items on widgets
760 if(iw.ItemsLeftClickDropped()) {
761 return;
762 }
763
764 // Note: musten't directly use source for handling the link
765 // because all link operations will by-pass the widgets special
766 // handling with links...
767 Item widgetLink = iw.getItems().get(0);
768 assert (widgetLink != null);
769 clicked = widgetLink;
770 } else {
771 for (Item i : _lastClickedIn) {
772 /*
773 * Find the first linked item or the first unlinked Dot This
774 * code assumes that items are are ordered from top to
775 * bottom. TODO make sure the list will always be ordered
776 * correctly!!
777 */
778 if (i.hasLink() || i instanceof Dot) {
779 clicked = i;
780 break;
781 }
782 }
783 }
784
785 }
786
787 if (clicked != null) {
788 // check item permissions
789 boolean hasLinkOrAction = clicked.hasLink() || clicked.hasAction();
790
791 if ((hasLinkOrAction && !clicked
792 .hasPermission(UserAppliedPermission.followLinks))
793 || (!hasLinkOrAction && !clicked
794 .hasPermission(UserAppliedPermission.createFrames))) {
795 Item editTarget = clicked.getEditTarget();
796 if (editTarget != clicked) {
797 if (editTarget.hasPermission(UserAppliedPermission.followLinks)) {
798 clicked = editTarget;
799 } else {
800 MessageBay
801 .displayMessage("Insufficient permission to perform action on item");
802 return;
803 }
804 }
805 }
806
807 Item clickedOn = clicked;
808
809 // actions take priority
810 if (_lastMouseClick != null && !_lastMouseClick.isControlDown()
811 && clickedOn.hasAction()) {
812 clickedOn.performActions();
813 clickedOn.setHighlightMode(HighlightMode.None);
814 getInstance().refreshHighlights();
815 return;
816 } else if (clickedOn.getLink() != null) {
817 /*
818 * Dont save the frame if we are moving to an old version of
819 * this frame because everytime we save with the old tag... the
820 * frame is backed up
821 */
822 if (!clickedOn.isOldTag())
823 FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
824
825 Navigation.setLastNavigationItem(clickedOn);
826 load(clickedOn.getAbsoluteLink(), clickedOn.getLinkHistory());
827 // DisplayIO.UpdateTitle();
828 return;
829 // no link is found, perform TDFC
830 } else {
831 /*
832 * if the user is clicking on the frame name then move to the
833 * next or previous frame regardless of whether or not the frame
834 * is protected
835 */
836 if (clickedOn.isFrameName()) {
837 if (isControlDown)
838 Navigation.PreviousFrame(false);
839 else
840 Navigation.NextFrame(false);
841 return;
842 }
843
844 // check for TDFC permission
845 if (!clicked.hasPermission(UserAppliedPermission.createFrames)) {
846 MessageBay
847 .displayMessage("Insufficient permission to TDFC (Top Down Frame Creation) from that item");
848 return;
849 }
850
851 if (clickedOn.isOldTag())
852 return;
853
854 try {
855 tdfc(clickedOn);
856 } catch (RuntimeException e) {
857 e.printStackTrace();
858 MessageBay.errorMessage("Top Down Frame Creation (TDFC) error: " + e.getMessage());
859 }
860 return;
861 }
862
863 } else {
864
865 // if user is not pointing at something,this is a back
866 if (isShiftDown || isControlDown)
867 forward();
868 else
869 back();
870
871 }
872 }
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 private static OnNewFrameAction _onFrameAction = null;
1823
1824 public static void setTDFCAction(OnNewFrameAction action) {
1825 _onFrameAction = action;
1826 }
1827
1828 public static boolean isControlDown() {
1829 return _controlDown;
1830 }
1831
1832 public static boolean isShiftDown() {
1833 return _shiftDown;
1834 }
1835
1836 public static void setLastRobotMove(float x, float y) {
1837 // Make sure the system is in the right state while waiting for the
1838 // Robots event to arrive.
1839 MouseX = x;
1840 MouseY = y;
1841 // System.out.println("MouseMoved: " + MouseX + "," + MouseY + " " +
1842 // System.currentTimeMillis());
1843 LastRobotX = Math.round(x);
1844 LastRobotY = Math.round(y);
1845 _RobotTimer.start();
1846 }
1847
1848 public static boolean isWaitingForRobot() {
1849 return LastRobotX != null;
1850 }
1851
1852 /**
1853 * Updates the stored mouse position and highlights any items as necessary.
1854 */
1855 public void mouseMoved(MouseEvent e) {
1856 mouseMoved(e, false);
1857 }
1858
1859 private void mouseMoved(MouseEvent e, boolean shiftStateChanged) {
1860 // System.out.println("mouseMoved");
1861 // System.out.println(_context);
1862 if (_context == CONTEXT_FREESPACE)
1863 FrameKeyboardActions.resetEnclosedItems();
1864 // System.out.println(e.getX() + "," + e.getY() + " " + e.getWhen());
1865 if (LastRobotX != null) {
1866 // Wait until the last Robot mouse move event arrives before
1867 // processing other events
1868 if (/* FreeItems.getInstance().size() == 0 || */
1869 (LastRobotX == e.getX() && LastRobotY == e.getY())) {
1870 LastRobotX = null;
1871 LastRobotY = null;
1872 _RobotTimer.stop();
1873 } else {
1874 // System.out.println("Ignored: " +
1875 // FreeItems.getInstance().size());
1876 return;
1877 }
1878 }
1879
1880 MouseX = e.getX();
1881 MouseY = e.getY();
1882
1883 // System.out.println(MouseX + "," + MouseY);
1884
1885 // Moving the mouse a certain distance removes the last edited text if
1886 // it is empty
1887 Text lastEdited = FrameUtils.getLastEdited();
1888 if (lastEdited != null && lastEdited.getText().length() == 0
1889 && lastEdited.getPosition().distance(e.getPoint()) > 20) {
1890 FrameUtils.setLastEdited(null);
1891 }
1892
1893 // If shift is down then the movement is constrained
1894 if (_controlDown && FreeItems.getInstance().size() > 0) {
1895 // Check if we are rubber banding a line
1896 if (shiftStateChanged && rubberBanding()) {
1897 // Get the line end that is being rubber banded
1898 Item thisEnd = FreeItems.getInstance().get(0).isLineEnd() ? FreeItems
1899 .getInstance().get(0)
1900 : FreeItems.getInstance().get(1);
1901 Line line = (Line) (FreeItems.getInstance().get(0).isLineEnd() ? FreeItems
1902 .getInstance().get(1)
1903 : FreeItems.getInstance().get(0));
1904 Item otherEnd = line.getOppositeEnd(thisEnd);
1905 int deltaX = Math.abs(e.getX() - otherEnd.getX());
1906 int deltaY = Math.abs(e.getY() - otherEnd.getY());
1907 // Check if its a vertical line
1908 if (deltaX < deltaY / 2) {
1909 // otherEnd.setX(thisEnd.getX());
1910 // MouseX = otherEnd.getX();
1911 if (shiftStateChanged) {
1912 new Constraint(thisEnd, otherEnd, thisEnd
1913 .getParentOrCurrentFrame().getNextItemID(),
1914 Constraint.VERTICAL);
1915 }
1916 }
1917 // Check if its horizontal
1918 else if (deltaY <= deltaX / 2) {
1919 // MouseY = otherEnd.getY();
1920 // otherEnd.setY(thisEnd.getY());
1921 if (shiftStateChanged) {
1922 new Constraint(thisEnd, otherEnd, thisEnd
1923 .getParentOrCurrentFrame().getNextItemID(),
1924 Constraint.HORIZONTAL);
1925 }
1926 } else {
1927 // Add DIAGONAL constraints
1928 // if (deltaX > deltaY) {
1929 // otherEnd.setY(thisEnd.getY() + deltaX
1930 // * (e.getY() < otherEnd.getY() ? 1 : -1));
1931 // } else {
1932 // otherEnd.setX(thisEnd.getX() + deltaY
1933 // * (e.getX() < otherEnd.getX() ? 1 : -1));
1934 // }
1935 if (shiftStateChanged) {
1936 int constraint = Constraint.DIAGONAL_NEG;
1937 // Check if the slope is positive
1938 if ((thisEnd.getY() - otherEnd.getY())
1939 / (double) (thisEnd.getX() - otherEnd.getX()) > 0.0) {
1940 constraint = Constraint.DIAGONAL_POS;
1941 }
1942
1943 new Constraint(thisEnd, otherEnd, thisEnd
1944 .getParentOrCurrentFrame().getNextItemID(),
1945 constraint);
1946 }
1947 }
1948 }// If its a lineend attached to two lines lengthen the shorter
1949 // so it is the same length as the longer line
1950 else if (FreeItems.getInstance().size() == 3) {
1951 // check if we are rubber banding the corner of a shape
1952 Item thisEnd = getShapeCorner(FreeItems.getInstance());
1953 if (thisEnd != null) {
1954 Line line1 = thisEnd.getLines().get(0);
1955 Line line2 = thisEnd.getLines().get(1);
1956 // Check if the two lines are constrained and hence it is a
1957 // rectangle
1958 Integer c1 = line1.getPossibleConstraint();
1959 Integer c2 = line2.getPossibleConstraint();
1960
1961 if (c1 != null && c2 != null) {
1962 // This is the case of a constrained rectangle
1963 if ((c2 == Constraint.VERTICAL || c2 == Constraint.HORIZONTAL)
1964 && (c1 == Constraint.VERTICAL || c1 == Constraint.HORIZONTAL)
1965 && (c1 != c2)) {
1966 Line vLine = line2;
1967 Line hLine = line1;
1968 if (c1 == Constraint.VERTICAL) {
1969 vLine = line1;
1970 hLine = line2;
1971 }
1972 Item hOtherEnd = hLine.getOppositeEnd(thisEnd);
1973 Item vOtherEnd = vLine.getOppositeEnd(thisEnd);
1974
1975 double vLength = Math
1976 .abs(vOtherEnd.getY() - MouseY);
1977 double hLength = Math
1978 .abs(hOtherEnd.getX() - MouseX);
1979
1980 if (vLength > hLength) {
1981 MouseX = Math.round(hOtherEnd.getX() + vLength
1982 * (MouseX > hOtherEnd.getX() ? 1 : -1));
1983 } else /* if (hLength > vLength) */{
1984 MouseY = Math.round(vOtherEnd.getY() + hLength
1985 * (MouseY > vOtherEnd.getY() ? 1 : -1));
1986 }
1987 }
1988 // } else if (c2 != null) {
1989 //
1990 // } // Other wise it is a not constrained shape so
1991 // constrain
1992 // the two lines lengths to be equal
1993 } else {
1994 Item lineEnd1 = line1.getOppositeEnd(thisEnd);
1995 Item lineEnd2 = line2.getOppositeEnd(thisEnd);
1996 double l1 = Line.getLength(lineEnd1.getPosition(), e
1997 .getPoint());
1998 double l2 = Line.getLength(lineEnd2.getPosition(), e
1999 .getPoint());
2000 double l3 = Line.getLength(lineEnd1.getPosition(),
2001 lineEnd2.getPosition());
2002 // l1 needs to be the shorter end
2003 if (l1 > l2) {
2004 Item temp = lineEnd1;
2005 lineEnd1 = lineEnd2;
2006 lineEnd2 = temp;
2007 double tempL = l1;
2008 l1 = l2;
2009 l2 = tempL;
2010 }
2011 // Now use the cosine rule to calculate the angle
2012 // between l1 and l3
2013 double cosTheta = (l1 * l1 + l3 * l3 - l2 * l2)
2014 / (2 * l1 * l3);
2015 // now calculate the new length for the lines using cos
2016 // rule
2017 double l_new = l3 / (2 * cosTheta);
2018 double ratio = l_new / l1;
2019 MouseX = Math.round((e.getX() - lineEnd1.getX())
2020 * ratio)
2021 + lineEnd1.getX();
2022 MouseY = Math.round((e.getY() - lineEnd1.getY())
2023 * ratio)
2024 + lineEnd1.getY();
2025
2026 }
2027 }
2028 }
2029 } else if (shiftStateChanged && !_controlDown && rubberBanding()) {
2030 // Get the line end that is being rubber banded
2031 Item thisEnd = FreeItems.getInstance().get(0).isLineEnd() ? FreeItems
2032 .getInstance().get(0)
2033 : FreeItems.getInstance().get(1);
2034 thisEnd.removeAllConstraints();
2035 }
2036
2037 if (_lastMouseMoved == null)
2038 _lastMouseMoved = e;
2039
2040 _lastMouseMoved = e;
2041
2042 refreshHighlights();
2043
2044 if (FreeItems.hasCursor()) {
2045 move(FreeItems.getCursor(), true);
2046 }
2047
2048 if (FreeItems.itemsAttachedToCursor()) {
2049 move(FreeItems.getInstance());
2050 // System.out.println(FreeItems.getInstance().size());
2051 }
2052
2053 if (_forceArrowCursor)
2054 updateCursor();
2055
2056 _forceArrowCursor = true;
2057 }
2058
2059 public void refreshHighlights() {
2060 // ByMike: Get the item the mouse is hovering over
2061 Item click = FrameUtils.getCurrentItem();
2062 Item on = null;
2063 // System.out.println(click);
2064 if (click != null) {
2065 on = click;
2066 // set the context
2067 if (on instanceof Line)
2068 _context = CONTEXT_AT_LINE;
2069 else if (on instanceof Dot)
2070 _context = CONTEXT_AT_DOT;
2071 else if (on instanceof Text) {
2072 _context = CONTEXT_AT_TEXT;
2073 }
2074 if (FreeItems.getInstance().size() > 0)
2075 _alpha = 60;
2076 else
2077 _alpha = -1;
2078 } else {
2079 _context = CONTEXT_FREESPACE;
2080 _alpha = -1;
2081 }
2082
2083 // if the user is pointing at an item, highlight it
2084 if (on != null && !FreeItems.getInstance().contains(on)) {
2085 // if the user can spot-weld, show the virtual spot
2086 if (FreeItems.getInstance().size() == 2 && on instanceof Line) {
2087 Line line = (Line) on;
2088 Item freeItem0 = FreeItems.getInstance().get(0);
2089 Item freeItem1 = FreeItems.getInstance().get(1);
2090 Item lineEnd = freeItem0.isLineEnd() ? freeItem0 : (freeItem1
2091 .isLineEnd() ? freeItem1 : null);
2092 if (lineEnd != null) {
2093 if (_mouseDown == 0)
2094 line.showVirtualSpot(lineEnd, DisplayIO.getMouseX(),
2095 FrameMouseActions.getY());
2096 } else
2097 // The user is pointing at another point or text item
2098 // etc
2099 FrameGraphics.changeHighlightMode(on,
2100 Item.HighlightMode.Normal);
2101 } else {
2102 // FrameGraphics.ChangeSelectionMode(on,
2103 // Item.SelectedMode.Connected);
2104 // TODO: The method below is for the most part redundant
2105 on = FrameGraphics.Highlight(on.getEditTarget());
2106 }
2107 // if the last item highlighted is still highlighted, clear it
2108 if (_lastHoldsHighlight) {
2109 _lastHoldsHighlight = false;
2110 for (Item i : DisplayIO.getCurrentFrame().getItems())
2111 if (i.isHighlighted() && i != on)
2112 FrameGraphics.changeHighlightMode(i,
2113 Item.HighlightMode.None);
2114 }
2115
2116 // if the user is not pointing at an item, check for enclosure
2117 // highlighting
2118 } else if (on == null) {
2119 Collection<Item> enclosure = FrameUtils.getEnclosingLineEnds();
2120 if (enclosure != null && enclosure.size() > 0) {
2121 Item firstLineEnd = enclosure.iterator().next();
2122 HighlightMode hm;
2123 if(isShiftDown()) {
2124 hm = HighlightMode.Connected;
2125 } else {
2126 hm = HighlightMode.Enclosed;
2127 }
2128 if (firstLineEnd.getLines().size() > 1 &&
2129 // check that the enclosure is not part of a point being
2130 // dragged in space
2131 !ContainsOneOf(enclosure, FreeItems.getInstance())) {
2132 on = firstLineEnd.getLines().get(0);
2133 // System.out.println(on == null ? "Null" :
2134 // on.toString());
2135 FrameGraphics.changeHighlightMode(on, hm);
2136 } else if (firstLineEnd instanceof XRayable) {
2137 on = firstLineEnd;
2138 FrameGraphics.changeHighlightMode(firstLineEnd, hm);
2139 }
2140 _context = CONTEXT_AT_ENCLOSURE;
2141 } else if (_lastHighlightedItem != null) {
2142 // System.out.println("LastHighlightedItem");
2143 _lastHoldsHighlight = false;
2144 }
2145 }
2146
2147 // disable cursor changes when the cursor has items attached
2148 if (FreeItems.itemsAttachedToCursor()
2149 && DisplayIO.getCursor() != Item.TEXT_CURSOR)
2150 _forceArrowCursor = false;
2151
2152 // setLastHighlightedItem(on);
2153
2154 if (_lastHighlightedItem != null && _lastHighlightedItem != on
2155 && !_lastHoldsHighlight) {
2156 // Turn off the highlighting only if
2157 // the last highlighted item is not connected to the currentItem
2158 // Otherwise we get flickering in transition from connected to
2159 // normal mode while moving the cursor along a line.
2160 if (on == null
2161 || (!on.getAllConnected().contains(_lastHighlightedItem))) {
2162 FrameGraphics.changeHighlightMode(_lastHighlightedItem,
2163 Item.HighlightMode.None);
2164 }
2165 }
2166
2167 _lastHighlightedItem = on;
2168
2169 }
2170
2171 private boolean ContainsOneOf(Collection<Item> enclosure,
2172 Collection<Item> freeItems) {
2173 if (freeItems == null)
2174 return false;
2175 for (Item i : freeItems) {
2176 if (enclosure.contains(i))
2177 return true;
2178 }
2179 return false;
2180 }
2181
2182 /**
2183 * Checks if lines are being rubber banded.
2184 *
2185 * @return true if the user is rubberBanding one or more lines
2186 */
2187 private static boolean rubberBanding() {
2188 if (FreeItems.getInstance().size() != 2) {
2189 return false;
2190 }
2191
2192 // if rubber-banding, there will be 1 lineend and the rest will be lines
2193 boolean foundLineEnd = false;
2194 for (Item i : FreeItems.getInstance()) {
2195 if (i.isLineEnd()) {
2196 if (foundLineEnd) {
2197 return false;
2198 }
2199 foundLineEnd = true;
2200 } else if (!(i instanceof Line) || !i.isVisible()) {
2201 return false;
2202 }
2203 }
2204 return true;
2205 }
2206
2207 /**
2208 * Updates the current mouse cursor to whatever it should be. i.e. Hidden
2209 * when rubber-banding lines, otherwise default (arrow)
2210 */
2211 public static void updateCursor() {
2212 if (rubberBanding()) {
2213 DisplayIO.setCursor(Item.HIDDEN_CURSOR);
2214 return;
2215 }
2216 // This is to make sure the TEXT_CURSOR doesnt get inadvertantly turned
2217 // off!
2218 Item on = FrameUtils.getCurrentItem();
2219 if (on != null && on instanceof Text) {
2220 return;
2221 }
2222 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2223 }
2224
2225 public static void setHighlightHold(boolean hold) {
2226 _lastHoldsHighlight = hold;
2227 }
2228
2229 public static void resetOffset() {
2230 if (FreeItems.itemsAttachedToCursor()) {
2231 _offX = DisplayIO.getMouseX()
2232 - FreeItems.getInstance().get(0).getX()
2233 + FreeItems.getInstance().get(0).getOffset().x;
2234 _offY = getY() - FreeItems.getInstance().get(0).getY()
2235 + FreeItems.getInstance().get(0).getOffset().y;
2236 }
2237 }
2238
2239 /**
2240 * Moves the items to the current mouse position (plus the current offset)
2241 *
2242 * @param toMove
2243 */
2244 static void move(Collection<Item> toMove) {
2245 move(toMove, false);
2246 }
2247
2248 static void move(Collection<Item> toMove, boolean cursor) {
2249
2250 // Gets the origin of the first item to move
2251 int xPos = (DisplayIO.getMouseX() - (cursor ? 0 : _offX));
2252
2253 Item firstDot = toMove.iterator().next();
2254
2255 int deltax = firstDot.getX() - xPos;
2256 int deltay = firstDot.getY() - (getY() - (cursor ? 0 : _offY));
2257
2258 for (Item move : toMove) {
2259 move.setPosition(move.getX() - deltax, move.getY() - deltay);
2260
2261 if (!cursor && move instanceof Text) {
2262 ((Text) move).setAlpha(_alpha);
2263 }
2264 }
2265
2266 FrameGraphics.requestRefresh(true);
2267 // FrameGraphics.refresh(true);
2268 }
2269
2270 private static void load(String toLoad, boolean addToHistory) {
2271 if (FrameIO.isValidFrameName(toLoad)) {
2272 DisplayIO.clearBackedUpFrames();
2273 FrameUtils.DisplayFrame(toLoad, addToHistory, true);
2274 } else {
2275 MessageBay.errorMessage(toLoad + " is not a valid frame name.");
2276 }
2277 }
2278
2279 private static void back() {
2280 DisplayIO.Back();
2281
2282 // repaint things if necessary
2283 if (FreeItems.itemsAttachedToCursor())
2284 move(FreeItems.getInstance());
2285
2286 if (FreeItems.hasCursor())
2287 move(FreeItems.getCursor(), true);
2288 }
2289
2290 private static void forward() {
2291 DisplayIO.Forward();
2292
2293 // repaint things if necessary
2294 if (FreeItems.itemsAttachedToCursor())
2295 move(FreeItems.getInstance());
2296
2297 if (FreeItems.hasCursor())
2298 move(FreeItems.getCursor(), true);
2299 }
2300
2301 /**
2302 * Returns true if the mouse moved during TDFC. This will happen if there is
2303 * a start annotation item on the frame.
2304 *
2305 * @param linker
2306 * @return
2307 */
2308 public static boolean tdfc(Item linker) throws RuntimeException {
2309 // if this is a non-usable item
2310 if (linker.getID() < 0)
2311 return false;
2312
2313 // Check if its an image that can be resized to fit a box
2314 // around it
2315 String text = linker.getText();
2316 boolean isVector = text.equals("@v") || text.equals("@av");
2317 boolean isFrameImage = text.equals("@f");
2318 boolean isBitmap = false; // text.equals("@b");
2319
2320 if (isVector || isFrameImage || isBitmap) {
2321 Collection<Item> enclosure = FrameUtils.getEnclosingLineEnds(linker
2322 .getPosition());
2323 if (enclosure != null) {
2324 for (Item i : enclosure) {
2325 if (i.isLineEnd() && i.isEnclosed()) {
2326 if (!isVector)
2327 DisplayIO.getCurrentFrame().removeAllItems(
2328 enclosure);
2329 Rectangle rect = i.getEnclosedRectangle();
2330 long width = Math.round(rect.getWidth());
2331 if (isVector) {
2332 NumberFormat nf = Vector.getNumberFormatter();
2333 linker.setText(linker.getText()
2334 + ": "
2335 + nf.format((width / FrameGraphics
2336 .getMaxFrameSize().getWidth())));
2337 } else {
2338 linker.setText(linker.getText() + ": " + width);
2339 }
2340
2341 linker.setPosition(new Point(rect.x, rect.y));
2342 linker.setThickness(i.getThickness());
2343 linker.setBorderColor(i.getColor());
2344 break;
2345 }
2346 }
2347 if (!isVector)
2348 FrameMouseActions.deleteItems(enclosure, false);
2349 }
2350 }
2351
2352 boolean mouseMoved;
2353
2354 linker.getParent().setChanged(true);
2355
2356 Frame next = FrameIO.CreateNewFrame(linker);
2357
2358 linker.setLink("" + next.getNumber());
2359
2360 for (Item i : next.getTextItems()) {
2361 // Set the link for @Parent annotation item if one
2362 if (ItemUtils.startsWithTag(i, ItemUtils.TAG_PARENT)
2363 && i.getLink() == null) {
2364 Frame parent = linker.getParentOrCurrentFrame();
2365 i.setLink(parent.getName());
2366 } else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_BACKUP, false)) {
2367 // Delink backup tag if it is on the frame
2368 i.setLink(null);
2369 }
2370 }
2371
2372 FrameUtils.DisplayFrame(next, true, true);
2373 FrameUtils.setTdfcItem(linker);
2374
2375 mouseMoved = next.moveMouseToDefaultLocation();
2376 // this needs to be done if the user doesnt move the mouse before doing
2377 // tdfc while the cursor is set to the text cursor
2378 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2379 // This needs to be done in case there was a @start on the frame which
2380 // triggers changed to be set to true when it should stay as false
2381 next.setChanged(false);
2382 return mouseMoved;
2383 }
2384
2385 /**
2386 * Creates a new Text item and fills it with particular attributes extracted
2387 * from the given Item. Note: Users always have permission to extract
2388 * attributes, so it is not checked.
2389 *
2390 * @param toExtract
2391 * Item containing the Item to extract the attributes from.
2392 */
2393 private static void extractAttributes(Item toExtract) {
2394 if (toExtract == null || toExtract == null)
2395 return;
2396
2397 if (FreeItems.itemsAttachedToCursor())
2398 return;
2399
2400 Item attribs;
2401 Item item = toExtract;
2402 // Extract the frames attributes when the user clicks on the frame name
2403 FrameGraphics.changeHighlightMode(item, HighlightMode.None);
2404 if (item.isFrameName())
2405 attribs = AttributeUtils.extractAttributes(item.getParent());
2406 else {
2407 attribs = AttributeUtils.extractAttributes(item);
2408 }
2409
2410 if (attribs == null)
2411 MessageBay
2412 .displayMessage("All attributes of that item are default values.");
2413 else {
2414 // Give the attribute text item the color of the item for which
2415 // attributes are being extracted.
2416 // attribs.setColor(item.getColor());
2417 pickup(attribs);
2418 }
2419 }
2420
2421 public static void delete(Item toDelete) {
2422 boolean bRecalculate = false;
2423
2424 FrameUtils.setLastEdited(null);
2425 _offX = _offY = 0;
2426
2427 Frame currentFrame = DisplayIO.getCurrentFrame();
2428 // check if the user is pointing at the frame's framename
2429 if (toDelete != null && toDelete == currentFrame.getNameItem()) {
2430 currentFrame.clear(false);
2431 FrameGraphics.Repaint();
2432 return;
2433 }
2434
2435 // if the user is deleting items attached to the cursor
2436 if (FreeItems.itemsAttachedToCursor()) {
2437 // if this is an item-swap
2438 if (FreeItems.getInstance().size() == 1
2439 && FreeItems.getInstance().get(0) instanceof Text
2440 && toDelete != null && toDelete instanceof Text) {
2441
2442 // check permissions
2443 if (!toDelete.hasPermission(UserAppliedPermission.full)) {
2444 MessageBay
2445 .displayMessage("Insufficient permission to swap Item text");
2446 return;
2447 }
2448 Text anchored = (Text) toDelete;
2449 Text free = (Text) FreeItems.getInstance().get(0);
2450 SessionStats.DeletedItem(free);
2451 // List<String> temp = anchored.getText();
2452 anchored.setText(free.getText());
2453 anchored.setFormula(free.getFormula());
2454
2455 // free.setTextList(temp);
2456 FreeItems.getInstance().clear();
2457
2458 anchored.getParent().setChanged(true);
2459
2460 bRecalculate |= free.recalculateWhenChanged();
2461 bRecalculate |= anchored.recalculateWhenChanged();
2462
2463 // update the offset since the text has changed
2464 _offX = DisplayIO.getMouseX() - anchored.getX()
2465 + anchored.getOffset().x;
2466 _offY = getY() - anchored.getY() + anchored.getOffset().y;
2467 } else {
2468 // if shift is pressed delete the entire shape attached to the dot
2469 if(isShiftDown()) {
2470 List<Item> tmp = new ArrayList<Item>(FreeItems.getInstance());
2471 for(Item i : tmp) {
2472 // remove entire rectangles instead of just the corner
2473 if(i instanceof Dot) {
2474 FreeItems.getInstance().addAll(i.getAllConnected());
2475 for(Item j : i.getAllConnected()) {
2476 if(j instanceof Dot) {
2477 FreeItems.getInstance().addAll(j.getLines());
2478 }
2479 }
2480 }
2481 }
2482 }
2483 deleteItems(FreeItems.getInstance());
2484 }
2485 // reset the mouse cursor
2486 updateCursor();
2487 // the user is not pointing at an item
2488 } else if (toDelete == null) {
2489
2490 // if the user is pointing inside a closed shape, delete it
2491
2492 Collection<Item> items = null;
2493 // if shift is down, only delete the enclosing shape (ignore the items inside)
2494 if(isShiftDown()) {
2495 Collection<Item> tmp = FrameUtils.getEnclosingLineEnds();
2496 if(tmp != null) {
2497 items = new ArrayList<Item>();
2498 items.addAll(tmp);
2499 for(Item i : tmp) {
2500 if(i instanceof Dot) {
2501 items.addAll(((Dot)i).getLines());
2502 }
2503 }
2504 }
2505 } else {
2506 items = FrameUtils.getCurrentItems(null);
2507 }
2508
2509 if (items != null) {
2510 Collection<Item> toRemove = new LinkedHashSet<Item>(items
2511 .size());
2512 for (Item ip : items) {
2513 if (ip.hasPermission(UserAppliedPermission.full)) {
2514 // Only include lines if one of their enpoints are also
2515 // being removed
2516 if (ip instanceof Line) {
2517 Line l = (Line) ip;
2518 Item end = l.getEndItem();
2519 Item start = l.getStartItem();
2520
2521 // If one end of a line is being delted, remove the
2522 // other end if all its connecting lines are being
2523 // delted
2524 if (items.contains(end)) {
2525 if (!items.contains(start)
2526 && items.containsAll(start.getLines())) {
2527 toRemove.add(start);
2528 }
2529 } else if (items.contains(start)) {
2530 if (items.containsAll(end.getLines())) {
2531 toRemove.add(end);
2532 }
2533 } else {
2534 continue;
2535 }
2536 }
2537 toRemove.add(ip);
2538 }
2539 }
2540
2541 deleteItems(toRemove);
2542
2543 // reset the mouse cursor
2544 updateCursor();
2545 FrameGraphics.Repaint();
2546
2547 // otherwise this is an undo command
2548 } else {
2549 DisplayIO.getCurrentFrame().undo();
2550 }
2551 return;
2552 // this is a delete command
2553 } else {
2554 // check permissions
2555 if (!toDelete.hasPermission(UserAppliedPermission.full)) {
2556 Item editTarget = toDelete.getEditTarget();
2557 if (editTarget != toDelete
2558 && editTarget.hasPermission(UserAppliedPermission.full)) {
2559 toDelete = editTarget;
2560 } else {
2561 MessageBay
2562 .displayMessage("Insufficient permission to delete item");
2563 return;
2564 }
2565 }
2566
2567 Frame parent = toDelete.getParent();
2568 if (parent != null) {
2569 parent.setChanged(true);
2570 }
2571 Collection<Item> toUndo = null;
2572 if (toDelete.isLineEnd()) {
2573 // delete the entire connected shape if shift is down
2574 if(isShiftDown()) {
2575 List<Item> tmp = new ArrayList<Item>();
2576 tmp.add(toDelete);
2577 // remove entire shape instead of just the corner
2578 tmp.addAll(toDelete.getAllConnected());
2579 for(Item j : toDelete.getAllConnected()) {
2580 if(j instanceof Dot) {
2581 tmp.addAll(j.getLines());
2582 }
2583 }
2584 deleteItems(tmp);
2585 return;
2586 } else {
2587 toUndo = deleteLineEnd(toDelete);
2588 }
2589 // delete the entire connected shape if shift is down, unless we're hovering the end of the line
2590 } else if (toDelete instanceof WidgetEdge) { // must notify
2591 // widgets that they
2592 // are being deleted
2593 ((WidgetEdge) toDelete).getWidgetSource().onDelete();
2594 toUndo = toDelete.getConnected();
2595 } else if (toDelete instanceof Line && isShiftDown() ||
2596 toDelete.getHighlightMode() == Item.HighlightMode.Disconnect) {
2597 Line line = (Line) toDelete;
2598 Item start = line.getStartItem();
2599 Item end = line.getEndItem();
2600 Collection<Item> delete = new LinkedList<Item>();
2601 delete.add(toDelete);
2602 if (end.getLines().size() == 1) {
2603 delete.add(end);
2604 } else {
2605 end.removeLine(line);
2606 }
2607 if (start.getLines().size() == 1) {
2608 delete.add(start);
2609 } else {
2610 start.removeLine(line);
2611 }
2612 toUndo = delete;
2613 } else {
2614 bRecalculate |= toDelete.recalculateWhenChanged();
2615 toUndo = toDelete.getConnected(); // copy(toDelete.getConnected());
2616 }
2617 SessionStats.DeletedItems(toUndo);
2618 if (parent != null) {
2619 parent.addAllToUndo(toUndo);
2620 parent.removeAllItems(toUndo); // toDelete.getConnected()
2621 }
2622 // reset the mouse cursor
2623 updateCursor();
2624 if (parent != null)
2625 ItemUtils.EnclosedCheck(parent.getItems());
2626 if (toDelete.hasOverlay()) {
2627 FrameUtils.Parse(parent, false, false);
2628 FrameGraphics.requestRefresh(false);
2629 }
2630
2631 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2632
2633 }
2634
2635 currentFrame.notifyObservers(bRecalculate);
2636
2637 FrameGraphics.Repaint();
2638 }
2639
2640 public static void deleteItems(Collection<Item> itemList) {
2641 deleteItems(itemList, true);
2642 }
2643
2644 public static void deleteItems(Collection<Item> itemList, boolean addToUndo) {
2645 boolean bReparse = false;
2646 boolean bRecalculate = false;
2647
2648 SessionStats.DeletedItems(itemList);
2649 List<Frame> modifiedFrames = new LinkedList<Frame>();
2650 // Get a list of all the modified frames
2651 for (Item i : itemList) {
2652 Frame parent = i.getParent();
2653 if (parent != null)
2654 modifiedFrames.add(parent);
2655 i.setHighlightMode(HighlightMode.None);
2656 bReparse |= i.hasOverlay();
2657 bRecalculate |= i.recalculateWhenChanged();
2658 }
2659 // If they are all free items then add the current frame
2660 if (modifiedFrames.size() == 0) {
2661 modifiedFrames.add(DisplayIO.getCurrentFrame());
2662 }
2663
2664 Collection<Item> toUndo = new LinkedHashSet<Item>();
2665 // disconnect any connected items
2666 for (Item i : itemList) {
2667
2668 // Only delete the current item if have not already deleted.
2669 // This is especially important for heavy duty widgets - so they
2670 // do not have to expire several times per delete.
2671 if (toUndo.contains(i))
2672 continue;
2673
2674 // Make sure text items attached to cursor are reset back to the
2675 // transparency they should have.
2676 if (i instanceof Text) {
2677 ((Text) i).setAlpha(-1);
2678 }
2679
2680 if (i.getLines().size() > 0) {
2681
2682 Collection<Item> toDelete = deleteLineEnd(i);
2683 if (addToUndo) {
2684 // add the copied items to the undo stack
2685 for (Item itemToUndo : toDelete) {
2686
2687 if (!toUndo.contains(itemToUndo))
2688 toUndo.add(itemToUndo);
2689
2690 }
2691 }
2692 } else if (!toUndo.contains(i)) {
2693 if (addToUndo)
2694 toUndo.add(i); // Why was is this a copy
2695 }
2696 }
2697
2698 for (Frame f : modifiedFrames) {
2699 f.removeAllItems(itemList);
2700 ItemUtils.EnclosedCheck(f.getItems());
2701 }
2702 // TODO: How should undelete deal with undo when items are removed from
2703 // the current frame as well as the overlay frame
2704 Frame currentFrame = DisplayIO.getCurrentFrame();
2705 currentFrame.addAllToUndo(itemList);
2706 itemList.clear();
2707 if (bReparse) {
2708 FrameUtils.Parse(currentFrame, false, false);
2709 /*
2710 * TODO check if I need to recalculate even if reparse occurs, here
2711 * and in anchor, pickup etc
2712 */
2713 } else {
2714 currentFrame.notifyObservers(bRecalculate);
2715 }
2716
2717 }
2718
2719 private static Collection<Item> deleteLineEnd(Item lineEnd) {
2720
2721 if (lineEnd instanceof WidgetCorner) { // Brook
2722
2723 WidgetCorner wc = (WidgetCorner) lineEnd;
2724 Frame parent = wc.getWidgetSource().getParentFrame();
2725
2726 // Remove from the parent frame
2727 if (parent != null) {
2728 parent.removeAllItems(wc.getWidgetSource().getItems());
2729 }
2730
2731 wc.getWidgetSource().onDelete(); // Changes the widgets
2732 // corner/edges ID's...
2733
2734 return wc.getWidgetSource().getItems();
2735
2736 } else {
2737
2738 // // create a backup copy of the dot and its lines
2739 // List<Item> copy = copy(lineEnd.getConnected());
2740 //
2741 // // Remove lines from their anchored dots
2742 // // note: the line is kept so that it can be properly restored
2743 // for (Item ic : copy) {
2744 // if (ic instanceof Line) {
2745 // Line line = (Line) ic;
2746 // // Remove the line from the item that is not the copy of the
2747 // // line end being deletedF
2748 // if (!copy.contains(line.getStartItem()))
2749 // line.getStartItem().removeLine(line);
2750 // if (!copy.contains(line.getEndItem()))
2751 // line.getEndItem().removeLine(line);
2752 // }
2753 // }
2754
2755 Collection<Item> copy = lineEnd.getConnected();
2756
2757 // remove all lines being deleted
2758 for (Item ic : lineEnd.getConnected()) {
2759 if (ic instanceof Line
2760 && ((Line) ic).getOppositeEnd(lineEnd) != null) {
2761 Line line = (Line) ic;
2762
2763 // Invalidate the line to make sure we dont get any ghost
2764 // arrowheads.
2765 ic.invalidateAll();
2766
2767 Item d = line.getOppositeEnd(lineEnd);
2768 d.removeLine(line);
2769
2770 // if the dot was only part of one line, it can be
2771 // removed
2772 if (d.getLines().size() == 0) {
2773 if (d.getParent() != null)
2774 d.getParent().removeItem(d);
2775 if (!copy.contains(d))
2776 copy.add(d);
2777 }
2778
2779 if (lineEnd.getParent() != null)
2780 lineEnd.getParent().removeItem(ic);
2781 }
2782 }
2783 return copy;
2784 }
2785 }
2786
2787 private static void removeAllLinesExcept(Item from, Item but) {
2788 List<Line> lines = new LinkedList<Line>();
2789 lines.addAll(from.getLines());
2790 for (Line line : lines)
2791 if (line.getOppositeEnd(from) != but)
2792 from.removeLine(line);
2793
2794 List<Constraint> consts = new LinkedList<Constraint>();
2795 consts.addAll(from.getConstraints());
2796 for (Constraint c : consts)
2797 if (c.getOppositeEnd(from) != but)
2798 from.removeConstraint(c);
2799 }
2800
2801 public static Collection<Item> merge(List<Item> merger, Item mergee) {
2802 assert (mergee != null);
2803 if (mergee.isFrameName()) {
2804 return mergee.getParent().merge(merger);
2805 }
2806
2807 // if(mergee instanceof XRayable)
2808 // return merger;
2809
2810 // check for rectangle merging
2811 if (merger.size() == 3 && mergee.getLines().size() == 2) {
2812 Item corner = getShapeCorner(merger);
2813 // if this is a corner of a shape
2814 if (corner != null) {
2815 Collection<Item> allConnected = corner.getAllConnected();
2816 // Check if we are collapsing a rectangle
2817 if (allConnected.size() == 8 && allConnected.contains(mergee)) {
2818 DisplayIO.setCursorPosition(mergee.getPosition());
2819 DisplayIO.getCurrentFrame().removeAllItems(allConnected);
2820
2821 // find the point opposite corner...
2822 Item opposite = null;
2823 List<Line> lines = corner.getLines();
2824 for (Line l : lines) {
2825 allConnected.remove(l.getOppositeEnd(corner));
2826 }
2827 allConnected.remove(corner);
2828 for (Item i : allConnected) {
2829 if (i.isLineEnd()) {
2830 opposite = i;
2831 break;
2832 }
2833 }
2834 assert (opposite != null);
2835
2836 // check if the rectangle is small enough that it should be
2837 // collapsed to a single point
2838 int x1 = Math.abs(opposite.getX() - mergee.getX());
2839 int x2 = Math.abs(opposite.getY() - mergee.getY());
2840 int distance = (int) Math.sqrt(Math.pow(x1, 2)
2841 + Math.pow(x2, 2));
2842
2843 if (distance < RECTANGLE_TO_POINT_THRESHOLD) {
2844 mergee.removeAllConstraints();
2845 mergee.removeAllLines();
2846 mergee.setThickness(4 * mergee.getThickness());
2847 return mergee.getAllConnected();
2848 } else {
2849 removeAllLinesExcept(mergee, opposite);
2850 removeAllLinesExcept(opposite, mergee);
2851
2852 return mergee.getAllConnected();
2853 }
2854 }
2855 }
2856 }
2857
2858 List<Item> remain = new ArrayList<Item>();
2859 Item res = null;
2860
2861 for (Item i : merger) {
2862 if (!i.isVisible())
2863 continue;
2864 // check for link merging
2865 if (i instanceof Text
2866 && FrameIO.isValidFrameName((((Text) i).getFirstLine()))
2867 && FrameIO.canAccessFrame((((Text) i).getFirstLine()))) {
2868 // check that we can actually access the frame this link
2869 // corresponds to
2870 mergee.setLink(((Text) i).getFirstLine());
2871 } else {
2872 // check for attribute merging
2873 if (i instanceof Text && !i.isLineEnd()) {
2874 Text txt = (Text) i;
2875
2876 // if this is not an attribute merge
2877 if (!AttributeUtils.setAttribute(mergee, txt)) {
2878 // set mouse position for text merges
2879 if (mergee instanceof Text) {
2880 ((Text) mergee).insertText(txt.getText(), DisplayIO
2881 .getMouseX(), FrameMouseActions.getY());
2882 //Delete the item which had its text merged
2883 txt.delete();
2884 return remain;
2885 } else if (mergee instanceof WidgetCorner) {
2886 if (merger.size() == 1 && txt.getLink() != null) {
2887 // If the text item is linked then use that
2888 ((WidgetCorner) mergee).setLink(txt
2889 .getAbsoluteLink(), txt);
2890 } else {
2891 remain.addAll(merger);
2892 }
2893 return remain;
2894 } else if (mergee instanceof WidgetEdge) {
2895 if (merger.size() == 1 && txt.getLink() != null) {
2896 // If the text item is linked then use that
2897 ((WidgetEdge) mergee).setLink(txt
2898 .getAbsoluteLink(), txt);
2899 } else {
2900 remain.addAll(merger);
2901 }
2902 return remain;
2903 } else if (mergee instanceof Dot) {
2904 DisplayIO.setCursorPosition(mergee.getPosition());
2905 txt.setPosition(mergee.getPosition());
2906 txt.setThickness(mergee.getThickness());
2907 Frame parent = mergee.getParent();
2908 parent.removeItem(mergee);
2909 anchor(txt);
2910 // change the end points of the lines to the text
2911 // item
2912 while (mergee.getLines().size() > 0) {
2913 Line l = mergee.getLines().get(0);
2914 l.replaceLineEnd(mergee, txt);
2915 }
2916 break;
2917 }
2918
2919 // TODO tidy this up...
2920 // Dot override doesnt use the x and y coords
2921 // Text does... but could be removed
2922 res = mergee.merge(i, DisplayIO.getMouseX(), getY());
2923 if (res != null) {
2924 remain.add(res);
2925 }
2926 }
2927 } else {
2928 if (mergee.isLineEnd()) {
2929 DisplayIO.setCursorPosition(mergee.getPosition());
2930 }
2931 // Moving the cursor ensures shapes are anchored correctly
2932 res = mergee.merge(i, DisplayIO.getMouseX(), getY());
2933 if (res != null)
2934 remain.addAll(res.getConnected());
2935
2936 }
2937 }
2938 }
2939 updateCursor();
2940
2941 mergee.getParent().setChanged(true);
2942
2943 ItemUtils.EnclosedCheck(mergee.getParent().getItems());
2944 // Mike: Why does parse frame have to be called?!?
2945 FrameUtils.Parse(mergee.getParent());
2946
2947 return remain;
2948 }
2949
2950 /**
2951 * Picks up an item on a frame.
2952 *
2953 * @param toGrab
2954 * item to be picked up
2955 * @param removeItem
2956 * true if the item should be removed from the frame
2957 */
2958 public static void pickup(Item toGrab) {
2959 if (toGrab.isFrameName())
2960 return;
2961
2962 if (!toGrab.hasPermission(UserAppliedPermission.full)) {
2963 if (toGrab.getEditTarget() != toGrab) {
2964 pickup(toGrab.getEditTarget());
2965 } else {
2966 MessageBay
2967 .displayMessage("Insufficient permission pickup the item");
2968 }
2969 return;
2970 }
2971
2972 if (toGrab instanceof Circle)
2973 toGrab.setHighlightMode(HighlightMode.Connected);
2974 // Dont set the highlight mode if a vector is being picked up
2975 else if (toGrab.isVisible()) {
2976 toGrab.setHighlightMode(HighlightMode.Normal);
2977 }
2978
2979 // Brook: If the widget corner is being picked up. Instead refer to
2980 // picking up the edge for fixed-sized widgets so it is not so confusing
2981 if (toGrab instanceof WidgetCorner) {
2982 WidgetCorner wc = (WidgetCorner) toGrab;
2983 if (wc.getWidgetSource().isFixedSize()) {
2984 for (Item i : toGrab.getConnected()) {
2985 if (i instanceof WidgetEdge) {
2986 toGrab = i;
2987 break;
2988 }
2989 }
2990 }
2991 }
2992 pickup(toGrab.getConnected());
2993 }
2994
2995 public static void pickup(Collection<Item> toGrab) {
2996 if (toGrab == null || toGrab.size() == 0)
2997 return;
2998
2999 boolean bReparse = false;
3000 boolean bRecalculate = false;
3001
3002 Frame currentFrame = DisplayIO.getCurrentFrame();
3003 String currentFrameName = currentFrame.getName();
3004 Iterator<Item> iter = toGrab.iterator();
3005 while (iter.hasNext()) {
3006 Item i = iter.next();
3007 if (!i.hasPermission(UserAppliedPermission.full)) {
3008 iter.remove();
3009 continue;
3010 }
3011 if (i.equals(_lastHighlightedItem))
3012 _lastHighlightedItem = null;
3013
3014 bRecalculate |= i.recalculateWhenChanged();
3015 // i.setSelectedMode(SelectedMode.None);
3016 // Check if it has a relative link if so make it absolute
3017 i.setAbsoluteLink();
3018 // parent may be null
3019 if (i.getParent() != null) {
3020 i.getParent().removeItem(i);
3021 if (currentFrameName.equals(i.getParent().getName()))
3022 i.setParent(null);
3023 }
3024 FreeItems.getInstance().add(i);
3025 i.setFloating(true);
3026 // If its a vector pick up a copy of the stuff on the vector frame
3027 if (i.hasVector()) {
3028 bReparse = true;
3029
3030 Frame overlayFrame = FrameIO.LoadFrame(i.getAbsoluteLink());
3031 Collection<Item> copies = ItemUtils.CopyItems(overlayFrame
3032 .getNonAnnotationItems(false), i.getVector());
3033 for (Item copy : copies) {
3034 FreeItems.getInstance().add(copy);
3035 copy.setEditTarget(i);
3036 copy.setFloating(true);
3037 copy.setParent(null);
3038 // copy.setHighlightMode(HighlightMode.Connected);
3039 }
3040 }
3041 }
3042 currentFrame.change();
3043
3044 _lastHighlightedItem = null;
3045 updateCursor();
3046
3047 // if there are multiple items in the list, determine which to use for
3048 // offset calculations
3049 if (toGrab.size() > 1) {
3050 for (Item i : toGrab) {
3051 // MIKE: Movement goes haywire if these are removed because Line
3052 // class returns 0 for getX
3053 if (!(i instanceof Line) && !(i instanceof XRayable)) {
3054 _offX = DisplayIO.getMouseX() - i.getX() + i.getOffset().x;
3055 _offY = getY() - i.getY() + i.getOffset().y;
3056
3057 // make the offset item the first item in the list (so
3058 // move method knows which item to use)
3059 FreeItems.getInstance().set(
3060 FreeItems.getInstance().indexOf(i),
3061 FreeItems.getInstance().get(0));
3062 FreeItems.getInstance().set(0, i);
3063 break;
3064 }
3065 }
3066
3067 move(FreeItems.getInstance());
3068 ItemUtils.EnclosedCheck(toGrab);
3069 // otherwise, just use the first item
3070 } else if (toGrab.size() == 1) {
3071 Item soleItem = toGrab.iterator().next();
3072 _offX = DisplayIO.getMouseX() - soleItem.getX()
3073 + soleItem.getOffset().x;
3074 _offY = getY() - soleItem.getY() + soleItem.getOffset().y;
3075 // Now call move so that if we are on a message in the message box
3076 // It doesnt appear up the top of the scree!!
3077 move(toGrab);
3078 } else {
3079 MessageBay
3080 .displayMessage("Insufficient permission to pickup the items");
3081 }
3082 if (bReparse)
3083 FrameUtils.Parse(currentFrame, false, false);
3084 else
3085 currentFrame.notifyObservers(bRecalculate);
3086
3087 FrameGraphics.Repaint();
3088 }
3089
3090 private static Line createLine() {
3091 Frame current = DisplayIO.getCurrentFrame();
3092 // create the two endpoints
3093 Item end = DisplayIO.getCurrentFrame().createDot();
3094 Item start = DisplayIO.getCurrentFrame().createDot();
3095
3096 // create the Line
3097 Line line = new Line(start, end, current.getNextItemID());
3098 line.autoArrowheadLength();
3099
3100 // anchor the start
3101 anchor(start);
3102
3103 // attach the line to the cursor
3104 pickup(end);
3105 _lastHighlightedItem = null;
3106
3107 // TODO figure out how to get the end to highlight
3108 // end.setSelectedMode(SelectedMode.Normal);
3109 // end.setSelectedMode(SelectedMode.None);
3110
3111 return line;
3112 }
3113
3114 /**
3115 * Returns a list of copies of the list passed in
3116 *
3117 * @param toCopy
3118 * The list of items to copy
3119 * @return A List of copied Items
3120 */
3121 private static List<Item> copy(Collection<Item> toCopy) {
3122 return ItemUtils.CopyItems(toCopy);
3123 }
3124
3125 public static void anchor(Item toAnchor, boolean checkEnclosure) {
3126 // Only anchor items we have full permission over... ie dont anchor
3127 // vector items
3128 if (!toAnchor.hasPermission(UserAppliedPermission.full))
3129 return;
3130
3131 toAnchor.anchor();
3132
3133 if (checkEnclosure) {
3134 ItemUtils.EnclosedCheck(toAnchor.getParentOrCurrentFrame()
3135 .getItems());
3136 FrameGraphics.Repaint();
3137 }
3138 }
3139
3140 public static void anchor(Item toAnchor) {
3141 anchor(toAnchor, true);
3142 }
3143
3144 public static void anchor(Collection<Item> toAnchor) {
3145 boolean bReparse = false;
3146 boolean bRecalculate = false;
3147 // Need to make sure we check enclosure for overlays etc
3148 Set<Frame> checkEnclosure = new HashSet<Frame>();
3149
3150 // Create a clone of toAnchor since in the proccess of anchoring items
3151 // they can change the state of the toAnchor collection and thus create
3152 // concurrent modification exceptions.
3153 // This is especially needed for widgets being removed when anchored:
3154 // since they
3155 // currently are composed of 8 items this is vital. In the new revision
3156 // of
3157 // widgets being implemented as a single item this this can be
3158 // depreciated
3159 // however it may be useful for other applications.
3160 Collection<Item> toAnchorCopy = new ArrayList<Item>(toAnchor);
3161
3162 for (Item i : toAnchorCopy) {
3163 if (toAnchor.contains(i)) { // since to anchor could change while
3164 // anchoring
3165 // if (!i.hasVector())
3166 anchor(i, false);
3167 checkEnclosure.add(i.getParentOrCurrentFrame());
3168 bReparse |= i.hasOverlay();
3169 bRecalculate |= i.recalculateWhenChanged();
3170 }
3171 }
3172
3173 toAnchor.clear();
3174 // Check enclosure for all the frames of the items that were anchored
3175 for (Frame f : checkEnclosure) {
3176 ItemUtils.EnclosedCheck(f.getItems());
3177 }
3178
3179 Frame currentFrame = DisplayIO.getCurrentFrame();
3180 if (bReparse)
3181 FrameUtils.Parse(currentFrame, false, false);
3182 else {
3183 currentFrame.notifyObservers(bRecalculate);
3184 }
3185 FrameGraphics.Repaint();
3186 }
3187
3188 /*
3189 * private boolean mouseMovedRecently() { Date now = new Date();
3190 *
3191 * return now.getTime() - _lastMouseMovement.getTime() < 150; }
3192 */
3193
3194 public void mouseWheelMoved(MouseWheelEvent arg0) {
3195 Navigation.ResetLastAddToBack();
3196
3197 int clicks = arg0.getClickCount();
3198
3199 if (FreeItems.getInstance().size() == 2) {
3200 if ((FreeItems.getInstance().get(0).isLineEnd() && FreeItems
3201 .getInstance().get(1) instanceof Line)
3202 || (FreeItems.getInstance().get(1).isLineEnd() && FreeItems
3203 .getInstance().get(0) instanceof Line)) {
3204
3205 Line line;
3206 if (FreeItems.getInstance().get(0) instanceof Line)
3207 line = (Line) FreeItems.getInstance().get(0);
3208 else
3209 line = (Line) FreeItems.getInstance().get(1);
3210
3211 // User must do multiple clicks to toggle the line
3212 if (clicks == MOUSE_WHEEL_THRESHOLD) {
3213 if (arg0.isShiftDown())
3214 line.toggleArrowHeadRatio(arg0.getWheelRotation());
3215 else
3216 line.toggleArrowHeadLength(arg0.getWheelRotation());
3217 // line.getParent().change();
3218 FrameGraphics.Repaint();
3219 }
3220 }
3221 } else if (arg0.getWheelRotation() != 0 && arg0.isShiftDown()) {
3222
3223 FunctionKey rotationType = FunctionKey.SizeUp;
3224 if (arg0.getWheelRotation() > 0) {
3225 rotationType = FunctionKey.SizeDown;
3226 }
3227
3228 Item ip = FrameUtils.getCurrentItem();
3229 if (ip != null && clicks > 1) {
3230 float size = ip.getSize();
3231 if (ip instanceof Dot || ip instanceof Line
3232 || ip instanceof Circle) {
3233 size = ip.getThickness();
3234 }
3235 // base the number of clicks on the size of the object
3236 clicks = (int) Math.ceil(size / 20.0 * clicks);
3237 }
3238 FrameKeyboardActions.functionKey(rotationType, clicks, arg0
3239 .isShiftDown(), arg0.isControlDown());
3240
3241 } else if (clicks == MOUSE_WHEEL_THRESHOLD) {
3242 Item item = FrameUtils.getCurrentItem();
3243
3244 // if the user is not pointing to any item
3245 if (item == null) {
3246 FrameKeyboardActions.NextTextItem(null,
3247 arg0.getWheelRotation() > 0);
3248 return;
3249 }
3250
3251 if (item instanceof Line || item instanceof Circle) {
3252 // check permissions
3253 if (!item.hasPermission(UserAppliedPermission.full)) {
3254 MessageBay
3255 .displayMessage("Insufficient permission to edit the Line");
3256 return;
3257 }
3258 item.toggleDashed(arg0.getWheelRotation());
3259 item.getParent().change();
3260 FrameGraphics.Repaint();
3261 return;
3262 } else if (item instanceof Text) {
3263 FrameKeyboardActions.NextTextItem(item,
3264 arg0.getWheelRotation() > 0);
3265 }
3266 }
3267 }
3268
3269 /**
3270 *
3271 * @return the integer value for the last mouse button clicked.
3272 */
3273 public static int getLastMouseButton() {
3274 if (_lastMouseClick == null)
3275 return MouseEvent.NOBUTTON;
3276
3277 return _lastMouseClick.getButton();
3278 }
3279
3280 public static boolean isDelete(int modifiersEx) {
3281
3282 int onMask = MouseEvent.BUTTON3_DOWN_MASK
3283 | MouseEvent.BUTTON2_DOWN_MASK;
3284 return (modifiersEx & onMask) == onMask;
3285 }
3286
3287 public static boolean isGetAttributes(int modifiersEx) {
3288 int onMask = MouseEvent.BUTTON3_DOWN_MASK
3289 | MouseEvent.BUTTON1_DOWN_MASK;
3290 return (modifiersEx & onMask) == onMask;
3291 }
3292
3293 public static boolean isTwoClickNoOp(int modifiersEx) {
3294 int onMask = MouseEvent.BUTTON2_DOWN_MASK
3295 | MouseEvent.BUTTON1_DOWN_MASK;
3296 return (modifiersEx & onMask) == onMask;
3297 }
3298
3299 public static boolean wasDeleteClicked() {
3300 if (_lastMouseClick == null)
3301 return false;
3302 return isDelete(_lastMouseClickModifiers);
3303 }
3304
3305 public static void control(KeyEvent ke) {
3306 for (Item i : FreeItems.getInstance()) {
3307 i.invalidateCommonTrait(ItemAppearence.PreMoved);
3308 }
3309
3310 for (Item i : FreeItems.getCursor()) {
3311 i.invalidateCommonTrait(ItemAppearence.PreMoved);
3312 }
3313
3314 _controlDown = ke.isControlDown();
3315
3316 if (_controlDown) {
3317 // TODO why are these two lines needed?!?!
3318 // _offX = 0;
3319 // _offY = 0;
3320 } else {
3321 resetOffset();
3322 }
3323
3324 if (_mouseDown > 0 && _lastMouseDragged != null) {
3325 MouseEvent me = _lastMouseDragged;
3326 _lastMouseDragged = new MouseEvent(ke.getComponent(),
3327 MouseEvent.NOBUTTON, ke.getWhen(), ke.getModifiers(), me
3328 .getX(), me.getY(), 0, false);
3329 _instance.mouseDragged(_lastMouseDragged);
3330 } else if (_lastMouseMoved != null) {
3331 MouseEvent me = _lastMouseMoved;
3332 _lastMouseMoved = new MouseEvent(ke.getComponent(),
3333 MouseEvent.NOBUTTON, ke.getWhen(), ke.getModifiers(), me
3334 .getX(), me.getY(), 0, false);
3335 _instance.mouseMoved(_lastMouseMoved, true);
3336 }
3337 updateCursor();
3338
3339 Help.updateStatus();
3340
3341 for (Item i : FreeItems.getInstance()) {
3342 i.invalidateCommonTrait(ItemAppearence.PostMoved);
3343 }
3344
3345 for (Item i : FreeItems.getCursor()) {
3346 i.invalidateCommonTrait(ItemAppearence.PostMoved);
3347 }
3348 // TODO: Check why the new constrained line is not repainted immediately
3349 FrameGraphics.requestRefresh(true);
3350 FrameGraphics.refresh(true);
3351 }
3352
3353 public static int getX() {
3354 return Math.round(MouseX);
3355 }
3356
3357 public static int getY() {
3358 return Math.round(MouseY);
3359 }
3360
3361 public static Item getlastHighlightedItem() {
3362 return _lastHighlightedItem;
3363 }
3364
3365 public static Point getPosition() {
3366 return new Point(getX(), getY());
3367 }
3368
3369 public static Point getFreeItemsOffset() {
3370 return new Point(_offX, _offY);
3371 }
3372
3373 public static void shift(KeyEvent e) {
3374 _shiftDown = e.isShiftDown();
3375 Help.updateStatus();
3376 getInstance().refreshHighlights();
3377 }
3378}
Note: See TracBrowser for help on using the repository browser.