001 /* BasicTextUI.java --
002 Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.plaf.basic;
040
041 import gnu.classpath.SystemProperties;
042
043 import java.awt.Color;
044 import java.awt.Container;
045 import java.awt.Dimension;
046 import java.awt.Graphics;
047 import java.awt.HeadlessException;
048 import java.awt.Insets;
049 import java.awt.Point;
050 import java.awt.Rectangle;
051 import java.awt.Shape;
052 import java.awt.Toolkit;
053 import java.awt.datatransfer.Clipboard;
054 import java.awt.datatransfer.StringSelection;
055 import java.awt.event.FocusEvent;
056 import java.awt.event.FocusListener;
057 import java.beans.PropertyChangeEvent;
058 import java.beans.PropertyChangeListener;
059
060 import javax.swing.Action;
061 import javax.swing.ActionMap;
062 import javax.swing.InputMap;
063 import javax.swing.JComponent;
064 import javax.swing.LookAndFeel;
065 import javax.swing.SwingConstants;
066 import javax.swing.SwingUtilities;
067 import javax.swing.TransferHandler;
068 import javax.swing.UIManager;
069 import javax.swing.event.DocumentEvent;
070 import javax.swing.event.DocumentListener;
071 import javax.swing.plaf.ActionMapUIResource;
072 import javax.swing.plaf.InputMapUIResource;
073 import javax.swing.plaf.TextUI;
074 import javax.swing.plaf.UIResource;
075 import javax.swing.text.AbstractDocument;
076 import javax.swing.text.AttributeSet;
077 import javax.swing.text.BadLocationException;
078 import javax.swing.text.Caret;
079 import javax.swing.text.DefaultCaret;
080 import javax.swing.text.DefaultEditorKit;
081 import javax.swing.text.DefaultHighlighter;
082 import javax.swing.text.Document;
083 import javax.swing.text.EditorKit;
084 import javax.swing.text.Element;
085 import javax.swing.text.Highlighter;
086 import javax.swing.text.JTextComponent;
087 import javax.swing.text.Keymap;
088 import javax.swing.text.Position;
089 import javax.swing.text.View;
090 import javax.swing.text.ViewFactory;
091
092 /**
093 * The abstract base class from which the UI classes for Swings text
094 * components are derived. This provides most of the functionality for
095 * the UI classes.
096 *
097 * @author original author unknown
098 * @author Roman Kennke (roman@kennke.org)
099 */
100 public abstract class BasicTextUI extends TextUI
101 implements ViewFactory
102 {
103 /**
104 * A {@link DefaultCaret} that implements {@link UIResource}.
105 */
106 public static class BasicCaret extends DefaultCaret implements UIResource
107 {
108 public BasicCaret()
109 {
110 // Nothing to do here.
111 }
112 }
113
114 /**
115 * A {@link DefaultHighlighter} that implements {@link UIResource}.
116 */
117 public static class BasicHighlighter extends DefaultHighlighter
118 implements UIResource
119 {
120 public BasicHighlighter()
121 {
122 // Nothing to do here.
123 }
124 }
125
126 private static class FocusHandler
127 implements FocusListener
128 {
129 public void focusGained(FocusEvent e)
130 {
131 // Nothing to do here.
132 }
133 public void focusLost(FocusEvent e)
134 {
135 JTextComponent textComponent = (JTextComponent) e.getComponent();
136 // Integrates Swing text components with the system clipboard:
137 // The idea is that if one wants to copy text around X11-style
138 // (select text and middle-click in the target component) the focus
139 // will move to the new component which gives the old focus owner the
140 // possibility to paste its selection into the clipboard.
141 if (!e.isTemporary()
142 && textComponent.getSelectionStart()
143 != textComponent.getSelectionEnd())
144 {
145 SecurityManager sm = System.getSecurityManager();
146 try
147 {
148 if (sm != null)
149 sm.checkSystemClipboardAccess();
150
151 Clipboard cb = Toolkit.getDefaultToolkit().getSystemSelection();
152 if (cb != null)
153 {
154 StringSelection selection = new StringSelection(
155 textComponent.getSelectedText());
156 cb.setContents(selection, selection);
157 }
158 }
159 catch (SecurityException se)
160 {
161 // Not allowed to access the clipboard: Ignore and
162 // do not access it.
163 }
164 catch (HeadlessException he)
165 {
166 // There is no AWT: Ignore and do not access the
167 // clipboard.
168 }
169 catch (IllegalStateException ise)
170 {
171 // Clipboard is currently unavaible.
172 }
173 }
174 }
175 }
176
177 /**
178 * This FocusListener triggers repaints on focus shift.
179 */
180 private static FocusListener focusListener;
181
182 /**
183 * Receives notifications when properties of the text component change.
184 */
185 private class Handler
186 implements PropertyChangeListener, DocumentListener
187 {
188 /**
189 * Notifies when a property of the text component changes.
190 *
191 * @param event the PropertyChangeEvent describing the change
192 */
193 public void propertyChange(PropertyChangeEvent event)
194 {
195 if (event.getPropertyName().equals("document"))
196 {
197 // Document changed.
198 Object oldValue = event.getOldValue();
199 if (oldValue != null)
200 {
201 Document oldDoc = (Document) oldValue;
202 oldDoc.removeDocumentListener(handler);
203 }
204 Object newValue = event.getNewValue();
205 if (newValue != null)
206 {
207 Document newDoc = (Document) newValue;
208 newDoc.addDocumentListener(handler);
209 }
210 modelChanged();
211 }
212
213 BasicTextUI.this.propertyChange(event);
214 }
215
216 /**
217 * Notification about a document change event.
218 *
219 * @param ev the DocumentEvent describing the change
220 */
221 public void changedUpdate(DocumentEvent ev)
222 {
223 // Updates are forwarded to the View even if 'getVisibleEditorRect'
224 // method returns null. This means the View classes have to be
225 // aware of that possibility.
226 rootView.changedUpdate(ev, getVisibleEditorRect(),
227 rootView.getViewFactory());
228 }
229
230 /**
231 * Notification about a document insert event.
232 *
233 * @param ev the DocumentEvent describing the insertion
234 */
235 public void insertUpdate(DocumentEvent ev)
236 {
237 // Updates are forwarded to the View even if 'getVisibleEditorRect'
238 // method returns null. This means the View classes have to be
239 // aware of that possibility.
240 rootView.insertUpdate(ev, getVisibleEditorRect(),
241 rootView.getViewFactory());
242 }
243
244 /**
245 * Notification about a document removal event.
246 *
247 * @param ev the DocumentEvent describing the removal
248 */
249 public void removeUpdate(DocumentEvent ev)
250 {
251 // Updates are forwarded to the View even if 'getVisibleEditorRect'
252 // method returns null. This means the View classes have to be
253 // aware of that possibility.
254 rootView.removeUpdate(ev, getVisibleEditorRect(),
255 rootView.getViewFactory());
256 }
257
258 }
259
260 /**
261 * This view forms the root of the View hierarchy. However, it delegates
262 * most calls to another View which is the real root of the hierarchy.
263 * The purpose is to make sure that all Views in the hierarchy, including
264 * the (real) root have a well-defined parent to which they can delegate
265 * calls like {@link #preferenceChanged}, {@link #getViewFactory} and
266 * {@link #getContainer}.
267 */
268 private class RootView extends View
269 {
270 /** The real root view. */
271 private View view;
272
273 /**
274 * Creates a new RootView.
275 */
276 public RootView()
277 {
278 super(null);
279 }
280
281 /**
282 * Returns the ViewFactory for this RootView. If the current EditorKit
283 * provides a ViewFactory, this is used. Otherwise the TextUI itself
284 * is returned as a ViewFactory.
285 *
286 * @return the ViewFactory for this RootView
287 */
288 public ViewFactory getViewFactory()
289 {
290 ViewFactory factory = null;
291 EditorKit editorKit = BasicTextUI.this.getEditorKit(getComponent());
292 factory = editorKit.getViewFactory();
293 if (factory == null)
294 factory = BasicTextUI.this;
295 return factory;
296 }
297
298 /**
299 * Indicates that the preferences of one of the child view has changed.
300 * This calls revalidate on the text component.
301 *
302 * @param v the child view which's preference has changed
303 * @param width <code>true</code> if the width preference has changed
304 * @param height <code>true</code> if the height preference has changed
305 */
306 public void preferenceChanged(View v, boolean width, boolean height)
307 {
308 textComponent.revalidate();
309 }
310
311 /**
312 * Sets the real root view.
313 *
314 * @param v the root view to set
315 */
316 public void setView(View v)
317 {
318 if (view != null)
319 view.setParent(null);
320
321 if (v != null)
322 v.setParent(this);
323
324 view = v;
325 }
326
327 /**
328 * Returns the real root view, regardless of the index.
329 *
330 * @param index not used here
331 *
332 * @return the real root view, regardless of the index.
333 */
334 public View getView(int index)
335 {
336 return view;
337 }
338
339 /**
340 * Returns <code>1</code> since the RootView always contains one
341 * child, that is the real root of the View hierarchy.
342 *
343 * @return <code>1</code> since the RootView always contains one
344 * child, that is the real root of the View hierarchy
345 */
346 public int getViewCount()
347 {
348 int count = 0;
349 if (view != null)
350 count = 1;
351 return count;
352 }
353
354 /**
355 * Returns the <code>Container</code> that contains this view. This
356 * normally will be the text component that is managed by this TextUI.
357 *
358 * @return the <code>Container</code> that contains this view
359 */
360 public Container getContainer()
361 {
362 return textComponent;
363 }
364
365 /**
366 * Sets the size of the renderer. This is synchronized because that
367 * potentially triggers layout and we don't want more than one thread
368 * playing with the layout information.
369 */
370 public synchronized void setSize(float w, float h)
371 {
372 if (view != null)
373 view.setSize(w, h);
374 }
375
376 /**
377 * Paints the view. This is delegated to the real root view.
378 *
379 * @param g the <code>Graphics</code> context to paint to
380 * @param s the allocation for the View
381 */
382 public void paint(Graphics g, Shape s)
383 {
384 if (view != null)
385 {
386 Rectangle b = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
387 setSize(b.width, b.height);
388 view.paint(g, s);
389 }
390 }
391
392
393 /**
394 * Maps a position in the document into the coordinate space of the View.
395 * The output rectangle usually reflects the font height but has a width
396 * of zero.
397 *
398 * This is delegated to the real root view.
399 *
400 * @param position the position of the character in the model
401 * @param a the area that is occupied by the view
402 * @param bias either {@link Position.Bias#Forward} or
403 * {@link Position.Bias#Backward} depending on the preferred
404 * direction bias. If <code>null</code> this defaults to
405 * <code>Position.Bias.Forward</code>
406 *
407 * @return a rectangle that gives the location of the document position
408 * inside the view coordinate space
409 *
410 * @throws BadLocationException if <code>pos</code> is invalid
411 * @throws IllegalArgumentException if b is not one of the above listed
412 * valid values
413 */
414 public Shape modelToView(int position, Shape a, Position.Bias bias)
415 throws BadLocationException
416 {
417 return view.modelToView(position, a, bias);
418 }
419
420 /**
421 * Maps coordinates from the <code>View</code>'s space into a position
422 * in the document model.
423 *
424 * @param x the x coordinate in the view space
425 * @param y the y coordinate in the view space
426 * @param a the allocation of this <code>View</code>
427 * @param b the bias to use
428 *
429 * @return the position in the document that corresponds to the screen
430 * coordinates <code>x, y</code>
431 */
432 public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
433 {
434 return view.viewToModel(x, y, a, b);
435 }
436
437 /**
438 * Notification about text insertions. These are forwarded to the
439 * real root view.
440 *
441 * @param ev the DocumentEvent describing the change
442 * @param shape the current allocation of the view's display
443 * @param vf the ViewFactory to use for creating new Views
444 */
445 public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
446 {
447 if (view != null)
448 view.insertUpdate(ev, shape, vf);
449 }
450
451 /**
452 * Notification about text removals. These are forwarded to the
453 * real root view.
454 *
455 * @param ev the DocumentEvent describing the change
456 * @param shape the current allocation of the view's display
457 * @param vf the ViewFactory to use for creating new Views
458 */
459 public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
460 {
461 if (view != null)
462 view.removeUpdate(ev, shape, vf);
463 }
464
465 /**
466 * Notification about text changes. These are forwarded to the
467 * real root view.
468 *
469 * @param ev the DocumentEvent describing the change
470 * @param shape the current allocation of the view's display
471 * @param vf the ViewFactory to use for creating new Views
472 */
473 public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
474 {
475 if (view != null)
476 view.changedUpdate(ev, shape, vf);
477 }
478
479 /**
480 * Returns the document position that is (visually) nearest to the given
481 * document position <code>pos</code> in the given direction <code>d</code>.
482 *
483 * @param pos the document position
484 * @param b the bias for <code>pos</code>
485 * @param a the allocation for the view
486 * @param d the direction, must be either {@link SwingConstants#NORTH},
487 * {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or
488 * {@link SwingConstants#EAST}
489 * @param biasRet an array of {@link Position.Bias} that can hold at least
490 * one element, which is filled with the bias of the return position
491 * on method exit
492 *
493 * @return the document position that is (visually) nearest to the given
494 * document position <code>pos</code> in the given direction
495 * <code>d</code>
496 *
497 * @throws BadLocationException if <code>pos</code> is not a valid offset in
498 * the document model
499 */
500 public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
501 int d, Position.Bias[] biasRet)
502 throws BadLocationException
503 {
504 return view.getNextVisualPositionFrom(pos, b, a, d, biasRet);
505 }
506
507 /**
508 * Returns the startOffset of this view, which is always the beginning
509 * of the document.
510 *
511 * @return the startOffset of this view
512 */
513 public int getStartOffset()
514 {
515 return 0;
516 }
517
518 /**
519 * Returns the endOffset of this view, which is always the end
520 * of the document.
521 *
522 * @return the endOffset of this view
523 */
524 public int getEndOffset()
525 {
526 return getDocument().getLength();
527 }
528
529 /**
530 * Returns the document associated with this view.
531 *
532 * @return the document associated with this view
533 */
534 public Document getDocument()
535 {
536 return textComponent.getDocument();
537 }
538
539 /**
540 * Returns the attributes, which is null for the RootView.
541 */
542 public AttributeSet getAttributes()
543 {
544 return null;
545 }
546
547 /**
548 * Overridden to forward to the view.
549 */
550 public float getPreferredSpan(int axis)
551 {
552 // The RI returns 10 in the degenerate case.
553 float span = 10;
554 if (view != null)
555 span = view.getPreferredSpan(axis);
556 return span;
557 }
558
559 /**
560 * Overridden to forward to the real view.
561 */
562 public float getMinimumSpan(int axis)
563 {
564 // The RI returns 10 in the degenerate case.
565 float span = 10;
566 if (view != null)
567 span = view.getMinimumSpan(axis);
568 return span;
569 }
570
571 /**
572 * Overridden to return Integer.MAX_VALUE.
573 */
574 public float getMaximumSpan(int axis)
575 {
576 // The RI returns Integer.MAX_VALUE here, regardless of the real view's
577 // maximum size.
578 return Integer.MAX_VALUE;
579 }
580 }
581
582 /**
583 * The EditorKit used by this TextUI.
584 */
585 private static EditorKit kit;
586
587 /**
588 * The combined event handler for text components.
589 *
590 * This is package private to avoid accessor methods.
591 */
592 Handler handler;
593
594 /**
595 * The root view.
596 *
597 * This is package private to avoid accessor methods.
598 */
599 RootView rootView;
600
601 /**
602 * The text component that we handle.
603 */
604 JTextComponent textComponent;
605
606 /**
607 * Creates a new <code>BasicTextUI</code> instance.
608 */
609 public BasicTextUI()
610 {
611 // Nothing to do here.
612 }
613
614 /**
615 * Creates a {@link Caret} that should be installed into the text component.
616 *
617 * @return a caret that should be installed into the text component
618 */
619 protected Caret createCaret()
620 {
621 return new BasicCaret();
622 }
623
624 /**
625 * Creates a {@link Highlighter} that should be installed into the text
626 * component.
627 *
628 * @return a <code>Highlighter</code> for the text component
629 */
630 protected Highlighter createHighlighter()
631 {
632 return new BasicHighlighter();
633 }
634
635 /**
636 * The text component that is managed by this UI.
637 *
638 * @return the text component that is managed by this UI
639 */
640 protected final JTextComponent getComponent()
641 {
642 return textComponent;
643 }
644
645 /**
646 * Installs this UI on the text component.
647 *
648 * @param c the text component on which to install the UI
649 */
650 public void installUI(final JComponent c)
651 {
652 textComponent = (JTextComponent) c;
653
654 if (rootView == null)
655 rootView = new RootView();
656
657 installDefaults();
658 installFixedDefaults();
659
660 // These listeners must be installed outside of installListeners(),
661 // because overriding installListeners() doesn't prevent installing
662 // these in the RI, but overriding isntallUI() does.
663 if (handler == null)
664 handler = new Handler();
665 textComponent.addPropertyChangeListener(handler);
666 Document doc = textComponent.getDocument();
667 if (doc == null)
668 {
669 // The Handler takes care of installing the necessary listeners
670 // on the document here.
671 doc = getEditorKit(textComponent).createDefaultDocument();
672 textComponent.setDocument(doc);
673 }
674 else
675 {
676 // Must install the document listener.
677 doc.addDocumentListener(handler);
678 modelChanged();
679 }
680
681 installListeners();
682 installKeyboardActions();
683 }
684
685 /**
686 * Installs UI defaults on the text components.
687 */
688 protected void installDefaults()
689 {
690 String prefix = getPropertyPrefix();
691 // Install the standard properties.
692 LookAndFeel.installColorsAndFont(textComponent, prefix + ".background",
693 prefix + ".foreground", prefix + ".font");
694 LookAndFeel.installBorder(textComponent, prefix + ".border");
695
696 // Some additional text component only properties.
697 Color color = textComponent.getCaretColor();
698 if (color == null || color instanceof UIResource)
699 {
700 color = UIManager.getColor(prefix + ".caretForeground");
701 textComponent.setCaretColor(color);
702 }
703
704 // Fetch the colors for enabled/disabled text components.
705 color = textComponent.getDisabledTextColor();
706 if (color == null || color instanceof UIResource)
707 {
708 color = UIManager.getColor(prefix + ".inactiveForeground");
709 textComponent.setDisabledTextColor(color);
710 }
711 color = textComponent.getSelectedTextColor();
712 if (color == null || color instanceof UIResource)
713 {
714 color = UIManager.getColor(prefix + ".selectionForeground");
715 textComponent.setSelectedTextColor(color);
716 }
717 color = textComponent.getSelectionColor();
718 if (color == null || color instanceof UIResource)
719 {
720 color = UIManager.getColor(prefix + ".selectionBackground");
721 textComponent.setSelectionColor(color);
722 }
723
724 Insets margin = textComponent.getMargin();
725 if (margin == null || margin instanceof UIResource)
726 {
727 margin = UIManager.getInsets(prefix + ".margin");
728 textComponent.setMargin(margin);
729 }
730
731 }
732
733 /**
734 * Installs defaults that can't be overridden by overriding
735 * installDefaults().
736 */
737 private void installFixedDefaults()
738 {
739 String prefix = getPropertyPrefix();
740 Caret caret = textComponent.getCaret();
741 if (caret == null || caret instanceof UIResource)
742 {
743 caret = createCaret();
744 textComponent.setCaret(caret);
745 caret.setBlinkRate(UIManager.getInt(prefix + ".caretBlinkRate"));
746 }
747
748 Highlighter highlighter = textComponent.getHighlighter();
749 if (highlighter == null || highlighter instanceof UIResource)
750 textComponent.setHighlighter(createHighlighter());
751
752 }
753
754 /**
755 * Install all listeners on the text component.
756 */
757 protected void installListeners()
758 {
759 //
760 if (SystemProperties.getProperty("gnu.swing.text.no-xlike-clipboard")
761 == null)
762 {
763 if (focusListener == null)
764 focusListener = new FocusHandler();
765 textComponent.addFocusListener(focusListener);
766 }
767 }
768
769 /**
770 * Returns the name of the keymap for this type of TextUI.
771 *
772 * This is implemented so that the classname of this TextUI
773 * without the package prefix is returned. This way subclasses
774 * don't have to override this method.
775 *
776 * @return the name of the keymap for this TextUI
777 */
778 protected String getKeymapName()
779 {
780 String fullClassName = getClass().getName();
781 int index = fullClassName.lastIndexOf('.');
782 String className = fullClassName.substring(index + 1);
783 return className;
784 }
785
786 /**
787 * Creates the {@link Keymap} that is installed on the text component.
788 *
789 * @return the {@link Keymap} that is installed on the text component
790 */
791 protected Keymap createKeymap()
792 {
793 String keymapName = getKeymapName();
794 Keymap keymap = JTextComponent.getKeymap(keymapName);
795 if (keymap == null)
796 {
797 Keymap parentMap =
798 JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
799 keymap = JTextComponent.addKeymap(keymapName, parentMap);
800 Object val = UIManager.get(getPropertyPrefix() + ".keyBindings");
801 if (val != null && val instanceof JTextComponent.KeyBinding[])
802 {
803 JTextComponent.KeyBinding[] bindings =
804 (JTextComponent.KeyBinding[]) val;
805 JTextComponent.loadKeymap(keymap, bindings,
806 getComponent().getActions());
807 }
808 }
809 return keymap;
810 }
811
812 /**
813 * Installs the keyboard actions on the text components.
814 */
815 protected void installKeyboardActions()
816 {
817 // This is only there for backwards compatibility.
818 textComponent.setKeymap(createKeymap());
819
820 // load any bindings for the newer InputMap / ActionMap interface
821 SwingUtilities.replaceUIInputMap(textComponent, JComponent.WHEN_FOCUSED,
822 getInputMap());
823 SwingUtilities.replaceUIActionMap(textComponent, getActionMap());
824 }
825
826 /**
827 * Creates an ActionMap to be installed on the text component.
828 *
829 * @return an ActionMap to be installed on the text component
830 */
831 private ActionMap getActionMap()
832 {
833 // Note: There are no .actionMap entries in the standard L&Fs. However,
834 // with the RI it is possible to install action maps via such keys, so
835 // we must load them too. It can be observed that when there is no
836 // .actionMap entry in the UIManager, one gets installed after a text
837 // component of that type has been loaded.
838 String prefix = getPropertyPrefix();
839 String amName = prefix + ".actionMap";
840 ActionMap am = (ActionMap) UIManager.get(amName);
841 if (am == null)
842 {
843 am = createActionMap();
844 UIManager.put(amName, am);
845 }
846
847 ActionMap map = new ActionMapUIResource();
848 map.setParent(am);
849
850 return map;
851 }
852
853 /**
854 * Creates a default ActionMap for text components that have no UI default
855 * for this (the standard for the built-in L&Fs). The ActionMap is copied
856 * from the text component's getActions() method.
857 *
858 * @returna default ActionMap
859 */
860 private ActionMap createActionMap()
861 {
862 ActionMap am = new ActionMapUIResource();
863 Action[] actions = textComponent.getActions();
864 for (int i = actions.length - 1; i >= 0; i--)
865 {
866 Action action = actions[i];
867 am.put(action.getValue(Action.NAME), action);
868 }
869 // Add TransferHandler's actions here. They don't seem to be in the
870 // JTextComponent's default actions, and I can't make up a better place
871 // to add them.
872 Action copyAction = TransferHandler.getCopyAction();
873 am.put(copyAction.getValue(Action.NAME), copyAction);
874 Action cutAction = TransferHandler.getCutAction();
875 am.put(cutAction.getValue(Action.NAME), cutAction);
876 Action pasteAction = TransferHandler.getPasteAction();
877 am.put(pasteAction.getValue(Action.NAME), pasteAction);
878
879 return am;
880 }
881
882 /**
883 * Gets the input map for the specified <code>condition</code>.
884 *
885 * @return the InputMap for the specified condition
886 */
887 private InputMap getInputMap()
888 {
889 InputMap im = new InputMapUIResource();
890 String prefix = getPropertyPrefix();
891 InputMap shared =
892 (InputMap) SharedUIDefaults.get(prefix + ".focusInputMap");
893 if (shared != null)
894 im.setParent(shared);
895 return im;
896 }
897
898 /**
899 * Uninstalls this TextUI from the text component.
900 *
901 * @param component the text component to uninstall the UI from
902 */
903 public void uninstallUI(final JComponent component)
904 {
905 textComponent.removePropertyChangeListener(handler);
906 textComponent.getDocument().removeDocumentListener(handler);
907 rootView.setView(null);
908
909 uninstallDefaults();
910 uninstallFixedDefaults();
911 uninstallListeners();
912 uninstallKeyboardActions();
913
914 textComponent = null;
915 }
916
917 /**
918 * Uninstalls all default properties that have previously been installed by
919 * this UI.
920 */
921 protected void uninstallDefaults()
922 {
923 if (textComponent.getCaretColor() instanceof UIResource)
924 textComponent.setCaretColor(null);
925 if (textComponent.getSelectionColor() instanceof UIResource)
926 textComponent.setSelectionColor(null);
927 if (textComponent.getDisabledTextColor() instanceof UIResource)
928 textComponent.setDisabledTextColor(null);
929 if (textComponent.getSelectedTextColor() instanceof UIResource)
930 textComponent.setSelectedTextColor(null);
931 LookAndFeel.uninstallBorder(textComponent);
932 if (textComponent.getMargin() instanceof UIResource)
933 textComponent.setMargin(null);
934 }
935
936 /**
937 * Uninstalls additional fixed defaults that were installed
938 * by installFixedDefaults().
939 */
940 private void uninstallFixedDefaults()
941 {
942 if (textComponent.getCaret() instanceof UIResource)
943 textComponent.setCaret(null);
944 if (textComponent.getHighlighter() instanceof UIResource)
945 textComponent.setHighlighter(null);
946 }
947
948 /**
949 * Uninstalls all listeners that have previously been installed by
950 * this UI.
951 */
952 protected void uninstallListeners()
953 {
954 // Don't nullify the focusListener field, as it is static and shared
955 // between components.
956 if (focusListener != null)
957 textComponent.removeFocusListener(focusListener);
958 }
959
960 /**
961 * Uninstalls all keyboard actions that have previously been installed by
962 * this UI.
963 */
964 protected void uninstallKeyboardActions()
965 {
966 SwingUtilities.replaceUIInputMap(textComponent, JComponent.WHEN_FOCUSED,
967 null);
968 SwingUtilities.replaceUIActionMap(textComponent, null);
969 }
970
971 /**
972 * Returns the property prefix by which the text component's UIDefaults
973 * are looked up.
974 *
975 * @return the property prefix by which the text component's UIDefaults
976 * are looked up
977 */
978 protected abstract String getPropertyPrefix();
979
980 /**
981 * Returns the preferred size of the text component.
982 *
983 * @param c not used here
984 *
985 * @return the preferred size of the text component
986 */
987 public Dimension getPreferredSize(JComponent c)
988 {
989 Dimension d = c.getSize();
990 Insets i = c.getInsets();
991 // We need to lock here, since we require the view hierarchy to _not_
992 // change in between.
993 float w;
994 float h;
995 Document doc = textComponent.getDocument();
996 if (doc instanceof AbstractDocument)
997 ((AbstractDocument) doc).readLock();
998 try
999 {
1000 if (d.width > (i.left + i.right) && d.height > (i.top + i.bottom))
1001 {
1002 rootView.setSize(d.width - i.left - i.right,
1003 d.height - i.top - i.bottom);
1004 }
1005 else
1006 {
1007 // Not laid out yet. Force some pseudo size.
1008 rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
1009 }
1010 w = rootView.getPreferredSpan(View.X_AXIS);
1011 h = rootView.getPreferredSpan(View.Y_AXIS);
1012 }
1013 finally
1014 {
1015 if (doc instanceof AbstractDocument)
1016 ((AbstractDocument) doc).readUnlock();
1017 }
1018 Dimension size = new Dimension((int) w + i.left + i.right,
1019 (int) h + i.top + i.bottom);
1020 return size;
1021 }
1022
1023 /**
1024 * Returns the maximum size for text components that use this UI.
1025 *
1026 * This returns (Integer.MAX_VALUE, Integer.MAX_VALUE).
1027 *
1028 * @param c not used here
1029 *
1030 * @return the maximum size for text components that use this UI
1031 */
1032 public Dimension getMaximumSize(JComponent c)
1033 {
1034 Dimension d = new Dimension();
1035 Insets i = c.getInsets();
1036 Document doc = textComponent.getDocument();
1037 // We need to lock here, since we require the view hierarchy to _not_
1038 // change in between.
1039 if (doc instanceof AbstractDocument)
1040 ((AbstractDocument) doc).readLock();
1041 try
1042 {
1043 // Check for overflow here.
1044 d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS)
1045 + i.left + i.right, Integer.MAX_VALUE);
1046 d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS)
1047 + i.top + i.bottom, Integer.MAX_VALUE);
1048 }
1049 finally
1050 {
1051 if (doc instanceof AbstractDocument)
1052 ((AbstractDocument) doc).readUnlock();
1053 }
1054 return d;
1055 }
1056
1057 /**
1058 * Returns the minimum size for text components. This returns the size
1059 * of the component's insets.
1060 *
1061 * @return the minimum size for text components
1062 */
1063 public Dimension getMinimumSize(JComponent c)
1064 {
1065 Dimension d = new Dimension();
1066 Document doc = textComponent.getDocument();
1067 // We need to lock here, since we require the view hierarchy to _not_
1068 // change in between.
1069 if (doc instanceof AbstractDocument)
1070 ((AbstractDocument) doc).readLock();
1071 try
1072 {
1073 d.width = (int) rootView.getMinimumSpan(View.X_AXIS);
1074 d.height = (int) rootView.getMinimumSpan(View.Y_AXIS);
1075 }
1076 finally
1077 {
1078 if (doc instanceof AbstractDocument)
1079 ((AbstractDocument) doc).readUnlock();
1080 }
1081 Insets i = c.getInsets();
1082 d.width += i.left + i.right;
1083 d.height += i.top + i.bottom;
1084 return d;
1085 }
1086
1087 /**
1088 * Paints the text component. This acquires a read lock on the model and then
1089 * calls {@link #paintSafely(Graphics)} in order to actually perform the
1090 * painting.
1091 *
1092 * @param g the <code>Graphics</code> context to paint to
1093 * @param c not used here
1094 */
1095 public final void paint(Graphics g, JComponent c)
1096 {
1097 try
1098 {
1099 Document doc = textComponent.getDocument();
1100 if (doc instanceof AbstractDocument)
1101 {
1102 AbstractDocument aDoc = (AbstractDocument) doc;
1103 aDoc.readLock();
1104 }
1105 paintSafely(g);
1106 }
1107 finally
1108 {
1109 Document doc = textComponent.getDocument();
1110 if (doc instanceof AbstractDocument)
1111 {
1112 AbstractDocument aDoc = (AbstractDocument) doc;
1113 aDoc.readUnlock();
1114 }
1115 }
1116 }
1117
1118 /**
1119 * This paints the text component while beeing sure that the model is not
1120 * modified while painting.
1121 *
1122 * The following is performed in this order:
1123 * <ol>
1124 * <li>If the text component is opaque, the background is painted by
1125 * calling {@link #paintBackground(Graphics)}.</li>
1126 * <li>If there is a highlighter, the highlighter is painted.</li>
1127 * <li>The view hierarchy is painted.</li>
1128 * <li>The Caret is painter.</li>
1129 * </ol>
1130 *
1131 * @param g the <code>Graphics</code> context to paint to
1132 */
1133 protected void paintSafely(Graphics g)
1134 {
1135 Caret caret = textComponent.getCaret();
1136 Highlighter highlighter = textComponent.getHighlighter();
1137
1138 if (textComponent.isOpaque())
1139 paintBackground(g);
1140
1141 // Try painting with the highlighter without checking whether there
1142 // is a selection because a highlighter can be used to do more than
1143 // marking selected text.
1144 if (highlighter != null)
1145 {
1146 // Handle restoring of the color here to prevent
1147 // drawing problems when the Highlighter implementor
1148 // forgets to restore it.
1149 Color oldColor = g.getColor();
1150 highlighter.paint(g);
1151 g.setColor(oldColor);
1152 }
1153
1154 rootView.paint(g, getVisibleEditorRect());
1155
1156 if (caret != null && textComponent.hasFocus())
1157 caret.paint(g);
1158 }
1159
1160 /**
1161 * Paints the background of the text component.
1162 *
1163 * @param g the <code>Graphics</code> context to paint to
1164 */
1165 protected void paintBackground(Graphics g)
1166 {
1167 Color old = g.getColor();
1168 g.setColor(textComponent.getBackground());
1169 g.fillRect(0, 0, textComponent.getWidth(), textComponent.getHeight());
1170 g.setColor(old);
1171 }
1172
1173 /**
1174 * Overridden for better control over background painting. This now simply
1175 * calls {@link #paint} and this delegates the background painting to
1176 * {@link #paintBackground}.
1177 *
1178 * @param g the graphics to use
1179 * @param c the component to be painted
1180 */
1181 public void update(Graphics g, JComponent c)
1182 {
1183 paint(g, c);
1184 }
1185
1186 /**
1187 * Marks the specified range inside the text component's model as
1188 * damaged and queues a repaint request.
1189 *
1190 * @param t the text component
1191 * @param p0 the start location inside the document model of the range that
1192 * is damaged
1193 * @param p1 the end location inside the document model of the range that
1194 * is damaged
1195 */
1196 public void damageRange(JTextComponent t, int p0, int p1)
1197 {
1198 damageRange(t, p0, p1, Position.Bias.Forward, Position.Bias.Backward);
1199 }
1200
1201 /**
1202 * Marks the specified range inside the text component's model as
1203 * damaged and queues a repaint request. This variant of this method
1204 * allows a {@link Position.Bias} object to be specified for the start
1205 * and end location of the range.
1206 *
1207 * @param t the text component
1208 * @param p0 the start location inside the document model of the range that
1209 * is damaged
1210 * @param p1 the end location inside the document model of the range that
1211 * is damaged
1212 * @param firstBias the bias for the start location
1213 * @param secondBias the bias for the end location
1214 */
1215 public void damageRange(JTextComponent t, int p0, int p1,
1216 Position.Bias firstBias, Position.Bias secondBias)
1217 {
1218 Rectangle alloc = getVisibleEditorRect();
1219 if (alloc != null)
1220 {
1221 Document doc = t.getDocument();
1222
1223 // Acquire lock here to avoid structural changes in between.
1224 if (doc instanceof AbstractDocument)
1225 ((AbstractDocument) doc).readLock();
1226 try
1227 {
1228 rootView.setSize(alloc.width, alloc.height);
1229 Shape damage = rootView.modelToView(p0, firstBias, p1, secondBias,
1230 alloc);
1231 Rectangle r = damage instanceof Rectangle ? (Rectangle) damage
1232 : damage.getBounds();
1233 textComponent.repaint(r.x, r.y, r.width, r.height);
1234 }
1235 catch (BadLocationException ex)
1236 {
1237 // Lets ignore this as it causes no serious problems.
1238 // For debugging, comment this out.
1239 // ex.printStackTrace();
1240 }
1241 finally
1242 {
1243 // Release lock.
1244 if (doc instanceof AbstractDocument)
1245 ((AbstractDocument) doc).readUnlock();
1246 }
1247 }
1248 }
1249
1250 /**
1251 * Returns the {@link EditorKit} used for the text component that is managed
1252 * by this UI.
1253 *
1254 * @param t the text component
1255 *
1256 * @return the {@link EditorKit} used for the text component that is managed
1257 * by this UI
1258 */
1259 public EditorKit getEditorKit(JTextComponent t)
1260 {
1261 if (kit == null)
1262 kit = new DefaultEditorKit();
1263 return kit;
1264 }
1265
1266 /**
1267 * Gets the next position inside the document model that is visible on
1268 * screen, starting from <code>pos</code>.
1269 *
1270 * @param t the text component
1271 * @param pos the start positionn
1272 * @param b the bias for pos
1273 * @param direction the search direction
1274 * @param biasRet filled by the method to indicate the bias of the return
1275 * value
1276 *
1277 * @return the next position inside the document model that is visible on
1278 * screen
1279 */
1280 public int getNextVisualPositionFrom(JTextComponent t, int pos,
1281 Position.Bias b, int direction,
1282 Position.Bias[] biasRet)
1283 throws BadLocationException
1284 {
1285 int offset = -1;
1286 Document doc = textComponent.getDocument();
1287 if (doc instanceof AbstractDocument)
1288 ((AbstractDocument) doc).readLock();
1289 try
1290 {
1291 Rectangle alloc = getVisibleEditorRect();
1292 if (alloc != null)
1293 {
1294 rootView.setSize(alloc.width, alloc.height);
1295 offset = rootView.getNextVisualPositionFrom(pos, b, alloc,
1296 direction, biasRet);
1297 }
1298 }
1299 finally
1300 {
1301 if (doc instanceof AbstractDocument)
1302 ((AbstractDocument) doc).readUnlock();
1303 }
1304 return offset;
1305 }
1306
1307 /**
1308 * Returns the root {@link View} of a text component.
1309 *
1310 * @return the root {@link View} of a text component
1311 */
1312 public View getRootView(JTextComponent t)
1313 {
1314 return rootView;
1315 }
1316
1317 /**
1318 * Maps a position in the document into the coordinate space of the View.
1319 * The output rectangle usually reflects the font height but has a width
1320 * of zero. A bias of {@link Position.Bias#Forward} is used in this method.
1321 *
1322 * @param t the text component
1323 * @param pos the position of the character in the model
1324 *
1325 * @return a rectangle that gives the location of the document position
1326 * inside the view coordinate space
1327 *
1328 * @throws BadLocationException if <code>pos</code> is invalid
1329 * @throws IllegalArgumentException if b is not one of the above listed
1330 * valid values
1331 */
1332 public Rectangle modelToView(JTextComponent t, int pos)
1333 throws BadLocationException
1334 {
1335 return modelToView(t, pos, Position.Bias.Forward);
1336 }
1337
1338 /**
1339 * Maps a position in the document into the coordinate space of the View.
1340 * The output rectangle usually reflects the font height but has a width
1341 * of zero.
1342 *
1343 * @param t the text component
1344 * @param pos the position of the character in the model
1345 * @param bias either {@link Position.Bias#Forward} or
1346 * {@link Position.Bias#Backward} depending on the preferred
1347 * direction bias. If <code>null</code> this defaults to
1348 * <code>Position.Bias.Forward</code>
1349 *
1350 * @return a rectangle that gives the location of the document position
1351 * inside the view coordinate space
1352 *
1353 * @throws BadLocationException if <code>pos</code> is invalid
1354 * @throws IllegalArgumentException if b is not one of the above listed
1355 * valid values
1356 */
1357 public Rectangle modelToView(JTextComponent t, int pos, Position.Bias bias)
1358 throws BadLocationException
1359 {
1360 // We need to read-lock here because we depend on the document
1361 // structure not beeing changed in between.
1362 Document doc = textComponent.getDocument();
1363 if (doc instanceof AbstractDocument)
1364 ((AbstractDocument) doc).readLock();
1365 Rectangle rect = null;
1366 try
1367 {
1368 Rectangle r = getVisibleEditorRect();
1369 if (r != null)
1370 {
1371 rootView.setSize(r.width, r.height);
1372 Shape s = rootView.modelToView(pos, r, bias);
1373 if (s != null)
1374 rect = s.getBounds();
1375 }
1376 }
1377 finally
1378 {
1379 if (doc instanceof AbstractDocument)
1380 ((AbstractDocument) doc).readUnlock();
1381 }
1382 return rect;
1383 }
1384
1385 /**
1386 * Maps a point in the <code>View</code> coordinate space to a position
1387 * inside a document model.
1388 *
1389 * @param t the text component
1390 * @param pt the point to be mapped
1391 *
1392 * @return the position inside the document model that corresponds to
1393 * <code>pt</code>
1394 */
1395 public int viewToModel(JTextComponent t, Point pt)
1396 {
1397 return viewToModel(t, pt, new Position.Bias[1]);
1398 }
1399
1400 /**
1401 * Maps a point in the <code>View</code> coordinate space to a position
1402 * inside a document model.
1403 *
1404 * @param t the text component
1405 * @param pt the point to be mapped
1406 * @param biasReturn filled in by the method to indicate the bias of the
1407 * return value
1408 *
1409 * @return the position inside the document model that corresponds to
1410 * <code>pt</code>
1411 */
1412 public int viewToModel(JTextComponent t, Point pt, Position.Bias[] biasReturn)
1413 {
1414 int offset = -1;
1415 Document doc = textComponent.getDocument();
1416 if (doc instanceof AbstractDocument)
1417 ((AbstractDocument) doc).readLock();
1418 try
1419 {
1420 Rectangle alloc = getVisibleEditorRect();
1421 if (alloc != null)
1422 {
1423 rootView.setSize(alloc.width, alloc.height);
1424 offset = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn);
1425 }
1426 }
1427 finally
1428 {
1429 if (doc instanceof AbstractDocument)
1430 ((AbstractDocument) doc).readUnlock();
1431 }
1432 return offset;
1433 }
1434
1435 /**
1436 * Creates a {@link View} for the specified {@link Element}.
1437 *
1438 * @param elem the <code>Element</code> to create a <code>View</code> for
1439 *
1440 * @see ViewFactory
1441 */
1442 public View create(Element elem)
1443 {
1444 // Subclasses have to implement this to get this functionality.
1445 return null;
1446 }
1447
1448 /**
1449 * Creates a {@link View} for the specified {@link Element}.
1450 *
1451 * @param elem the <code>Element</code> to create a <code>View</code> for
1452 * @param p0 the start offset
1453 * @param p1 the end offset
1454 *
1455 * @see ViewFactory
1456 */
1457 public View create(Element elem, int p0, int p1)
1458 {
1459 // Subclasses have to implement this to get this functionality.
1460 return null;
1461 }
1462
1463 /**
1464 * A cached Insets instance to be reused below.
1465 */
1466 private Insets cachedInsets;
1467
1468 /**
1469 * Returns the allocation to give the root view.
1470 *
1471 * @return the allocation to give the root view
1472 *
1473 * @specnote The allocation has nothing to do with visibility. According
1474 * to the specs the naming of this method is unfortunate and
1475 * has historical reasons
1476 */
1477 protected Rectangle getVisibleEditorRect()
1478 {
1479 int width = textComponent.getWidth();
1480 int height = textComponent.getHeight();
1481
1482 // Return null if the component has no valid size.
1483 if (width <= 0 || height <= 0)
1484 return null;
1485
1486 Insets insets = textComponent.getInsets(cachedInsets);
1487 return new Rectangle(insets.left, insets.top,
1488 width - insets.left - insets.right,
1489 height - insets.top - insets.bottom);
1490 }
1491
1492 /**
1493 * Sets the root view for the text component.
1494 *
1495 * @param view the <code>View</code> to be set as root view
1496 */
1497 protected final void setView(View view)
1498 {
1499 rootView.setView(view);
1500 textComponent.revalidate();
1501 textComponent.repaint();
1502 }
1503
1504 /**
1505 * Indicates that the model of a text component has changed. This
1506 * triggers a rebuild of the view hierarchy.
1507 */
1508 protected void modelChanged()
1509 {
1510 if (textComponent == null || rootView == null)
1511 return;
1512 ViewFactory factory = rootView.getViewFactory();
1513 if (factory == null)
1514 return;
1515 Document doc = textComponent.getDocument();
1516 if (doc == null)
1517 return;
1518 Element elem = doc.getDefaultRootElement();
1519 if (elem == null)
1520 return;
1521 View view = factory.create(elem);
1522 setView(view);
1523 }
1524
1525 /**
1526 * Receives notification whenever one of the text component's bound
1527 * properties changes. This default implementation does nothing.
1528 * It is a hook that enables subclasses to react to property changes
1529 * on the text component.
1530 *
1531 * @param ev the property change event
1532 */
1533 protected void propertyChange(PropertyChangeEvent ev)
1534 {
1535 // The default implementation does nothing.
1536 }
1537
1538 }