Package org.pentaho.openformula.ui

Source Code of org.pentaho.openformula.ui.FormulaEditorPanel$ParameterUpdateHandler

/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2002-2013 Pentaho Corporation..  All rights reserved.
*/

package org.pentaho.openformula.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.border.EmptyBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;

import org.pentaho.openformula.ui.model2.FunctionInformation;
import org.pentaho.openformula.ui.util.FunctionParameterEditHelper;
import org.pentaho.openformula.ui.util.InlineEditTextArea;
import org.pentaho.openformula.ui.util.SelectFieldAction;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.designtime.swing.HorizontalLayout;
import org.pentaho.reporting.libraries.designtime.swing.ToolbarButton;
import org.pentaho.reporting.libraries.formula.DefaultFormulaContext;
import org.pentaho.reporting.libraries.formula.Formula;
import org.pentaho.reporting.libraries.formula.FormulaContext;
import org.pentaho.reporting.libraries.formula.LibFormulaErrorValue;
import org.pentaho.reporting.libraries.formula.function.FunctionDescription;
import org.pentaho.reporting.libraries.formula.lvalues.TypeValuePair;
import org.pentaho.reporting.libraries.formula.parser.ParseException;
import org.pentaho.reporting.libraries.formula.typing.Type;
import org.pentaho.reporting.libraries.formula.typing.TypeUtil;
import org.pentaho.reporting.libraries.formula.util.FormulaUtil;

public class FormulaEditorPanel extends JComponent implements FieldDefinitionSource
{
  private class CaretHandler implements CaretListener
  {
    /**
     * Called when the caret position is updated.
     *
     * @param e the caret event
     */
    public void caretUpdate(final CaretEvent e)
    {
      if (ignoreTextEvents)
      {
        return;
      }

      editorModel.setCaretPosition(functionTextArea.getCaretPosition());
      refreshInformationPanel();
      revalidateParameters(true);
    }

  }

  /**
   * A event handler that keeps the InformationPanel up to date.
   */
  private class FunctionDescriptionUpdateHandler implements PropertyChangeListener, ActionListener
  {
    private FunctionDescriptionUpdateHandler()
    {
    }

    public void propertyChange(final PropertyChangeEvent evt)
    {
      refreshInformationPanel();
    }

    /**
     * Invoked when an action occurs.
     *
     * @noinspection MagicCharacter
     */
    public void actionPerformed(final ActionEvent e)
    {
      final FunctionDescription selectedFunction = functionSelectorPanel.getSelectedValue();
      final StringBuilder b = new StringBuilder(100);
      b.append(selectedFunction.getCanonicalName());
      b.append('(');

      final int count;
      if (selectedFunction.isInfiniteParameterCount())
      {
        count = Math.min(1, selectedFunction.getParameterCount());
      }
      else
      {
        count = selectedFunction.getParameterCount();
      }
      for (int i = 0; i < count; i++)
      {
        if (i > 0)
        {
          b.append(";");
        }
        final Type type = selectedFunction.getParameterType(i);
        b.append(TypeUtil.getParameterType(type, getLocale()));
      }
      b.append(')');

      try
      {
        final Document document = functionTextArea.getDocument();
        final int selectionStart = functionTextArea.getSelectionStart();
        document.remove(selectionStart, functionTextArea.getSelectionEnd() - selectionStart);
        document.insertString(functionTextArea.getCaretPosition(), b.toString(), null);
      }
      catch (BadLocationException e1)
      {
        e1.printStackTrace();
      }
    }
  }

  private class DocumentSyncHandler implements PropertyChangeListener
  {
    private DocumentSyncHandler()
    {
    }

    public void propertyChange(final PropertyChangeEvent evt)
    {
      if ("text".equals(evt.getPropertyName()) == false)
      {
        return;
      }
      if (ignoreTextEvents)
      {
        return;
      }
      run();
    }

    public void run()
    {
      editorModel.setFormulaText(functionTextArea.getText());
      editorModel.setCaretPosition(functionTextArea.getCaretPosition());

      ignoreTextEvents = false;
      revalidateParameters(false);

      revalidateFormulaSyntax();
    }
  }

  public class ParameterUpdateHandler implements ParameterUpdateListener
  {
    private ParameterUpdateHandler()
    {
    }


    public boolean isEmbeddedFunction(final String parameterText)
    {
      // Determine if the parameter is a function (i.e. has '(' and ')').  If so,
      // then figure if the
      if (parameterText != null)
      {
        if (parameterText.contains("(") && parameterText.contains(")"))
        {
          return true;
        }
      }

      return false;
    }

    /**
     * This method gets called after each parameter text has been entered in the
     * parameter field.  If user is manually entering text in formula text-area,
     * then this method is called for each character entered.  If user is entering
     * a formula, the parameter field will not change to the corresponding embedded
     * formula unless user puts their cursor on the formula.
     *
     * @param event
     */
    public synchronized void parameterUpdated(final ParameterUpdateEvent event)
    {
      if (ignoreTextEvents == true)
      {
        return;
      }

      final FunctionInformation fn = editorModel.getCurrentFunction();
      if (fn == null)
      {
        return;
      }

      final FunctionParameterEditHelper.EditResult formulaText =
          FunctionParameterEditHelper.buildFormulaText(event, fn, editorModel.getFormulaText());

      ignoreTextEvents = true;
      // The formula in the formula text-area represents the correct and updated formula text.
      // Rebuild the element nodes based on this new representation.
      editorModel.setFormulaText(formulaText.text);

      // Update for formula text-area
      functionTextArea.setText(formulaText.text);
      functionTextArea.setCaretPosition(formulaText.caretPositionAfterEdit);
      editorModel.setCaretPosition(functionTextArea.getCaretPosition());
      ignoreTextEvents = false;

      revalidateParameters(false);
      revalidateFormulaSyntax();
    }
  }

  private class FieldSelectorListener implements PropertyChangeListener
  {
    private FieldSelectorListener()
    {
    }

    /**
     * This method gets called when a bound property is changed.
     *
     * @param evt A PropertyChangeEvent object describing the event source
     *            and the property that has changed.
     */
    public void propertyChange(final PropertyChangeEvent evt)
    {
      final FieldDefinition value = (FieldDefinition) evt.getNewValue();
      final String text = FormulaUtil.quoteReference(value.getName());
      insertText(text);
    }
  }

  private class InsertOperatorAction extends AbstractAction
  {
    private String symbol;
    private static final int IMAGE_SIZE = 16;

    private InsertOperatorAction(final String symbol,
                                 final String description)
    {
      this.symbol = symbol;
      putValue(Action.SMALL_ICON, createImage(symbol));
      putValue(Action.SHORT_DESCRIPTION, description);
    }

    /**
     * Invoked when an action occurs.
     */
    public void actionPerformed(final ActionEvent e)
    {
      insertText(symbol);
    }

    private ImageIcon createImage(final String symbol)
    {
      final BufferedImage bi = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
      final Graphics graphics = bi.getGraphics();
      final Rectangle2D stringBounds = graphics.getFontMetrics().getStringBounds(symbol, graphics);
      final int xspace = (int) Math.max(IMAGE_SIZE - stringBounds.getWidth(), 0);
      final int yspace = (int) Math.max(IMAGE_SIZE - stringBounds.getHeight(), 0);
      graphics.setColor(Color.BLACK);
      final double y2 = stringBounds.getY();
      final int y1 = (int) ((yspace / 2) - y2);
      graphics.drawString(symbol, xspace / 2, y1);
      graphics.dispose();
      return new ImageIcon(bi);
    }
  }

  private boolean ignoreTextEvents;

  private FunctionListPanel functionSelectorPanel;
  private MultiplexFunctionParameterEditor functionParameterEditor;
  private FunctionInformationPanel functionInformationPanel;

  private FormulaContext formulaContext;
  private InlineEditTextArea functionTextArea;
  private JLabel errorTextHolder;
  private JLabel errorIconHolder;
  private FieldDefinition[] fields;
  private FormulaEditorModel editorModel;
  private ImageIcon errorIcon;
  private SelectFieldAction selectFieldsAction;
  private JToolBar operatorPanel;
  private DocumentSyncHandler docSyncHandler;
  private ParameterUpdateHandler parameterUpdateHandler;

  public FormulaEditorPanel()
  {
    init();
  }

  public FormulaEditorModel getEditorModel()
  {
    return editorModel;
  }

  public DocumentSyncHandler getDocSyncHandler()
  {
    return docSyncHandler;
  }

  public void setDocSyncHandler(final DocumentSyncHandler docSyncHandler)
  {
    this.docSyncHandler = docSyncHandler;
  }

  protected MultiplexFunctionParameterEditor getFunctionParameterEditor()
  {
    return functionParameterEditor;
  }

  protected void insertText(final String text)
  {
    final int start = functionTextArea.getCaretPosition();
    final String formulaTextOriginal = editorModel.getFormulaText();
    final StringBuilder formulaText = new StringBuilder(formulaTextOriginal);

    // Ensure that only one equal sign in first cursor position exists.
    int textLength = text.length();
    if ("=".equals(formulaTextOriginal))
    {
      if (text.startsWith("="))
      {
        formulaText.append(text.substring(1));
        textLength--;
      }
      else
      {
        formulaText.append(text);
      }
    }
    else
    {
      String formulaFrag = text;
      if ((formulaTextOriginal.length() == 0) && (start == 0))
      {
        formulaFrag = "=" + text;
      }

      formulaText.insert(start, formulaFrag);
    }


    ignoreTextEvents = true;
    editorModel.setFormulaText(formulaText.toString());
    functionTextArea.setText(formulaText.toString());
    ignoreTextEvents = false;

    functionTextArea.setCaretPosition(textLength + start);
    functionTextArea.requestFocus();
    revalidateParameters(false);
    revalidateFormulaSyntax();
  }


  public JToolBar getOperatorPanel()
  {
    return operatorPanel;
  }

  public void setEditor(final String function, final FunctionParameterEditor editor)
  {
    functionParameterEditor.setEditor(function, editor);
  }

  public FunctionParameterEditor getEditor(final String function)
  {
    return functionParameterEditor.getEditor(function);
  }

  public JTextArea getFunctionTextArea()
  {
    return functionTextArea;
  }

  protected void init()
  {
    editorModel = new FormulaEditorModel();

    functionInformationPanel = new FunctionInformationPanel();
    functionParameterEditor = new MultiplexFunctionParameterEditor();

    parameterUpdateHandler = new ParameterUpdateHandler();
    functionParameterEditor.addParameterUpdateListener(parameterUpdateHandler);

    functionTextArea = new InlineEditTextArea();
    this.setDocSyncHandler(new DocumentSyncHandler());
    functionTextArea.addPropertyChangeListener("text", getDocSyncHandler());
    functionTextArea.setRows(6);
    functionTextArea.addCaretListener(new CaretHandler());
    functionTextArea.setFont
        (new Font(Font.MONOSPACED, functionTextArea.getFont().getStyle(), functionTextArea.getFont().getSize()));

    formulaContext = new DefaultFormulaContext();

    functionSelectorPanel = new FunctionListPanel();
    functionSelectorPanel.addPropertyChangeListener("selectedValue", new FunctionDescriptionUpdateHandler()); // NON-NLS
    functionSelectorPanel.addActionListener(new FunctionDescriptionUpdateHandler());
    functionSelectorPanel.setFormulaContext(this.formulaContext);

    errorIcon = new ImageIcon(getClass().getResource("/org/pentaho/openformula/ui/images/error.gif")); // NON-NLS
    errorIconHolder = new JLabel();
    errorTextHolder = new JLabel();
    errorTextHolder.setName("errorTextHolder");

    selectFieldsAction = new SelectFieldAction(this, new FieldSelectorListener(), this);

    final JSplitPane functionPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
    functionPanel.setTopComponent(functionParameterEditor.getEditorComponent());
    functionPanel.setBottomComponent(buildFormulaTextPanel());
    functionPanel.setBorder(new EmptyBorder(0, 0, 0, 0));

    setLayout(new BorderLayout());
    setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    add(functionSelectorPanel, BorderLayout.WEST);
    add(functionInformationPanel, BorderLayout.SOUTH);
    add(functionPanel, BorderLayout.CENTER);
  }

  private JComponent buildFormulaTextPanel()
  {
    operatorPanel = createOperatorPanel();

    final JPanel textPanel = new JPanel(new BorderLayout());
    textPanel.setLayout(new GridBagLayout());

    GridBagConstraints gbc = new GridBagConstraints();
    gbc.gridx = 0;
    gbc.gridy = 0;
    gbc.gridwidth = 3;
    gbc.fill = GridBagConstraints.BOTH;
    gbc.insets = new Insets(5, 0, 5, 0);
    textPanel.add(operatorPanel, gbc);

    gbc = new GridBagConstraints();
    gbc.gridx = 0;
    gbc.gridy = 1;
    textPanel.add(new JLabel(Messages.getInstance().getString("FormulaEditorDialog.Formula")), gbc);

    gbc = new GridBagConstraints();
    gbc.gridx = 1;
    gbc.gridy = 1;
    textPanel.add(errorIconHolder, gbc);

    gbc = new GridBagConstraints();
    gbc.gridx = 2;
    gbc.gridy = 1;
    gbc.fill = GridBagConstraints.BOTH;
    textPanel.add(errorTextHolder, gbc);

    gbc = new GridBagConstraints();
    gbc.gridx = 0;
    gbc.gridy = 2;
    gbc.gridwidth = 3;
    gbc.weightx = 1;
    gbc.weighty = 1;
    gbc.fill = GridBagConstraints.BOTH;
    textPanel.add(new JScrollPane(functionTextArea), gbc);
    return textPanel;
  }

  protected JToolBar createOperatorPanel()
  {
    final JToolBar operatorButtonPanel = new JToolBar();
    operatorButtonPanel.setFloatable(false);
    operatorButtonPanel.setOpaque(false);
    operatorButtonPanel.setLayout(new HorizontalLayout(2));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("+", Messages.getInstance().getString("FormulaEditorDialog.Operator.Add"))));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("-", Messages.getInstance().getString("FormulaEditorDialog.Operator.Subtract"))));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("*", Messages.getInstance().getString("FormulaEditorDialog.Operator.Multiply"))));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("/", Messages.getInstance().getString("FormulaEditorDialog.Operator.Divide"))));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("^", Messages.getInstance().getString("FormulaEditorDialog.Operator.Power"))));
    operatorButtonPanel.add(Box.createRigidArea(new Dimension(10, 1)));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("=", Messages.getInstance().getString("FormulaEditorDialog.Operator.Equal"))));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("<>", Messages.getInstance().getString("FormulaEditorDialog.Operator.NotEqual"))));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("<", Messages.getInstance().getString("FormulaEditorDialog.Operator.Lesser"))));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction(">", Messages.getInstance().getString("FormulaEditorDialog.Operator.Greater"))));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("<=", Messages.getInstance().getString("FormulaEditorDialog.Operator.LesserEqual"))));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction(">=", Messages.getInstance().getString("FormulaEditorDialog.Operator.GreaterEqual"))));
    operatorButtonPanel.add(Box.createRigidArea(new Dimension(10, 1)));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("%", Messages.getInstance().getString("FormulaEditorDialog.Operator.Percentage"))));
    operatorButtonPanel.add(new ToolbarButton
        (new InsertOperatorAction("&", Messages.getInstance().getString("FormulaEditorDialog.Operator.Concatenation"))));
    operatorButtonPanel.add(Box.createRigidArea(new Dimension(10, 1)));
    operatorButtonPanel.add(new ToolbarButton(selectFieldsAction));
    return operatorButtonPanel;
  }

  public ParameterUpdateHandler getParameterUpdateHandler()
  {
    return parameterUpdateHandler;
  }

  public String getFormulaText()
  {
    return functionTextArea.getText();
  }

  public void setFormulaText(String formulaText)
  {
    if ((formulaText == null) || (formulaText.length() == 0))
    {
      formulaText = "=";
    }
    else if (formulaText.startsWith("=") == false)
    {
      formulaText = "=" + formulaText;
    }

    this.functionTextArea.setText(formulaText);
    this.functionTextArea.setCaretPosition(formulaText.length());

    // Update model
    editorModel.setFormulaText(formulaText);
    editorModel.setCaretPosition(functionTextArea.getCaretPosition());

    // Revalidate parameters and force refresh of parameter fields
    revalidateParameters(true);
  }

  public void setFields(final FieldDefinition[] fields)
  {
    if (fields == null)
    {
      throw new NullPointerException();
    }
    this.fields = fields.clone();
    this.functionParameterEditor.setFields(fields);
  }

  public FieldDefinition[] getFields()
  {
    return fields.clone();
  }

  /**
   * Re-validate the parameters of the selected formula.
   *
   * @param switchParameterEditor - if true, then the parameter editor will adjust to correspond to
   *                              formula in the formula text-area.  This prevents parameter editor from
   *                              changing while user is entering an embedded formula.
   * @noinspection MagicCharacter
   */
  protected void revalidateParameters(final boolean switchParameterEditor)
  {
    editorModel.revalidateStructure();
    if (formulaContext == null)
    {
      functionParameterEditor.clearSelectedFunction();
      return;
    }
    final FunctionInformation fnInfo = editorModel.getCurrentFunction();
    if (fnInfo == null)
    {
      functionParameterEditor.clearSelectedFunction();
      return;
    }
    final FunctionDescription fnDesc = formulaContext.getFunctionRegistry().getMetaData(fnInfo.getCanonicalName());
    if (fnDesc == null)
    {
      functionParameterEditor.clearSelectedFunction();
      return;
    }

    functionInformationPanel.setSelectedFunction(fnDesc);

    try
    {
      ignoreTextEvents = true;
      functionParameterEditor.setSelectedFunction(new FunctionParameterContext
          (fnDesc, fnInfo, switchParameterEditor, editorModel));
    }
    finally
    {
      ignoreTextEvents = false;
    }
  }

  private void refreshInformationPanel()
  {
    final FunctionInformation currentFunction = editorModel.getCurrentFunction();

    final FunctionDescription description;
    if (currentFunction != null)
    {
      description = formulaContext.getFunctionRegistry().getMetaData(currentFunction.getCanonicalName());
    }
    else
    {
      description = functionSelectorPanel.getSelectedValue();
    }
    functionInformationPanel.setSelectedFunction(description);
  }


  protected void revalidateFormulaSyntax()
  {
    try
    {
      final String rawFormula = editorModel.getFormulaText();
      if (StringUtils.isEmpty(rawFormula))
      {
        errorTextHolder.setText("");
        errorTextHolder.setToolTipText(null);
        errorIconHolder.setIcon(null);
        return;
      }
      final String formulaText = FormulaUtil.extractFormula(rawFormula);
      if (StringUtils.isEmpty(formulaText))
      {
        errorTextHolder.setText(Messages.getInstance().getString("FormulaEditorDialog.ShortErrorNoFormulaContext"));
        errorTextHolder.setToolTipText(Messages.getInstance().getString("FormulaEditorDialog.ErrorNoFormulaContext"));
        return;
      }
      final Formula formula = new Formula(formulaText);
      formula.initialize(formulaContext);
      final TypeValuePair pair = formula.evaluateTyped();

      if (pair.getValue() instanceof LibFormulaErrorValue)
      {
        errorTextHolder.setText(Messages.getInstance().getString("FormulaEditorDialog.ShortEvaluationError"));
        errorTextHolder.setToolTipText(Messages.getInstance().getString("FormulaEditorDialog.EvaluationError"));
      }
      else
      {
        errorTextHolder.setToolTipText(null);
        errorTextHolder.setText(Messages.getInstance().getString("FormulaEditorDialog.EvaluationResult", String.valueOf(pair.getValue())));
      }
      errorIconHolder.setIcon(null);
    }
    catch (ParseException pe)
    {
      errorIconHolder.setIcon(errorIcon);
      if (pe.currentToken == null)
      {
        errorTextHolder.setText(Messages.getInstance().getString("FormulaEditorDialog.ShortParseError"));
        errorTextHolder.setToolTipText(Messages.getInstance().getString("FormulaEditorDialog.GenericParseError", pe.getLocalizedMessage()));
      }
      else
      {
        final String token = pe.currentToken.toString();
        final int line = pe.currentToken.beginLine;
        final int column = pe.currentToken.beginColumn;
        errorTextHolder.setText(Messages.getInstance().getString("FormulaEditorDialog.ShortParseError"));
        errorTextHolder.setToolTipText(Messages.getInstance().getString("FormulaEditorDialog.ParseError",
            new Object[]{token, line, column}));
      }
    }
    catch (Exception e)
    {
      errorIconHolder.setIcon(errorIcon);
      errorTextHolder.setText(Messages.getInstance().getString("FormulaEditorDialog.ShortParseError"));
      errorTextHolder.setToolTipText(Messages.getInstance().getString("FormulaEditorDialog.GenericParseError", e.getLocalizedMessage()));
    }
  }
}
TOP

Related Classes of org.pentaho.openformula.ui.FormulaEditorPanel$ParameterUpdateHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.