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+ */
25+package javax.swing.plaf.basic;
26+
27+import java.util.*;
28+import java.awt.*;
29+import java.awt.event.*;
30+import java.awt.datatransfer.*;
31+import java.awt.im.InputContext;
32+import java.beans.*;
33+import java.io.*;
34+import javax.swing.*;
35+import javax.swing.plaf.*;
36+import javax.swing.text.*;
37+import javax.swing.event.*;
38+import javax.swing.border.Border;
39+import javax.swing.plaf.UIResource;
40+import javax.swing.plaf.synth.SynthUI;
41+import sun.swing.DefaultLookup;
42+import sun.awt.AppContext;
43+import 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+ */
103+public 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+}