1 /*
2 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25 package javax.swing.plaf.basic;
26
27 import java.util;
28 import java.awt;
29 import java.awt.event;
30 import java.awt.font;
31 import java.awt.datatransfer;
32 import java.awt.dnd;
33 import java.awt.im.InputContext;
34 import java.beans;
35 import java.io;
36 import java.net;
37 import javax.swing;
38 import javax.swing.plaf;
39 import javax.swing.text;
40 import javax.swing.event;
41 import javax.swing.border.Border;
42 import javax.swing.plaf.UIResource;
43 import sun.swing.DefaultLookup;
44 import sun.awt.AppContext;
45 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
46
47 /**
48 * <p>
49 * Basis of a text components look-and-feel. This provides the
50 * basic editor view and controller services that may be useful
51 * when creating a look-and-feel for an extension of
52 * <code>JTextComponent</code>.
53 * <p>
54 * Most state is held in the associated <code>JTextComponent</code>
55 * as bound properties, and the UI installs default values for the
56 * various properties. This default will install something for
57 * all of the properties. Typically, a LAF implementation will
58 * do more however. At a minimum, a LAF would generally install
59 * key bindings.
60 * <p>
61 * This class also provides some concurrency support if the
62 * <code>Document</code> associated with the JTextComponent is a subclass of
63 * <code>AbstractDocument</code>. Access to the View (or View hierarchy) is
64 * serialized between any thread mutating the model and the Swing
65 * event thread (which is expected to render, do model/view coordinate
66 * translation, etc). <em>Any access to the root view should first
67 * acquire a read-lock on the AbstractDocument and release that lock
68 * in a finally block.</em>
69 * <p>
70 * An important method to define is the {@link #getPropertyPrefix} method
71 * which is used as the basis of the keys used to fetch defaults
72 * from the UIManager. The string should reflect the type of
73 * TextUI (eg. TextField, TextArea, etc) without the particular
74 * LAF part of the name (eg Metal, Motif, etc).
75 * <p>
76 * To build a view of the model, one of the following strategies
77 * can be employed.
78 * <ol>
79 * <li>
80 * One strategy is to simply redefine the
81 * ViewFactory interface in the UI. By default, this UI itself acts
82 * as the factory for View implementations. This is useful
83 * for simple factories. To do this reimplement the
84 * {@link #create} method.
85 * <li>
86 * A common strategy for creating more complex types of documents
87 * is to have the EditorKit implementation return a factory. Since
88 * the EditorKit ties all of the pieces necessary to maintain a type
89 * of document, the factory is typically an important part of that
90 * and should be produced by the EditorKit implementation.
91 * </ol>
92 * <p>
93 * <strong>Warning:</strong>
94 * Serialized objects of this class will not be compatible with
95 * future Swing releases. The current serialization support is
96 * appropriate for short term storage or RMI between applications running
97 * the same version of Swing. As of 1.4, support for long term storage
98 * of all JavaBeans<sup><font size="-2">TM</font></sup>
99 * has been added to the <code>java.beans</code> package.
100 * Please see {@link java.beans.XMLEncoder}.
101 *
102 * @author Timothy Prinzing
103 * @author Shannon Hickey (drag and drop)
104 */
105 public abstract class BasicTextUI extends TextUI implements ViewFactory {
106
107 /**
108 * Creates a new UI.
109 */
110 public BasicTextUI() {
111 painted = false;
112 }
113
114 /**
115 * Creates the object to use for a caret. By default an
116 * instance of BasicCaret is created. This method
117 * can be redefined to provide something else that implements
118 * the InputPosition interface or a subclass of JCaret.
119 *
120 * @return the caret object
121 */
122 protected Caret createCaret() {
123 return new BasicCaret();
124 }
125
126 /**
127 * Creates the object to use for adding highlights. By default
128 * an instance of BasicHighlighter is created. This method
129 * can be redefined to provide something else that implements
130 * the Highlighter interface or a subclass of DefaultHighlighter.
131 *
132 * @return the highlighter
133 */
134 protected Highlighter createHighlighter() {
135 return new BasicHighlighter();
136 }
137
138 /**
139 * Fetches the name of the keymap that will be installed/used
140 * by default for this UI. This is implemented to create a
141 * name based upon the classname. The name is the the name
142 * of the class with the package prefix removed.
143 *
144 * @return the name
145 */
146 protected String getKeymapName() {
147 String nm = getClass().getName();
148 int index = nm.lastIndexOf('.');
149 if (index >= 0) {
150 nm = nm.substring(index+1, nm.length());
151 }
152 return nm;
153 }
154
155 /**
156 * Creates the keymap to use for the text component, and installs
157 * any necessary bindings into it. By default, the keymap is
158 * shared between all instances of this type of TextUI. The
159 * keymap has the name defined by the getKeymapName method. If the
160 * keymap is not found, then DEFAULT_KEYMAP from JTextComponent is used.
161 * <p>
162 * The set of bindings used to create the keymap is fetched
163 * from the UIManager using a key formed by combining the
164 * {@link #getPropertyPrefix} method
165 * and the string <code>.keyBindings</code>. The type is expected
166 * to be <code>JTextComponent.KeyBinding[]</code>.
167 *
168 * @return the keymap
169 * @see #getKeymapName
170 * @see javax.swing.text.JTextComponent
171 */
172 protected Keymap createKeymap() {
173 String nm = getKeymapName();
174 Keymap map = JTextComponent.getKeymap(nm);
175 if (map == null) {
176 Keymap parent = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
177 map = JTextComponent.addKeymap(nm, parent);
178 String prefix = getPropertyPrefix();
179 Object o = DefaultLookup.get(editor, this,
180 prefix + ".keyBindings");
181 if ((o != null) && (o instanceof JTextComponent.KeyBinding[])) {
182 JTextComponent.KeyBinding[] bindings = (JTextComponent.KeyBinding[]) o;
183 JTextComponent.loadKeymap(map, bindings, getComponent().getActions());
184 }
185 }
186 return map;
187 }
188
189 /**
190 * This method gets called when a bound property is changed
191 * on the associated JTextComponent. This is a hook
192 * which UI implementations may change to reflect how the
193 * UI displays bound properties of JTextComponent subclasses.
194 * This is implemented to do nothing (i.e. the response to
195 * properties in JTextComponent itself are handled prior
196 * to calling this method).
197 *
198 * This implementation updates the background of the text
199 * component if the editable and/or enabled state changes.
200 *
201 * @param evt the property change event
202 */
203 protected void propertyChange(PropertyChangeEvent evt) {
204 if (evt.getPropertyName().equals("editable") ||
205 evt.getPropertyName().equals("enabled")) {
206
207 updateBackground((JTextComponent)evt.getSource());
208 }
209 }
210
211 /**
212 * Updates the background of the text component based on whether the
213 * text component is editable and/or enabled.
214 *
215 * @param c the JTextComponent that needs its background color updated
216 */
217 private void updateBackground(JTextComponent c) {
218 // This is a temporary workaround.
219 // This code does not correctly deal with Synth (Synth doesn't use
220 // properties like this), nor does it deal with the situation where
221 // the developer grabs the color from a JLabel and sets it as
222 // the background for a JTextArea in all look and feels. The problem
223 // scenario results if the Color obtained for the Label and TextArea
224 // is ==, which is the case for the windows look and feel.
225 // Until an appropriate solution is found, the code is being
226 // reverted to what it was before the original fix.
227 if (this instanceof sun.swing.plaf.synth.SynthUI ||
228 (c instanceof JTextArea)) {
229 return;
230 }
231 Color background = c.getBackground();
232 if (background instanceof UIResource) {
233 String prefix = getPropertyPrefix();
234
235 Color disabledBG =
236 DefaultLookup.getColor(c, this, prefix + ".disabledBackground", null);
237 Color inactiveBG =
238 DefaultLookup.getColor(c, this, prefix + ".inactiveBackground", null);
239 Color bg =
240 DefaultLookup.getColor(c, this, prefix + ".background", null);
241
242 /* In an ideal situation, the following check would not be necessary
243 * and we would replace the color any time the previous color was a
244 * UIResouce. However, it turns out that there is existing code that
245 * uses the following inadvisable pattern to turn a text area into
246 * what appears to be a multi-line label:
247 *
248 * JLabel label = new JLabel();
249 * JTextArea area = new JTextArea();
250 * area.setBackground(label.getBackground());
251 * area.setEditable(false);
252 *
253 * JLabel's default background is a UIResource. As such, just
254 * checking for UIResource would have us always changing the
255 * background away from what the developer wanted.
256 *
257 * Therefore, for JTextArea/JEditorPane, we'll additionally check
258 * that the color we're about to replace matches one that was
259 * installed by us from the UIDefaults.
260 */
261 if ((c instanceof JTextArea || c instanceof JEditorPane)
262 && background != disabledBG
263 && background != inactiveBG
264 && background != bg) {
265
266 return;
267 }
268
269 Color newColor = null;
270 if (!c.isEnabled()) {
271 newColor = disabledBG;
272 }
273 if (newColor == null && !c.isEditable()) {
274 newColor = inactiveBG;
275 }
276 if (newColor == null) {
277 newColor = bg;
278 }
279 if (newColor != null && newColor != background) {
280 c.setBackground(newColor);
281 }
282 }
283 }
284
285 /**
286 * Gets the name used as a key to look up properties through the
287 * UIManager. This is used as a prefix to all the standard
288 * text properties.
289 *
290 * @return the name
291 */
292 protected abstract String getPropertyPrefix();
293
294 /**
295 * Initializes component properties, e.g. font, foreground,
296 * background, caret color, selection color, selected text color,
297 * disabled text color, and border color. The font, foreground, and
298 * background properties are only set if their current value is either null
299 * or a UIResource, other properties are set if the current
300 * value is null.
301 *
302 * @see #uninstallDefaults
303 * @see #installUI
304 */
305 protected void installDefaults()
306 {
307 String prefix = getPropertyPrefix();
308 Font f = editor.getFont();
309 if ((f == null) || (f instanceof UIResource)) {
310 editor.setFont(UIManager.getFont(prefix + ".font"));
311 }
312
313 Color bg = editor.getBackground();
314 if ((bg == null) || (bg instanceof UIResource)) {
315 editor.setBackground(UIManager.getColor(prefix + ".background"));
316 }
317
318 Color fg = editor.getForeground();
319 if ((fg == null) || (fg instanceof UIResource)) {
320 editor.setForeground(UIManager.getColor(prefix + ".foreground"));
321 }
322
323 Color color = editor.getCaretColor();
324 if ((color == null) || (color instanceof UIResource)) {
325 editor.setCaretColor(UIManager.getColor(prefix + ".caretForeground"));
326 }
327
328 Color s = editor.getSelectionColor();
329 if ((s == null) || (s instanceof UIResource)) {
330 editor.setSelectionColor(UIManager.getColor(prefix + ".selectionBackground"));
331 }
332
333 Color sfg = editor.getSelectedTextColor();
334 if ((sfg == null) || (sfg instanceof UIResource)) {
335 editor.setSelectedTextColor(UIManager.getColor(prefix + ".selectionForeground"));
336 }
337
338 Color dfg = editor.getDisabledTextColor();
339 if ((dfg == null) || (dfg instanceof UIResource)) {
340 editor.setDisabledTextColor(UIManager.getColor(prefix + ".inactiveForeground"));
341 }
342
343 Border b = editor.getBorder();
344 if ((b == null) || (b instanceof UIResource)) {
345 editor.setBorder(UIManager.getBorder(prefix + ".border"));
346 }
347
348 Insets margin = editor.getMargin();
349 if (margin == null || margin instanceof UIResource) {
350 editor.setMargin(UIManager.getInsets(prefix + ".margin"));
351 }
352
353 updateCursor();
354 }
355
356 private void installDefaults2() {
357 editor.addMouseListener(dragListener);
358 editor.addMouseMotionListener(dragListener);
359
360 String prefix = getPropertyPrefix();
361
362 Caret caret = editor.getCaret();
363 if (caret == null || caret instanceof UIResource) {
364 caret = createCaret();
365 editor.setCaret(caret);
366
367 int rate = DefaultLookup.getInt(getComponent(), this, prefix + ".caretBlinkRate", 500);
368 caret.setBlinkRate(rate);
369 }
370
371 Highlighter highlighter = editor.getHighlighter();
372 if (highlighter == null || highlighter instanceof UIResource) {
373 editor.setHighlighter(createHighlighter());
374 }
375
376 TransferHandler th = editor.getTransferHandler();
377 if (th == null || th instanceof UIResource) {
378 editor.setTransferHandler(getTransferHandler());
379 }
380 }
381
382 /**
383 * Sets the component properties that haven't been explicitly overridden to
384 * null. A property is considered overridden if its current value
385 * is not a UIResource.
386 *
387 * @see #installDefaults
388 * @see #uninstallUI
389 */
390 protected void uninstallDefaults()
391 {
392 editor.removeMouseListener(dragListener);
393 editor.removeMouseMotionListener(dragListener);
394
395 if (editor.getCaretColor() instanceof UIResource) {
396 editor.setCaretColor(null);
397 }
398
399 if (editor.getSelectionColor() instanceof UIResource) {
400 editor.setSelectionColor(null);
401 }
402
403 if (editor.getDisabledTextColor() instanceof UIResource) {
404 editor.setDisabledTextColor(null);
405 }
406
407 if (editor.getSelectedTextColor() instanceof UIResource) {
408 editor.setSelectedTextColor(null);
409 }
410
411 if (editor.getBorder() instanceof UIResource) {
412 editor.setBorder(null);
413 }
414
415 if (editor.getMargin() instanceof UIResource) {
416 editor.setMargin(null);
417 }
418
419 if (editor.getCaret() instanceof UIResource) {
420 editor.setCaret(null);
421 }
422
423 if (editor.getHighlighter() instanceof UIResource) {
424 editor.setHighlighter(null);
425 }
426
427 if (editor.getTransferHandler() instanceof UIResource) {
428 editor.setTransferHandler(null);
429 }
430
431 if (editor.getCursor() instanceof UIResource) {
432 editor.setCursor(null);
433 }
434 }
435
436 /**
437 * Installs listeners for the UI.
438 */
439 protected void installListeners() {
440 }
441
442 /**
443 * Uninstalls listeners for the UI.
444 */
445 protected void uninstallListeners() {
446 }
447
448 protected void installKeyboardActions() {
449 // backward compatibility support... keymaps for the UI
450 // are now installed in the more friendly input map.
451 editor.setKeymap(createKeymap());
452
453 InputMap km = getInputMap();
454 if (km != null) {
455 SwingUtilities.replaceUIInputMap(editor, JComponent.WHEN_FOCUSED,
456 km);
457 }
458
459 ActionMap map = getActionMap();
460 if (map != null) {
461 SwingUtilities.replaceUIActionMap(editor, map);
462 }
463
464 updateFocusAcceleratorBinding(false);
465 }
466
467 /**
468 * Get the InputMap to use for the UI.
469 */
470 InputMap getInputMap() {
471 InputMap map = new InputMapUIResource();
472
473 InputMap shared =
474 (InputMap)DefaultLookup.get(editor, this,
475 getPropertyPrefix() + ".focusInputMap");
476 if (shared != null) {
477 map.setParent(shared);
478 }
479 return map;
480 }
481
482 /**
483 * Invoked when the focus accelerator changes, this will update the
484 * key bindings as necessary.
485 */
486 void updateFocusAcceleratorBinding(boolean changed) {
487 char accelerator = editor.getFocusAccelerator();
488
489 if (changed || accelerator != '\0') {
490 InputMap km = SwingUtilities.getUIInputMap
491 (editor, JComponent.WHEN_IN_FOCUSED_WINDOW);
492
493 if (km == null && accelerator != '\0') {
494 km = new ComponentInputMapUIResource(editor);
495 SwingUtilities.replaceUIInputMap(editor, JComponent.
496 WHEN_IN_FOCUSED_WINDOW, km);
497 ActionMap am = getActionMap();
498 SwingUtilities.replaceUIActionMap(editor, am);
499 }
500 if (km != null) {
501 km.clear();
502 if (accelerator != '\0') {
503 km.put(KeyStroke.getKeyStroke(accelerator,
504 ActionEvent.ALT_MASK),
505 "requestFocus");
506 }
507 }
508 }
509 }
510
511
512 /**
513 * Invoked when editable property is changed.
514 *
515 * removing 'TAB' and 'SHIFT-TAB' from traversalKeysSet in case
516 * editor is editable
517 * adding 'TAB' and 'SHIFT-TAB' to traversalKeysSet in case
518 * editor is non editable
519 */
520
521 void updateFocusTraversalKeys() {
522 /*
523 * Fix for 4514331 Non-editable JTextArea and similar
524 * should allow Tab to keyboard - accessibility
525 */
526 EditorKit editorKit = getEditorKit(editor);
527 if ( editorKit != null
528 && editorKit instanceof DefaultEditorKit) {
529 Set storedForwardTraversalKeys = editor.
530 getFocusTraversalKeys(KeyboardFocusManager.
531 FORWARD_TRAVERSAL_KEYS);
532 Set storedBackwardTraversalKeys = editor.
533 getFocusTraversalKeys(KeyboardFocusManager.
534 BACKWARD_TRAVERSAL_KEYS);
535 Set forwardTraversalKeys =
536 new HashSet(storedForwardTraversalKeys);
537 Set backwardTraversalKeys =
538 new HashSet(storedBackwardTraversalKeys);
539 if (editor.isEditable()) {
540 forwardTraversalKeys.
541 remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0));
542 backwardTraversalKeys.
543 remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
544 InputEvent.SHIFT_MASK));
545 } else {
546 forwardTraversalKeys.add(KeyStroke.
547 getKeyStroke(KeyEvent.VK_TAB, 0));
548 backwardTraversalKeys.
549 add(KeyStroke.
550 getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK));
551 }
552 LookAndFeel.installProperty(editor,
553 "focusTraversalKeysForward",
554 forwardTraversalKeys);
555 LookAndFeel.installProperty(editor,
556 "focusTraversalKeysBackward",
557 backwardTraversalKeys);
558 }
559
560 }
561
562 /**
563 * As needed updates cursor for the target editor.
564 */
565 private void updateCursor() {
566 if ((! editor.isCursorSet())
567 || editor.getCursor() instanceof UIResource) {
568 Cursor cursor = (editor.isEditable()) ? textCursor : null;
569 editor.setCursor(cursor);
570 }
571 }
572
573 /**
574 * Returns the <code>TransferHandler</code> that will be installed if
575 * their isn't one installed on the <code>JTextComponent</code>.
576 */
577 TransferHandler getTransferHandler() {
578 return defaultTransferHandler;
579 }
580
581 /**
582 * Fetch an action map to use.
583 */
584 ActionMap getActionMap() {
585 String mapName = getPropertyPrefix() + ".actionMap";
586 ActionMap map = (ActionMap)UIManager.get(mapName);
587
588 if (map == null) {
589 map = createActionMap();
590 if (map != null) {
591 UIManager.getLookAndFeelDefaults().put(mapName, map);
592 }
593 }
594 ActionMap componentMap = new ActionMapUIResource();
595 componentMap.put("requestFocus", new FocusAction());
596 /*
597 * fix for bug 4515750
598 * JTextField & non-editable JTextArea bind return key - default btn not accessible
599 *
600 * Wrap the return action so that it is only enabled when the
601 * component is editable. This allows the default button to be
602 * processed when the text component has focus and isn't editable.
603 *
604 */
605 if (getEditorKit(editor) instanceof DefaultEditorKit) {
606 if (map != null) {
607 Object obj = map.get(DefaultEditorKit.insertBreakAction);
608 if (obj != null
609 && obj instanceof DefaultEditorKit.InsertBreakAction) {
610 Action action = new TextActionWrapper((TextAction)obj);
611 componentMap.put(action.getValue(Action.NAME),action);
612 }
613 }
614 }
615 if (map != null) {
616 componentMap.setParent(map);
617 }
618 return componentMap;
619 }
620
621 /**
622 * Create a default action map. This is basically the
623 * set of actions found exported by the component.
624 */
625 ActionMap createActionMap() {
626 ActionMap map = new ActionMapUIResource();
627 Action[] actions = editor.getActions();
628 //System.out.println("building map for UI: " + getPropertyPrefix());
629 int n = actions.length;
630 for (int i = 0; i < n; i++) {
631 Action a = actions[i];
632 map.put(a.getValue(Action.NAME), a);
633 //System.out.println(" " + a.getValue(Action.NAME));
634 }
635 map.put(TransferHandler.getCutAction().getValue(Action.NAME),
636 TransferHandler.getCutAction());
637 map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
638 TransferHandler.getCopyAction());
639 map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
640 TransferHandler.getPasteAction());
641 return map;
642 }
643
644 protected void uninstallKeyboardActions() {
645 editor.setKeymap(null);
646 SwingUtilities.replaceUIInputMap(editor, JComponent.
647 WHEN_IN_FOCUSED_WINDOW, null);
648 SwingUtilities.replaceUIActionMap(editor, null);
649 }
650
651 /**
652 * Paints a background for the view. This will only be
653 * called if isOpaque() on the associated component is
654 * true. The default is to paint the background color
655 * of the component.
656 *
657 * @param g the graphics context
658 */
659 protected void paintBackground(Graphics g) {
660 g.setColor(editor.getBackground());
661 g.fillRect(0, 0, editor.getWidth(), editor.getHeight());
662 }
663
664 /**
665 * Fetches the text component associated with this
666 * UI implementation. This will be null until
667 * the ui has been installed.
668 *
669 * @return the editor component
670 */
671 protected final JTextComponent getComponent() {
672 return editor;
673 }
674
675 /**
676 * Flags model changes.
677 * This is called whenever the model has changed.
678 * It is implemented to rebuild the view hierarchy
679 * to represent the default root element of the
680 * associated model.
681 */
682 protected void modelChanged() {
683 // create a view hierarchy
684 ViewFactory f = rootView.getViewFactory();
685 Document doc = editor.getDocument();
686 Element elem = doc.getDefaultRootElement();
687 setView(f.create(elem));
688 }
689
690 /**
691 * Sets the current root of the view hierarchy and calls invalidate().
692 * If there were any child components, they will be removed (i.e.
693 * there are assumed to have come from components embedded in views).
694 *
695 * @param v the root view
696 */
697 protected final void setView(View v) {
698 rootView.setView(v);
699 painted = false;
700 editor.revalidate();
701 editor.repaint();
702 }
703
704 /**
705 * Paints the interface safely with a guarantee that
706 * the model won't change from the view of this thread.
707 * This does the following things, rendering from
708 * back to front.
709 * <ol>
710 * <li>
711 * If the component is marked as opaque, the background
712 * is painted in the current background color of the
713 * component.
714 * <li>
715 * The highlights (if any) are painted.
716 * <li>
717 * The view hierarchy is painted.
718 * <li>
719 * The caret is painted.
720 * </ol>
721 *
722 * @param g the graphics context
723 */
724 protected void paintSafely(Graphics g) {
725 painted = true;
726 Highlighter highlighter = editor.getHighlighter();
727 Caret caret = editor.getCaret();
728
729 // paint the background
730 if (editor.isOpaque()) {
731 paintBackground(g);
732 }
733
734 // paint the highlights
735 if (highlighter != null) {
736 highlighter.paint(g);
737 }
738
739 // paint the view hierarchy
740 Rectangle alloc = getVisibleEditorRect();
741 if (alloc != null) {
742 rootView.paint(g, alloc);
743 }
744
745 // paint the caret
746 if (caret != null) {
747 caret.paint(g);
748 }
749
750 if (dropCaret != null) {
751 dropCaret.paint(g);
752 }
753 }
754
755 // --- ComponentUI methods --------------------------------------------
756
757 /**
758 * Installs the UI for a component. This does the following
759 * things.
760 * <ol>
761 * <li>
762 * Set the associated component to opaque (can be changed
763 * easily by a subclass or on JTextComponent directly),
764 * which is the most common case. This will cause the
765 * component's background color to be painted.
766 * <li>
767 * Install the default caret and highlighter into the
768 * associated component.
769 * <li>
770 * Attach to the editor and model. If there is no
771 * model, a default one is created.
772 * <li>
773 * create the view factory and the view hierarchy used
774 * to represent the model.
775 * </ol>
776 *
777 * @param c the editor component
778 * @see ComponentUI#installUI
779 */
780 public void installUI(JComponent c) {
781 if (c instanceof JTextComponent) {
782 editor = (JTextComponent) c;
783
784 // install defaults
785 installDefaults();
786 installDefaults2();
787
788 // common case is background painted... this can
789 // easily be changed by subclasses or from outside
790 // of the component.
791 LookAndFeel.installProperty(editor, "opaque", Boolean.TRUE);
792 LookAndFeel.installProperty(editor, "autoscrolls", Boolean.TRUE);
793
794 // attach to the model and editor
795 editor.addPropertyChangeListener(updateHandler);
796 Document doc = editor.getDocument();
797 if (doc == null) {
798 // no model, create a default one. This will
799 // fire a notification to the updateHandler
800 // which takes care of the rest.
801 editor.setDocument(getEditorKit(editor).createDefaultDocument());
802 } else {
803 doc.addDocumentListener(updateHandler);
804 modelChanged();
805 }
806
807 // install keymap
808 installListeners();
809 installKeyboardActions();
810
811 LayoutManager oldLayout = editor.getLayout();
812 if ((oldLayout == null) || (oldLayout instanceof UIResource)) {
813 // by default, use default LayoutManger implementation that
814 // will position the components associated with a View object.
815 editor.setLayout(updateHandler);
816 }
817
818 updateBackground(editor);
819 } else {
820 throw new Error("TextUI needs JTextComponent");
821 }
822 }
823
824 /**
825 * Deinstalls the UI for a component. This removes the listeners,
826 * uninstalls the highlighter, removes views, and nulls out the keymap.
827 *
828 * @param c the editor component
829 * @see ComponentUI#uninstallUI
830 */
831 public void uninstallUI(JComponent c) {
832 // detach from the model
833 editor.removePropertyChangeListener(updateHandler);
834 editor.getDocument().removeDocumentListener(updateHandler);
835
836 // view part
837 painted = false;
838 uninstallDefaults();
839 rootView.setView(null);
840 c.removeAll();
841 LayoutManager lm = c.getLayout();
842 if (lm instanceof UIResource) {
843 c.setLayout(null);
844 }
845
846 // controller part
847 uninstallKeyboardActions();
848 uninstallListeners();
849
850 editor = null;
851 }
852
853 /**
854 * Superclass paints background in an uncontrollable way
855 * (i.e. one might want an image tiled into the background).
856 * To prevent this from happening twice, this method is
857 * reimplemented to simply paint.
858 * <p>
859 * <em>NOTE:</em> Superclass is also not thread-safe in
860 * it's rendering of the background, although that's not
861 * an issue with the default rendering.
862 */
863 public void update(Graphics g, JComponent c) {
864 paint(g, c);
865 }
866
867 /**
868 * Paints the interface. This is routed to the
869 * paintSafely method under the guarantee that
870 * the model won't change from the view of this thread
871 * while it's rendering (if the associated model is
872 * derived from AbstractDocument). This enables the
873 * model to potentially be updated asynchronously.
874 *
875 * @param g the graphics context
876 * @param c the editor component
877 */
878 public final void paint(Graphics g, JComponent c) {
879 if ((rootView.getViewCount() > 0) && (rootView.getView(0) != null)) {
880 Document doc = editor.getDocument();
881 if (doc instanceof AbstractDocument) {
882 ((AbstractDocument)doc).readLock();
883 }
884 try {
885 paintSafely(g);
886 } finally {
887 if (doc instanceof AbstractDocument) {
888 ((AbstractDocument)doc).readUnlock();
889 }
890 }
891 }
892 }
893
894 /**
895 * Gets the preferred size for the editor component. If the component
896 * has been given a size prior to receiving this request, it will
897 * set the size of the view hierarchy to reflect the size of the component
898 * before requesting the preferred size of the view hierarchy. This
899 * allows formatted views to format to the current component size before
900 * answering the request. Other views don't care about currently formatted
901 * size and give the same answer either way.
902 *
903 * @param c the editor component
904 * @return the size
905 */
906 public Dimension getPreferredSize(JComponent c) {
907 Document doc = editor.getDocument();
908 Insets i = c.getInsets();
909 Dimension d = c.getSize();
910
911 if (doc instanceof AbstractDocument) {
912 ((AbstractDocument)doc).readLock();
913 }
914 try {
915 if ((d.width > (i.left + i.right)) && (d.height > (i.top + i.bottom))) {
916 rootView.setSize(d.width - i.left - i.right, d.height - i.top - i.bottom);
917 }
918 else if (d.width == 0 && d.height == 0) {
919 // Probably haven't been layed out yet, force some sort of
920 // initial sizing.
921 rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
922 }
923 d.width = (int) Math.min((long) rootView.getPreferredSpan(View.X_AXIS) +
924 (long) i.left + (long) i.right, Integer.MAX_VALUE);
925 d.height = (int) Math.min((long) rootView.getPreferredSpan(View.Y_AXIS) +
926 (long) i.top + (long) i.bottom, Integer.MAX_VALUE);
927 } finally {
928 if (doc instanceof AbstractDocument) {
929 ((AbstractDocument)doc).readUnlock();
930 }
931 }
932 return d;
933 }
934
935 /**
936 * Gets the minimum size for the editor component.
937 *
938 * @param c the editor component
939 * @return the size
940 */
941 public Dimension getMinimumSize(JComponent c) {
942 Document doc = editor.getDocument();
943 Insets i = c.getInsets();
944 Dimension d = new Dimension();
945 if (doc instanceof AbstractDocument) {
946 ((AbstractDocument)doc).readLock();
947 }
948 try {
949 d.width = (int) rootView.getMinimumSpan(View.X_AXIS) + i.left + i.right;
950 d.height = (int) rootView.getMinimumSpan(View.Y_AXIS) + i.top + i.bottom;
951 } finally {
952 if (doc instanceof AbstractDocument) {
953 ((AbstractDocument)doc).readUnlock();
954 }
955 }
956 return d;
957 }
958
959 /**
960 * Gets the maximum size for the editor component.
961 *
962 * @param c the editor component
963 * @return the size
964 */
965 public Dimension getMaximumSize(JComponent c) {
966 Document doc = editor.getDocument();
967 Insets i = c.getInsets();
968 Dimension d = new Dimension();
969 if (doc instanceof AbstractDocument) {
970 ((AbstractDocument)doc).readLock();
971 }
972 try {
973 d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS) +
974 (long) i.left + (long) i.right, Integer.MAX_VALUE);
975 d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS) +
976 (long) i.top + (long) i.bottom, Integer.MAX_VALUE);
977 } finally {
978 if (doc instanceof AbstractDocument) {
979 ((AbstractDocument)doc).readUnlock();
980 }
981 }
982 return d;
983 }
984
985 // ---- TextUI methods -------------------------------------------
986
987
988 /**
989 * Gets the allocation to give the root View. Due
990 * to an unfortunate set of historical events this
991 * method is inappropriately named. The Rectangle
992 * returned has nothing to do with visibility.
993 * The component must have a non-zero positive size for
994 * this translation to be computed.
995 *
996 * @return the bounding box for the root view
997 */
998 protected Rectangle getVisibleEditorRect() {
999 Rectangle alloc = editor.getBounds();
1000 if ((alloc.width > 0) && (alloc.height > 0)) {
1001 alloc.x = alloc.y = 0;
1002 Insets insets = editor.getInsets();
1003 alloc.x += insets.left;
1004 alloc.y += insets.top;
1005 alloc.width -= insets.left + insets.right;
1006 alloc.height -= insets.top + insets.bottom;
1007 return alloc;
1008 }
1009 return null;
1010 }
1011
1012 /**
1013 * Converts the given location in the model to a place in
1014 * the view coordinate system.
1015 * The component must have a non-zero positive size for
1016 * this translation to be computed.
1017 *
1018 * @param tc the text component for which this UI is installed
1019 * @param pos the local location in the model to translate >= 0
1020 * @return the coordinates as a rectangle, null if the model is not painted
1021 * @exception BadLocationException if the given position does not
1022 * represent a valid location in the associated document
1023 * @see TextUI#modelToView
1024 */
1025 public Rectangle modelToView(JTextComponent tc, int pos) throws BadLocationException {
1026 return modelToView(tc, pos, Position.Bias.Forward);
1027 }
1028
1029 /**
1030 * Converts the given location in the model to a place in
1031 * the view coordinate system.
1032 * The component must have a non-zero positive size for
1033 * this translation to be computed.
1034 *
1035 * @param tc the text component for which this UI is installed
1036 * @param pos the local location in the model to translate >= 0
1037 * @return the coordinates as a rectangle, null if the model is not painted
1038 * @exception BadLocationException if the given position does not
1039 * represent a valid location in the associated document
1040 * @see TextUI#modelToView
1041 */
1042 public Rectangle modelToView(JTextComponent tc, int pos, Position.Bias bias) throws BadLocationException {
1043 Document doc = editor.getDocument();
1044 if (doc instanceof AbstractDocument) {
1045 ((AbstractDocument)doc).readLock();
1046 }
1047 try {
1048 Rectangle alloc = getVisibleEditorRect();
1049 if (alloc != null) {
1050 rootView.setSize(alloc.width, alloc.height);
1051 Shape s = rootView.modelToView(pos, alloc, bias);
1052 if (s != null) {
1053 return s.getBounds();
1054 }
1055 }
1056 } finally {
1057 if (doc instanceof AbstractDocument) {
1058 ((AbstractDocument)doc).readUnlock();
1059 }
1060 }
1061 return null;
1062 }
1063
1064 /**
1065 * Converts the given place in the view coordinate system
1066 * to the nearest representative location in the model.
1067 * The component must have a non-zero positive size for
1068 * this translation to be computed.
1069 *
1070 * @param tc the text component for which this UI is installed
1071 * @param pt the location in the view to translate. This
1072 * should be in the same coordinate system as the mouse events.
1073 * @return the offset from the start of the document >= 0,
1074 * -1 if not painted
1075 * @see TextUI#viewToModel
1076 */
1077 public int viewToModel(JTextComponent tc, Point pt) {
1078 return viewToModel(tc, pt, discardBias);
1079 }
1080
1081 /**
1082 * Converts the given place in the view coordinate system
1083 * to the nearest representative location in the model.
1084 * The component must have a non-zero positive size for
1085 * this translation to be computed.
1086 *
1087 * @param tc the text component for which this UI is installed
1088 * @param pt the location in the view to translate. This
1089 * should be in the same coordinate system as the mouse events.
1090 * @return the offset from the start of the document >= 0,
1091 * -1 if the component doesn't yet have a positive size.
1092 * @see TextUI#viewToModel
1093 */
1094 public int viewToModel(JTextComponent tc, Point pt,
1095 Position.Bias[] biasReturn) {
1096 int offs = -1;
1097 Document doc = editor.getDocument();
1098 if (doc instanceof AbstractDocument) {
1099 ((AbstractDocument)doc).readLock();
1100 }
1101 try {
1102 Rectangle alloc = getVisibleEditorRect();
1103 if (alloc != null) {
1104 rootView.setSize(alloc.width, alloc.height);
1105 offs = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn);
1106 }
1107 } finally {
1108 if (doc instanceof AbstractDocument) {
1109 ((AbstractDocument)doc).readUnlock();
1110 }
1111 }
1112 return offs;
1113 }
1114
1115 /**
1116 * {@inheritDoc}
1117 */
1118 public int getNextVisualPositionFrom(JTextComponent t, int pos,
1119 Position.Bias b, int direction, Position.Bias[] biasRet)
1120 throws BadLocationException{
1121 Document doc = editor.getDocument();
1122 if (doc instanceof AbstractDocument) {
1123 ((AbstractDocument)doc).readLock();
1124 }
1125 try {
1126 if (painted) {
1127 Rectangle alloc = getVisibleEditorRect();
1128 if (alloc != null) {
1129 rootView.setSize(alloc.width, alloc.height);
1130 }
1131 return rootView.getNextVisualPositionFrom(pos, b, alloc, direction,
1132 biasRet);
1133 }
1134 } finally {
1135 if (doc instanceof AbstractDocument) {
1136 ((AbstractDocument)doc).readUnlock();
1137 }
1138 }
1139 return -1;
1140 }
1141
1142 /**
1143 * Causes the portion of the view responsible for the
1144 * given part of the model to be repainted. Does nothing if
1145 * the view is not currently painted.
1146 *
1147 * @param tc the text component for which this UI is installed
1148 * @param p0 the beginning of the range >= 0
1149 * @param p1 the end of the range >= p0
1150 * @see TextUI#damageRange
1151 */
1152 public void damageRange(JTextComponent tc, int p0, int p1) {
1153 damageRange(tc, p0, p1, Position.Bias.Forward, Position.Bias.Backward);
1154 }
1155
1156 /**
1157 * Causes the portion of the view responsible for the
1158 * given part of the model to be repainted.
1159 *
1160 * @param p0 the beginning of the range >= 0
1161 * @param p1 the end of the range >= p0
1162 */
1163 public void damageRange(JTextComponent t, int p0, int p1,
1164 Position.Bias p0Bias, Position.Bias p1Bias) {
1165 if (painted) {
1166 Rectangle alloc = getVisibleEditorRect();
1167 if (alloc != null) {
1168 Document doc = t.getDocument();
1169 if (doc instanceof AbstractDocument) {
1170 ((AbstractDocument)doc).readLock();
1171 }
1172 try {
1173 rootView.setSize(alloc.width, alloc.height);
1174 Shape toDamage = rootView.modelToView(p0, p0Bias,
1175 p1, p1Bias, alloc);
1176 Rectangle rect = (toDamage instanceof Rectangle) ?
1177 (Rectangle)toDamage : toDamage.getBounds();
1178 editor.repaint(rect.x, rect.y, rect.width, rect.height);
1179 } catch (BadLocationException e) {
1180 } finally {
1181 if (doc instanceof AbstractDocument) {
1182 ((AbstractDocument)doc).readUnlock();
1183 }
1184 }
1185 }
1186 }
1187 }
1188
1189 /**
1190 * Fetches the EditorKit for the UI.
1191 *
1192 * @param tc the text component for which this UI is installed
1193 * @return the editor capabilities
1194 * @see TextUI#getEditorKit
1195 */
1196 public EditorKit getEditorKit(JTextComponent tc) {
1197 return defaultKit;
1198 }
1199
1200 /**
1201 * Fetches a View with the allocation of the associated
1202 * text component (i.e. the root of the hierarchy) that
1203 * can be traversed to determine how the model is being
1204 * represented spatially.
1205 * <p>
1206 * <font color=red><b>NOTE:</b>The View hierarchy can
1207 * be traversed from the root view, and other things
1208 * can be done as well. Things done in this way cannot
1209 * be protected like simple method calls through the TextUI.
1210 * Therefore, proper operation in the presence of concurrency
1211 * must be arranged by any logic that calls this method!
1212 * </font>
1213 *
1214 * @param tc the text component for which this UI is installed
1215 * @return the view
1216 * @see TextUI#getRootView
1217 */
1218 public View getRootView(JTextComponent tc) {
1219 return rootView;
1220 }
1221
1222
1223 /**
1224 * Returns the string to be used as the tooltip at the passed in location.
1225 * This forwards the method onto the root View.
1226 *
1227 * @see javax.swing.text.JTextComponent#getToolTipText
1228 * @see javax.swing.text.View#getToolTipText
1229 * @since 1.4
1230 */
1231 public String getToolTipText(JTextComponent t, Point pt) {
1232 if (!painted) {
1233 return null;
1234 }
1235 Document doc = editor.getDocument();
1236 String tt = null;
1237 Rectangle alloc = getVisibleEditorRect();
1238
1239 if (alloc != null) {
1240 if (doc instanceof AbstractDocument) {
1241 ((AbstractDocument)doc).readLock();
1242 }
1243 try {
1244 tt = rootView.getToolTipText(pt.x, pt.y, alloc);
1245 } finally {
1246 if (doc instanceof AbstractDocument) {
1247 ((AbstractDocument)doc).readUnlock();
1248 }
1249 }
1250 }
1251 return tt;
1252 }
1253
1254 // --- ViewFactory methods ------------------------------
1255
1256 /**
1257 * Creates a view for an element.
1258 * If a subclass wishes to directly implement the factory
1259 * producing the view(s), it should reimplement this
1260 * method. By default it simply returns null indicating
1261 * it is unable to represent the element.
1262 *
1263 * @param elem the element
1264 * @return the view
1265 */
1266 public View create(Element elem) {
1267 return null;
1268 }
1269
1270 /**
1271 * Creates a view for an element.
1272 * If a subclass wishes to directly implement the factory
1273 * producing the view(s), it should reimplement this
1274 * method. By default it simply returns null indicating
1275 * it is unable to represent the part of the element.
1276 *
1277 * @param elem the element
1278 * @param p0 the starting offset >= 0
1279 * @param p1 the ending offset >= p0
1280 * @return the view
1281 */
1282 public View create(Element elem, int p0, int p1) {
1283 return null;
1284 }
1285
1286 public static class BasicCaret extends DefaultCaret implements UIResource {}
1287
1288 public static class BasicHighlighter extends DefaultHighlighter implements UIResource {}
1289
1290 static class BasicCursor extends Cursor implements UIResource {
1291 BasicCursor(int type) {
1292 super(type);
1293 }
1294
1295 BasicCursor(String name) {
1296 super(name);
1297 }
1298 }
1299
1300 private static BasicCursor textCursor = new BasicCursor(Cursor.TEXT_CURSOR);
1301 // ----- member variables ---------------------------------------
1302
1303 private static final EditorKit defaultKit = new DefaultEditorKit();
1304 transient JTextComponent editor;
1305 transient boolean painted;
1306 transient RootView rootView = new RootView();
1307 transient UpdateHandler updateHandler = new UpdateHandler();
1308 private static final TransferHandler defaultTransferHandler = new TextTransferHandler();
1309 private final DragListener dragListener = getDragListener();
1310 private static final Position.Bias[] discardBias = new Position.Bias[1];
1311 private DefaultCaret dropCaret;
1312
1313 /**
1314 * Root view that acts as a gateway between the component
1315 * and the View hierarchy.
1316 */
1317 class RootView extends View {
1318
1319 RootView() {
1320 super(null);
1321 }
1322
1323 void setView(View v) {
1324 View oldView = view;
1325 view = null;
1326 if (oldView != null) {
1327 // get rid of back reference so that the old
1328 // hierarchy can be garbage collected.
1329 oldView.setParent(null);
1330 }
1331 if (v != null) {
1332 v.setParent(this);
1333 }
1334 view = v;
1335 }
1336
1337 /**
1338 * Fetches the attributes to use when rendering. At the root
1339 * level there are no attributes. If an attribute is resolved
1340 * up the view hierarchy this is the end of the line.
1341 */
1342 public AttributeSet getAttributes() {
1343 return null;
1344 }
1345
1346 /**
1347 * Determines the preferred span for this view along an axis.
1348 *
1349 * @param axis may be either X_AXIS or Y_AXIS
1350 * @return the span the view would like to be rendered into.
1351 * Typically the view is told to render into the span
1352 * that is returned, although there is no guarantee.
1353 * The parent may choose to resize or break the view.
1354 */
1355 public float getPreferredSpan(int axis) {
1356 if (view != null) {
1357 return view.getPreferredSpan(axis);
1358 }
1359 return 10;
1360 }
1361
1362 /**
1363 * Determines the minimum span for this view along an axis.
1364 *
1365 * @param axis may be either X_AXIS or Y_AXIS
1366 * @return the span the view would like to be rendered into.
1367 * Typically the view is told to render into the span
1368 * that is returned, although there is no guarantee.
1369 * The parent may choose to resize or break the view.
1370 */
1371 public float getMinimumSpan(int axis) {
1372 if (view != null) {
1373 return view.getMinimumSpan(axis);
1374 }
1375 return 10;
1376 }
1377
1378 /**
1379 * Determines the maximum span for this view along an axis.
1380 *
1381 * @param axis may be either X_AXIS or Y_AXIS
1382 * @return the span the view would like to be rendered into.
1383 * Typically the view is told to render into the span
1384 * that is returned, although there is no guarantee.
1385 * The parent may choose to resize or break the view.
1386 */
1387 public float getMaximumSpan(int axis) {
1388 return Integer.MAX_VALUE;
1389 }
1390
1391 /**
1392 * Specifies that a preference has changed.
1393 * Child views can call this on the parent to indicate that
1394 * the preference has changed. The root view routes this to
1395 * invalidate on the hosting component.
1396 * <p>
1397 * This can be called on a different thread from the
1398 * event dispatching thread and is basically unsafe to
1399 * propagate into the component. To make this safe,
1400 * the operation is transferred over to the event dispatching
1401 * thread for completion. It is a design goal that all view
1402 * methods be safe to call without concern for concurrency,
1403 * and this behavior helps make that true.
1404 *
1405 * @param child the child view
1406 * @param width true if the width preference has changed
1407 * @param height true if the height preference has changed
1408 */
1409 public void preferenceChanged(View child, boolean width, boolean height) {
1410 editor.revalidate();
1411 }
1412
1413 /**
1414 * Determines the desired alignment for this view along an axis.
1415 *
1416 * @param axis may be either X_AXIS or Y_AXIS
1417 * @return the desired alignment, where 0.0 indicates the origin
1418 * and 1.0 the full span away from the origin
1419 */
1420 public float getAlignment(int axis) {
1421 if (view != null) {
1422 return view.getAlignment(axis);
1423 }
1424 return 0;
1425 }
1426
1427 /**
1428 * Renders the view.
1429 *
1430 * @param g the graphics context
1431 * @param allocation the region to render into
1432 */
1433 public void paint(Graphics g, Shape allocation) {
1434 if (view != null) {
1435 Rectangle alloc = (allocation instanceof Rectangle) ?
1436 (Rectangle)allocation : allocation.getBounds();
1437 setSize(alloc.width, alloc.height);
1438 view.paint(g, allocation);
1439 }
1440 }
1441
1442 /**
1443 * Sets the view parent.
1444 *
1445 * @param parent the parent view
1446 */
1447 public void setParent(View parent) {
1448 throw new Error("Can't set parent on root view");
1449 }
1450
1451 /**
1452 * Returns the number of views in this view. Since
1453 * this view simply wraps the root of the view hierarchy
1454 * it has exactly one child.
1455 *
1456 * @return the number of views
1457 * @see #getView
1458 */
1459 public int getViewCount() {
1460 return 1;
1461 }
1462
1463 /**
1464 * Gets the n-th view in this container.
1465 *
1466 * @param n the number of the view to get
1467 * @return the view
1468 */
1469 public View getView(int n) {
1470 return view;
1471 }
1472
1473 /**
1474 * Returns the child view index representing the given position in
1475 * the model. This is implemented to return the index of the only
1476 * child.
1477 *
1478 * @param pos the position >= 0
1479 * @return index of the view representing the given position, or
1480 * -1 if no view represents that position
1481 * @since 1.3
1482 */
1483 public int getViewIndex(int pos, Position.Bias b) {
1484 return 0;
1485 }
1486
1487 /**
1488 * Fetches the allocation for the given child view.
1489 * This enables finding out where various views
1490 * are located, without assuming the views store
1491 * their location. This returns the given allocation
1492 * since this view simply acts as a gateway between
1493 * the view hierarchy and the associated component.
1494 *
1495 * @param index the index of the child
1496 * @param a the allocation to this view.
1497 * @return the allocation to the child
1498 */
1499 public Shape getChildAllocation(int index, Shape a) {
1500 return a;
1501 }
1502
1503 /**
1504 * Provides a mapping from the document model coordinate space
1505 * to the coordinate space of the view mapped to it.
1506 *
1507 * @param pos the position to convert
1508 * @param a the allocated region to render into
1509 * @return the bounding box of the given position
1510 */
1511 public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
1512 if (view != null) {
1513 return view.modelToView(pos, a, b);
1514 }
1515 return null;
1516 }
1517
1518 /**
1519 * Provides a mapping from the document model coordinate space
1520 * to the coordinate space of the view mapped to it.
1521 *
1522 * @param p0 the position to convert >= 0
1523 * @param b0 the bias toward the previous character or the
1524 * next character represented by p0, in case the
1525 * position is a boundary of two views.
1526 * @param p1 the position to convert >= 0
1527 * @param b1 the bias toward the previous character or the
1528 * next character represented by p1, in case the
1529 * position is a boundary of two views.
1530 * @param a the allocated region to render into
1531 * @return the bounding box of the given position is returned
1532 * @exception BadLocationException if the given position does
1533 * not represent a valid location in the associated document
1534 * @exception IllegalArgumentException for an invalid bias argument
1535 * @see View#viewToModel
1536 */
1537 public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException {
1538 if (view != null) {
1539 return view.modelToView(p0, b0, p1, b1, a);
1540 }
1541 return null;
1542 }
1543
1544 /**
1545 * Provides a mapping from the view coordinate space to the logical
1546 * coordinate space of the model.
1547 *
1548 * @param x x coordinate of the view location to convert
1549 * @param y y coordinate of the view location to convert
1550 * @param a the allocated region to render into
1551 * @return the location within the model that best represents the
1552 * given point in the view
1553 */
1554 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
1555 if (view != null) {
1556 int retValue = view.viewToModel(x, y, a, bias);
1557 return retValue;
1558 }
1559 return -1;
1560 }
1561
1562 /**
1563 * Provides a way to determine the next visually represented model
1564 * location that one might place a caret. Some views may not be visible,
1565 * they might not be in the same order found in the model, or they just
1566 * might not allow access to some of the locations in the model.
1567 *
1568 * @param pos the position to convert >= 0
1569 * @param a the allocated region to render into
1570 * @param direction the direction from the current position that can
1571 * be thought of as the arrow keys typically found on a keyboard.
1572 * This may be SwingConstants.WEST, SwingConstants.EAST,
1573 * SwingConstants.NORTH, or SwingConstants.SOUTH.
1574 * @return the location within the model that best represents the next
1575 * location visual position.
1576 * @exception BadLocationException
1577 * @exception IllegalArgumentException for an invalid direction
1578 */
1579 public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
1580 int direction,
1581 Position.Bias[] biasRet)
1582 throws BadLocationException {
1583 if( view != null ) {
1584 int nextPos = view.getNextVisualPositionFrom(pos, b, a,
1585 direction, biasRet);
1586 if(nextPos != -1) {
1587 pos = nextPos;
1588 }
1589 else {
1590 biasRet[0] = b;
1591 }
1592 }
1593 return pos;
1594 }
1595
1596 /**
1597 * Gives notification that something was inserted into the document
1598 * in a location that this view is responsible for.
1599 *
1600 * @param e the change information from the associated document
1601 * @param a the current allocation of the view
1602 * @param f the factory to use to rebuild if the view has children
1603 */
1604 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
1605 if (view != null) {
1606 view.insertUpdate(e, a, f);
1607 }
1608 }
1609
1610 /**
1611 * Gives notification that something was removed from the document
1612 * in a location that this view is responsible for.
1613 *
1614 * @param e the change information from the associated document
1615 * @param a the current allocation of the view
1616 * @param f the factory to use to rebuild if the view has children
1617 */
1618 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
1619 if (view != null) {
1620 view.removeUpdate(e, a, f);
1621 }
1622 }
1623
1624 /**
1625 * Gives notification from the document that attributes were changed
1626 * in a location that this view is responsible for.
1627 *
1628 * @param e the change information from the associated document
1629 * @param a the current allocation of the view
1630 * @param f the factory to use to rebuild if the view has children
1631 */
1632 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
1633 if (view != null) {
1634 view.changedUpdate(e, a, f);
1635 }
1636 }
1637
1638 /**
1639 * Returns the document model underlying the view.
1640 *
1641 * @return the model
1642 */
1643 public Document getDocument() {
1644 return editor.getDocument();
1645 }
1646
1647 /**
1648 * Returns the starting offset into the model for this view.
1649 *
1650 * @return the starting offset
1651 */
1652 public int getStartOffset() {
1653 if (view != null) {
1654 return view.getStartOffset();
1655 }
1656 return getElement().getStartOffset();
1657 }
1658
1659 /**
1660 * Returns the ending offset into the model for this view.
1661 *
1662 * @return the ending offset
1663 */
1664 public int getEndOffset() {
1665 if (view != null) {
1666 return view.getEndOffset();
1667 }
1668 return getElement().getEndOffset();
1669 }
1670
1671 /**
1672 * Gets the element that this view is mapped to.
1673 *
1674 * @return the view
1675 */
1676 public Element getElement() {
1677 if (view != null) {
1678 return view.getElement();
1679 }
1680 return editor.getDocument().getDefaultRootElement();
1681 }
1682
1683 /**
1684 * Breaks this view on the given axis at the given length.
1685 *
1686 * @param axis may be either X_AXIS or Y_AXIS
1687 * @param len specifies where a break is desired in the span
1688 * @param the current allocation of the view
1689 * @return the fragment of the view that represents the given span
1690 * if the view can be broken, otherwise null
1691 */
1692 public View breakView(int axis, float len, Shape a) {
1693 throw new Error("Can't break root view");
1694 }
1695
1696 /**
1697 * Determines the resizability of the view along the
1698 * given axis. A value of 0 or less is not resizable.
1699 *
1700 * @param axis may be either X_AXIS or Y_AXIS
1701 * @return the weight
1702 */
1703 public int getResizeWeight(int axis) {
1704 if (view != null) {
1705 return view.getResizeWeight(axis);
1706 }
1707 return 0;
1708 }
1709
1710 /**
1711 * Sets the view size.
1712 *
1713 * @param width the width
1714 * @param height the height
1715 */
1716 public void setSize(float width, float height) {
1717 if (view != null) {
1718 view.setSize(width, height);
1719 }
1720 }
1721
1722 /**
1723 * Fetches the container hosting the view. This is useful for
1724 * things like scheduling a repaint, finding out the host
1725 * components font, etc. The default implementation
1726 * of this is to forward the query to the parent view.
1727 *
1728 * @return the container
1729 */
1730 public Container getContainer() {
1731 return editor;
1732 }
1733
1734 /**
1735 * Fetches the factory to be used for building the
1736 * various view fragments that make up the view that
1737 * represents the model. This is what determines
1738 * how the model will be represented. This is implemented
1739 * to fetch the factory provided by the associated
1740 * EditorKit unless that is null, in which case this
1741 * simply returns the BasicTextUI itself which allows
1742 * subclasses to implement a simple factory directly without
1743 * creating extra objects.
1744 *
1745 * @return the factory
1746 */
1747 public ViewFactory getViewFactory() {
1748 EditorKit kit = getEditorKit(editor);
1749 ViewFactory f = kit.getViewFactory();
1750 if (f != null) {
1751 return f;
1752 }
1753 return BasicTextUI.this;
1754 }
1755
1756 private View view;
1757
1758 }
1759
1760 /**
1761 * Handles updates from various places. If the model is changed,
1762 * this class unregisters as a listener to the old model and
1763 * registers with the new model. If the document model changes,
1764 * the change is forwarded to the root view. If the focus
1765 * accelerator changes, a new keystroke is registered to request
1766 * focus.
1767 */
1768 class UpdateHandler implements PropertyChangeListener, DocumentListener, LayoutManager2, UIResource {
1769
1770 // --- PropertyChangeListener methods -----------------------
1771
1772 /**
1773 * This method gets called when a bound property is changed.
1774 * We are looking for document changes on the editor.
1775 */
1776 public final void propertyChange(PropertyChangeEvent evt) {
1777 Object oldValue = evt.getOldValue();
1778 Object newValue = evt.getNewValue();
1779 String propertyName = evt.getPropertyName();
1780 if ((oldValue instanceof Document) || (newValue instanceof Document)) {
1781 if (oldValue != null) {
1782 ((Document)oldValue).removeDocumentListener(this);
1783 i18nView = false;
1784 }
1785 if (newValue != null) {
1786 ((Document)newValue).addDocumentListener(this);
1787 if ("document" == propertyName) {
1788 setView(null);
1789 BasicTextUI.this.propertyChange(evt);
1790 modelChanged();
1791 return;
1792 }
1793 }
1794 modelChanged();
1795 }
1796 if ("focusAccelerator" == propertyName) {
1797 updateFocusAcceleratorBinding(true);
1798 } else if ("componentOrientation" == propertyName) {
1799 // Changes in ComponentOrientation require the views to be
1800 // rebuilt.
1801 modelChanged();
1802 } else if ("font" == propertyName) {
1803 modelChanged();
1804 } else if ("dropLocation" == propertyName) {
1805 dropIndexChanged();
1806 } else if ("editable" == propertyName) {
1807 updateCursor();
1808 modelChanged();
1809 }
1810 BasicTextUI.this.propertyChange(evt);
1811 }
1812
1813 private void dropIndexChanged() {
1814 if (editor.getDropMode() == DropMode.USE_SELECTION) {
1815 return;
1816 }
1817
1818 JTextComponent.DropLocation dropLocation = editor.getDropLocation();
1819
1820 if (dropLocation == null) {
1821 if (dropCaret != null) {
1822 dropCaret.deinstall(editor);
1823 editor.repaint(dropCaret);
1824 dropCaret = null;
1825 }
1826 } else {
1827 if (dropCaret == null) {
1828 dropCaret = new BasicCaret();
1829 dropCaret.install(editor);
1830 dropCaret.setVisible(true);
1831 }
1832
1833 dropCaret.setDot(dropLocation.getIndex(),
1834 dropLocation.getBias());
1835 }
1836 }
1837
1838 // --- DocumentListener methods -----------------------
1839
1840 /**
1841 * The insert notification. Gets sent to the root of the view structure
1842 * that represents the portion of the model being represented by the
1843 * editor. The factory is added as an argument to the update so that
1844 * the views can update themselves in a dynamic (not hardcoded) way.
1845 *
1846 * @param e The change notification from the currently associated
1847 * document.
1848 * @see DocumentListener#insertUpdate
1849 */
1850 public final void insertUpdate(DocumentEvent e) {
1851 Document doc = e.getDocument();
1852 Object o = doc.getProperty("i18n");
1853 if (o instanceof Boolean) {
1854 Boolean i18nFlag = (Boolean) o;
1855 if (i18nFlag.booleanValue() != i18nView) {
1856 // i18n flag changed, rebuild the view
1857 i18nView = i18nFlag.booleanValue();
1858 modelChanged();
1859 return;
1860 }
1861 }
1862
1863 // normal insert update
1864 Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
1865 rootView.insertUpdate(e, alloc, rootView.getViewFactory());
1866 }
1867
1868 /**
1869 * The remove notification. Gets sent to the root of the view structure
1870 * that represents the portion of the model being represented by the
1871 * editor. The factory is added as an argument to the update so that
1872 * the views can update themselves in a dynamic (not hardcoded) way.
1873 *
1874 * @param e The change notification from the currently associated
1875 * document.
1876 * @see DocumentListener#removeUpdate
1877 */
1878 public final void removeUpdate(DocumentEvent e) {
1879 Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
1880 rootView.removeUpdate(e, alloc, rootView.getViewFactory());
1881 }
1882
1883 /**
1884 * The change notification. Gets sent to the root of the view structure
1885 * that represents the portion of the model being represented by the
1886 * editor. The factory is added as an argument to the update so that
1887 * the views can update themselves in a dynamic (not hardcoded) way.
1888 *
1889 * @param e The change notification from the currently associated
1890 * document.
1891 * @see DocumentListener#changeUpdate
1892 */
1893 public final void changedUpdate(DocumentEvent e) {
1894 Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
1895 rootView.changedUpdate(e, alloc, rootView.getViewFactory());
1896 }
1897
1898 // --- LayoutManager2 methods --------------------------------
1899
1900 /**
1901 * Adds the specified component with the specified name to
1902 * the layout.
1903 * @param name the component name
1904 * @param comp the component to be added
1905 */
1906 public void addLayoutComponent(String name, Component comp) {
1907 // not supported
1908 }
1909
1910 /**
1911 * Removes the specified component from the layout.
1912 * @param comp the component to be removed
1913 */
1914 public void removeLayoutComponent(Component comp) {
1915 if (constraints != null) {
1916 // remove the constraint record
1917 constraints.remove(comp);
1918 }
1919 }
1920
1921 /**
1922 * Calculates the preferred size dimensions for the specified
1923 * panel given the components in the specified parent container.
1924 * @param parent the component to be laid out
1925 *
1926 * @see #minimumLayoutSize
1927 */
1928 public Dimension preferredLayoutSize(Container parent) {
1929 // should not be called (JComponent uses UI instead)
1930 return null;
1931 }
1932
1933 /**
1934 * Calculates the minimum size dimensions for the specified
1935 * panel given the components in the specified parent container.
1936 * @param parent the component to be laid out
1937 * @see #preferredLayoutSize
1938 */
1939 public Dimension minimumLayoutSize(Container parent) {
1940 // should not be called (JComponent uses UI instead)
1941 return null;
1942 }
1943
1944 /**
1945 * Lays out the container in the specified panel. This is
1946 * implemented to position all components that were added
1947 * with a View object as a constraint. The current allocation
1948 * of the associated View is used as the location of the
1949 * component.
1950 * <p>
1951 * A read-lock is acquired on the document to prevent the
1952 * view tree from being modified while the layout process
1953 * is active.
1954 *
1955 * @param parent the component which needs to be laid out
1956 */
1957 public void layoutContainer(Container parent) {
1958 if ((constraints != null) && (! constraints.isEmpty())) {
1959 Rectangle alloc = getVisibleEditorRect();
1960 if (alloc != null) {
1961 Document doc = editor.getDocument();
1962 if (doc instanceof AbstractDocument) {
1963 ((AbstractDocument)doc).readLock();
1964 }
1965 try {
1966 rootView.setSize(alloc.width, alloc.height);
1967 Enumeration components = constraints.keys();
1968 while (components.hasMoreElements()) {
1969 Component comp = (Component) components.nextElement();
1970 View v = (View) constraints.get(comp);
1971 Shape ca = calculateViewPosition(alloc, v);
1972 if (ca != null) {
1973 Rectangle compAlloc = (ca instanceof Rectangle) ?
1974 (Rectangle) ca : ca.getBounds();
1975 comp.setBounds(compAlloc);
1976 }
1977 }
1978 } finally {
1979 if (doc instanceof AbstractDocument) {
1980 ((AbstractDocument)doc).readUnlock();
1981 }
1982 }
1983 }
1984 }
1985 }
1986
1987 /**
1988 * Find the Shape representing the given view.
1989 */
1990 Shape calculateViewPosition(Shape alloc, View v) {
1991 int pos = v.getStartOffset();
1992 View child = null;
1993 for (View parent = rootView; (parent != null) && (parent != v); parent = child) {
1994 int index = parent.getViewIndex(pos, Position.Bias.Forward);
1995 alloc = parent.getChildAllocation(index, alloc);
1996 child = parent.getView(index);
1997 }
1998 return (child != null) ? alloc : null;
1999 }
2000
2001 /**
2002 * Adds the specified component to the layout, using the specified
2003 * constraint object. We only store those components that were added
2004 * with a constraint that is of type View.
2005 *
2006 * @param comp the component to be added
2007 * @param constraint where/how the component is added to the layout.
2008 */
2009 public void addLayoutComponent(Component comp, Object constraint) {
2010 if (constraint instanceof View) {
2011 if (constraints == null) {
2012 constraints = new Hashtable(7);
2013 }
2014 constraints.put(comp, constraint);
2015 }
2016 }
2017
2018 /**
2019 * Returns the maximum size of this component.
2020 * @see java.awt.Component#getMinimumSize()
2021 * @see java.awt.Component#getPreferredSize()
2022 * @see LayoutManager
2023 */
2024 public Dimension maximumLayoutSize(Container target) {
2025 // should not be called (JComponent uses UI instead)
2026 return null;
2027 }
2028
2029 /**
2030 * Returns the alignment along the x axis. This specifies how
2031 * the component would like to be aligned relative to other
2032 * components. The value should be a number between 0 and 1
2033 * where 0 represents alignment along the origin, 1 is aligned
2034 * the furthest away from the origin, 0.5 is centered, etc.
2035 */
2036 public float getLayoutAlignmentX(Container target) {
2037 return 0.5f;
2038 }
2039
2040 /**
2041 * Returns the alignment along the y axis. This specifies how
2042 * the component would like to be aligned relative to other
2043 * components. The value should be a number between 0 and 1
2044 * where 0 represents alignment along the origin, 1 is aligned
2045 * the furthest away from the origin, 0.5 is centered, etc.
2046 */
2047 public float getLayoutAlignmentY(Container target) {
2048 return 0.5f;
2049 }
2050
2051 /**
2052 * Invalidates the layout, indicating that if the layout manager
2053 * has cached information it should be discarded.
2054 */
2055 public void invalidateLayout(Container target) {
2056 }
2057
2058 /**
2059 * The "layout constraints" for the LayoutManager2 implementation.
2060 * These are View objects for those components that are represented
2061 * by a View in the View tree.
2062 */
2063 private Hashtable constraints;
2064
2065 private boolean i18nView = false;
2066 }
2067
2068 /**
2069 * Wrapper for text actions to return isEnabled false in case editor is non editable
2070 */
2071 class TextActionWrapper extends TextAction {
2072 public TextActionWrapper(TextAction action) {
2073 super((String)action.getValue(Action.NAME));
2074 this.action = action;
2075 }
2076 /**
2077 * The operation to perform when this action is triggered.
2078 *
2079 * @param e the action event
2080 */
2081 public void actionPerformed(ActionEvent e) {
2082 action.actionPerformed(e);
2083 }
2084 public boolean isEnabled() {
2085 return (editor == null || editor.isEditable()) ? action.isEnabled() : false;
2086 }
2087 TextAction action = null;
2088 }
2089
2090
2091 /**
2092 * Registered in the ActionMap.
2093 */
2094 class FocusAction extends AbstractAction {
2095
2096 public void actionPerformed(ActionEvent e) {
2097 editor.requestFocus();
2098 }
2099
2100 public boolean isEnabled() {
2101 return editor.isEditable();
2102 }
2103 }
2104
2105 private static DragListener getDragListener() {
2106 synchronized(DragListener.class) {
2107 DragListener listener =
2108 (DragListener)AppContext.getAppContext().
2109 get(DragListener.class);
2110
2111 if (listener == null) {
2112 listener = new DragListener();
2113 AppContext.getAppContext().put(DragListener.class, listener);
2114 }
2115
2116 return listener;
2117 }
2118 }
2119
2120 /**
2121 * Listens for mouse events for the purposes of detecting drag gestures.
2122 * BasicTextUI will maintain one of these per AppContext.
2123 */
2124 static class DragListener extends MouseInputAdapter
2125 implements BeforeDrag {
2126
2127 private boolean dragStarted;
2128
2129 public void dragStarting(MouseEvent me) {
2130 dragStarted = true;
2131 }
2132
2133 public void mousePressed(MouseEvent e) {
2134 JTextComponent c = (JTextComponent)e.getSource();
2135 if (c.getDragEnabled()) {
2136 dragStarted = false;
2137 if (isDragPossible(e) && DragRecognitionSupport.mousePressed(e)) {
2138 e.consume();
2139 }
2140 }
2141 }
2142
2143 public void mouseReleased(MouseEvent e) {
2144 JTextComponent c = (JTextComponent)e.getSource();
2145 if (c.getDragEnabled()) {
2146 if (dragStarted) {
2147 e.consume();
2148 }
2149
2150 DragRecognitionSupport.mouseReleased(e);
2151 }
2152 }
2153
2154 public void mouseDragged(MouseEvent e) {
2155 JTextComponent c = (JTextComponent)e.getSource();
2156 if (c.getDragEnabled()) {
2157 if (dragStarted || DragRecognitionSupport.mouseDragged(e, this)) {
2158 e.consume();
2159 }
2160 }
2161 }
2162
2163 /**
2164 * Determines if the following are true:
2165 * <ul>
2166 * <li>the component is enabled
2167 * <li>the press event is located over a selection
2168 * </ul>
2169 */
2170 protected boolean isDragPossible(MouseEvent e) {
2171 JTextComponent c = (JTextComponent)e.getSource();
2172 if (c.isEnabled()) {
2173 Caret caret = c.getCaret();
2174 int dot = caret.getDot();
2175 int mark = caret.getMark();
2176 if (dot != mark) {
2177 Point p = new Point(e.getX(), e.getY());
2178 int pos = c.viewToModel(p);
2179
2180 int p0 = Math.min(dot, mark);
2181 int p1 = Math.max(dot, mark);
2182 if ((pos >= p0) && (pos < p1)) {
2183 return true;
2184 }
2185 }
2186 }
2187 return false;
2188 }
2189 }
2190
2191 static class TextTransferHandler extends TransferHandler implements UIResource {
2192
2193 private JTextComponent exportComp;
2194 private boolean shouldRemove;
2195 private int p0;
2196 private int p1;
2197
2198 /**
2199 * Whether or not this is a drop using
2200 * <code>DropMode.INSERT</code>.
2201 */
2202 private boolean modeBetween = false;
2203
2204 /**
2205 * Whether or not this is a drop.
2206 */
2207 private boolean isDrop = false;
2208
2209 /**
2210 * The drop action.
2211 */
2212 private int dropAction = MOVE;
2213
2214 /**
2215 * The drop bias.
2216 */
2217 private Position.Bias dropBias;
2218
2219 /**
2220 * Try to find a flavor that can be used to import a Transferable.
2221 * The set of usable flavors are tried in the following order:
2222 * <ol>
2223 * <li>First, an attempt is made to find a flavor matching the content type
2224 * of the EditorKit for the component.
2225 * <li>Second, an attempt to find a text/plain flavor is made.
2226 * <li>Third, an attempt to find a flavor representing a String reference
2227 * in the same VM is made.
2228 * <li>Lastly, DataFlavor.stringFlavor is searched for.
2229 * </ol>
2230 */
2231 protected DataFlavor getImportFlavor(DataFlavor[] flavors, JTextComponent c) {
2232 DataFlavor plainFlavor = null;
2233 DataFlavor refFlavor = null;
2234 DataFlavor stringFlavor = null;
2235
2236 if (c instanceof JEditorPane) {
2237 for (int i = 0; i < flavors.length; i++) {
2238 String mime = flavors[i].getMimeType();
2239 if (mime.startsWith(((JEditorPane)c).getEditorKit().getContentType())) {
2240 return flavors[i];
2241 } else if (plainFlavor == null && mime.startsWith("text/plain")) {
2242 plainFlavor = flavors[i];
2243 } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
2244 && flavors[i].getRepresentationClass() == java.lang.String.class) {
2245 refFlavor = flavors[i];
2246 } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
2247 stringFlavor = flavors[i];
2248 }
2249 }
2250 if (plainFlavor != null) {
2251 return plainFlavor;
2252 } else if (refFlavor != null) {
2253 return refFlavor;
2254 } else if (stringFlavor != null) {
2255 return stringFlavor;
2256 }
2257 return null;
2258 }
2259
2260
2261 for (int i = 0; i < flavors.length; i++) {
2262 String mime = flavors[i].getMimeType();
2263 if (mime.startsWith("text/plain")) {
2264 return flavors[i];
2265 } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
2266 && flavors[i].getRepresentationClass() == java.lang.String.class) {
2267 refFlavor = flavors[i];
2268 } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
2269 stringFlavor = flavors[i];
2270 }
2271 }
2272 if (refFlavor != null) {
2273 return refFlavor;
2274 } else if (stringFlavor != null) {
2275 return stringFlavor;
2276 }
2277 return null;
2278 }
2279
2280 /**
2281 * Import the given stream data into the text component.
2282 */
2283 protected void handleReaderImport(Reader in, JTextComponent c, boolean useRead)
2284 throws BadLocationException, IOException {
2285 if (useRead) {
2286 int startPosition = c.getSelectionStart();
2287 int endPosition = c.getSelectionEnd();
2288 int length = endPosition - startPosition;
2289 EditorKit kit = c.getUI().getEditorKit(c);
2290 Document doc = c.getDocument();
2291 if (length > 0) {
2292 doc.remove(startPosition, length);
2293 }
2294 kit.read(in, doc, startPosition);
2295 } else {
2296 char[] buff = new char[1024];
2297 int nch;
2298 boolean lastWasCR = false;
2299 int last;
2300 StringBuffer sbuff = null;
2301
2302 // Read in a block at a time, mapping \r\n to \n, as well as single
2303 // \r to \n.
2304 while ((nch = in.read(buff, 0, buff.length)) != -1) {
2305 if (sbuff == null) {
2306 sbuff = new StringBuffer(nch);
2307 }
2308 last = 0;
2309 for(int counter = 0; counter < nch; counter++) {
2310 switch(buff[counter]) {
2311 case '\r':
2312 if (lastWasCR) {
2313 if (counter == 0) {
2314 sbuff.append('\n');
2315 } else {
2316 buff[counter - 1] = '\n';
2317 }
2318 } else {
2319 lastWasCR = true;
2320 }
2321 break;
2322 case '\n':
2323 if (lastWasCR) {
2324 if (counter > (last + 1)) {
2325 sbuff.append(buff, last, counter - last - 1);
2326 }
2327 // else nothing to do, can skip \r, next write will
2328 // write \n
2329 lastWasCR = false;
2330 last = counter;
2331 }
2332 break;
2333 default:
2334 if (lastWasCR) {
2335 if (counter == 0) {
2336 sbuff.append('\n');
2337 } else {
2338 buff[counter - 1] = '\n';
2339 }
2340 lastWasCR = false;
2341 }
2342 break;
2343 }
2344 }
2345 if (last < nch) {
2346 if (lastWasCR) {
2347 if (last < (nch - 1)) {
2348 sbuff.append(buff, last, nch - last - 1);
2349 }
2350 } else {
2351 sbuff.append(buff, last, nch - last);
2352 }
2353 }
2354 }
2355 if (lastWasCR) {
2356 sbuff.append('\n');
2357 }
2358 c.replaceSelection(sbuff != null ? sbuff.toString() : "");
2359 }
2360 }
2361
2362 // --- TransferHandler methods ------------------------------------
2363
2364 /**
2365 * This is the type of transfer actions supported by the source. Some models are
2366 * not mutable, so a transfer operation of COPY only should
2367 * be advertised in that case.
2368 *
2369 * @param c The component holding the data to be transfered. This
2370 * argument is provided to enable sharing of TransferHandlers by
2371 * multiple components.
2372 * @return This is implemented to return NONE if the component is a JPasswordField
2373 * since exporting data via user gestures is not allowed. If the text component is
2374 * editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed.
2375 */
2376 public int getSourceActions(JComponent c) {
2377 if (c instanceof JPasswordField &&
2378 c.getClientProperty("JPasswordField.cutCopyAllowed") !=
2379 Boolean.TRUE) {
2380 return NONE;
2381 }
2382
2383 return ((JTextComponent)c).isEditable() ? COPY_OR_MOVE : COPY;
2384 }
2385
2386 /**
2387 * Create a Transferable to use as the source for a data transfer.
2388 *
2389 * @param comp The component holding the data to be transfered. This
2390 * argument is provided to enable sharing of TransferHandlers by
2391 * multiple components.
2392 * @return The representation of the data to be transfered.
2393 *
2394 */
2395 protected Transferable createTransferable(JComponent comp) {
2396 exportComp = (JTextComponent)comp;
2397 shouldRemove = true;
2398 p0 = exportComp.getSelectionStart();
2399 p1 = exportComp.getSelectionEnd();
2400 return (p0 != p1) ? (new TextTransferable(exportComp, p0, p1)) : null;
2401 }
2402
2403 /**
2404 * This method is called after data has been exported. This method should remove
2405 * the data that was transfered if the action was MOVE.
2406 *
2407 * @param source The component that was the source of the data.
2408 * @param data The data that was transferred or possibly null
2409 * if the action is <code>NONE</code>.
2410 * @param action The actual action that was performed.
2411 */
2412 protected void exportDone(JComponent source, Transferable data, int action) {
2413 // only remove the text if shouldRemove has not been set to
2414 // false by importData and only if the action is a move
2415 if (shouldRemove && action == MOVE) {
2416 TextTransferable t = (TextTransferable)data;
2417 t.removeText();
2418 }
2419
2420 exportComp = null;
2421 }
2422
2423 public boolean importData(TransferSupport