source: trunk/src/org/expeditee/gio/gesture/StandardGestureActions.java@ 1319

Last change on this file since 1319 was 1319, checked in by bln4, 5 years ago

Fixed bug with drawing polylines that apparently I introduced in revision 1155 by commenting a line.

My original reason for commenting this line is unknown. While a commit comment is present, it is only talking about the primary change that I made in that commit which is the addition of pre and post gesture functions. A exploration of the revision history around that time shows that at the time I was working on making widgets work in the new refactored code. To this end I have tried playing with SampleWidget1, SampleWidget2 and a SampledTrack widget and have been unable to find any <<new>> problems.

File size: 143.6 KB
Line 
1package org.expeditee.gio.gesture;
2
3import java.text.NumberFormat;
4import java.util.ArrayList;
5import java.util.Collection;
6import java.util.Collections;
7import java.util.HashMap;
8import java.util.HashSet;
9import java.util.Iterator;
10import java.util.LinkedHashSet;
11import java.util.LinkedList;
12import java.util.List;
13import java.util.Set;
14
15import org.expeditee.actions.Actions;
16import org.expeditee.actions.Misc;
17import org.expeditee.actions.Navigation;
18import org.expeditee.core.Colour;
19import org.expeditee.core.Point;
20import org.expeditee.core.bounds.AxisAlignedBoxBounds;
21import org.expeditee.gio.ClipboardManager.ClipboardData;
22import org.expeditee.gio.EcosystemManager;
23import org.expeditee.gio.gesture.Gesture.GestureType;
24import org.expeditee.gio.gesture.data.ChangeColourGestureData;
25import org.expeditee.gio.gesture.data.ClickGestureData;
26import org.expeditee.gio.gesture.data.CreateItemGestureData;
27import org.expeditee.gio.gesture.data.CreateItemGestureData.ItemType;
28import org.expeditee.gio.gesture.data.DeleteGestureData;
29import org.expeditee.gio.gesture.data.FormatGestureData;
30import org.expeditee.gio.gesture.data.InsertStringGestureData;
31import org.expeditee.gio.gesture.data.ItemSpecificGestureData;
32import org.expeditee.gio.gesture.data.JustifyGestureData;
33import org.expeditee.gio.gesture.data.LinkGestureData;
34import org.expeditee.gio.gesture.data.NavigateFrameGestureData;
35import org.expeditee.gio.gesture.data.NavigateTextGestureData;
36import org.expeditee.gio.gesture.data.PanGestureData;
37import org.expeditee.gio.gesture.data.PickUpGestureData;
38import org.expeditee.gio.gesture.data.RefreshGestureData;
39import org.expeditee.gio.gesture.data.ScaleGestureData;
40import org.expeditee.gio.gesture.data.SelectAreaGestureData;
41import org.expeditee.gio.gesture.data.UndoGestureData;
42import org.expeditee.gio.gesture.data.ZoomGestureData;
43import org.expeditee.gio.input.KBMInputEvent.Key;
44import org.expeditee.gio.input.StandardInputEventListeners;
45import org.expeditee.gui.AttributeUtils;
46import org.expeditee.gui.AttributeValuePair;
47import org.expeditee.gui.ColorUtils;
48import org.expeditee.gui.DisplayController;
49import org.expeditee.gui.Frame;
50import org.expeditee.gui.FrameCreator;
51import org.expeditee.gui.FrameGraphics;
52import org.expeditee.gui.FrameIO;
53import org.expeditee.gui.FrameUtils;
54import org.expeditee.gui.FreeItems;
55import org.expeditee.gui.MessageBay;
56import org.expeditee.gui.Vector;
57import org.expeditee.io.ExpClipReader;
58import org.expeditee.io.ItemSelection;
59import org.expeditee.io.ItemSelection.ExpDataHandler;
60import org.expeditee.items.Circle;
61import org.expeditee.items.Constraint;
62import org.expeditee.items.Dot;
63import org.expeditee.items.Item;
64import org.expeditee.items.Item.HighlightMode;
65import org.expeditee.items.ItemUtils;
66import org.expeditee.items.Line;
67import org.expeditee.items.Picture;
68import org.expeditee.items.Text;
69import org.expeditee.items.UserAppliedPermission;
70import org.expeditee.items.XRayable;
71import org.expeditee.items.MagneticConstraint.MagneticConstraints;
72import org.expeditee.items.widgets.ButtonWidget;
73import org.expeditee.items.widgets.Widget;
74import org.expeditee.items.widgets.WidgetCorner;
75import org.expeditee.items.widgets.WidgetEdge;
76import org.expeditee.settings.UserSettings;
77import org.expeditee.settings.experimental.ExperimentalFeatures;
78import org.expeditee.settings.templates.TemplateSettings;
79import org.expeditee.stats.SessionStats;
80
81public class StandardGestureActions implements GestureListener {
82 /** The gesture types offered by core Expeditee. */
83 //@formatter:off
84 public enum StandardGestureType {
85 ACTION, // Make action, remove action
86 CALCULATE,
87 CHANGE_COLOUR, // F3
88 CLICK,
89 COPY,
90 CREATE_ITEM,
91 CUT,
92 DELETE,
93 DROP_DOWN, // F0 (ESC) (Positions the cursor below the current text item. Similar to enter in traditional editors.)
94 EXTRACT_ATTRIBUTES,
95 EXTRUDE,
96 FORMAT,
97 INSERT_DATE, // F5
98 INSERT_STRING,
99 JUSTIFY,
100 LINK, // Create link, remove link, follow link
101 MAKE_CIRCLE,
102 MOVE_CURSOR,
103 NAVIGATE_FRAME, // Forward/back/next/previous frame
104 NAVIGATE_TEXT,
105 NEW_FRAMESET, // F6
106 NEXT_ITEM,
107 PAN,
108 PASTE,
109 PICK_UP,
110 PLACE,
111 REFRESH, // F12
112 ROTATE_DISPLAY_MODE, // F9
113 ROTATE_FONT_FAMILY, // F8
114 ROTATE_FONT_STYLE, // F7
115 SAVE, // F11
116 SCALE, // F1 & F2
117 SELECT_AREA,
118 SPLIT_TEXT,
119 TOGGLE_ANNOTATION, // F4
120 TOGGLE_ARROWHEAD,
121 TOGGLE_BOLD,
122 TOGGLE_ITALICS,
123 TOGGLE_ITEMS_MARK, // Show/hide the little circle indicating the item has a link and/or action
124 TOGGLE_XRAY_MODE, // F10
125 UNDO,
126 ZOOM
127 }
128 //@formatter:on
129
130 private static StandardGestureActions _instance = null;
131
132 public static StandardGestureActions getInstance() {
133 if (_instance == null) {
134 _instance = new StandardGestureActions();
135 }
136
137 return _instance;
138 }
139
140 private HashMap<StandardGestureType, GestureType> _gestureTypes;
141
142 private HashMap<GestureType, GestureAction> _actions;
143
144 private StandardGestureActions() {
145 _gestureTypes = new HashMap<StandardGestureType, GestureType>();
146 initialiseGestureTypes();
147 _actions = new HashMap<GestureType, GestureAction>();
148 initialiseActions();
149 }
150
151 @Override
152 public void preGesture(final Gesture gesture) {
153 final GestureAction action = getGestureAction(gesture.getType());
154 if (action == null) {
155 return;
156 }
157 action.prepare(gesture);
158 }
159
160 @Override
161 public void onGesture(final Gesture gesture) {
162 final GestureAction action = getGestureAction(gesture.getType());
163 if (action == null) {
164 return;
165 }
166 action.exec(gesture);
167 }
168
169 @Override
170 public void postGesture(final Gesture gesture) {
171 final GestureAction action = getGestureAction(gesture.getType());
172 if (action == null) {
173 return;
174 }
175 action.finalise(gesture);
176 }
177
178 private void setGestureAction(GestureType type, GestureAction action) {
179 if (type == null) {
180 return;
181 }
182
183 _actions.put(type, action);
184 }
185
186 private GestureAction getGestureAction(GestureType type) {
187 if (type == null) {
188 return null;
189 }
190
191 return _actions.get(type);
192 }
193
194 /**
195 * Sets up the standard gesture actions.
196 */
197 private void initialiseActions() {
198 // Set the ACTION action
199 setGestureAction(gestureType(StandardGestureType.ACTION), new GestureAction() {
200 @Override
201 public void exec(Gesture gesture) {
202 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
203 Item current = data.getCurrentItem();
204 // If its not linked then link it to its self
205 if (current instanceof Text) {
206 if (!current.hasAction()) {
207 String text = ((Text) current).getText().trim();
208 // first trim the annotation
209 if (text.startsWith("@")) {
210 text = text.substring(1).trim();
211 }
212 // then trim the action
213 String lowerCaseText = text.toLowerCase();
214 if (lowerCaseText.startsWith("a:")) {
215 text = text.substring("a:".length()).trim();
216 } else if (lowerCaseText.startsWith("action:")) {
217 text = text.substring("action:".length()).trim();
218
219 }
220 current.setAction(text);
221 } else {
222 // If its linked remove the link
223 current.setActions(null);
224 }
225 }
226 }
227 });
228
229 // Set the CALCULATE action
230 setGestureAction(gestureType(StandardGestureType.CALCULATE), new GestureAction() {
231 @Override
232 public void exec(Gesture gesture) {
233 Item on = ((ItemSpecificGestureData) gesture.getData()).getCurrentItem();
234 if (on != null) {
235 calculateItem(on);
236 DisplayController.requestRefresh(true);
237 }
238 }
239 });
240
241 // Set the CHANGE_COLOUR action
242 setGestureAction(gestureType(StandardGestureType.CHANGE_COLOUR), new GestureAction() {
243 @Override
244 public void exec(Gesture gesture) {
245 ChangeColourGestureData data = (ChangeColourGestureData) gesture.getData();
246 Item currentItem = data.getCurrentItem();
247 Collection<Item> currentItems = data.getCurrentItems();
248 Collection<Item> enclosure = data.getEnclosure();
249 boolean shouldSetTransparent = data.shouldSetTransparent();
250 boolean shouldSetSecondaryColour = data.shouldSetSecondaryColour();
251 if (currentItem == null && currentItems != null && currentItems.size() > 0) {
252 Collection<Item> connected = data.getEnclosure().iterator().next().getAllConnected();
253 if (connected.size() > 0) {
254 for (Item d : enclosure) {
255 if (shouldSetSecondaryColour) {
256 SetGradientColor(d, shouldSetTransparent);
257 } else {
258 SetFillColor(d, shouldSetTransparent);
259 }
260 break;
261 }
262 }
263 } else if (currentItem != null) {
264 SetColor(currentItem, shouldSetTransparent, shouldSetSecondaryColour);
265 }
266 }
267 });
268
269 // Set the CLICK action
270 setGestureAction(gestureType(StandardGestureType.CLICK), new GestureAction() {
271 @Override
272 public void exec(Gesture gesture) {
273 ClickGestureData data = (ClickGestureData) gesture.getData();
274 click(data.getCurrentItem(), data.getCurrentItems(), data.getPosition());
275 }
276 });
277
278 // Set the COPY action
279 setGestureAction(gestureType(StandardGestureType.COPY), new GestureAction() {
280 @Override
281 public void exec(Gesture gesture) {
282 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
283 Item current = data.getCurrentItem();
284 if (FreeItems.hasItemsAttachedToCursor()) {
285 ItemSelection.copyClone();
286 } else if (current instanceof Text) {
287 copyItemToClipboard(current);
288 }
289 }
290 });
291
292 // Set the CREATE_ITEM action
293 setGestureAction(gestureType(StandardGestureType.CREATE_ITEM), new GestureAction() {
294 @Override
295 public void exec(Gesture gesture) {
296 CreateItemGestureData data = (CreateItemGestureData) gesture.getData();
297 if (data.getItemType() == ItemType.LINE) {
298 newLineAction(data.getPosition(), data.getCurrentItem());
299 } else if (data.getItemType() == ItemType.BOX) {
300 createRectangleAction();
301 }
302 // TODO: Complete. cts16
303 }
304 });
305
306 // Set the CUT action
307 setGestureAction(gestureType(StandardGestureType.CUT), new GestureAction() {
308 @Override
309 public void exec(Gesture gesture) {
310 ItemSelection.cut();
311 }
312 });
313
314 // Set the DELETE action
315 setGestureAction(gestureType(StandardGestureType.DELETE), new GestureAction() {
316 @Override
317 public void exec(Gesture gesture) {
318 DeleteGestureData data = (DeleteGestureData) gesture.getData();
319 if (FreeItems.getInstance().isEmpty() && data.getCurrentItem() == null && data.getCurrentItems() == null
320 && data.getEnclosure() == null) {
321 final Gesture undoGesture = data.getAlternateMode()
322 ? new Gesture(StandardGestureActions.getInstance().gestureType(StandardGestureType.UNDO),
323 new UndoGestureData(true))
324 : new Gesture(StandardGestureActions.getInstance().gestureType(StandardGestureType.UNDO),
325 new UndoGestureData(false));
326 getGestureAction(gestureType(StandardGestureType.UNDO)).exec(undoGesture);
327 } else {
328 delete(data.getCurrentItem(), data.getCurrentItems(), data.getEnclosure(), data.getAlternateMode());
329 }
330 }
331 });
332
333 // Set the DROP_DOWN action
334 setGestureAction(gestureType(StandardGestureType.DROP_DOWN), new GestureAction() {
335 @Override
336 public void exec(Gesture gesture) {
337
338 // Get the enclosed items
339 Item on = FrameUtils.getCurrentItem();
340 if (on == null) {
341 Collection<Item> enclosed = FrameUtils.getCurrentItems(on);
342
343 Collection<Item> lineEnds;
344 Item firstConnected;
345 if (enclosed != null && enclosed.size() > 0) {
346 // ensure only one dot\line is present in the list
347 lineEnds = FrameUtils.getEnclosingLineEnds();
348 firstConnected = lineEnds.iterator().next();
349
350 // Get the last text item and drop from in
351 Item lastText = null;
352 for (Item i : enclosed) {
353 if (i instanceof Text) {
354 lastText = i;
355 }
356 }
357
358 // Drop from the item if there was a text item in the box
359 if (lastText != null) {
360 Drop(lastText, false);
361 } else {
362 // Move to the top of the box
363 AxisAlignedBoxBounds rect = firstConnected.getBoundingBox();
364 int newX = rect.getMinX() + Text.MARGIN_LEFT;
365 int newY = Text.MARGIN_LEFT + rect.getMinY()
366 + DisplayController.getCurrentFrame().getItemTemplate().getBoundsHeight();
367 moveCursorAndFreeItems(newX, newY);
368 // TODO can resetOffset be put inside moveCursorAndFreeItems
369 resetOffset();
370 }
371 return;
372 }
373 }
374
375 Drop(on, false);
376 }
377 });
378
379 // Set the EXTRACT_ATTRIBUTES action
380 setGestureAction(gestureType(StandardGestureType.EXTRACT_ATTRIBUTES), new GestureAction() {
381 @Override
382 public void exec(Gesture gesture) {
383 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
384 extractAttributes(data.getCurrentItem());
385 }
386 });
387
388 // Set the FORMAT action
389 setGestureAction(gestureType(StandardGestureType.FORMAT), new GestureAction() {
390 @Override
391 public void exec(Gesture gesture) {
392 FormatGestureData data = (FormatGestureData) gesture.getData();
393 if (data.getHorizontal()) {
394 Actions.LegacyPerformActionCatchErrors(data.getFrame(), null, "HFormat");
395 } else {
396 Actions.LegacyPerformActionCatchErrors(data.getFrame(), null, "Format");
397 }
398 }
399 });
400
401 // Set the INSERT_DATE action
402 setGestureAction(gestureType(StandardGestureType.INSERT_DATE), new GestureAction() {
403 @Override
404 public void exec(Gesture gesture) {
405 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
406 Item currentItem = data.getCurrentItem();
407 Collection<Item> currentItems = data.getCurrentItems();
408 if (currentItem == null && currentItems != null && currentItems.size() > 0) {
409 Text.AddDate(data.getEnclosure().iterator().next());
410 } else {
411 Text.AddDate(currentItem);
412 }
413 }
414 });
415
416 // Set the INSERT_STRING action
417 setGestureAction(gestureType(StandardGestureType.INSERT_STRING), new GestureAction() {
418 @Override
419 public void exec(Gesture gesture) {
420 InsertStringGestureData data = (InsertStringGestureData) gesture.getData();
421 for (char c : data.getString().toCharArray()) {
422 processChar(c, data.isShiftDown());
423 }
424 }
425 });
426
427 // Set the JUSTIFY action
428 setGestureAction(gestureType(StandardGestureType.JUSTIFY), new GestureAction() {
429 @Override
430 public void exec(Gesture gesture) {
431 JustifyGestureData data = (JustifyGestureData) gesture.getData();
432 Text textCurrent = data.getCurrentTextItem();
433 if (textCurrent == null) {
434 for (Text t : data.getFrame().getBodyTextItems(false)) {
435 if (data.getResetWidth()) {
436 t.setWidth(null);
437 }
438 t.justify(true);
439 }
440 } else {
441 if (data.getResetWidth()) {
442 textCurrent.setWidth(null);
443 }
444 textCurrent.justify(true);
445 }
446 }
447 });
448
449 // Set the LINK action
450 setGestureAction(gestureType(StandardGestureType.LINK), new GestureAction() {
451 @Override
452 public void exec(Gesture gesture) {
453 LinkGestureData data = (LinkGestureData) gesture.getData();
454 Item current = data.getCurrentItem();
455 boolean follow = data.getFollow();
456 // If its not linked then link it to itself
457 if (current instanceof Text && current.getLink() == null) {
458 String text = ((Text) current).getText();
459 // Ignore the annotation if there is one
460 if (text.charAt(0) == '@') {
461 text = text.substring(1);
462 }
463
464 if (FrameIO.isValidFrameName(text)) {
465 current.setLink(text);
466 } else if (FrameIO.isValidFramesetName(text)) {
467 current.setLink(text + '1');
468 }
469 } else if (current != null && !follow) {
470 // If its linked remove the link
471 current.setLink(null);
472 }
473 if (current != null && current.getLink() != null && follow) {
474 Navigation.Goto(current.getAbsoluteLink());
475 return;
476 }
477 }
478 });
479
480 // Set the MAKE_CIRCLE action
481 setGestureAction(gestureType(StandardGestureType.MAKE_CIRCLE), new GestureAction() {
482 @Override
483 public void exec(Gesture gesture) {
484 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
485 Item current = data.getCurrentItem();
486 Text item = null;
487 // Check if its a line to be turned into a circle
488 if (current instanceof Dot && current.getLines().size() == 1) {
489 item = Item.replaceDot(current, '@');
490 } else if (current instanceof Line && current.getAllConnected().size() == 3) {
491 Item end = ((Line) current).getEndItem();
492 if (end instanceof Dot) {
493 item = Item.replaceDot(end, '@');
494 } else if (end instanceof Text) {
495 item = (Text) end;
496 }
497 }
498 if (item != null) {
499 item.setText("@c");
500 DisplayController.setCursorPosition(item.getX(), item.getY());
501 FrameUtils.setLastEdited(null);
502 Refresh();
503 }
504 }
505 });
506
507 // Set the MOVE_CURSOR action
508 setGestureAction(gestureType(StandardGestureType.MOVE_CURSOR), new GestureAction() {
509 @Override
510 public void exec(Gesture gesture) {
511 if (gesture.isRobotic()) {
512 EcosystemManager.getInputManager().setCursorPosition(gesture.getData().getPosition());
513 } else {
514 mouseMoved(gesture.getData().getPosition());
515 }
516 }
517 });
518
519 // Set the NAVIGATE_FRAME action
520 setGestureAction(gestureType(StandardGestureType.NAVIGATE_FRAME), new GestureAction() {
521 @Override
522 public void exec(Gesture gesture) {
523 NavigateFrameGestureData data = (NavigateFrameGestureData) gesture.getData();
524 switch (data.getNavigateTo()) {
525 case BACK_FRAME:
526 DisplayController.Back();
527 break;
528 case EARLIEST_FRAME:
529 while (DisplayController.Back()) {
530 ;
531 }
532 break;
533 case FIRST_FRAME:
534 Navigation.ZeroFrame();
535 Navigation.NextFrame();
536 break;
537 case FORWARD_FRAME:
538 DisplayController.Forward();
539 break;
540 case LAST_FRAME:
541 Navigation.LastFrame();
542 break;
543 case LATEST_FRAME:
544 while (DisplayController.Forward()) {
545 ;
546 }
547 break;
548 case NEXT_FRAME:
549 Navigation.NextFrame(false);
550 break;
551 case PREVIOUS_FRAME:
552 Navigation.PreviousFrame(false);
553 break;
554 case SPECIFIC_FRAME:
555 Navigation.Goto(data.getCurrentFrameset() + data.getCurrentFrameNumber());
556 break;
557 case ZERO_FRAME:
558 Navigation.ZeroFrame();
559 break;
560 default:
561 break;
562 }
563 }
564 });
565
566 // Set the NAVIGATE_TEXT action
567 setGestureAction(gestureType(StandardGestureType.NAVIGATE_TEXT), new GestureAction() {
568 @Override
569 public void exec(Gesture gesture) {
570 NavigateTextGestureData data = (NavigateTextGestureData) gesture.getData();
571 switch (data.getNavigateTo()) {
572 case ABOVE_LINE:
573 move(Text.UP, data.getSelecting(), false);
574 break;
575 case BELOW_LINE:
576 move(Text.DOWN, data.getSelecting(), false);
577 break;
578 case LINE_END:
579 move(Text.LINE_END, data.getSelecting(), false);
580 break;
581 case LINE_HOME:
582 move(Text.LINE_HOME, data.getSelecting(), false);
583 break;
584 case NEXT_CHARACTER:
585 move(Text.RIGHT, data.getSelecting(), false);
586 break;
587 case NEXT_TEXT_ITEM:
588 NextTextItem(data.getCurrentItem(), true);
589 break;
590 case NEXT_WORD:
591 DisplayController.setTextCursor((Text) data.getCurrentItem(), Text.RIGHT, false,
592 data.getSelecting(), true, true);
593 break;
594 case PARAGRAPH_END:
595 move(Text.END, data.getSelecting(), true);
596 break;
597 case PARAGRAPH_HOME:
598 move(Text.HOME, data.getSelecting(), true);
599 break;
600 case PREVIOUS_CHARACTER:
601 move(Text.LEFT, data.getSelecting(), false);
602 break;
603 case PREVIOUS_TEXT_ITEM:
604 NextTextItem(data.getCurrentItem(), false);
605 break;
606 case PREVIOUS_WORD:
607 DisplayController.setTextCursor((Text) data.getCurrentItem(), Text.LEFT, false, data.getSelecting(),
608 true, true);
609 break;
610 default:
611 break;
612 }
613 }
614 });
615
616 // Set the NEW_FRAMESET action
617 setGestureAction(gestureType(StandardGestureType.NEW_FRAMESET), new GestureAction() {
618 @Override
619 public void exec(Gesture gesture) {
620 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
621 if (data.getCurrentItem() != null) {
622 CreateFrameset(data.getCurrentItem());
623 }
624 }
625 });
626
627 // Set the NEXT_ITEM action
628 setGestureAction(gestureType(StandardGestureType.NEXT_ITEM), new GestureAction() {
629 @Override
630 public void exec(Gesture gesture) {
631 // GestureData data = (GestureData) gesture.getData();
632 // TODO: Complete. cts16
633 }
634 });
635
636 // Set the PAN action
637 setGestureAction(gestureType(StandardGestureType.PAN), new GestureAction() {
638 @Override
639 public void exec(Gesture gesture) {
640 PanGestureData data = (PanGestureData) gesture.getData();
641 Misc.pan(DisplayController.getCurrentFrame(), data.getPanDelta().getX(), data.getPanDelta().getY());
642 }
643 });
644
645 // Set the PASTE action
646 setGestureAction(gestureType(StandardGestureType.PASTE), new GestureAction() {
647 @Override
648 public void exec(Gesture gesture) {
649 ItemSelection.paste();
650 }
651 });
652
653 // Set the PICK_UP action
654 setGestureAction(gestureType(StandardGestureType.PICK_UP), new GestureAction() {
655 @Override
656 public void exec(Gesture gesture) {
657 PickUpGestureData data = (PickUpGestureData) gesture.getData();
658 if (!data.wasDragged()) {
659 if (data.getCurrentItem() != null) {
660 handlePickup(data.getCurrentItem(), data.getPosition(), data.getCopy(), data.getExtrude());
661 } else if (data.getCurrentItems() != null) {
662 handlePickup(data.getCurrentItems(), data.getEnclosure(), data.getPosition(), data.getCopy());
663 }
664 } else {
665 Item item = data.getCurrentItem();
666 if (item instanceof Text) {
667 Text text = (Text) item;
668 text.setSelectionStart(data.getDraggedFrom());
669 text.setSelectionEnd(data.getPosition());
670 text.setSelectionColour(data.getCopy() ? Text.RANGE_COPY_COLOUR : Text.RANGE_CUT_COLOUR);
671 DisplayController.setTextCursor(text, Text.NONE, false, false, false, false);
672 if (data.getFinishedDragging()) {
673 pickupRange(text, data.getPosition(), data.getCopy(), data.getInheritAttributes());
674 } else {
675 DisplayController.requestRefresh(true);
676 }
677 } else if (item instanceof Picture && data.getCopy()) {
678 Picture picture = (Picture) item;
679 picture.setStartCrop(data.getDraggedFrom());
680 picture.setEndCrop(data.getPosition());
681 picture.setShowCrop(true);
682 picture.setHighlightMode(Item.HighlightMode.None);
683 picture.setHighlightColor(Item.DEPRESSED_HIGHLIGHT);
684 if (data.getFinishedDragging()) {
685 if (picture.isCropTooSmall()) {
686 return;
687 }
688 Picture cropped = picture.copy();
689 cropped.setParent(null);
690 // move the cropped image to the cursor
691 int width = cropped.getWidth();
692 int height = cropped.getHeight();
693 if (cropped.getSource().getX() + width < data.getPosition().getX()) {
694 cropped.getSource().setX(data.getPosition().getX() - width);
695 }
696 if (cropped.getSource().getY() + height < data.getPosition().getY()) {
697 cropped.getSource().setY(data.getPosition().getY() - height);
698 }
699 pickup(cropped);
700 // MIKE put the code below up here
701 picture.clearCropping();
702 FrameGraphics.changeHighlightMode(picture, HighlightMode.None);
703 }
704 DisplayController.requestRefresh(true);
705 }
706 }
707 }
708 });
709
710 // Set the PLACE action
711 setGestureAction(gestureType(StandardGestureType.PLACE), new GestureAction() {
712
713 private boolean shouldClearFreeItems = false;
714
715 @Override
716 public void exec(final Gesture gesture) {
717 final PickUpGestureData data = (PickUpGestureData) gesture.getData();
718 if (data.getCopy()) {
719 shouldClearFreeItems = false;
720 copyPlace(data.getCurrentItem());
721 } else {
722 if (doMerging(data.getCurrentItem())) {
723 shouldClearFreeItems = true;
724 deleteItemsAction(data.getCurrentItem());
725 } else {
726 shouldClearFreeItems = true;
727 anchorFreeItemsAction(data.getCurrentItems());
728 }
729 }
730 }
731
732 @Override
733 public void finalise(final Gesture gesture) {
734 if (shouldClearFreeItems) {
735 FreeItems.getInstance().clear();
736 }
737 }
738 });
739
740 // Set the REFRESH action
741 setGestureAction(gestureType(StandardGestureType.REFRESH), new GestureAction() {
742 @Override
743 public void exec(Gesture gesture) {
744 RefreshGestureData data = (RefreshGestureData) gesture.getData();
745 if (data.shouldReloadFrameFirst()) {
746 MessageBay.displayMessage("Loaded in data from external edit made my another user.");
747 DisplayController.Reload(DisplayController.TwinFramesSide.LEFT);
748 }
749 Refresh(data.shouldRefreshFrameSize());
750 }
751 });
752
753 // Set the ROTATE_DISPLAY_MODE action
754 setGestureAction(gestureType(StandardGestureType.ROTATE_DISPLAY_MODE), new GestureAction() {
755 @Override
756 public void exec(Gesture gesture) {
757 DisplayController.rotateAudienceModes();
758 }
759 });
760
761 // Set the ROTATE_FONT_FAMILY action
762 setGestureAction(gestureType(StandardGestureType.ROTATE_FONT_FAMILY), new GestureAction() {
763 @Override
764 public void exec(Gesture gesture) {
765 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
766 Item currentItem = data.getCurrentItem();
767 Collection<Item> currentItems = data.getCurrentItems();
768 if (currentItem == null && currentItems != null && currentItems.size() > 0) {
769 ToggleFontFamily(data.getEnclosure().iterator().next());
770 } else if (currentItem != null) {
771 ToggleFontFamily(currentItem);
772 }
773 }
774 });
775
776 // Set the ROTATE_FONT_STYLE action
777 setGestureAction(gestureType(StandardGestureType.ROTATE_FONT_STYLE), new GestureAction() {
778 @Override
779 public void exec(Gesture gesture) {
780 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
781 Item currentItem = data.getCurrentItem();
782 Collection<Item> currentItems = data.getCurrentItems();
783 if (currentItem == null && currentItems != null && currentItems.size() > 0) {
784 ToggleFontStyle(data.getEnclosure().iterator().next());
785 } else if (currentItem != null) {
786 ToggleFontStyle(currentItem);
787 }
788 }
789 });
790
791 // Set the SAVE action
792 setGestureAction(gestureType(StandardGestureType.SAVE), new GestureAction() {
793 @Override
794 public void exec(Gesture gesture) {
795 Save();
796 }
797 });
798
799 // Set the SCALE action
800 setGestureAction(gestureType(StandardGestureType.SCALE), new GestureAction() {
801 @Override
802 public void exec(Gesture gesture) {
803 ScaleGestureData data = (ScaleGestureData) gesture.getData();
804 boolean scaleAroundCursor = data.getPosition() != null;
805 int scaleFactor = data.getScaleFactor();
806 Item currentItem = data.getCurrentItem();
807 Collection<Item> currentItems = data.getCurrentItems();
808 if (currentItem == null && currentItems != null && currentItems.size() > 0) {
809 SetSize(data.getEnclosure().iterator().next(), scaleFactor, false, true, scaleAroundCursor);
810 } else if (currentItem != null) {
811 SetSize(currentItem, scaleFactor, true, false, scaleAroundCursor);
812 if (currentItem instanceof Text) {
813 DisplayController.setTextCursor((Text) currentItem, Text.NONE, true, false, false, true);
814 }
815 }
816 }
817 });
818
819 // Set the SELECT_AREA action
820 setGestureAction(gestureType(StandardGestureType.SELECT_AREA), new GestureAction() {
821 @Override
822 public void exec(Gesture gesture) {
823 SelectAreaGestureData data = (SelectAreaGestureData) gesture.getData();
824 Item item = data.getCurrentItem();
825 if (item != null && item instanceof Text) {
826 Text text = (Text) item;
827 text.setSelectionStart(data.getDraggedFrom());
828 text.setSelectionEnd(data.getPosition());
829 text.setSelectionColour(Text.RANGE_SELECT_COLOUR);
830 DisplayController.setTextCursor(text, Text.NONE, false, false, false, false);
831 DisplayController.requestRefresh(true);
832 }
833 }
834 });
835
836 // Set the SPLIT_TEXT action
837 setGestureAction(gestureType(StandardGestureType.SPLIT_TEXT), new GestureAction() {
838 @Override
839 public void exec(Gesture gesture) {
840 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
841 Text text = data.getCurrentTextItem();
842 if (text == null) {
843 return;
844 }
845 List<String> textLines = text.getTextList();
846 if (textLines.size() <= 1) {
847 return;
848 }
849 // remove all except the first line of text from the item being split
850 text.setText(textLines.get(0));
851 int y = text.getY();
852 Frame parent = text.getParent();
853 for (int i = 1; i < textLines.size(); i++) {
854 Text newText = text.copy();
855 newText.setText(textLines.get(i));
856 y += newText.getBoundsHeight();
857 newText.setY(y);
858 // update the items ID to prevent conflicts with the current frame
859 if (parent != null) {
860 newText.setID(parent.getNextItemID());
861 parent.addItem(newText);
862 }
863 }
864 }
865 });
866
867 // Set the TOGGLE_ANNOTATION action
868 setGestureAction(gestureType(StandardGestureType.TOGGLE_ANNOTATION), new GestureAction() {
869 @Override
870 public void exec(Gesture gesture) {
871 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
872 Item currentItem = data.getCurrentItem();
873 Collection<Item> currentItems = data.getCurrentItems();
874 if (currentItem == null && currentItems != null && currentItems.size() > 0) {
875 ToggleAnnotation(data.getEnclosure().iterator().next());
876 } else if (currentItem != null) {
877 ToggleAnnotation(currentItem);
878 }
879 }
880 });
881
882 // Set the TOGGLE_ARROWHEAD action
883 setGestureAction(gestureType(StandardGestureType.TOGGLE_ARROWHEAD), new GestureAction() {
884 @Override
885 public void exec(Gesture gesture) {
886 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
887 Item item = data.getCurrentItem();
888 if (item instanceof Line) {
889 ((Line) item).toggleArrow();
890 DisplayController.requestRefresh(true);
891 }
892 }
893 });
894
895 // Set the TOGGLE_BOLD action
896 setGestureAction(gestureType(StandardGestureType.TOGGLE_BOLD), new GestureAction() {
897 @Override
898 public void exec(Gesture gesture) {
899 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
900 Item current = data.getCurrentItem();
901 if (current instanceof Text) {
902 ((Text) current).toggleBold();
903 }
904 }
905 });
906
907 // Set the TOGGLE_ITALICS action
908 setGestureAction(gestureType(StandardGestureType.TOGGLE_ITALICS), new GestureAction() {
909 @Override
910 public void exec(Gesture gesture) {
911 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
912 Item current = data.getCurrentItem();
913 if (current instanceof Text) {
914 ((Text) current).toggleItalics();
915 }
916 }
917 });
918
919 // Set the TOGGLE_ITEMS_MARK action
920 setGestureAction(gestureType(StandardGestureType.TOGGLE_ITEMS_MARK), new GestureAction() {
921 @Override
922 public void exec(Gesture gesture) {
923 ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData();
924 Item current = data.getCurrentItem();
925 if (current == null) {
926 return;
927 }
928 if (current != null && !current.hasPermission(UserAppliedPermission.full)) {
929 MessageBay.displayMessage("Insufficient permission toggle the items mark");
930 return;
931 }
932 boolean newValue = !(current.getLinkMark() || current.getActionMark());
933 current.setLinkMark(newValue);
934 current.setActionMark(newValue);
935 }
936 });
937
938 // Set the TOGGLE_XRAY_MODE action
939 setGestureAction(gestureType(StandardGestureType.TOGGLE_XRAY_MODE), new GestureAction() {
940 @Override
941 public void exec(Gesture gesture) {
942 DisplayController.ToggleXRayMode();
943 }
944 });
945
946 // Set the UNDO action
947 setGestureAction(gestureType(StandardGestureType.UNDO), new GestureAction() {
948 @Override
949 public void exec(Gesture gesture) {
950 UndoGestureData data = (UndoGestureData) gesture.getData();
951 if (data.getRedo()) {
952 DisplayController.getCurrentFrame().redo();
953 } else {
954 DisplayController.getCurrentFrame().undo();
955 }
956 }
957 });
958
959 // Set the ZOOM action
960 setGestureAction(gestureType(StandardGestureType.ZOOM), new GestureAction() {
961 @Override
962 public void exec(Gesture gesture) {
963 ZoomGestureData data = (ZoomGestureData) gesture.getData();
964 zoomFrameIfEnabled(DisplayController.getCurrentFrame(), data.getScaleFactor(), data.getPosition());
965 }
966 });
967
968 }
969
970 /** Initialises the set of gesture types. */
971 private void initialiseGestureTypes() {
972 for (StandardGestureType type : StandardGestureType.values()) {
973 GestureType gestureType;
974 if ((gestureType = GestureType.register(type.toString())) == null) {
975 gestureType = GestureType.get(type.toString());
976 }
977 _gestureTypes.put(type, gestureType);
978 }
979 }
980
981 /** Gets the gesture type associated with the given standard type. */
982 public GestureType gestureType(StandardGestureType type) {
983 if (type == null) {
984 return null;
985 }
986
987 return _gestureTypes.get(type);
988 }
989
990 /*
991 * TODO: EVERYTHING BELOW HERE IS ONLY TEMPORARILY SITUATED IN THIS CLASS
992 *
993 * The following methods were mostly copy-pasted here from the old
994 * FrameKeyboardActions and FrameMouseActions classes (to enable quick
995 * change-over to the new input system). Really they should be relocated to more
996 * suitable homes based on their function.
997 */
998
999 /**
1000 * Picks up an item on a frame.
1001 *
1002 * @param toGrab
1003 * item to be picked up
1004 * @param removeItem
1005 * true if the item should be removed from the frame
1006 */
1007 public static void pickup(Item toGrab) {
1008 if (toGrab.isFrameName()) {
1009 return;
1010 }
1011
1012 if (!toGrab.hasPermission(UserAppliedPermission.full)) {
1013 if (toGrab.getEditTarget() != toGrab) {
1014 pickup(toGrab.getEditTarget());
1015 } else {
1016 MessageBay.displayMessage("Insufficient permission pickup the item");
1017 }
1018 return;
1019 }
1020
1021 if (toGrab instanceof Circle) {
1022 toGrab.setHighlightMode(HighlightMode.Connected);
1023 } else if (toGrab.isVisible()) {
1024 toGrab.setHighlightMode(HighlightMode.Normal);
1025 }
1026
1027 // Brook: If the widget corner is being picked up. Instead refer to
1028 // picking up the edge for fixed-sized widgets so it is not so confusing
1029 if (toGrab instanceof WidgetCorner) {
1030 WidgetCorner wc = (WidgetCorner) toGrab;
1031 if (wc.getWidgetSource().isFixedSize()) {
1032 for (Item i : toGrab.getConnected()) {
1033 if (i instanceof WidgetEdge) {
1034 toGrab = i;
1035 break;
1036 }
1037 }
1038 }
1039 }
1040 pickup(toGrab.getConnected());
1041 }
1042
1043 // TODO: Review if these are needed as they ugly up the code. cts16
1044 static int _offX, _offY;
1045
1046 /**
1047 * Picks up a group of items on a frame.
1048 *
1049 * @param toGrab
1050 * The items to pick up.
1051 */
1052 public static void pickup(Collection<Item> toGrab) {
1053 if (toGrab == null || toGrab.size() == 0) {
1054 return;
1055 }
1056
1057 boolean bReparse = false;
1058 boolean bRecalculate = false;
1059
1060 Frame currentFrame = DisplayController.getCurrentFrame();
1061 String currentFrameName = currentFrame.getName();
1062 Iterator<Item> iter = toGrab.iterator();
1063 while (iter.hasNext()) {
1064 Item i = iter.next();
1065 if (!i.hasPermission(UserAppliedPermission.full)) {
1066 iter.remove();
1067 continue;
1068 }
1069 if (i.equals(_lastHighlightedItem)) {
1070 _lastHighlightedItem = null;
1071 }
1072
1073 bRecalculate |= i.recalculateWhenChanged();
1074 // i.setSelectedMode(SelectedMode.None);
1075 // Check if it has a relative link if so make it absolute
1076 i.setAbsoluteLink();
1077 // parent may be null
1078 if (i.getParent() != null) {
1079 i.getParent().removeItem(i);
1080 if (currentFrameName.equals(i.getParent().getName())) {
1081 i.setParent(null);
1082 }
1083 }
1084 FreeItems.getInstance().add(i);
1085 i.setFloating(true);
1086 // If its a vector pick up a copy of the stuff on the vector frame
1087 if (i.hasVector()) {
1088 bReparse = true;
1089
1090 Frame overlayFrame = FrameIO.LoadFrame(i.getAbsoluteLink());
1091 Collection<Item> copies = ItemUtils.CopyItems(overlayFrame.getNonAnnotationItems(false), i.getVector());
1092 for (Item copy : copies) {
1093 FreeItems.getInstance().add(copy);
1094 copy.setEditTarget(i);
1095 copy.setFloating(true);
1096 copy.setParent(null);
1097 // copy.setHighlightMode(HighlightMode.Connected);
1098 }
1099 }
1100 }
1101 currentFrame.change();
1102
1103 _lastHighlightedItem = null;
1104 updateCursor();
1105
1106 // if there are multiple items in the list, determine which to use for
1107 // offset calculations
1108 if (toGrab.size() > 1) {
1109 for (Item i : toGrab) {
1110 // MIKE: Movement goes haywire if these are removed because Line
1111 // class returns 0 for getX
1112 if (!(i instanceof Line) && !(i instanceof XRayable)) {
1113 _offX = DisplayController.getMouseX() - i.getX() + i.getOffset().getX();
1114 _offY = DisplayController.getMouseY() - i.getY() + i.getOffset().getY();
1115
1116 // make the offset item the first item in the list (so
1117 // move method knows which item to use)
1118 FreeItems.getInstance().set(FreeItems.getInstance().indexOf(i), FreeItems.getInstance().get(0));
1119 FreeItems.getInstance().set(0, i);
1120 break;
1121 }
1122 }
1123
1124 move(FreeItems.getInstance(), EcosystemManager.getInputManager().getCursorPosition());
1125 ItemUtils.EnclosedCheck(toGrab);
1126 // otherwise, just use the first item
1127 } else if (toGrab.size() == 1) {
1128 Item soleItem = toGrab.iterator().next();
1129 _offX = DisplayController.getMouseX() - soleItem.getX() + soleItem.getOffset().getX();
1130 _offY = DisplayController.getMouseY() - soleItem.getY() + soleItem.getOffset().getY();
1131 // Now call move so that if we are on a message in the message box
1132 // It doesn't appear up the top of the screen!!
1133 move(toGrab, EcosystemManager.getInputManager().getCursorPosition());
1134 } else {
1135 MessageBay.displayMessage("Insufficient permission to pickup the items");
1136 }
1137 if (bReparse) {
1138 FrameUtils.Parse(currentFrame, false, false);
1139 } else {
1140 currentFrame.notifyObservers(bRecalculate);
1141 }
1142
1143 DisplayController.requestRefresh(true);
1144 }
1145
1146 /**
1147 * Updates the current mouse cursor to whatever it should be. i.e. Hidden when
1148 * rubber-banding lines, otherwise default (arrow)
1149 */
1150 public static void updateCursor() {
1151 if (FreeItems.rubberBanding()) {
1152 DisplayController.setCursor(Item.HIDDEN_CURSOR);
1153 return;
1154 }
1155 // This is to make sure the TEXT_CURSOR doesn't get inadvertently turned off!
1156 Item on = FrameUtils.getCurrentItem();
1157 if (on != null && on instanceof Text) {
1158 return;
1159 }
1160 DisplayController.setCursor(Item.DEFAULT_CURSOR);
1161 }
1162
1163 private static Text _toRemove = null;
1164
1165 public static void processChar(char ch, boolean isShiftDown) {
1166 Navigation.ResetLastAddToBack();
1167 Item on = FrameUtils.getCurrentItem();
1168
1169 // permission check
1170 if (on != null && !on.hasPermission(UserAppliedPermission.full)) {
1171 MessageBay.displayMessage("Insufficient permission to edit this item");
1172 return;
1173 }
1174
1175 // Certain keys are handled by MagneticConstraints.
1176 // If the shift key is down the 'inverted' version of that key is used.
1177 if (isShiftDown && MagneticConstraints.getInstance().keyHit(-ch, on)) {
1178 return;
1179 } else if (MagneticConstraints.getInstance().keyHit(ch, on)) {
1180 return;
1181 }
1182
1183 if (_toRemove != null && on != _toRemove) {
1184 assert (_toRemove.getLength() == 0);
1185 // This line is to protect mistaken removal of items if there is a bug...
1186 if (_toRemove.getLength() == 0) {
1187 DisplayController.getCurrentFrame().removeItem(_toRemove);
1188 }
1189 }
1190 _toRemove = null;
1191
1192 // ignore delete and backspace if in free space
1193 if ((on == null || !(on instanceof Text)) && (ch == '\b' || ch == '\t' || ch == Text.DELETE_CHARACTER)) {
1194 return;
1195 }
1196
1197 SessionStats.TypedChar(ch);
1198
1199 // check for dot's being replaced with text
1200 if (on != null && on instanceof Dot && !(on instanceof WidgetCorner)) {
1201 if (ch == '\b' || ch == Text.DELETE_CHARACTER) {
1202 return;
1203 }
1204 Item.replaceDot(on, ch);
1205 return;
1206 }
1207
1208 // only text can interact with keyboard events
1209 if (on != null && !(on instanceof Text)) {
1210 on = null;
1211 }
1212
1213 // DisplayIO.UpdateTitle();
1214
1215 Text text = (Text) on;
1216 // if this text is empty but has not been removed (such as from ESC-pushdown)
1217 if (text != null && text.isEmpty() && (ch == '\b' || ch == Text.DELETE_CHARACTER)) {
1218 if (text.getLines().size() > 0) {
1219 Text.replaceText(text);
1220 } else {
1221 DisplayController.setCursor(Item.DEFAULT_CURSOR);
1222 }
1223 return;
1224 }
1225
1226 // if the user is in free space, create a new text item
1227 // MikeSays: Why do we have to check is highlighted... doing so causes
1228 // problems if you type characters too fast, they turn into multiple text
1229 // items. ie. JK together on the Linux laptop.
1230 if (on == null /* || !on.isHighlighted() */) {
1231 // DisplayIO.UpdateTitle();
1232 text = Text.createText(ch);
1233 text.justify(false);
1234
1235 FrameUtils.setLastEdited(text);
1236 DisplayController.setTextCursor(text, Text.NONE);
1237 DisplayController.requestRefresh(true);
1238 return;
1239 } else {
1240 FrameUtils.setLastEdited(text);
1241 }
1242
1243 DisplayController.setTextCursor(text, Text.NONE);
1244 insertCharacterAction(text, isShiftDown, ch);
1245
1246 // This repaint is needed for WINDOWS only?!?!? Mike is not sure why!
1247 // if (ch == Text.DELETE_CHARACTER)
1248 DisplayController.requestRefresh(true);
1249
1250 // a change has occured to the Frame
1251 text.getParent().setChanged(true);
1252
1253 // check that the Text item still exists (hasn't been deleted\backspaced
1254 // away)
1255 if (text.isEmpty() && text.getMinWidth() == null) {
1256 _toRemove = text;
1257
1258 if (text.hasAction()) {
1259 text.setActionMark(true);
1260 } else if (text.getLink() != null) {
1261 text.setLinkMark(true);
1262 } else if (text.getLines().size() > 0) {
1263 Item.replaceText(text);
1264 } else {
1265 // DisplayIO.getCurrentFrame().removeItem(text);
1266 DisplayController.setCursor(Item.DEFAULT_CURSOR);
1267 }
1268 }
1269 }
1270
1271 public static Text insertCharacterAction(Text text, boolean isShiftDown, final char c) {
1272 final float oldY = DisplayController.getFloatMouseY();
1273 final float oldX = DisplayController.getFloatMouseX();
1274 // System.err.println("insertCharacterAction: Prior to inserting character mouse
1275 // at: " + oldX + "," + oldY);
1276 Point newMouse = null;
1277 if (c == '\t') {
1278 if (isShiftDown) {
1279 newMouse = text.removeTab(c, oldX, oldY);
1280 } else {
1281 newMouse = text.insertTab(c, oldX, oldY);
1282 }
1283 } else {
1284 newMouse = text.insertChar(c, oldX, DisplayController.getFloatMouseY());
1285 }
1286
1287 // check if the user hit enter and the cursor is now on another text item
1288 if (oldY < newMouse.getY()) {
1289 AxisAlignedBoxBounds rect = text.getBoundingBox();
1290
1291 Item justBelow = FrameUtils.onItem(DisplayController.getCurrentFrame(), text.getX() + 10,
1292 rect.getMinY() + rect.getHeight() + 1, false);
1293
1294 // Dont drop unless
1295 if (justBelow != null && justBelow instanceof Text && justBelow != text) {
1296 // Drop all the items below it down!
1297 // Get the list of items that must be dropped
1298 List<Text> column = DisplayController.getCurrentFrame().getColumn(text);
1299 FrameUtils.Align(column, false, 0);
1300 }
1301 }
1302
1303 DisplayController.setCursorPosition(newMouse.getX(), newMouse.getY(), false);
1304 return text;
1305 }
1306
1307 /**
1308 * Moves the items to the current mouse position (plus the current offset)
1309 *
1310 * @param toMove
1311 */
1312 public static void move(Collection<Item> toMove, Point to) {
1313 move(toMove, to, false);
1314 }
1315
1316 static void move(Collection<Item> toMove, Point to, boolean cursor) {
1317 Item firstDot = toMove.iterator().next();
1318
1319 int deltax = (to.getX() - (cursor ? 0 : _offX)) - firstDot.getX();
1320 int deltay = (to.getY() - (cursor ? 0 : _offY)) - firstDot.getY();
1321
1322 // Cache the position of all items before moving any as constraints
1323 // may move the items before we get to them, causing jumping
1324 HashMap<Item, Point> preMovePositions = new HashMap<Item, Point>();
1325 for (Item move : toMove) {
1326 preMovePositions.put(move, move.getPosition());
1327 }
1328
1329 for (Item move : toMove) {
1330 Point pos = preMovePositions.get(move);
1331 move.setPosition(pos.getX() + deltax, pos.getY() + deltay);
1332
1333 if (!cursor && move instanceof Text) {
1334 ((Text) move).setAlpha(FrameUtils.hasCurrentItem() && FreeItems.getInstance().size() > 0 ? 60 : -1);
1335 }
1336 }
1337
1338 DisplayController.requestRefresh(true);
1339 }
1340
1341 /**
1342 * Returns true if the mouse moved during TDFC. This will happen if there is a
1343 * start annotation item on the frame.
1344 *
1345 * @param linker
1346 * @return
1347 */
1348 public static boolean tdfc(Item linker) throws RuntimeException {
1349 // if this is a non-usable item
1350 if (linker.getID() < 0) {
1351 return false;
1352 }
1353
1354 // Check if its an image that can be resized to fit a box around it
1355 String text = linker.getText();
1356 boolean isVector = text.equals("@v") || text.equals("@av");
1357 boolean isFrameImage = text.equals("@f");
1358 boolean isBitmap = false; // text.equals("@b");
1359
1360 if (isVector || isFrameImage || isBitmap) {
1361 Collection<Item> enclosure = FrameUtils.getEnclosingLineEnds(linker.getPosition());
1362 if (enclosure != null) {
1363 for (Item i : enclosure) {
1364 if (i.isLineEnd() && i.isEnclosed()) {
1365 if (!isVector) {
1366 DisplayController.getCurrentFrame().removeAllItems(enclosure);
1367 }
1368 AxisAlignedBoxBounds rect = i.getEnclosedBox();
1369 long width = Math.round(rect.getWidth());
1370 if (isVector) {
1371 NumberFormat nf = Vector.getNumberFormatter();
1372 linker.setText(linker.getText() + ": "
1373 + nf.format((width / DisplayController.getFramePaintAreaWidth())));
1374 } else {
1375 linker.setText(linker.getText() + ": " + width);
1376 }
1377
1378 linker.setPosition(new Point(rect.getMinX(), rect.getMinY()));
1379 linker.setThickness(i.getThickness());
1380 linker.setBorderColor(i.getColor());
1381 break;
1382 }
1383 }
1384 if (!isVector) {
1385 deleteItems(enclosure, false);
1386 }
1387 }
1388 }
1389
1390 boolean mouseMoved;
1391
1392 linker.getParent().setChanged(true);
1393
1394 Frame next = FrameIO.CreateNewFrame(linker, null);
1395
1396 linker.setLink("" + next.getNumber());
1397
1398 for (Item i : next.getTextItems()) {
1399 // Set the link for @Parent annotation item if one
1400 if (ItemUtils.startsWithTag(i, ItemUtils.TAG_PARENT) && i.getLink() == null) {
1401 Frame parent = linker.getParentOrCurrentFrame();
1402 i.setLink(parent.getName());
1403 } else if (ItemUtils.startsWithTag(i, ItemUtils.TAG_BACKUP, false)) {
1404 // Delink backup tag if it is on the frame
1405 i.setLink(null);
1406 }
1407 }
1408
1409 FrameUtils.DisplayFrame(next, true, true);
1410 FrameUtils.setTdfcItem(linker);
1411
1412 mouseMoved = next.moveMouseToDefaultLocation();
1413 // this needs to be done if the user doesnt move the mouse before doing
1414 // tdfc while the cursor is set to the text cursor
1415 DisplayController.setCursor(Item.DEFAULT_CURSOR);
1416 // This needs to be done in case there was a @start on the frame which
1417 // triggers changed to be set to true when it should stay as false
1418 next.setChanged(false);
1419 return mouseMoved;
1420 }
1421
1422 public static void resetOffset() {
1423 if (FreeItems.hasItemsAttachedToCursor()) {
1424 _offX = DisplayController.getMouseX() - FreeItems.getInstance().get(0).getX()
1425 + FreeItems.getInstance().get(0).getOffset().getX();
1426 _offY = DisplayController.getMouseY() - FreeItems.getInstance().get(0).getY()
1427 + FreeItems.getInstance().get(0).getOffset().getY();
1428 }
1429 }
1430
1431 /**
1432 * Forces a re-parse and repaint of the current Frame.
1433 */
1434 public static void Refresh() {
1435 Refresh(false);
1436 }
1437
1438 /**
1439 * Forces a re-parse and repaint of the current Frame.
1440 */
1441 public static void Refresh(boolean refreshFrameSize) {
1442 Frame currentFrame = DisplayController.getCurrentFrame();
1443
1444 if (refreshFrameSize) {
1445 currentFrame.refreshSize();
1446 }
1447
1448 // Refresh widgets that use its self as a data source
1449 currentFrame.notifyObservers(true);
1450
1451 if (FrameIO.isProfileFrame(currentFrame)) {
1452 // TODO ensure that users can not delete the first frame in a frameset...
1453 // TODO handle the case when users manually delete the first frame in a frameset
1454 // from the filesystem
1455 Frame profile = FrameIO.LoadFrame(currentFrame.getFramesetName() + "1");
1456 assert (profile != null);
1457 FrameUtils.Parse(currentFrame);
1458 FrameUtils.ParseProfile(profile);
1459 } else {
1460 FrameUtils.Parse(currentFrame);
1461 }
1462 // Need to update the cursor for when text items change to @b pictures
1463 // etc and the text cursor is showing
1464 updateCursor();
1465 refreshHighlights();
1466 DisplayController.requestRefresh(false);
1467 }
1468
1469 /**
1470 * Saves the current frame.
1471 */
1472 public static void Save() {
1473 Frame current = DisplayController.getCurrentFrame();
1474 current.change();
1475 FrameIO.SaveFrame(current, true, true);
1476 MessageBay.displayMessage("Current frame has been saved.");
1477 }
1478
1479 public static final String DEFAULT_NEW_ITEM_TEXT = "";
1480
1481 public static String getAutoBullet(String s) {
1482 return getBullet(s, true);
1483 }
1484
1485 private static String getBullet(String s, boolean nextBullet) {
1486 String newItemText = DEFAULT_NEW_ITEM_TEXT;
1487
1488 if (s == null) {
1489 return newItemText;
1490 }
1491
1492 /*
1493 * Item i = ItemUtils.FindTag(DisplayIO.getCurrentFrame().getItems(),
1494 * "@NoAutoBullets"); if (i != null) return newItemText;
1495 */
1496 // Separate the space at the start of the text item
1497 String preceedingSpace = "";
1498 for (int i = 0; i < s.length(); i++) {
1499 if (!Character.isSpaceChar(s.charAt(i))) {
1500 preceedingSpace = s.substring(0, i);
1501 s = s.substring(i);
1502 break;
1503 }
1504 }
1505
1506 // figure out the type of the text item
1507 // This allows us to do auto bulleting
1508 if (s != null && s.length() > 1) {
1509 // First check for text beginning with * @ # etc
1510 // These are simple auto bullets
1511 if (!Character.isLetterOrDigit(s.charAt(0)) && !Character.isSpaceChar(s.charAt(0))) {
1512 if (Text.isBulletChar(s.charAt(0))) {
1513 int nonSpaceIndex = 1;
1514 // Find the end of the bullet and space after the bullet
1515 while (nonSpaceIndex < s.length() && s.charAt(nonSpaceIndex) == ' ') {
1516 nonSpaceIndex++;
1517 }
1518 // we must have a special char followed by >= 1 space
1519 if (nonSpaceIndex > 1) {
1520 newItemText = s.substring(0, nonSpaceIndex);
1521 }
1522 }
1523 // Auto numbering and lettering
1524 } else {
1525 if (Character.isDigit(s.charAt(0))) {
1526 newItemText = getAutoNumber(s, nextBullet);
1527 // Auto lettering
1528 } else if (Character.isLetter(s.charAt(0))) {
1529 newItemText = getAutoLetter(s, nextBullet);
1530 }
1531 }
1532 }
1533 return preceedingSpace + newItemText;
1534 }
1535
1536 /**
1537 * Gets the string to be used to start the next auto lettered text item.
1538 *
1539 * @param s
1540 * the previous text items
1541 * @return the initial text for the new text item
1542 */
1543 private static String getAutoLetter(String s, boolean nextBullet) {
1544 String newItemText = DEFAULT_NEW_ITEM_TEXT;
1545
1546 int nonLetterIndex = 1;
1547
1548 if (isAutoNumberOrLetterChar(s.charAt(nonLetterIndex))) {
1549
1550 // Now search for the next non space character
1551 int nonSpaceIndex = nonLetterIndex + 1;
1552 while (nonSpaceIndex < s.length() && s.charAt(nonSpaceIndex) == ' ') {
1553 nonSpaceIndex++;
1554 }
1555
1556 // If there was a space then we have reached the end of our auto
1557 // text
1558 if (nonSpaceIndex > nonLetterIndex + 1) {
1559 if (nextBullet) {
1560 newItemText = nextLetterSequence(s.substring(0, nonLetterIndex))
1561 + s.substring(nonLetterIndex, nonSpaceIndex);
1562 } else {
1563 newItemText = s.substring(0, nonSpaceIndex);
1564 }
1565 }
1566 }
1567 return newItemText;
1568 }
1569
1570 /**
1571 * Gets the string to be used to start the next auto numbered text item.
1572 *
1573 * @param s
1574 * the previous text item
1575 * @return the beginning of the next auto numbered text item
1576 */
1577 private static String getAutoNumber(String s, boolean nextBullet) {
1578 String newItemText = DEFAULT_NEW_ITEM_TEXT;
1579
1580 int nonDigitIndex = 1;
1581 while (Character.isDigit(s.charAt(nonDigitIndex))) {
1582 nonDigitIndex++;
1583
1584 if (nonDigitIndex + 1 >= s.length()) {
1585 return DEFAULT_NEW_ITEM_TEXT;
1586 }
1587 }
1588
1589 if (isAutoNumberOrLetterChar(s.charAt(nonDigitIndex))) {
1590
1591 // we must have a number followed one non letter
1592 // then one or more spaces
1593 int nonSpaceIndex = nonDigitIndex + 1;
1594 while (nonSpaceIndex < s.length() && s.charAt(nonSpaceIndex) == ' ') {
1595 nonSpaceIndex++;
1596 }
1597
1598 if (nonSpaceIndex > nonDigitIndex + 1) {
1599 if (nextBullet) {
1600 newItemText = (Integer.parseInt(s.substring(0, nonDigitIndex)) + 1)
1601 + s.substring(nonDigitIndex, nonSpaceIndex);
1602 } else {
1603 newItemText = s.substring(0, nonSpaceIndex);
1604 }
1605 }
1606 }
1607 return newItemText;
1608 }
1609
1610 private static boolean isAutoNumberOrLetterChar(char c) {
1611 return c == ':' || c == '-' || c == '.' || c == ')' || c == '>';
1612 }
1613
1614 /**
1615 * Gets the next letter sequence for a given string to be used in auto
1616 * lettering.
1617 *
1618 * @param s
1619 * a sequence of letters
1620 * @return the next sequence of letters
1621 */
1622 static private String nextLetterSequence(String s) {
1623 if (s.length() > 1) {
1624 return s;
1625 }
1626
1627 if (s.equals("z")) {
1628 return "a";
1629 }
1630
1631 return (char) (s.charAt(0) + 1) + "";
1632 }
1633
1634 public static String getBullet(String s) {
1635 return getBullet(s, false);
1636 }
1637
1638 /**
1639 * true if lastItem only has highlighting removed when a new item is
1640 * highlighted.
1641 */
1642 private static boolean _lastHoldsHighlight = false;
1643
1644 public static void setHighlightHold(boolean hold) {
1645 _lastHoldsHighlight = hold;
1646 }
1647
1648 public static final int CONTEXT_FREESPACE = 0;
1649
1650 public static final int CONTEXT_AT_TEXT = 1;
1651
1652 public static final int CONTEXT_AT_LINE = 2;
1653
1654 public static final int CONTEXT_AT_DOT = 3;
1655
1656 public static final int CONTEXT_AT_ENCLOSURE = 4;
1657
1658 /** the current context of the cursor. */
1659 private static int _context = 0;
1660
1661 /** keeps track of the last highlighted Item. */
1662 private static Item _lastHighlightedItem = null;
1663
1664 private static boolean _forceArrowCursor = true;
1665
1666 public static void refreshHighlights() {
1667 // ByMike: Get the item the mouse is hovering over
1668 Item click = FrameUtils.getCurrentItem();
1669 Item on = null;
1670
1671 // System.out.println(click);
1672 if (click != null) {
1673 on = click;
1674 // set the context
1675 if (on instanceof Line) {
1676 _context = CONTEXT_AT_LINE;
1677 } else if (on instanceof Dot) {
1678 _context = CONTEXT_AT_DOT;
1679 } else if (on instanceof Text) {
1680 _context = CONTEXT_AT_TEXT;
1681 }
1682 } else {
1683 _context = CONTEXT_FREESPACE;
1684 }
1685
1686 // if the user is pointing at an item, highlight it
1687 if (on != null && !FreeItems.getInstance().contains(on)) {
1688 // if the user can spot-weld, show the virtual spot
1689 if (FreeItems.getInstance().size() == 2 && on instanceof Line) {
1690 Line line = (Line) on;
1691 Item freeItem0 = FreeItems.getInstance().get(0);
1692 Item freeItem1 = FreeItems.getInstance().get(1);
1693 Item lineEnd = freeItem0.isLineEnd() ? freeItem0 : (freeItem1.isLineEnd() ? freeItem1 : null);
1694 if (lineEnd != null) {
1695 line.showVirtualSpot(lineEnd, DisplayController.getMouseX(), DisplayController.getMouseY());
1696 } else {
1697 // The user is pointing at another point or text item etc
1698 FrameGraphics.changeHighlightMode(on, Item.HighlightMode.Normal);
1699 }
1700 } else {
1701 // FrameGraphics.ChangeSelectionMode(on,
1702 // Item.SelectedMode.Connected);
1703 // TODO: The method below is for the most part redundant
1704
1705 // Stops interfering with the highlighting while cropping a picture
1706 if (on instanceof Picture) {
1707 Picture p = (Picture) on;
1708 if (!p.isBeingCropped()) {
1709 on = FrameGraphics.Highlight(on.getEditTarget());
1710 }
1711 } else {
1712 on = FrameGraphics.Highlight(on.getEditTarget());
1713 }
1714
1715 }
1716 // if the last item highlighted is still highlighted, clear it
1717 if (_lastHoldsHighlight) {
1718 _lastHoldsHighlight = false;
1719 for (Item i : DisplayController.getCurrentFrame().getItems()) {
1720 if (i.isHighlighted() && i != on) {
1721 FrameGraphics.changeHighlightMode(i, Item.HighlightMode.None);
1722 }
1723 }
1724 }
1725
1726 // if the user is not pointing at an item, check for enclosure highlighting
1727 } else if (on == null) {
1728 Collection<Item> enclosure = FrameUtils.getEnclosingLineEnds();
1729 if (enclosure != null && enclosure.size() > 0) {
1730 Item firstLineEnd = enclosure.iterator().next();
1731 HighlightMode hm;
1732 if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) {
1733 hm = HighlightMode.Connected;
1734 } else {
1735 hm = HighlightMode.Enclosed;
1736 }
1737 if (firstLineEnd.getLines().size() > 1 &&
1738 // check that the enclosure is not part of a point being dragged in space
1739 !ContainsOneOf(enclosure, FreeItems.getInstance())) {
1740 on = firstLineEnd.getLines().get(0);
1741 // System.out.println(on == null ? "Null" :
1742 // on.toString());
1743 FrameGraphics.changeHighlightMode(on, hm);
1744 } else if (firstLineEnd instanceof XRayable) {
1745 on = firstLineEnd;
1746 FrameGraphics.changeHighlightMode(firstLineEnd, hm);
1747 }
1748 _context = CONTEXT_AT_ENCLOSURE;
1749 } else if (_lastHighlightedItem != null) {
1750 // System.out.println("LastHighlightedItem");
1751 _lastHoldsHighlight = false;
1752 }
1753 }
1754
1755 // disable cursor changes when the cursor has items attached
1756 if (FreeItems.hasItemsAttachedToCursor() && DisplayController.getCursor() != Item.TEXT_CURSOR) {
1757 _forceArrowCursor = false;
1758 }
1759
1760 // setLastHighlightedItem(on);
1761
1762 if (_lastHighlightedItem != null && _lastHighlightedItem != on && !_lastHoldsHighlight) {
1763 // Turn off the highlighting only if the last highlighted item
1764 // is not connected to the currentItem. Otherwise we get flickering
1765 // in transition from connected to normal mode while moving the cursor along a
1766 // line.
1767 if (on == null || (!on.getAllConnected().contains(_lastHighlightedItem))) {
1768 FrameGraphics.changeHighlightMode(_lastHighlightedItem, Item.HighlightMode.None);
1769 }
1770 }
1771
1772 _lastHighlightedItem = on;
1773
1774 }
1775
1776 private static boolean ContainsOneOf(Collection<Item> enclosure, Collection<Item> freeItems) {
1777 if (freeItems == null) {
1778 return false;
1779 }
1780 for (Item i : freeItems) {
1781 if (enclosure.contains(i)) {
1782 return true;
1783 }
1784 }
1785 return false;
1786 }
1787
1788 public static void anchor(Item toAnchor, boolean checkEnclosure) {
1789 // Only anchor items we have full permission over... i.e. don't anchor vector
1790 // items
1791 if (!toAnchor.hasPermission(UserAppliedPermission.full)) {
1792 return;
1793 }
1794
1795 toAnchor.anchor();
1796
1797 if (checkEnclosure) {
1798 ItemUtils.EnclosedCheck(toAnchor.getParentOrCurrentFrame().getItems());
1799 DisplayController.requestRefresh(true);
1800 }
1801 }
1802
1803 public static void anchor(Item toAnchor) {
1804 anchor(toAnchor, true);
1805 }
1806
1807 public static void anchor(Collection<Item> toAnchor) {
1808 boolean bReparse = false;
1809 boolean bRecalculate = false;
1810 // Need to make sure we check enclosure for overlays etc
1811 Set<Frame> checkEnclosure = new HashSet<Frame>();
1812
1813 // Create a clone of toAnchor since in the proccess of anchoring items
1814 // they can change the state of the toAnchor collection and thus create
1815 // concurrent modification exceptions.
1816 // This is especially needed for widgets being removed when anchored:
1817 // since they
1818 // currently are composed of 8 items this is vital. In the new revision
1819 // of
1820 // widgets being implemented as a single item this this can be
1821 // depreciated
1822 // however it may be useful for other applications.
1823 Collection<Item> toAnchorCopy = new ArrayList<Item>(toAnchor);
1824
1825 for (Item i : toAnchorCopy) {
1826 // Widget components in this loop.
1827 if (toAnchor.contains(i)) { // since to anchor could change while
1828 // anchoring
1829 // if (!i.hasVector())
1830 anchor(i, false);
1831 checkEnclosure.add(i.getParentOrCurrentFrame());
1832 bReparse |= i.hasOverlay();
1833 bRecalculate |= i.recalculateWhenChanged();
1834 // toAnchor.remove(i);
1835 }
1836 }
1837
1838 toAnchor.clear();
1839 // Check enclosure for all the frames of the items that were anchored
1840 for (Frame f : checkEnclosure) {
1841 // ItemUtils.EnclosedCheck(f.getItems());
1842 ItemUtils.Justify(f);
1843 }
1844
1845 Frame currentFrame = DisplayController.getCurrentFrame();
1846 if (bReparse) {
1847 FrameUtils.Parse(currentFrame, false, false);
1848 } else {
1849 currentFrame.notifyObservers(bRecalculate);
1850 }
1851 DisplayController.requestRefresh(true);
1852 }
1853
1854 public static void deleteItems(Collection<Item> itemList) {
1855 deleteItems(itemList, true);
1856 }
1857
1858 public static void deleteItems(Collection<Item> itemList, boolean addToUndo) {
1859 boolean bReparse = false;
1860 boolean bRecalculate = false;
1861
1862 SessionStats.DeletedItems(itemList);
1863 List<Frame> modifiedFrames = new LinkedList<Frame>();
1864 // Get a list of all the modified frames
1865 for (Item i : itemList) {
1866 Frame parent = i.getParent();
1867 if (parent != null) {
1868 modifiedFrames.add(parent);
1869 }
1870 i.setHighlightMode(HighlightMode.None);
1871 bReparse |= i.hasOverlay();
1872 bRecalculate |= i.recalculateWhenChanged();
1873 }
1874 // If they are all free items then add the current frame
1875 if (modifiedFrames.size() == 0) {
1876 modifiedFrames.add(DisplayController.getCurrentFrame());
1877 }
1878
1879 Collection<Item> toUndo = new LinkedHashSet<Item>();
1880 // disconnect any connected items
1881 for (Item i : itemList) {
1882
1883 // Only delete the current item if have not already deleted.
1884 // This is especially important for heavy duty widgets - so they
1885 // do not have to expire several times per delete.
1886 if (toUndo.contains(i)) {
1887 continue;
1888 }
1889
1890 // Make sure text items attached to cursor are reset back to the
1891 // transparency they should have.
1892 if (i instanceof Text) {
1893 ((Text) i).setAlpha(-1);
1894 }
1895
1896 if (i.getLines().size() > 0) {
1897
1898 Collection<Item> toDelete = deleteLineEnd(i);
1899 if (addToUndo) {
1900 // add the copied items to the undo stack
1901 for (Item itemToUndo : toDelete) {
1902
1903 if (!toUndo.contains(itemToUndo)) {
1904 toUndo.add(itemToUndo);
1905 }
1906
1907 }
1908 }
1909 } else if (!toUndo.contains(i)) {
1910 if (addToUndo)
1911 {
1912 toUndo.add(i); // Why was is this a copy
1913 }
1914 }
1915 }
1916
1917 for (Frame f : modifiedFrames) {
1918 f.removeAllItems(itemList);
1919 // ItemUtils.EnclosedCheck(f.getItems());
1920 ItemUtils.Justify(f);
1921 }
1922 // TODO: How should undelete deal with undo when items are removed from
1923 // the current frame as well as the overlay frame
1924 Frame currentFrame = DisplayController.getCurrentFrame();
1925 currentFrame.addToUndoDelete(itemList);
1926 itemList.clear();
1927 if (bReparse) {
1928 FrameUtils.Parse(currentFrame, false, false);
1929 /*
1930 * TODO check if I need to recalculate even if reparse occurs, here and in
1931 * anchor, pickup etc
1932 */
1933 } else {
1934 currentFrame.notifyObservers(bRecalculate);
1935 }
1936
1937 }
1938
1939 private static Collection<Item> deleteLineEnd(Item lineEnd) {
1940
1941 if (lineEnd instanceof WidgetCorner) { // Brook
1942
1943 WidgetCorner wc = (WidgetCorner) lineEnd;
1944 Frame parent = wc.getWidgetSource().getParentFrame();
1945
1946 // Remove from the parent frame
1947 if (parent != null) {
1948 parent.removeAllItems(wc.getWidgetSource().getItems());
1949 }
1950
1951 wc.getWidgetSource().onDelete(); // Changes the widgets
1952 // corner/edges ID's...
1953
1954 return wc.getWidgetSource().getItems();
1955
1956 } else {
1957
1958 // // create a backup copy of the dot and its lines
1959 // List<Item> copy = copy(lineEnd.getConnected());
1960 //
1961 // // Remove lines from their anchored dots
1962 // // note: the line is kept so that it can be properly restored
1963 // for (Item ic : copy) {
1964 // if (ic instanceof Line) {
1965 // Line line = (Line) ic;
1966 // // Remove the line from the item that is not the copy of the
1967 // // line end being deletedF
1968 // if (!copy.contains(line.getStartItem()))
1969 // line.getStartItem().removeLine(line);
1970 // if (!copy.contains(line.getEndItem()))
1971 // line.getEndItem().removeLine(line);
1972 // }
1973 // }
1974
1975 Collection<Item> copy = lineEnd.getConnected();
1976
1977 // remove all lines being deleted
1978 for (Item ic : lineEnd.getConnected()) {
1979 if (ic instanceof Line && ((Line) ic).getOppositeEnd(lineEnd) != null) {
1980 Line line = (Line) ic;
1981
1982 // Invalidate the line to make sure we dont get any ghost
1983 // arrowheads.
1984 ic.invalidateAll();
1985
1986 Item d = line.getOppositeEnd(lineEnd);
1987 d.removeLine(line);
1988
1989 // if the dot was only part of one line, it can be
1990 // removed
1991 if (d.getLines().size() == 0) {
1992 if (d.getParent() != null) {
1993 d.getParent().removeItem(d);
1994 }
1995 if (!copy.contains(d)) {
1996 copy.add(d);
1997 }
1998 }
1999
2000 if (lineEnd.getParent() != null) {
2001 lineEnd.getParent().removeItem(ic);
2002 }
2003 }
2004 }
2005 return copy;
2006 }
2007 }
2008
2009 public static Item getlastHighlightedItem() {
2010 return _lastHighlightedItem;
2011 }
2012
2013 /**
2014 * event called when mouse exits window (can't use MouseListener callback since
2015 * that callback doesn't correctly receive all mouse exit events) *** Above
2016 * comment no longer applies! *** TODO: Rename. cts16
2017 */
2018 public static void mouseExitedWindow() {
2019 if (FreeItems.hasItemsAttachedToCursor()) {
2020 boolean cut = true;
2021 for (Item i : FreeItems.getInstance()) {
2022 for (Item j : i.getAllConnected()) {
2023 if (!FreeItems.getInstance().contains(j)) {
2024 cut = false;
2025 break;
2026 }
2027 }
2028 }
2029 if (cut) {
2030 ItemSelection.cut();
2031 }
2032 }
2033 }
2034
2035 public static void setForceArrow(boolean val) {
2036 _forceArrowCursor = val;
2037 }
2038
2039 protected static void zoomFrame(Frame frame, double scaleFactor, int x, int y) {
2040
2041 if (frame == null) {
2042 return;
2043 }
2044
2045 Collection<Item> items = frame.getVisibleItems();
2046
2047 for (Item item : items) {
2048 if (item instanceof Text && item.getSize() <= Text.MINIMUM_FONT_SIZE && scaleFactor < 1) {
2049 return;
2050 }
2051 }
2052
2053 for (Vector v : frame.getVectors()) {
2054 v.Source.scale((float) scaleFactor, x, y);
2055 }
2056
2057 for (Item item : items) {
2058 // This line is only needed for circles!!
2059 // Need to really fix up the way this works!!
2060 if (item.hasEnclosures()) {
2061 continue;
2062 }
2063 if (!item.hasPermission(UserAppliedPermission.full)) {
2064 continue;
2065 }
2066 item.invalidateAll();
2067 if (!(item instanceof Line)) {
2068 item.scale((float) scaleFactor, x, y);
2069 }
2070 }
2071
2072 for (Item item : items) {
2073 if (!item.hasPermission(UserAppliedPermission.full)) {
2074 continue;
2075 }
2076 // if (!(item instanceof Line))
2077 item.invalidateBounds();
2078
2079 if (item instanceof Line) {
2080 ((Line) item).refreshStroke(item.getThickness());
2081 }
2082
2083 item.invalidateAll();
2084 }
2085 }
2086
2087 public static boolean zoomFrameIfEnabled(Frame frame, double scaleFactor, Point centreOfZoom) {
2088 boolean zoom_active = ExperimentalFeatures.FrameZoom.get();
2089
2090 if (zoom_active) {
2091
2092 zoomFrame(DisplayController.getCurrentFrame(), scaleFactor, centreOfZoom.getX(), centreOfZoom.getY());
2093 DisplayController.getCurrentFrame().refreshSize();
2094 Refresh();
2095 } else {
2096 String frameZoomingDisabledMessage = "Frame Zooming currently disabled. "
2097 + "Access Settings->Experimental->FrameZoom and set to 'true' to enable this";
2098 MessageBay.displayMessageOnce(frameZoomingDisabledMessage);
2099 }
2100
2101 return zoom_active;
2102 }
2103
2104 /**
2105 * Performs the dropping action: If the cursor is in free space then: the cursor
2106 * is repositioned below the last non-annotation text item. If the cursor is on
2107 * an item, and has items attached then: the cursor is positioned below the
2108 * pointed to item, and the items below are 'pushed down' to make room.
2109 *
2110 * @param toDropFrom
2111 * The Item being pointed at by the mouse, may be null to indicate
2112 * the cursor is in free space.
2113 */
2114 public static boolean Drop(Item toDropFrom, boolean bPasting) {
2115 try {
2116 FrameUtils.setLastEdited(null);
2117
2118 String newItemText = DEFAULT_NEW_ITEM_TEXT;
2119
2120 // if a line is being rubber-banded, this is a no-op
2121 if (Frame.rubberbandingLine()) {
2122 return false;
2123 }
2124
2125 // if the cursor is in free space then the drop will happen from the
2126 // last non annotation text item on the frame
2127 if (toDropFrom == null) {
2128 toDropFrom = DisplayController.getCurrentFrame().getLastNonAnnotationTextItem();
2129 }
2130
2131 // if no item was found, return
2132 if (toDropFrom == null) {
2133 MessageBay.errorMessage("No item could be found to drop from");
2134 return false;
2135 }
2136
2137 if (!(toDropFrom instanceof Text)) {
2138 MessageBay.displayMessage("Only text items can be dropped from");
2139 return false;
2140 }
2141
2142 // Get the list of items that must be dropped
2143 List<Text> column = DisplayController.getCurrentFrame().getColumn(toDropFrom);
2144
2145 if (column == null) {
2146 MessageBay.errorMessage("No column found to align items to");
2147 return false;
2148 }
2149
2150 Item title = DisplayController.getCurrentFrame().getTitleItem();
2151
2152 // We won't do auto-bulleting when dropping from titles
2153 if (!bPasting && toDropFrom != title) {
2154 newItemText = getAutoBullet(((Text) toDropFrom).getFirstLine());
2155 }
2156
2157 Text dummyItem = null;
2158 if (!bPasting && FreeItems.textOnlyAttachedToCursor()) {
2159 dummyItem = (Text) FreeItems.getItemAttachedToCursor();
2160 String autoBullet = getAutoBullet(dummyItem.getText());
2161
2162 if (autoBullet.length() > 0) {
2163 newItemText = "";
2164 }
2165 dummyItem.setText(newItemText + dummyItem.getText());
2166 }
2167
2168 dummyItem = Text.createText();
2169 if (FreeItems.textOnlyAttachedToCursor()) {
2170 Text t = (Text) FreeItems.getItemAttachedToCursor();
2171 dummyItem.setSize(t.getSize());
2172 int lines = t.getTextList().size();
2173 for (int i = 0; i < lines; i++) {
2174 newItemText += '\n';
2175 }
2176 }
2177
2178 dummyItem.setText(newItemText);
2179
2180 // If the only item on the frame is the title and the frame name
2181 // goto the zero frame and drop to the @start if there is one
2182 // or a fixed amount if there is not
2183 if (column.size() == 0) {
2184 Frame current = DisplayController.getCurrentFrame();
2185 // Item itemTemplate = current.getItemTemplate();
2186 int xPos = title.getX() + FrameCreator.INDENT_FROM_TITLE;
2187 int yPos = FrameCreator.getYStart(title);
2188 // Check for @start on the zero frame
2189 Frame zero = FrameIO.LoadFrame(current.getFramesetName() + '0');
2190 Text start = zero.getAnnotation("start");
2191 if (start != null) {
2192 xPos = start.getX();
2193 yPos = start.getY();
2194 }
2195
2196 dummyItem.setPosition(xPos, yPos);
2197 // DisplayIO.setCursorPosition(xPos, yPos);
2198
2199 checkMovingCursor(dummyItem);
2200 } else {
2201 int yPos = column.get(0).getY() + 1;
2202 int xPos = column.get(0).getX();
2203 // Either position the new item below the title or just above
2204 // the first item below the title
2205 if (toDropFrom == title && column.get(0) != title) {
2206 // If dropping from the title position just above top item
2207 yPos = column.get(0).getY() - 1;
2208
2209 Frame current = DisplayController.getCurrentFrame();
2210 // Check for @start on the zero frame
2211 Frame zero = FrameIO.LoadFrame(current.getFramesetName() + '0');
2212 Text start = zero.getAnnotation("start");
2213 if (start != null) {
2214 yPos = Math.min(yPos, start.getY());
2215 }
2216 }
2217 dummyItem.setPosition(xPos, yPos);
2218 column.add(dummyItem);
2219 FrameUtils.Align(column, false, 0);
2220 // Check if it will be outside the frame area
2221 if (dummyItem.getY() < 0 || dummyItem.getY() > DisplayController.getFramePaintAreaHeight()) {
2222 // Check for the 'next' tag!
2223 Frame current = DisplayController.getCurrentFrame();
2224 Item next = current.getAnnotation("next");
2225 Item prev = current.getAnnotation("previous");
2226 // Check for an unlinked next tag
2227 if ((next != null && !next.hasLink()) || (prev != null && prev.hasLink())) {
2228 Frame firstFrame = current;
2229 if (next != null) {
2230 next.delete();
2231 }
2232 FrameCreator frameCreator = new FrameCreator(null);
2233 // Add the next button
2234 next = frameCreator.addNextButton(current, null);
2235
2236 // Create the new frame linked to the next tag
2237 boolean mouseMoved = tdfc(next);
2238 Frame moreFrame = DisplayController.getCurrentFrame();
2239
2240 // Add previous button to the new frame
2241 frameCreator.addPreviousButton(moreFrame, firstFrame.getName());
2242 Item first = current.getAnnotation("first");
2243 if (first != null) {
2244 frameCreator.addFirstButton(moreFrame, first.getLink());
2245 } else {
2246 frameCreator.addFirstButton(moreFrame, firstFrame.getName());
2247 }
2248 // Add the @next if we are pasting
2249 // if (bPasting) {
2250 // Item copy = next.copy();
2251 // copy.setLink(null);
2252 // moreFrame.addItem(copy);
2253 // }
2254
2255 moreFrame.setTitle(firstFrame.getTitleItem().getText());
2256 // need to move the mouse to the top of the frame if
2257 // there wasnt an @start on it
2258 if (!mouseMoved) {
2259 Item moreTitle = moreFrame.getTitleItem();
2260 moreTitle.setOverlayPermission(UserAppliedPermission.full);
2261 Drop(moreTitle, bPasting);
2262 }
2263 // Add the bullet text to the item
2264 dummyItem.setPosition(DisplayController.getMouseX(), DisplayController.getMouseY());
2265 } else {
2266 MessageBay.warningMessage("Can not create items outside the frame area");
2267 // ensures correct repainting when items don't move
2268 DisplayController.setCursorPosition(DisplayController.getMouseX(),
2269 DisplayController.getMouseY());
2270 return false;
2271 }
2272 }
2273 if (!FreeItems.textOnlyAttachedToCursor() && !dummyItem.isEmpty()) {
2274 DisplayController.getCurrentFrame().addItem(dummyItem);
2275 }
2276
2277 checkMovingCursor(dummyItem);
2278 }
2279 if (dummyItem.getText().length() == 0 || FreeItems.hasItemsAttachedToCursor()) {
2280 dummyItem.getParentOrCurrentFrame().removeItem(dummyItem);
2281 dummyItem.setRightMargin(DisplayController.getFramePaintAreaWidth(), false);
2282 } else {
2283 dummyItem.setWidth(toDropFrom.getWidth());
2284 }
2285
2286 DisplayController.resetCursorOffset();
2287 DisplayController.requestRefresh(true);
2288 } catch (RuntimeException e) {
2289 // MessageBay.errorMessage(e.getMessage());
2290 e.printStackTrace();
2291 return false;
2292 }
2293 return true;
2294 }
2295
2296 /**
2297 * @param dummyItem
2298 */
2299 public static void moveCursorAndFreeItems(int x, int y) {
2300 Point oldMousePosition = DisplayController.getMousePosition();
2301
2302 if (oldMousePosition.getX() == x && oldMousePosition.getY() == y) {
2303 return;
2304 }
2305
2306 DisplayController.setCursorPosition(x, y);
2307 Item firstItem = FreeItems.getItemAttachedToCursor();
2308
2309 if (firstItem == null) {
2310 return;
2311 }
2312
2313 int deltaX = firstItem.getX() - x;
2314 int deltaY = firstItem.getY() - y;
2315
2316 for (Item i : FreeItems.getInstance()) {
2317 i.setPosition(i.getX() - deltaX, i.getY() - deltaY);
2318 }
2319 }
2320
2321 /**
2322 * @param dummyItem
2323 */
2324 private static void checkMovingCursor(Text dummyItem) {
2325 // Move the item to the cursor position
2326 if (FreeItems.hasItemsAttachedToCursor()) {
2327 moveCursorAndFreeItems(dummyItem.getX(), dummyItem.getY());
2328 } else {
2329 DisplayController.MoveCursorToEndOfItem(dummyItem);
2330 }
2331 }
2332
2333 private static void calculateItem(Item toCalculate) {
2334 if (toCalculate == null) {
2335 return;
2336 }
2337
2338 if (!toCalculate.update()) {
2339 toCalculate.setFormula(null);
2340 MessageBay.errorMessage("Can not calculate formula [" + toCalculate.getText() + ']');
2341 }
2342 }
2343
2344 /**
2345 * Adjusts the size of the given Item, by the given amount. Note: The amount is
2346 * relative and can be positive or negative.
2347 *
2348 * @param toSet
2349 * The Item whose size is to be adjusted
2350 * @param diff
2351 * The amount to adjust the Item's size by
2352 * @param moveCursor
2353 * true if the cursor position should be automatically adjusted with
2354 * resizing
2355 */
2356 public static void SetSize(Item item, int diff, boolean moveCursor, boolean insideEnclosure,
2357 boolean isControlDown) {
2358 Collection<Item> toSize = new HashSet<Item>();
2359 Collection<Widget> widgets = new HashSet<Widget>();
2360 // the mouse is only moved when the Item is on the frame, not free
2361 // boolean moveMouse = false;
2362 Item toSet = null;
2363
2364 // if the user is not pointing to any item
2365 if (item == null) {
2366 if (FreeItems.hasItemsAttachedToCursor()) {
2367 toSize.addAll(FreeItems.getInstance());
2368 } else {
2369 MessageBay.displayMessage("There are no Items selected on the Frame or on the Cursor");
2370 return;
2371 }
2372 } else {
2373 if (item.isFrameName()) {
2374 // scale the entire frame
2375 if (diff != 0) {
2376 double scaleFactor = diff > 0 ? 1.1 : 0.909090909;
2377 zoomFrameIfEnabled(DisplayController.getCurrentFrame(), scaleFactor, new Point(0, 0));
2378 }
2379 // MessageBay.displayMessage("Can not resize the frame name");
2380 return;
2381 }
2382 // check permissions
2383 if (!item.hasPermission(UserAppliedPermission.full)) {
2384 Item editTarget = item.getEditTarget();
2385 if (editTarget != item && editTarget.hasPermission(UserAppliedPermission.full)) {
2386 item = editTarget;
2387 } else {
2388 MessageBay.displayMessage("Insufficient permission to change the size of that item");
2389 return;
2390 }
2391 }
2392 toSet = item;
2393 // For resizing enclosures pick up everything that is attached to
2394 // items partly in the enclosure
2395 // TODO make this only pick up stuff COMPLETELY enclosed... if we
2396 // change copying to copy only the stuff completely enclosed
2397 if (insideEnclosure) {
2398 for (Item i : FrameUtils.getCurrentItems(toSet)) {
2399 if (i.hasPermission(UserAppliedPermission.full) && !toSize.contains(i)) {
2400 toSize.addAll(i.getAllConnected());
2401 }
2402 }
2403
2404 } // Enclosed circle centers are resized with the center as origin
2405 // Just add the circle center to the list of items to size
2406 else if (!toSet.hasEnclosures() && !(toSet instanceof Text) && toSet.isLineEnd()) {
2407 toSize.addAll(toSet.getLines());
2408 } else if (toSet instanceof Line) {
2409
2410 Line line = (Line) toSet;
2411
2412 if (!(toSet instanceof WidgetEdge)
2413 || ((WidgetEdge) toSet).getWidgetSource().isWidgetEdgeThicknessAdjustable()) {
2414
2415 float current = Math.abs(line.getThickness());
2416 current = Math.max(current + diff, Item.MINIMUM_THICKNESS);
2417 line.setThickness(current);
2418 DisplayController.requestRefresh(true);
2419 return;
2420
2421 }
2422
2423 } else {
2424 toSize.add(toSet);
2425 }
2426 }
2427
2428 // add widgets to notify
2429 for (Item i : toSize) {
2430 if (i instanceof WidgetEdge) {
2431 widgets.add(((WidgetEdge) i).getWidgetSource());
2432 } else if (i instanceof WidgetCorner) {
2433 widgets.add(((WidgetCorner) i).getWidgetSource());
2434 }
2435 }
2436
2437 Point origin = EcosystemManager.getInputManager().getCursorPosition();
2438 // Inside enclosures increase the size of the enclosure
2439 double ratio = (100.0 + diff * 2) / 100.0;
2440 if (insideEnclosure) {
2441 Collection<Item> done = new HashSet<Item>();
2442 // adjust the size of all the items
2443 for (Item i : toSize) {
2444 if (done.contains(i)) {
2445 continue;
2446 }
2447
2448 if (i.isLineEnd()) {
2449 if (!(i instanceof WidgetCorner) || !((WidgetCorner) i).getWidgetSource().isFixedSize()) // don't
2450 // size
2451 // fixed
2452 // widgets
2453 {
2454 Collection<Item> allConnected = i.getAllConnected();
2455 done.addAll(allConnected);
2456 for (Item it : allConnected) {
2457 it.translate(origin, ratio);
2458 it.setArrowheadLength((float) (it.getArrowheadLength() * ratio));
2459 }
2460 i.setThickness((float) (i.getThickness() * ratio));
2461 }
2462 } else if (i instanceof XRayable) {
2463 XRayable xRay = (XRayable) i;
2464 Text source = xRay.getSource();
2465 // Ensure that the source is done before the XRayable
2466 if (!done.contains(source)) {
2467 scaleText(insideEnclosure, origin, ratio, done, source);
2468 }
2469 i.translate(origin, ratio);
2470 i.setThickness((float) (i.getThickness() * ratio));
2471 done.add(i);
2472 } else if (i.hasVector()) {
2473 // TODO Improve the effiency of resizing vectors... ie...
2474 // dont want to have to reparse all the time
2475 assert (i instanceof Text);
2476 Text text = (Text) i;
2477 AttributeValuePair avp = new AttributeValuePair(text.getText());
2478 double scale = 1F;
2479 try {
2480 scale = avp.getDoubleValue();
2481 } catch (Exception e) {
2482 }
2483 scale *= ratio;
2484 NumberFormat nf = Vector.getNumberFormatter();
2485 text.setAttributeValue(nf.format(scale));
2486 text.translate(origin, ratio);
2487 item.getParent().parse();
2488 } else if (i instanceof Text) {
2489 scaleText(insideEnclosure, origin, ratio, done, (Text) i);
2490 }
2491 }
2492 // refresh anchored items
2493 if (refreshAnchors(toSize)) {
2494 FrameUtils.Parse(DisplayController.getCurrentFrame(), false);
2495 }
2496 // notify widgets they were resized
2497 for (Widget iw : widgets) {
2498 iw.onResized();
2499 }
2500 DisplayController.requestRefresh(true);
2501 return;
2502 }
2503
2504 // adjust the size of all the items
2505 for (Item i : toSize) {
2506 // Lines and dots use thickness, not size
2507 if (i.hasEnclosures()) {
2508 Circle c = (Circle) i.getEnclosures().iterator().next();
2509 c.setSize(c.getSize() * (float) ratio);
2510 } else if (i instanceof Line || i instanceof Circle && !insideEnclosure) {
2511 float current = Math.abs(i.getThickness());
2512 current = Math.max(current + diff, Item.MINIMUM_THICKNESS);
2513 i.setThickness(current);
2514 } else if (i instanceof Dot) {
2515 Item dot = i;
2516 float current = Math.abs(dot.getThickness());
2517 current = Math.max(current + diff, Item.MINIMUM_THICKNESS);
2518 dot.setThickness(current);
2519 } else if (i.hasVector()) {
2520 assert (item instanceof Text);
2521 Text text = (Text) item;
2522 AttributeValuePair avp = new AttributeValuePair(text.getText());
2523 double scale = 1F;
2524 try {
2525 scale = avp.getDoubleValue();
2526 } catch (Exception e) {
2527 }
2528 scale *= ratio;
2529 NumberFormat nf = Vector.getNumberFormatter();
2530 text.setAttributeValue(nf.format(scale));
2531 text.translate(origin, ratio);
2532 item.getParent().parse();
2533 } else {
2534 float oldSize = Math.abs(i.getSize());
2535 float newSize = Math.max(oldSize + diff, Item.MINIMUM_THICKNESS);
2536 float resizeRatio = newSize / oldSize;
2537 // Set size for Picture also translates
2538 i.setSize(newSize);
2539 if (i instanceof Text && i.getSize() != oldSize) {
2540 if (toSize.size() == 1 && !isControlDown) {
2541 moveCursorAndFreeItems(i.getX(), i.getY());
2542 } else {
2543 i.translate(origin, resizeRatio);
2544 if (i.isLineEnd()) {
2545 i.setPosition(i.getPosition());
2546 }
2547 }
2548 }
2549 }
2550 }
2551
2552 if (toSet != null) {
2553 toSet.getParent().setChanged(true);
2554 }
2555
2556 // refresh anchored items
2557 if (refreshAnchors(toSize)) {
2558 FrameUtils.Parse(DisplayController.getCurrentFrame(), false);
2559 }
2560
2561 DisplayController.requestRefresh(true);
2562 }
2563
2564 /**
2565 * @param origin
2566 * @param ratio
2567 * @param done
2568 * @param source
2569 */
2570 private static void scaleText(boolean insideEnclosure, Point origin, double ratio, Collection<Item> done,
2571 Text source) {
2572 if (insideEnclosure) {
2573 source.setWidth(Math.round((float) (source.getAbsoluteWidth() * ratio)));
2574 }
2575 source.translate(origin, ratio);
2576 source.setSize((float) (source.getSize() * ratio));
2577 done.add(source);
2578 }
2579
2580 private static boolean refreshAnchors(Collection<Item> items) {
2581 boolean bReparse = false;
2582
2583 for (Item i : items) {
2584 Integer anchorLeft = i.getAnchorLeft();
2585 Integer anchorRight = i.getAnchorRight();
2586 Integer anchorTop = i.getAnchorTop();
2587 Integer anchorBottom = i.getAnchorBottom();
2588
2589 if (anchorLeft != null) {
2590 i.setAnchorLeft(anchorLeft);
2591 if (i.hasVector()) {
2592 bReparse = true;
2593 }
2594 }
2595
2596 if (anchorRight != null) {
2597 i.setAnchorRight(anchorRight);
2598 if (i.hasVector()) {
2599 bReparse = true;
2600 }
2601 }
2602
2603 if (anchorTop != null) {
2604 i.setAnchorTop(anchorTop);
2605 if (i.hasVector()) {
2606 bReparse = true;
2607 }
2608 }
2609
2610 if (anchorBottom != null) {
2611 i.setAnchorBottom(anchorBottom);
2612 if (i.hasVector()) {
2613 bReparse = true;
2614 }
2615 }
2616 }
2617 return bReparse;
2618 }
2619
2620 /**
2621 * Sets the colour of the current Item based on its current colour. The colours
2622 * proceed in the order stored in COLOR_WHEEL.
2623 *
2624 * @param toSet
2625 * The Item whose colour is to be changed
2626 */
2627 private static void SetColor(Item item, boolean setTransparent, boolean setBackgroundColor) {
2628 // first determine the next color
2629 Colour color = null;
2630 Frame currentFrame = DisplayController.getCurrentFrame();
2631 if (item == null) {
2632 if (FreeItems.hasItemsAttachedToCursor()) {
2633 color = FreeItems.getInstance().get(0).getColor();
2634 } else {
2635 return;
2636 }
2637 // change the background color if the user is pointing on the frame name
2638 } else if (item == currentFrame.getNameItem()) {
2639 // check permissions
2640 if (!item.hasPermission(UserAppliedPermission.full)) {
2641 MessageBay.displayMessage("Insufficient permission to the frame's background color");
2642 return;
2643 }
2644
2645 if (setTransparent) {
2646 currentFrame.setBackgroundColor(null);
2647 } else {
2648 currentFrame.toggleBackgroundColor();
2649 }
2650
2651 DisplayController.requestRefresh(true);
2652 return;
2653 } else {
2654 // check permissions
2655 if (!item.hasPermission(UserAppliedPermission.full)) {
2656 Item editTarget = item.getEditTarget();
2657 if (editTarget != item && editTarget.hasPermission(UserAppliedPermission.full)) {
2658 item = editTarget;
2659 } else {
2660 MessageBay.displayMessage("Insufficient permission to change color");
2661 return;
2662 }
2663 }
2664
2665 // Toggling color of circle center changes the circle fill color
2666 if (item.hasEnclosures()) {
2667 if (setBackgroundColor) {
2668 SetGradientColor(item.getEnclosures().iterator().next(), setTransparent);
2669 } else {
2670 SetFillColor(item.getEnclosures().iterator().next(), setTransparent);
2671 }
2672 } else if (setBackgroundColor) {
2673 color = item.getPaintBackgroundColor();
2674 } else {
2675 color = item.getPaintColor();
2676 }
2677 }
2678
2679 if (setTransparent) {
2680 color = null;
2681 } else if (setBackgroundColor) {
2682 color = ColorUtils.getNextColor(color, TemplateSettings.FillColorWheel.get(), item.getPaintColor());
2683 } else {
2684 color = ColorUtils.getNextColor(color, TemplateSettings.ColorWheel.get(),
2685 currentFrame.getPaintBackgroundColor());
2686 }
2687
2688 if (setBackgroundColor) {
2689 if (item == null && FreeItems.hasItemsAttachedToCursor()) {
2690 for (Item i : FreeItems.getInstance()) {
2691 i.setBackgroundColor(color);
2692 }
2693 } else {
2694 item.setBackgroundColor(color);
2695 item.getParent().setChanged(true);
2696 }
2697 } else {
2698 if (item == null && FreeItems.hasItemsAttachedToCursor()) {
2699 for (Item i : FreeItems.getInstance()) {
2700 i.setColor(color);
2701 }
2702 } else {
2703 item.setColor(color);
2704 item.getParent().setChanged(true);
2705 }
2706 }
2707
2708 DisplayController.requestRefresh(true);
2709 }
2710
2711 private static void SetFillColor(Item item, boolean setTransparent) {
2712 if (item == null) {
2713 return;
2714 }
2715
2716 if (!item.hasPermission(UserAppliedPermission.full)) {
2717 MessageBay.displayMessage("Insufficient permission to change fill color");
2718 return;
2719 }
2720
2721 Item toSet = item;
2722 Colour color = toSet.getFillColor();
2723 if (setTransparent) {
2724 color = null;
2725 } else {
2726 color = ColorUtils.getNextColor(color, TemplateSettings.FillColorWheel.get(), toSet.getGradientColor());
2727 }
2728
2729 // if (color == null) {
2730 // MessageBay.displayMessage("FillColor is now transparent");
2731 // }
2732
2733 toSet.setFillColor(color);
2734 toSet.getParent().setChanged(true);
2735
2736 DisplayController.requestRefresh(true);
2737 }
2738
2739 private static void SetGradientColor(Item item, boolean setTransparent) {
2740 if (item == null) {
2741 return;
2742 }
2743
2744 if (!item.hasPermission(UserAppliedPermission.full)) {
2745 MessageBay.displayMessage("Insufficient permission to change gradient color");
2746 return;
2747 }
2748
2749 Item toSet = item;
2750 Colour color = toSet.getGradientColor();
2751 if (setTransparent) {
2752 color = null;
2753 } else {
2754 color = ColorUtils.getNextColor(color, TemplateSettings.ColorWheel.get(), toSet.getFillColor());
2755 }
2756
2757 // if (color == null) {
2758 // MessageBay.displayMessage("FillColor is now transparent");
2759 // }
2760
2761 toSet.setGradientColor(color);
2762 toSet.getParent().setChanged(true);
2763
2764 DisplayController.requestRefresh(true);
2765 }
2766
2767 /**
2768 * Toggles the given Item's annotation status on\off.
2769 *
2770 * @param toToggle
2771 * The Item to toggle
2772 */
2773 private static void ToggleAnnotation(Item toToggle) {
2774 if (toToggle == null) {
2775 MessageBay.displayMessage("There is no Item selected to toggle");
2776 return;
2777 }
2778
2779 // check permissions
2780 if (!toToggle.hasPermission(UserAppliedPermission.full)) {
2781 MessageBay.displayMessage("Insufficient permission to toggle that item's annotation");
2782 return;
2783 }
2784 toToggle.setAnnotation(!toToggle.isAnnotation());
2785
2786 toToggle.getParent().setChanged(true);
2787 DisplayController.requestRefresh(true);
2788 }
2789
2790 /**
2791 * Toggles the face style of a text item
2792 *
2793 * @param toToggle
2794 * The Item to toggle
2795 */
2796 private static void ToggleFontStyle(Item toToggle) {
2797 if (toToggle == null) {
2798 MessageBay.displayMessage("There is no Item selected to toggle");
2799 return;
2800 }
2801
2802 // check permissions
2803 if (!toToggle.hasPermission(UserAppliedPermission.full)) {
2804 MessageBay.displayMessage("Insufficient permission to toggle that item's annotation");
2805 return;
2806 }
2807
2808 if (toToggle instanceof Text) {
2809 Text text = (Text) toToggle;
2810 text.toggleFontStyle();
2811
2812 text.getParent().setChanged(true);
2813 DisplayController.requestRefresh(true);
2814 }
2815 }
2816
2817 /**
2818 * Toggles the face style of a text item
2819 *
2820 * @param toToggle
2821 * The Item to toggle
2822 */
2823 private static void ToggleFontFamily(Item toToggle) {
2824 if (toToggle == null) {
2825 MessageBay.displayMessage("There is no Item selected to toggle");
2826 return;
2827 }
2828
2829 // check permissions
2830 if (!toToggle.hasPermission(UserAppliedPermission.full)) {
2831 MessageBay.displayMessage("Insufficient permission to toggle that item's annotation");
2832 return;
2833 }
2834
2835 if (toToggle instanceof Text) {
2836 Text text = (Text) toToggle;
2837 text.toggleFontFamily();
2838
2839 text.getParent().setChanged(true);
2840 DisplayController.requestRefresh(true);
2841 }
2842 }
2843
2844 /**
2845 * Creates a new Frameset with the name given by the Item
2846 *
2847 * @param name
2848 */
2849 private static void CreateFrameset(Item item) {
2850 if (item == null) {
2851 MessageBay.displayMessage("There is no selected item to use for the frameset name");
2852 return;
2853 }
2854
2855 if (!(item instanceof Text)) {
2856 MessageBay.displayMessage("Framesets can only be created from text items");
2857 return;
2858 }
2859
2860 // Don't create frameset if the item is linked
2861 if (item.getLink() != null) {
2862 MessageBay.displayMessage("A frameset can not be created from a linked item");
2863 return;
2864 }
2865
2866 // check permissions
2867 if (!item.hasPermission(UserAppliedPermission.full)) {
2868 MessageBay.displayMessage("Insufficient permission to create a frameset from this item");
2869 return;
2870 }
2871
2872 Text text = (Text) item;
2873 try {
2874 // create the new frameset
2875 Frame linkTo = FrameIO.CreateNewFrameset(text.getFirstLine());
2876 DisplayController.setCursor(Item.DEFAULT_CURSOR);
2877 text.setLink(linkTo.getName());
2878 text.getParent().setChanged(true);
2879 FrameUtils.DisplayFrame(linkTo, true, true);
2880 linkTo.moveMouseToDefaultLocation();
2881 // this needs to be done if the user doesn't move the mouse before
2882 // doing Tdfc while the cursor is set to the text cursor
2883 DisplayController.setCursor(Item.DEFAULT_CURSOR);
2884 } catch (Exception e) {
2885 MessageBay.errorMessage(e.getMessage());
2886 }
2887 }
2888
2889 private void move(int direction, boolean isShiftDown, boolean isCtrlDown) {
2890 Item on = FrameUtils.getCurrentItem();
2891
2892 if ((on == null) || (on instanceof Picture)) {
2893 navigateFrame(direction);
2894 return;
2895 }
2896
2897 if (on instanceof Text) {
2898 Text text = (Text) on;
2899 // When the user hits the left and right button with mouse
2900 // positions over the the frame name navigation occurs
2901 if (text.isFrameName()) {
2902 navigateFrame(direction);
2903 return;
2904 } else {
2905 FrameUtils.setLastEdited(text);
2906 DisplayController.setTextCursor(text, direction, false, isShiftDown, isCtrlDown, true);
2907 }
2908 }
2909 }
2910
2911 private void navigateFrame(int direction) {
2912 switch (direction) {
2913 case Text.RIGHT:
2914 case Text.PAGE_UP:
2915 Navigation.NextFrame(false);
2916 break;
2917 case Text.LEFT:
2918 case Text.PAGE_DOWN:
2919 Navigation.PreviousFrame(false);
2920 break;
2921 case Text.HOME:
2922 case Text.LINE_HOME:
2923 Navigation.ZeroFrame();
2924 break;
2925 case Text.END:
2926 case Text.LINE_END:
2927 Navigation.LastFrame();
2928 break;
2929 }
2930 }
2931
2932 /**
2933 * Moves the cursor to the next text item on the frame
2934 *
2935 * @param currentItem
2936 * @param direction
2937 * move up if direction is negative, down if direction is positive
2938 */
2939 public static void NextTextItem(Item currentItem, boolean down) {
2940 // Move the cursor to the next text item
2941 Frame current = DisplayController.getCurrentFrame();
2942 Text title = current.getTitleItem();
2943
2944 Collection<Text> currentItems = FrameUtils.getCurrentTextItems();
2945 List<Text> textItems = new ArrayList<Text>();
2946 // Move to the next text item in the box if
2947 if (currentItems.contains(currentItem)) {
2948 textItems.addAll(currentItems);
2949 } else {
2950 if (title != null) {
2951 textItems.add(title);
2952 }
2953 textItems.addAll(current.getBodyTextItems(true));
2954 }
2955
2956 Collections.sort(textItems);
2957
2958 if (textItems.size() == 0) {
2959 // If there are no text items on the frame its a NoOp
2960 if (title == null) {
2961 return;
2962 }
2963 if (title != null) {
2964 DisplayController.MoveCursorToEndOfItem(title);
2965 }
2966 DisplayController.requestRefresh(true);
2967 return;
2968 }
2969
2970 // If the user is mouse wheeling in free space...
2971 if (currentItem == null) {
2972 // find the nearest item in the correct direction
2973 int currY = DisplayController.getMouseY();
2974 for (int i = 0; i < textItems.size(); i++) {
2975 Item t = textItems.get(i);
2976 if (currY < t.getY()) {
2977 if (down) {
2978 DisplayController.MoveCursorToEndOfItem(t);
2979 } else {
2980 if (i == 0) {
2981 DisplayController.MoveCursorToEndOfItem(current.getTitleItem());
2982 } else {
2983 DisplayController.MoveCursorToEndOfItem(textItems.get(i - 1));
2984 }
2985 }
2986 DisplayController.requestRefresh(true);
2987 return;
2988 }
2989 }
2990 // If we are at the bottom of the screen and the user scrolls down
2991 // then scroll backup to the title
2992 if (textItems.size() > 0) {
2993 DisplayController.MoveCursorToEndOfItem(textItems.get(textItems.size() - 1));
2994 }
2995 return;
2996 }
2997
2998 // Find the current item... then move to the next item
2999 int i = textItems.indexOf(currentItem);
3000
3001 int nextIndex = i + (down ? 1 : -1);
3002 if (nextIndex >= 0 && nextIndex < textItems.size()) {
3003 DisplayController.MoveCursorToEndOfItem(textItems.get(nextIndex));
3004 } else {
3005 DisplayController.MoveCursorToEndOfItem(currentItem);
3006 }
3007 return;
3008 }
3009
3010 private static void copyItemToClipboard(Item on) {
3011 if (on == null || !(on instanceof Text)) {
3012 return;
3013 }
3014
3015 Text text = (Text) on;
3016 String string = text.copySelectedText();
3017
3018 if (string == null || string.length() == 0) {
3019 string = text.getText();
3020 }
3021
3022 // add the text of the item to the clipboard
3023 ClipboardData clipboardData = ClipboardData.fromString(string);
3024 EcosystemManager.getClipboardManager().set(clipboardData);
3025 }
3026
3027 public static void delete(Item toDelete, Collection<Item> deleteItems, Collection<Item> deleteEnclosure,
3028 boolean alternateMode) {
3029 boolean bRecalculate = false;
3030
3031 FrameUtils.setLastEdited(null);
3032 _offX = _offY = 0;
3033
3034 Frame currentFrame = DisplayController.getCurrentFrame();
3035 // check if the user is pointing at the frame's framename
3036 if (toDelete != null && toDelete == currentFrame.getNameItem()) {
3037 currentFrame.clear(false);
3038 DisplayController.requestRefresh(true);
3039 return;
3040 }
3041
3042 // if the user is deleting items attached to the cursor
3043 if (FreeItems.hasItemsAttachedToCursor()) {
3044 // if this is an item-swap
3045 if (FreeItems.getInstance().size() == 1 && FreeItems.getInstance().get(0) instanceof Text
3046 && toDelete != null && toDelete instanceof Text) {
3047
3048 // check permissions
3049 if (!toDelete.hasPermission(UserAppliedPermission.full)) {
3050 MessageBay.displayMessage("Insufficient permission to swap Item text");
3051 return;
3052 }
3053 Text anchored = (Text) toDelete;
3054 Text free = (Text) FreeItems.getInstance().get(0);
3055 SessionStats.DeletedItem(free);
3056 // List<String> temp = anchored.getText();
3057 anchored.setText(free.getText());
3058 anchored.setFormula(free.getFormula());
3059
3060 // free.setTextList(temp);
3061 FreeItems.getInstance().clear();
3062
3063 anchored.getParent().setChanged(true);
3064
3065 bRecalculate |= free.recalculateWhenChanged();
3066 bRecalculate |= anchored.recalculateWhenChanged();
3067
3068 // update the offset since the text has changed
3069 _offX = DisplayController.getMouseX() - anchored.getX() + anchored.getOffset().getX();
3070 _offY = DisplayController.getMouseY() - anchored.getY() + anchored.getOffset().getY();
3071 } else {
3072 // if shift is pressed delete the entire shape attached to the dot
3073 if (alternateMode) {
3074 List<Item> tmp = new ArrayList<Item>(FreeItems.getInstance());
3075 for (Item i : tmp) {
3076 // remove entire rectangles instead of just the corner
3077 if (i instanceof Dot) {
3078 FreeItems.getInstance().addAll(i.getAllConnected());
3079 for (Item j : i.getAllConnected()) {
3080 if (j instanceof Dot) {
3081 FreeItems.getInstance().addAll(j.getLines());
3082 }
3083 }
3084 }
3085 }
3086 }
3087 deleteItems(FreeItems.getInstance());
3088 }
3089 // reset the mouse cursor
3090 updateCursor();
3091 // the user is not pointing at an item
3092 } else if (toDelete == null) {
3093
3094 // if the user is pointing inside a closed shape, delete it
3095
3096 Collection<Item> items = null;
3097 // if shift is down, only delete the enclosing shape (ignore the items inside)
3098 if (alternateMode) {
3099 Collection<Item> tmp = deleteEnclosure;
3100 if (tmp != null) {
3101 items = new ArrayList<Item>();
3102 items.addAll(tmp);
3103 for (Item i : tmp) {
3104 if (i instanceof Dot) {
3105 items.addAll(((Dot) i).getLines());
3106 }
3107 }
3108 }
3109 } else {
3110 items = deleteItems;
3111 }
3112
3113 if (items != null) {
3114 Collection<Item> toRemove = new LinkedHashSet<Item>(items.size());
3115 for (Item ip : items) {
3116 if (ip.hasPermission(UserAppliedPermission.full)) {
3117 // Only include lines if one of their enpoints are also
3118 // being removed
3119 if (ip instanceof Line) {
3120 Line l = (Line) ip;
3121 Item end = l.getEndItem();
3122 Item start = l.getStartItem();
3123
3124 // If one end of a line is being deleted, remove the
3125 // other end if all its connecting lines are being
3126 // deleted
3127 if (items.contains(end)) {
3128 if (!items.contains(start) && items.containsAll(start.getLines())) {
3129 toRemove.add(start);
3130 }
3131 } else if (items.contains(start)) {
3132 if (items.containsAll(end.getLines())) {
3133 toRemove.add(end);
3134 }
3135 } else {
3136 continue;
3137 }
3138 }
3139 toRemove.add(ip);
3140 }
3141 }
3142
3143 deleteItems(toRemove);
3144
3145 // reset the mouse cursor
3146 updateCursor();
3147 DisplayController.requestRefresh(true);
3148
3149 // otherwise this is an undo command
3150 }
3151 return;
3152 // this is a delete command
3153 } else {
3154
3155 // Special case if toDelete item is an image: only want to delete if over
3156 // non-background pixel color
3157 if (toDelete instanceof Picture) {
3158 int mouseX = DisplayController.getMouseX();
3159 int mouseY = DisplayController.getMouseY();
3160 Picture clickedOnPicture = (Picture) toDelete;
3161 Frame current_frame = DisplayController.getCurrentFrame();
3162 Colour bg_col = current_frame.getBackgroundColor();
3163 if (clickedOnPicture.MouseOverBackgroundPixel(mouseX, mouseY, bg_col)) {
3164 return;
3165 }
3166 // If get to here, then user clicked on an image (non-trival pixel color),
3167 // so go ahead and let it be deleted in the usual way
3168 }
3169
3170 // check permissions
3171 if (!toDelete.hasPermission(UserAppliedPermission.full)) {
3172 Item editTarget = toDelete.getEditTarget();
3173 if (editTarget != toDelete && editTarget.hasPermission(UserAppliedPermission.full)) {
3174 toDelete = editTarget;
3175 } else {
3176 MessageBay.displayMessage("Insufficient permission to delete item");
3177 return;
3178 }
3179 }
3180
3181 Frame parent = toDelete.getParent();
3182 if (parent != null) {
3183 parent.setChanged(true);
3184 }
3185 Collection<Item> toUndo = null;
3186 if (toDelete.isLineEnd()) {
3187 // delete the entire connected shape if shift is down
3188 if (alternateMode) {
3189 List<Item> tmp = new ArrayList<Item>();
3190 tmp.add(toDelete);
3191 // remove entire shape instead of just the corner
3192 tmp.addAll(toDelete.getAllConnected());
3193 for (Item j : toDelete.getAllConnected()) {
3194 if (j instanceof Dot) {
3195 tmp.addAll(j.getLines());
3196 }
3197 }
3198 deleteItems(tmp);
3199 return;
3200 } else {
3201 toUndo = deleteLineEnd(toDelete);
3202 }
3203 // delete the entire connected shape if shift is down, unless we're hovering the
3204 // end of the line
3205 } else if (toDelete instanceof WidgetEdge) { // must notify
3206 // widgets that they
3207 // are being deleted
3208 ((WidgetEdge) toDelete).getWidgetSource().onDelete();
3209 toUndo = toDelete.getConnected();
3210 } else if (toDelete instanceof Line && alternateMode
3211 || toDelete.getHighlightMode() == Item.HighlightMode.Disconnect) {
3212 Line line = (Line) toDelete;
3213 Item start = line.getStartItem();
3214 Item end = line.getEndItem();
3215 Collection<Item> delete = new LinkedList<Item>();
3216 delete.add(toDelete);
3217 if (end.getLines().size() == 1) {
3218 delete.add(end);
3219 } else {
3220 end.removeLine(line);
3221 }
3222 if (start.getLines().size() == 1) {
3223 delete.add(start);
3224 } else {
3225 start.removeLine(line);
3226 }
3227 toUndo = delete;
3228 } else {
3229 bRecalculate |= toDelete.recalculateWhenChanged();
3230 toUndo = toDelete.getConnected(); // copy(toDelete.getConnected());
3231 }
3232 SessionStats.DeletedItems(toUndo);
3233 if (parent != null) {
3234 parent.addToUndoDelete(toUndo);
3235 parent.removeAllItems(toUndo); // toDelete.getConnected()
3236 }
3237 // reset the mouse cursor
3238 updateCursor();
3239 if (parent != null) {
3240 // ItemUtils.EnclosedCheck(parent.getItems());
3241 ItemUtils.Justify(parent);
3242 }
3243 if (toDelete.hasOverlay()) {
3244 FrameUtils.Parse(parent, false, false);
3245 DisplayController.requestRefresh(false);
3246 }
3247
3248 DisplayController.setCursor(Item.DEFAULT_CURSOR);
3249
3250 }
3251
3252 currentFrame.notifyObservers(bRecalculate);
3253
3254 DisplayController.requestRefresh(true);
3255 }
3256
3257 public static void mouseEnteredWindow() {
3258 // if there is expeditee data on the clipboard that has not yet been autoPasted,
3259 // autoPaste it
3260 // Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
3261 // Transferable content = c.getContents(null);
3262 ClipboardData clipboardData = EcosystemManager.getClipboardManager().get();
3263 try {
3264 if (clipboardData.data instanceof ExpDataHandler) { // Expeditee data
3265 ExpDataHandler expData = (ExpDataHandler) clipboardData.data;
3266 if (expData.autoPaste) {
3267 List<Item> items = new ExpClipReader(DisplayController.getMouseX(), DisplayController.getMouseY())
3268 .read(expData.items);
3269 // generate new IDs and pickup
3270 pickup(ItemUtils.CopyItems(items));
3271 // update the clipboard contents so they won't be autoPasted again
3272 expData.autoPaste = false;
3273 /*
3274 * String stringData = ""; Image imageData = null;
3275 * if(content.isDataFlavorSupported(DataFlavor.stringFlavor)) { stringData =
3276 * (String) content.getTransferData(DataFlavor.stringFlavor); }
3277 * if(content.isDataFlavorSupported(DataFlavor.imageFlavor)) { imageData =
3278 * (Image) content.getTransferData(DataFlavor.imageFlavor); } c.setContents(new
3279 * ItemSelection(stringData, imageData, expData), null);
3280 */
3281 EcosystemManager.getClipboardManager().set(clipboardData);
3282 }
3283 }
3284 } catch (Exception ex) {
3285 ex.printStackTrace();
3286 }
3287 }
3288
3289 private static boolean oldControlDown = false;
3290
3291 private void mouseMoved(Point to) {
3292
3293 boolean shiftStateChanged = false;
3294 if (oldControlDown != StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL)) {
3295 shiftStateChanged = true;
3296 oldControlDown = StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL);
3297 }
3298
3299 float MouseX = to.getX();
3300 float MouseY = to.getY();
3301
3302 // Moving the mouse a certain distance removes the last edited text if it is
3303 // empty
3304 Text lastEdited = FrameUtils.getLastEdited();
3305 if (lastEdited != null && lastEdited.getText().length() == 0 && lastEdited.getPosition().getDistanceTo(to) > 20) // TODO:
3306 // Remove
3307 // magic
3308 // constant.
3309 // cts16
3310 {
3311 FrameUtils.setLastEdited(null);
3312 }
3313
3314 // If shift is down then the movement is constrained
3315 if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL) && FreeItems.getInstance().size() > 0) {
3316 // Check if we are rubber banding a line
3317 if (shiftStateChanged && FreeItems.rubberBanding()) {
3318 // Get the line end that is being rubber banded
3319 Item thisEnd = FreeItems.getInstance().get(0).isLineEnd() ? FreeItems.getInstance().get(0)
3320 : FreeItems.getInstance().get(1);
3321 Line line = (Line) (FreeItems.getInstance().get(0).isLineEnd() ? FreeItems.getInstance().get(1)
3322 : FreeItems.getInstance().get(0));
3323 Item otherEnd = line.getOppositeEnd(thisEnd);
3324 int deltaX = Math.abs(to.getX() - otherEnd.getX());
3325 int deltaY = Math.abs(to.getY() - otherEnd.getY());
3326 // Check if its a vertical line
3327 if (deltaX < deltaY / 2) {
3328 new Constraint(thisEnd, otherEnd, thisEnd.getParentOrCurrentFrame().getNextItemID(),
3329 Constraint.VERTICAL);
3330 }
3331 // Check if its horizontal
3332 else if (deltaY <= deltaX / 2) {
3333 new Constraint(thisEnd, otherEnd, thisEnd.getParentOrCurrentFrame().getNextItemID(),
3334 Constraint.HORIZONTAL);
3335 } else {
3336 // Add DIAGONAL constraints
3337 int constraint = Constraint.DIAGONAL_NEG;
3338 // Check if the slope is positive
3339 if ((thisEnd.getY() - otherEnd.getY()) / (double) (thisEnd.getX() - otherEnd.getX()) > 0.0) {
3340 constraint = Constraint.DIAGONAL_POS;
3341 }
3342
3343 new Constraint(thisEnd, otherEnd, thisEnd.getParentOrCurrentFrame().getNextItemID(), constraint);
3344 }
3345 // If it's a line-end attached to two lines lengthen the
3346 // shorter so it is the same length as the longer line
3347 } else if (FreeItems.getInstance().size() == 3) {
3348 // check if we are rubber banding the corner of a shape
3349 Item thisEnd = getShapeCorner(FreeItems.getInstance());
3350 if (thisEnd != null) {
3351 Line line1 = thisEnd.getLines().get(0);
3352 Line line2 = thisEnd.getLines().get(1);
3353
3354 // Check if the two lines are constrained and hence it is a rectangle
3355 Integer c1 = line1.getPossibleConstraint();
3356 Integer c2 = line2.getPossibleConstraint();
3357
3358 if (c1 != null && c2 != null) {
3359 // This is the case of a constrained rectangle
3360 if ((c1 == Constraint.VERTICAL && c2 == Constraint.HORIZONTAL)
3361 || (c1 == Constraint.HORIZONTAL && c2 == Constraint.VERTICAL)) {
3362 Line vLine = line2;
3363 Line hLine = line1;
3364 if (c1 == Constraint.VERTICAL) {
3365 vLine = line1;
3366 hLine = line2;
3367 }
3368 Item hOtherEnd = hLine.getOppositeEnd(thisEnd);
3369 Item vOtherEnd = vLine.getOppositeEnd(thisEnd);
3370
3371 double vLength = Math.abs(vOtherEnd.getY() - MouseY);
3372 double hLength = Math.abs(hOtherEnd.getX() - MouseX);
3373
3374 if (vLength > hLength) {
3375 MouseX = Math.round(hOtherEnd.getX() + vLength * (MouseX > hOtherEnd.getX() ? 1 : -1));
3376 } else /* if (hLength > vLength) */ {
3377 MouseY = Math.round(vOtherEnd.getY() + hLength * (MouseY > vOtherEnd.getY() ? 1 : -1));
3378 }
3379 }
3380 // Other wise it is a not constrained shape so constrain
3381 // the two lines lengths to be equal
3382 } else {
3383 Item lineEnd1 = line1.getOppositeEnd(thisEnd);
3384 Item lineEnd2 = line2.getOppositeEnd(thisEnd);
3385 double l1 = Point.distanceBetween(lineEnd1.getPosition(), to);
3386 double l2 = Point.distanceBetween(lineEnd2.getPosition(), to);
3387 double l3 = Point.distanceBetween(lineEnd1.getPosition(), lineEnd2.getPosition());
3388 // l1 needs to be the shorter end
3389 if (l1 > l2) {
3390 Item temp = lineEnd1;
3391 lineEnd1 = lineEnd2;
3392 lineEnd2 = temp;
3393 double tempL = l1;
3394 l1 = l2;
3395 l2 = tempL;
3396 }
3397 // Now use the cosine rule to calculate the angle between l1 and l3
3398 double cosTheta = (l1 * l1 + l3 * l3 - l2 * l2) / (2 * l1 * l3);
3399 // now calculate the new length for the lines using cos rule
3400 double l_new = l3 / (2 * cosTheta);
3401 double ratio = l_new / l1;
3402 MouseX = Math.round((to.getX() - lineEnd1.getX()) * ratio) + lineEnd1.getX();
3403 MouseY = Math.round((to.getY() - lineEnd1.getY()) * ratio) + lineEnd1.getY();
3404 }
3405 }
3406 }
3407 } else if (shiftStateChanged && !StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL)
3408 && FreeItems.rubberBanding()) {
3409 // Get the line end that is being rubber banded
3410 Item thisEnd = FreeItems.getInstance().get(0).isLineEnd() ? FreeItems.getInstance().get(0)
3411 : FreeItems.getInstance().get(1);
3412 thisEnd.removeAllConstraints();
3413 }
3414
3415 refreshHighlights();
3416
3417 if (FreeItems.hasCursor()) {
3418 move(FreeItems.getCursor(), to, true);
3419 }
3420
3421 if (FreeItems.hasItemsAttachedToCursor()) {
3422 move(FreeItems.getInstance(), to);
3423 }
3424
3425 if (_forceArrowCursor) {
3426 updateCursor();
3427 }
3428
3429 _forceArrowCursor = true;
3430 }
3431
3432 /**
3433 * Gets the rectangle corner from the list of items that are part of a
3434 * rectangle.
3435 *
3436 * @param partialRectangle
3437 * a corner and its two connecting lines.
3438 * @return the rectangle corner or null if the list of items is not part of a
3439 * rectangle.
3440 */
3441 private static Item getShapeCorner(List<Item> partialRectangle) {
3442 if (partialRectangle.size() < 3) {
3443 return null;
3444 }
3445 Item lineEnd = null;
3446 // only one lineEnd will be present for rectangles
3447 // All other items must be lines
3448 for (Item i : partialRectangle) {
3449 if (i.isLineEnd()) {
3450 if (lineEnd == null) {
3451 lineEnd = i;
3452 } else {
3453 return null;
3454 }
3455 } else if (!(i instanceof Line)) {
3456 return null;
3457 }
3458 }
3459 // if this is at least the corner of two connected lines
3460 if (lineEnd != null && lineEnd.getAllConnected().size() >= 5) {
3461 return lineEnd;
3462 }
3463
3464 return null;
3465 }
3466
3467 /**
3468 * Performs picking up of a single item, with special handling for lines.
3469 */
3470 private void handlePickup(Item item, Point atPosition, boolean copy, boolean extrude) {
3471 // check permissions
3472 if (copy && item.isLineEnd()) {
3473 if (!item.hasPermission(UserAppliedPermission.full)) {
3474 MessageBay.displayMessage("Insufficient permission to unreel");
3475 return;
3476 }
3477 } else if (!item.hasPermission(UserAppliedPermission.full)) {
3478 Item editTarget = item.getEditTarget();
3479 if ((editTarget != item && editTarget.hasPermission(UserAppliedPermission.full))
3480 || (copy && editTarget.hasPermission(UserAppliedPermission.copy))) {
3481 item = editTarget;
3482 } else {
3483 MessageBay.displayMessage("Insufficient permission to pick up item");
3484 return;
3485 }
3486 }
3487
3488 // Make a copy of the picked-up item
3489 if (copy) {
3490 List<Item> copies = ItemUtils.UnreelLine(item, false);
3491 // Copies will NOT be null if the user right clicked on a point
3492 if (copies == null) {
3493 Collection<Item> originals = item.getConnected();
3494 copies = ItemUtils.CopyItems(originals, extrude);
3495 // if this is the title of the frame, link it to the frame
3496 if (originals.size() == 1 && copies.size() == 1) {
3497 Item firstCopy = copies.get(0);
3498 Item original = originals.iterator().next();
3499 if (original.getLink() == null && original.isFrameTitle()) {
3500 // save the frame after copying
3501 // i.getParent().setChanged(true);
3502 firstCopy.setLink(original.getParentOrCurrentFrame().getName());
3503 }
3504 }
3505
3506 FrameGraphics.changeHighlightMode(item, HighlightMode.None);
3507
3508 if (!extrude) {
3509 clearParent(copies);
3510 }
3511 }
3512
3513 ItemUtils.Justify(copies);
3514
3515 pickup(copies);
3516
3517 // Just pick up the item without copying
3518 } else {
3519
3520 // BROOK: WIDGET RECTANGLES DONT ALLOW DISCONNECTION
3521 if (item instanceof Line && !(item instanceof WidgetEdge)) {
3522 // Check if within 20% of the end of the line
3523 Line l = (Line) item;
3524 Item toDisconnect = l.getEndPointToDisconnect(atPosition.getX(), atPosition.getY());
3525
3526 if (toDisconnect == null) {
3527 pickup(item);
3528 } else {
3529 if (toDisconnect.getHighlightMode() == Item.HighlightMode.Normal) {
3530 DisplayController.setCursorPosition(toDisconnect.getPosition(), false);
3531 pickup(toDisconnect);
3532 } else {
3533 List<Line> lines = toDisconnect.getLines();
3534 // This is to remove constraints from single lines
3535 // with constraints...
3536 // ie. partially deleted rectangles
3537 if (lines.size() == 1) {
3538 toDisconnect.removeAllConstraints();
3539
3540 DisplayController.setCursorPosition(toDisconnect.getPosition(), false);
3541 // This is to ensure the selected mode will be set
3542 // to Normal rather than disconnect when the line is
3543 // anchored
3544 toDisconnect.setHighlightMode(Item.HighlightMode.Normal);
3545 pickup(toDisconnect);
3546 } else {
3547 // If we are then detatch the line and pick up its
3548 // end point...
3549 Frame currentFrame = DisplayController.getCurrentFrame();
3550 Item newPoint = null;
3551
3552 // If the point we are disconnecting is text...
3553 // Then we want to leave the text behind
3554 // And disconnect a point
3555 if (toDisconnect instanceof Text) {
3556 newPoint = new Dot(toDisconnect.getX(), toDisconnect.getY(), -1);
3557 Item.DuplicateItem(toDisconnect, newPoint);
3558 } else {
3559 newPoint = toDisconnect.copy();
3560 }
3561
3562 currentFrame.addItem(newPoint);
3563 // remove the current item from the connected
3564 // list for this item
3565 l.replaceLineEnd(toDisconnect, newPoint);
3566 // remove unneeded constrains
3567 newPoint.removeAllConstraints();
3568
3569 // Set the new points mode to normal before picking
3570 // it up so it will be restored correctly when
3571 // anchored
3572 newPoint.setHighlightMode(Item.HighlightMode.Normal);
3573 toDisconnect.setHighlightMode(Item.HighlightMode.None);
3574 DisplayController.setCursorPosition(toDisconnect.getPosition(), false);
3575 pickup(newPoint);
3576 ItemUtils.EnclosedCheck(toDisconnect.getParentOrCurrentFrame().getItems());
3577 }
3578 }
3579 }
3580 } else {
3581 if (item.isLineEnd()) {
3582 DisplayController.setCursorPosition(item.getPosition(), false);
3583 }
3584 pickup(item);
3585 }
3586 }
3587 }
3588
3589 /**
3590 * Performs picking up of a single item, with special handling for lines.
3591 */
3592 private void handlePickup(Collection<Item> items, Collection<Item> enclosure, Point atPosition, boolean copy) {
3593 List<Item> toPickup = new ArrayList<Item>(items.size());
3594
3595 if (copy) {
3596 List<Item> copies = new ArrayList<Item>();
3597 // Set the selection mode for the items that were clicked in
3598 Collection<Item> enclosed = getFullyEnclosedItems(items);
3599 if (enclosed.size() == 0) {
3600 MessageBay.displayMessage("Insufficient permission to copy items");
3601 } else {
3602 copies = copy(enclosed);
3603 clearParent(copies);
3604 ItemUtils.Justify(copies);
3605 pickup(copies);
3606 for (Item i : items) {
3607 i.setHighlightMode(HighlightMode.None);
3608 }
3609 }
3610 toPickup = copies;
3611 } else {
3612 for (Item ip : items) {
3613 if (ip.hasPermission(UserAppliedPermission.full)) {
3614 toPickup.add(ip);
3615 }
3616 }
3617 }
3618 pickup(toPickup);
3619 }
3620
3621 /**
3622 * Marks the items as not belonging to any specific frame. When picking up items
3623 * the parent will be automatically cleared for items on the current frame but
3624 * not for overlay items. This method ensures that overlay items will also be
3625 * cleared. This is useful when picking up copies of items from an overlay (with
3626 * the right mouse button) to ensure that the copy will be anchored on the
3627 * current frame rather than the overlay. When items are picked up with the
3628 * middle button clearParent should NOT be called.
3629 *
3630 * @param items
3631 * to have their parent cleared
3632 */
3633 private static void clearParent(List<Item> items) {
3634 for (Item i : items) {
3635 // The next line is only necessary for circles...
3636 // Need to clean up/refactory some of this stuff
3637 i.getParentOrCurrentFrame().removeItem(i);
3638 i.setParent(null);
3639 }
3640 }
3641
3642 private static boolean doMerging(Item clicked) {
3643 if (clicked == null) {
3644 return false;
3645 }
3646
3647 if(clicked instanceof WidgetCorner) {
3648 final WidgetCorner wc = (WidgetCorner) clicked;
3649 if(wc.getWidgetSource().ItemsMiddleClickDropped()) {
3650 return true;
3651 }
3652 }
3653
3654 // // Brook: widgets do not merge
3655 // if (clicked instanceof WidgetCorner)
3656 // return false;
3657 //
3658 // // Brook: widgets do not merge
3659 // if (clicked instanceof WidgetEdge)
3660 // return false;
3661
3662 // System.out.println(FreeItems.getInstance().size());
3663 if (isRubberBandingCorner()) {
3664 if (clicked.isLineEnd() || clicked.getAllConnected().contains(FreeItems.getItemAttachedToCursor())) {
3665 return true;
3666 }
3667 }
3668
3669 if (FreeItems.getInstance().size() > 2) {
3670 return false;
3671 }
3672
3673 Item attachedToCursor = FreeItems.getItemAttachedToCursor();
3674
3675 if (clicked instanceof Text && !(attachedToCursor instanceof Text || attachedToCursor.isLineEnd())) {
3676 return false;
3677 }
3678
3679 if (clicked instanceof Picture) {
3680 int mouseX = DisplayController.getMouseX();
3681 int mouseY = DisplayController.getMouseY();
3682 Picture clickedOnPicture = (Picture) clicked;
3683 Frame current_frame = DisplayController.getCurrentFrame();
3684 Colour bg_col = current_frame.getBackgroundColor();
3685 if (clickedOnPicture.MouseOverBackgroundPixel(mouseX, mouseY, bg_col)) {
3686 // Make 'clicked' null, effectively causing a back() operation
3687 return false;
3688 }
3689 }
3690
3691 return true;
3692 }
3693
3694 private static boolean isRubberBandingCorner() {
3695 return getShapeCorner(FreeItems.getInstance()) != null;
3696 }
3697
3698 public List<Item> deleteItemsAction(Item item) {
3699 List<Item> items = new ArrayList<Item>();
3700 Item merger = FreeItems.getItemAttachedToCursor();
3701 assert (merger != null);
3702
3703 // check permissions
3704 boolean isOwnerPermissionChange =
3705 item.getOwner().equals(UserSettings.UserName.get()) && merger.getText().startsWith("Permission:");
3706 if (!item.hasPermission(UserAppliedPermission.full) && !isOwnerPermissionChange) {
3707 // Items on the message box have parent == null
3708 if (item.getParent() != null) {
3709 if (!item.isFrameName()) {
3710 Item editTarget = item.getEditTarget();
3711 if (editTarget != item && editTarget.hasPermission(UserAppliedPermission.full)) {
3712 item = editTarget;
3713 } else {
3714 MessageBay.displayMessage("Insufficient permission");
3715 return items;
3716 }
3717 }
3718
3719 } else /* Its in the message area */ {
3720 MessageBay.displayMessage("Insufficient permission");
3721 return items;
3722 }
3723 }
3724
3725 Collection<Item> left = null;
3726 // when anchoring a line end onto a text line end, holding shift
3727 // prevents the line ends from being merged
3728 if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) {
3729 left = FreeItems.getInstance();
3730 } else {
3731 left = merge(FreeItems.getInstance(), item);
3732 }
3733 Collection<Item> toDelete = new LinkedList<Item>();
3734 toDelete.addAll(FreeItems.getInstance());
3735 toDelete.removeAll(left);
3736 anchor(left);
3737 FreeItems.getInstance().clear();
3738 DisplayController.getCurrentFrame().removeAllItems(toDelete);
3739 updateCursor();
3740 // Make sure the dot goes away when anchoring a line end behind
3741 // a text line end
3742 if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) {
3743 refreshHighlights();
3744 }
3745 DisplayController.requestRefresh(true);
3746 return items;
3747 // otherwise, anchor the items
3748 }
3749
3750 private static final int RECTANGLE_TO_POINT_THRESHOLD = 20;
3751
3752 public static Collection<Item> merge(List<Item> merger, Item mergee) {
3753 assert (mergee != null);
3754 if (mergee.isFrameName()) {
3755 return mergee.getParent().merge(merger);
3756 }
3757
3758 // if(mergee instanceof XRayable)
3759 // return merger;
3760
3761 // check for rectangle merging
3762 if (merger.size() == 3 && mergee.getLines().size() == 2) {
3763 Item corner = getShapeCorner(merger);
3764 // if this is a corner of a shape
3765 if (corner != null) {
3766 Collection<Item> allConnected = corner.getAllConnected();
3767 // Check if we are collapsing a rectangle
3768 if (allConnected.size() == 8 && allConnected.contains(mergee)) {
3769 DisplayController.setCursorPosition(mergee.getPosition());
3770 DisplayController.getCurrentFrame().removeAllItems(allConnected);
3771
3772 // find the point opposite corner...
3773 Item opposite = null;
3774 List<Line> lines = corner.getLines();
3775 for (Line l : lines) {
3776 allConnected.remove(l.getOppositeEnd(corner));
3777 }
3778 allConnected.remove(corner);
3779 for (Item i : allConnected) {
3780 if (i.isLineEnd()) {
3781 opposite = i;
3782 break;
3783 }
3784 }
3785 assert (opposite != null);
3786
3787 // check if the rectangle is small enough that it should be
3788 // collapsed to a single point
3789 int x1 = Math.abs(opposite.getX() - mergee.getX());
3790 int x2 = Math.abs(opposite.getY() - mergee.getY());
3791 int distance = (int) Math.sqrt(Math.pow(x1, 2) + Math.pow(x2, 2));
3792
3793 if (distance < RECTANGLE_TO_POINT_THRESHOLD) {
3794 mergee.removeAllConstraints();
3795 mergee.removeAllLines();
3796 mergee.setThickness(4 * mergee.getThickness());
3797 return mergee.getAllConnected();
3798 } else {
3799 removeAllLinesExcept(mergee, opposite);
3800 removeAllLinesExcept(opposite, mergee);
3801
3802 return mergee.getAllConnected();
3803 }
3804 }
3805 }
3806 }
3807
3808 List<Item> remain = new ArrayList<Item>();
3809 Item res = null;
3810
3811 for (Item i : merger) {
3812 if (!i.isVisible()) {
3813 continue;
3814 }
3815 // check for link merging
3816 if (i instanceof Text && FrameIO.isValidFrameName((((Text) i).getFirstLine()))
3817 && FrameIO.canAccessFrame((((Text) i).getFirstLine()))) {
3818 // check that we can actually access the frame this link
3819 // corresponds to
3820 mergee.setLink(((Text) i).getFirstLine());
3821 } else {
3822 // check for attribute merging
3823 if (i instanceof Text && !i.isLineEnd()) {
3824 Text txt = (Text) i;
3825
3826 // if this is not an attribute merge
3827 if (!AttributeUtils.setAttribute(mergee, txt)) {
3828 // set mouse position for text merges
3829 if (mergee instanceof Text) {
3830 ((Text) mergee).insertText(txt.getText(), DisplayController.getMouseX(),
3831 DisplayController.getMouseY());
3832 // Delete the item which had its text merged
3833 txt.delete();
3834 return remain;
3835 } else if (mergee instanceof WidgetCorner) {
3836 if (merger.size() == 1 && txt.getLink() != null) {
3837 // If the text item is linked then use that
3838 ((WidgetCorner) mergee).setLink(txt.getAbsoluteLink(), txt);
3839 } else {
3840 remain.addAll(merger);
3841 }
3842 return remain;
3843 } else if (mergee instanceof WidgetEdge) {
3844 if (merger.size() == 1 && txt.getLink() != null) {
3845 // If the text item is linked then use that
3846 ((WidgetEdge) mergee).setLink(txt.getAbsoluteLink(), txt);
3847 } else {
3848 remain.addAll(merger);
3849 }
3850 return remain;
3851 } else if (mergee instanceof Dot) {
3852 DisplayController.setCursorPosition(mergee.getPosition());
3853 txt.setPosition(mergee.getPosition());
3854 txt.setThickness(mergee.getThickness());
3855 Frame parent = mergee.getParent();
3856 parent.removeItem(mergee);
3857 anchor(txt);
3858 // change the end points of the lines to the text
3859 // item
3860 while (mergee.getLines().size() > 0) {
3861 Line l = mergee.getLines().get(0);
3862 l.replaceLineEnd(mergee, txt);
3863 }
3864 break;
3865 }
3866
3867 // TODO tidy this up...
3868 // Dot override doesnt use the x and y coords
3869 // Text does... but could be removed
3870 res = mergee.merge(i, DisplayController.getMouseX(), DisplayController.getMouseY());
3871 if (res != null) {
3872 remain.add(res);
3873 }
3874 }
3875 } else {
3876 if (mergee.isLineEnd()) {
3877 DisplayController.setCursorPosition(mergee.getPosition());
3878 }
3879 // Moving the cursor ensures shapes are anchored correctly
3880 res = mergee.merge(i, DisplayController.getMouseX(), DisplayController.getMouseY());
3881 if (res != null) {
3882 remain.addAll(res.getConnected());
3883 }
3884
3885 }
3886 }
3887 }
3888 updateCursor();
3889
3890 mergee.getParent().setChanged(true);
3891
3892 ItemUtils.EnclosedCheck(mergee.getParent().getItems());
3893 // Mike: Why does parse frame have to be called?!?
3894 FrameUtils.Parse(mergee.getParent());
3895
3896 return remain;
3897 }
3898
3899 private static void removeAllLinesExcept(Item from, Item but) {
3900 List<Line> lines = new LinkedList<Line>();
3901 lines.addAll(from.getLines());
3902 for (Line line : lines) {
3903 if (line.getOppositeEnd(from) != but) {
3904 from.removeLine(line);
3905 }
3906 }
3907
3908 List<Constraint> consts = new LinkedList<Constraint>();
3909 consts.addAll(from.getConstraints());
3910 for (Constraint c : consts) {
3911 if (c.getOppositeEnd(from) != but) {
3912 from.removeConstraint(c);
3913 }
3914 }
3915 }
3916
3917 public void anchorFreeItemsAction(Collection<Item> items) {
3918 if (items != null && FreeItems.getInstance().size() == 1) {
3919 Item item = FreeItems.getItemAttachedToCursor();
3920 if (item instanceof Text) {
3921 Text text = (Text) item;
3922 if (AttributeUtils.setAttribute(text, text, 2)) {
3923 items.removeAll(FrameUtils.getEnclosingLineEnds().iterator().next().getAllConnected());
3924 for (Item i : items) {
3925 AttributeUtils.setAttribute(i, text);
3926 }
3927 FreeItems.getInstance().clear();
3928 }
3929 }
3930 }
3931
3932 // if a line is being rubber-banded, check for auto
3933 // straightening
3934 anchor(FreeItems.getInstance());
3935 // FreeItems.getInstance().clear();
3936 updateCursor();
3937 _offX = _offY = 0;
3938 }
3939
3940 public void copyPlace(Item item) {
3941 List<Item> copies = null;
3942 if (FreeItems.getInstance().size() == 1 && FreeItems.getItemAttachedToCursor().isAutoStamp()) {
3943 // Dont stamp if the user is painting... because we dont want to
3944 // save any of the items created!
3945 return;
3946 // if the user is clicking on something, merge the items unless
3947 // it is a point onto something other than a lineEnd or a dot
3948 } else if (item != null
3949 && (!(FreeItems.getItemAttachedToCursor() instanceof Dot) || item instanceof Dot || item.isLineEnd())) {
3950 if(item instanceof WidgetCorner) {
3951 final WidgetCorner wc = (WidgetCorner) item;
3952 wc.getWidgetSource().ItemsRightClickDropped();
3953 return;
3954 }
3955
3956 // TODO Change the items merge methods so the logic is simplified
3957 // check permissions
3958 if (!item.hasPermission(UserAppliedPermission.full) && item.getParent().getNameItem() != item) {
3959 MessageBay.displayMessage("Insufficient permission to merge items");
3960 return;
3961 }
3962 if (item instanceof Text || item instanceof Dot || item instanceof XRayable) {
3963 if (isRubberBandingCorner()) {
3964 copies = mergeGroupAction(item);
3965 // line onto something
3966 } else if (FreeItems.getInstance().size() == 2 /* && clicked instanceof XRayable */) {
3967 copies = mergeTwoItemsAction(item);
3968 } else if (FreeItems.getInstance().size() == 1) {
3969 copies = mergeSingleItemAction(item);
3970 } else {
3971 copies = stampAction();
3972 }
3973 } else {
3974 copies = ItemUtils.UnreelLine(FreeItems.getInstance(), false);
3975 if (copies == null) {
3976 copies = copy(FreeItems.getInstance());
3977 }
3978 for (Item i : copies) {
3979 i.setOffset(0, 0);
3980 }
3981 anchor(FreeItems.getInstance());
3982 FreeItems.getInstance().clear();
3983 pickup(copies);
3984 }
3985 // otherwise, anchor the items
3986 } else {
3987 // check if this is anchoring a rectangle
3988 if (isRubberBandingCorner()) {
3989 copies = rubberBandingCornerAction();
3990 } else {
3991 if (FreeItems.rubberBanding()) {
3992 copies = rubberBandingCopyAction(item);
3993 } else {
3994 stampItemsOnCursor(true);
3995 copies = FreeItems.getInstance();
3996 }
3997 }
3998 }
3999 }
4000
4001 /**
4002 * Returns a list of copies of the list passed in
4003 *
4004 * @param toCopy
4005 * The list of items to copy
4006 * @return A List of copied Items
4007 */
4008 private static List<Item> copy(Collection<Item> toCopy) {
4009 return ItemUtils.CopyItems(toCopy);
4010 }
4011
4012 public List<Item> mergeGroupAction(Item item) {
4013 List<Item> copies = new ArrayList<Item>();
4014 // Move the cursor so that the copy is exactly the
4015 // same as the shape that was anchored
4016 DisplayController.setCursorPosition(item.getPosition());
4017 Item d = getFirstFreeLineEnd();
4018 // get a copy of all enclosed items before merging
4019 // lineEnds
4020 Collection<Item> items = FrameUtils.getItemsEnclosedBy(DisplayController.getCurrentFrame(),
4021 d.getEnclosedShape());
4022 // If its not an enclosed shape then pick up the
4023 // connected shape
4024 if (items == null || items.size() == 0) {
4025 items = d.getAllConnected();
4026 } else {
4027 // For some reason the item that was clicked ends up
4028 // in the enclosure and needs to be removed
4029 items.removeAll(item.getConnected());
4030 // the item that was the origin of the enclosed
4031 // shape used to create the enclosure does not get
4032 // returned from getItemsEnclosedBy to the enclosure
4033 // so it must be added
4034 items.addAll(d.getConnected());
4035 }
4036
4037 Collection<Item> toCopy = new LinkedHashSet<Item>();
4038
4039 for (Item ip : items) {
4040 if (ip.hasPermission(UserAppliedPermission.copy)) {
4041 toCopy.add(ip);
4042 }
4043 }
4044 copies = copy(toCopy);
4045 // Now do the merging
4046 Collection<Item> remain = merge(FreeItems.getInstance(), item);
4047 // anchor the points
4048 anchor(remain);
4049 FreeItems.getInstance().clear();
4050 pickup(copies);
4051 return copies;
4052 }
4053
4054 private static Item getFirstFreeLineEnd() {
4055 for (Item i : FreeItems.getInstance()) {
4056 if (i.isLineEnd()) {
4057 return i;
4058 }
4059 }
4060 return null;
4061 }
4062
4063 public List<Item> mergeTwoItemsAction(Item item) {
4064 List<Item> copies = new ArrayList<Item>();
4065 copies = ItemUtils.UnreelLine(FreeItems.getInstance(), false);
4066 Collection<Item> leftOver = merge(FreeItems.getInstance(), item);
4067 anchor(leftOver);
4068 if (copies == null) {
4069 copies = copy(FreeItems.getInstance());
4070 }
4071 FreeItems.getInstance().clear();
4072 for (Item i : copies) {
4073 i.setOffset(0, 0);
4074 }
4075 // need to move to prevent cursor dislocation
4076 move(copies, EcosystemManager.getInputManager().getCursorPosition());
4077 pickup(copies);
4078 // point onto point
4079 return copies;
4080 }
4081
4082 public List<Item> mergeSingleItemAction(Item item) {
4083 List<Item> copies = new ArrayList<Item>();
4084 copies = copy(FreeItems.getInstance());
4085 Collection<Item> remain = merge(copies, item);
4086
4087 // ignore items that could not be merged.
4088 anchor(remain);
4089 return copies;
4090 }
4091
4092 public List<Item> stampAction() {
4093 List<Item> copies = new ArrayList<Item>();
4094 stampItemsOnCursor(true);
4095 copies = FreeItems.getInstance();
4096 return copies;
4097 }
4098
4099 /**
4100 *
4101 */
4102 private static void stampItemsOnCursor(boolean save) {
4103 List<Item> copies = copy(FreeItems.getInstance());
4104 // MIKE: what does the below 2 lines do?
4105 for (Item i : copies) {
4106 i.setOffset(0, 0);
4107 i.setSave(save);
4108 }
4109 // The below code has a little problem withflicker when stamp and dragging
4110 move(FreeItems.getInstance(), EcosystemManager.getInputManager().getCursorPosition());
4111 for (Item i : copies) {
4112 i.setHighlightMode(HighlightMode.None);
4113 }
4114 anchor(copies);
4115 }
4116
4117 public List<Item> rubberBandingCornerAction() {
4118 List<Item> copies = new ArrayList<Item>();
4119 Item d = getFirstFreeLineEnd();
4120 // anchor the points
4121 anchor(FreeItems.getInstance());
4122 FreeItems.getInstance().clear();
4123 updateCursor();
4124 // pick up a copy of all enclosed items
4125 Collection<Item> enclosedItems = FrameUtils.getItemsEnclosedBy(DisplayController.getCurrentFrame(),
4126 d.getEnclosedShape());
4127 if (enclosedItems != null) {
4128 enclosedItems.removeAll(d.getAllConnected());
4129 Collection<Item> toCopy = getFullyEnclosedItems(enclosedItems);
4130
4131 if (toCopy.size() > 0) {
4132 // Find the closest item to the mouse cursor
4133 double currentX = DisplayController.getMouseX();
4134 double currentY = DisplayController.getMouseY();
4135 Item closest = null;
4136 double shortestDistance = Double.MAX_VALUE;
4137 for (Item next : toCopy) {
4138 if (next instanceof Line) {
4139 continue;
4140 }
4141 double distance = Point.distanceBetween((int) currentX, (int) currentY, next.getX(), next.getY());
4142 if (distance < shortestDistance) {
4143 shortestDistance = distance;
4144 closest = next;
4145 }
4146 }
4147 // Move the cursor to closest item
4148 DisplayController.setCursorPosition(closest.getPosition());
4149 // Pickup copy of the stuff inside the rectangle
4150 copies = copy(toCopy);
4151 pickup(copies);
4152 // Remove the rectangle
4153 d.getParentOrCurrentFrame().removeAllItems(d.getAllConnected());
4154 } else {
4155 // Pick up a copy of the rectangle
4156 copies = copy(d.getAllConnected());
4157 pickup(copies);
4158 }
4159 }
4160 return copies;
4161 }
4162
4163 /**
4164 * @param enclosedItems
4165 * @return
4166 */
4167 private static Collection<Item> getFullyEnclosedItems(Collection<Item> enclosure) {
4168 // copy the enclosedItems because the list will be modified
4169 Collection<Item> enclosedItems = new LinkedHashSet<Item>(enclosure);
4170 Collection<Item> toCopy = new LinkedHashSet<Item>(enclosedItems.size());
4171
4172 while (enclosedItems.size() > 0) {
4173 Item i = enclosedItems.iterator().next();
4174 if (i.hasPermission(UserAppliedPermission.copy)) {
4175 Collection<Item> items = i.getAllConnected();
4176 // Only copy if the entire shape is enclosed
4177 if (enclosedItems.containsAll(items)) {
4178 toCopy.addAll(items);
4179 }
4180 enclosedItems.removeAll(items);
4181 } else {
4182 enclosedItems.remove(i);
4183 }
4184 }
4185 return toCopy;
4186 }
4187
4188 public List<Item> rubberBandingCopyAction(Item item) {
4189 List<Item> copies = new ArrayList<Item>();
4190 if (item != null) {
4191 Collection<Item> leftOver = merge(FreeItems.getInstance(), item);
4192 anchor(leftOver);
4193 }
4194 // This is executed when the user is putting down a line
4195 // endpoint and unreeling. ie. Normal unreeling
4196 copies = ItemUtils.UnreelLine(FreeItems.getInstance(), false);
4197
4198 if (copies == null) {
4199 copies = copy(FreeItems.getInstance());
4200 }
4201
4202 anchor(FreeItems.getInstance());
4203 for (Item i : copies) {
4204 i.setOffset(0, 0);
4205 }
4206 // need to move to prevent cursor dislocation
4207 move(copies, EcosystemManager.getInputManager().getCursorPosition());
4208 pickup(copies);
4209 return copies;
4210 }
4211
4212 public List<Item> newLineAction(Point position, Item item) {
4213 List<Item> copies = new ArrayList<Item>();
4214 // If we have permission to copy this item then pick it up
4215 if (item != null && item.isLineEnd() && item.hasPermission(UserAppliedPermission.full)) {
4216 item.removeAllConstraints();
4217 pickup(item);
4218 return copies;
4219 }
4220
4221 if (item instanceof WidgetEdge) {
4222 // Don't allow the user to break widget edges.
4223 // Note: had to return here because random dots would
4224 // appear otherwise... cannot understand code below
4225 // with create line.
4226 return copies;
4227 }
4228
4229 // if its on a line then split the line and put a point on it and
4230 // pick that point up. Only if it is not a widget line
4231 if (item instanceof Line && item.hasPermission(UserAppliedPermission.full)) {
4232 Frame current = DisplayController.getCurrentFrame();
4233 // create the two endpoints
4234 Line oldLine = (Line) item;
4235 Item newPoint = oldLine.getStartItem().copy();
4236 newPoint.setPosition(position);
4237
4238 Item end = oldLine.getEndItem();
4239 // create the Line
4240 Line newLine = new Line(newPoint, end, current.getNextItemID());
4241 oldLine.replaceLineEnd(end, newPoint);
4242 newPoint.removeAllConstraints();
4243 pickup(newPoint);
4244 // Update the stats
4245 Collection<Item> created = new LinkedList<Item>();
4246 created.add(newPoint);
4247 created.add(newLine);
4248 SessionStats.CreatedItems(newLine.getAllConnected());
4249 return copies;
4250 }
4251 Line newLine = createLine();
4252 SessionStats.CreatedItems(newLine.getAllConnected());
4253 return copies;
4254 }
4255
4256 private static Line createLine() {
4257 Frame current = DisplayController.getCurrentFrame();
4258 // create the two endpoints
4259 Item end = DisplayController.getCurrentFrame().createDot();
4260 Item start = DisplayController.getCurrentFrame().createDot();
4261
4262 // create the Line
4263 Line line = new Line(start, end, current.getNextItemID());
4264 line.autoArrowheadLength();
4265
4266 // anchor the start
4267 anchor(start);
4268
4269 // attach the line to the cursor
4270 pickup(end);
4271 _lastHighlightedItem = null;
4272
4273 // TODO figure out how to get the end to highlight
4274 // end.setSelectedMode(SelectedMode.Normal);
4275 // end.setSelectedMode(SelectedMode.None);
4276
4277 return line;
4278 }
4279
4280 public List<Item> createRectangleAction() {
4281 final List<Item> copies = new ArrayList<Item>();
4282 Item[] d = new Item[4];
4283 // create dots
4284 Frame current = DisplayController.getCurrentFrame();
4285 for (int i = 0; i < d.length; i++) {
4286 d[i] = current.createDot();
4287 copies.add(d[i]);
4288 }
4289
4290 current.nextDot();
4291
4292 // create lines
4293 copies.add(new Line(d[0], d[1], current.getNextItemID()));
4294 copies.add(new Line(d[1], d[2], current.getNextItemID()));
4295 copies.add(new Line(d[2], d[3], current.getNextItemID()));
4296 copies.add(new Line(d[3], d[0], current.getNextItemID()));
4297
4298 new Constraint(d[0], d[1], current.getNextItemID(), Constraint.HORIZONTAL);
4299 new Constraint(d[2], d[3], current.getNextItemID(), Constraint.HORIZONTAL);
4300 new Constraint(d[1], d[2], current.getNextItemID(), Constraint.VERTICAL);
4301 new Constraint(d[3], d[0], current.getNextItemID(), Constraint.VERTICAL);
4302
4303 anchor(new ArrayList<Item>(copies));
4304 pickup(d[3]);
4305 d[3].setHighlightMode(HighlightMode.Normal);
4306
4307 SessionStats.CreatedItems(copies);
4308 copies.clear();
4309 return copies;
4310 }
4311
4312 /**
4313 * This method handles all left-click actions
4314 */
4315 private void click(Item clicked, Collection<Item> clickedIn, Point position) {
4316
4317 // Gets the current frame
4318 Frame f = DisplayController.getCurrentFrame();
4319
4320 // Checks if the current frame is an overlay
4321 if (f.getOverlays() != null && FrameUtils.getCurrentItem() != null) {
4322 Item i = FrameUtils.getCurrentItem();
4323
4324 // Checks if the item clicked in the overlay is a Rubbish Bin. If it is, delete
4325 // the item attached to the cursor and return.
4326 if (i instanceof WidgetCorner) {
4327
4328 try {
4329 WidgetCorner wc = (WidgetCorner) i;
4330 ButtonWidget bw = (ButtonWidget) wc.getWidgetSource();
4331
4332 // Should call a button widgets 'itemheldwhileclicked' method, and process
4333 // depending on the widget type - else will return false.
4334 if (bw.itemHeldWhileClicked(bw) == true) {
4335
4336 return;
4337 }
4338 } catch (Exception e) {
4339
4340 e.printStackTrace();
4341 }
4342 }
4343 }
4344
4345 // if the user is pointing at something then either follow the link or
4346 // do TDFC
4347 if (clicked == null) {
4348 // Check if the user is nearby another item...
4349 int mouseX = position.getX();
4350 int mouseY = position.getY();
4351 // System.out.println(mouseX + "," + mouseY);
4352 for (Item i : DisplayController.getCurrentFrame().getItems()) {
4353 // System.out.println(i.getName().toString());
4354 if (i instanceof Text) {
4355 if (i.isNear(mouseX, mouseY)) {
4356 clicked = i;
4357 break;
4358 }
4359 }
4360 }
4361 }
4362
4363 if (clicked instanceof Text) {
4364 Text text = (Text) clicked;
4365 /* Don't follow link when just highlighting text with the left button */
4366 if (text.getText().length() == 0) {
4367 clicked = null;
4368 } else if (text.getSelectionSize() > 0) {
4369 return;
4370 }
4371 }
4372
4373 // If the user clicked into a widgets free space...
4374 if (clicked == null && clickedIn != null && clickedIn.size() >= 4) {
4375
4376 // Check to see if the user clicked into a widgets empty space
4377 Widget iw = null;
4378
4379 for (Item i : clickedIn) {
4380
4381 if (i instanceof WidgetCorner) {
4382 iw = ((WidgetCorner) i).getWidgetSource();
4383 break;
4384 } else if (i instanceof WidgetEdge) {
4385 iw = ((WidgetEdge) i).getWidgetSource();
4386 break;
4387 }
4388 }
4389
4390 if (iw != null) {
4391
4392 // Handle dropping items on widgets
4393 if (iw.ItemsLeftClickDropped()) {
4394 return;
4395 }
4396
4397 // Note: mustn't directly use source for handling the link
4398 // because all link operations will by-pass the widgets special
4399 // handling with links...
4400 Item widgetLink = iw.getItems().get(0);
4401 assert (widgetLink != null);
4402 clicked = widgetLink;
4403 } else {
4404 for (Item i : clickedIn) {
4405 /*
4406 * Find the first linked item or the first unlinked Dot This code assumes that
4407 * items are are ordered from top to bottom. TODO make sure the list will always
4408 * be ordered correctly!!
4409 */
4410 if (i.hasLink() || i instanceof Dot) {
4411 clicked = i;
4412 break;
4413 }
4414 }
4415 }
4416
4417 }
4418
4419 if (clicked instanceof Picture) {
4420 int mouseX = position.getX();
4421 int mouseY = position.getY();
4422 Picture clickedOnPicture = (Picture) clicked;
4423 Frame current_frame = DisplayController.getCurrentFrame();
4424 Colour bg_col = current_frame.getBackgroundColor();
4425 if (clickedOnPicture.MouseOverBackgroundPixel(mouseX, mouseY, bg_col)) {
4426 // Make 'clicked' null, effectively causing a back() operation
4427 clicked = null;
4428 }
4429 }
4430
4431 // This makes it so clicking repeatedly on the frameName doesn't add the
4432 // frames to the backup stack. Only the first frame is added to the
4433 // backup stack.
4434 if (!(clicked != null && clicked.isFrameName())) {
4435 Navigation.ResetLastAddToBack();
4436 }
4437
4438 if (clicked != null) {
4439 // check item permissions
4440 boolean hasLinkOrAction = clicked.hasLink() || clicked.hasAction();
4441
4442 if ((hasLinkOrAction && !clicked.hasPermission(UserAppliedPermission.followLinks))
4443 || (!hasLinkOrAction && !clicked.hasPermission(UserAppliedPermission.createFrames))) {
4444 Item editTarget = clicked.getEditTarget();
4445 if (editTarget != clicked) {
4446 if (editTarget.hasPermission(UserAppliedPermission.followLinks)) {
4447 clicked = editTarget;
4448 } else {
4449 MessageBay.displayMessage("Insufficient permission to perform action on item");
4450 return;
4451 }
4452 }
4453 }
4454
4455 Item clickedOn = clicked;
4456
4457 // actions take priority
4458 if (clickedOn.hasAction()) {
4459 executeActionAction(clicked);
4460 } else if (clickedOn.getLink() != null) {
4461 followLinkAction(clicked);
4462 // no link is found, perform TDFC
4463 } else {
4464 /*
4465 * if the user is clicking on the frame name then move to the next or previous
4466 * frame regardless of whether or not the frame is protected
4467 */
4468 if (clickedOn.isFrameName()) {
4469 respondToFrameNameClickAction();
4470 }
4471
4472 TDFCAction(clicked);
4473 }
4474
4475 } else {
4476 backAction();
4477 }
4478 }
4479
4480 public void executeActionAction(Item item) {
4481 item.performActions();
4482 item.setHighlightMode(HighlightMode.None);
4483 refreshHighlights();
4484 }
4485
4486 public void followLinkAction(Item item) {
4487 /*
4488 * Dont save the frame if we are moving to an old version of this frame because
4489 * everytime we save with the old tag... the frame is backed up
4490 */
4491 if (!item.isOldTag()) {
4492 FrameIO.SaveFrame(DisplayController.getCurrentFrame());
4493 }
4494
4495 Navigation.setLastNavigationItem(item);
4496 load(item.getAbsoluteLink(), item.getLinkHistory());
4497 // DisplayIO.UpdateTitle();
4498 }
4499
4500 private static void load(String toLoad, boolean addToHistory) {
4501 if (FrameIO.isValidFrameName(toLoad)) {
4502 DisplayController.clearBackedUpFrames();
4503 FrameUtils.DisplayFrame(toLoad, addToHistory, true);
4504 } else {
4505 MessageBay.errorMessage(toLoad + " is not a valid frame name.");
4506 }
4507 }
4508
4509 public void respondToFrameNameClickAction() {
4510 if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL)) {
4511 Navigation.PreviousFrame(false);
4512 } else {
4513 Navigation.NextFrame(false);
4514 }
4515 }
4516
4517 public List<Item> TDFCAction(Item item) {
4518 // check for TDFC permission
4519 if (!item.hasPermission(UserAppliedPermission.createFrames)) {
4520 MessageBay.displayMessage("Insufficient permission to TDFC (Top Down Frame Creation) from that item");
4521 return null;
4522 }
4523
4524 if (item.isOldTag()) {
4525 return null;
4526 }
4527
4528 try {
4529 tdfc(item);
4530 } catch (RuntimeException e) {
4531 e.printStackTrace();
4532 MessageBay.errorMessage("Top Down Frame Creation (TDFC) error: " + e.getMessage());
4533 }
4534 return null;
4535 }
4536
4537 public void backAction() {
4538 // if user is not pointing at something, this is a back
4539 if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL)
4540 || StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) {
4541 forward();
4542 } else {
4543 back();
4544 }
4545 }
4546
4547 private static void forward() {
4548 DisplayController.Forward();
4549
4550 // repaint things if necessary
4551 if (FreeItems.hasItemsAttachedToCursor()) {
4552 move(FreeItems.getInstance(), EcosystemManager.getInputManager().getCursorPosition());
4553 }
4554
4555 if (FreeItems.hasCursor()) {
4556 move(FreeItems.getCursor(), EcosystemManager.getInputManager().getCursorPosition(), true);
4557 }
4558 }
4559
4560 private static void back() {
4561 DisplayController.Back();
4562
4563 // repaint things if necessary
4564 if (FreeItems.hasItemsAttachedToCursor()) {
4565 move(FreeItems.getInstance(), EcosystemManager.getInputManager().getCursorPosition());
4566 }
4567
4568 if (FreeItems.hasCursor()) {
4569 move(FreeItems.getCursor(), EcosystemManager.getInputManager().getCursorPosition(), true);
4570 }
4571 }
4572
4573 /**
4574 * Creates a new Text item and fills it with particular attributes extracted
4575 * from the given Item. Note: Users always have permission to extract
4576 * attributes, so it is not checked.
4577 *
4578 * @param toExtract
4579 * Item containing the Item to extract the attributes from.
4580 */
4581 private static void extractAttributes(Item toExtract) {
4582 if (toExtract == null || toExtract == null) {
4583 return;
4584 }
4585
4586 if (FreeItems.hasItemsAttachedToCursor()) {
4587 return;
4588 }
4589
4590 Item attribs;
4591 Item item = toExtract;
4592 // Extract the frames attributes when the user clicks on the frame name
4593 FrameGraphics.changeHighlightMode(item, HighlightMode.None);
4594 if (item.isFrameName()) {
4595 attribs = AttributeUtils.extractAttributes(item.getParent());
4596 } else {
4597 attribs = AttributeUtils.extractAttributes(item);
4598 }
4599
4600 if (attribs == null) {
4601 MessageBay.displayMessage("All attributes of that item are default values.");
4602 } else {
4603 // Give the attribute text item the color of the item for which
4604 // attributes are being extracted.
4605 // attribs.setColor(item.getColor());
4606 pickup(attribs);
4607 }
4608 }
4609
4610 private void pickupRange(Text text, Point position, boolean copy, boolean inheritAttributes) {
4611 Text ranged;
4612 if (inheritAttributes) {
4613 // If shift is down, copy everything (size, color, etc.) except actions, links
4614 // and data
4615 ranged = text.copy();
4616 ranged.setActions(null);
4617 ranged.setData((List<String>) null);
4618 ranged.setLink(null);
4619 } else {
4620 // If shift isn't down, don't copy any attributes, but base the new text item on
4621 // the appropriate template
4622 final String copySelectedText = text.copySelectedText();
4623 if (copySelectedText.length() < 1) {
4624 return;
4625 }
4626 ranged = DisplayController.getCurrentFrame().getItemTemplate(copySelectedText.charAt(0));
4627 }
4628
4629 // if the user is cutting text from the item
4630 if (!copy) {
4631 // Check if the user is trying to range an item for which they
4632 // do not have permission to do so... or it is the frame name
4633 if (!text.hasPermission(UserAppliedPermission.full) || text.isFrameName()) {
4634 MessageBay.displayMessage("Insufficient permission to cut text");
4635 text.clearSelection();
4636 DisplayController.requestRefresh(true);
4637 return;
4638 }
4639 // if the entire text is selected and its not a line end then pickup the item
4640 boolean entireText = text.getSelectionSize() == text.getLength();
4641 if (entireText && !text.isLineEnd()) {
4642 text.clearSelection();
4643 ranged.delete();
4644 handlePickup(text, position, false, false);
4645 return;
4646 } else {
4647 ranged.setText(text.cutSelectedText());
4648 ranged.setWidth(text.getWidth());
4649 // If its the whole text then replace last ranged with a dot
4650 if (entireText) {
4651 Item dot = Item.replaceText(text);
4652 dot.setHighlightMode(HighlightMode.None);
4653 }
4654 }
4655 // if the user is copying text from the item
4656 } else {
4657 // Check if the user is trying to range an item for which they
4658 // do not have permission to do so... or it is the frame name
4659 if (!text.hasPermission(UserAppliedPermission.copy)) {
4660 MessageBay.displayMessage("Insufficient permission to copy text");
4661 text.clearSelection();
4662 DisplayController.requestRefresh(true);
4663 return;
4664 }
4665 ranged.setText(text.copySelectedText());
4666 }
4667
4668 ranged.setParent(null);
4669 ranged.setPosition(position);
4670 pickup(ranged);
4671 text.clearSelection();
4672 text.setHighlightMode(HighlightMode.None);
4673 refreshHighlights();
4674 DisplayController.requestRefresh(false);
4675 return;
4676 }
4677}
Note: See TracBrowser for help on using the repository browser.