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

Last change on this file since 504 was 504, checked in by jts21, 11 years ago

New copy/paste handling, add pdfImporter, add dictionary and documentation to resources, other small changes

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