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

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

Add status message area, add basic help text that gives info about what mouse buttons in the current context.
TODO: Add messages for button combos somehow (e.g. middle+right = delete) - this may pose a problem as my current method of displaying help text doesn't have room for text about button combos, so will need to rethink how to display the text.

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