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 of two views.
  1220. * @param a the allocated region to render into
  1221. * @return the bounding box of the given position is returned
  1222. * @exception BadLocationException if the given position does
  1223. * not represent a valid location in the associated document
  1224. * @exception IllegalArgumentException for an invalid bias argument
  1225. * @see View#viewToModel
  1226. */
  1227. public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException {
  1228. if (view != null) {
  1229. return view.modelToView(p0, b0, p1, b1, a);
  1230. }
  1231. return null;
  1232. }
  1233. /**
  1234. * Provides a mapping from the view coordinate space to the logical
  1235. * coordinate space of the model.
  1236. *
  1237. * @param x x coordinate of the view location to convert
  1238. * @param y y coordinate of the view location to convert
  1239. * @param a the allocated region to render into
  1240. * @return the location within the model that best represents the
  1241. * given point in the view
  1242. */
  1243. public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
  1244. if (view != null) {
  1245. int retValue = view.viewToModel(x, y, a, bias);
  1246. return retValue;
  1247. }
  1248. return -1;
  1249. }
  1250. /**
  1251. * Provides a way to determine the next visually represented model
  1252. * location that one might place a caret. Some views may not be visible,
  1253. * they might not be in the same order found in the model, or they just
  1254. * might not allow access to some of the locations in the model.
  1255. *
  1256. * @param pos the position to convert >= 0
  1257. * @param a the allocated region to render into
  1258. * @param direction the direction from the current position that can
  1259. * be thought of as the arrow keys typically found on a keyboard.
  1260. * This may be SwingConstants.WEST, SwingConstants.EAST,
  1261. * SwingConstants.NORTH, or SwingConstants.SOUTH.
  1262. * @return the location within the model that best represents the next
  1263. * location visual position.
  1264. * @exception BadLocationException
  1265. * @exception IllegalArgumentException for an invalid direction
  1266. */
  1267. public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
  1268. int direction,
  1269. Position.Bias[] biasRet)
  1270. throws BadLocationException {
  1271. if( view != null ) {
  1272. int nextPos = view.getNextVisualPositionFrom(pos, b, a,
  1273. direction, biasRet);
  1274. if(nextPos != -1) {
  1275. pos = nextPos;
  1276. }
  1277. else {
  1278. biasRet[0] = b;
  1279. }
  1280. }
  1281. return pos;
  1282. }
  1283. /**
  1284. * Gives notification that something was inserted into the document
  1285. * in a location that this view is responsible for.
  1286. *
  1287. * @param e the change information from the associated document
  1288. * @param a the current allocation of the view
  1289. * @param f the factory to use to rebuild if the view has children
  1290. */
  1291. public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  1292. if (view != null) {
  1293. view.insertUpdate(e, a, f);
  1294. }
  1295. }
  1296. /**
  1297. * Gives notification that something was removed from the document
  1298. * in a location that this view is responsible for.
  1299. *
  1300. * @param e the change information from the associated document
  1301. * @param a the current allocation of the view
  1302. * @param f the factory to use to rebuild if the view has children
  1303. */
  1304. public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  1305. if (view != null) {
  1306. view.removeUpdate(e, a, f);
  1307. }
  1308. }
  1309. /**
  1310. * Gives notification from the document that attributes were changed
  1311. * in a location that this view is responsible for.
  1312. *
  1313. * @param e the change information from the associated document
  1314. * @param a the current allocation of the view
  1315. * @param f the factory to use to rebuild if the view has children
  1316. */
  1317. public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  1318. if (view != null) {
  1319. view.changedUpdate(e, a, f);
  1320. }
  1321. }
  1322. /**
  1323. * Returns the document model underlying the view.
  1324. *
  1325. * @return the model
  1326. */
  1327. public Document getDocument() {
  1328. return editor.getDocument();
  1329. }
  1330. /**
  1331. * Returns the starting offset into the model for this view.
  1332. *
  1333. * @return the starting offset
  1334. */
  1335. public int getStartOffset() {
  1336. if (view != null) {
  1337. return view.getStartOffset();
  1338. }
  1339. return getElement().getStartOffset();
  1340. }
  1341. /**
  1342. * Returns the ending offset into the model for this view.
  1343. *
  1344. * @return the ending offset
  1345. */
  1346. public int getEndOffset() {
  1347. if (view != null) {
  1348. return view.getEndOffset();
  1349. }
  1350. return getElement().getEndOffset();
  1351. }
  1352. /**
  1353. * Gets the element that this view is mapped to.
  1354. *
  1355. * @return the view
  1356. */
  1357. public Element getElement() {
  1358. if (view != null) {
  1359. return view.getElement();
  1360. }
  1361. return editor.getDocument().getDefaultRootElement();
  1362. }
  1363. /**
  1364. * Breaks this view on the given axis at the given length.
  1365. *
  1366. * @param axis may be either X_AXIS or Y_AXIS
  1367. * @param len specifies where a break is desired in the span
  1368. * @param the current allocation of the view
  1369. * @return the fragment of the view that represents the given span
  1370. * if the view can be broken, otherwise null
  1371. */
  1372. public View breakView(int axis, float len, Shape a) {
  1373. throw new Error("Can't break root view");
  1374. }
  1375. /**
  1376. * Determines the resizability of the view along the
  1377. * given axis. A value of 0 or less is not resizable.
  1378. *
  1379. * @param axis may be either X_AXIS or Y_AXIS
  1380. * @return the weight
  1381. */
  1382. public int getResizeWeight(int axis) {
  1383. if (view != null) {
  1384. return view.getResizeWeight(axis);
  1385. }
  1386. return 0;
  1387. }
  1388. /**
  1389. * Sets the view size.
  1390. *
  1391. * @param width the width
  1392. * @param height the height
  1393. */
  1394. public void setSize(float width, float height) {
  1395. if (view != null) {
  1396. view.setSize(width, height);
  1397. }
  1398. }
  1399. /**
  1400. * Fetches the container hosting the view. This is useful for
  1401. * things like scheduling a repaint, finding out the host
  1402. * components font, etc. The default implementation
  1403. * of this is to forward the query to the parent view.
  1404. *
  1405. * @return the container
  1406. */
  1407. public Container getContainer() {
  1408. return editor;
  1409. }
  1410. /**
  1411. * Fetches the factory to be used for building the
  1412. * various view fragments that make up the view that
  1413. * represents the model. This is what determines
  1414. * how the model will be represented. This is implemented
  1415. * to fetch the factory provided by the associated
  1416. * EditorKit unless that is null, in which case this
  1417. * simply returns the SynthTextUI itself which allows
  1418. * subclasses to implement a simple factory directly without
  1419. * creating extra objects.
  1420. *
  1421. * @return the factory
  1422. */
  1423. public ViewFactory getViewFactory() {
  1424. EditorKit kit = getEditorKit(editor);
  1425. ViewFactory f = kit.getViewFactory();
  1426. if (f != null) {
  1427. return f;
  1428. }
  1429. return SynthTextUI.this;
  1430. }
  1431. private View view;
  1432. }
  1433. /**
  1434. * Handles updates from various places. If the model is changed,
  1435. * this class unregisters as a listener to the old model and
  1436. * registers with the new model. If the document model changes,
  1437. * the change is forwarded to the root view. If the focus
  1438. * accelerator changes, a new keystroke is registered to request
  1439. * focus.
  1440. */
  1441. class UpdateHandler implements PropertyChangeListener, DocumentListener, LayoutManager2, UIResource {
  1442. // --- PropertyChangeListener methods -----------------------
  1443. /**
  1444. * This method gets called when a bound property is changed.
  1445. * We are looking for document changes on the editor.
  1446. */
  1447. public final void propertyChange(PropertyChangeEvent evt) {
  1448. if (SynthLookAndFeel.shouldUpdateStyle(evt)) {
  1449. fetchStyle((JTextComponent)evt.getSource());
  1450. }
  1451. Object oldValue = evt.getOldValue();
  1452. Object newValue = evt.getNewValue();
  1453. String propertyName = evt.getPropertyName();
  1454. if ((oldValue instanceof Document) || (newValue instanceof Document)) {
  1455. if (oldValue != null) {
  1456. ((Document)oldValue).removeDocumentListener(this);
  1457. }
  1458. if (newValue != null) {
  1459. ((Document)newValue).addDocumentListener(this);
  1460. if ("document".equals(propertyName)) {
  1461. SynthTextUI.this.propertyChange(evt);
  1462. modelChanged();
  1463. return;
  1464. }
  1465. }
  1466. modelChanged();
  1467. }
  1468. if (JTextComponent.FOCUS_ACCELERATOR_KEY.equals(propertyName)) {
  1469. updateFocusAcceleratorBinding(true);
  1470. } else if ("componentOrientation".equals(propertyName)) {
  1471. // Changes in ComponentOrientation require the views to be
  1472. // rebuilt.
  1473. modelChanged();
  1474. } else if ("font".equals(propertyName)) {
  1475. modelChanged();
  1476. } else if ("transferHandler".equals(propertyName)) {
  1477. DropTarget dropTarget = editor.getDropTarget();
  1478. if (dropTarget instanceof UIResource) {
  1479. if (defaultDropTargetListener == null) {
  1480. defaultDropTargetListener = new TextDropTargetListener();
  1481. }
  1482. try {
  1483. dropTarget.addDropTargetListener(defaultDropTargetListener);
  1484. } catch (TooManyListenersException tmle) {
  1485. // should not happen... swing drop target is multicast
  1486. }
  1487. }
  1488. }
  1489. SynthTextUI.this.propertyChange(evt);
  1490. }
  1491. // --- DocumentListener methods -----------------------
  1492. /**
  1493. * The insert notification. Gets sent to the root of the view structure
  1494. * that represents the portion of the model being represented by the
  1495. * editor. The factory is added as an argument to the update so that
  1496. * the views can update themselves in a dynamic (not hardcoded) way.
  1497. *
  1498. * @param e The change notification from the currently associated
  1499. * document.
  1500. * @see DocumentListener#insertUpdate
  1501. */
  1502. public final void insertUpdate(DocumentEvent e) {
  1503. Document doc = e.getDocument();
  1504. Object o = doc.getProperty("i18n");
  1505. if (o instanceof Boolean) {
  1506. Boolean i18nFlag = (Boolean) o;
  1507. if (i18nFlag.booleanValue() != i18nView) {
  1508. // i18n flag changed, rebuild the view
  1509. i18nView = i18nFlag.booleanValue();
  1510. modelChanged();
  1511. return;
  1512. }
  1513. }
  1514. // normal insert update
  1515. Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
  1516. rootView.insertUpdate(e, alloc, rootView.getViewFactory());
  1517. }
  1518. /**
  1519. * The remove notification. Gets sent to the root of the view structure
  1520. * that represents the portion of the model being represented by the
  1521. * editor. The factory is added as an argument to the update so that
  1522. * the views can update themselves in a dynamic (not hardcoded) way.
  1523. *
  1524. * @param e The change notification from the currently associated
  1525. * document.
  1526. * @see DocumentListener#removeUpdate
  1527. */
  1528. public final void removeUpdate(DocumentEvent e) {
  1529. Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
  1530. rootView.removeUpdate(e, alloc, rootView.getViewFactory());
  1531. }
  1532. /**
  1533. * The change notification. Gets sent to the root of the view structure
  1534. * that represents the portion of the model being represented by the
  1535. * editor. The factory is added as an argument to the update so that
  1536. * the views can update themselves in a dynamic (not hardcoded) way.
  1537. *
  1538. * @param e The change notification from the currently associated
  1539. * document.
  1540. * @see DocumentListener#changeUpdate
  1541. */
  1542. public final void changedUpdate(DocumentEvent e) {
  1543. Rectangle alloc = (painted) ? getVisibleEditorRect() : null;
  1544. rootView.changedUpdate(e, alloc, rootView.getViewFactory());
  1545. }
  1546. // --- LayoutManager2 methods --------------------------------
  1547. /**
  1548. * Adds the specified component with the specified name to
  1549. * the layout.
  1550. * @param name the component name
  1551. * @param comp the component to be added
  1552. */
  1553. public void addLayoutComponent(String name, Component comp) {
  1554. // not supported
  1555. }
  1556. /**
  1557. * Removes the specified component from the layout.
  1558. * @param comp the component to be removed
  1559. */
  1560. public void removeLayoutComponent(Component comp) {
  1561. if (constraints != null) {
  1562. // remove the constraint record
  1563. constraints.remove(comp);
  1564. }
  1565. }
  1566. /**
  1567. * Calculates the preferred size dimensions for the specified
  1568. * panel given the components in the specified parent container.
  1569. * @param parent the component to be laid out
  1570. *
  1571. * @see #minimumLayoutSize
  1572. */
  1573. public Dimension preferredLayoutSize(Container parent) {
  1574. // should not be called (JComponent uses UI instead)
  1575. return null;
  1576. }
  1577. /**
  1578. * Calculates the minimum size dimensions for the specified
  1579. * panel given the components in the specified parent container.
  1580. * @param parent the component to be laid out
  1581. * @see #preferredLayoutSize
  1582. */
  1583. public Dimension minimumLayoutSize(Container parent) {
  1584. // should not be called (JComponent uses UI instead)
  1585. return null;
  1586. }
  1587. /**
  1588. * Lays out the container in the specified panel. This is
  1589. * implemented to position all components that were added
  1590. * with a View object as a constraint. The current allocation
  1591. * of the associated View is used as the location of the
  1592. * component.
  1593. * <p>
  1594. * A read-lock is acquired on the document to prevent the
  1595. * view tree from being modified while the layout process
  1596. * is active.
  1597. *
  1598. * @param parent the component which needs to be laid out
  1599. */
  1600. public void layoutContainer(Container parent) {
  1601. if ((constraints != null) && (! constraints.isEmpty())) {
  1602. Rectangle alloc = getVisibleEditorRect();
  1603. if (alloc != null) {
  1604. Document doc = editor.getDocument();
  1605. if (doc instanceof AbstractDocument) {
  1606. ((AbstractDocument)doc).readLock();
  1607. }
  1608. try {
  1609. rootView.setSize(alloc.width, alloc.height);
  1610. Enumeration components = constraints.keys();
  1611. while (components.hasMoreElements()) {
  1612. Component comp = (Component) components.nextElement();
  1613. View v = (View) constraints.get(comp);
  1614. Shape ca = calculateViewPosition(alloc, v);
  1615. if (ca != null) {
  1616. Rectangle compAlloc = (ca instanceof Rectangle) ?
  1617. (Rectangle) ca : ca.getBounds();
  1618. comp.setBounds(compAlloc);
  1619. }
  1620. }
  1621. } finally {
  1622. if (doc instanceof AbstractDocument) {
  1623. ((AbstractDocument)doc).readUnlock();
  1624. }
  1625. }
  1626. }
  1627. }
  1628. }
  1629. /**
  1630. * Find the Shape representing the given view.
  1631. */
  1632. Shape calculateViewPosition(Shape alloc, View v) {
  1633. int pos = v.getStartOffset();
  1634. View child = null;
  1635. for (View parent = rootView; (parent != null) && (parent != v); parent = child) {
  1636. int index = parent.getViewIndex(pos, Position.Bias.Forward);
  1637. alloc = parent.getChildAllocation(index, alloc);
  1638. child = parent.getView(index);
  1639. }
  1640. return (child != null) ? alloc : null;
  1641. }
  1642. /**
  1643. * Adds the specified component to the layout, using the specified
  1644. * constraint object. We only store those components that were added
  1645. * with a constraint that is of type View.
  1646. *
  1647. * @param comp the component to be added
  1648. * @param constraint where/how the component is added to the layout.
  1649. */
  1650. public void addLayoutComponent(Component comp, Object constraint) {
  1651. if (constraint instanceof View) {
  1652. if (constraints == null) {
  1653. constraints = new Hashtable(7);
  1654. }
  1655. constraints.put(comp, constraint);
  1656. }
  1657. }
  1658. /**
  1659. * Returns the maximum size of this component.
  1660. * @see java.awt.Component#getMinimumSize()
  1661. * @see java.awt.Component#getPreferredSize()
  1662. * @see LayoutManager
  1663. */
  1664. public Dimension maximumLayoutSize(Container target) {
  1665. // should not be called (JComponent uses UI instead)
  1666. return null;
  1667. }
  1668. /**
  1669. * Returns the alignment along the x axis. This specifies how
  1670. * the component would like to be aligned relative to other
  1671. * components. The value should be a number between 0 and 1
  1672. * where 0 represents alignment along the origin, 1 is aligned
  1673. * the furthest away from the origin, 0.5 is centered, etc.
  1674. */
  1675. public float getLayoutAlignmentX(Container target) {
  1676. return 0.5f;
  1677. }
  1678. /**
  1679. * Returns the alignment along the y axis. This specifies how
  1680. * the component would like to be aligned relative to other
  1681. * components. The value should be a number between 0 and 1
  1682. * where 0 represents alignment along the origin, 1 is aligned
  1683. * the furthest away from the origin, 0.5 is centered, etc.
  1684. */
  1685. public float getLayoutAlignmentY(Container target) {
  1686. return 0.5f;
  1687. }
  1688. /**
  1689. * Invalidates the layout, indicating that if the layout manager
  1690. * has cached information it should be discarded.
  1691. */
  1692. public void invalidateLayout(Container target) {
  1693. }
  1694. /**
  1695. * The "layout constraints" for the LayoutManager2 implementation.
  1696. * These are View objects for those components that are represented
  1697. * by a View in the View tree.
  1698. */
  1699. private Hashtable constraints;
  1700. private boolean i18nView = false;
  1701. }
  1702. /**
  1703. * Wrapper for text actions to return isEnabled false in case editor is non editable
  1704. */
  1705. class TextActionWrapper extends TextAction {
  1706. public TextActionWrapper(TextAction action) {
  1707. super((String)action.getValue(Action.NAME));
  1708. this.action = action;
  1709. }
  1710. /**
  1711. * The operation to perform when this action is triggered.
  1712. *
  1713. * @param e the action event
  1714. */
  1715. public void actionPerformed(ActionEvent e) {
  1716. action.actionPerformed(e);
  1717. }
  1718. public boolean isEnabled() {
  1719. return (editor == null || editor.isEditable()) ? action.isEnabled() : false;
  1720. }
  1721. TextAction action = null;
  1722. }
  1723. /**
  1724. * Registered in the ActionMap.
  1725. */
  1726. class FocusAction extends AbstractAction {
  1727. public void actionPerformed(ActionEvent e) {
  1728. editor.requestFocus();
  1729. }
  1730. public boolean isEnabled() {
  1731. return editor.isEditable();
  1732. }
  1733. }
  1734. /**
  1735. * Drag gesture recognizer for text components.
  1736. */
  1737. static class TextDragGestureRecognizer extends SynthDragGestureRecognizer {
  1738. /**
  1739. * Determines if the following are true:
  1740. * <ul>
  1741. * <li>the press event is located over a selection
  1742. * <li>the dragEnabled property is true
  1743. * <li>A TranferHandler is installed
  1744. * </ul>
  1745. * <p>
  1746. * This is implemented to check for a TransferHandler.
  1747. * Subclasses should perform the remaining conditions.
  1748. */
  1749. protected boolean isDragPossible(MouseEvent e) {
  1750. if (super.isDragPossible(e)) {
  1751. JTextComponent c = (JTextComponent) this.getComponent(e);
  1752. if (c.getDragEnabled()) {
  1753. Caret caret = c.getCaret();
  1754. int dot = caret.getDot();
  1755. int mark = caret.getMark();
  1756. if (dot != mark) {
  1757. Point p = new Point(e.getX(), e.getY());
  1758. int pos = c.viewToModel(p);
  1759. int p0 = Math.min(dot, mark);
  1760. int p1 = Math.max(dot, mark);
  1761. if ((pos >= p0) && (pos < p1)) {
  1762. return true;
  1763. }
  1764. }
  1765. }
  1766. }
  1767. return false;
  1768. }
  1769. }
  1770. /**
  1771. * A DropTargetListener to extend the default Swing handling of drop operations
  1772. * by moving the caret to the nearest location to the mouse pointer.
  1773. */
  1774. static class TextDropTargetListener extends SynthDropTargetListener {
  1775. /**
  1776. * called to save the state of a component in case it needs to
  1777. * be restored because a drop is not performed.
  1778. */
  1779. protected void saveComponentState(JComponent comp) {
  1780. JTextComponent c = (JTextComponent) comp;
  1781. Caret caret = c.getCaret();
  1782. dot = caret.getDot();
  1783. mark = caret.getMark();
  1784. visible = caret.isVisible();
  1785. caret.setVisible(true);
  1786. }
  1787. /**
  1788. * called to restore the state of a component
  1789. * because a drop was not performed.
  1790. */
  1791. protected void restoreComponentState(JComponent comp) {
  1792. JTextComponent c = (JTextComponent) comp;
  1793. Caret caret = c.getCaret();
  1794. caret.setDot(mark);
  1795. caret.moveDot(dot);
  1796. caret.setVisible(visible);
  1797. }
  1798. /**
  1799. * called to restore the state of a component
  1800. * because a drop was performed.
  1801. */
  1802. protected void restoreComponentStateForDrop(JComponent comp) {
  1803. JTextComponent c = (JTextComponent) comp;
  1804. Caret caret = c.getCaret();
  1805. caret.setVisible(visible);
  1806. }
  1807. /**
  1808. * called to set the insertion location to match the current
  1809. * mouse pointer coordinates.
  1810. */
  1811. protected void updateInsertionLocation(JComponent comp, Point p) {
  1812. JTextComponent c = (JTextComponent) comp;
  1813. c.setCaretPosition(c.viewToModel(p));
  1814. }
  1815. int dot;
  1816. int mark;
  1817. boolean visible;
  1818. }
  1819. static class TextTransferHandler extends TransferHandler implements UIResource {
  1820. private JTextComponent exportComp;
  1821. private boolean shouldRemove;
  1822. private int p0;
  1823. private int p1;
  1824. /**
  1825. * Try to find a flavor that can be used to import a Transferable.
  1826. * The set of usable flavors are tried in the following order:
  1827. * <ol>
  1828. * <li>First, an attempt is made to find a flavor matching the content type
  1829. * of the EditorKit for the component.
  1830. * <li>Second, an attempt to find a text/plain flavor is made.
  1831. * <li>Third, an attempt to find a flavor representing a String reference
  1832. * in the same VM is made.
  1833. * <li>Lastly, DataFlavor.stringFlavor is searched for.
  1834. * </ol>
  1835. */
  1836. protected DataFlavor getImportFlavor(DataFlavor[] flavors, JTextComponent c) {
  1837. DataFlavor plainFlavor = null;
  1838. DataFlavor refFlavor = null;
  1839. DataFlavor stringFlavor = null;
  1840. if (c instanceof JEditorPane) {
  1841. for (int i = 0; i < flavors.length; i++) {
  1842. String mime = flavors[i].getMimeType();
  1843. if (mime.startsWith(((JEditorPane)c).getEditorKit().getContentType())) {
  1844. return flavors[i];
  1845. } else if (plainFlavor == null && mime.startsWith("text/plain")) {
  1846. plainFlavor = flavors[i];
  1847. } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
  1848. && flavors[i].getRepresentationClass() == java.lang.String.class) {
  1849. refFlavor = flavors[i];
  1850. } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
  1851. stringFlavor = flavors[i];
  1852. }
  1853. }
  1854. if (plainFlavor != null) {
  1855. return plainFlavor;
  1856. } else if (refFlavor != null) {
  1857. return refFlavor;
  1858. } else if (stringFlavor != null) {
  1859. return stringFlavor;
  1860. }
  1861. return null;
  1862. }
  1863. for (int i = 0; i < flavors.length; i++) {
  1864. String mime = flavors[i].getMimeType();
  1865. if (mime.startsWith("text/plain")) {
  1866. return flavors[i];
  1867. } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref")
  1868. && flavors[i].getRepresentationClass() == java.lang.String.class) {
  1869. refFlavor = flavors[i];
  1870. } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) {
  1871. stringFlavor = flavors[i];
  1872. }
  1873. }
  1874. if (refFlavor != null) {
  1875. return refFlavor;
  1876. } else if (stringFlavor != null) {
  1877. return stringFlavor;
  1878. }
  1879. return null;
  1880. }
  1881. /**
  1882. * Import the given stream data into the text component.
  1883. */
  1884. protected void handleReaderImport(Reader in, JTextComponent c, boolean useRead)
  1885. throws BadLocationException, IOException {
  1886. if (useRead) {
  1887. int startPosition = c.getSelectionStart();
  1888. int endPosition = c.getSelectionEnd();
  1889. int length = endPosition - startPosition;
  1890. EditorKit kit = c.getUI().getEditorKit(c);
  1891. Document doc = c.getDocument();
  1892. if (length > 0) {
  1893. doc.remove(startPosition, length);
  1894. }
  1895. kit.read(in, doc, startPosition);
  1896. } else {
  1897. char[] buff = new char[1024];
  1898. int nch;
  1899. boolean lastWasCR = false;
  1900. int last;
  1901. StringBuffer sbuff = null;
  1902. // Read in a block at a time, mapping \r\n to \n, as well as single
  1903. // \r to \n.
  1904. while ((nch = in.read(buff, 0, buff.length)) != -1) {
  1905. if (sbuff == null) {
  1906. sbuff = new StringBuffer(nch);
  1907. }
  1908. last = 0;
  1909. for(int counter = 0; counter < nch; counter++) {
  1910. switch(buff[counter]) {
  1911. case '\r':
  1912. if (lastWasCR) {
  1913. if (counter == 0) {
  1914. sbuff.append('\n');
  1915. } else {
  1916. buff[counter - 1] = '\n';
  1917. }
  1918. } else {
  1919. lastWasCR = true;
  1920. }
  1921. break;
  1922. case '\n':
  1923. if (lastWasCR) {
  1924. if (counter > (last + 1)) {
  1925. sbuff.append(buff, last, counter - last - 1);
  1926. }
  1927. // else nothing to do, can skip \r, next write will
  1928. // write \n
  1929. lastWasCR = false;
  1930. last = counter;
  1931. }
  1932. break;
  1933. default:
  1934. if (lastWasCR) {
  1935. if (counter == 0) {
  1936. sbuff.append('\n');
  1937. } else {
  1938. buff[counter - 1] = '\n';
  1939. }
  1940. lastWasCR = false;
  1941. }
  1942. break;
  1943. }
  1944. }
  1945. if (last < nch) {
  1946. if (lastWasCR) {
  1947. if (last < (nch - 1)) {
  1948. sbuff.append(buff, last, nch - last - 1);
  1949. }
  1950. } else {
  1951. sbuff.append(buff, last, nch - last);
  1952. }
  1953. }
  1954. }
  1955. if (lastWasCR) {
  1956. sbuff.append('\n');
  1957. }
  1958. c.replaceSelection(sbuff != null ? sbuff.toString() : "");
  1959. }
  1960. }
  1961. // --- TransferHandler methods ------------------------------------
  1962. /**
  1963. * This is the type of transfer actions supported by the source. Some models are
  1964. * not mutable, so a transfer operation of COPY only should
  1965. * be advertised in that case.
  1966. *
  1967. * @param c The component holding the data to be transfered. This
  1968. * argument is provided to enable sharing of TransferHandlers by
  1969. * multiple components.
  1970. * @return This is implemented to return NONE if the component is a JPasswordField
  1971. * since exporting data via user gestures is not allowed. If the text component is
  1972. * editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed.
  1973. */
  1974. public int getSourceActions(JComponent c) {
  1975. int actions = NONE;
  1976. if (! (c instanceof JPasswordField)) {
  1977. if (((JTextComponent)c).isEditable()) {
  1978. actions = COPY_OR_MOVE;
  1979. } else {
  1980. actions = COPY;
  1981. }
  1982. }
  1983. return actions;
  1984. }
  1985. /**
  1986. * Create a Transferable to use as the source for a data transfer.
  1987. *
  1988. * @param comp The component holding the data to be transfered. This
  1989. * argument is provided to enable sharing of TransferHandlers by
  1990. * multiple components.
  1991. * @return The representation of the data to be transfered.
  1992. *
  1993. */
  1994. protected Transferable createTransferable(JComponent comp) {
  1995. exportComp = (JTextComponent)comp;
  1996. shouldRemove = true;
  1997. p0 = exportComp.getSelectionStart();
  1998. p1 = exportComp.getSelectionEnd();
  1999. return (p0 != p1) ? (new TextTransferable(exportComp, p0, p1)) : null;
  2000. }
  2001. /**
  2002. * This method is called after data has been exported. This method should remove
  2003. * the data that was transfered if the action was MOVE.
  2004. *
  2005. * @param source The component that was the source of the data.
  2006. * @param data The data that was transferred or possibly null
  2007. * if the action is <code>NONE</code>.
  2008. * @param action The actual action that was performed.
  2009. */
  2010. protected void exportDone(JComponent source, Transferable data, int action) {
  2011. // only remove the text if shouldRemove has not been set to
  2012. // false by importData and only if the action is a move
  2013. if (shouldRemove && action == MOVE) {
  2014. TextTransferable t = (TextTransferable)data;
  2015. t.removeText();
  2016. }
  2017. exportComp = null;
  2018. }
  2019. /**
  2020. * This method causes a transfer to a component from a clipboard or a
  2021. * DND drop operation. The Transferable represents the data to be
  2022. * imported into the component.
  2023. *
  2024. * @param comp The component to receive the transfer. This
  2025. * argument is provided to enable sharing of TransferHandlers by
  2026. * multiple components.
  2027. * @param t The data to import
  2028. * @return true if the data was inserted into the component, false otherwise.
  2029. */
  2030. public boolean importData(JComponent comp, Transferable t) {
  2031. JTextComponent c = (JTextComponent)comp;
  2032. // if we are importing to the same component that we exported from
  2033. // then don't actually do anything if the drop location is inside
  2034. // the drag location and set shouldRemove to false so that exportDone
  2035. // knows not to remove any data
  2036. if (c == exportComp && c.getCaretPosition() >= p0 && c.getCaretPosition() <= p1) {
  2037. shouldRemove = false;
  2038. return true;
  2039. }
  2040. boolean imported = false;
  2041. DataFlavor importFlavor = getImportFlavor(t.getTransferDataFlavors(), c);
  2042. if (importFlavor != null) {
  2043. try {
  2044. boolean useRead = false;
  2045. if (comp instanceof JEditorPane) {
  2046. JEditorPane ep = (JEditorPane)comp;
  2047. if (!ep.getContentType().startsWith("text/plain") &&
  2048. importFlavor.getMimeType().startsWith(ep.getContentType())) {
  2049. useRead = true;
  2050. }
  2051. }
  2052. Reader r = importFlavor.getReaderForText(t);
  2053. handleReaderImport(r, c, useRead);
  2054. imported = true;
  2055. } catch (UnsupportedFlavorException ufe) {
  2056. } catch (BadLocationException ble) {
  2057. } catch (IOException ioe) {
  2058. }
  2059. }
  2060. return imported;
  2061. }
  2062. /**
  2063. * This method indicates if a component would accept an import of the given
  2064. * set of data flavors prior to actually attempting to import it.
  2065. *
  2066. * @param comp The component to receive the transfer. This
  2067. * argument is provided to enable sharing of TransferHandlers by
  2068. * multiple components.
  2069. * @param flavors The data formats available
  2070. * @return true if the data can be inserted into the component, false otherwise.
  2071. */
  2072. public boolean canImport(JComponent comp, DataFlavor[] flavors) {
  2073. JTextComponent c = (JTextComponent)comp;
  2074. if (!(c.isEditable() && c.isEnabled())) {
  2075. return false;
  2076. }
  2077. return (getImportFlavor(flavors, c) != null);
  2078. }
  2079. /**
  2080. * A possible implementation of the Transferable interface
  2081. * for text components. For a JEditorPane with a rich set
  2082. * of EditorKit implementations, conversions could be made
  2083. * giving a wider set of formats. This is implemented to
  2084. * offer up only the active content type and text/plain
  2085. * (if that is not the active format) since that can be
  2086. * extracted from other formats.
  2087. */
  2088. static class TextTransferable extends SynthTransferable {
  2089. TextTransferable(JTextComponent c, int start, int end) {
  2090. super(null, null);
  2091. this.c = c;
  2092. Document doc = c.getDocument();
  2093. try {
  2094. p0 = doc.createPosition(start);
  2095. p1 = doc.createPosition(end);
  2096. plainData = c.getSelectedText();
  2097. if (c instanceof JEditorPane) {
  2098. JEditorPane ep = (JEditorPane)c;
  2099. mimeType = ep.getContentType();
  2100. if (mimeType.startsWith("text/plain")) {
  2101. return;
  2102. }
  2103. StringWriter sw = new StringWriter(p1.getOffset() - p0.getOffset());
  2104. ep.getEditorKit().write(sw, doc, p0.getOffset(), p1.getOffset() - p0.getOffset());
  2105. if (mimeType.startsWith("text/html")) {
  2106. htmlData = sw.toString();
  2107. } else {
  2108. richText = sw.toString();
  2109. }
  2110. }
  2111. } catch (BadLocationException ble) {
  2112. } catch (IOException ioe) {
  2113. }
  2114. }
  2115. void removeText() {
  2116. if ((p0 != null) && (p1 != null) && (p0.getOffset() != p1.getOffset())) {
  2117. try {
  2118. Document doc = c.getDocument();
  2119. doc.remove(p0.getOffset(), p1.getOffset() - p0.getOffset());
  2120. } catch (BadLocationException e) {
  2121. }
  2122. }
  2123. }
  2124. // ---- EditorKit other than plain or HTML text -----------------------
  2125. /**
  2126. * If the EditorKit is not for text/plain or text/html, that format
  2127. * is supported through the "richer flavors" part of SynthTransferable.
  2128. */
  2129. protected DataFlavor[] getRicherFlavors() {
  2130. if (richText == null) {
  2131. return null;
  2132. }
  2133. try {
  2134. DataFlavor[] flavors = new DataFlavor[3];
  2135. flavors[0] = new DataFlavor(mimeType + ";class=java.lang.String");
  2136. flavors[1] = new DataFlavor(mimeType + ";class=java.io.Reader");
  2137. flavors[2] = new DataFlavor(mimeType + ";class=java.io.InputStream;charset=unicode");
  2138. return flavors;
  2139. } catch (ClassNotFoundException cle) {
  2140. // fall through to unsupported (should not happen)
  2141. }
  2142. return null;
  2143. }
  2144. /**
  2145. * The only richer format supported is the file list flavor
  2146. */
  2147. protected Object getRicherData(DataFlavor flavor) throws UnsupportedFlavorException {
  2148. if (richText == null) {
  2149. return null;
  2150. }
  2151. if (String.class.equals(flavor.getRepresentationClass())) {
  2152. return richText;
  2153. } else if (Reader.class.equals(flavor.getRepresentationClass())) {
  2154. return new StringReader(richText);
  2155. } else if (InputStream.class.equals(flavor.getRepresentationClass())) {
  2156. return new StringBufferInputStream(richText);
  2157. }
  2158. throw new UnsupportedFlavorException(flavor);
  2159. }
  2160. Position p0;
  2161. Position p1;
  2162. String mimeType;
  2163. String richText;
  2164. JTextComponent c;
  2165. }
  2166. }
  2167. }