source: trunk/src_apollo/org/apollo/items/EmulatedTextItem.java@ 344

Last change on this file since 344 was 344, checked in by bjn8, 16 years ago

Improve mouse interactions a little with accurate audio placement

File size: 15.3 KB
Line 
1package org.apollo.items;
2
3import java.awt.AWTException;
4import java.awt.Color;
5import java.awt.Component;
6import java.awt.Cursor;
7import java.awt.Font;
8import java.awt.Graphics;
9import java.awt.Graphics2D;
10import java.awt.IllegalComponentStateException;
11import java.awt.Point;
12import java.awt.Rectangle;
13import java.awt.Robot;
14import java.awt.Shape;
15import java.awt.event.KeyEvent;
16import java.awt.event.KeyListener;
17import java.awt.event.MouseEvent;
18import java.awt.event.MouseListener;
19import java.awt.event.MouseMotionListener;
20import java.awt.geom.Area;
21import java.awt.geom.Point2D;
22import java.util.LinkedList;
23import java.util.List;
24
25import javax.swing.JComponent;
26import javax.swing.SwingUtilities;
27
28import org.expeditee.gui.Browser;
29import org.expeditee.gui.DisplayIO;
30import org.expeditee.gui.Frame;
31import org.expeditee.gui.FrameGraphics;
32import org.expeditee.gui.FrameMouseActions;
33import org.expeditee.gui.MouseEventRouter;
34import org.expeditee.gui.Popup;
35import org.expeditee.gui.PopupManager;
36import org.expeditee.items.Text;
37import org.expeditee.items.Item.HighlightMode;
38
39/**
40 *
41 * Its a terrible hack - but it will do.
42 *
43 * @author Brook Novak
44 *
45 */
46public class EmulatedTextItem {
47
48 private static final long serialVersionUID = 1L;
49
50 private static Robot robot;
51
52 private List<TextChangeListener> textChangeListeners = new LinkedList<TextChangeListener>();
53
54 private RestrictedTextItem emulatedSource;
55 private LabelEditPopup popup = null;
56 private JComponent parentComponant = null;
57 private Point masterOffset;
58
59 private static final Font DEFAULT_FONT = new Font("Serif-Plain", Font.PLAIN, 12);
60
61 static {
62 try {
63 robot = new Robot();
64 } catch (AWTException e) {
65 e.printStackTrace();
66 }
67 }
68
69 /**
70 * Constructor.
71 *
72 * @param parent
73 * Must not be null: the parent that lives directly on the content pane.
74 *
75 * @param offset
76 * The offset to add to the label position - from the parents top left corner.
77 * Must not be null.
78 */
79 public EmulatedTextItem(JComponent parent, Point offset) {
80 assert(parent != null);
81 assert(offset != null);
82
83 parentComponant = parent;
84 masterOffset = offset;
85 emulatedSource = new RestrictedTextItem();
86 emulatedSource.setPosition(parent.getX() + offset.x, parent.getY() + offset.y);
87 emulatedSource.setFont(DEFAULT_FONT);
88
89 }
90
91
92 public void setFontStyle(int style) {
93 emulatedSource.setFont(emulatedSource.getFont().deriveFont(style));
94 }
95
96 public void setFontSize(float size) {
97 emulatedSource.setSize(size);
98 }
99
100
101 /**
102 * Determines whether or no a mouse event is on the emulated text item.
103 * @param swingME
104 * Can be null.
105 *
106 * @return
107 * True if the event is over the emulated item.
108 */
109 private boolean isOnEmulatedText(MouseEvent swingME) {
110 if (swingME == null) return false;
111 Point p = convertToExpediteeSpace(swingME.getLocationOnScreen());
112 return isOnEmulatedText(p.x, p.y);
113 }
114
115 private boolean isOnEmulatedText(int expX, int expY) {
116 return emulatedSource.contains(expX, expY);
117 }
118
119 private Point convertToExpediteeSpace(Point screenPos) {
120 assert(screenPos != null);
121
122 Point expPoint = new Point(0, 0);
123 SwingUtilities.convertPointToScreen(expPoint, Browser._theBrowser.getContentPane());
124
125 return new Point(screenPos.x - expPoint.x, screenPos.y - expPoint.y);
126 }
127
128 /**
129 * Applies highlights - shows popup
130 *
131 * @param e
132 * @param parent
133 */
134 public boolean onMouseMoved(MouseEvent e, Component parent) {
135
136 if (isOnEmulatedText(e)) {
137
138 if (emulatedSource.getHighlightMode() != HighlightMode.Normal) {
139 emulatedSource.setHighlightMode(HighlightMode.Normal);
140 repaint();
141 }
142
143 if (parent != popup &&
144 (emulatedSource.getBoundsWidth() + masterOffset.x) > (parent.getWidth() - 10)) { // have some give to transition over widget edge
145 if (popup == null) {
146 popup = new LabelEditPopup();
147 }
148
149 int x = (emulatedSource.getPolygon() == null) ? parent.getX() + masterOffset.x : emulatedSource.getPolygon().getBounds().x;
150 int y = (emulatedSource.getPolygon() == null) ? parent.getY() + masterOffset.y : emulatedSource.getPolygon().getBounds().y;
151 x -= 2;
152 y -= 2;
153
154 if (!PopupManager.getInstance().isShowing(popup)) {
155 PopupManager.getInstance().showPopup(
156 popup,
157 new Point(x, y),
158 popup);
159 }
160
161 }
162
163 return true;
164
165 } else {
166
167 lostFocus();
168
169 }
170
171 return false;
172
173 }
174
175 /**
176 *
177 * @param e
178 *
179 * @param parent
180 */
181 public boolean onKeyPressed(KeyEvent e, Component parent) {
182
183 MouseEvent currentME = MouseEventRouter.getCurrentMouseEvent();
184
185 if (currentME == null) return false;
186
187 Point p = convertToExpediteeSpace(currentME.getLocationOnScreen());
188
189 // If so and the event really comes from its parent ...
190 if (currentME.getComponent() == parent &&
191 (isOnEmulatedText(p.x, p.y))) {
192
193 int yOffset = (parent == popup) ? -6 : -6;
194
195 Browser._theBrowser.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
196
197 // If a alphanumeric text entry...
198 if (!e.isActionKey() &&
199 (Character.isLetterOrDigit(e.getKeyChar()) ||
200 (e.getKeyChar() == KeyEvent.VK_BACK_SPACE && !emulatedSource.isEmpty()) ||
201 (e.getKeyChar() == KeyEvent.VK_DELETE && !emulatedSource.isEmpty()) ||
202 e.getKeyChar() == KeyEvent.VK_SPACE ||
203 e.getKeyChar() == KeyEvent.VK_UNDERSCORE)) {
204
205
206 Rectangle[] oldArea = emulatedSource.getDrawingArea();
207 int oldWidth = emulatedSource.getBoundsWidth();
208
209 // Insert the text according to the current mouse position
210 Point2D.Float newMouse = emulatedSource.insertChar(
211 e.getKeyChar(),
212 p.x,
213 p.y);
214
215 List<String> lines = emulatedSource.getTextList();
216 if (lines != null && lines.size() > 1) {
217 emulatedSource.setText(lines.get(0));
218 }
219
220 // Notify observers
221 fireTextChanged();
222
223 // Move "cursured mouse"
224 if (newMouse != null) {
225 Point bloc = Browser._theBrowser.getContentPane().getLocationOnScreen();
226 robot.mouseMove(bloc.x + (int)newMouse.x, bloc.y + (int)newMouse.y + yOffset);
227 }
228
229 // Invalidate emulated item if not in popup (popup will naturally handle invalidation)
230 if (parent != popup) {
231
232 if (oldWidth > emulatedSource.getBoundsWidth()) {
233 for (Rectangle r : oldArea)
234 FrameGraphics.invalidateArea(r);
235 }
236
237 repaint();
238
239 }
240
241 } else if (e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_RIGHT) {
242
243 Point2D.Float newMouse = emulatedSource.moveCursor(
244 (e.getKeyCode() == KeyEvent.VK_LEFT) ? Text.LEFT : Text.RIGHT,
245 (float)p.getX(), (float)p.getY(), false);
246
247 if (newMouse != null) {
248 Point bloc = Browser._theBrowser.getContentPane().getLocationOnScreen();
249 robot.mouseMove(bloc.x + (int)newMouse.x, bloc.y + (int)newMouse.y + yOffset);
250 }
251
252 }
253
254 if (emulatedSource.hasSelection()) {
255 emulatedSource.clearSelection();
256 repaint();
257 }
258
259 return true;
260
261 }
262
263 return false;
264 }
265
266 /**
267 *
268 * @param e
269 *
270 * @param parent
271 */
272 public boolean onKeyReleased(KeyEvent e, Component parent) {
273 return isOnEmulatedText(MouseEventRouter.getCurrentMouseEvent());
274
275 }
276
277
278 public boolean onMouseReleased(MouseEvent e) {
279
280 Point p = convertToExpediteeSpace(e.getLocationOnScreen());
281
282 // If so and the event really comes from its parent ...
283 if (isOnEmulatedText(p.x, p.y)) {
284
285
286 String toMoveIntoFreespace = null;
287
288 if (e.getButton() == MouseEvent.BUTTON1) {
289 // LINK
290 } else if (e.getButton() == MouseEvent.BUTTON2 &&
291 emulatedSource.hasSelection() &&
292 !emulatedSource.isEmpty() &&
293 emulatedSource.selectionMouseButton == MouseEvent.BUTTON2) {
294
295 invalidate();
296
297 toMoveIntoFreespace = emulatedSource.cutSelectedText();
298
299 } else if (e.getButton() == MouseEvent.BUTTON3 &&
300 emulatedSource.hasSelection() &&
301 !emulatedSource.isEmpty() &&
302 emulatedSource.selectionMouseButton == MouseEvent.BUTTON3) {
303 toMoveIntoFreespace = emulatedSource.copySelectedText();
304
305 } else if (e.getButton() == MouseEvent.BUTTON3 &&
306 !emulatedSource.hasSelection() &&
307 !emulatedSource.isEmpty()) {
308 toMoveIntoFreespace = emulatedSource.getText();
309 }
310
311 if (toMoveIntoFreespace != null) {
312
313 Frame target = DisplayIO.getCurrentFrame();
314 if (target != null) {
315 Text selectionCopy = new Text(target.getNextItemID(), toMoveIntoFreespace);
316 selectionCopy.setPosition(p.x, p.y);
317 selectionCopy.setSize(emulatedSource.getSize());
318 FrameMouseActions.pickup(selectionCopy);
319 lostFocus();
320 }
321
322
323 }
324
325 emulatedSource.clearSelection();
326 repaint();
327
328 return true;
329 }
330
331 return false;
332 }
333
334 public boolean onMousePressed(MouseEvent e) {
335
336 Point p = convertToExpediteeSpace(e.getLocationOnScreen());
337
338 // If so and the event really comes from its parent ...
339 if (isOnEmulatedText(p.x, p.y)) {
340
341 emulatedSource.clearSelection();
342 emulatedSource.setSelectionStart(p.x, p.y, e.getButton());
343
344 repaint();
345
346 return true;
347 }
348
349 return false;
350 }
351
352 public boolean onMouseClicked(MouseEvent e) {
353 return isOnEmulatedText(e);
354 }
355 public boolean onMouseDragged(MouseEvent e) {
356
357 Point p = convertToExpediteeSpace(e.getLocationOnScreen());
358
359 // If so and the event really comes from its parent ...
360 if (isOnEmulatedText(p.x, p.y)) {
361
362 Browser._theBrowser.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
363
364 emulatedSource.setSelectionEnd(p.x, p.y);
365 repaint();
366
367 return true;
368 }
369
370 return false;
371 }
372
373 public void addTextChangeListener(TextChangeListener listener) {
374 assert(listener != null);
375 if (!textChangeListeners.contains(listener))
376 textChangeListeners.add(listener);
377 }
378
379 public void lostFocus() {
380 Browser._theBrowser.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
381
382 boolean dirty = false;
383 if (emulatedSource.hasSelection()) {
384 emulatedSource.clearSelection();
385 dirty = true;
386 }
387
388 if (emulatedSource.getHighlightMode() != HighlightMode.None) {
389 emulatedSource.setHighlightMode(HighlightMode.None);
390 dirty = true;
391 }
392
393 if (popup != null &&
394 PopupManager.getInstance().isShowing(popup)) {
395 PopupManager.getInstance().hidePopup(popup);
396 }
397
398 if (dirty) repaint();
399
400 }
401
402 /**
403 * Sets the text and fires a text changed event if the text has changed.
404 *
405 * @param text
406 * Null is allowed
407 */
408 public void setText(String text) {
409 if (text == null) text = "";
410
411 if (emulatedSource.getText().equals(text)) return;
412
413 invalidate();
414
415 this.emulatedSource.setText(text);
416
417 invalidate();
418
419 fireTextChanged();
420 }
421
422 public String getText() {
423 return emulatedSource.getText();
424 }
425
426 private void fireTextChanged() {
427
428 for (TextChangeListener listener : textChangeListeners)
429 listener.onTextChanged(this, this.emulatedSource.getText());
430
431 }
432
433 public void setBackgroundColor(Color c) {
434 emulatedSource.setBackgroundColor(c);
435 }
436
437 private void repaint() {
438
439 if (popup != null && PopupManager.getInstance().isShowing(popup)) {
440 popup.invalidateAppearance();
441 FrameGraphics.refresh(true);
442 } else {
443 invalidate();
444 FrameGraphics.refresh(true);
445 }
446 }
447
448 private void invalidate() {
449 for (Rectangle r : emulatedSource.getDrawingArea())
450 FrameGraphics.invalidateArea(r);
451 }
452
453 public void paint(Graphics g) {
454
455 if ((popup != null && PopupManager.getInstance().isShowing(popup)) ||
456 !parentComponant.isShowing())
457 return;
458
459 Point pos;
460 try {
461 pos = this.convertToExpediteeSpace(parentComponant.getLocationOnScreen());
462 } catch (Exception e) {
463 e.printStackTrace();
464 return;
465 }
466
467 paint( g,
468 pos.x + masterOffset.x,
469 pos.y + masterOffset.y,
470 parentComponant.getWidth() - masterOffset.x);
471 }
472
473 private void paint(Graphics g, int parentX, int parentY, int maxWidth) {
474
475 if (Browser._theBrowser == null) return;
476
477 /*int xoffset = (emulatedSource.getLink() == null) ? 6 : 18;
478 int yoffset = emulatedSource.getBoundsHeight() - 3;
479 parentX += xoffset;
480 parentY += yoffset;*/
481
482 // Set the emulated position according to the parent position - the parent position
483 // can be from the popup or the actual parent componant
484 if (emulatedSource.getX() != parentX || emulatedSource.getY() != parentY)
485 emulatedSource.setPosition(parentX, parentY);
486
487 Shape saveClip = g.getClip();
488
489 if (maxWidth > 0) {
490
491 Area clip = FrameGraphics.getCurrentClip();
492
493 Area tmpClip = (clip != null) ? clip :
494 new Area(new Rectangle(0, 0,
495 Browser._theBrowser.getContentPane().getWidth(),
496 Browser._theBrowser.getContentPane().getHeight()));
497
498 /* tmpClip.intersect(new Area(new Rectangle(0, 0,
499 parentX + maxWidth - xoffset, Browser._theBrowser.getContentPane().getHeight()
500 )));*/
501
502 tmpClip.intersect(new Area(new Rectangle(0, 0,
503 parentX + maxWidth, Browser._theBrowser.getContentPane().getHeight()
504 )));
505
506 // No intersection
507 if (!tmpClip.isEmpty())
508 g.setClip(tmpClip);
509
510 }
511
512 emulatedSource.paint((Graphics2D)g);
513
514 // Restore
515 if (maxWidth > 0) {
516 g.setClip(saveClip);
517 }
518
519 // Restore emulated position for consuming mouse movements
520 try {
521 Point pos = this.convertToExpediteeSpace(parentComponant.getLocationOnScreen());
522 emulatedSource.setPosition(
523 pos.x + masterOffset.x,
524 pos.y + masterOffset.y);
525 } catch (IllegalComponentStateException ex) { // Cannot fix this .. this whole class needs revising
526 ex.printStackTrace();
527 }
528 }
529
530 public interface TextChangeListener
531 {
532 public void onTextChanged(Object source, String newLabel);
533 }
534
535 private class LabelEditPopup extends Popup implements KeyListener, MouseListener, MouseMotionListener {
536
537 private static final long serialVersionUID = 1L;
538
539 LabelEditPopup() {
540 super.setAudoHide(true);
541 super.setConsumeBackClick(false);
542 super.setBorderThickness(0);
543
544 updateSize();
545
546 addKeyListener(this);
547 addMouseListener(this);
548 addMouseMotionListener(this);
549 }
550
551 public void updateSize() {
552 setSize(emulatedSource.getBoundsWidth() + 4, emulatedSource.getBoundsHeight() + 4);
553 }
554
555
556 public void keyPressed(KeyEvent e) {
557 onKeyPressed(e, LabelEditPopup.this);
558 updateSize();
559 }
560
561 public void keyReleased(KeyEvent e) {
562 }
563
564
565 public void keyTyped(KeyEvent e) {
566 }
567
568
569 public void mouseClicked(MouseEvent e) {
570 }
571
572 public void mouseEntered(MouseEvent e) {
573 }
574
575 public void mouseExited(MouseEvent e) {
576 }
577
578 public void mousePressed(MouseEvent e) {
579 onMousePressed(e);
580 }
581
582 public void mouseReleased(MouseEvent e) {
583 onMouseReleased(e);
584 updateSize();
585 }
586
587
588 public void mouseDragged(MouseEvent e) {
589 onMouseDragged(e);
590 }
591
592 public void mouseMoved(MouseEvent e) {
593
594 }
595
596 @Override
597 public void onHide() {
598 super.onHide();
599 }
600
601 @Override
602 public void onShow() {
603 super.onShow();
604 }
605
606
607
608 @Override
609 public void paint(Graphics g) {
610
611 EmulatedTextItem.this.paint(g, 7, 16, -1);
612
613 }
614
615 }
616
617 private class RestrictedTextItem extends Text {
618
619 private Color selectionColor = null;
620 private int selectionMouseButton = -1;
621
622 RestrictedTextItem() {
623 super(1);
624 }
625
626 private void updateSelection(int mouseButton) {
627 selectionColor = super.getSelectionColor(mouseButton);
628 selectionMouseButton = mouseButton;
629 }
630
631 public void setSelectionStart(float mouseX, float mouseY, int mouseButton) {
632 super.setSelectionStart(mouseX, mouseY);
633 updateSelection(mouseButton);
634 }
635
636
637
638 @Override
639 protected Color getSelectionColor(int mouseButton) {
640
641 if (selectionColor != null)
642 return selectionColor;
643
644 else return super.getSelectionColor(mouseButton);
645
646 }
647
648
649 }
650}
Note: See TracBrowser for help on using the repository browser.