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

Last change on this file since 1097 was 1097, checked in by davidb, 6 years ago

Newly structured files from Corey's work on logic/graphics separation

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