package org.pentaho.openformula.ui.table; import org.pentaho.openformula.ui.FieldDefinition; import org.pentaho.openformula.ui.FormulaEditorDialog; import org.pentaho.reporting.libraries.base.util.DebugLog; import org.pentaho.reporting.libraries.designtime.swing.EllipsisButton; import org.pentaho.reporting.libraries.designtime.swing.EmptyValueListCellRenderer; import org.pentaho.reporting.libraries.designtime.swing.NonFilteringPlainDocument; import org.pentaho.reporting.libraries.formula.DefaultFormulaContext; import org.pentaho.reporting.libraries.formula.FormulaContext; import org.pentaho.reporting.libraries.formula.util.FormulaUtil; import javax.swing.*; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.EventListenerList; import javax.swing.plaf.basic.BasicComboBoxEditor; import javax.swing.table.TableCellEditor; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.EventObject; public class FormulaFragmentCellEditor extends JPanel implements TableCellEditor { protected class ExtendedEditorAction extends AbstractAction { /** * Defines an <code>Action</code> object with a default description string and default icon. */ protected ExtendedEditorAction() { } /** * Invoked when an action occurs. */ public void actionPerformed( final ActionEvent e ) { final JComboBox comboBox = getComboBox(); final FormulaEditorDialog editorDialog = FormulaFragmentCellEditor.this.createEditorDialog(); final String originalFormula = (String) comboBox.getSelectedItem(); final String formula = editorDialog.editFormula( originalFormula, getFields() ); if ( formula != null ) { comboBox.setSelectedItem( formula ); } stopCellEditing(); } } protected FormulaEditorDialog createEditorDialog() { Window windowAncestor = SwingUtilities.getWindowAncestor( this ); if ( windowAncestor instanceof Dialog ) { return new FormulaEditorDialog( (Dialog) windowAncestor ); } else if ( windowAncestor instanceof Frame ) { return new FormulaEditorDialog( (Frame) windowAncestor ); } else { return new FormulaEditorDialog(); } } protected class SelectionAction extends AbstractAction { /** * Defines an <code>Action</code> object with a default description string and default icon. */ public SelectionAction() { putValue( Action.NAME, EditorMessages.getInstance().getString( "AbstractStringValueCellEditor.SelectValue" ) ); } /** * Invoked when an action occurs. */ public void actionPerformed( final ActionEvent e ) { if ( filterEvents ) { return; } stopCellEditing(); } } protected class CancelAction extends AbstractAction { public CancelAction() { } /** * Invoked when an action occurs. */ public void actionPerformed( final ActionEvent e ) { cancelCellEditing(); } } protected static final String POPUP_EDITOR = "popupEditor"; protected static final FieldDefinition[] EMPTY_FIELDS = new FieldDefinition[ 0 ]; private EventListenerList eventListenerList; private boolean nullable; private JButton ellipsisButton; private JComboBox comboBox; private transient Object originalValue; private volatile boolean filterEvents; private boolean formulaFragment; private FieldDefinition[] fields; private FormulaContext formulaContext; public FormulaFragmentCellEditor() { setLayout( new BorderLayout() ); final Action action = createExtendedEditorAction(); this.eventListenerList = new EventListenerList(); ellipsisButton = new EllipsisButton( "..." ); ellipsisButton.addActionListener( action ); comboBox = new JComboBox(); final ComboBoxEditor boxEditor = comboBox.getEditor(); if ( boxEditor instanceof BasicComboBoxEditor ) { final BasicComboBoxEditor basicComboBoxEditor = (BasicComboBoxEditor) boxEditor; final Object editorComponent = basicComboBoxEditor.getEditorComponent(); if ( editorComponent instanceof JTextField ) { final JTextField editorTextField = (JTextField) editorComponent; editorTextField.setDocument( new NonFilteringPlainDocument() ); } } comboBox.setRenderer( new EmptyValueListCellRenderer() ); comboBox.addActionListener( new SelectionAction() ); comboBox.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), new CancelAction() ); comboBox.getInputMap().put( EditorMessages.getInstance().getKeyStroke ( "AbstractStringValueCellEditor.Popup.Accelerator" ), POPUP_EDITOR ); comboBox.setBorder( BorderFactory.createEmptyBorder() ); comboBox.setEditable( true ); add( comboBox, BorderLayout.CENTER ); add( ellipsisButton, BorderLayout.EAST ); formulaContext = new DefaultFormulaContext(); nullable = false; } public boolean isFormulaFragment() { return formulaFragment; } public void setFormulaFragment( final boolean formulaFragment ) { this.formulaFragment = formulaFragment; } public FormulaContext getFormulaContext() { return formulaContext; } public void setFormulaContext( final FormulaContext formulaContext ) { this.formulaContext = formulaContext; } protected Action createExtendedEditorAction() { return new ExtendedEditorAction(); } public Component getTableCellEditorComponent( final JTable table, final Object value, final boolean isSelected, final int row, final int column ) { return create( value ); } protected Component create( final Object value ) { try { filterEvents = true; final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(); final FieldDefinition[] definitions = getFields(); for ( int i = 0; i < definitions.length; i++ ) { final FieldDefinition fieldDefinition = definitions[ i ]; comboBoxModel.addElement( "=" + FormulaUtil.quoteReference( fieldDefinition.getName() ) ); } comboBox.setModel( comboBoxModel ); comboBox.setRenderer( new EmptyValueListCellRenderer() ); comboBox.setEditable( true ); add( comboBox, BorderLayout.CENTER ); add( ellipsisButton, BorderLayout.EAST ); comboBox.requestFocus(); if ( value == null ) { comboBox.setSelectedItem( null ); } else { String rawFormulaText; if ( isFormulaFragment() == false ) { rawFormulaText = FormulaUtil.extractFormula( String.valueOf( value ) ); } else { rawFormulaText = String.valueOf( value ); } String formulaText = FormulaUtil.createEditorTextFromFormula( rawFormulaText, formulaContext ); comboBox.setSelectedItem( formulaText ); } originalValue = value; return this; } finally { filterEvents = false; } } protected void configureEditorStyle( final Font font, final Color foreground, final Color background ) { comboBox.setFont( font ); comboBox.setForeground( foreground ); comboBox.setBackground( background ); } protected JComboBox getComboBox() { return comboBox; } protected boolean isNullable() { return nullable; } protected void setNullable( final boolean nullable ) { this.nullable = nullable; } public void requestFocus() { comboBox.requestFocus(); } protected JButton getEllipsisButton() { return ellipsisButton; } /** * Returns the value contained in the editor. * * @return the value contained in the editor */ public Object getCellEditorValue() { final Object selectedItem = comboBox.getSelectedItem(); if ( "".equals( selectedItem ) || selectedItem == null ) { return null; } String text = String.valueOf( selectedItem ); if ( text.startsWith( "'" ) ) { text = "=" + FormulaUtil.quoteString( text.substring( 1 ) ); } if ( isFormulaFragment() ) { return FormulaUtil.createFormulaFromUIText( text ); } return text; } /** * Asks the editor if it can start editing using <code>anEvent</code>. <code>anEvent</code> is in the invoking * component coordinate system. The editor can not assume the Component returned by * <code>getCellEditorComponent</code> is installed. This method is intended for the use of client to avoid the cost * of setting up and installing the editor component if editing is not possible. If editing can be started this method * returns true. * * @param anEvent the event the editor should use to consider whether to begin editing or not * @return true if editing can be started */ public boolean isCellEditable( final EventObject anEvent ) { return true; } /** * Returns true if the editing cell should be selected, false otherwise. Typically, the return value is true, because * is most cases the editing cell should be selected. However, it is useful to return false to keep the selection * from changing for some types of edits. eg. A table that contains a column of check boxes, the user might want to be * able to change those checkboxes without altering the selection. (See Netscape Communicator for just such an * example) Of course, it is up to the client of the editor to use the return value, but it doesn't need to if it * doesn't want to. * * @param anEvent the event the editor should use to start editing * @return true if the editor would like the editing cell to be selected; otherwise returns false */ public boolean shouldSelectCell( final EventObject anEvent ) { return true; } /** * Tells the editor to stop editing and accept any partially edited value as the value of the editor. The editor * returns false if editing was not stopped; this is useful for editors that validate and can not accept invalid * entries. * * @return true if editing was stopped; false otherwise */ public boolean stopCellEditing() { try { // ugly hack to make the combobox editor commit any changes before we go out of focus. comboBox.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, comboBox.getActionCommand() ) ); fireEditingStopped(); return true; } catch ( final Exception e ) { DebugLog.log( "Exception caught while editing cell-value", e ); // NON-NLS // exception ignored fireEditingCanceled(); return true; } } /** * Tells the editor to cancel editing and not accept any partially edited value. */ public void cancelCellEditing() { try { filterEvents = true; comboBox.setSelectedItem( originalValue ); } finally { filterEvents = false; } fireEditingCanceled(); } protected void fireEditingCanceled() { final CellEditorListener[] listeners = eventListenerList.getListeners( CellEditorListener.class ); final ChangeEvent event = new ChangeEvent( this ); for ( int i = 0; i < listeners.length; i++ ) { final CellEditorListener listener = listeners[ i ]; listener.editingCanceled( event ); } } protected void fireEditingStopped() { final CellEditorListener[] listeners = eventListenerList.getListeners( CellEditorListener.class ); final ChangeEvent event = new ChangeEvent( this ); for ( int i = 0; i < listeners.length; i++ ) { final CellEditorListener listener = listeners[ i ]; listener.editingStopped( event ); } } /** * Adds a listener to the list that's notified when the editor stops, or cancels editing. * * @param l the CellEditorListener */ public void addCellEditorListener( final CellEditorListener l ) { eventListenerList.add( CellEditorListener.class, l ); } /** * Removes a listener from the list that's notified * * @param l the CellEditorListener */ public void removeCellEditorListener( final CellEditorListener l ) { eventListenerList.remove( CellEditorListener.class, l ); } public FieldDefinition[] getFields() { return fields; } public void setFields( final FieldDefinition[] fields ) { this.fields = fields; } }