History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/plaf/basic/BasicTextUI.java (1 of 18352 files). Show all

Unified · Split

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