source: trunk/src/org/expeditee/gui/FrameKeyboardActions.java@ 306

Last change on this file since 306 was 306, checked in by ra33, 16 years ago

Added some HELP actions
More mail stuff... and networking stuff
Can now save frames and send messages to peers
Also version control prevent versions from being lost

File size: 59.3 KB
Line 
1package org.expeditee.gui;
2
3import java.awt.Color;
4import java.awt.Polygon;
5import java.awt.Rectangle;
6import java.awt.Toolkit;
7import java.awt.datatransfer.DataFlavor;
8import java.awt.datatransfer.StringSelection;
9import java.awt.event.KeyEvent;
10import java.awt.event.KeyListener;
11import java.awt.geom.Point2D;
12import java.text.NumberFormat;
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.HashSet;
17import java.util.LinkedList;
18import java.util.List;
19import java.util.StringTokenizer;
20
21import org.expeditee.actions.Actions;
22import org.expeditee.actions.Misc;
23import org.expeditee.actions.Navigation;
24import org.expeditee.actions.Simple;
25import org.expeditee.io.Logger;
26import org.expeditee.items.Circle;
27import org.expeditee.items.Dot;
28import org.expeditee.items.Item;
29import org.expeditee.items.ItemUtils;
30import org.expeditee.items.Line;
31import org.expeditee.items.Permission;
32import org.expeditee.items.StringUtils;
33import org.expeditee.items.Text;
34import org.expeditee.items.XRayable;
35import org.expeditee.items.widgets.WidgetCorner;
36import org.expeditee.stats.SessionStats;
37
38public class FrameKeyboardActions implements KeyListener {
39
40 private static Text _toRemove = null;
41
42 private static Collection<Item> _enclosedItems = null;
43
44 public static void resetEnclosedItems() {
45 _enclosedItems = null;
46 }
47
48 public synchronized void keyTyped(KeyEvent e) {
49 if (Simple.isProgramRunning()) {
50 if (e.isControlDown()
51 && (e.getKeyChar() == KeyEvent.VK_ESCAPE || e.getKeyChar() == KeyEvent.VK_C)) {
52 Simple.stop();
53 return;
54 } else if (e.isControlDown() && e.getKeyChar() == KeyEvent.VK_SPACE) {
55 Simple.nextStatement();
56 return;
57 } else {
58 Simple.KeyStroke(e.getKeyChar());
59 }
60 if (Simple.consumeKeyboardInput())
61 return;
62 }
63
64 // ignore escape character and control characters
65 if (e.getKeyChar() == KeyEvent.VK_ESCAPE || e.isControlDown()) {
66 return;
67 }
68
69 // Deal with splitting text items when typing too fast
70 // Mike: thinks this problem may have been solved and was due to
71 // rounding errors in the text class...
72 // It may have been fixed by changing to the use of floats instead of
73 // ints for text positioning etc
74 // if (FrameMouseActions.isWaitingForRobot()) {
75 // System.out.println("Waiting: " + e.getKeyChar());
76 // return;
77 // }
78 e.consume();
79 char ch = e.getKeyChar();
80 // System.out.println(ch);
81
82 if (e.isAltDown()) {
83
84 } else {
85 processChar(ch, e.isShiftDown());
86 }
87 // FrameGraphics.Repaint();
88 }
89
90 public static void processChar(char ch, boolean isShiftDown) {
91 Navigation.ResetLastAddToBack();
92 Item on = FrameUtils.getCurrentItem();
93
94 // permission check
95 if (on != null && !on.hasPermission(Permission.full)) {
96 MessageBay
97 .displayMessage("Insufficient permission to edit this item");
98 return;
99 }
100
101 if (_toRemove != null && on != _toRemove) {
102 assert (_toRemove.getLength() == 0);
103 // This line is to protect mistaken removal of items if there is a
104 // bug...
105 if (_toRemove.getLength() == 0)
106 DisplayIO.getCurrentFrame().removeItem(_toRemove);
107 }
108 _toRemove = null;
109
110 // ignore delete and backspace if in free space
111 if ((on == null || !(on instanceof Text))
112 && (ch == KeyEvent.VK_BACK_SPACE || ch == KeyEvent.VK_TAB || ch == KeyEvent.VK_DELETE))
113 return;
114
115 SessionStats.TypedChar(ch);
116
117 // check for dot's being replaced with text
118 if (on != null && on instanceof Dot && !(on instanceof WidgetCorner)) {
119 if (ch == KeyEvent.VK_BACK_SPACE || ch == KeyEvent.VK_DELETE) {
120 return;
121 }
122 replaceDot((Item) on, ch);
123 return;
124 }
125
126 // only text can interact with keyboard events
127 if (on != null && !(on instanceof Text))
128 on = null;
129
130 // DisplayIO.UpdateTitle();
131
132 Text text = (Text) on;
133 // if this text is empty but has not been removed (such as from
134 // ESC-pushdown)
135 if (text != null && text.isEmpty()
136 && (ch == KeyEvent.VK_BACK_SPACE || ch == KeyEvent.VK_DELETE)) {
137 if (text.getLines().size() > 0)
138 replaceText(text);
139 else {
140 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
141 }
142 return;
143 }
144
145 FrameUtils.setLastEdited(text);
146
147 // if the user is in free space, create a new text item
148 if (on == null || !on.isHighlighted()) {
149 // DisplayIO.UpdateTitle();
150 text = createText(ch);
151 justify(text);
152
153 FrameUtils.setLastEdited(text);
154 DisplayIO.setTextCursor(text, Text.NONE);
155 return;
156 }
157
158 DisplayIO.setTextCursor(text, Text.NONE);
159 Point2D.Float newMouse = null;
160 if (ch == '\t') {
161 if (isShiftDown) {
162 newMouse = text.removeTab(ch, DisplayIO.getFloatMouseX(),
163 FrameMouseActions.MouseY);
164 } else {
165 newMouse = text.insertTab(ch, DisplayIO.getFloatMouseX(),
166 FrameMouseActions.MouseY);
167 }
168 } else {
169 newMouse = text.insertChar(ch, DisplayIO.getFloatMouseX(),
170 FrameMouseActions.MouseY);
171 }
172 DisplayIO.setCursorPosition(newMouse.x, newMouse.y, false);
173
174 // This repaint is needed for WINDOWS only?!?!? Mike is not sure why!
175 if (ch == KeyEvent.VK_DELETE)
176 FrameGraphics.requestRefresh(true);
177
178 // a change has occured to the Frame
179 text.getParent().setChanged(true);
180
181 // check that the Text item still exists (hasn't been deleted\backspaced
182 // away)
183 if (text.isEmpty()) {
184 _toRemove = text;
185
186 if (text.hasAction())
187 text.setActionMark(true);
188 else if (text.getLink() != null)
189 text.setLinkMark(true);
190 else if (text.getLines().size() > 0)
191 replaceText(text);
192 else {
193 // DisplayIO.getCurrentFrame().removeItem(text);
194 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
195 }
196 }
197 }
198
199 /**
200 * @param text
201 */
202 private static void justify(Text text) {
203 // Check if that text item is inside an enclosing rectangle...
204 // Set its max width accordingly
205 Polygon enclosure = FrameUtils.getEnlosingPolygon();
206 if (enclosure != null) {
207 Rectangle bounds = enclosure.getBounds();
208 text.setRightMargin(bounds.x + bounds.width);
209 } else {
210 text.setRightMargin(FrameGraphics.getMaxFrameSize().width);
211 }
212 }
213
214 public static Text replaceDot(Item dot, char ch) {
215 Text text = createText(ch);
216 Item.DuplicateItem(dot, text);
217 FrameUtils.setLastEdited(text);
218
219 // Copy the lines list so it can be modified
220 List<Line> lines = new LinkedList<Line>(dot.getLines());
221 for (Line line : lines)
222 line.replaceLineEnd(dot, text);
223 Frame current = dot.getParentOrCurrentFrame();
224 current.removeItem(dot);
225 ItemUtils.EnclosedCheck(current.getItems());
226 return text;
227 }
228
229 /**
230 * Replaces the given text item with a dot
231 */
232 public static Item replaceText(Item text) {
233 Item dot = new Dot(text.getX(), text.getY(), text.getID());
234 Item.DuplicateItem(text, dot);
235
236 List<Line> lines = new LinkedList<Line>();
237 lines.addAll(text.getLines());
238 if (lines.size() > 0)
239 dot.setColor(lines.get(0).getColor());
240 for (Line line : lines) {
241 line.replaceLineEnd(text, dot);
242 }
243 text.delete();
244 Frame current = text.getParentOrCurrentFrame();
245 current.addItem(dot);
246 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
247 ItemUtils.EnclosedCheck(current.getItems());
248 return dot;
249 }
250
251 /**
252 * Creates a new Text Item whose text contains the given character. This
253 * method also moves the mouse cursor to be pointing at the newly created
254 * Text Item ready to insert the next character.
255 *
256 * @param start
257 * The character to use as the initial text of this Item.
258 * @return The newly created Text Item
259 */
260 private static Text createText(char start) {
261 Text t = DisplayIO.getCurrentFrame().createBlankText("" + start);
262
263 Point2D.Float newMouse = t.insertChar(start, DisplayIO.getMouseX(),
264 FrameMouseActions.getY());
265 DisplayIO.setCursorPosition(newMouse.x, newMouse.y, false);
266
267 return t;
268 }
269
270 /**
271 * Creates a new Text Item with no text. The newly created Item is a copy of
272 * any ItemTemplate if one is present, and inherits all the attributes of
273 * the Template
274 *
275 * @return The newly created Text Item
276 */
277 private static Text createText() {
278 return DisplayIO.getCurrentFrame().createNewText();
279 }
280
281 private void move(int direction, boolean isShiftDown) {
282 Item on = FrameUtils.getCurrentItem();
283
284 if (on == null) {
285 navigateFrame(direction);
286 return;
287 }
288
289 if (on instanceof Text) {
290 Text text = (Text) on;
291 // When the user hits the left and right button with mouse
292 // positions over the the frame name navigation occurs
293 if (text.isFrameName()) {
294 navigateFrame(direction);
295 return;
296 } else {
297 FrameUtils.setLastEdited(text);
298 if (!isShiftDown)
299 text.clearSelection();
300 DisplayIO.setTextCursor(text, direction, false, isShiftDown);
301 }
302 }
303 }
304
305 private void navigateFrame(int direction) {
306 switch (direction) {
307 case Text.RIGHT:
308 case Text.PAGE_UP:
309 Navigation.NextFrame(false);
310 break;
311 case Text.LEFT:
312 case Text.PAGE_DOWN:
313 Navigation.PreviousFrame(false);
314 break;
315 case Text.HOME:
316 case Text.LINE_HOME:
317 Navigation.ZeroFrame();
318 break;
319 case Text.END:
320 case Text.LINE_END:
321 Navigation.LastFrame();
322 break;
323 }
324 }
325
326 /**
327 * Receives and processes any Function, Control, and Escape key presses
328 *
329 * @param e
330 * The KeyEvent received from the keyboard
331 */
332 public void keyPressed(KeyEvent e) {
333 int keyCode = e.getKeyCode();
334
335 if (keyCode != KeyEvent.VK_F1 && keyCode != KeyEvent.VK_F2) {
336 resetEnclosedItems();
337 }
338
339 SessionStats.AddFrameEvent("k" + KeyEvent.getKeyText(keyCode));
340
341 FrameUtils.ResponseTimer.restart();
342 // e.consume();
343
344 if (Actions.isAgentRunning()) {
345 if (keyCode == KeyEvent.VK_ESCAPE)
346 Actions.stopAgent();
347 else
348 Actions.interruptAgent();
349 return;
350 } else if (Simple.consumeKeyboardInput()) {
351 return;
352 }
353
354 if (keyCode >= KeyEvent.VK_F1 && keyCode <= KeyEvent.VK_F12) {
355 functionKey(FunctionKey.values()[keyCode - KeyEvent.VK_F1 + 1], e
356 .isShiftDown(), e.isControlDown());
357 return;
358 } else if (e.isAltDown()) {
359 int distance = e.isShiftDown() ? 1 : 20;
360 switch (keyCode) {
361 case KeyEvent.VK_1:
362 FrameMouseActions.leftButton();
363 // DisplayIO.clickMouse(InputEvent.BUTTON1_MASK);
364 break;
365 case KeyEvent.VK_2:
366 // DisplayIO.clickMouse(InputEvent.BUTTON2_MASK);
367 FrameMouseActions.middleButton();
368 break;
369 case KeyEvent.VK_3:
370 // DisplayIO.clickMouse(InputEvent.BUTTON3_MASK);
371 FrameMouseActions.rightButton();
372 break;
373 case KeyEvent.VK_LEFT:
374 DisplayIO.translateCursor(-distance, 0);
375 break;
376 case KeyEvent.VK_RIGHT:
377 DisplayIO.translateCursor(distance, 0);
378 break;
379 case KeyEvent.VK_UP:
380 DisplayIO.translateCursor(0, -distance);
381 break;
382 case KeyEvent.VK_DOWN:
383 DisplayIO.translateCursor(0, distance);
384 break;
385 }
386 return;
387 } else if (e.isControlDown()) {
388 if (keyCode == KeyEvent.VK_CONTROL) {
389 FrameMouseActions.control(e);
390 } else {
391 controlChar(e.getKeyCode(), e.isShiftDown());
392 }
393 return;
394 }
395
396 switch (keyCode) {
397 case KeyEvent.VK_ESCAPE:
398 // Do escape after control so Ctl+Escape does not perform DropDown
399 functionKey(FunctionKey.DropDown, e.isShiftDown(), e
400 .isControlDown());
401 SessionStats.Escape();
402 break;
403 case KeyEvent.VK_LEFT:
404 move(Text.LEFT, e.isShiftDown());
405 break;
406 case KeyEvent.VK_RIGHT:
407 move(Text.RIGHT, e.isShiftDown());
408 break;
409 case KeyEvent.VK_PAGE_DOWN:
410 navigateFrame(Text.PAGE_DOWN);
411 break;
412 case KeyEvent.VK_PAGE_UP:
413 navigateFrame(Text.PAGE_UP);
414 break;
415 case KeyEvent.VK_UP:
416 if (e.isControlDown()) {
417 NextTextItem(FrameUtils.getCurrentItem(), false);
418 } else {
419 move(Text.UP, e.isShiftDown());
420 }
421 break;
422 case KeyEvent.VK_DOWN:
423 if (e.isControlDown()) {
424 NextTextItem(FrameUtils.getCurrentItem(), true);
425 } else {
426 move(Text.DOWN, e.isShiftDown());
427 }
428 break;
429 case KeyEvent.VK_END:
430 if (e.isControlDown())
431 move(Text.END, e.isShiftDown());
432 else
433 move(Text.LINE_END, e.isShiftDown());
434 break;
435 case KeyEvent.VK_HOME:
436 if (e.isControlDown())
437 move(Text.HOME, e.isShiftDown());
438 else
439 move(Text.LINE_HOME, e.isShiftDown());
440 break;
441 // TODO remove this when upgrading Java
442 // This is a patch because Java6 wont trigger KeyTyped event for
443 // Shift+Tab
444 case KeyEvent.VK_TAB:
445 if (e.isShiftDown()) {
446 e.setKeyChar('\t');
447 keyTyped(e);
448 }
449 break;
450 }
451 }
452
453 /**
454 * Moves the cursor to the next text item on the frame
455 *
456 * @param currentItem
457 * @param direction
458 * move up if direction is negative, down if direction is
459 * positive
460 */
461 public static void NextTextItem(Item currentItem, boolean down) {
462 // Move the cursor to the next text item
463 Frame current = DisplayIO.getCurrentFrame();
464 Item title = current.getTitleItem();
465
466 Collection<Text> currentItems = FrameUtils.getCurrentTextItems();
467 List<Text> textItems = new ArrayList<Text>();
468 // Move to the next text item in the box if
469 if (currentItems.contains(currentItem)) {
470 textItems.addAll(currentItems);
471 } else {
472 textItems.add(current.getTitleItem());
473 textItems.addAll(current.getBodyTextItems(true));
474 }
475
476 Collections.sort(textItems);
477
478 if (textItems.size() == 0) {
479 // If there are no text items on the frame its a NoOp
480 if (title == null)
481 return;
482 DisplayIO.MoveCursorToEndOfItem(title);
483 FrameGraphics.Repaint();
484 return;
485 }
486
487 // If the user is mouse wheeling in free space...
488 if (currentItem == null) {
489 // find the nearest item in the correct direction
490 int currY = FrameMouseActions.getY();
491 for (int i = 0; i < textItems.size(); i++) {
492 Item t = textItems.get(i);
493 if (currY < t.getY()) {
494 if (down) {
495 DisplayIO.MoveCursorToEndOfItem(t);
496 } else {
497 if (i == 0) {
498 DisplayIO.MoveCursorToEndOfItem(current
499 .getTitleItem());
500 } else {
501 DisplayIO.MoveCursorToEndOfItem(textItems
502 .get(i - 1));
503 }
504 }
505 FrameGraphics.Repaint();
506 return;
507 }
508 }
509 // If we are at the botton of the screen and the user scrolls down
510 // then scroll backup to the title
511 if (textItems.size() > 0) {
512 DisplayIO.MoveCursorToEndOfItem(textItems
513 .get(textItems.size() - 1));
514 }
515 return;
516 }
517
518 // Find the current item... then move to the next item
519 int i = textItems.indexOf(currentItem);
520
521 int nextIndex = i + (down ? 1 : -1);
522 if (nextIndex >= 0 && nextIndex < textItems.size()) {
523 DisplayIO.MoveCursorToEndOfItem(textItems.get(nextIndex));
524 } else {
525 DisplayIO.MoveCursorToEndOfItem(currentItem);
526 }
527 return;
528
529 }
530
531 public void keyReleased(KeyEvent e) {
532 if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
533 FrameMouseActions.control(e);
534 } else if (e.isAltDown() || e.isControlDown()) {
535 // switch (e.getKeyCode()) {
536 // case KeyEvent.VK_1:
537 // DisplayIO.releaseMouse(InputEvent.BUTTON1_MASK);
538 // break;
539 // case KeyEvent.VK_2:
540 // DisplayIO.releaseMouse(InputEvent.BUTTON2_MASK);
541 // break;
542 // case KeyEvent.VK_3:
543 // DisplayIO.releaseMouse(InputEvent.BUTTON3_MASK);
544 // break;
545 // }
546 }
547 }
548
549 /**
550 * Used to add items to a frame when pasting text from the clipboard.
551 *
552 * @param text
553 * the next paragraph of text to be pasted.
554 * @param lastItem
555 * the item to drop from when pasting the next paragraph of text.
556 * @return the new text item created from pasting the text.
557 */
558 private static Item addTextItemToCurrentFrame(String text, Item lastItem,
559 boolean bDrop) {
560 if (text.trim().length() > 0) {
561 if (lastItem != null && bDrop)
562 Drop(lastItem, true);
563
564 Text textItem = createText();
565 textItem.setText(text.substring(0, text.length() - 1));
566
567 if (lastItem != null && !bDrop) {
568 textItem.setY(lastItem.getY() + lastItem.getBoundsHeight());
569 } else {
570 textItem.setY(FrameMouseActions.getY());
571 }
572 textItem.setX(FrameMouseActions.getX());
573 justify(textItem);
574 DisplayIO.getCurrentFrame().addItem(textItem);
575 return textItem;
576 }
577 return lastItem;
578 }
579
580 private static void copyItemToClipboard(Item on) {
581 if (on == null || !(on instanceof Text))
582 return;
583
584 Text text = (Text) on;
585 String string = text.copySelectedText();
586
587 if (string == null || string.length() == 0)
588 string = text.getText();
589
590 // add the text of the item to the clipboard
591 StringSelection selection = new StringSelection(string);
592 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection,
593 null);
594 }
595
596 /**
597 * Processes all control character keystrokes. Currently Ctrl+C and Ctrl+V
598 * are copy and paste, all other keystrokes are ignored.
599 *
600 * @param ch
601 * The character being pressed along with the control key
602 */
603 private void controlChar(int key, boolean isShiftDown) {
604 Logger.Log(Logger.USER, Logger.CONTROL_CHAR, "User pressing: Ctrl+"
605 + KeyEvent.getKeyText(key));
606 //
607 // if (FrameUtils.getCurrentItem() == null
608 // && !Frame.itemAttachedToCursor()) {
609 // Item t = DisplayIO.getCurrentFrame().createNewText(ch + ": ");
610 // FrameMouseActions.pickup(t);
611 // } else {
612 // remove the link for alt+l
613 Item current = FrameUtils.getCurrentItem();
614 Frame currentFrame = DisplayIO.getCurrentFrame();
615 int distance = isShiftDown ? 1 : 20;
616 switch (key) {
617 case KeyEvent.VK_HOME:
618 if (current != null && current instanceof Text) {
619 move(Text.HOME, isShiftDown);
620 } else {
621 while (DisplayIO.Back())
622 ;
623 }
624 break;
625 case KeyEvent.VK_END:
626 if (current != null && current instanceof Text) {
627 move(Text.END, isShiftDown);
628 } else {
629 while (DisplayIO.Forward())
630 ;
631 }
632 break;
633 case KeyEvent.VK_PAGE_UP:
634 DisplayIO.Back();
635 break;
636 case KeyEvent.VK_PAGE_DOWN:
637 DisplayIO.Forward();
638 break;
639 case KeyEvent.VK_TAB:
640 calculateItem(current);
641 break;
642 case KeyEvent.VK_ESCAPE:
643 // Do escape after control so Ctl+Escape does not perform DropDown
644 functionKey(FunctionKey.DropDown, isShiftDown, true);
645 SessionStats.Escape();
646 break;
647 case KeyEvent.VK_1:
648 FrameMouseActions.leftButton();
649 // DisplayIO.clickMouse(InputEvent.BUTTON1_MASK);
650 break;
651 case KeyEvent.VK_2:
652 FrameMouseActions.middleButton();
653 // DisplayIO.clickMouse(InputEvent.BUTTON2_MASK);
654 break;
655 case KeyEvent.VK_3:
656 FrameMouseActions.rightButton();
657 // DisplayIO.clickMouse(InputEvent.BUTTON3_MASK);
658 break;
659 case KeyEvent.VK_LEFT:
660 DisplayIO.translateCursor(-distance, 0);
661 break;
662 case KeyEvent.VK_RIGHT:
663 DisplayIO.translateCursor(distance, 0);
664 break;
665 case KeyEvent.VK_UP:
666 if (current instanceof Text) {
667 NextTextItem(FrameUtils.getCurrentItem(), false);
668 } else {
669 DisplayIO.translateCursor(0, -distance);
670 }
671 break;
672 case KeyEvent.VK_DOWN:
673 if (current instanceof Text) {
674 NextTextItem(FrameUtils.getCurrentItem(), true);
675 } else {
676 DisplayIO.translateCursor(0, distance);
677 }
678 break;
679 case KeyEvent.VK_L:
680 // If its not linked then link it to its self
681 if (current instanceof Text && current.getLink() == null) {
682 String text = ((Text) current).getText();
683 // Ignore the annotation if there is one
684 if (text.charAt(0) == '@')
685 text = text.substring(1);
686
687 if (FrameIO.isValidFrameName(text)) {
688 current.setLink(text);
689 } else if (FrameIO.isValidFramesetName(text)) {
690 current.setLink(text + '1');
691 }
692 } else if (current != null) {
693 // If its linked remove the link
694 current.setLink(null);
695 }
696 break;
697 case KeyEvent.VK_G:
698 // If its not linked then link it to its self
699 if (current instanceof Text) {
700 String text = ((Text) current).getText();
701 if (text.charAt(0) == '@')
702 text = text.substring(1);
703
704 if (FrameIO.isValidFrameName(text)) {
705 current.setLink(text);
706 } else if (FrameIO.isValidFramesetName(text)) {
707 current.setLink(text + '1');
708 }
709 }
710 if (current.getLink() != null) {
711 Navigation.Goto(current.getAbsoluteLink());
712 return;
713 }
714 break;
715 case KeyEvent.VK_A:
716 // If its not linked then link it to its self
717 if (current instanceof Text) {
718 if (!current.hasAction()) {
719 String text = ((Text) current).getText().trim();
720 // first trim the annotation
721 if (text.startsWith("@")) {
722 text = text.substring(1).trim();
723 }
724 // then trim the action
725 String lowerCaseText = text.toLowerCase();
726 if (lowerCaseText.startsWith("a:")) {
727 text = text.substring("a:".length()).trim();
728 } else if (lowerCaseText.startsWith("action:")) {
729 text = text.substring("action:".length()).trim();
730
731 }
732 current.setAction(text);
733 } else {
734 // If its linked remove the link
735 current.setActions(null);
736 }
737 }
738 break;
739 case KeyEvent.VK_V:
740 try {
741 // read in the data from the clipboard
742 String clip = ((String) Toolkit.getDefaultToolkit()
743 .getSystemClipboard().getContents(null)
744 .getTransferData(DataFlavor.stringFlavor));
745 // Covert the line separator char when pasting in
746 // windows (\r\n) or max (\r)
747 clip = StringUtils.convertNewLineChars(clip);
748
749 Item clicked = FrameUtils.getCurrentItem();
750
751 if (clicked != null) {
752 // check permissions
753 if (!clicked.hasPermission(Permission.full)
754 && clicked.getParent() != null
755 && clicked.getParent().getNameItem() != clicked) {
756 MessageBay.displayMessage("Insufficient Permission");
757 return;
758 }
759
760 Text text = createText();
761 text.setText(clip);
762 List<Item> clipboard = new ArrayList<Item>();
763 clipboard.add(text);
764 FrameMouseActions.merge(clipboard, clicked);
765 text.delete();
766 } else {
767 StringTokenizer st = new StringTokenizer(clip, "\n", true);
768
769 String temp = "";
770 // int y = FrameMouseActions.getY();
771 Item lastItem = null;
772
773 boolean drop = currentFrame.hasAnnotation("next");
774 // separate the clipboard content into items based on
775 // blank lines
776 while (st.hasMoreTokens()) {
777 String s = st.nextToken();
778 // if this is a blank line, then it is an item separator
779 if (s.trim().length() == 0) {
780 lastItem = addTextItemToCurrentFrame(temp,
781 lastItem, drop);
782 temp = "";
783 } else {
784 temp += s + "\n";
785 if (st.hasMoreTokens())
786 st.nextToken();
787 }
788 }
789 // the last item will not be finished by the above loop
790 addTextItemToCurrentFrame(temp, lastItem, drop);
791 FrameIO.ForceSaveFrame(DisplayIO.getCurrentFrame());
792 DisplayIO.setCurrentFrame(currentFrame, false);
793 }
794 } catch (Exception ex) {
795 }
796 // if this is a copy command
797 break;
798 case KeyEvent.VK_C:
799 if (current == null)
800 return;
801
802 if (current instanceof Text) {
803 if (current != null && !current.hasPermission(Permission.copy)) {
804 MessageBay
805 .displayMessage("Insufficient permission to copy that item");
806 return;
807 }
808 copyItemToClipboard(current);
809 // MessageBay.displayMessage("Item copied to clipboard");
810 return;
811 }
812 if (current != null && !current.hasPermission(Permission.full)) {
813 MessageBay
814 .displayMessage("Insufficient permission edit that item");
815 return;
816 }
817 Text item = null;
818 // Check if its a line to be turned into a circle
819 if (current instanceof Dot && current.getLines().size() == 1) {
820 item = replaceDot(current, '@');
821 } else if (current instanceof Line
822 && current.getAllConnected().size() == 3) {
823 Item end = ((Line) current).getEndItem();
824 if (end instanceof Dot) {
825 item = replaceDot(end, '@');
826 } else if (end instanceof Text) {
827 item = (Text) end;
828 }
829 }
830 if (item != null) {
831 item.setText("@c");
832 DisplayIO.setCursorPosition(item.getX(), item.getY());
833 FrameUtils.setLastEdited(null);
834 Refresh();
835 }
836 return;
837 case KeyEvent.VK_X:
838 if (current == null)
839 return;
840 if (current != null && !current.hasPermission(Permission.full)) {
841 MessageBay
842 .displayMessage("Insufficient permission to cut that item");
843 return;
844 }
845 copyItemToClipboard(current);
846 if (current instanceof Text && ((Text) current).hasSelection()) {
847 ((Text) current).cutSelectedText();
848 ((Text) current).clearSelection();
849 } else {
850 FrameMouseActions.delete(current);
851 }
852 return;
853 case KeyEvent.VK_M:
854 if (current == null)
855 return;
856 if (current != null && !current.hasPermission(Permission.full)) {
857 MessageBay
858 .displayMessage("Insufficient permission toggle the items mark");
859 return;
860 }
861 boolean newValue = !(current.getLinkMark() || current
862 .getActionMark());
863 current.setLinkMark(newValue);
864 current.setActionMark(newValue);
865 break;
866 case KeyEvent.VK_Z:
867 DisplayIO.getCurrentFrame().undo();
868 return;
869 case KeyEvent.VK_D:
870 // perform a delete operation
871 processChar((char) KeyEvent.VK_DELETE, isShiftDown);
872 break;
873 case KeyEvent.VK_DELETE:
874 // perform a delete operation
875 FrameMouseActions.delete(current);
876 break;
877 case KeyEvent.VK_SPACE:
878 if (isShiftDown) {
879 FrameMouseActions.rightButton();
880 } else {
881 FrameMouseActions.middleButton();
882 }
883 break;
884 case KeyEvent.VK_F:
885 // perform a delete operation
886 Actions.PerformActionCatchErrors(currentFrame, null, "Format");
887 return;
888 case KeyEvent.VK_J:
889 Text text = getCurrentTextItem();
890 if (text == null) {
891 for (Text t : FrameUtils.getCurrentTextItems()) {
892 justify(t);
893 }
894
895 return;
896 }
897
898 // if (text.getWidth() < 0)
899 // text.setWidth(text.getBoundsWidth() - Item.MARGIN_RIGHT
900 // - UserSettings.Gravity);
901 justify(text);
902 break;
903 case KeyEvent.VK_S:
904 Text text2 = getCurrentTextItem();
905 // split the current text item
906 if (text2 == null)
907 return;
908 List<String> textLines = text2.getTextList();
909 if (textLines.size() <= 1)
910 return;
911 // remove all except the first line of text from the item being
912 // split
913 text2.setText(textLines.get(0));
914 int y = text2.getY();
915 for (int i = 1; i < textLines.size(); i++) {
916 Text newText = text2.copy();
917 newText.setText(textLines.get(i));
918 y += newText.getBoundsHeight();
919 newText.setY(y);
920 // update the items ID to prevent conflicts with the current
921 // frame
922 newText.setID(currentFrame.getNextItemID());
923 currentFrame.addItem(newText);
924 }
925 break;
926 case KeyEvent.VK_ENTER:
927 FrameMouseActions.leftButton();
928 break;
929 case KeyEvent.VK_BACK_SPACE:
930 DisplayIO.Back();
931 break;
932 }
933 FrameGraphics.Repaint();
934 }
935
936 /**
937 * Gets the currently selected item if the user is allowed to modify it.
938 *
939 * @return null if the currently selected item is not a Text item that the
940 * user has permission to modify
941 */
942 private static Text getCurrentTextItem() {
943 Item item = FrameUtils.getCurrentItem();
944
945 if (item != null && !item.hasPermission(Permission.full)) {
946 MessageBay
947 .displayMessage("Insufficient permission to copy that item");
948 return null;
949 }
950
951 Item on = null;
952 if (item != null)
953 on = item;
954
955 if (on == null || !(on instanceof Text))
956 return null;
957
958 return (Text) on;
959 }
960
961 public static void functionKey(FunctionKey key, boolean isShiftDown,
962 boolean isControlDown) {
963 functionKey(key, 1, isShiftDown, isControlDown);
964 }
965
966 /**
967 * Called when a Function key has been pressed, and performs the specific
968 * action based on the key.
969 */
970 public static void functionKey(FunctionKey key, int repeat,
971 boolean isShiftDown, boolean isControlDown) {
972 // get whatever the user is pointing at
973 Item on = FrameUtils.getCurrentItem();
974 String displayMessage = "F" + key.ordinal() + ": " + key.toString();
975 // check for enclosed mode
976 if (on == null && key.ordinal() < FunctionKey.AudienceMode.ordinal()) {
977 Collection<Item> enclosed = FrameUtils.getCurrentItems(on);
978
979 if (enclosed != null && enclosed.size() > 0) {
980 // ensure only one dot\line is present in the list
981 Collection<Item> lineEnds = FrameUtils.getEnclosingLineEnds();
982 Item firstConnected = lineEnds.iterator().next();
983 Collection<Item> connected = firstConnected.getAllConnected();
984
985 switch (key) {
986 case DropDown:
987 // Get the last text item and drop from in
988 Item lastText = null;
989 for (Item i : enclosed) {
990 if (i instanceof Text) {
991 lastText = i;
992 }
993 }
994 // Drop from the item if there was a text item in the box
995 if (lastText != null) {
996 Drop(lastText, false);
997 } else {
998 // Move to the top of the box
999 Rectangle rect = firstConnected.getEnclosedShape()
1000 .getBounds();
1001 DisplayIO.setCursorPosition(rect.x + Text.MARGIN_LEFT,
1002 Text.MARGIN_LEFT
1003 + rect.y
1004 + DisplayIO.getCurrentFrame()
1005 .getItemTemplate()
1006 .getBoundsHeight());
1007 }
1008 break;
1009 case SizeUp:
1010 SetSize(firstConnected, repeat, false, true);
1011 break;
1012 case SizeDown:
1013 SetSize(firstConnected, -repeat, false, true);
1014 break;
1015 case ChangeColor:
1016 if (connected.size() > 0) {
1017 for (Item d : lineEnds) {
1018 if (isControlDown)
1019 SetGradientColor(d, isShiftDown);
1020 else
1021 SetFillColor(d, isShiftDown);
1022 break;
1023 }
1024 }
1025 break;
1026 case ToggleAnnotation:
1027 ToggleAnnotation(firstConnected);
1028 break;
1029 case ChangeFontStyle:
1030 ToggleFontStyle(firstConnected);
1031 break;
1032 case ChangeFontFamily:
1033 ToggleFontFamily(firstConnected);
1034 break;
1035 case InsertDate:
1036 AddDate(firstConnected);
1037 break;
1038 case Save:
1039 Save();
1040 MessageBay.displayMessage(displayMessage);
1041 break;
1042 }
1043 return;
1044 }
1045 }
1046 // Show a description of the function key pressed if the user is in free
1047 // space and return for the F keys that dont do anything in free space.
1048 if (on == null) {
1049 switch (key) {
1050 // These function keys still work in free space
1051 case DropDown:
1052 case InsertDate:
1053 case XRayMode:
1054 case AudienceMode:
1055 case Refresh:
1056 case Save:
1057 break;
1058 case SizeDown:
1059 if (isControlDown) {
1060 UserSettings.ScaleFactor -= 0.05;
1061 Misc.repaint();
1062 return;
1063 }
1064 case SizeUp:
1065 if (isControlDown) {
1066 UserSettings.ScaleFactor += 0.05;
1067 Misc.repaint();
1068 return;
1069 }
1070 default:
1071 MessageBay.displayMessageOnce(displayMessage);
1072 return;
1073 }
1074 }
1075
1076 switch (key) {
1077 case DropDown:
1078 if (isShiftDown || isControlDown) {
1079 if (on != null) {
1080 calculateItem(on);
1081 }
1082 }
1083 Drop(on, false);
1084 return;
1085 case SizeUp:
1086 SetSize(on, repeat, true, false);
1087 if (on instanceof Text) {
1088 DisplayIO.setTextCursor((Text) on, Text.NONE, true, false);
1089 }
1090 break;
1091 case SizeDown:
1092 SetSize(on, -repeat, true, false);
1093 if (on instanceof Text) {
1094 DisplayIO.setTextCursor((Text) on, Text.NONE, true, false);
1095 }
1096 break;
1097 case ChangeColor:
1098 SetColor(on, isShiftDown, isControlDown);
1099 break;
1100 case ToggleAnnotation:
1101 ToggleAnnotation(on);
1102 break;
1103 case ChangeFontStyle:
1104 ToggleFontStyle(on);
1105 break;
1106 case ChangeFontFamily:
1107 ToggleFontFamily(on);
1108 break;
1109 case InsertDate:
1110 AddDate(on);
1111 return;
1112 case NewFrameset:
1113 CreateFrameset(on);
1114 break;
1115 case XRayMode:
1116 FrameGraphics.ToggleXRayMode();
1117 break;
1118 case AudienceMode:
1119 FrameGraphics.ToggleAudienceMode();
1120 break;
1121 case Refresh:
1122 Refresh();
1123 break;
1124 case Save:
1125 Save();
1126 break;
1127 }
1128 on = FrameUtils.getCurrentItem();
1129 Collection<Item> enclosed = FrameUtils.getCurrentItems(on);
1130 if (on == null && (enclosed == null || enclosed.size() == 0))
1131 MessageBay.displayMessage(displayMessage);
1132 }
1133
1134 private static void calculateItem(Item toCalculate) {
1135 if (!toCalculate.update()) {
1136 toCalculate.setFormula(null);
1137 MessageBay.errorMessage("Can not calculate formula ["
1138 + toCalculate.getText() + ']');
1139 }
1140 }
1141
1142 private static void Save() {
1143 Frame current = DisplayIO.getCurrentFrame();
1144 current.change();
1145 FrameIO.SaveFrame(current, true, true);
1146 }
1147
1148 public static final String DEFAULT_NEW_ITEM_TEXT = "";
1149
1150 public static final String SHORT_DATE_FORMAT = "ddMMMyyyy";
1151
1152 public static final String TIME_DATE_FORMAT = "[HH:mm]";
1153
1154 public static final String LONG_DATE_FORMAT = "ddMMMyyyy[HH:mm]";
1155
1156 /**
1157 * Performs the dropping action: If the cursor is in free space then: the
1158 * cursor is repositioned below the last non-annotation text item. If the
1159 * cursor is on an item, and has items attached then: the cusor is
1160 * positioned below the pointed to item, and the items below are 'pushed
1161 * down' to make room.
1162 *
1163 * @param toDropFrom
1164 * The Item being pointed at by the mouse, may be null to
1165 * indicate the cursor is in free space.
1166 */
1167 public static boolean Drop(Item toDropFrom, boolean bPasting) {
1168 try {
1169 FrameUtils.setLastEdited(null);
1170
1171 String newItemText = DEFAULT_NEW_ITEM_TEXT;
1172
1173 // if a line is being rubber-banded, this is a no-op
1174 if (Frame.rubberbandingLine())
1175 return false; // No-op
1176
1177 // if the cursor is in free space then the drop will happen from the
1178 // last non annotation text item on the frame
1179 if (toDropFrom == null) {
1180 toDropFrom = DisplayIO.getCurrentFrame()
1181 .getLastNonAnnotationTextItem();
1182 }
1183
1184 // if no item was found, return
1185 if (toDropFrom == null) {
1186 MessageBay.errorMessage("No item could be found to drop from");
1187 return false;
1188 }
1189
1190 if (!(toDropFrom instanceof Text)) {
1191 MessageBay
1192 .displayMessage("Only text items can be dropped from");
1193 return false;
1194 }
1195
1196 // Get the list of items that must be dropped
1197 List<Text> column = DisplayIO.getCurrentFrame().getColumn(
1198 toDropFrom);
1199
1200 if (column == null) {
1201 MessageBay.errorMessage("No column found to align items to");
1202 return false;
1203 }
1204
1205 Item title = DisplayIO.getCurrentFrame().getTitleItem();
1206
1207 // We wont do auto bulleting when dropping from titles
1208 if (!bPasting && toDropFrom != title) {
1209 newItemText = getAutoBullet(((Text) toDropFrom).getFirstLine());
1210 }
1211
1212 Text dummyItem = null;
1213 if (!bPasting && FreeItems.textOnlyAttachedToCursor()) {
1214 dummyItem = (Text) FreeItems.getItemAttachedToCursor();
1215 String autoBullet = getAutoBullet(dummyItem.getText());
1216
1217 if (autoBullet.length() > 0)
1218 newItemText = "";
1219 dummyItem.setText(newItemText + dummyItem.getText());
1220 }
1221
1222 dummyItem = createText();
1223 if (FreeItems.textOnlyAttachedToCursor()) {
1224 Text t = (Text) FreeItems.getItemAttachedToCursor();
1225 dummyItem.setSize(t.getSize());
1226 int lines = t.getTextList().size();
1227 for (int i = 0; i < lines; i++) {
1228 newItemText += '\n';
1229 }
1230 }
1231
1232 dummyItem.setText(newItemText);
1233
1234 // If the only item on the frame is the title and the frame name
1235 // goto the zero frame and drop to the @start if there is one
1236 // or a fixed amount if there is not
1237 if (column.size() == 0) {
1238 Frame current = DisplayIO.getCurrentFrame();
1239 // Item itemTemplate = current.getItemTemplate();
1240 int xPos = title.getX() + FrameCreator.INDENT_FROM_TITLE;
1241 int yPos = FrameCreator.getYStart(title);
1242 // Check for @start on the zero frame
1243 Frame zero = FrameIO.LoadFrame(current.getFramesetName() + '0');
1244 Text start = zero.getAnnotation("start");
1245 if (start != null) {
1246 xPos = start.getX();
1247 yPos = start.getY();
1248 }
1249
1250 dummyItem.setPosition(xPos, yPos);
1251 DisplayIO.setCursorPosition(xPos, yPos);
1252 } else {
1253 int yPos = column.get(0).getY() + 1;
1254 // Either position the new item below the title or just above
1255 // the first item below the title
1256 if (toDropFrom == title)
1257 yPos = Math.min(column.get(0).getY() - 1, title.getY()
1258 + title.getBoundsHeight()
1259 + dummyItem.getBoundsHeight());
1260 dummyItem.setPosition(column.get(0).getX(), yPos);
1261 column.add(dummyItem);
1262 FrameUtils.Align(column, false, 0);
1263 // Check if it will be outside the frame area
1264 if (dummyItem.getY() < 0
1265 || dummyItem.getY() > FrameGraphics.getMaxFrameSize()
1266 .getHeight()) {
1267 // Check for the 'next' tag!
1268 Frame current = DisplayIO.getCurrentFrame();
1269 Item next = current.getAnnotation("next");
1270 Item prev = current.getAnnotation("previous");
1271 // Check for an unlinked next tag
1272 if ((next != null && !next.hasLink())
1273 || (prev != null && prev.hasLink())) {
1274 Frame firstFrame = current;
1275 if (next != null)
1276 next.delete();
1277 FrameCreator frameCreator = new FrameCreator(null);
1278 // Add the next button
1279 next = frameCreator.addNextButton(current, null);
1280
1281 // Create the new frame linked to the next tag
1282 boolean mouseMoved = FrameMouseActions.tdfc(next);
1283 Frame moreFrame = DisplayIO.getCurrentFrame();
1284
1285 // Add previous button to the new frame
1286 frameCreator.addPreviousButton(moreFrame, firstFrame
1287 .getName());
1288 Item first = current.getAnnotation("first");
1289 if (first != null) {
1290 frameCreator.addFirstButton(moreFrame, first
1291 .getLink());
1292 } else {
1293 frameCreator.addFirstButton(moreFrame, firstFrame
1294 .getName());
1295 }
1296 // Add the @next if we are pasting
1297 // if (bPasting) {
1298 // Item copy = next.copy();
1299 // copy.setLink(null);
1300 // moreFrame.addItem(copy);
1301 // }
1302
1303 moreFrame.setTitle(firstFrame.getTitleItem().getText());
1304 // need to move the mouse to the top of the frame if
1305 // there wasnt an @start on it
1306 if (!mouseMoved) {
1307 Item moreTitle = moreFrame.getTitleItem();
1308 moreTitle.setPermission(Permission.full);
1309 Drop(moreTitle, bPasting);
1310 }
1311 // Add the bullet text to the item
1312 dummyItem.setPosition(DisplayIO.getMouseX(),
1313 FrameMouseActions.getY());
1314 } else {
1315 MessageBay
1316 .warningMessage("Can not create items outside the frame area");
1317 // ensures correct repainting when items don't move
1318 DisplayIO.setCursorPosition(DisplayIO.getMouseX(),
1319 FrameMouseActions.getY());
1320 return false;
1321 }
1322 }
1323 if (!FreeItems.textOnlyAttachedToCursor()
1324 && !dummyItem.isEmpty()) {
1325 DisplayIO.getCurrentFrame().addItem(dummyItem);
1326 }
1327
1328 // Move the item to the cursor position
1329 if (FreeItems.itemsAttachedToCursor()) {
1330 DisplayIO.setCursorPosition(dummyItem.getX(), dummyItem
1331 .getY());
1332 Item firstItem = FreeItems.getItemAttachedToCursor();
1333 int deltaX = firstItem.getX() - dummyItem.getX();
1334 int deltaY = firstItem.getY() - dummyItem.getY();
1335
1336 for (Item i : FreeItems.getInstance()) {
1337 i.setPosition(i.getX() - deltaX, i.getY() - deltaY);
1338 }
1339 } else {
1340 DisplayIO.MoveCursorToEndOfItem(dummyItem);
1341 }
1342 }
1343 if (dummyItem.getText().length() == 0
1344 || FreeItems.itemsAttachedToCursor()) {
1345 dummyItem.getParentOrCurrentFrame().removeItem(dummyItem);
1346 dummyItem.setRightMargin(FrameGraphics.getMaxFrameSize().width);
1347 } else {
1348 dummyItem.setWidth(toDropFrom.getWidth());
1349 }
1350
1351 DisplayIO.resetCursorOffset();
1352 FrameGraphics.Repaint();
1353 } catch (RuntimeException e) {
1354 // MessageBay.errorMessage(e.getMessage());
1355 e.printStackTrace();
1356 return false;
1357 }
1358 return true;
1359 }
1360
1361 /**
1362 * Gets the next letter sequence for a given string to be used in auto
1363 * lettering.
1364 *
1365 * @param s
1366 * a sequence of letters
1367 * @return the next sequence of letters
1368 */
1369 static private String nextLetterSequence(String s) {
1370 if (s.length() > 1)
1371 return s;
1372
1373 if (s.equals("z"))
1374 return "a";
1375
1376 return (char) ((int) s.charAt(0) + 1) + "";
1377 }
1378
1379 public static String getBullet(String s) {
1380 return getBullet(s, false);
1381 }
1382
1383 public static String getAutoBullet(String s) {
1384 return getBullet(s, true);
1385 }
1386
1387 private static String getBullet(String s, boolean nextBullet) {
1388 String newItemText = DEFAULT_NEW_ITEM_TEXT;
1389
1390 if (s == null)
1391 return newItemText;
1392 /*
1393 * Item i = ItemUtils.FindTag(DisplayIO.getCurrentFrame().getItems(),
1394 * "@NoAutoBullets"); if (i != null) return newItemText;
1395 */
1396 // Separate the space at the start of the text item
1397 String preceedingSpace = "";
1398 for (int i = 0; i < s.length(); i++) {
1399 if (!Character.isSpaceChar(s.charAt(i))) {
1400 preceedingSpace = s.substring(0, i);
1401 s = s.substring(i);
1402 break;
1403 }
1404 }
1405
1406 // figure out the type of the text item
1407 // This allows us to do auto bulleting
1408 if (s != null && s.length() > 1) {
1409 // First check for text beginning with * @ # etc
1410 // These are simple auto bullets
1411 if (!Character.isLetterOrDigit(s.charAt(0))
1412 && !Character.isSpaceChar(s.charAt(0))) {
1413 if (Text.isBulletChar(s.charAt(0))) {
1414 int nonSpaceIndex = 1;
1415 // Find the end of the bullet and space after the bullet
1416 while (nonSpaceIndex < s.length()
1417 && s.charAt(nonSpaceIndex) == ' ') {
1418 nonSpaceIndex++;
1419 }
1420 // we must have a special char followed by >= 1 space
1421 if (nonSpaceIndex > 1)
1422 newItemText = s.substring(0, nonSpaceIndex);
1423 }
1424 // Auto numbering and lettering
1425 } else {
1426 if (Character.isDigit(s.charAt(0))) {
1427 newItemText = getAutoNumber(s, nextBullet);
1428 // Auto lettering
1429 } else if (Character.isLetter(s.charAt(0))) {
1430 newItemText = getAutoLetter(s, nextBullet);
1431 }
1432 }
1433 }
1434 return preceedingSpace + newItemText;
1435 }
1436
1437 private static boolean isAutoNumberOrLetterChar(char c) {
1438 return c == ':' || c == '-' || c == '.' || c == ')' || c == '>';
1439 }
1440
1441 /**
1442 * Gets the string to be used to start the next auto numbered text item.
1443 *
1444 * @param s
1445 * the previous text item
1446 * @return the beginning of the next auto numbered text item
1447 */
1448 private static String getAutoNumber(String s, boolean nextBullet) {
1449 String newItemText = DEFAULT_NEW_ITEM_TEXT;
1450
1451 int nonDigitIndex = 1;
1452 while (Character.isDigit(s.charAt(nonDigitIndex))) {
1453 nonDigitIndex++;
1454
1455 if (nonDigitIndex + 1 >= s.length())
1456 return DEFAULT_NEW_ITEM_TEXT;
1457 }
1458
1459 if (isAutoNumberOrLetterChar(s.charAt(nonDigitIndex))) {
1460
1461 // we must have a number followed one non letter
1462 // then one or more spaces
1463 int nonSpaceIndex = nonDigitIndex + 1;
1464 while (nonSpaceIndex < s.length() && s.charAt(nonSpaceIndex) == ' ') {
1465 nonSpaceIndex++;
1466 }
1467
1468 if (nonSpaceIndex > nonDigitIndex + 1) {
1469 if (nextBullet)
1470 newItemText = (Integer.parseInt(s.substring(0,
1471 nonDigitIndex)) + 1)
1472 + s.substring(nonDigitIndex, nonSpaceIndex);
1473 else
1474 newItemText = s.substring(0, nonSpaceIndex);
1475 }
1476 }
1477 return newItemText;
1478 }
1479
1480 /**
1481 * Gets the string to be used to start the next auto lettered text item.
1482 *
1483 * @param s
1484 * the previous text items
1485 * @return the initial text for the new text item
1486 */
1487 private static String getAutoLetter(String s, boolean nextBullet) {
1488 String newItemText = DEFAULT_NEW_ITEM_TEXT;
1489
1490 int nonLetterIndex = 1;
1491
1492 if (isAutoNumberOrLetterChar(s.charAt(nonLetterIndex))) {
1493
1494 // Now search for the next non space character
1495 int nonSpaceIndex = nonLetterIndex + 1;
1496 while (nonSpaceIndex < s.length() && s.charAt(nonSpaceIndex) == ' ') {
1497 nonSpaceIndex++;
1498 }
1499
1500 // If there was a space then we have reached the end of our auto
1501 // text
1502 if (nonSpaceIndex > nonLetterIndex + 1) {
1503 if (nextBullet)
1504 newItemText = nextLetterSequence(s.substring(0,
1505 nonLetterIndex))
1506 + s.substring(nonLetterIndex, nonSpaceIndex);
1507 else
1508 newItemText = s.substring(0, nonSpaceIndex);
1509 }
1510 }
1511 return newItemText;
1512 }
1513
1514 /**
1515 * Adjusts the size of the given Item, by the given amount. Note: The amount
1516 * is relative and can be positive or negative.
1517 *
1518 * @param toSet
1519 * The Item whose size is to be adjusted
1520 * @param diff
1521 * The amount to adjust the Item's size by
1522 * @param moveCursor
1523 * true if the cursor position should be automatically adjusted
1524 * with resizing
1525 */
1526 public static void SetSize(Item item, int diff, boolean moveCursor,
1527 boolean insideEnclosure) {
1528 Collection<Item> toSize = new HashSet<Item>();
1529 // the mouse is only moved when the Item is on the frame, not free
1530 // boolean moveMouse = false;
1531 Item toSet = null;
1532
1533 // if the user is not pointing to any item
1534 if (item == null) {
1535 if (FreeItems.itemsAttachedToCursor())
1536 toSize.addAll(FreeItems.getInstance());
1537 else {
1538 MessageBay
1539 .displayMessage("There are no Items selected on the Frame or on the Cursor");
1540 return;
1541 }
1542 } else {
1543 if (item.isFrameName()) {
1544 MessageBay.displayMessage("Can not resize the frame name");
1545 return;
1546 }
1547 // check permissions
1548 if (!item.hasPermission(Permission.full)) {
1549 Item editTarget = item.getEditTarget();
1550 if (editTarget != item
1551 && editTarget.hasPermission(Permission.full)) {
1552 item = editTarget;
1553 } else {
1554 MessageBay
1555 .displayMessage("Insufficient permission to change the size of that item");
1556 return;
1557 }
1558 }
1559 toSet = item;
1560 // For resizing enclosures pick up everything that is attached to
1561 // items partly in the enclosure
1562 // TODO make this only pick up stuff COMPLETELY enclosed... if we
1563 // change copying to copy only the stuff completely enclosed
1564 if (insideEnclosure) {
1565 if (_enclosedItems == null) {
1566 for (Item i : FrameUtils.getCurrentItems(toSet)) {
1567 if (i.hasPermission(Permission.full)
1568 && !toSize.contains(i))
1569 toSize.addAll(i.getAllConnected());
1570 }
1571 _enclosedItems = toSize;
1572 } else {
1573 toSize = _enclosedItems;
1574 }
1575
1576 }// Enclosed circle centers are resized with the center as origin
1577 // Just add the circle center to the list of items to size
1578 else if (!toSet.hasEnclosures() && !(toSet instanceof Text)
1579 && toSet.isLineEnd()) {
1580 toSize.addAll(toSet.getLines());
1581 } else if (toSet instanceof Line) {
1582 Line line = (Line) toSet;
1583 float current = Math.abs(line.getThickness());
1584 current = Math.max(current + diff, Item.MINIMUM_THICKNESS);
1585 line.getStartItem().setThickness(current);
1586 FrameGraphics.Repaint();
1587 return;
1588 } else {
1589 toSize.add(toSet);
1590 }
1591 }
1592
1593 Point2D origin = new Point2D.Float(FrameMouseActions.MouseX,
1594 FrameMouseActions.MouseY);
1595 // Inside enclosures increase the size of the enclosure
1596 double ratio = (100.0 + diff * 2) / 100.0;
1597 if (insideEnclosure) {
1598 Collection<Item> done = new HashSet<Item>();
1599 // adjust the size of all the items
1600 for (Item i : toSize) {
1601 if (done.contains(i))
1602 continue;
1603
1604 if (i.isLineEnd()) {
1605 Collection<Item> allConnected = i.getAllConnected();
1606 done.addAll(allConnected);
1607 for (Item it : allConnected) {
1608 it.translate(origin, ratio);
1609 it
1610 .setArrowheadLength((float) (it
1611 .getArrowheadLength() * ratio));
1612 }
1613 i.setThickness((float) (i.getThickness() * ratio));
1614 } else if (i instanceof XRayable) {
1615 XRayable xRay = (XRayable) i;
1616 Text source = xRay.getSource();
1617 // Ensure that the source is done before the XRayable
1618 if (!done.contains(source)) {
1619 scaleText(origin, ratio, done, source);
1620 }
1621
1622 i.translate(origin, ratio);
1623 i.setThickness((float) (i.getThickness() * ratio));
1624 done.add(i);
1625 } else if (i.hasVector()) {
1626 // TODO Improve the effiency of resizing vectors... ie...
1627 // dont want to have to reparse all the time
1628 assert (i instanceof Text);
1629 Text text = (Text) i;
1630 AttributeValuePair avp = new AttributeValuePair(text
1631 .getText());
1632 double scale = 1F;
1633 try {
1634 scale = avp.getDoubleValue();
1635 } catch (Exception e) {
1636 }
1637 scale *= ratio;
1638 NumberFormat nf = Vector.getNumberFormatter();
1639 text.setAttributeValue(nf.format(scale));
1640 text.translate(origin, ratio);
1641 item.getParent().parse();
1642 } else if (i instanceof Text) {
1643 scaleText(origin, ratio, done, (Text) i);
1644 }
1645 }
1646 FrameGraphics.refresh(true);
1647 return;
1648 }
1649
1650 // adjust the size of all the items
1651 for (Item i : toSize) {
1652 // Lines and dots use thickness, not size
1653 if (i.hasEnclosures()) {
1654 Circle c = (Circle) i.getEnclosures().iterator().next();
1655 c.setSize(c.getSize() * (float) ratio);
1656 } else if (i instanceof Line || i instanceof Circle
1657 && !insideEnclosure) {
1658 float current = Math.abs(i.getThickness());
1659 current = Math.max(current + diff, Item.MINIMUM_THICKNESS);
1660 i.setThickness(current);
1661 } else if (i instanceof Dot) {
1662 Item dot = (Item) i;
1663 float current = Math.abs(dot.getThickness());
1664 current = Math.max(current + diff, Item.MINIMUM_THICKNESS);
1665 dot.setThickness(current);
1666 } else if (i.hasVector()) {
1667 assert (item instanceof Text);
1668 Text text = (Text) item;
1669 AttributeValuePair avp = new AttributeValuePair(text.getText());
1670 double scale = 1F;
1671 try {
1672 scale = avp.getDoubleValue();
1673 } catch (Exception e) {
1674 }
1675 scale *= ratio;
1676 NumberFormat nf = Vector.getNumberFormatter();
1677 text.setAttributeValue(nf.format(scale));
1678 text.translate(origin, ratio);
1679 item.getParent().parse();
1680 } else {
1681 float oldSize = Math.abs(i.getSize());
1682 float newSize = Math
1683 .max(oldSize + diff, Item.MINIMUM_THICKNESS);
1684 float resizeRatio = newSize / oldSize;
1685 // Set size for Picture also translates
1686 i.setSize(newSize);
1687 if (i instanceof Text && i.getSize() != oldSize) {
1688 i.translate(origin, resizeRatio);
1689 if (i.isLineEnd()) {
1690 i.setPosition(i.getPosition());
1691 }
1692 }
1693 }
1694 }
1695
1696 if (toSet != null)
1697 toSet.getParent().setChanged(true);
1698
1699 FrameGraphics.refresh(true);
1700 }
1701
1702 /**
1703 * @param origin
1704 * @param ratio
1705 * @param done
1706 * @param source
1707 */
1708 private static void scaleText(Point2D origin, double ratio,
1709 Collection<Item> done, Text source) {
1710 source.translate(origin, ratio);
1711 source.setSize((float) (source.getSize() * ratio));
1712 done.add(source);
1713 }
1714
1715 private static void SetFillColor(Item item, boolean setTransparent) {
1716 if (item == null)
1717 return;
1718
1719 if (!item.hasPermission(Permission.full)) {
1720 MessageBay
1721 .displayMessage("Insufficient permission to change fill color");
1722 return;
1723 }
1724
1725 Item toSet = item;
1726 Color color = toSet.getFillColor();
1727 if (setTransparent)
1728 color = null;
1729 else
1730 color = ColorUtils.getNextColor(color, Item.FILL_COLOR_WHEEL, toSet
1731 .getGradientColor());
1732
1733 // if (color == null) {
1734 // MessageBay.displayMessage("FillColor is now transparent");
1735 // }
1736
1737 toSet.setFillColor(color);
1738 toSet.getParent().setChanged(true);
1739
1740 FrameGraphics.Repaint();
1741 }
1742
1743 private static void SetGradientColor(Item item, boolean setTransparent) {
1744 if (item == null)
1745 return;
1746
1747 if (!item.hasPermission(Permission.full)) {
1748 MessageBay
1749 .displayMessage("Insufficient permission to change gradient color");
1750 return;
1751 }
1752
1753 Item toSet = item;
1754 Color color = toSet.getGradientColor();
1755 if (setTransparent)
1756 color = null;
1757 else
1758 color = ColorUtils.getNextColor(color, Item.COLOR_WHEEL, toSet
1759 .getFillColor());
1760
1761 // if (color == null) {
1762 // MessageBay.displayMessage("FillColor is now transparent");
1763 // }
1764
1765 toSet.setGradientColor(color);
1766 toSet.getParent().setChanged(true);
1767
1768 FrameGraphics.Repaint();
1769 }
1770
1771 /**
1772 * Sets the colour of the current Item based on its current colour. The
1773 * colours proceed in the order stored in COLOR_WHEEL.
1774 *
1775 * @param toSet
1776 * The Item whose colour is to be changed
1777 */
1778 private static void SetColor(Item item, boolean setTransparent,
1779 boolean setBackgroundColor) {
1780 // first determine the next color
1781 Color color = null;
1782 Frame currentFrame = DisplayIO.getCurrentFrame();
1783 if (item == null) {
1784 if (FreeItems.itemsAttachedToCursor()) {
1785 color = FreeItems.getInstance().get(0).getColor();
1786 } else {
1787 return;
1788 }
1789 // change the background color if the user is pointing on the
1790 // frame name
1791 } else if (item == currentFrame.getNameItem()) {
1792 // check permissions
1793 if (!item.hasPermission(Permission.full)) {
1794 MessageBay
1795 .displayMessage("Insufficient permission to the frame's background color");
1796 return;
1797 }
1798 if (setTransparent)
1799 currentFrame.setBackgroundColor(null);
1800 else
1801 currentFrame.toggleBackgroundColor();
1802 // Display a message if the color has changed to transparent
1803 // if (currentFrame.getBackgroundColor() == null)
1804 // FrameGraphics
1805 // .displayMessage("Background color is now transparent");
1806 FrameGraphics.Repaint();
1807 return;
1808 } else {
1809 // check permissions
1810 if (!item.hasPermission(Permission.full)) {
1811 Item editTarget = item.getEditTarget();
1812 if (editTarget != item
1813 && editTarget.hasPermission(Permission.full)) {
1814 item = editTarget;
1815 } else {
1816 MessageBay
1817 .displayMessage("Insufficient permission to change color");
1818 return;
1819 }
1820 }
1821 // Toggling color of circle center changes the circle fill color
1822 if (item.hasEnclosures()) {
1823 if (setBackgroundColor) {
1824 SetGradientColor(item.getEnclosures().iterator().next(),
1825 setTransparent);
1826 } else {
1827 SetFillColor(item.getEnclosures().iterator().next(),
1828 setTransparent);
1829 }
1830 } else if (setBackgroundColor) {
1831 color = item.getPaintBackgroundColor();
1832 } else {
1833 color = item.getPaintColor();
1834 }
1835 }
1836 if (setTransparent)
1837 color = null;
1838 else if (setBackgroundColor) {
1839 color = ColorUtils.getNextColor(color, Item.FILL_COLOR_WHEEL, item
1840 .getPaintColor());
1841 } else {
1842 color = ColorUtils.getNextColor(color, Item.COLOR_WHEEL,
1843 currentFrame.getPaintBackgroundColor());
1844 }
1845 // if (currentFrame.getPaintForegroundColor().equals(color))
1846 // color = null;
1847
1848 // if color is being set to default display a message to indicate that
1849 // if (color == null) {
1850 // MessageBay.displayMessage("Color is set to default");
1851 // }
1852
1853 if (setBackgroundColor) {
1854 if (item == null && FreeItems.itemsAttachedToCursor()) {
1855 for (Item i : FreeItems.getInstance())
1856 i.setBackgroundColor(color);
1857 } else {
1858 item.setBackgroundColor(color);
1859 item.getParent().setChanged(true);
1860 }
1861 } else {
1862 if (item == null && FreeItems.itemsAttachedToCursor()) {
1863 for (Item i : FreeItems.getInstance())
1864 i.setColor(color);
1865 } else {
1866 item.setColor(color);
1867 item.getParent().setChanged(true);
1868 }
1869 }
1870 FrameGraphics.Repaint();
1871 }
1872
1873 /**
1874 * Toggles the given Item's annotation status on\off.
1875 *
1876 * @param toToggle
1877 * The Item to toggle
1878 */
1879 private static void ToggleAnnotation(Item toToggle) {
1880 if (toToggle == null) {
1881 MessageBay.displayMessage("There is no Item selected to toggle");
1882 return;
1883 }
1884
1885 // check permissions
1886 if (!toToggle.hasPermission(Permission.full)) {
1887 MessageBay
1888 .displayMessage("Insufficient permission to toggle that item's annotation");
1889 return;
1890 }
1891 toToggle.setAnnotation(!toToggle.isAnnotation());
1892
1893 toToggle.getParent().setChanged(true);
1894 FrameGraphics.Repaint();
1895 }
1896
1897 /**
1898 * Toggles the face style of a text item
1899 *
1900 * @param toToggle
1901 * The Item to toggle
1902 */
1903 private static void ToggleFontStyle(Item toToggle) {
1904 if (toToggle == null) {
1905 MessageBay.displayMessage("There is no Item selected to toggle");
1906 return;
1907 }
1908
1909 // check permissions
1910 if (!toToggle.hasPermission(Permission.full)) {
1911 MessageBay
1912 .displayMessage("Insufficient permission to toggle that item's annotation");
1913 return;
1914 }
1915
1916 if (toToggle instanceof Text) {
1917 Text text = (Text) toToggle;
1918 text.toggleFontStyle();
1919
1920 text.getParent().setChanged(true);
1921 FrameGraphics.Repaint();
1922 }
1923 }
1924
1925 /**
1926 * Toggles the face style of a text item
1927 *
1928 * @param toToggle
1929 * The Item to toggle
1930 */
1931 private static void ToggleFontFamily(Item toToggle) {
1932 if (toToggle == null) {
1933 MessageBay.displayMessage("There is no Item selected to toggle");
1934 return;
1935 }
1936
1937 // check permissions
1938 if (!toToggle.hasPermission(Permission.full)) {
1939 MessageBay
1940 .displayMessage("Insufficient permission to toggle that item's annotation");
1941 return;
1942 }
1943
1944 if (toToggle instanceof Text) {
1945 Text text = (Text) toToggle;
1946 text.toggleFontFamily();
1947
1948 text.getParent().setChanged(true);
1949 FrameGraphics.Repaint();
1950 }
1951 }
1952
1953 /**
1954 * If the given Item is null, then a new Text item is created with the
1955 * current date If the given Item is not null, then the current date is
1956 * prepended to the Item's text
1957 *
1958 * @param toAdd
1959 * The Item to prepend the date to, or null
1960 */
1961 private static void AddDate(Item toAdd) {
1962 String date1 = Logger.EasyDateFormat(LONG_DATE_FORMAT);
1963 String date2 = Logger.EasyDateFormat(SHORT_DATE_FORMAT);
1964 final String leftSeparator = " :";
1965 final String rightSeparator = ": ";
1966 String dateToAdd = date1 + rightSeparator;
1967 boolean prepend = false;
1968 boolean append = false;
1969
1970 // if the user is pointing at an item, add the date where ever the
1971 // cursor is pointing
1972 if (toAdd != null) {
1973 if (toAdd instanceof Text) {
1974 // permission check
1975 if (!toAdd.hasPermission(Permission.full)) {
1976 MessageBay
1977 .displayMessage("Insufficicent permission to add the date to that item");
1978 return;
1979 }
1980
1981 Text textItem = (Text) toAdd;
1982
1983 String text = textItem.getText();
1984
1985 // check if the default date has already been put on this item
1986 if (text.startsWith(date1 + rightSeparator)) {
1987 textItem.removeText(date1 + rightSeparator);
1988 dateToAdd = date2 + rightSeparator;
1989 prepend = true;
1990 } else if (text.startsWith(date2 + rightSeparator)) {
1991 textItem.removeText(date2 + rightSeparator);
1992 dateToAdd = leftSeparator + date2;
1993 append = true;
1994 } else if (text.endsWith(leftSeparator + date2)) {
1995 textItem.removeEndText(leftSeparator + date2);
1996 append = true;
1997 dateToAdd = leftSeparator + date1;
1998 } else if (text.endsWith(leftSeparator + date1)) {
1999 textItem.removeEndText(leftSeparator + date1);
2000 if (textItem.getLength() > 0) {
2001 dateToAdd = "";
2002 prepend = true;
2003 } else {
2004 // use the default date format
2005 prepend = true;
2006 }
2007 }
2008
2009 if (prepend) {
2010 // add the date to the text item
2011 textItem.prependText(dateToAdd);
2012 if (dateToAdd.length() == textItem.getLength())
2013 DisplayIO.setCursorPosition(textItem
2014 .getParagraphEndPosition());
2015 } else if (append) {
2016 textItem.appendText(dateToAdd);
2017 if (dateToAdd.length() == textItem.getLength())
2018 DisplayIO.setCursorPosition(textItem.getPosition());
2019 } else {
2020 for (int i = 0; i < date1.length(); i++) {
2021 processChar(date1.charAt(i), false);
2022 }
2023 }
2024
2025 textItem.getParent().setChanged(true);
2026 FrameGraphics.Repaint();
2027 } else {
2028 MessageBay
2029 .displayMessage("Only text items can have the date prepended to them");
2030 }
2031 // otherwise, create a new text item
2032 } else {
2033 Text newText = createText();
2034 newText.setText(dateToAdd);
2035 DisplayIO.getCurrentFrame().addItem(newText);
2036 DisplayIO.getCurrentFrame().setChanged(true);
2037 FrameGraphics.Repaint();
2038
2039 DisplayIO.setCursorPosition(newText.getParagraphEndPosition());
2040 }
2041
2042 }
2043
2044 /**
2045 * Creates a new Frameset with the name given by the Item
2046 *
2047 * @param name
2048 */
2049 private static void CreateFrameset(Item item) {
2050 if (item == null) {
2051 MessageBay
2052 .displayMessage("There is no selected item to use for the frameset name");
2053 return;
2054 }
2055
2056 if (!(item instanceof Text)) {
2057 MessageBay
2058 .displayMessage("Framesets can only be created from text items");
2059 return;
2060 }
2061
2062 // dont create frameset if the item is linked
2063 if (item.getLink() != null) {
2064 MessageBay
2065 .displayMessage("A frameset can not be created from a linked item");
2066 return;
2067 }
2068
2069 // check permissions
2070 if (!item.hasPermission(Permission.full)) {
2071 MessageBay
2072 .displayMessage("Insufficient permission to create a frameset from this item");
2073 return;
2074 }
2075
2076 Text text = (Text) item;
2077 try {
2078 // create the new frameset
2079 Frame linkTo = FrameIO.CreateNewFrameset(text.getFirstLine());
2080 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2081 text.setLink(linkTo.getName());
2082 text.getParent().setChanged(true);
2083 FrameUtils.DisplayFrame(linkTo, true);
2084 linkTo.moveMouseToDefaultLocation();
2085 // this needs to be done if the user doesnt move the mouse before
2086 // doing Tdfc while the cursor is set to the text cursor
2087 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2088 } catch (Exception e) {
2089 MessageBay.errorMessage(e.getMessage());
2090 }
2091 }
2092
2093 /**
2094 * Forces a re-parse and repaint of the current Frame.
2095 */
2096 public static void Refresh() {
2097 Frame currentFrame = DisplayIO.getCurrentFrame();
2098
2099 //Refresh widgets that use its self as a data source
2100 currentFrame.notifyObservers();
2101
2102 if (FrameIO.isProfileFrame(currentFrame)) {
2103 // TODO ensure that users can not delete the first frame in a
2104 // frameset...
2105 // TODO handle the case when users manually delete the first frame
2106 // in a frameset from the filesystem
2107 Frame profile = FrameIO.LoadFrame(currentFrame.getFramesetName()
2108 + "1");
2109 assert (profile != null);
2110 FrameUtils.Parse(currentFrame);
2111 FrameUtils.ParseProfile(profile);
2112 } else {
2113 FrameUtils.Parse(currentFrame);
2114 }
2115 // Need to update the cursor for when text items change to @b pictures
2116 // etc and the text cursor is showing
2117 FrameMouseActions.updateCursor();
2118 FrameMouseActions.getInstance().refreshHighlights();
2119 FrameGraphics.ForceRepaint();
2120 }
2121}
Note: See TracBrowser for help on using the repository browser.