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

Last change on this file since 294 was 294, checked in by ra33, 16 years ago
File size: 59.4 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.NavigationActions;
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 NavigationActions.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 NavigationActions.NextFrame(false);
310 break;
311 case Text.LEFT:
312 case Text.PAGE_DOWN:
313 NavigationActions.PreviousFrame(false);
314 break;
315 case Text.HOME:
316 case Text.LINE_HOME:
317 NavigationActions.ZeroFrame();
318 break;
319 case Text.END:
320 case Text.LINE_END:
321 NavigationActions.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 {
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 NavigationActions.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.