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

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

Justify text items on delete and undo/redo

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