1. /*
  2. * @(#)SynthTextUI.java 1.11 03/02/18
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.java.swing.plaf.gtk;
  8. import javax.swing.*;
  9. import javax.swing.event.*;
  10. import javax.swing.text.*;
  11. import javax.swing.plaf.*;
  12. import javax.swing.border.Border;
  13. import java.beans.*;
  14. import java.awt.*;
  15. import java.awt.event.*;
  16. import java.awt.dnd.*;
  17. import java.awt.im.InputContext;
  18. import java.awt.datatransfer.*;
  19. import java.util.*;
  20. import java.io.*;
  21. /**
  22. * <p>
  23. * Basis of a text components look-and-feel in the Synth look and
  24. * feel. This provides the
  25. * basic editor view and controller services that may be useful
  26. * when creating a look-and-feel for an extension of
  27. * <code>JTextComponent</code>.
  28. * <p>
  29. * Most state is held in the associated <code>JTextComponent</code>
  30. * as bound properties, and the UI installs default values for the
  31. * various properties. This default will install something for
  32. * all of the properties. Typically, a LAF implementation will
  33. * do more however. At a minimum, a LAF would generally install
  34. * key bindings.
  35. * <p>
  36. * This class also provides some concurrency support if the
  37. * <code>Document</code> associated with the JTextComponent is a subclass of
  38. * <code>AbstractDocument</code>. Access to the View (or View hierarchy) is
  39. * serialized between any thread mutating the model and the Swing
  40. * event thread (which is expected to render, do model/view coordinate
  41. * translation, etc). <em>Any access to the root view should first
  42. * acquire a read-lock on the AbstractDocument and release that lock
  43. * in a finally block.</em>
  44. * <p>
  45. * An important method to define is the {@link #getPropertyPrefix} method
  46. * which is used as the basis of the keys used to fetch defaults
  47. * from the UIManager. The string should reflect the type of
  48. * TextUI (eg. TextField, TextArea, etc) without the particular
  49. * LAF part of the name (eg Metal, Motif, etc).
  50. * <p>
  51. * To build a view of the model, one of the following strategies
  52. * can be employed.
  53. * <ol>
  54. * <li>
  55. * One strategy is to simply redefine the
  56. * ViewFactory interface in the UI. By default, this UI itself acts
  57. * as the factory for View implementations. This is useful
  58. * for simple factories. To do this reimplement the
  59. * {@link #create} method.
  60. * <li>
  61. * A common strategy for creating more complex types of documents
  62. * is to have the EditorKit implementation return a factory. Since
  63. * the EditorKit ties all of the pieces necessary to maintain a type
  64. * of document, the factory is typically an important part of that
  65. * and should be produced by the EditorKit implementation.
  66. * <li>
  67. * A less common way to create more complex types is to have
  68. * the UI implementation create a.
  69. * separate object for the factory. To do this, the
  70. * {@link #createViewFactory} method should be reimplemented to
  71. * return some factory.
  72. * </ol>
  73. * <p>
  74. * <strong>Warning:</strong>
  75. * Serialized objects of this class will not be compatible with
  76. * future Swing releases. The current serialization support is
  77. * appropriate for short term storage or RMI between applications running
  78. * the same version of Swing. As of 1.4, support for long term storage
  79. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  80. * has been added to the <code>java.beans</code> package.
  81. * Please see {@link java.beans.XMLEncoder}.
  82. *
  83. * @author Shannon Hickey
  84. * @version 1.11 02/18/03 (based on revision 1.82 of BasicTextUI)
  85. */
  86. abstract class SynthTextUI extends TextUI implements SynthUI, ViewFactory {
  87. private SynthStyle style;
  88. private static final EditorKit defaultKit = new DefaultEditorKit();
  89. transient JTextComponent editor;
  90. transient boolean painted;
  91. transient RootView rootView = new RootView();
  92. transient UpdateHandler updateHandler = new UpdateHandler();
  93. private static final TransferHandler defaultTransferHandler = new TextTransferHandler();
  94. private static DropTargetListener defaultDropTargetListener = null;
  95. private static final TextDragGestureRecognizer defaultDragRecognizer = new TextDragGestureRecognizer();
  96. private static final Position.Bias[] discardBias = new Position.Bias[1];
  97. public static class SynthCaret extends DefaultCaret implements UIResource {}
  98. public static class SynthHighlighter extends DefaultHighlighter implements UIResource {}
  99. void forceFetchStyle(JTextComponent comp) {
  100. style = null;
  101. fetchStyle(comp);
  102. }
  103. private void fetchStyle(JTextComponent comp) {
  104. SynthContext context = getContext(comp, ENABLED);
  105. SynthStyle oldStyle = style;
  106. style = SynthLookAndFeel.updateStyle(context, this);
  107. if (style != oldStyle) {
  108. String prefix = getPropertyPrefix();
  109. Color color = editor.getCaretColor();
  110. if (color == null || color instanceof UIResource) {
  111. editor.setCaretColor((Color)style.get(context, prefix + ".caretForeground"));
  112. }
  113. Color fg = editor.getForeground();
  114. if (fg == null || fg instanceof UIResource) {
  115. editor.setSelectionColor(style.getColor(context, ColorType.TEXT_FOREGROUND));
  116. }
  117. context.setComponentState(SELECTED | FOCUSED);
  118. Color s = editor.getSelectionColor();
  119. if (s == null || s instanceof UIResource) {
  120. editor.setSelectionColor(style.getColor(context, ColorType.TEXT_BACKGROUND));
  121. }
  122. Color sfg = editor.getSelectedTextColor();
  123. if (sfg == null || sfg instanceof UIResource) {
  124. editor.setSelectedTextColor(style.getColor(context, ColorType.TEXT_FOREGROUND));
  125. }
  126. context.setComponentState(DISABLED);
  127. Color dfg = editor.getDisabledTextColor();
  128. if (dfg == null || dfg instanceof UIResource) {
  129. editor.setDisabledTextColor((Color)style.get(context, ColorType.FOREGROUND));
  130. }
  131. Insets margin = editor.getMargin();
  132. if (margin == null || margin instanceof UIResource) {
  133. margin = (Insets)style.get(context, prefix + ".margin");
  134. if (margin == null) {
  135. // Some places assume margins are non-null.
  136. margin = SynthLookAndFeel.EMPTY_UIRESOURCE_INSETS;
  137. }
  138. editor.setMargin(margin);
  139. }
  140. Caret caret = editor.getCaret();
  141. if (caret instanceof UIResource) {
  142. Object o = style.get(context, prefix + ".caretBlinkRate");
  143. if (o != null && o instanceof Integer) {
  144. Integer rate = (Integer)o;
  145. caret.setBlinkRate(rate.intValue());
  146. }
  147. }
  148. }
  149. context.dispose();
  150. }
  151. public SynthContext getContext(JComponent c) {
  152. return getContext(c, getComponentState(c));
  153. }
  154. private SynthContext getContext(JComponent c, int state) {
  155. return SynthContext.getContext(SynthContext.class, c,
  156. SynthLookAndFeel.getRegion(c), style, state);
  157. }
  158. private Region getRegion(JComponent c) {
  159. return SynthLookAndFeel.getRegion(c);
  160. }
  161. private int getComponentState(JComponent c) {
  162. return SynthLookAndFeel.getComponentState(c);
  163. }
  164. public void update(Graphics g, JComponent c) {
  165. SynthContext context = getContext(c);
  166. SynthLookAndFeel.update(context, g);
  167. paint(context, g);
  168. context.dispose();
  169. }
  170. public void paint(Graphics g, JComponent c) {
  171. SynthContext context = getContext(c);
  172. paint(context, g);
  173. context.dispose();
  174. }
  175. /**
  176. * Paints the interface. This is routed to the
  177. * paintSafely method under the guarantee that
  178. * the model won't change from the view of this thread
  179. * while it's rendering (if the associated model is
  180. * derived from AbstractDocument). This enables the
  181. * model to potentially be updated asynchronously.
  182. */
  183. protected void paint(SynthContext context, Graphics g) {
  184. if ((rootView.getViewCount() > 0) && (rootView.getView(0) != null)) {
  185. Document doc = editor.getDocument();
  186. if (doc instanceof AbstractDocument) {
  187. ((AbstractDocument)doc).readLock();
  188. }
  189. try {
  190. paintSafely(g);
  191. } finally {
  192. if (doc instanceof AbstractDocument) {
  193. ((AbstractDocument)doc).readUnlock();
  194. }
  195. }
  196. }
  197. }
  198. /**
  199. * Creates a new UI.
  200. */
  201. public SynthTextUI() {
  202. painted = false;
  203. }
  204. /**
  205. * Creates the object to use for a caret. By default an
  206. * instance of SynthCaret is created. This method
  207. * can be redefined to provide something else that implements
  208. * the InputPosition interface or a subclass of Caret.
  209. *
  210. * @return the caret object
  211. */
  212. protected Caret createCaret() {
  213. return new SynthCaret();
  214. }
  215. /**
  216. * Creates the object to use for adding highlights. By default
  217. * an instance of SynthHighlighter is created. This method
  218. * can be redefined to provide something else that implements
  219. * the Highlighter interface or a subclass of DefaultHighlighter.
  220. *
  221. * @return the highlighter
  222. */
  223. protected Highlighter createHighlighter() {
  224. return new SynthHighlighter();
  225. }
  226. /**
  227. * This method gets called when a bound property is changed
  228. * on the associated JTextComponent. This is a hook
  229. * which UI implementations may change to reflect how the
  230. * UI displays bound properties of JTextComponent subclasses.
  231. * This is implemented to do nothing (i.e. the response to
  232. * properties in JTextComponent itself are handled prior
  233. * to calling this method).
  234. *
  235. * @param evt the property change event
  236. */
  237. protected void propertyChange(PropertyChangeEvent evt) {
  238. }
  239. /**
  240. * Gets the name used as a key to look up properties through the
  241. * UIManager. This is used as a prefix to all the standard
  242. * text properties.
  243. *
  244. * @return the name
  245. */
  246. protected abstract String getPropertyPrefix();
  247. protected void installDefaults() {
  248. editor.addMouseListener(defaultDragRecognizer);
  249. editor.addMouseMotionListener(defaultDragRecognizer);
  250. String prefix = getPropertyPrefix();
  251. Caret caret = editor.getCaret();
  252. if (caret == null || caret instanceof UIResource) {
  253. editor.setCaret(createCaret());
  254. }
  255. Highlighter highlighter = editor.getHighlighter();
  256. if (highlighter == null || highlighter instanceof UIResource) {
  257. editor.setHighlighter(createHighlighter());
  258. }
  259. // this must come after creation of the caret since
  260. // fetchStyle might have to set the caret's blink rate
  261. fetchStyle(editor);
  262. TransferHandler th = editor.getTransferHandler();
  263. if (th == null || th instanceof UIResource) {
  264. editor.setTransferHandler(getTransferHandler());
  265. }
  266. DropTarget dropTarget = editor.getDropTarget();
  267. if (dropTarget instanceof UIResource) {
  268. if (defaultDropTargetListener == null) {
  269. defaultDropTargetListener = new TextDropTargetListener();
  270. }
  271. try {
  272. dropTarget.addDropTargetListener(defaultDropTargetListener);
  273. } catch (TooManyListenersException tmle) {
  274. // should not happen... swing drop target is multicast
  275. }
  276. }
  277. }
  278. protected void uninstallDefaults() {
  279. SynthContext context = getContext(editor, ENABLED);
  280. style.uninstallDefaults(context);
  281. context.dispose();
  282. style = null;
  283. editor.removeMouseListener(defaultDragRecognizer);
  284. editor.removeMouseMotionListener(defaultDragRecognizer);
  285. if (editor.getCaretColor() instanceof UIResource) {
  286. editor.setCaretColor(null);
  287. }
  288. if (editor.getSelectionColor() instanceof UIResource) {
  289. editor.setSelectionColor(null);
  290. }
  291. if (editor.getDisabledTextColor() instanceof UIResource) {
  292. editor.setDisabledTextColor(null);
  293. }
  294. if (editor.getSelectedTextColor() instanceof UIResource) {
  295. editor.setSelectedTextColor(null);
  296. }
  297. if (editor.getMargin() instanceof UIResource) {
  298. editor.setMargin(null);
  299. }
  300. if (editor.getCaret() instanceof UIResource) {
  301. editor.setCaret(null);
  302. }
  303. if (editor.getHighlighter() instanceof UIResource) {
  304. editor.setHighlighter(null);
  305. }
  306. if (editor.getTransferHandler() instanceof UIResource) {
  307. editor.setTransferHandler(null);
  308. }
  309. }
  310. /**
  311. * Installs listeners for the UI.
  312. */
  313. protected void installListeners() {
  314. }
  315. /**
  316. * Uninstalls listeners for the UI.
  317. */
  318. protected void uninstallListeners() {
  319. }
  320. protected void installKeyboardActions() {
  321. editor.setKeymap(JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP));
  322. InputMap km = getInputMap();
  323. if (km != null) {
  324. SwingUtilities.replaceUIInputMap(editor, JComponent.WHEN_FOCUSED,
  325. km);
  326. }
  327. ActionMap map = getActionMap();
  328. if (map != null) {
  329. SwingUtilities.replaceUIActionMap(editor, map);
  330. }
  331. updateFocusAcceleratorBinding(false);
  332. }
  333. /**
  334. * Get the InputMap to use for the UI.
  335. */
  336. InputMap getInputMap() {
  337. SynthContext context = getContext(editor, ENABLED);
  338. SynthStyle style = context.getStyle();
  339. InputMap map = new InputMapUIResource();
  340. InputMap shared =
  341. (InputMap)style.get(context, getPropertyPrefix() + ".focusInputMap");
  342. if (shared != null) {
  343. map.setParent(shared);
  344. }
  345. context.dispose();
  346. return map;
  347. }
  348. /**
  349. * Invoked when the focus accelerator changes, this will update the
  350. * key bindings as necessary.
  351. */
  352. void updateFocusAcceleratorBinding(boolean changed) {
  353. char accelerator = editor.getFocusAccelerator();
  354. if (changed || accelerator != '\0') {
  355. InputMap km = SwingUtilities.getUIInputMap
  356. (editor, JComponent.WHEN_IN_FOCUSED_WINDOW);
  357. if (km == null && accelerator != '\0') {
  358. km = new ComponentInputMapUIResource(editor);
  359. SwingUtilities.replaceUIInputMap(editor, JComponent.
  360. WHEN_IN_FOCUSED_WINDOW, km);
  361. ActionMap am = getActionMap();
  362. SwingUtilities.replaceUIActionMap(editor, am);
  363. }
  364. if (km != null) {
  365. km.clear();
  366. if (accelerator != '\0') {
  367. km.put(KeyStroke.getKeyStroke(accelerator,
  368. ActionEvent.ALT_MASK),
  369. "requestFocus");
  370. }
  371. }
  372. }
  373. }
  374. /**
  375. * Invoked when editable property is changed.
  376. *
  377. * removing 'TAB' and 'SHIFT-TAB' from traversalKeysSet in case
  378. * editor is editable
  379. * adding 'TAB' and 'SHIFT-TAB' to traversalKeysSet in case
  380. * editor is non editable
  381. */
  382. void updateFocusTraversalKeys() {
  383. /*
  384. * Fix for 4514331 Non-editable JTextArea and similar
  385. * should allow Tab to keyboard - accessibility
  386. */
  387. EditorKit editorKit = getEditorKit(editor);
  388. if ( editorKit != null
  389. && editorKit instanceof DefaultEditorKit) {
  390. Set storedForwardTraversalKeys = editor.
  391. getFocusTraversalKeys(KeyboardFocusManager.
  392. FORWARD_TRAVERSAL_KEYS);
  393. Set storedBackwardTraversalKeys = editor.
  394. getFocusTraversalKeys(KeyboardFocusManager.
  395. BACKWARD_TRAVERSAL_KEYS);
  396. Set forwardTraversalKeys =
  397. new HashSet(storedForwardTraversalKeys);
  398. Set backwardTraversalKeys =
  399. new HashSet(storedBackwardTraversalKeys);
  400. if (editor.isEditable()) {
  401. forwardTraversalKeys.
  402. remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0));
  403. backwardTraversalKeys.
  404. remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
  405. InputEvent.SHIFT_MASK));
  406. } else {
  407. forwardTraversalKeys.add(KeyStroke.
  408. getKeyStroke(KeyEvent.VK_TAB, 0));
  409. backwardTraversalKeys.
  410. add(KeyStroke.
  411. getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK));
  412. }
  413. editor.setFocusTraversalKeys(KeyboardFocusManager.
  414. FORWARD_TRAVERSAL_KEYS,
  415. forwardTraversalKeys);
  416. editor.setFocusTraversalKeys(KeyboardFocusManager.
  417. BACKWARD_TRAVERSAL_KEYS,
  418. backwardTraversalKeys);
  419. }
  420. }
  421. /**
  422. * Returns the <code>TransferHandler</code> that will be installed if
  423. * their isn't one installed on the <code>JTextComponent</code>.
  424. */
  425. TransferHandler getTransferHandler() {
  426. return defaultTransferHandler;
  427. }
  428. /**
  429. * Fetch an action map to use.
  430. */
  431. ActionMap getActionMap() {
  432. String mapName = getPropertyPrefix() + ".actionMap";
  433. ActionMap map = (ActionMap)UIManager.get(mapName);
  434. if (map == null) {
  435. map = createActionMap();
  436. if (map != null) {
  437. UIManager.getLookAndFeelDefaults().put(mapName, map);
  438. }
  439. }
  440. ActionMap componentMap = new ActionMapUIResource();
  441. componentMap.put("requestFocus", new FocusAction());
  442. /*
  443. * fix for bug 4515750
  444. * JTextField & non-editable JTextArea bind return key - default btn not accessible
  445. *
  446. * Wrap the return action so that it is only enabled when the
  447. * component is editable. This allows the default button to be
  448. * processed when the text component has focus and isn't editable.
  449. *
  450. */
  451. if (getEditorKit(editor) instanceof DefaultEditorKit) {
  452. if (map != null) {
  453. Object obj = map.get(DefaultEditorKit.insertBreakAction);
  454. if (obj != null
  455. && obj instanceof DefaultEditorKit.InsertBreakAction) {
  456. Action action = new TextActionWrapper((TextAction)obj);
  457. componentMap.put(action.getValue(Action.NAME),action);
  458. }
  459. }
  460. }
  461. if (map != null) {
  462. componentMap.setParent(map);
  463. }
  464. return componentMap;
  465. }
  466. /**
  467. * Create a default action map. This is basically the
  468. * set of actions found exported by the component.
  469. */
  470. ActionMap createActionMap() {
  471. ActionMap map = new ActionMapUIResource();
  472. Action[] actions = editor.getActions();
  473. //System.out.println("building map for UI: " + getPropertyPrefix());
  474. int n = actions.length;
  475. for (int i = 0; i < n; i++) {
  476. Action a = actions[i];
  477. map.put(a.getValue(Action.NAME), a);
  478. //System.out.println(" " + a.getValue(Action.NAME));
  479. }
  480. map.put(TransferHandler.getCutAction().getValue(Action.NAME),
  481. TransferHandler.getCutAction());
  482. map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
  483. TransferHandler.getCopyAction());
  484. map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
  485. TransferHandler.getPasteAction());
  486. return map;
  487. }
  488. protected void uninstallKeyboardActions() {
  489. editor.setKeymap(null);
  490. SwingUtilities.replaceUIInputMap(editor, JComponent.
  491. WHEN_IN_FOCUSED_WINDOW, null);
  492. SwingUtilities.replaceUIActionMap(editor, null);
  493. }
  494. /**
  495. * Fetches the text component associated with this
  496. * UI implementation. This will be null until
  497. * the ui has been installed.
  498. *
  499. * @return the editor component
  500. */
  501. protected final JTextComponent getComponent() {
  502. return editor;
  503. }
  504. /**
  505. * Flags model changes.
  506. * This is called whenever the model has changed.
  507. * It is implemented to rebuild the view hierarchy
  508. * to represent the default root element of the
  509. * associated model.
  510. */
  511. protected void modelChanged() {
  512. // create a view hierarchy
  513. ViewFactory f = rootView.getViewFactory();
  514. Document doc = editor.getDocument();
  515. Element elem = doc.getDefaultRootElement();
  516. setView(f.create(elem));
  517. }
  518. /**
  519. * Sets the current root of the view hierarchy and calls invalidate().
  520. * If there were any child components, they will be removed (i.e.
  521. * there are assumed to have come from components embedded in views).
  522. *
  523. * @param v the root view
  524. */
  525. protected final void setView(View v) {
  526. editor.removeAll();
  527. rootView.setView(v);
  528. painted = false;
  529. editor.revalidate();
  530. editor.repaint();
  531. }
  532. /**
  533. * Paints the interface safely with a guarantee that
  534. * the model won't change from the view of this thread.
  535. * This does the following things, rendering from
  536. * back to front.
  537. * <ol>
  538. * <li>
  539. * The highlights (if any) are painted.
  540. * <li>
  541. * The view hierarchy is painted.
  542. * <li>
  543. * The caret is painted.
  544. * </ol>
  545. *
  546. * @param g the graphics context
  547. */
  548. protected void paintSafely(Graphics g) {
  549. painted = true;
  550. Highlighter highlighter = editor.getHighlighter();
  551. Caret caret = editor.getCaret();
  552. // paint the highlights
  553. if (highlighter != null) {
  554. highlighter.paint(g);
  555. }
  556. // paint the view hierarchy
  557. Rectangle alloc = getVisibleEditorRect();
  558. rootView.paint(g, alloc);
  559. // paint the caret
  560. if (caret != null) {
  561. caret.paint(g);
  562. }
  563. }
  564. /**
  565. * Installs the UI for a component. This does the following
  566. * things.
  567. * <ol>
  568. * <li>
  569. * Set the associated component to opaque (can be changed
  570. * easily by a subclass or on JTextComponent directly),
  571. * which is the most common case. This will cause the
  572. * component's background color to be painted.
  573. * <li>
  574. * Install the default caret and highlighter into the
  575. * associated component.
  576. * <li>
  577. * Attach to the editor and model. If there is no
  578. * model, a default one is created.
  579. * <li>
  580. * create the view factory and the view hierarchy used
  581. * to represent the model.
  582. * </ol>
  583. *
  584. * @param c the editor component
  585. * @see ComponentUI#installUI
  586. */
  587. public void installUI(JComponent c) {
  588. if (c instanceof JTextComponent) {
  589. editor = (JTextComponent) c;
  590. // install defaults
  591. installDefaults();
  592. editor.setAutoscrolls(true);
  593. // attach to the model and editor
  594. editor.addPropertyChangeListener(updateHandler);
  595. Document doc = editor.getDocument();
  596. if (doc == null) {
  597. // no model, create a default one. This will
  598. // fire a notification to the updateHandler
  599. // which takes care of the rest.
  600. editor.setDocument(getEditorKit(editor).createDefaultDocument());
  601. } else {
  602. doc.addDocumentListener(updateHandler);
  603. modelChanged();
  604. }
  605. // install keymap
  606. installListeners();
  607. installKeyboardActions();
  608. LayoutManager oldLayout = editor.getLayout();
  609. if ((oldLayout == null) || (oldLayout instanceof UIResource)) {
  610. // by default, use default LayoutManger implementation that
  611. // will position the components associated with a View object.
  612. editor.setLayout(updateHandler);
  613. }
  614. } else {
  615. throw new Error("TextUI needs JTextComponent");
  616. }
  617. }
  618. /**
  619. * Deinstalls the UI for a component. This removes the listeners,
  620. * uninstalls the highlighter, removes views, and nulls out the keymap.
  621. *
  622. * @param c the editor component
  623. * @see ComponentUI#uninstallUI
  624. */
  625. public void uninstallUI(JComponent c) {
  626. // detach from the model
  627. editor.removePropertyChangeListener(updateHandler);
  628. editor.getDocument().removeDocumentListener(updateHandler);
  629. // view part
  630. painted = false;
  631. uninstallDefaults();
  632. rootView.setView(null);
  633. c.removeAll();
  634. LayoutManager lm = c.getLayout();
  635. if (lm instanceof UIResource) {
  636. c.setLayout(null);
  637. }
  638. // controller part
  639. uninstallKeyboardActions();
  640. uninstallListeners();
  641. editor = null;
  642. }
  643. /**
  644. * Gets the preferred size for the editor component. If the component
  645. * has been given a size prior to receiving this request, it will
  646. * set the size of the view hierarchy to reflect the size of the component
  647. * before requesting the preferred size of the view hierarchy. This
  648. * allows formatted views to format to the current component size before
  649. * answering the request. Other views don't care about currently formatted
  650. * size and give the same answer either way.
  651. *
  652. * @param c the editor component
  653. * @return the size
  654. */
  655. public Dimension getPreferredSize(JComponent c) {
  656. Document doc = editor.getDocument();
  657. Insets i = c.getInsets();
  658. Dimension d = c.getSize();
  659. if (doc instanceof AbstractDocument) {
  660. ((AbstractDocument)doc).readLock();
  661. }
  662. try {
  663. if ((d.width > (i.left + i.right)) && (d.height > (i.top + i.bottom))) {
  664. rootView.setSize(d.width - i.left - i.right, d.height - i.top - i.bottom);
  665. }
  666. else if (d.width == 0 && d.height == 0) {
  667. // Probably haven't been layed out yet, force some sort of
  668. // initial sizing.
  669. rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
  670. }
  671. d.width = (int) Math.min((long) rootView.getPreferredSpan(View.X_AXIS) +
  672. (long) i.left + (long) i.right, Integer.MAX_VALUE);
  673. d.height = (int) Math.min((long) rootView.getPreferredSpan(View.Y_AXIS) +
  674. (long) i.top + (long) i.bottom, Integer.MAX_VALUE);
  675. } finally {
  676. if (doc instanceof AbstractDocument) {
  677. ((AbstractDocument)doc).readUnlock();
  678. }
  679. }
  680. return d;
  681. }
  682. /**
  683. * Gets the minimum size for the editor component.
  684. *
  685. * @param c the editor component
  686. * @return the size
  687. */
  688. public Dimension getMinimumSize(JComponent c) {
  689. Document doc = editor.getDocument();
  690. Insets i = c.getInsets();
  691. Dimension d = new Dimension();
  692. if (doc instanceof AbstractDocument) {
  693. ((AbstractDocument)doc).readLock();
  694. }
  695. try {
  696. d.width = (int) rootView.getMinimumSpan(View.X_AXIS) + i.left + i.right;
  697. d.height = (int) rootView.getMinimumSpan(View.Y_AXIS) + i.top + i.bottom;
  698. } finally {
  699. if (doc instanceof AbstractDocument) {
  700. ((AbstractDocument)doc).readUnlock();
  701. }
  702. }
  703. return d;
  704. }
  705. /**
  706. * Gets the maximum size for the editor component.
  707. *
  708. * @param c the editor component
  709. * @return the size
  710. */
  711. public Dimension getMaximumSize(JComponent c) {
  712. Document doc = editor.getDocument();
  713. Insets i = c.getInsets();
  714. Dimension d = new Dimension();
  715. if (doc instanceof AbstractDocument) {
  716. ((AbstractDocument)doc).readLock();
  717. }
  718. try {
  719. d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS) +
  720. (long) i.left + (long) i.right, Integer.MAX_VALUE);
  721. d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS) +
  722. (long) i.top + (long) i.bottom, Integer.MAX_VALUE);
  723. } finally {
  724. if (doc instanceof AbstractDocument) {
  725. ((AbstractDocument)doc).readUnlock();
  726. }
  727. }
  728. return d;
  729. }
  730. // ---- TextUI methods -------------------------------------------
  731. /**
  732. * Gets the allocation to give the root View. Due
  733. * to an unfortunate set of historical events this
  734. * method is inappropriately named. The Rectangle
  735. * returned has nothing to do with visibility.
  736. * The component must have a non-zero positive size for
  737. * this translation to be computed.
  738. *
  739. * @return the bounding box for the root view
  740. */
  741. protected Rectangle getVisibleEditorRect() {
  742. Rectangle alloc = editor.getBounds();
  743. if ((alloc.width > 0) && (alloc.height > 0)) {
  744. alloc.x = alloc.y = 0;
  745. Insets insets = editor.getInsets();
  746. alloc.x += insets.left;
  747. alloc.y += insets.top;
  748. alloc.width -= insets.left + insets.right;
  749. alloc.height -= insets.top + insets.bottom;
  750. return alloc;
  751. }
  752. return null;
  753. }
  754. /**
  755. * Converts the given location in the model to a place in
  756. * the view coordinate system.
  757. * The component must have a non-zero positive size for
  758. * this translation to be computed.
  759. *
  760. * @param tc the text component for which this UI is installed
  761. * @param pos the local location in the model to translate >= 0
  762. * @return the coordinates as a rectangle, null if the model is not painted
  763. * @exception BadLocationException if the given position does not
  764. * represent a valid location in the associated document
  765. * @see TextUI#modelToView
  766. */
  767. public Rectangle modelToView(JTextComponent tc, int pos) throws BadLocationException {
  768. return modelToView(tc, pos, Position.Bias.Forward);
  769. }
  770. /**
  771. * Converts the given location in the model to a place in
  772. * the view coordinate system.
  773. * The component must have a non-zero positive size for
  774. * this translation to be computed.
  775. *
  776. * @param tc the text component for which this UI is installed
  777. * @param pos the local location in the model to translate >= 0
  778. * @return the coordinates as a rectangle, null if the model is not painted
  779. * @exception BadLocationException if the given position does not
  780. * represent a valid location in the associated document
  781. * @see TextUI#modelToView
  782. */
  783. public Rectangle modelToView(JTextComponent tc, int pos, Position.Bias bias) throws BadLocationException {
  784. Document doc = editor.getDocument();
  785. if (doc instanceof AbstractDocument) {
  786. ((AbstractDocument)doc).readLock();
  787. }
  788. try {
  789. Rectangle alloc = getVisibleEditorRect();
  790. if (alloc != null) {
  791. rootView.setSize(alloc.width, alloc.height);
  792. Shape s = rootView.modelToView(pos, alloc, bias);
  793. if (s != null) {
  794. return s.getBounds();
  795. }
  796. }
  797. } finally {
  798. if (doc instanceof AbstractDocument) {
  799. ((AbstractDocument)doc).readUnlock();
  800. }
  801. }
  802. return null;
  803. }
  804. /**
  805. * Converts the given place in the view coordinate system
  806. * to the nearest representative location in the model.
  807. * The component must have a non-zero positive size for
  808. * this translation to be computed.
  809. *
  810. * @param tc the text component for which this UI is installed
  811. * @param pt the location in the view to translate. This
  812. * should be in the same coordinate system as the mouse events.
  813. * @return the offset from the start of the document >= 0,
  814. * -1 if not painted
  815. * @see TextUI#viewToModel
  816. */
  817. public int viewToModel(JTextComponent tc, Point pt) {
  818. return viewToModel(tc, pt, discardBias);
  819. }
  820. /**
  821. * Converts the given place in the view coordinate system
  822. * to the nearest representative location in the model.
  823. * The component must have a non-zero positive size for
  824. * this translation to be computed.
  825. *
  826. * @param tc the text component for which this UI is installed
  827. * @param pt the location in the view to translate. This
  828. * should be in the same coordinate system as the mouse events.
  829. * @return the offset from the start of the document >= 0,
  830. * -1 if the component doesn't yet have a positive size.
  831. * @see TextUI#viewToModel
  832. */
  833. public int viewToModel(JTextComponent tc, Point pt,
  834. Position.Bias[] biasReturn) {
  835. int offs = -1;
  836. Document doc = editor.getDocument();
  837. if (doc instanceof AbstractDocument) {
  838. ((AbstractDocument)doc).readLock();
  839. }
  840. try {
  841. Rectangle alloc = getVisibleEditorRect();
  842. if (alloc != null) {
  843. rootView.setSize(alloc.width, alloc.height);
  844. offs = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn);
  845. }
  846. } finally {
  847. if (doc instanceof AbstractDocument) {
  848. ((AbstractDocument)doc).readUnlock();
  849. }
  850. }
  851. return offs;
  852. }
  853. /**
  854. * Provides a way to determine the next visually represented model
  855. * location that one might place a caret. Some views may not be visible,
  856. * they might not be in the same order found in the model, or they just
  857. * might not allow access to some of the locations in the model.
  858. *
  859. * @param pos the position to convert >= 0
  860. * @param a the allocated region to render into
  861. * @param direction the direction from the current position that can
  862. * be thought of as the arrow keys typically found on a keyboard.
  863. * This may be SwingConstants.WEST, SwingConstants.EAST,
  864. * SwingConstants.NORTH, or SwingConstants.SOUTH.
  865. * @return the location within the model that best represents the next
  866. * location visual position.
  867. * @exception BadLocationException
  868. * @exception IllegalArgumentException for an invalid direction
  869. */
  870. public int getNextVisualPositionFrom(JTextComponent t, int pos,
  871. Position.Bias b, int direction, Position.Bias[] biasRet)
  872. throws BadLocationException{
  873. Document doc = editor.getDocument();
  874. if (doc instanceof AbstractDocument) {
  875. ((AbstractDocument)doc).readLock();
  876. }
  877. try {
  878. if (painted) {
  879. Rectangle alloc = getVisibleEditorRect();
  880. rootView.setSize(alloc.width, alloc.height);
  881. return rootView.getNextVisualPositionFrom(pos, b, alloc, direction,
  882. biasRet);
  883. }
  884. } finally {
  885. if (doc instanceof AbstractDocument) {
  886. ((AbstractDocument)doc).readUnlock();
  887. }
  888. }
  889. return -1;
  890. }
  891. /**
  892. * Causes the portion of the view responsible for the
  893. * given part of the model to be repainted. Does nothing if
  894. * the view is not currently painted.
  895. *
  896. * @param tc the text component for which this UI is installed
  897. * @param p0 the beginning of the range >= 0
  898. * @param p1 the end of the range >= p0
  899. * @see TextUI#damageRange
  900. */
  901. public void damageRange(JTextComponent tc, int p0, int p1) {
  902. damageRange(tc, p0, p1, Position.Bias.Forward, Position.Bias.Backward);
  903. }
  904. /**
  905. * Causes the portion of the view responsible for the
  906. * given part of the model to be repainted.
  907. *
  908. * @param p0 the beginning of the range >= 0
  909. * @param p1 the end of the range >= p0
  910. */
  911. public void damageRange(JTextComponent t, int p0, int p1,
  912. Position.Bias p0Bias, Position.Bias p1Bias) {
  913. if (painted) {
  914. Rectangle alloc = getVisibleEditorRect();
  915. Document doc = t.getDocument();
  916. if (doc instanceof AbstractDocument) {
  917. ((AbstractDocument)doc).readLock();
  918. }
  919. try {
  920. rootView.setSize(alloc.width, alloc.height);
  921. Shape toDamage = rootView.modelToView(p0, p0Bias,
  922. p1, p1Bias, alloc);
  923. Rectangle rect = (toDamage instanceof Rectangle) ?
  924. (Rectangle)toDamage : toDamage.getBounds();
  925. editor.repaint(rect.x, rect.y, rect.width, rect.height);
  926. } catch (BadLocationException e) {
  927. } finally {
  928. if (doc instanceof AbstractDocument) {
  929. ((AbstractDocument)doc).readUnlock();
  930. }
  931. }
  932. }
  933. }
  934. /**
  935. * Fetches the EditorKit for the UI.
  936. *
  937. * @param tc the text component for which this UI is installed
  938. * @return the editor capabilities
  939. * @see TextUI#getEditorKit
  940. */
  941. public EditorKit getEditorKit(JTextComponent tc) {
  942. return defaultKit;
  943. }
  944. /**
  945. * Fetches a View with the allocation of the associated
  946. * text component (i.e. the root of the hierarchy) that
  947. * can be traversed to determine how the model is being
  948. * represented spatially.
  949. * <p>
  950. * <font color=red><b>NOTE:</b>The View hierarchy can
  951. * be traversed from the root view, and other things
  952. * can be done as well. Things done in this way cannot
  953. * be protected like simple method calls through the TextUI.
  954. * Therefore, proper operation in the presence of concurrency
  955. * must be arranged by any logic that calls this method!
  956. * </font>
  957. *
  958. * @param tc the text component for which this UI is installed
  959. * @return the view
  960. * @see TextUI#getRootView
  961. */
  962. public View getRootView(JTextComponent tc) {
  963. return rootView;
  964. }
  965. /**
  966. * Returns the string to be used as the tooltip at the passed in location.
  967. * This forwards the method onto the root View.
  968. *
  969. * @see javax.swing.text.JTextComponent#getToolTipText
  970. * @see javax.swing.text.View#getToolTipText
  971. * @since 1.4
  972. */
  973. public String getToolTipText(JTextComponent t, Point pt) {
  974. if (!painted) {
  975. return null;
  976. }
  977. Document doc = editor.getDocument();
  978. String tt = null;
  979. Rectangle alloc = getVisibleEditorRect();
  980. if (doc instanceof AbstractDocument) {
  981. ((AbstractDocument)doc).readLock();
  982. }
  983. try {
  984. tt = rootView.getToolTipText(pt.x, pt.y, alloc);
  985. } finally {
  986. if (doc instanceof AbstractDocument) {
  987. ((AbstractDocument)doc).readUnlock();
  988. }
  989. }
  990. return tt;
  991. }
  992. // --- ViewFactory methods ------------------------------
  993. /**
  994. * Creates a view for an element.
  995. * If a subclass wishes to directly implement the factory
  996. * producing the view(s), it should reimplement this
  997. * method. By default it simply returns null indicating
  998. * it is unable to represent the element.
  999. *
  1000. * @param elem the element
  1001. * @return the view
  1002. */
  1003. public View create(Element elem) {
  1004. return null;
  1005. }
  1006. /**
  1007. * Creates a view for an element.
  1008. * If a subclass wishes to directly implement the factory
  1009. * producing the view(s), it should reimplement this
  1010. * method. By default it simply returns null indicating
  1011. * it is unable to represent the part of the element.
  1012. *
  1013. * @param elem the element
  1014. * @param p0 the starting offset >= 0
  1015. * @param p1 the ending offset >= p0
  1016. * @return the view
  1017. */
  1018. public View create(Element elem, int p0, int p1) {
  1019. return null;
  1020. }
  1021. /**
  1022. * Root view that acts as a gateway between the component
  1023. * and the View hierarchy.
  1024. */
  1025. class RootView extends View {
  1026. RootView() {
  1027. super(null);
  1028. }
  1029. void setView(View v) {
  1030. if (view != null) {
  1031. // get rid of back reference so that the old
  1032. // hierarchy can be garbage collected.
  1033. view.setParent(null);
  1034. }
  1035. view = v;
  1036. if (view != null) {
  1037. view.setParent(this);
  1038. }
  1039. }
  1040. /**
  1041. * Fetches the attributes to use when rendering. At the root
  1042. * level there are no attributes. If an attribute is resolved
  1043. * up the view hierarchy this is the end of the line.
  1044. */
  1045. public AttributeSet getAttributes() {
  1046. return null;
  1047. }
  1048. /**
  1049. * Determines the preferred span for this view along an axis.
  1050. *
  1051. * @param axis may be either X_AXIS or Y_AXIS
  1052. * @return the span the view would like to be rendered into.
  1053. * Typically the view is told to render into the span
  1054. * that is returned, although there is no guarantee.
  1055. * The parent may choose to resize or break the view.
  1056. */
  1057. public float getPreferredSpan(int axis) {
  1058. if (view != null) {
  1059. return view.getPreferredSpan(axis);
  1060. }
  1061. return 10;
  1062. }
  1063. /**
  1064. * Determines the minimum span for this view along an axis.
  1065. *
  1066. * @param axis may be either X_AXIS or Y_AXIS
  1067. * @return the span the view would like to be rendered into.
  1068. * Typically the view is told to render into the span
  1069. * that is returned, although there is no guarantee.
  1070. * The parent may choose to resize or break the view.
  1071. */
  1072. public float getMinimumSpan(int axis) {
  1073. if (view != null) {
  1074. return view.getMinimumSpan(axis);
  1075. }
  1076. return 10;
  1077. }
  1078. /**
  1079. * Determines the maximum span for this view along an axis.
  1080. *
  1081. * @param axis may be either X_AXIS or Y_AXIS
  1082. * @return the span the view would like to be rendered into.
  1083. * Typically the view is told to render into the span
  1084. * that is returned, although there is no guarantee.
  1085. * The parent may choose to resize or break the view.
  1086. */
  1087. public float getMaximumSpan(int axis) {
  1088. return Integer.MAX_VALUE;
  1089. }
  1090. /**
  1091. * Specifies that a preference has changed.
  1092. * Child views can call this on the parent to indicate that
  1093. * the preference has changed. The root view routes this to
  1094. * invalidate on the hosting component.
  1095. * <p>
  1096. * This can be called on a different thread from the
  1097. * event dispatching thread and is basically unsafe to
  1098. * propagate into the component. To make this safe,
  1099. * the operation is transferred over to the event dispatching
  1100. * thread for completion. It is a design goal that all view
  1101. * methods be safe to call without concern for concurrency,
  1102. * and this behavior helps make that true.
  1103. *
  1104. * @param child the child view
  1105. * @param width true if the width preference has changed
  1106. * @param height true if the height preference has changed
  1107. */
  1108. public void preferenceChanged(View child, boolean width, boolean height) {
  1109. editor.revalidate();
  1110. }
  1111. /**
  1112. * Determines the desired alignment for this view along an axis.
  1113. *
  1114. * @param axis may be either X_AXIS or Y_AXIS
  1115. * @return the desired alignment, where 0.0 indicates the origin
  1116. * and 1.0 the full span away from the origin
  1117. */
  1118. public float getAlignment(int axis) {
  1119. if (view != null) {
  1120. return view.getAlignment(axis);
  1121. }
  1122. return 0;
  1123. }
  1124. /**
  1125. * Renders the view.
  1126. *
  1127. * @param g the graphics context
  1128. * @param allocation the region to render into
  1129. */
  1130. public void paint(Graphics g, Shape allocation) {
  1131. if (view != null) {
  1132. Rectangle alloc = (allocation instanceof Rectangle) ?
  1133. (Rectangle)allocation : allocation.getBounds();
  1134. setSize(alloc.width, alloc.height);
  1135. view.paint(g, allocation);
  1136. }
  1137. }
  1138. /**
  1139. * Sets the view parent.
  1140. *
  1141. * @param parent the parent view
  1142. */
  1143. public void setParent(View parent) {
  1144. throw new Error("Can't set parent on root view");
  1145. }
  1146. /**
  1147. * Returns the number of views in this view. Since
  1148. * this view simply wraps the root of the view hierarchy
  1149. * it has exactly one child.
  1150. *
  1151. * @return the number of views
  1152. * @see #getView
  1153. */
  1154. public int getViewCount() {
  1155. return 1;
  1156. }
  1157. /**
  1158. * Gets the n-th view in this container.
  1159. *
  1160. * @param n the number of the view to get
  1161. * @return the view
  1162. */
  1163. public View getView(int n) {
  1164. return view;
  1165. }
  1166. /**
  1167. * Returns the child view index representing the given position in
  1168. * the model. This is implemented to return the index of the only
  1169. * child.
  1170. *
  1171. * @param pos the position >= 0
  1172. * @return index of the view representing the given position, or
  1173. * -1 if no view represents that position
  1174. * @since 1.3
  1175. */
  1176. public int getViewIndex(int pos, Position.Bias b) {
  1177. return 0;
  1178. }
  1179. /**
  1180. * Fetches the allocation for the given child view.
  1181. * This enables finding out where various views
  1182. * are located, without assuming the views store
  1183. * their location. This returns the given allocation
  1184. * since this view simply acts as a gateway between
  1185. * the view hierarchy and the associated component.
  1186. *
  1187. * @param index the index of the child
  1188. * @param a the allocation to this view.
  1189. * @return the allocation to the child
  1190. */
  1191. public Shape getChildAllocation(int index, Shape a) {
  1192. return a;
  1193. }
  1194. /**
  1195. * Provides a mapping from the document model coordinate space
  1196. * to the coordinate space of the view mapped to it.
  1197. *
  1198. * @param pos the position to convert
  1199. * @param a the allocated region to render into
  1200. * @return the bounding box of the given position
  1201. */
  1202. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
  1203. if (view != null) {
  1204. return view.modelToView(pos, a, b);
  1205. }
  1206. return null;
  1207. }
  1208. /**
  1209. * Provides a mapping from the document model coordinate space
  1210. * to the coordinate space of the view mapped to it.
  1211. *
  1212. * @param p0 the position to convert >= 0
  1213. * @param b0 the bias toward the previous character or the
  1214. * next character represented by p0, in case the
  1215. * position is a boundary of two views.
  1216. * @param p1 the position to convert >= 0
  1217. * @param b1 the bias toward the previous character or the
  1218. * next character represented by p1, in case the
  1219. * position is a boundary