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

Last change on this file since 942 was 942, checked in by bln4, 9 years ago

The beginnings of indirect controls.

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