/plugins/AStylePlugin/trunk/org/gjt/sp/jedit/options/BeanOptionPane.java

# · Java · 423 lines · 243 code · 64 blank · 116 comment · 37 complexity · 7c2eefb981b8b613d027818866b1de1e MD5 · raw file

  1. /*
  2. * BeanOptionPane.java - jEdit option panel for a Java bean
  3. * Copyright (C) 2001 Dirk Moebius
  4. *
  5. * :tabSize=4:indentSize=4:noTabs=false:maxLineLen=0:
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. */
  21. package org.gjt.sp.jedit.options;
  22. import java.lang.reflect.Method;
  23. import java.awt.*;
  24. import java.awt.event.*;
  25. import java.beans.*;
  26. import javax.swing.*;
  27. import java.util.Enumeration;
  28. import java.util.Hashtable;
  29. import java.util.StringTokenizer;
  30. import java.util.Vector;
  31. import org.gjt.sp.jedit.jEdit;
  32. import org.gjt.sp.jedit.AbstractOptionPane;
  33. import org.gjt.sp.jedit.MiscUtilities;
  34. import org.gjt.sp.jedit.gui.JMouseComboBox;
  35. import org.gjt.sp.jedit.gui.VariableGridLayout;
  36. import org.gjt.sp.util.Log;
  37. /**
  38. * A jEdit <code>OptionPane</code> for setting the properties of a Java bean.
  39. *
  40. * The option pane shows a tabular list of property editors for each
  41. * bean property:
  42. *
  43. * <ul>
  44. * <li>A <code>JComboBox</code> with the tag values for a tagged property
  45. * (This includes properties of type <code>boolean</code>, because
  46. * boolean fields have the tags "True" and "False" by default).
  47. * <li>A <code>JButton</code> for a property that has a custom editor.
  48. * Clicking on the button invokes the custom editor.
  49. * <li>A <code>JTextField</code> for all other properties.
  50. * </ul>
  51. *
  52. * The following Java bean features are currently not supported:
  53. *
  54. * <ul>
  55. * <li>non-editable properties (will not be shown)
  56. * <li>bound/constrained properties (other values may be entered)
  57. * <li>properties with attribute names
  58. * <ul>
  59. *
  60. * @author Dirk Moebius (<a href="mailto:dmoebius@gmx.net">dmoebius@gmx.net</a>)
  61. * @see java.beans.BeanInfo
  62. * @see java.beans.PropertyDescriptor
  63. */
  64. public class BeanOptionPane extends AbstractOptionPane {
  65. /**
  66. * Creates a new BeanOptionPane using the current class loader.
  67. *
  68. * @param optionsKey prefix for bean properties when loaded and
  69. * saved from/to the jEdit properties.
  70. * @param beanClassName the class name of the bean.
  71. */
  72. public BeanOptionPane( String optionsKey, String beanClassName ) {
  73. this( optionsKey, beanClassName, null );
  74. }
  75. /**
  76. * Creates a new BeanOptionPane using a custom class loader.
  77. *
  78. * @param optionsKey prefix for bean properties when loaded and
  79. * saved from/to the jEdit properties.
  80. * @param beanClassName the class name of the bean.
  81. * @param beanClassLoader the class loader used to construct a bean
  82. * instance for getting its properties.
  83. */
  84. public BeanOptionPane( String optionsKey, String beanClassName, ClassLoader beanClassLoader ) {
  85. super( optionsKey );
  86. this.beanClassName = beanClassName;
  87. this.beanClassLoader = beanClassLoader;
  88. }
  89. public void _init() {
  90. JSplitPane split = new JSplitPane( JSplitPane.VERTICAL_SPLIT, true, getBeanPropertiesPane(), getHelpPane() );
  91. this.setLayout( new BorderLayout( 5, 5 ) );
  92. this.add( split );
  93. }
  94. public void _save() {
  95. for ( Enumeration e = properties.keys(); e.hasMoreElements(); ) {
  96. Component editor = ( Component ) e.nextElement();
  97. PropertyInfo info = ( PropertyInfo ) properties.get( editor );
  98. String value = null;
  99. // Three types of editors are used in this BeanOptionPane:
  100. // - JButton for bean properties with a custom editor
  101. // (the button invokes the custom editor),
  102. // - JComboBox for tagged bean properties,
  103. // - JTextField for untagged bean properties.
  104. if ( editor instanceof JComboBox )
  105. value = ( String ) ( ( JComboBox ) editor ).getSelectedItem();
  106. else if ( editor instanceof JTextField )
  107. value = ( ( JTextField ) editor ).getText();
  108. else if ( editor instanceof JButton )
  109. value = info.editor.getAsText();
  110. else
  111. continue; // ignore - it's a label
  112. String name = info.descriptor.getName();
  113. jEdit.setProperty( super.getName() + "." + name, value );
  114. Log.log( Log.DEBUG, this, "saved property: " + name + "=" + value );
  115. }
  116. }
  117. private JScrollPane getHelpPane() {
  118. helpArea = new JTextArea();
  119. helpArea.setEditable( false );
  120. helpArea.setLineWrap( true );
  121. helpArea.setWrapStyleWord( true );
  122. helpArea.setFont( HELP_FONT );
  123. helpArea.setBackground( HELP_BACKGROUND );
  124. helpArea.setForeground( HELP_FOREGROUND );
  125. helpArea.setText( "" );
  126. JScrollPane scrHelpArea = new JScrollPane( helpArea );
  127. scrHelpArea.setPreferredSize( new Dimension( 300, 50 ) );
  128. return scrHelpArea;
  129. }
  130. private JScrollPane getBeanPropertiesPane() {
  131. JPanel content = new JPanel( new VariableGridLayout( VariableGridLayout.FIXED_NUM_COLUMNS, 2, 10, 3 ) );
  132. JScrollPane scrContent = new JScrollPane( content );
  133. scrContent.setPreferredSize( new Dimension( 300, 300 ) );
  134. try {
  135. // Try to instantiate a beanClass object:
  136. Object beanObject;
  137. try {
  138. beanObject = Beans.instantiate( beanClassLoader, beanClassName );
  139. }
  140. catch ( NoSuchMethodError nsmerr ) {
  141. throw new Exception( "Could not instantiate bean class " + beanClassName + " because default constructor not found!" );
  142. }
  143. catch ( Exception ex ) {
  144. throw new Exception( "Could not instantiate bean class " + beanClassName + ": " + ex );
  145. }
  146. // Try to get BeanInfo (it should be there!):
  147. BeanInfo beanInfo = Introspector.getBeanInfo( beanObject.getClass() );
  148. if ( beanInfo == null ) // cannot work without a BeanInfo:
  149. throw new Exception( "BeanInfo not found for class " + beanClassName + "! Maybe this is not a bean?!?" );
  150. // Try to get customizer class, if available:
  151. BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
  152. if ( beanDescriptor != null ) {
  153. Class customizerClass = beanDescriptor.getCustomizerClass();
  154. if ( customizerClass != null ) {
  155. Customizer customizer = ( Customizer ) customizerClass.newInstance();
  156. if ( !( customizer instanceof Component ) )
  157. throw new Exception( "can't use bean customizer because it is no java.awt.Component" );
  158. // Add bean customizer as the only component:
  159. content.add( ( Component ) customizer );
  160. return scrContent;
  161. }
  162. }
  163. // Get all property descriptions:
  164. PropertyDescriptor[] beanProperties = beanInfo.getPropertyDescriptors();
  165. if ( beanProperties == null ) {
  166. Log.log( Log.WARNING, this, "no bean properties found for bean " + beanClassName );
  167. return scrContent;
  168. }
  169. java.util.Arrays.sort( beanProperties, new FeatureDescriptorComparator() );
  170. // Get all property editors:
  171. BeanHelper beanHelper = new BeanHelper( super.getName(), beanClassName, beanClassLoader );
  172. Hashtable beanPropertyEditors = beanHelper.getPropertyEditors( beanProperties );
  173. // Traverse all properties:
  174. for ( int i = 0; i < beanProperties.length; i++ ) {
  175. PropertyDescriptor pd = beanProperties[ i ];
  176. String name = pd.getName();
  177. String displayName = pd.getDisplayName();
  178. PropertyEditor propertyEditor = ( PropertyEditor ) beanPropertyEditors.get( name );
  179. if ( propertyEditor == null )
  180. continue;
  181. // Try to get value of property from jEdit properties:
  182. String valueText = jEdit.getProperty( super.getName() + "." + name );
  183. if ( valueText != null ) {
  184. propertyEditor.setAsText( valueText );
  185. }
  186. else {
  187. // Get the value from the bean object:
  188. Method method = pd.getReadMethod();
  189. if ( method != null ) {
  190. try {
  191. Object value = method.invoke( beanObject, null );
  192. propertyEditor.setValue( value );
  193. }
  194. catch ( Exception ex ) {
  195. Log.log( Log.ERROR, this, "property " + name + ": could not get value, got exception:" );
  196. Log.log( Log.ERROR, this, ex );
  197. continue;
  198. }
  199. }
  200. else {
  201. Log.log( Log.WARNING, this, "property " + name + ": no read method to get property value" );
  202. }
  203. }
  204. // Add an editor for the property. The editor is either a JTextField,
  205. // a JComboBox or a JButton (for calling a custom editor):
  206. Component editor;
  207. if ( propertyEditor.getCustomEditor() != null ) {
  208. // The property editor has a custom editor:
  209. // need to wrap it in a dialog that's invoked by a button:
  210. JButton button = new JButton( "Customize..." );
  211. button.setActionCommand( name );
  212. button.addActionListener( propertyConfigureAction );
  213. editor = button;
  214. }
  215. else if ( propertyEditor.isPaintable() ) {
  216. Log.log( Log.ERROR, this, "property " + name + ": paintable editors are not supported" );
  217. continue;
  218. }
  219. else {
  220. // Get the property value as text:
  221. String propertyText = propertyEditor.getAsText();
  222. // If the property editor support tags, add a JComboBox of all tags:
  223. String[] tags = propertyEditor.getTags();
  224. if ( tags != null ) {
  225. JComboBox comboBox = new JMouseComboBox( tags );
  226. comboBox.setEditable( false );
  227. comboBox.setSelectedItem( propertyText );
  228. editor = comboBox;
  229. }
  230. else {
  231. JTextField textField = new JTextField( propertyText );
  232. editor = textField;
  233. }
  234. }
  235. // Add the editor for this property:
  236. JLabel label = new JLabel( displayName + ":", SwingConstants.RIGHT );
  237. label.addMouseListener( mouseHandler );
  238. editor.addMouseListener( mouseHandler );
  239. content.add( label );
  240. content.add( editor );
  241. // Remember the property for save() and the mouse handler
  242. // and property configure action:
  243. PropertyInfo info = new PropertyInfo( pd, propertyEditor );
  244. properties.put( label, info );
  245. properties.put( editor, info );
  246. } // for all properties
  247. }
  248. catch ( Exception ex ) {
  249. Log.log( Log.ERROR, this, ex );
  250. }
  251. content.add( Box.createGlue() );
  252. return scrContent;
  253. }
  254. private MouseHandler mouseHandler = new MouseHandler();
  255. private PropertyConfigureAction propertyConfigureAction = new PropertyConfigureAction();
  256. private String beanClassName = null;
  257. private ClassLoader beanClassLoader = null;
  258. // The following members cannot be private because inner classes like
  259. // MouseHandler cannot access private fields if compiled on JDK 1.1.x
  260. // (this is a bug on old JDK's).
  261. /*private*/
  262. JTextArea helpArea;
  263. /*private*/
  264. Hashtable properties = new Hashtable();
  265. private static final Color HELP_BACKGROUND = UIManager.getColor( "Label.background" );
  266. private static final Color HELP_FOREGROUND = UIManager.getColor( "Label.foreground" );
  267. private static final Font HELP_FONT = new Font( UIManager.getFont( "Label.font" ).getName(), UIManager.getFont( "Label.font" ).getStyle(), 11 );
  268. /**
  269. * Displays short descritions for properties in the help area,
  270. * if the mouse is over them.
  271. */
  272. private class MouseHandler extends MouseAdapter {
  273. public void mouseEntered( MouseEvent evt ) {
  274. PropertyInfo info = ( PropertyInfo ) properties.get( evt.getSource() );
  275. helpArea.setText( info.descriptor.getShortDescription() );
  276. helpArea.setCaretPosition( 0 );
  277. }
  278. }
  279. /**
  280. * Action to perform if a property with a "Customize..." button is pressed.
  281. */
  282. private class PropertyConfigureAction implements ActionListener {
  283. public void actionPerformed( ActionEvent evt ) {
  284. PropertyInfo info = ( PropertyInfo ) properties.get( evt.getSource() );
  285. new CustomPropertyEditorDialog( info );
  286. }
  287. }
  288. /**
  289. * Modal dialog for properties with a custom property editor.
  290. */
  291. private class CustomPropertyEditorDialog extends JDialog implements ActionListener {
  292. public CustomPropertyEditorDialog( final PropertyInfo info ) {
  293. super( ( Frame ) null, "Customize " + info.descriptor.getDisplayName(), true );
  294. this.info = info;
  295. origValue = info.editor.getValue();
  296. Component editor = info.editor.getCustomEditor();
  297. ok = new JButton( jEdit.getProperty( "common.ok" ) );
  298. ok.addActionListener( this );
  299. cancel = new JButton( jEdit.getProperty( "common.cancel" ) );
  300. cancel.addActionListener( this );
  301. JPanel buttons = new JPanel( new FlowLayout( FlowLayout.CENTER, 10, 10 ) );
  302. buttons.add( ok );
  303. buttons.add( cancel );
  304. this.getContentPane().setLayout( new BorderLayout( 10, 10 ) );
  305. this.getContentPane().add( editor, BorderLayout.CENTER );
  306. this.getContentPane().add( buttons, BorderLayout.SOUTH );
  307. this.addWindowListener( new WindowAdapter() {
  308. public void windowClosing( WindowEvent e ) {
  309. // Restore original value:
  310. info.editor.setValue( origValue );
  311. }
  312. }
  313. );
  314. this.pack();
  315. this.setLocationRelativeTo( BeanOptionPane.this );
  316. this.setVisible( true );
  317. }
  318. public void actionPerformed( ActionEvent evt ) {
  319. if ( evt.getSource() == ok ) {
  320. this.dispose();
  321. }
  322. else if ( evt.getSource() == cancel ) {
  323. // Restore original value:
  324. info.editor.setValue( origValue );
  325. this.dispose();
  326. }
  327. }
  328. private JButton ok;
  329. private JButton cancel;
  330. private PropertyInfo info;
  331. private Object origValue;
  332. }
  333. /**
  334. * Entry for Hashtable 'properties'.
  335. */
  336. private static class PropertyInfo {
  337. public PropertyDescriptor descriptor;
  338. public PropertyEditor editor;
  339. public PropertyInfo( PropertyDescriptor d, PropertyEditor e ) {
  340. this.descriptor = d;
  341. this.editor = e;
  342. }
  343. }
  344. /**
  345. * A comparator that sorts by the display name of
  346. * <code>java.beans.FeatureDescriptor</code> objects.
  347. * @see java.beans.FeatureDescriptor#getDisplayName()
  348. */
  349. private static class FeatureDescriptorComparator implements java.util.Comparator {
  350. /**
  351. * @exception ClassCastException if obj1 and obj2 are not instances of
  352. * <code>java.beans.FeatureDescriptor</code>.
  353. * @see java.beans.FeatureDescriptor
  354. */
  355. public int compare( Object obj1, Object obj2 ) {
  356. return ( ( FeatureDescriptor ) obj1 ).getDisplayName().compareTo( ( ( FeatureDescriptor ) obj2 ).getDisplayName() );
  357. }
  358. }
  359. }